fix: store_data may cache incorrect owner#2277
Conversation
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. 📝 WalkthroughWalkthroughThreads an Changes
Sequence Diagram(s)mermaid Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
programs/system/tests/cpi_context.rs (2)
350-394:⚠️ Potential issue | 🟠 Major
create_test_instruction_datauses emptynew_address_params, so the owner fix is not exercised.
new_address_paramsisvec![]on line 357. Since the entire point of this PR is fixing owner assignment instore_datafor new addresses, none of the existing tests actually store any new addresses via the fixed code path. This means:
- The fix at
state.rs:136(let owner_bytes = invoking_program) is never reached in tests.- There's no regression coverage for the bug being fixed.
Consider adding a test variant where
new_address_paramsis non-empty and then asserting that the deserializedCpiContextNewAddressParamsAssignedPacked.ownerequalsinvoking_program(not whatever owner the instruction data originally specified).Would you like me to draft a test case that exercises the new address path and verifies the owner is correctly set to
invoking_program?
836-886: 🛠️ Refactor suggestion | 🟠 MajorMalicious input scenario: good coverage for the clearing behavior, but missing owner assertion.
The test at line 846-858 injects malicious data via
store_data, then verifies thefirst_set_contextpath clears it. This is valuable for testing the clear-on-first-set behavior.However, even in this scenario, no new addresses are present (per
create_test_instruction_data), so theinvoking_program→owner_bytespath instore_dataremains untested.programs/system/src/cpi_context/state.rs (1)
138-153:⚠️ Potential issue | 🟡 MinorTest coverage gap: the
new_addresses_eqhelper doesn't verify stored address owners matchinvoking_program.The
NewAddresstrait provides anowner()method (returningOption<&[u8; 32]>), andCpiContextNewAddressParamsAssignedPackedimplements it to return the stored owner. However, the test helper atprograms/system/tests/cpi_context.rs:98-110comparesseed,address_queue_index,address_merkle_tree_account_index,address_merkle_tree_root_index, andassigned_compressed_account_index— but skipsowner().Since this PR's purpose is to fix the owner being cached incorrectly, the test should verify that:
- Addresses are stored with owner equal to the
invoking_programpubkey passed tostore_data().- This is confirmed by deserializing the CPI context account and asserting each address's owner matches the invoking program.
Without this check, the fix lacks test coverage at the unit level.
8716a2e to
80f1448
Compare
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (5)
program-libs/compressed-account/src/instruction_data/zero_copy.rs (2)
540-551: 🧹 Nitpick | 🔵 TrivialDuplicate
owner()implementation — inherent method shadows the trait method.
ZInstructionDataInvokeCpihas two identicalowner()methods: one as an inherentimpl(line 540) and one in theInstructionDatatrait impl (line 585). The inherent method will be called by default on a concreteZInstructionDataInvokeCpivalue, potentially surprising callers who expect the trait method. Since the bodies are identical, consider removing the inherent method and always going through the trait.Also applies to: 585-596
1303-1315:⚠️ Potential issue | 🔴 CriticalThis test will now panic — the
elsebranch callsowner()with no inputs.With the new panic behavior on line 469, the
elsebranch at line 1314 (z_copy.owner()) will panic wheneverinput_compressed_accounts_with_merkle_contextis empty (which happens for 80% of iterations — everyiwherei % 5 != 0).You need to update this test to match the new semantics. The simplest fix: only assert
owner()when inputs are non-empty, and usestd::panic::catch_unwind(or#[should_panic]in a dedicated test) to verify the panic path.🐛 Proposed fix
// Check owner() method returns expected value if !invoke_ref .input_compressed_accounts_with_merkle_context .is_empty() { let expected_owner: Pubkey = invoke_ref .input_compressed_accounts_with_merkle_context[0] .compressed_account .owner; assert_eq!(z_copy.owner(), expected_owner); - } else { - assert_eq!(z_copy.owner(), Pubkey::default()); }programs/system/src/cpi_context/state.rs (1)
127-153: 🧹 Nitpick | 🔵 TrivialThis is the heart of the bug fix — looks correct.
Previously,
owner_byteswas derived frominstruction_data.owner(), which could return an incorrect value (or panic when no input accounts exist). Now it's taken directly from the verifiedinvoking_program, which is the actual program that made the CPI call.Note that
owner_bytesis only used for new addresses (line 144). Input accounts (line 167) and output accounts (line 219) correctly use per-account owners from the instruction data itself — that's the right design since those owners are individually validated.One minor naming nit:
owner_bytesis now aPubkey, not raw bytes. Consider renaming to justownerorinvoking_program_keyfor clarity, though this is non-blocking.✏️ Optional: rename for clarity
- // Use invoking_program as the owner for new addresses - let owner_bytes = invoking_program; + // Use invoking_program as the owner for new addresses + let owner = invoking_program;And update line 144 accordingly:
- owner: owner_bytes, // Use cached owner bytes + owner, // Use invoking program as ownerprograms/system/tests/cpi_context.rs (2)
846-858: 🛠️ Refactor suggestion | 🟠 MajorMalicious data injection scenario updated correctly, but same owner verification gap applies.
The test injects malicious data then overwrites it via
first_set_context. Thestore_datacall at line 857 now correctly passesinvoking_program. However, sinceinstruction_data_eqdoesn't compare theownerfield of new addresses, this test wouldn't detect if a malicious invocation cached a wrong owner that survived the overwrite (in the new address context specifically). The assertions at line 880 verify data integrity but not owner integrity.That said, the
first_set_contextpath clears the account entirely (viadeserialize_cpi_context_account_cleared), so old malicious owner data would be wiped regardless. The scenario is sound — just add the owner assertion as suggested above for completeness.
420-444:⚠️ Potential issue | 🟠 MajorTests don't verify that the
ownerfield is correctly stored in new addresses.This PR fixes a bug where
invoking_programcould be cached as the wrong owner. The tests have been updated to threadinvoking_programthrough all call sites—good. However,new_addresses_eq(lines 103–109) deliberately omits theownerfield from its comparison, even though theNewAddresstrait exposes it. This means the tests don't verify that the stored owner matchesinvoking_program.Add an explicit assertion after deserializing the CPI context to confirm that each new address has
ownerset to the expectedinvoking_program:for addr in cpi_context.new_addresses.iter() { assert_eq!(addr.owner, invoking_program, "owner must match invoking_program"); }Without this check, the fix exists in the code but remains unverified by tests.
program-libs/compressed-account/src/instruction_data/zero_copy.rs
Outdated
Show resolved
Hide resolved
The new_addresses_eq helper was missing owner() comparison, which is critical since this PR fixes store_data caching the incorrect owner. Add unit tests with non-empty new_address_params to verify store_data sets owner to invoking_program on first and subsequent invocations.
Summary by CodeRabbit
Refactor
Bug Fixes
Chores