File size: 7,244 Bytes
b009429 | 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;
}
|