tsk-arasu's picture
Upload poc/harness_execute_fuzzer.cpp with huggingface_hub
efdb2b1 verified
Raw
History Blame Contribute Delete
7.24 kB
// 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;
}