// 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 #include #include #include #include #include #include #include #include #include #include #include #include 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 /*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 init( BackendInitContext& /*context*/, FreeableBuffer* /*processed*/, ArrayRef /*compile_specs*/) const override { return static_cast(nullptr); } Error execute( BackendExecutionContext& /*context*/, DelegateHandle* /*handle*/, Span /*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 method_pool(512 * 1024); static std::vector planned_pool(256 * 1024); MemoryAllocator method_allocator(method_pool.size(), method_pool.data()); static Span 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(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(exec_err)); #endif (void)exec_err; } return 0; }