Skip to content

Commit 1b2d92c

Browse files
committed
feat(avm): avm fuzzer bytecode mutation
1 parent 3d86939 commit 1b2d92c

File tree

10 files changed

+206
-39
lines changed

10 files changed

+206
-39
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,14 @@ SimulatorResult fuzz_against_ts_simulator(FuzzerData& fuzzer_data)
4545

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

5454
ws_mgr->checkpoint();
55-
auto js_result = js_simulator->simulate(*ws_mgr, contract_db, tx);
55+
auto js_result = js_simulator->simulate(*ws_mgr, contract_db, tx, /*public_data_writes=*/{});
5656

5757
ContractDBProxy::reset_instance();
5858

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ SimulatorResult simulate_with_default_tx(std::vector<uint8_t>& bytecode, std::ve
4949

5050
ws_mgr->checkpoint();
5151
try {
52-
auto result = cpp_simulator.simulate(*ws_mgr, contract_db, tx);
52+
auto result = cpp_simulator.simulate(*ws_mgr, contract_db, tx, /*public_data_writes=*/empty_writes);
5353
ws_mgr->revert();
5454
ws_mgr->reset_world_state();
5555
return result;

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

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,12 @@ using namespace bb::avm2::simulation;
3030
using namespace bb::avm2::fuzzer;
3131
using namespace bb::world_state;
3232

33-
// Helper function to serialize simulation request via
34-
std::string serialize_simulation_request(const Tx& tx,
35-
const GlobalVariables& globals,
36-
const FuzzerContractDB& contract_db)
33+
// Helper function to serialize simulation request via msgpack
34+
std::string serialize_simulation_request(
35+
const Tx& tx,
36+
const GlobalVariables& globals,
37+
const FuzzerContractDB& contract_db,
38+
const std::vector<bb::crypto::merkle_tree::PublicDataLeafValue>& public_data_writes)
3739
{
3840
// Build vectors from contract_db
3941
std::vector<ContractClass> classes_vec = contract_db.get_contract_classes();
@@ -46,6 +48,7 @@ std::string serialize_simulation_request(const Tx& tx,
4648
.globals = globals,
4749
.contract_classes = std::move(classes_vec),
4850
.contract_instances = std::move(instances_vec),
51+
.public_data_writes = public_data_writes,
4952
};
5053

5154
auto [buffer, size] = msgpack_encode_buffer(request);
@@ -69,10 +72,13 @@ GlobalVariables create_default_globals()
6972
};
7073
}
7174

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

7783
const PublicSimulatorConfig config{
7884
.skip_fee_enforcement = false,
@@ -137,13 +143,15 @@ void JsSimulator::initialize(std::string& simulator_path)
137143
instance = new JsSimulator(simulator_path);
138144
}
139145

140-
SimulatorResult JsSimulator::simulate([[maybe_unused]] fuzzer::FuzzerWorldStateManager& ws_mgr,
141-
fuzzer::FuzzerContractDB& contract_db,
142-
const Tx& tx)
146+
SimulatorResult JsSimulator::simulate(
147+
[[maybe_unused]] fuzzer::FuzzerWorldStateManager& ws_mgr,
148+
fuzzer::FuzzerContractDB& contract_db,
149+
const Tx& tx,
150+
const std::vector<bb::crypto::merkle_tree::PublicDataLeafValue>& public_data_writes)
143151
{
144152
auto globals = create_default_globals();
145153

146-
std::string serialized = serialize_simulation_request(tx, globals, contract_db);
154+
std::string serialized = serialize_simulation_request(tx, globals, contract_db, public_data_writes);
147155

148156
// Send the request
149157
process.write_line(serialized);

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: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,22 @@ 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

@@ -44,6 +52,15 @@ void setup_fuzzer_state(FuzzerWorldStateManager& ws_mgr, FuzzerContractDB& contr
4452
ws_mgr.register_contract_address(addr);
4553
}
4654
}
55+
56+
// Apply public data tree writes (e.g., for contract instance upgrades)
57+
if (!tx_data.public_data_writes.empty()) {
58+
auto& ws = ws_mgr.get_world_state();
59+
auto fork_id = ws_mgr.get_current_revision().forkId;
60+
for (const auto& write : tx_data.public_data_writes) {
61+
ws.update_public_data(write, fork_id);
62+
}
63+
}
4764
}
4865

4966
void fund_fee_payer(FuzzerWorldStateManager& ws_mgr, const Tx& tx)
@@ -69,7 +86,7 @@ SimulatorResult fuzz_tx(FuzzerWorldStateManager& ws_mgr, FuzzerContractDB& contr
6986

7087
try {
7188
ws_mgr.checkpoint();
72-
cpp_result = cpp_simulator.simulate(ws_mgr, contract_db, tx_data.tx);
89+
cpp_result = cpp_simulator.simulate(ws_mgr, contract_db, tx_data.tx, tx_data.public_data_writes);
7390
ws_mgr.revert();
7491
} catch (const std::exception& e) {
7592
fuzz_info("CppSimulator threw an exception: ", e.what());
@@ -83,7 +100,7 @@ SimulatorResult fuzz_tx(FuzzerWorldStateManager& ws_mgr, FuzzerContractDB& contr
83100
}
84101

85102
ws_mgr.checkpoint();
86-
auto js_result = js_simulator->simulate(ws_mgr, contract_db, tx_data.tx);
103+
auto js_result = js_simulator->simulate(ws_mgr, contract_db, tx_data.tx, tx_data.public_data_writes);
87104

88105
// If the results do not match
89106
if (!compare_simulator_results(cpp_result, js_result)) {
@@ -270,6 +287,7 @@ size_t mutate_tx_data(uint8_t* serialized_fuzzer_data,
270287
tx_data.contract_classes.clear();
271288
tx_data.contract_instances.clear();
272289
tx_data.contract_addresses.clear();
290+
tx_data.public_data_writes.clear();
273291
std::vector<AztecAddress> contract_addresses;
274292

275293
for (auto& fuzzer_data : tx_data.input_programs) {
@@ -297,17 +315,21 @@ size_t mutate_tx_data(uint8_t* serialized_fuzzer_data,
297315
}
298316

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

303320
switch (mutation_choice) {
304-
case TxDataMutationType::TxMutation:
321+
case FuzzerTxDataMutationType::TxMutation:
305322
mutate_tx(tx_data.tx, contract_addresses, rng);
306323
break;
307-
// case TxDataMutationType::BytecodeMutation:
308-
// // todo: Maybe here we can do some direct mutations on the bytecode
309-
// // Mutations here are likely to cause immediate failure
310-
// break;
324+
case FuzzerTxDataMutationType::BytecodeMutation: {
325+
// Mutate bytecode and append public data writes for world state setup
326+
mutate_bytecode(tx_data.contract_classes,
327+
tx_data.contract_instances,
328+
tx_data.contract_addresses,
329+
tx_data.public_data_writes,
330+
rng);
331+
break;
332+
}
311333
// case TxDataMutationType::ContractClassMutation:
312334
// // Mutations here are likely to cause immediate failure
313335
// break;

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

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
#include "barretenberg/avm_fuzzer/common/interfaces/dbs.hpp"
99
#include "barretenberg/avm_fuzzer/fuzz_lib/fuzzer_data.hpp"
1010
#include "barretenberg/avm_fuzzer/fuzz_lib/simulator.hpp"
11+
#include "barretenberg/avm_fuzzer/mutations/bytecode.hpp"
12+
#include "barretenberg/avm_fuzzer/mutations/tx_data.hpp"
13+
#include "barretenberg/crypto/merkle_tree/indexed_tree/indexed_leaf.hpp"
1114
#include "barretenberg/serialize/msgpack_impl.hpp"
1215
#include "barretenberg/vm2/common/avm_io.hpp"
1316
#include "barretenberg/vm2/common/aztec_types.hpp"
@@ -27,13 +30,17 @@ struct FuzzerTxData {
2730
GlobalVariables global_variables;
2831
ProtocolContracts protocol_contracts;
2932

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

3946
inline std::ostream& operator<<(std::ostream& os, const FuzzerTxData& data)
@@ -49,16 +56,22 @@ using Bytecode = std::vector<uint8_t>;
4956
using ContractArtifacts = std::tuple<Bytecode, ContractClass, ContractInstance>;
5057

5158
// Mutation configuration
52-
enum class TxDataMutationType : uint8_t {
59+
enum class FuzzerTxDataMutationType : uint8_t {
5360
TxMutation,
54-
// todo: implement other mutation types
55-
// BytecodeMutation,
61+
BytecodeMutation,
5662
// ContractClassMutation,
5763
// ContractInstanceMutation,
5864
// GlobalVariablesMutation,
5965
// ProtocolContractsMutation
6066
};
6167

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

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
#include "barretenberg/avm_fuzzer/mutations/bytecode.hpp"
2+
3+
#include "barretenberg/crypto/poseidon2/poseidon2.hpp"
4+
#include "barretenberg/vm2/common/aztec_constants.hpp"
5+
#include "barretenberg/vm2/simulation/lib/contract_crypto.hpp"
6+
7+
extern "C" size_t LLVMFuzzerMutate(uint8_t* Data, size_t Size, size_t MaxSize);
8+
9+
namespace bb::avm2::fuzzer {
10+
11+
void mutate_bytecode(std::vector<ContractClass>& contract_classes,
12+
std::vector<ContractInstance>& contract_instances,
13+
const std::vector<AztecAddress>& contract_addresses,
14+
std::vector<bb::crypto::merkle_tree::PublicDataLeafValue>& public_data_writes,
15+
std::mt19937_64& rng)
16+
{
17+
using Poseidon2 = crypto::Poseidon2<crypto::Poseidon2Bn254ScalarFieldParams>;
18+
19+
// Skip if no contracts to mutate
20+
if (contract_classes.empty()) {
21+
return;
22+
}
23+
24+
// Select a random contract
25+
size_t idx = std::uniform_int_distribution<size_t>(0, contract_classes.size() - 1)(rng);
26+
27+
ContractClass& klass = contract_classes[idx];
28+
ContractInstance& instance = contract_instances[idx];
29+
AztecAddress address = contract_addresses[idx];
30+
31+
// Copy bytecode and mutate it, we allow the default byte-wise fuzzing strategy to modify the
32+
// bytecode, including expanding or shrinking it.
33+
std::vector<uint8_t> bytecode = klass.packed_bytecode;
34+
size_t original_size = bytecode.size();
35+
size_t max_size = original_size * 2; // Allow growth up to 2x original size
36+
// We have to resize before calling LLVMFuzzerMutate to ensure there's enough space without writing OOB
37+
// We have to resize after so that the vector's metadata is correct
38+
// LLVMFuzzerMutate is a C function that operates on raw pointers
39+
bytecode.resize(max_size);
40+
size_t new_size = LLVMFuzzerMutate(bytecode.data(), original_size, max_size);
41+
bytecode.resize(new_size); // We need to resize here in case it shrunk
42+
43+
// Compute new bytecode commitment and class ID
44+
FF new_bytecode_commitment = simulation::compute_public_bytecode_commitment(bytecode);
45+
FF new_class_id = simulation::compute_contract_class_id(
46+
klass.artifact_hash, klass.private_functions_root, new_bytecode_commitment);
47+
48+
// Store original class ID before modifications
49+
FF original_class_id = instance.original_contract_class_id;
50+
51+
// Copy into NEW contract class with updated bytecode and id, we don't modify the existing one in case
52+
// other instances refer to it
53+
ContractClass new_class = klass;
54+
new_class.id = new_class_id;
55+
new_class.packed_bytecode = std::move(bytecode);
56+
57+
// Update instance's current class ID to point to the newly upgraded-to class
58+
instance.current_contract_class_id = new_class_id;
59+
60+
// Add the new contract class to the vector (for serialization to TS)
61+
contract_classes.push_back(new_class);
62+
63+
// Compute public data tree writes for UpdateCheck to pass
64+
FF delayed_public_mutable_slot = Poseidon2::hash({ FF(UPDATED_CLASS_IDS_SLOT), address });
65+
66+
// Build preimage
67+
FF metadata = 0; // The lower 32 bits are the timestamp_of_change, we set to 0 so it has "taken effect"
68+
FF hash = Poseidon2::hash({ metadata, original_class_id, new_class_id });
69+
70+
std::array<FF, 4> values = { metadata, original_class_id, new_class_id, hash };
71+
72+
for (size_t i = 0; i < 4; i++) {
73+
FF storage_slot = delayed_public_mutable_slot + i;
74+
FF leaf_slot = Poseidon2::hash(
75+
{ FF(DOM_SEP__PUBLIC_LEAF_INDEX), FF(CONTRACT_INSTANCE_REGISTRY_CONTRACT_ADDRESS), storage_slot });
76+
public_data_writes.push_back(bb::crypto::merkle_tree::PublicDataLeafValue{ leaf_slot, values[i] });
77+
}
78+
}
79+
80+
} // namespace bb::avm2::fuzzer
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#pragma once
2+
3+
#include "barretenberg/crypto/merkle_tree/indexed_tree/indexed_leaf.hpp"
4+
#include "barretenberg/vm2/common/aztec_types.hpp"
5+
#include <random>
6+
#include <vector>
7+
8+
namespace bb::avm2::fuzzer {
9+
10+
void mutate_bytecode(std::vector<ContractClass>& contract_classes,
11+
std::vector<ContractInstance>& contract_instances,
12+
const std::vector<AztecAddress>& contract_addresses,
13+
std::vector<bb::crypto::merkle_tree::PublicDataLeafValue>& public_data_writes,
14+
std::mt19937_64& rng);
15+
16+
} // namespace bb::avm2::fuzzer

0 commit comments

Comments
 (0)