File size: 7,244 Bytes
efdb2b1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
// libFuzzer harness for Program::load_method() + Method::execute().
// Registers a fake kernel (for every op name that might appear) that ALWAYS
// calls context.fail(), to deterministically exercise the KernelCall failure
// logging path in Method::execute_instruction() - specifically testing
// whether op->overload()->c_str() is dereferenced unconditionally when
// overload is null (optional per schema).
//
// Also registers a fake backend (matching common backend id strings) whose
// init()/execute() are minimal, to exercise BackendDelegate::Init() and
// GetProcessedData() paths without requiring a real backend implementation.
#include <cstddef>
#include <cstdint>
#include <vector>

#include <executorch/extension/data_loader/buffer_data_loader.h>
#include <executorch/runtime/backend/interface.h>
#include <executorch/runtime/executor/method.h>
#include <executorch/runtime/executor/program.h>
#include <executorch/runtime/core/memory_allocator.h>
#include <executorch/runtime/core/hierarchical_allocator.h>
#include <executorch/runtime/executor/memory_manager.h>
#include <executorch/runtime/kernel/operator_registry.h>
#include <executorch/runtime/kernel/kernel_runtime_context.h>
#include <executorch/runtime/platform/runtime.h>

using executorch::extension::BufferDataLoader;
using executorch::runtime::Program;
using executorch::runtime::Method;
using executorch::runtime::MemoryAllocator;
using executorch::runtime::HierarchicalAllocator;
using executorch::runtime::MemoryManager;
using executorch::runtime::Span;
using executorch::runtime::EValue;
using executorch::runtime::Error;
using executorch::runtime::Result;
using executorch::runtime::KernelRuntimeContext;
using executorch::runtime::Kernel;
using executorch::runtime::register_kernels;
using executorch::runtime::BackendInterface;
using executorch::runtime::Backend;
using executorch::runtime::register_backend;
using executorch::runtime::DelegateHandle;
using executorch::runtime::FreeableBuffer;
using executorch::runtime::ArrayRef;
using executorch::runtime::CompileSpec;
using executorch::runtime::BackendInitContext;
using executorch::runtime::BackendExecutionContext;

// A kernel that always fails - forces Method::execute_instruction()'s
// KernelCall failure-logging path (which dereferences op->overload()) on
// EVERY KernelCall instruction, regardless of which operator is named.
void always_fail_kernel(KernelRuntimeContext& context, Span<EValue*> /*args*/) {
  context.fail(Error::Internal);
}

// A minimal backend that succeeds trivially, to exercise
// BackendDelegate::Init()/GetProcessedData() without needing a real backend.
class FuzzTestBackend final : public BackendInterface {
 public:
  bool is_available() const override {
    return true;
  }
  Result<DelegateHandle*> init(
      BackendInitContext& /*context*/,
      FreeableBuffer* /*processed*/,
      ArrayRef<CompileSpec> /*compile_specs*/) const override {
    return static_cast<DelegateHandle*>(nullptr);
  }
  Error execute(
      BackendExecutionContext& /*context*/,
      DelegateHandle* /*handle*/,
      Span<EValue*> /*args*/) const override {
    return Error::Ok;
  }
};

static bool g_initialized = false;
static FuzzTestBackend* g_backend = nullptr;

extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, std::size_t size) {
  if (!g_initialized) {
    executorch::runtime::runtime_init();

    // Register a wildcard-ish set of fallback kernels for common op names
    // seen in the seed corpus, all pointing to always_fail_kernel. We can't
    // truly wildcard-match in this registry, so register kernels for the
    // most common ops that appear in the existing .pte seed corpus.
    static const Kernel kernels[] = {
        Kernel("aten::add.out", always_fail_kernel),
        Kernel("aten::add.Tensor_out", always_fail_kernel),
        Kernel("aten::mul.out", always_fail_kernel),
        Kernel("aten::mul.Tensor_out", always_fail_kernel),
        Kernel("aten::sub.out", always_fail_kernel),
        Kernel("aten::relu.out", always_fail_kernel),
        Kernel("aten::linear.out", always_fail_kernel),
        Kernel("aten::cat.out", always_fail_kernel),
        Kernel("aten::view_copy.out", always_fail_kernel),
        Kernel("aten::index.Tensor_out", always_fail_kernel),
        Kernel("aten::_softmax.out", always_fail_kernel),
        Kernel("aten::convolution.out", always_fail_kernel),
        Kernel("aten::permute_copy.out", always_fail_kernel),
        Kernel("aten::_to_copy.out", always_fail_kernel),
        // No-overload variants: matches Operator entries whose `overload`
        // field is null/absent, which is the specific condition needed to
        // test whether Method::execute_instruction()'s KernelCall failure
        // path dereferences op->overload() unconditionally.
        Kernel("aten::add", always_fail_kernel),
        Kernel("aten::mul", always_fail_kernel),
        Kernel("aten::sub", always_fail_kernel),
        Kernel("aten::relu", always_fail_kernel),
        Kernel("aten::linear", always_fail_kernel),
        Kernel("aten::cat", always_fail_kernel),
        Kernel("test_op", always_fail_kernel),
        Kernel("test_op_no_overload", always_fail_kernel),
    };
    register_kernels({kernels, sizeof(kernels) / sizeof(kernels[0])});

    static FuzzTestBackend backend_instance;
    g_backend = &backend_instance;
    static const char* backend_names[] = {
        "XnnpackBackend", "CoreMLBackend", "QnnBackend",
        "VulkanBackend", "MPSBackend", "TCE0", "TCE1"};
    for (const char* name : backend_names) {
      register_backend(Backend{name, g_backend});
    }

    g_initialized = true;
  }

  constexpr std::size_t kMaxInput = 8U * 1024U * 1024U;
  if (data == nullptr || size == 0 || size > kMaxInput) {
    return 0;
  }

  BufferDataLoader loader(data, size);
  auto program = Program::load(&loader, Program::Verification::InternalConsistency);
  if (!program.ok()) {
    return 0;
  }

  static std::vector<uint8_t> method_pool(512 * 1024);
  static std::vector<uint8_t> planned_pool(256 * 1024);
  MemoryAllocator method_allocator(method_pool.size(), method_pool.data());
  static Span<uint8_t> planned_span(planned_pool.data(), planned_pool.size());
  HierarchicalAllocator planned_memory({&planned_span, 1});
  MemoryManager mm(&method_allocator, &planned_memory, nullptr);

  auto& p = program.get();
  auto n = p.num_methods();
  for (size_t i = 0; i < n; ++i) {
    auto name = p.get_method_name(i);
    if (!name.ok()) continue;
    auto method = p.load_method(name.get(), &mm);
    if (!method.ok()) {
#ifdef ET_FUZZ_DEBUG
      fprintf(stderr, "[dbg] load_method failed: 0x%x\n",
              static_cast<unsigned int>(method.error()));
#endif
      continue;
    }
    // Attempt to set trivial inputs where possible, then execute. We don't
    // try hard to satisfy every possible input shape - we're hunting for
    // crashes during initialization and the KernelCall failure path, not
    // testing correct numerical execution.
    auto& m = method.get();
    auto exec_err = m.execute();
#ifdef ET_FUZZ_DEBUG
    fprintf(stderr, "[dbg] execute() returned: 0x%x\n",
            static_cast<unsigned int>(exec_err));
#endif
    (void)exec_err;
  }
  return 0;
}