Commit d782968
fix!: bitwise ghost row vulnerablities - start_sha and start_keccak (#19875)
## Summary
- Fix a vulnerability in `bitwise.pil` where `start_keccak` and
`start_sha256` destination selectors can be activated on inactive rows
(`sel=0`), allowing a malicious prover to forge arbitrary XOR/AND
results for keccak and SHA256 hash computations.
- Add exploit tests demonstrating the vulnerability and verifying the
fix.
## Vulnerability
The `start_keccak` and `start_sha256` columns serve as lookup
destination selectors for keccak permutation and SHA256 compression XOR
operations respectively. No constraint prevented these selectors from
being set to 1 on rows where `sel=0`. Since all bitwise polynomial
constraints (including `BYTE_OPERATIONS`) are gated by `sel`, a ghost
row with `sel=0` bypasses all correctness checks while still appearing
as a valid lookup destination.
## Attack Path (Keccak)
1. **Ghost row creation**: The prover fills an unused row (`sel=0`) in
the bitwise subtrace with `start=1`, `start_keccak=1`, and a fake
accumulator output (`acc_ic = FORGED_XOR_RESULT`). A tag mismatch
(`tag_a != tag_b`) forces `err=1`, which sets `sel_get_ctr=0` (skipping
the `INTEGRAL_TAG_LENGTH` lookup), sets `last=1`, and leaves `ctr`
unconstrained (so `ctr=0` implies `sel=0`). All polynomial constraints
are genuinely satisfied on this row.
2. **No conflict with `DISPATCH_TO_BITWISE`**: The execution-to-bitwise
dispatch lookup uses `bitwise.start` as its destination selector. The
ghost row is in this destination table (`start=1`), but its counts
column is 0 (no execution source matches it). In log-derivative lookups,
destination entries with multiplicity 0 are valid — they don't
contribute to the sum.
3. **Keccak lookup satisfied**: The `THETA_XOR_01` lookup uses
`bitwise.start_keccak` as destination. The ghost row has
`start_keccak=1` and matching tuple values (with the forged output). The
counts column is 1, satisfying the lookup for the mutated source row.
4. **Forging the committed column**: `theta_xor_01` in the keccak trace
is a committed polynomial constrained only by the `THETA_XOR_01` lookup
— no polynomial relation independently enforces `theta_xor_01 =
state_in_00 XOR state_in_01`. The ghost row provides the matching
destination entry for an arbitrary value, completely breaking the keccak
permutation.
## Attack Path (SHA256)
1. **Ghost row creation**: Same mechanism as keccak, but with
`start_sha256=1` instead. The prover sets `tag_a=U32, tag_b=U8` (tag
mismatch) to force the error path with `sel=0`.
2. **No conflict with `DISPATCH_TO_BITWISE`**: Same as keccak — the
ghost row has `start=1` with multiplicity 0 for the execution dispatch
lookup.
3. **SHA256 lookup satisfied**: The `W_S_0_XOR_0` lookup (and similar
SHA256 XOR lookups) use `bitwise.start_sha256` as destination. The ghost
row has `start_sha256=1` and matching tuple values with a forged XOR
output.
4. **Forging the committed column**: `w_15_rotr_7_xor_w_15_rotr_18` in
the SHA256 trace is a committed polynomial constrained only by the
`W_S_0_XOR_0` lookup. By providing a forged destination entry, the
attacker corrupts the SHA256 message schedule computation (`sigma_0`),
producing arbitrary compression outputs.
## Fix
Added two constraints to `bitwise.pil`:
```
+#[BITW_START_ONLY_WHEN_SEL]
+(start_keccak + start_sha256) * (1 - sel) = 0;
```
This ensures keccak/sha256 destination selectors can only be active on
rows where `sel=1`, meaning the `BYTE_OPERATIONS` lookup enforces
correct byte-level XOR/AND computation. Note: the more general `start *
(1 - sel) = 0` would be too aggressive since error rows legitimately
need `start=1` with `sel=0` for the execution dispatch.
## Test plan
- [x] Exploit tests (`VulnerabilityStartKeccakWithoutSel`,
`VulnerabilityStartSha256WithoutSel`) that are turned into expected
constraint violation tests after the fix
- [x] Full exploit tests (`VulnerabilityFakeKeccakXorOutput`,
`VulnerabilityFakeSha256XorOutput`) that are turned into expected
constraint violation tests after the fix — ghost rows are rejected
- [x] Existing positive bitwise tests (AND/OR/XOR with tracegen, mixed
operations, error handling) continue to pass
- [x] Keccak and SHA256 integration tests pass (legitimate operations
unaffected by the new constraints)
---------
Co-authored-by: jeanmon <[email protected]>1 parent 42fd086 commit d782968
File tree
7 files changed
+544
-80
lines changed- barretenberg/cpp
- pil/vm2
- src/barretenberg/vm2
- constraining/relations
- generated/relations
- tracegen
7 files changed
+544
-80
lines changed| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2 | 2 | | |
3 | 3 | | |
4 | 4 | | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
5 | 43 | | |
6 | 44 | | |
7 | 45 | | |
| |||
61 | 99 | | |
62 | 100 | | |
63 | 101 | | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
64 | 110 | | |
65 | 111 | | |
66 | 112 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
107 | 107 | | |
108 | 108 | | |
109 | 109 | | |
110 | | - | |
| 110 | + | |
111 | 111 | | |
112 | 112 | | |
113 | 113 | | |
| |||
289 | 289 | | |
290 | 290 | | |
291 | 291 | | |
292 | | - | |
| 292 | + | |
293 | 293 | | |
294 | | - | |
| 294 | + | |
295 | 295 | | |
296 | 296 | | |
297 | 297 | | |
| |||
0 commit comments