Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
internal_call.fuzzer.cpp
Go to the documentation of this file.
2#include <array>
3#include <cassert>
4#include <cstddef>
5#include <cstdint>
6#include <fuzzer/FuzzedDataProvider.h>
7#include <memory>
8#include <string>
9#include <vector>
10
28
29using namespace bb::avm2::simulation;
30using namespace bb::avm2::tracegen;
31using namespace bb::avm2::constraining;
32using namespace bb::avm2::fuzzing;
33
34using bb::avm2::FF;
37
39
40const uint8_t max_flat_calls = 5;
41const uint8_t max_nested_calls = 5;
43
44// Constant instructions:
45const auto call_instr =
46 bb::avm2::testing::InstructionBuilder(WireOpCode::INTERNALCALL)
47 .operand<uint32_t>(10)
48 .build(); // TODO(MW): This operand value is ignored here since we don't test mem/addressing here
49const auto ret_instr = bb::avm2::testing::InstructionBuilder(WireOpCode::INTERNALRETURN).build();
51 .operand<uint8_t>(0)
52 .operand<uint8_t>(0)
53 .operand<uint8_t>(0)
54 .build();
55
57 uint32_t start_pc = 0;
58 // Number i where we have (INTERNALCALL -> (INTERNALCALL -> INTERNALRETURN) xj -> INTERNALRETURN) xi
59 uint8_t num_flat_calls = 1;
60 // Number j where we have (INTERNALCALL -> (INTERNALCALL -> INTERNALRETURN) xj -> INTERNALRETURN) xi
61 uint8_t num_nested_calls = 0;
62 bool extra_pop = false;
63
64 std::array<uint32_t, max_total_calls> local_pcs{};
65
67
68 void print() const
69 {
70 info("start_pc: ", start_pc);
71 info("num_flat_calls: ", int(num_flat_calls));
72 info("num_nested_calls: ", int(num_nested_calls));
73 info("extra_pop: ", extra_pop);
74 for (size_t i = 0; i < local_pcs.size(); i++) {
75 if (local_pcs[i] != 0) {
76 info("local_pcs ", i, ": ", local_pcs[i]);
77 }
78 }
79 }
80
81 void to_buffer(uint8_t* buffer) const
82 {
83 size_t offset = 0;
85 offset += sizeof(start_pc);
87 offset += sizeof(num_flat_calls);
89 offset += sizeof(num_nested_calls);
91 offset += sizeof(extra_pop);
92 std::memcpy(buffer + offset, &local_pcs[0], sizeof(uint32_t) * local_pcs.size());
93 }
94
96 {
98 size_t offset = 0;
99 std::memcpy(&input.start_pc, buffer + offset, sizeof(input.start_pc));
100 offset += sizeof(input.start_pc);
101 std::memcpy(&input.num_flat_calls, buffer + offset, sizeof(input.num_flat_calls));
102 offset += sizeof(input.num_flat_calls);
104 offset += sizeof(input.num_nested_calls);
105 std::memcpy(&input.extra_pop, buffer + offset, sizeof(input.extra_pop));
106 offset += sizeof(input.extra_pop);
107 std::memcpy(&input.local_pcs[0], buffer + offset, sizeof(uint32_t) * input.local_pcs.size());
108
109 return input;
110 }
111};
112
113extern "C" size_t LLVMFuzzerCustomMutator(uint8_t* data, size_t size, size_t max_size, unsigned int seed)
114{
115 if (size < sizeof(InternalCallFuzzerInput)) {
116 // Initialize with default input
118 input.to_buffer(data);
119 return sizeof(InternalCallFuzzerInput);
120 }
121 std::mt19937 rng(seed);
122
123 // Deserialize current input
125 size_t num_events =
126 static_cast<size_t>(input.num_flat_calls) * (input.num_nested_calls == 0 ? 1 : input.num_nested_calls);
127
128 // Choose random mutation
129 std::uniform_int_distribution<int> mutation_dist(0, 4);
130 int mutation_choice = mutation_dist(rng);
131
132 switch (mutation_choice) {
133 case 0: {
134 // Modify number of flat internal calls
136 input.num_flat_calls = num_flat_calls_dist(rng);
137 break;
138 }
139 case 1: {
140 // Modify number of nested internal calls
142 input.num_nested_calls = num_nested_calls_dist(rng);
143 break;
144 }
145 case 2: {
146 // Modify initial context pc
148 0, std::numeric_limits<uint32_t>::max() - uint32_t(num_events));
149 input.start_pc = start_pc_dist(rng);
150 break;
151 }
152 case 3: {
153 // Modify a random local pc (using num_events to ensure it's used in a run)
154 std::uniform_int_distribution<size_t> index_dist(0, num_events == 0 ? 0 : num_events - 1);
155 size_t value_idx = index_dist(rng);
156 std::uniform_int_distribution<uint32_t> pc_dist(0, std::numeric_limits<uint32_t>::max() - uint32_t(num_events));
157 input.local_pcs[value_idx] = pc_dist(rng);
158 break;
159 }
160 case 4: {
161 // Toggle testing error case where we try to pop off an empty stack (just for gadget coverage):
162 input.extra_pop = !input.extra_pop;
163 break;
164 }
165 default:
166 break;
167 }
168 // Serialize mutated input back to buffer
169 input.to_buffer(data);
170
171 if (max_size > sizeof(InternalCallFuzzerInput)) {
172 return sizeof(InternalCallFuzzerInput);
173 }
174
175 return sizeof(InternalCallFuzzerInput);
176}
177
178// NOTE: context->serialize_context_event() causes stack overflow :(
180 InternalCallStackManagerInterface& internal_call_stack_manager)
181{
182 return {
183 .id = context->get_context_id(),
184 .pc = context->get_pc(),
185 .internal_call_id = internal_call_stack_manager.get_call_id(),
186 .internal_call_return_id = internal_call_stack_manager.get_return_call_id(),
187 .next_internal_call_id = internal_call_stack_manager.get_next_call_id(),
188 };
189}
190
193 InternalCallStackManagerInterface& internal_call_stack_manager,
194 uint32_t loc)
195{
197 .before_context_event = fill_context_event(context, internal_call_stack_manager) };
198 // Execution.execute pre-dispatch:
199 context->set_next_pc(context->get_pc() + static_cast<uint32_t>(call_instr.size_in_bytes()));
200
201 // Execution.internal_call(context, loc) - internal_call_stack_manager.push() emits the internal call stack
202 // event:
203 internal_call_stack_manager.push(context->get_pc(), context->get_next_pc());
204 context->set_next_pc(loc);
205
206 // Execution.execute post-dispatch:
207 context->set_pc(context->get_next_pc());
208
209 ex_event.after_context_event = fill_context_event(context, internal_call_stack_manager);
210 ex_events.push_back(ex_event);
211}
212
215 InternalCallStackManagerInterface& internal_call_stack_manager)
216{
218 .before_context_event = fill_context_event(context, internal_call_stack_manager) };
219 // Execution.execute pre-dispatch:
220 context->set_next_pc(context->get_pc() + static_cast<uint32_t>(ret_instr.size_in_bytes()));
221
222 // Execution.internal_return(context):
223 try {
224 auto next_pc = internal_call_stack_manager.pop();
225 context->set_next_pc(next_pc);
226 } catch (const InternalCallStackException& e) {
227 // Do post-dispatch error handling from Execution.execute
228 ex_event.error = ExecutionError::OPCODE_EXECUTION;
229 context->set_gas_used(context->get_gas_limit()); // Consume all gas.
230 context->halt();
231 // In Execution.execute, we do the above then continue without throwing. Here I want to re-throw to pass
232 // the opcode error out of the loop, so repeating the post-dispatch code below (TODO(MW): cleanup):
233 // Execution.execute post-dispatch:
234 context->set_pc(context->get_next_pc());
235 ex_event.after_context_event = fill_context_event(context, internal_call_stack_manager);
236 ex_events.push_back(ex_event);
237 // Re-throw
238 throw OpcodeExecutionException("Internal return failed: " + std::string(e.what()));
239 }
240
241 // Execution.execute post-dispatch:
242 context->set_pc(context->get_next_pc());
243 ex_event.after_context_event = fill_context_event(context, internal_call_stack_manager);
244 ex_events.push_back(ex_event);
245}
246
247extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
248{
250
251 if (size < sizeof(InternalCallFuzzerInput)) {
252 return 0;
253 }
254
256 bool error = false;
257
258 // Set up gadgets and event emitters
259 GadgetFuzzerContextHelper context_helper;
260 auto context = context_helper.make_enqueued_fuzzing_context();
261 auto& internal_call_stack_manager = context->get_internal_call_stack_manager();
262 context->set_pc(input.start_pc);
263
264 // TODO(MW): Can also:
265 // 1. make_enqueued_context(fuzzed data) via context_helper
266 // 2. run Execution::execute on it to emit execution event
267 // 3. builder.process(ex_event) & usual interaction checks
268 // NOTE: context->serialize_context_event() causes stack overflow :( (also, setting up bytecode so instruction
269 // reading works is very involved...)
270
271 // Instead, building an execution event with the relevant internal call fields:
273
274 try {
275 size_t current_call_idx = 0;
276 for (auto i = 0; i < input.num_flat_calls; i++) {
277 fuzz_internal_call(ex_events, context, internal_call_stack_manager, input.local_pcs[current_call_idx++]);
278 for (auto j = 0; j < input.num_nested_calls; j++) {
280 ex_events, context, internal_call_stack_manager, input.local_pcs[current_call_idx++]);
281 fuzz_internal_return(ex_events, context, internal_call_stack_manager);
282 }
283 fuzz_internal_return(ex_events, context, internal_call_stack_manager);
284 }
285 // Handle popping from empty stack error:
286 if (input.extra_pop) {
287 fuzz_internal_return(ex_events, context, internal_call_stack_manager);
288 }
289 } catch (const OpcodeExecutionException& e) {
290 // May be recoverable with sel_opcode_error
291 error = true;
292 }
293
294 assert(internal_call_stack_manager.get_current_call_stack().size() == 0);
295
296 if (!error) {
297 // Ideally I would set the final row via a gadget or at least an event, but I'm not sure how these
298 // are actually set in the standard flow:
299 ex_events.push_back(
300 { .wire_instruction = dummy_instr,
301 .inputs = { MemoryValue::from(FF(0)), MemoryValue::from(FF(0)), MemoryValue::from(FF(0)) },
302 .before_context_event = fill_context_event(context, internal_call_stack_manager) });
303 } else {
304 assert(ex_events.at(ex_events.size() - 1).error == ExecutionError::OPCODE_EXECUTION);
305 }
306
308 ExecutionTraceBuilder ex_builder;
310
311 ex_builder.process(ex_events, trace);
312 builder.process(context_helper.internal_call_stack_emitter.dump_events(), trace);
313
314 if (getenv("AVM_DEBUG") != nullptr) {
315 info("Debugging trace:");
317 debugger.run();
318 }
319
320 check_relation<internal_call_rel>(trace);
324
325 return 0;
326}
void run(uint32_t starting_row=0)
Definition debugger.cpp:76
static TaggedValue from(T value)
Sets up gadgets and instance managers to provide a context for fuzzing. NOTE: rudimentary set up for ...
EventEmitter< InternalCallStackEvent > internal_call_stack_emitter
std::unique_ptr< simulation::ContextInterface > make_enqueued_fuzzing_context(AztecAddress address=AztecAddress(0), AztecAddress msg_sender=AztecAddress(0), bool is_static=false, FF transaction_fee=FF(0), std::span< const FF > calldata={}, Gas gas_limit=GAS_LIMIT, Gas gas_used=GAS_USED_BY_PRIVATE, TransactionPhase phase=TransactionPhase::APP_LOGIC)
virtual void push(PC caller_pc, PC return_pc)=0
virtual InternalCallId get_return_call_id() const =0
virtual InternalCallId get_next_call_id() const =0
simulation::Instruction build() const
InstructionBuilder & operand(OperandBuilder operand)
void process(const simulation::EventEmitterInterface< simulation::AluEvent >::Container &events, TraceContainer &trace)
Process the ALU events and populate the ALU relevant columns in the trace.
void process(const simulation::EventEmitterInterface< simulation::ExecutionEvent >::Container &ex_events, TraceContainer &trace)
void info(Args... args)
Definition log.hpp:89
AluTraceBuilder builder
Definition alu.test.cpp:124
const std::vector< MemoryValue > data
TestTraceContainer trace
ssize_t offset
Definition engine.cpp:36
uint8_t buffer[RANDOM_BUFFER_SIZE]
Definition engine.cpp:34
const auto call_instr
const uint8_t max_total_calls
ContextEvent fill_context_event(std::unique_ptr< ContextInterface > &context, InternalCallStackManagerInterface &internal_call_stack_manager)
const auto ret_instr
void fuzz_internal_return(std::vector< ExecutionEvent > &ex_events, std::unique_ptr< ContextInterface > &context, InternalCallStackManagerInterface &internal_call_stack_manager)
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
size_t LLVMFuzzerCustomMutator(uint8_t *data, size_t size, size_t max_size, unsigned int seed)
const auto dummy_instr
const uint8_t max_flat_calls
void fuzz_internal_call(std::vector< ExecutionEvent > &ex_events, std::unique_ptr< ContextInterface > &context, InternalCallStackManagerInterface &internal_call_stack_manager, uint32_t loc)
const uint8_t max_nested_calls
void check_interaction(tracegen::TestTraceContainer &trace)
permutation_settings< perm_internal_call_push_call_stack_settings_ > perm_internal_call_push_call_stack_settings
AvmFlavorSettings::FF FF
Definition field.hpp:10
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
InternalCallFuzzerInput()=default
static InternalCallFuzzerInput from_buffer(const uint8_t *buffer)
std::array< uint32_t, max_total_calls > local_pcs
void to_buffer(uint8_t *buffer) const