fix!: multiple traces had ghost row injection vulnerabilities#19480
Conversation
This stack of pull requests is managed by Graphite. Learn more about stacking. |
62a7ade to
5632cdc
Compare
| // if we are not ending, the next is_write_memory_value is equal to current is_write_memory_value or is_write_contract_address | ||
| // since both can't be on at the same time, we can use add. | ||
| NOT_END * (is_write_memory_value + is_write_contract_address - is_write_memory_value') = 0; | ||
| // Force is_write_memory_value to 0 when sel is 0 (prevents ghost row injection attacks) |
There was a problem hiding this comment.
We could instead do sel_should_read_memory = sel * is_write_memory_value * (1 - error_out_of_bounds);. However it might not be necessary, since it does control a permutation, but a read only one.
There was a problem hiding this comment.
I agree it is not a problem because superfluous illegitimate memory reads do not harm.
However, it is cleaner and cost is very minimal.
I would also favor the proposal of @sirasistant to add the gate sel.
| WRITES_OUT_OF_BOUNDS * is_valid_member_enum = 0; | ||
| // Force is_valid_member_enum to 0 when sel is 0 (prevents ghost row injection attacks) | ||
| #[IS_VALID_MEMBER_ENUM_REQUIRES_SEL] | ||
| is_valid_member_enum * (1 - sel) = 0; |
There was a problem hiding this comment.
Should we instead do is_valid_writes_in_bounds*(1-sel) = 0? is upper in the chain of selectors, and we also disable is_valid_member_enum if is_valid_writes_in_bounds is zero
#[IS_VALID_MEMBER_ENUM_ONLY_SET_BY_PRECOMPUTED_LOOKUP]
WRITES_OUT_OF_BOUNDS * is_valid_member_enum = 0;
There was a problem hiding this comment.
I think it is a good idea @sirasistant. I would suggest then that @dbanks12 adds a comment that is_valid_member_enum == 0 outside of the active trace portion as a consequence of
#[IS_VALID_MEMBER_ENUM_ONLY_SET_BY_PRECOMPUTED_LOOKUP] and the new relation.
| NOT_LAST * (sel_should_write_mem' - sel_should_write_mem) = 0; | ||
| // Force sel_should_write_mem to 0 when sel is 0 (prevents ghost row injection attacks) | ||
| #[SEL_SHOULD_WRITE_MEM_REQUIRES_SEL] | ||
| sel_should_write_mem * (1 - sel) = 0; |
| // if we are not ending, the next is_write_memory_value is equal to current is_write_memory_value or is_write_contract_address | ||
| // since both can't be on at the same time, we can use add. | ||
| NOT_END * (is_write_memory_value + is_write_contract_address - is_write_memory_value') = 0; | ||
| // Force is_write_memory_value to 0 when sel is 0 (prevents ghost row injection attacks) |
There was a problem hiding this comment.
I agree it is not a problem because superfluous illegitimate memory reads do not harm.
However, it is cleaner and cost is very minimal.
I would also favor the proposal of @sirasistant to add the gate sel.
| WRITES_OUT_OF_BOUNDS * is_valid_member_enum = 0; | ||
| // Force is_valid_member_enum to 0 when sel is 0 (prevents ghost row injection attacks) | ||
| #[IS_VALID_MEMBER_ENUM_REQUIRES_SEL] | ||
| is_valid_member_enum * (1 - sel) = 0; |
There was a problem hiding this comment.
I think it is a good idea @sirasistant. I would suggest then that @dbanks12 adds a comment that is_valid_member_enum == 0 outside of the active trace portion as a consequence of
#[IS_VALID_MEMBER_ENUM_ONLY_SET_BY_PRECOMPUTED_LOOKUP] and the new relation.
| // ===================================================================== | ||
| // Ghost Row Injection Vulnerability Tests | ||
| // ===================================================================== | ||
| // These tests verify that ghost rows (sel=0) cannot fire permutations. |
There was a problem hiding this comment.
If we keep this one, I would welcome a little comment that it is more of a "defensive/sanity" measure because this is a "memory read" and therefore there is no known exploitability.
5632cdc to
1ae3f53
Compare
1ae3f53 to
1d5608f
Compare
jeanmon
left a comment
There was a problem hiding this comment.
Approved but please address my comment.
| // since both can't be on at the same time, we can use add. | ||
| NOT_END * (is_write_memory_value + is_write_contract_address - is_write_memory_value') = 0; | ||
| // Force is_write_memory_value to 0 when sel is 0 (prevents ghost row injection attacks) | ||
| #[IS_WRITE_MEMORY_VALUE_REQUIRES_SEL] |
There was a problem hiding this comment.
But now @dbanks12 you can remove this one. Because, by definition, we have sel ==0 => sel_should_read_memory == 0.
1d5608f to
5d70edb
Compare
5d70edb to
c5ce137
Compare
Flakey Tests🤖 says: This CI run detected 2 tests that failed, but were tolerated due to a .test_patterns.yml entry. |
BEGIN_COMMIT_OVERRIDE feat(avm security): add static check for isolated/unused columns (#19489) feat(avm): use noop calldata hasher in fast sim (#19495) chore(avm): rename indirect -> addressing mode (#19491) chore(avm): small cursor optimizations chore(avm):! rename indirect -> addressing mode (PIL) (#19493) fix(avm): constraint when unwinding empty call stack (#19485) feat(avm): Fuzz debug log and refactor env getter (#19494) fix!: ecc add predicate completeness bug (#19471) chore(avm): callstackmetadatacollector clarifications (#19490) chore: sanity assert in execution for bytecode id (#19486) fix!: sstore allowed injection of malicious write rows (#19470) fix!: defensive ghost row constraints in bc_hashing pil (#19481) fix(avm): fix execution::mov for mac? (#19507) chore(avm)!: resolve execution TODOs (#19501) fix!: multiple traces had ghost row injection vulnerabilities (#19480) fix(avm): defensively copy MemoryValues (#19512) feat: align TS and BB log levels (#19518) END_COMMIT_OVERRIDE

Summary
Fixes ghost row injection vulnerabilities in three PIL traces where sub-selectors firing permutations were not constrained to be 0 when the main selector was inactive.
The vulnerability pattern: When
sel=0(gadget inactive), sub-selectors likeis_write_memory_valuecould still be set to 1 by a malicious prover. This would fire permutations to memory, allowing arbitrary memory writes without going through legitimate execution paths.The fix pattern: Add constraints of the form
sub_selector * (1 - sel) = 0which forces sub-selectors to 0 whensel=0.Changes
PIL Fixes
emit_unencrypted_log.pil#[IS_WRITE_MEMORY_VALUE_REQUIRES_SEL]get_contract_instance.pil#[IS_VALID_WRITES_IN_BOUNDS_REQUIRES_SEL]to_radix_mem.pil#[SEL_SHOULD_WRITE_MEM_REQUIRES_SEL]Test Updates
EXPECT_THROW_WITH_MESSAGEto confirm constraints fail as expectedNegativeFullAttackWithAllTracestoNegativeGhostRowInjectionBlocked