Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
external_call.fuzzer.cpp
Go to the documentation of this file.
5#include <array>
6#include <cstddef>
7#include <cstdint>
8#include <memory>
9#include <vector>
10
26
27using namespace bb::avm2::simulation;
28using namespace bb::avm2::tracegen;
29using namespace bb::avm2::constraining;
30using namespace bb::avm2::fuzzing;
31
32using bb::avm2::FF;
35
37
38const uint8_t max_flat_calls = 3;
39const uint8_t max_nested_calls = 2;
41// To avoid OOG error:
43
44// Constant instructions:
46 .operand<uint8_t>(0)
47 .operand<uint8_t>(0)
48 .operand<uint8_t>(0)
49 .build();
50
53 MemoryValue l2_gas = MemoryValue::from<uint32_t>(min_l2_gas);
54 MemoryValue da_gas = MemoryValue::from<uint32_t>(0);
56
58
59 void print() const
60 {
61 info("contract_address: ", contract_address);
62 info("l2_gas: ", l2_gas.to_string());
63 info("da_gas: ", da_gas.to_string());
64 info("is_static: ", is_static);
65 }
66
67 void to_buffer(uint8_t* buffer) const
68 {
69 size_t offset = 0;
71 offset += sizeof(contract_address);
73 offset += sizeof(l2_gas);
75 offset += sizeof(da_gas);
77 offset += sizeof(is_static);
78 }
79
81 {
83 size_t offset = 0;
85 offset += sizeof(input.contract_address);
86 std::memcpy(&input.l2_gas, buffer + offset, sizeof(input.l2_gas));
87 offset += sizeof(input.l2_gas);
88 std::memcpy(&input.da_gas, buffer + offset, sizeof(input.da_gas));
89 offset += sizeof(input.da_gas);
90 std::memcpy(&input.is_static, buffer + offset, sizeof(input.is_static));
91 offset += sizeof(input.is_static);
92
93 return input;
94 }
95};
96
98 uint32_t start_pc = 0;
99 // Number i where we have (CALL/STATICCALL -> (CALL/STATICCALL -> RETURN) xj -> RETURN) xi
100 uint8_t num_flat_calls = 1;
101 // Number j where we have (CALL/STATICCALL -> (CALL/STATICCALL -> RETURN) xj -> RETURN) xi
102 uint8_t num_nested_calls = 0;
103
104 std::array<ExternalCallFuzzerInstance, max_total_calls> call_instances{};
105
107
108 void print() const
109 {
110 info("start_pc: ", start_pc);
111 info("num_flat_calls: ", int(num_flat_calls));
112 info("num_nested_calls: ", int(num_nested_calls));
113 for (size_t i = 0; i < call_instances.size(); i++) {
114 info("call_instance ", i, ": ");
115 call_instances[i].print();
116 }
117 }
118
119 void to_buffer(uint8_t* buffer) const
120 {
121 size_t offset = 0;
123 offset += sizeof(start_pc);
125 offset += sizeof(num_flat_calls);
127 offset += sizeof(num_nested_calls);
128 for (const auto& call_instance : call_instances) {
129 call_instance.to_buffer(buffer + offset);
131 }
132 }
133
135 {
137 size_t offset = 0;
138 std::memcpy(&input.start_pc, buffer + offset, sizeof(input.start_pc));
139 offset += sizeof(input.start_pc);
140 std::memcpy(&input.num_flat_calls, buffer + offset, sizeof(input.num_flat_calls));
141 offset += sizeof(input.num_flat_calls);
143 offset += sizeof(input.num_nested_calls);
144 for (auto& call_instance : input.call_instances) {
147 }
148
149 return input;
150 }
151};
152
153// Mutate a single random call instance
155{
156 // Modify a random call instance (using num_events to ensure it's used in a run)
157 size_t num_events =
158 static_cast<size_t>(input.num_flat_calls) * (input.num_nested_calls == 0 ? 1 : input.num_nested_calls);
159 std::uniform_int_distribution<size_t> index_dist(0, num_events == 0 ? 0 : num_events - 1);
160 size_t value_idx = index_dist(rng);
161 std::uniform_int_distribution<int> inner_mutation_dist(0, 3);
162 int inner_mutation_choice = inner_mutation_dist(rng);
163 switch (inner_mutation_choice) {
164 case 0: {
165 // Modify l2 gas (a minimum of min_l2_gas to avoid OOG error)
166 std::uniform_int_distribution<uint32_t> gas_dist(min_l2_gas, std::numeric_limits<uint32_t>::max());
167 input.call_instances[value_idx].l2_gas = MemoryValue::from<uint32_t>(gas_dist(rng));
168 break;
169 }
170 case 1: {
171 // Modify da gas
172 std::uniform_int_distribution<uint32_t> gas_dist(0, std::numeric_limits<uint32_t>::max());
173 input.call_instances[value_idx].da_gas = MemoryValue::from<uint32_t>(gas_dist(rng));
174 break;
175 }
176 case 2: {
177 // Modify contract address
178 std::uniform_int_distribution<uint64_t> addr_dist(0, std::numeric_limits<uint64_t>::max());
179 input.call_instances[value_idx].contract_address =
180 FF(addr_dist(rng), addr_dist(rng), addr_dist(rng), addr_dist(rng));
181 break;
182 }
183 case 3: {
184 // Toggle is_static
185 input.call_instances[value_idx].is_static = !input.call_instances[value_idx].is_static;
186 break;
187 }
188 default:
189 break;
190 }
191}
192
193extern "C" size_t LLVMFuzzerCustomMutator(uint8_t* data, size_t size, size_t max_size, unsigned int seed)
194{
195 if (size < sizeof(ExternalCallFuzzerInput)) {
196 // Initialize with default input
198 input.to_buffer(data);
199 return sizeof(ExternalCallFuzzerInput);
200 }
201 std::mt19937 rng(seed);
202
203 // Deserialize current input
205
206 // Choose random mutation
207 std::uniform_int_distribution<int> mutation_dist(0, 3);
208 int mutation_choice = mutation_dist(rng);
209
210 switch (mutation_choice) {
211 case 0: {
212 // Modify number of flat internal calls
214 input.num_flat_calls = num_flat_calls_dist(rng);
215 break;
216 }
217 case 1: {
218 // Modify number of nested internal calls
220 input.num_nested_calls = num_nested_calls_dist(rng);
221 break;
222 }
223 case 2: {
224 // Modify initial context pc
225 size_t num_events =
226 static_cast<size_t>(input.num_flat_calls) * (input.num_nested_calls == 0 ? 1 : input.num_nested_calls);
227 // Creating a large offset to avoid overflow of pc
228 const auto& spec = get_wire_instruction_spec();
229 size_t instr_sizes_offset =
230 num_events * (spec.at(WireOpCode::CALL).size_in_bytes + spec.at(WireOpCode::RETURN).size_in_bytes +
231 dummy_instr.size_in_bytes());
233 0, std::numeric_limits<uint32_t>::max() - uint32_t(instr_sizes_offset));
234 input.start_pc = start_pc_dist(rng);
235 break;
236 }
237 case 3: {
238 // Modify a random calldata instance
239 mutate_call_instance(input, rng);
240 break;
241 }
242 default:
243 break;
244 }
245 // Serialize mutated input back to buffer
246 input.to_buffer(data);
247
248 if (max_size > sizeof(ExternalCallFuzzerInput)) {
249 return sizeof(ExternalCallFuzzerInput);
250 }
251
252 return sizeof(ExternalCallFuzzerInput);
253}
254
255// NOTE: context->serialize_context_event() causes stack overflow :(
257{
258 return {
259 .id = context->get_context_id(),
260 .parent_id = context->get_parent_id(),
261 .pc = context->get_pc(),
262 .gas_used = context->get_gas_used(),
263 .gas_limit = context->get_gas_limit(),
264 };
265}
266
269 std::unique_ptr<ContextInterface>& parent_context,
270 ExecutionComponentsProvider& execution_components,
272{
273 auto allocated_l2_gas_read = input.l2_gas;
274 auto allocated_da_gas_read = input.da_gas;
275 auto instr = bb::avm2::testing::InstructionBuilder(input.is_static ? WireOpCode::STATICCALL : WireOpCode::CALL)
276 .operand<uint8_t>(2)
277 .operand<uint8_t>(4)
278 .operand<uint8_t>(6)
279 .operand<uint8_t>(10)
280 .operand<uint8_t>(20)
281 .build();
282
283 ExecutionEvent ex_event = { .wire_instruction = instr, .before_context_event = fill_context_event(parent_context) };
284 AddressingEvent addressing_event;
286
287 // Execution.execute pre - dispatch
288 parent_context->set_next_pc(parent_context->get_pc() + static_cast<uint32_t>(instr.size_in_bytes()));
289 auto addressing = execution_components.make_addressing(addressing_event);
290 addressing->resolve(ex_event.wire_instruction, parent_context->get_memory());
291 auto gas_tracker = execution_components.make_gas_tracker(gas_event, ex_event.wire_instruction, *parent_context);
292
293 // Execution.call / static_call
294 gas_tracker->consume_gas();
295 auto new_gas_limit = gas_tracker->compute_gas_limit_for_call(
296 Gas{ allocated_l2_gas_read.as<uint32_t>(), allocated_da_gas_read.as<uint32_t>() });
297
298 auto child_context = helper.make_nested_fuzzing_context(
299 input.contract_address, input.contract_address, *parent_context, input.is_static, new_gas_limit);
300
301 // Execution.execute post-dispatch:
302 parent_context->set_pc(parent_context->get_next_pc());
303 ex_event.inputs = { allocated_l2_gas_read,
304 allocated_da_gas_read,
305 MemoryValue::from<FF>(input.contract_address),
306 /* cd_size = */ MemoryValue::from<uint32_t>(0) };
307 ex_event.addressing_event = addressing_event;
308 ex_event.gas_event = gas_event;
309 ex_event.after_context_event = fill_context_event(parent_context);
310
311 ex_events.push_back(ex_event);
312
313 // Push event from the call itself (via nested child context)
314 // Note: we only need the gas_limit in after_context_event, hence filling that rather than .before_context_event
315 ExecutionEvent nested_event = {
317 .inputs = { MemoryValue::from(FF(0)), MemoryValue::from(FF(0)), MemoryValue::from(FF(0)) },
318 .after_context_event = fill_context_event(child_context)
319 };
320
321 ex_events.push_back(nested_event);
322
323 return child_context;
324}
325
328 ExecutionComponentsProvider& execution_components)
329{
330 auto instr = bb::avm2::testing::InstructionBuilder(WireOpCode::RETURN)
331 .operand<uint8_t>(30) // ret_size_offset
332 .operand<uint8_t>(40) // ret_offset
333 .build();
334
335 ExecutionEvent ex_event = { .wire_instruction = instr, .before_context_event = fill_context_event(context) };
337
338 // Execution.execute pre - dispatch
339 context->set_next_pc(context->get_pc() + static_cast<uint32_t>(instr.size_in_bytes()));
340 auto gas_tracker = execution_components.make_gas_tracker(gas_event, ex_event.wire_instruction, *context);
341
342 // Execution.ret
343 gas_tracker->consume_gas();
344
345 // TODO(MW): Mimic set execution result to set gas_used and check w asserts for nested returns:
346
347 // set_execution_result({ .rd_offset = ret_offset,
348 // .rd_size = rd_size.as<uint32_t>(),
349 // .gas_used = context.get_gas_used(),
350 // .success = true,
351 // .halting_pc = context.get_pc(),
352 // .halting_message = std::nullopt });
353
354 context->halt();
355
356 // Execution.execute post-dispatch:
357 context->set_pc(context->get_next_pc());
358 ex_event.inputs = { MemoryValue::from<uint32_t>(10) /* =rd_size, TODO(MW): fuzz? */ };
360
361 ex_events.push_back(ex_event);
362}
363
364extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
365{
367
368 if (size < sizeof(ExternalCallFuzzerInput)) {
369 return 0;
370 }
371
373
374 // Set up gadgets and event emitters
375 GadgetFuzzerContextHelper context_helper;
376 auto context = context_helper.make_enqueued_fuzzing_context();
377 context->set_pc(input.start_pc);
379 ExecutionComponentsProvider execution_components(context_helper.greater_than, instruction_info_db);
381
382 try {
383 size_t current_call_idx = 0;
384 for (auto i = 0; i < input.num_flat_calls; i++) {
385 auto child_context = fuzz_call(
386 ex_events, context_helper, context, execution_components, input.call_instances[current_call_idx++]);
387 if (input.num_nested_calls > 0) {
388 fuzz_call(ex_events,
389 context_helper,
390 child_context,
391 execution_components,
392 input.call_instances[current_call_idx++]);
393 fuzz_return(ex_events, child_context, execution_components);
394 // This fuzzer doesn't test beyond the external_call.pil relations/lookups, so we don't need a
395 // handle_exit_call()
396 }
397 fuzz_return(ex_events, context, execution_components);
398 }
399
400 } catch (const std::exception& e) {
401 // No opcode errors to test here
402 return 0;
403 }
404
407 ExecutionTraceBuilder ex_builder;
408
409 ex_builder.process(ex_events, trace);
410 gt_builder.process(context_helper.greater_than_emitter.dump_events(), trace);
411
412 if (getenv("AVM_DEBUG") != nullptr) {
413 info("Debugging trace:");
415 debugger.run();
416 }
417
418 check_relation<external_call_rel>(trace);
422
423 return 0;
424}
#define AVM_RETURN_BASE_L2_GAS
#define AVM_CALL_BASE_L2_GAS
void run(uint32_t starting_row=0)
Definition debugger.cpp:76
static TaggedValue from(T value)
std::string to_string() const
Sets up gadgets and instance managers to provide a context for fuzzing. NOTE: rudimentary set up for ...
DeduplicatingEventEmitter< GreaterThanEvent > greater_than_emitter
std::unique_ptr< simulation::ContextInterface > make_nested_fuzzing_context(AztecAddress address, AztecAddress msg_sender, ContextInterface &parent_context, bool is_static=false, Gas gas_limit=GAS_LIMIT)
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)
std::unique_ptr< GasTrackerInterface > make_gas_tracker(GasEvent &gas_event, const Instruction &instruction, ContextInterface &context) override
std::unique_ptr< AddressingInterface > make_addressing(AddressingEvent &event) override
simulation::Instruction build() const
InstructionBuilder & operand(OperandBuilder operand)
void process(const simulation::EventEmitterInterface< simulation::ExecutionEvent >::Container &ex_events, TraceContainer &trace)
void process(const simulation::EventEmitterInterface< simulation::GreaterThanEvent >::Container &events, TraceContainer &trace)
Definition gt_trace.cpp:11
void info(Args... args)
Definition log.hpp:89
GreaterThanTraceBuilder gt_builder
Definition alu.test.cpp:123
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 uint32_t min_l2_gas
const uint8_t max_total_calls
ContextEvent fill_context_event(std::unique_ptr< ContextInterface > &context)
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_return(std::vector< ExecutionEvent > &ex_events, std::unique_ptr< ContextInterface > &context, ExecutionComponentsProvider &execution_components)
const uint8_t max_nested_calls
void mutate_call_instance(ExternalCallFuzzerInput &input, std::mt19937 rng)
std::unique_ptr< ContextInterface > fuzz_call(std::vector< ExecutionEvent > &ex_events, GadgetFuzzerContextHelper &helper, std::unique_ptr< ContextInterface > &parent_context, ExecutionComponentsProvider &execution_components, ExternalCallFuzzerInstance input)
InstructionInfoDB instruction_info_db
GasEvent gas_event
void check_interaction(tracegen::TestTraceContainer &trace)
lookup_settings< lookup_external_call_is_l2_gas_left_gt_allocated_settings_ > lookup_external_call_is_l2_gas_left_gt_allocated_settings
const std::unordered_map< WireOpCode, WireInstructionSpec > & get_wire_instruction_spec()
AvmFlavorSettings::FF FF
Definition field.hpp:10
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
ExternalCallFuzzerInput()=default
std::array< ExternalCallFuzzerInstance, max_total_calls > call_instances
void to_buffer(uint8_t *buffer) const
static ExternalCallFuzzerInput from_buffer(const uint8_t *buffer)
static ExternalCallFuzzerInstance from_buffer(const uint8_t *buffer)
ExternalCallFuzzerInstance()=default
void to_buffer(uint8_t *buffer) const
std::vector< MemoryValue > inputs