Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
acir_null_deref.test.cpp
Go to the documentation of this file.
1
17#include <gtest/gtest.h>
18#include <memory>
19#include <regex>
20#include <vector>
21
25#include "serde/acir.hpp"
26
27using namespace acir_format;
28
29class AcirNullDerefTest : public ::testing::Test {};
30
36static std::vector<uint8_t> serialize_program_with_circuit(const Acir::Circuit& circuit)
37{
38 msgpack::sbuffer sbuf;
39 msgpack::packer<msgpack::sbuffer> packer(sbuf);
40
41 // ProgramWithoutBrillig = ARRAY[functions, unconstrained_functions]
42 packer.pack_array(2);
43
44 // functions = ARRAY[circuit]
45 packer.pack_array(1);
46 packer.pack(circuit);
47
48 // unconstrained_functions = NIL (std::monostate — ignored by ProgramWithoutBrillig::msgpack_unpack)
49 packer.pack_nil();
50
51 // Prepend format marker 0x03 (FORMAT_MSGPACK_COMPACT)
52 std::vector<uint8_t> buf;
53 buf.reserve(1 + sbuf.size());
54 buf.push_back(0x03);
55 buf.insert(buf.end(), sbuf.data(), sbuf.data() + sbuf.size());
56 return buf;
57}
58
59// ============================================================================
60// Exploit tests: These construct null shared_ptr directly in the struct,
61// bypassing deserialization. They prove the dereference sites are unguarded.
62// ============================================================================
63
64TEST_F(AcirNullDerefTest, AES128Encrypt_NullIV_DirectCircuit_Crashes)
65{
67 .inputs = {},
68 .iv = nullptr,
69 .key = nullptr,
70 .outputs = {},
71 };
72
74 bbfc.value = aes_op;
75
76 Acir::Circuit circuit{
78 .public_parameters = {},
79 .return_values = {},
80 };
81
82 // Dereferences nullptr at acir_to_constraint_buf.cpp:636: *arg.iv
83 EXPECT_DEATH(circuit_serde_to_acir_format(circuit), "");
84}
85
86TEST_F(AcirNullDerefTest, Keccakf1600_NullInputs_DirectCircuit_Crashes)
87{
89 .inputs = nullptr,
90 .outputs = nullptr,
91 };
92
94 bbfc.value = keccak_op;
95
96 Acir::Circuit circuit{
98 .public_parameters = {},
99 .return_values = {},
100 };
101
102 // Dereferences nullptr at acir_to_constraint_buf.cpp:716: *arg.inputs
103 EXPECT_DEATH(circuit_serde_to_acir_format(circuit), "");
104}
105
106TEST_F(AcirNullDerefTest, Sha256Compression_NullInputs_DirectCircuit_Crashes)
107{
109 .inputs = nullptr,
110 .hash_values = nullptr,
111 .outputs = nullptr,
112 };
113
115 bbfc.value = sha_op;
116
117 Acir::Circuit circuit{
119 .public_parameters = {},
120 .return_values = {},
121 };
122
123 // Dereferences nullptr at acir_to_constraint_buf.cpp:644: *arg.inputs
124 EXPECT_DEATH(circuit_serde_to_acir_format(circuit), "");
125}
126
127// ============================================================================
128// Fix tests: These go through the deserialization path (raw bytes or msgpack
129// roundtrip). The fix in conv_fld_from_array rejects NIL before it can become
130// a null shared_ptr, throwing instead of crashing.
131// ============================================================================
132
133TEST_F(AcirNullDerefTest, AES128Encrypt_NullIV_FromBytes_ThrowsAfterFix)
134{
136 .inputs = {},
137 .iv = nullptr, // Serialized as NIL (0xc0)
138 .key = nullptr,
139 .outputs = {},
140 };
141
143 bbfc.value = aes_op;
144
145 Acir::Circuit circuit{
147 .public_parameters = {},
148 .return_values = {},
149 };
150
151 auto buf = serialize_program_with_circuit(circuit);
152
153 // Verify the buffer contains NIL byte (0xc0) from the null shared_ptr
154 size_t nil_count = 0;
155 for (uint8_t b : buf) {
156 if (b == 0xc0) {
157 nil_count++;
158 }
159 }
160 ASSERT_GE(nil_count, 2U) << "Buffer must contain at least 2 NIL (0xc0) bytes for null iv and key";
161
162 // After fix: deserialization rejects NIL for required fields → throws instead of SIGSEGV
163 EXPECT_THROW_WITH_MESSAGE(circuit_buf_to_acir_format(std::move(buf)), "nil value for required field");
164}
165
166TEST_F(AcirNullDerefTest, NullSharedPtr_RejectedByMsgpackRoundtrip)
167{
168 // After the fix, a null shared_ptr encoded as NIL is rejected during deserialization.
169 // conv_fld_from_array detects NIL at the array slot and throws before converting.
171 .inputs = {},
172 .iv = nullptr,
173 .key = nullptr,
174 .outputs = {},
175 };
176
177 // Serialize (null shared_ptr → NIL byte 0xc0)
178 msgpack::sbuffer sbuf;
179 msgpack::pack(sbuf, original);
180
181 // Deserialize — should throw because iv slot is NIL
182 auto oh = msgpack::unpack(sbuf.data(), sbuf.size());
184 EXPECT_THROW_WITH_MESSAGE(oh.get().convert(deserialized), "nil value for required field");
185}
#define EXPECT_THROW_WITH_MESSAGE(code, expectedMessageRegex)
Definition assert.hpp:193
FF b
AcirFormat circuit_serde_to_acir_format(Acir::Circuit const &circuit)
Convert an Acir::Circuit into an AcirFormat by processing all the opcodes.
AcirFormat circuit_buf_to_acir_format(std::vector< uint8_t > &&buf)
Convert a buffer representing a circuit into Barretenberg's internal AcirFormat representation.
TEST_F(BoomerangGoblinRecursiveVerifierTests, graph_description_basic)
Construct and check a goblin recursive verification circuit.
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
std::vector< Acir::FunctionInput > inputs
Definition acir.hpp:2928
std::shared_ptr< std::array< Acir::FunctionInput, 25 > > inputs
Definition acir.hpp:3300
std::shared_ptr< std::array< Acir::FunctionInput, 16 > > inputs
Definition acir.hpp:3406
std::variant< AES128Encrypt, AND, XOR, RANGE, Blake2s, Blake3, EcdsaSecp256k1, EcdsaSecp256r1, MultiScalarMul, EmbeddedCurveAdd, Keccakf1600, RecursiveAggregation, Poseidon2Permutation, Sha256Compression > value
Definition acir.hpp:3453
std::vector< Acir::Opcode > opcodes
Definition acir.hpp:4796
Acir::BlackBoxFuncCall value
Definition acir.hpp:4198