Skip to content

Commit e67fc66

Browse files
authored
feat(avm): avm fuzzer bytecode mutation (#19378)
Introduces bytecode mutation using the standard `LLVMFuzzerMutate`. We allow the mutated bytecode to expand up to 2x the original size. The mutation itself then utilises the contract upgrade path, this way we do not need to modify other classes or instances that may be used by other enqueued calls. This does require the addition of public data writes as part of the setup to the fuzzer state (that also needs to happen in TS)
1 parent 9e814d3 commit e67fc66

File tree

12 files changed

+226
-48
lines changed

12 files changed

+226
-48
lines changed

barretenberg/cpp/src/barretenberg/avm_fuzzer/common/interfaces/dbs.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,4 +237,10 @@ void FuzzerWorldStateManager::write_fee_payer_balance(const AztecAddress& fee_pa
237237
ws->update_public_data(PublicDataLeafValue(leaf_slot, balance), fork_id);
238238
}
239239

240+
void FuzzerWorldStateManager::public_data_write(const bb::crypto::merkle_tree::PublicDataLeafValue& public_data)
241+
{
242+
auto fork_id = fork_ids.top();
243+
ws->update_public_data(public_data, fork_id);
244+
}
245+
240246
} // namespace bb::avm2::fuzzer

barretenberg/cpp/src/barretenberg/avm_fuzzer/common/interfaces/dbs.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ class FuzzerWorldStateManager {
8888
void reset_world_state();
8989
void register_contract_address(const AztecAddress& contract_address);
9090
void write_fee_payer_balance(const AztecAddress& fee_payer, const FF& balance);
91+
void public_data_write(const bb::crypto::merkle_tree::PublicDataLeafValue& public_data);
9192

9293
world_state::WorldStateRevision get_current_revision() const;
9394
world_state::WorldStateRevision fork();

barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzz_lib/fuzz.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,14 @@ SimulatorResult fuzz_against_ts_simulator(FuzzerData& fuzzer_data, FuzzerContext
4242

4343
try {
4444
ws_mgr->checkpoint();
45-
cpp_result = cpp_simulator.simulate(*ws_mgr, contract_db, tx);
45+
cpp_result = cpp_simulator.simulate(*ws_mgr, contract_db, tx, /*public_data_writes=*/{});
4646
ws_mgr->revert();
4747
} catch (const std::exception& e) {
4848
throw std::runtime_error(std::string("CppSimulator threw an exception: ") + e.what());
4949
}
5050

5151
ws_mgr->checkpoint();
52-
auto js_result = js_simulator->simulate(*ws_mgr, contract_db, tx);
52+
auto js_result = js_simulator->simulate(*ws_mgr, contract_db, tx, /*public_data_writes=*/{});
5353

5454
context.reset();
5555

barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzz_lib/simulator.cpp

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,14 @@ using namespace bb::avm2::simulation;
3131
using namespace bb::avm2::fuzzer;
3232
using namespace bb::world_state;
3333

34-
const auto MAX_RETURN_DATA_SIZE_IN_FIELDS = 1024;
34+
constexpr auto MAX_RETURN_DATA_SIZE_IN_FIELDS = 1024;
3535

3636
// Helper function to serialize simulation request via msgpack
37-
std::string serialize_simulation_request(const Tx& tx,
38-
const GlobalVariables& globals,
39-
const FuzzerContractDB& contract_db)
37+
std::string serialize_simulation_request(
38+
const Tx& tx,
39+
const GlobalVariables& globals,
40+
const FuzzerContractDB& contract_db,
41+
const std::vector<bb::crypto::merkle_tree::PublicDataLeafValue>& public_data_writes)
4042
{
4143
// Build vectors from contract_db
4244
std::vector<ContractClass> classes_vec = contract_db.get_contract_classes();
@@ -49,6 +51,7 @@ std::string serialize_simulation_request(const Tx& tx,
4951
.globals = globals,
5052
.contract_classes = std::move(classes_vec),
5153
.contract_instances = std::move(instances_vec),
54+
.public_data_writes = public_data_writes,
5255
};
5356

5457
auto [buffer, size] = msgpack_encode_buffer(request);
@@ -72,10 +75,13 @@ GlobalVariables create_default_globals()
7275
};
7376
}
7477

75-
SimulatorResult CppSimulator::simulate(fuzzer::FuzzerWorldStateManager& ws_mgr,
76-
fuzzer::FuzzerContractDB& contract_db,
77-
const Tx& tx)
78+
SimulatorResult CppSimulator::simulate(
79+
fuzzer::FuzzerWorldStateManager& ws_mgr,
80+
fuzzer::FuzzerContractDB& contract_db,
81+
const Tx& tx,
82+
[[maybe_unused]] const std::vector<bb::crypto::merkle_tree::PublicDataLeafValue>& public_data_writes)
7883
{
84+
// Note: public_data_writes are already applied to C++ world state in setup_fuzzer_state
7985

8086
const PublicSimulatorConfig config{
8187
.skip_fee_enforcement = false,
@@ -143,13 +149,15 @@ void JsSimulator::initialize(std::string& simulator_path)
143149
instance = new JsSimulator(simulator_path);
144150
}
145151

146-
SimulatorResult JsSimulator::simulate([[maybe_unused]] fuzzer::FuzzerWorldStateManager& ws_mgr,
147-
fuzzer::FuzzerContractDB& contract_db,
148-
const Tx& tx)
152+
SimulatorResult JsSimulator::simulate(
153+
[[maybe_unused]] fuzzer::FuzzerWorldStateManager& ws_mgr,
154+
fuzzer::FuzzerContractDB& contract_db,
155+
const Tx& tx,
156+
const std::vector<bb::crypto::merkle_tree::PublicDataLeafValue>& public_data_writes)
149157
{
150158
auto globals = create_default_globals();
151159

152-
std::string serialized = serialize_simulation_request(tx, globals, contract_db);
160+
std::string serialized = serialize_simulation_request(tx, globals, contract_db, public_data_writes);
153161

154162
// Send the request
155163
process.write_line(serialized);
@@ -172,7 +180,7 @@ SimulatorResult JsSimulator::simulate([[maybe_unused]] fuzzer::FuzzerWorldStateM
172180
bool compare_simulator_results(SimulatorResult& result1, SimulatorResult& result2)
173181
{
174182
// Since the simulator results are interchangeable between TS and C++, we limit the return data size for comparison
175-
// todo(ilyas): we ideally specfify one param as the TS result and truncate only that one
183+
// todo(ilyas): we ideally specify one param as the TS result and truncate only that one
176184
if (result1.output.size() > MAX_RETURN_DATA_SIZE_IN_FIELDS) {
177185
result1.output.resize(MAX_RETURN_DATA_SIZE_IN_FIELDS);
178186
}

barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzz_lib/simulator.hpp

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
#include "barretenberg/avm_fuzzer/common/interfaces/dbs.hpp"
66
#include "barretenberg/avm_fuzzer/common/process.hpp"
7+
#include "barretenberg/crypto/merkle_tree/indexed_tree/indexed_leaf.hpp"
78
#include "barretenberg/vm2/common/avm_io.hpp"
89
#include "barretenberg/vm2/common/aztec_types.hpp"
910
#include "barretenberg/vm2/common/field.hpp"
@@ -24,8 +25,11 @@ struct FuzzerSimulationRequest {
2425
std::vector<ContractClass> contract_classes;
2526
// Having addresses here avoids doing re-work in TS.
2627
std::vector<std::pair<AztecAddress, ContractInstance>> contract_instances;
28+
// Public data tree writes to apply before simulation (e.g., for bytecode upgrades)
29+
std::vector<bb::crypto::merkle_tree::PublicDataLeafValue> public_data_writes;
2730

28-
MSGPACK_CAMEL_CASE_FIELDS(ws_data_dir, ws_map_size_kb, tx, globals, contract_classes, contract_instances);
31+
MSGPACK_CAMEL_CASE_FIELDS(
32+
ws_data_dir, ws_map_size_kb, tx, globals, contract_classes, contract_instances, public_data_writes);
2933
};
3034

3135
struct SimulatorResult {
@@ -45,17 +49,21 @@ class Simulator {
4549
Simulator(Simulator&&) = delete;
4650
Simulator& operator=(Simulator&&) = delete;
4751
Simulator() = default;
48-
virtual SimulatorResult simulate(fuzzer::FuzzerWorldStateManager& ws_mgr,
49-
fuzzer::FuzzerContractDB& contract_db,
50-
const Tx& tx) = 0;
52+
virtual SimulatorResult simulate(
53+
fuzzer::FuzzerWorldStateManager& ws_mgr,
54+
fuzzer::FuzzerContractDB& contract_db,
55+
const Tx& tx,
56+
const std::vector<bb::crypto::merkle_tree::PublicDataLeafValue>& public_data_writes) = 0;
5157
};
5258

5359
/// @brief uses barretenberg/vm2 to simulate the bytecode
5460
class CppSimulator : public Simulator {
5561
public:
56-
SimulatorResult simulate(fuzzer::FuzzerWorldStateManager& ws_mgr,
57-
fuzzer::FuzzerContractDB& contract_db,
58-
const Tx& tx) override;
62+
SimulatorResult simulate(
63+
fuzzer::FuzzerWorldStateManager& ws_mgr,
64+
fuzzer::FuzzerContractDB& contract_db,
65+
const Tx& tx,
66+
const std::vector<bb::crypto::merkle_tree::PublicDataLeafValue>& public_data_writes) override;
5967
};
6068

6169
/// @brief uses the yarn-project/simulator to simulate the bytecode
@@ -77,9 +85,11 @@ class JsSimulator : public Simulator {
7785
static JsSimulator* getInstance();
7886
static void initialize(std::string& simulator_path);
7987

80-
SimulatorResult simulate(fuzzer::FuzzerWorldStateManager& ws_mgr,
81-
fuzzer::FuzzerContractDB& contract_db,
82-
const Tx& tx) override;
88+
SimulatorResult simulate(
89+
fuzzer::FuzzerWorldStateManager& ws_mgr,
90+
fuzzer::FuzzerContractDB& contract_db,
91+
const Tx& tx,
92+
const std::vector<bb::crypto::merkle_tree::PublicDataLeafValue>& public_data_writes) override;
8393
};
8494

8595
GlobalVariables create_default_globals();

barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzzer_lib.cpp

Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -25,24 +25,33 @@ using namespace bb::avm2::fuzzer;
2525
using namespace bb::avm2::simulation;
2626
using namespace bb::world_state;
2727

28+
extern size_t LLVMFuzzerMutate(uint8_t* Data, size_t Size, size_t MaxSize);
29+
2830
void setup_fuzzer_state(FuzzerWorldStateManager& ws_mgr, FuzzerContractDB& contract_db, const FuzzerTxData& tx_data)
2931
{
3032
// Add all contract classes and instances to the contract DB
33+
// There may be more classes than instances because of the possibility of mutated bytecodes that are used in
34+
// upgrades - these are not directly instantiated
3135
for (size_t i = 0; i < tx_data.contract_classes.size(); ++i) {
3236
const auto& contract_class = tx_data.contract_classes[i];
37+
contract_db.add_contract_class(contract_class.id, contract_class);
38+
}
39+
40+
// Add contract instances to the contract DB
41+
for (size_t i = 0; i < tx_data.contract_instances.size(); ++i) {
3342
const auto& contract_instance = tx_data.contract_instances[i];
3443
auto contract_address = tx_data.contract_addresses[i];
35-
contract_db.add_contract_class(contract_class.id, contract_class);
3644
contract_db.add_contract_instance(contract_address, contract_instance);
3745
}
3846

39-
// Register the de-duplicated set of contract addresses to the world state (in insertion order)
40-
std::unordered_set<AztecAddress> seen_addresses;
47+
// Register contract addresses in the world state
4148
for (const auto& addr : tx_data.contract_addresses) {
42-
if (seen_addresses.insert(addr).second) {
43-
fuzz_info("Registering contract address in world state: ", addr);
44-
ws_mgr.register_contract_address(addr);
45-
}
49+
ws_mgr.register_contract_address(addr);
50+
}
51+
52+
// Apply public data tree writes (e.g., for contract instance upgrades)
53+
for (const auto& write : tx_data.public_data_writes) {
54+
ws_mgr.public_data_write(write);
4655
}
4756
}
4857

@@ -69,7 +78,9 @@ SimulatorResult fuzz_tx(FuzzerWorldStateManager& ws_mgr, FuzzerContractDB& contr
6978

7079
try {
7180
ws_mgr.checkpoint();
72-
cpp_result = cpp_simulator.simulate(ws_mgr, contract_db, tx_data.tx);
81+
cpp_result = cpp_simulator.simulate(ws_mgr, contract_db, tx_data.tx, tx_data.public_data_writes);
82+
fuzz_info("CppSimulator completed without exception");
83+
fuzz_info("CppSimulator result: ", cpp_result);
7384
ws_mgr.revert();
7485
} catch (const std::exception& e) {
7586
fuzz_info("CppSimulator threw an exception: ", e.what());
@@ -83,7 +94,7 @@ SimulatorResult fuzz_tx(FuzzerWorldStateManager& ws_mgr, FuzzerContractDB& contr
8394
}
8495

8596
ws_mgr.checkpoint();
86-
auto js_result = js_simulator->simulate(ws_mgr, contract_db, tx_data.tx);
97+
auto js_result = js_simulator->simulate(ws_mgr, contract_db, tx_data.tx, tx_data.public_data_writes);
8798

8899
// If the results do not match
89100
if (!compare_simulator_results(cpp_result, js_result)) {
@@ -271,14 +282,23 @@ size_t mutate_tx_data(FuzzerContext& context,
271282
tx_data.contract_classes.clear();
272283
tx_data.contract_instances.clear();
273284
tx_data.contract_addresses.clear();
285+
tx_data.public_data_writes.clear();
274286
std::vector<AztecAddress> contract_addresses;
275287

288+
std::unordered_set<AztecAddress> seen_addresses;
276289
for (auto& fuzzer_data : tx_data.input_programs) {
277290
const auto [bytecode, contract_class, contract_instance] = build_bytecode_and_artifacts(fuzzer_data);
278291

279292
auto contract_address = simulation::compute_contract_address(contract_instance);
280-
contract_addresses.push_back(contract_address);
281293

294+
// Skip duplicate addresses - multiple input_programs can generate the same address
295+
if (seen_addresses.contains(contract_address)) {
296+
fuzz_info("Skipping duplicate contract address: ", contract_address);
297+
continue;
298+
}
299+
seen_addresses.insert(contract_address);
300+
301+
contract_addresses.push_back(contract_address);
282302
tx_data.contract_classes.push_back(contract_class);
283303
tx_data.contract_instances.push_back(contract_instance);
284304
}
@@ -298,17 +318,21 @@ size_t mutate_tx_data(FuzzerContext& context,
298318
}
299319

300320
// Select mutation type (weighted against bytecode mutations) -- todo
301-
auto mutation_type = std::uniform_int_distribution<uint8_t>(0, 0);
302-
TxDataMutationType mutation_choice = static_cast<TxDataMutationType>(mutation_type(rng));
321+
FuzzerTxDataMutationType mutation_choice = FUZZER_TX_DATA_MUTATION_CONFIGURATION.select(rng);
303322

304323
switch (mutation_choice) {
305-
case TxDataMutationType::TxMutation:
324+
case FuzzerTxDataMutationType::TxMutation:
306325
mutate_tx(tx_data.tx, contract_addresses, rng);
307326
break;
308-
// case TxDataMutationType::BytecodeMutation:
309-
// // todo: Maybe here we can do some direct mutations on the bytecode
310-
// // Mutations here are likely to cause immediate failure
311-
// break;
327+
case FuzzerTxDataMutationType::BytecodeMutation: {
328+
// Mutate bytecode and append public data writes for world state setup
329+
mutate_bytecode(tx_data.contract_classes,
330+
tx_data.contract_instances,
331+
tx_data.contract_addresses,
332+
tx_data.public_data_writes,
333+
rng);
334+
break;
335+
}
312336
// case TxDataMutationType::ContractClassMutation:
313337
// // Mutations here are likely to cause immediate failure
314338
// break;

barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzzer_lib.hpp

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
#include "barretenberg/avm_fuzzer/fuzz_lib/fuzzer_context.hpp"
1010
#include "barretenberg/avm_fuzzer/fuzz_lib/fuzzer_data.hpp"
1111
#include "barretenberg/avm_fuzzer/fuzz_lib/simulator.hpp"
12+
#include "barretenberg/avm_fuzzer/mutations/bytecode.hpp"
13+
#include "barretenberg/avm_fuzzer/mutations/tx_data.hpp"
14+
#include "barretenberg/crypto/merkle_tree/indexed_tree/indexed_leaf.hpp"
1215
#include "barretenberg/serialize/msgpack_impl.hpp"
1316
#include "barretenberg/vm2/common/avm_io.hpp"
1417
#include "barretenberg/vm2/common/aztec_types.hpp"
@@ -28,13 +31,17 @@ struct FuzzerTxData {
2831
GlobalVariables global_variables;
2932
ProtocolContracts protocol_contracts;
3033

34+
// Public data tree writes to be applied during state setup (e.g., for bytecode upgrades)
35+
std::vector<bb::crypto::merkle_tree::PublicDataLeafValue> public_data_writes;
36+
3137
MSGPACK_FIELDS(input_programs,
3238
contract_classes,
3339
contract_instances,
3440
contract_addresses,
3541
tx,
3642
global_variables,
37-
protocol_contracts);
43+
protocol_contracts,
44+
public_data_writes);
3845
};
3946

4047
inline std::ostream& operator<<(std::ostream& os, const FuzzerTxData& data)
@@ -51,16 +58,22 @@ using ContractArtifacts = std::tuple<Bytecode, ContractClass, ContractInstance>;
5158
using FuzzerContext = bb::avm2::fuzzer::FuzzerContext;
5259

5360
// Mutation configuration
54-
enum class TxDataMutationType : uint8_t {
61+
enum class FuzzerTxDataMutationType : uint8_t {
5562
TxMutation,
56-
// todo: implement other mutation types
57-
// BytecodeMutation,
63+
BytecodeMutation,
5864
// ContractClassMutation,
5965
// ContractInstanceMutation,
6066
// GlobalVariablesMutation,
6167
// ProtocolContractsMutation
6268
};
6369

70+
using FuzzerTxDataMutationConfig = WeightedSelectionConfig<FuzzerTxDataMutationType, 2>;
71+
72+
constexpr FuzzerTxDataMutationConfig FUZZER_TX_DATA_MUTATION_CONFIGURATION = FuzzerTxDataMutationConfig({
73+
{ FuzzerTxDataMutationType::TxMutation, 10 },
74+
{ FuzzerTxDataMutationType::BytecodeMutation, 1 },
75+
});
76+
6477
// Build bytecode and contract artifacts from fuzzer data
6578
ContractArtifacts build_bytecode_and_artifacts(FuzzerData& fuzzer_data);
6679

0 commit comments

Comments
 (0)