Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
memory.fuzzer.cpp
Go to the documentation of this file.
2#include <algorithm>
3#include <cassert>
4#include <cstdint>
5#include <cstring>
6#include <random>
7
25
26using namespace bb::avm2::simulation;
27using namespace bb::avm2::tracegen;
28using namespace bb::avm2::constraining;
29
31using bb::avm2::FF;
35
37
38// Useful array of all memory tags for cycling through during upcast/downcast
40 MemoryTag::FF, MemoryTag::U1, MemoryTag::U8, MemoryTag::U16, MemoryTag::U32, MemoryTag::U64, MemoryTag::U128,
41};
42
44 uint8_t num_of_entries_input = 1; // The number of read/write operations to perform
45 uint64_t read_write_encoding = 0; // Bitmask: 1 = write, 0 = read
46 uint64_t upcast_encoding = 0; // Bitmask: 1 = upcast on write
47 uint64_t downcast_encoding = 0; // Bitmask: 1 = downcast on read
48 uint64_t selection_encoding = 0; // element selection
49 uint8_t space_ids = 0; //
50
52 std::array<MemoryAddress, 16> memory_addresses{};
53
54 MemoryFuzzerInput() = default;
55
75
76 MemoryFuzzerInput static from_buffer(const uint8_t* buffer)
77 {
79 size_t offset = 0;
81 offset += sizeof(input.num_of_entries_input);
83 offset += sizeof(input.read_write_encoding);
85 offset += sizeof(input.upcast_encoding);
87 offset += sizeof(input.downcast_encoding);
89 offset += sizeof(input.selection_encoding);
90 std::memcpy(&input.space_ids, buffer + offset, sizeof(input.space_ids));
91 offset += sizeof(input.space_ids);
93 &input.init_memory_values[0], buffer + offset, sizeof(MemoryValue) * input.init_memory_values.size());
94 offset += sizeof(MemoryValue) * input.init_memory_values.size();
95 std::memcpy(&input.memory_addresses[0], buffer + offset, sizeof(MemoryAddress) * input.memory_addresses.size());
96
97 return input;
98 }
99};
100
101extern "C" {
102__attribute__((section("__libfuzzer_extra_counters"))) uint8_t num_of_entries = 0;
103}
104
106{
108 values.reserve(num_of_entries);
109
110 // Place initial values
111 for (const auto& val : input.init_memory_values) {
112 values.emplace_back(val);
113 }
114
115 // Generate additional values based on encodings
116 for (size_t i = input.init_memory_values.size(); i < num_of_entries; ++i) {
117 auto entry_idx = (input.selection_encoding >> i) % values.size();
118 auto entry_value = values[entry_idx];
119
120 FF modified_value = entry_value.as_ff() + input.init_memory_values[i % input.init_memory_values.size()].as_ff();
121
122 auto should_upcast = (input.upcast_encoding >> i) & 1;
123 auto should_downcast = (input.downcast_encoding >> i) & 1;
124 if (should_upcast == 1) {
125 // Upcast logic (example: change tag to a larger type)
126 auto new_tag_index = (static_cast<uint8_t>(entry_value.get_tag()) + 1) % memory_tags.size();
127 auto memory_tag = memory_tags[new_tag_index];
128 entry_value = MemoryValue::from_tag_truncating(memory_tag, modified_value);
129 }
130 if (should_downcast == 1) {
131 // Downcast logic (example: change tag to a smaller type)
132 auto new_tag_index = (static_cast<uint8_t>(entry_value.get_tag()) - 1) % memory_tags.size();
133 auto memory_tag = memory_tags[new_tag_index];
134 entry_value = MemoryValue::from_tag_truncating(memory_tag, modified_value);
135 }
136 values.emplace_back(entry_value);
137 }
138 return values;
139}
140
142{
144 addresses.reserve(num_of_entries);
145
146 // Place initial addresses
147 for (const auto& addr : input.memory_addresses) {
148 addresses.emplace_back(addr);
149 }
150
151 for (size_t i = 0; i < num_of_entries; ++i) {
152 // Select addresses in a round-robin fashion
153 auto addr = input.memory_addresses[i % input.memory_addresses.size()];
154 addresses.emplace_back(addr + addr);
155 }
156 return addresses;
157}
158
159extern "C" size_t LLVMFuzzerCustomMutator(uint8_t* data, size_t size, size_t, unsigned int seed)
160{
161 if (size < sizeof(MemoryFuzzerInput)) {
162 // Initialize with default input
163 MemoryFuzzerInput input;
164 input.to_buffer(data);
165 return sizeof(MemoryFuzzerInput);
166 }
167
168 std::mt19937 rng(seed);
170 std::uniform_int_distribution<int> mutation_dist(0, 7);
171 int mutation_choice = mutation_dist(rng);
172
173 switch (mutation_choice) {
174 case 0: {
175 // Modify num_of_entries
176 std::uniform_int_distribution<int> num_entries_dist(-8, 8);
177 int new_val = static_cast<int>(input.num_of_entries_input) + num_entries_dist(rng);
178 input.num_of_entries_input = static_cast<uint8_t>(std::clamp(new_val, 0, 63));
179 break;
180 }
181 case 1: {
182 // Toggle a rw at a certain entry
184 size_t entry_idx = entry_dist(rng);
185 input.read_write_encoding ^= (1ULL << entry_idx);
186 break;
187 }
188 case 2: {
189 // Toggle upcast for a random entry
191 size_t entry_idx = entry_dist(rng);
192 input.upcast_encoding ^= (1ULL << entry_idx);
193 break;
194 }
195 case 3: {
196 // Toggle downcast for a random entry
198 size_t entry_idx = entry_dist(rng);
199 input.downcast_encoding ^= (1ULL << entry_idx);
200 break;
201 }
202 case 4: {
203 // Toggle selection encoding for a random entry
205 size_t entry_idx = entry_dist(rng);
206 input.selection_encoding ^= (1ULL << entry_idx);
207 break;
208 }
209 case 5: {
210 // Modify a random initial memory value
211 std::uniform_int_distribution<size_t> value_dist(0, input.init_memory_values.size() - 1);
212 size_t value_idx = value_dist(rng);
213 // Random Tag from memory_tags
215 size_t tag_idx = tag_dist(rng);
216 std::uniform_int_distribution<uint64_t> dist(0, std::numeric_limits<uint64_t>::max());
217
219 for (size_t i = 0; i < 4; ++i) {
220 limbs[i] = dist(rng);
221 }
222 auto random_value = FF(limbs[0], limbs[1], limbs[2], limbs[3]);
223 input.init_memory_values[value_idx] = MemoryValue::from_tag_truncating(memory_tags[tag_idx], random_value);
224 break;
225 }
226 case 6: {
227 // Incr/Decr a random memory address
228 std::uniform_int_distribution<size_t> addr_idx_dist(0, input.memory_addresses.size() - 1);
229 size_t addr_idx = addr_idx_dist(rng);
230 std::uniform_int_distribution<int> addr_change(-1000, 1000);
231 int new_addr = static_cast<int>(input.memory_addresses[addr_idx]) + addr_change(rng);
232 input.memory_addresses[addr_idx] = static_cast<uint32_t>(new_addr);
233 break;
234 }
235 case 7: {
236 // Incr/Decr space_ids
237 std::uniform_int_distribution<int> context_dist(-4, 4);
238 int new_val = static_cast<int>(input.space_ids) + context_dist(rng);
239 input.space_ids = static_cast<uint8_t>(new_val);
240 break;
241 }
242 default:
243 break;
244 }
245
246 input.to_buffer(data);
247 return sizeof(MemoryFuzzerInput);
248}
249
250extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
251{
253
254 if (size < sizeof(MemoryFuzzerInput)) {
255 info("Input size too small");
256 return 0;
257 }
258
259 // Parse input
261
262 // Set the libFuzzer extra counter from input
263 // LibFuzzer will track increases in this value as coverage progress
264 num_of_entries = input.num_of_entries_input;
265
266 // Set up gadgets and event emitters
268 EventEmitter<MemoryEvent> memory_emitter;
270
271 uint32_t clk = 0;
273 MemoryProvider mem_provider(range_check, execution_id_manager, memory_emitter);
274 // Ensure at least 1 memory context exists
275 size_t num_contexts = std::max(static_cast<size_t>(input.space_ids), 1UL);
277 memories.reserve(num_contexts);
278
279 for (size_t i = 0; i < num_contexts; ++i) {
280 memories.push_back(mem_provider.make_memory(static_cast<uint8_t>(i)));
281 }
282
283 std::vector<MemoryValue> memory_contents = generate_memory_values(input);
285
287
288 for (size_t i = 0; i < num_of_entries; ++i) {
289 // Pick a memory partition in round-robin fashion
290 MemoryInterface* mem = memories[i % memories.size()].get();
291 // Determine if read or write
292 bool is_write = ((input.read_write_encoding >> i) & 1) != 0;
293 MemoryAddress addr = memory_addresses[i];
294 if (is_write) {
295 mem->set(addr, memory_contents[i]);
296 // Update running memory state
297 running_memory_states[mem->get_space_id()][addr] = memory_contents[i];
298 } else {
299 auto retrieved_val = mem->get(addr);
300 // Verify against running memory state
301 if (running_memory_states[mem->get_space_id()].contains(addr)) {
302 auto expected_val = running_memory_states[mem->get_space_id()][addr];
303 assert(retrieved_val == expected_val);
304 } else {
305 // If address was never written to, assume default value is FF(0)
306 assert(retrieved_val == MemoryValue::from_tag_truncating(MemoryTag::FF, FF(0)));
307 }
308 }
310 }
311
313 MemoryTraceBuilder memory_trace_builder;
316
317 memory_trace_builder.process(memory_emitter.dump_events(), trace);
318
319 // Memory is not entirely standalone, we need to set a relation #[ACTIVE_ROW_NEEDS_PERM_SELECTOR]
320 for (uint32_t i = 1; i <= num_of_entries; ++i) {
321 trace.set(Column::memory_sel_register_op_0_, i, 1);
322 }
323 check_relation<memory_rel>(trace);
324
325 // This makes it all realllllly slow
326 // RangeCheckTraceBuilder range_check_builder;
327 // precomputed_builder.process_tag_parameters(trace);
328 // precomputed_builder.process_sel_range_16(trace);
329 // precomputed_builder.process_misc(trace, 1 << 16);
330 // range_check_builder.process(range_check_emitter.dump_events(), trace);
331
332 // check_all_interactions<MemoryTraceBuilder>(trace);
333
334 return 0;
335}
EventEmitter< simulation::RangeCheckEvent > range_check_emitter
static TaggedValue from_tag_truncating(ValueTag tag, FF value)
std::unique_ptr< MemoryInterface > make_memory(uint16_t space_id) override
Definition memory.hpp:57
void set(MemoryAddress index, MemoryValue value) override
uint16_t get_space_id() const override
const MemoryValue & get(MemoryAddress index) const override
void process(const simulation::EventEmitterInterface< simulation::MemoryEvent >::Container &events, TraceContainer &trace)
Processes memory events into the memory subtrace.
void process_misc(TraceContainer &trace, const uint32_t num_rows=PRECOMPUTED_TRACE_SIZE)
Populate miscellaneous precomputed columns: first_row selector and idx (row index).
void set(Column col, uint32_t row, const FF &value)
#define info(...)
Definition log.hpp:93
PrecomputedTraceBuilder precomputed_builder
Definition alu.test.cpp:120
ExecutionIdManager execution_id_manager
MemoryStore mem
const std::vector< MemoryValue > data
TestTraceContainer trace
ssize_t offset
Definition engine.cpp:62
std::unique_ptr< uint8_t[]> buffer
Definition engine.cpp:60
std::vector< MemoryValue > generate_memory_values(const MemoryFuzzerInput &input)
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
size_t LLVMFuzzerCustomMutator(uint8_t *data, size_t size, size_t, unsigned int seed)
const std::array< MemoryTag, 7 > memory_tags
std::vector< MemoryAddress > generate_memory_addresses(const MemoryFuzzerInput &input)
__attribute__((section("__libfuzzer_extra_counters"))) uint8_t num_of_entries=0
AVM range check gadget for witness generation.
TaggedValue MemoryValue
AvmFlavorSettings::FF FF
Definition field.hpp:10
uint32_t MemoryAddress
STL namespace.
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
std::array< MemoryAddress, 16 > memory_addresses
static MemoryFuzzerInput from_buffer(const uint8_t *buffer)
void to_buffer(uint8_t *buffer) const
uint64_t read_write_encoding
MemoryFuzzerInput()=default
std::array< MemoryValue, 16 > init_memory_values
uint64_t selection_encoding