-
Notifications
You must be signed in to change notification settings - Fork 581
fix!: multiple traces had ghost row injection vulnerabilities #19480
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
| // 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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