fix: eliminate Owner poisoning in own_result by requiring value recovery on error#621
fix: eliminate Owner poisoning in own_result by requiring value recovery on error#621QuantumExplorer wants to merge 4 commits into
Conversation
…ery on error The Owner::own_result method previously consumed the inner value and left the Owner in a poisoned state (inner = None) when the closure returned Err. While analysis confirmed this was not exploitable in the current codebase -- all error paths correctly drop the Walker without accessing the poisoned Owner -- the API was inherently fragile and could easily lead to panics from future code changes. This changes the own_result closure signature from FnOnce(T) -> Result<T, E> to FnOnce(T) -> Result<T, (T, E)>, requiring closures to return the value alongside the error. The Owner now restores the value on error, making poisoning impossible. Corresponding changes to TreeNode::attach and TreeNode::put_value variants ensure they return Self in the error tuple, enabling the recovery pattern throughout the Walker's tree operations. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
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. 📝 WalkthroughWalkthroughChanges implement an error-recovery pattern across the Owner and Tree APIs, where failed operations now return owned values alongside errors instead of discarding them. The Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes 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 |
…ttern The (Self, Error) return pattern is intentional — it enables callers to recover the TreeNode on error. Add #[allow(clippy::result_large_err)] to the five functions using this pattern. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## develop #621 +/- ##
===========================================
+ Coverage 90.70% 90.71% +0.01%
===========================================
Files 182 182
Lines 50734 50880 +146
===========================================
+ Hits 46016 46157 +141
- Misses 4718 4723 +5
🚀 New features to boost your workflow:
|
…sures The ownership-recovery pattern returns (Self, Error) tuples in closures passed to own_result, triggering clippy::result_large_err. This is by design for safe value recovery on error. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
merk/src/tree/mod.rs (1)
893-910:⚠️ Potential issue | 🟠 MajorReturn a hash-consistent node on these new recovery paths.
These methods now expose
Selfin the error tuple, but the error branch runs afterself.inner.kv/feature_typehave been mutated and before anyupdate_hashes*call. That means callers can recover a node whose value changed while its Merkle hashes still describe the old bytes. Either stage the KV mutation until all fallible work succeeds, or recompute the matching hashes before returningErr((self, e)). A small tuple-aware cost helper/macro would also make these four paths much harder to drift apart.💡 Minimal consistency fix
- if let Err(e) = self.just_in_time_tree_node_value_update( + if let Err(e) = self.just_in_time_tree_node_value_update( old_specialized_cost, get_temp_new_value_with_old_flags, update_tree_value_based_on_costs, section_removal_bytes, ) { + self.inner.kv = self.inner.kv.update_hashes().unwrap_add_cost(&mut cost); return Err((self, e)).wrap_with_cost(cost); }For the value-hash variants, use
update_hashes_using_reference_value_hash(value_hash)instead.As per coding guidelines,
Use cost_return_on_error! macro for early returns with cost accumulation in Rust source files.Also applies to: 947-966, 1003-1020, 1062-1082
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@merk/src/tree/mod.rs` around lines 893 - 910, The error branch returns Err((Self, Error)) after mutating self.inner.kv via put_value_no_update_of_hashes and setting self.inner.kv.feature_type, which can leave the node with an updated value but stale Merkle hashes; to fix, either stage the KV mutation until just_in_time_tree_node_value_update and other fallible calls succeed or, if you must mutate early, recompute the matching hashes (call the appropriate update_hashes_* helper — for value-hash variants use update_hashes_using_reference_value_hash(value_hash)) before returning Err((self, e)); also replace the manual Err((self, e)).wrap_with_cost(cost) pattern with the cost_return_on_error! macro to ensure consistent cost accumulation and reduce drift across the similar paths in just_in_time_tree_node_value_update and the surrounding methods (apply this same change to the other affected branches around lines 947–966, 1003–1020, and 1062–1082).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@merk/src/owner.rs`:
- Around line 69-71: Update the doc comments for Owner methods described around
the comments (references: the Owner type and the methods whose docs at the
blocks currently saying "never left in a poisoned state") to say the API is
error-safe (returns Ok or Err((T,E)) and restores the inner value on Err) but
not unwind/panic-safe — a panic inside the user closure can still observe Owner
in a poisoned state; also adjust any panic messages that claim "never poisoned"
to avoid overstating guarantees. Modify the text at both comment locations (the
current blocks around lines ~69–71 and ~127–130) to explicitly state the limit:
restoration is guaranteed for returned Err/Ok paths but not across
panics/unwinding.
---
Outside diff comments:
In `@merk/src/tree/mod.rs`:
- Around line 893-910: The error branch returns Err((Self, Error)) after
mutating self.inner.kv via put_value_no_update_of_hashes and setting
self.inner.kv.feature_type, which can leave the node with an updated value but
stale Merkle hashes; to fix, either stage the KV mutation until
just_in_time_tree_node_value_update and other fallible calls succeed or, if you
must mutate early, recompute the matching hashes (call the appropriate
update_hashes_* helper — for value-hash variants use
update_hashes_using_reference_value_hash(value_hash)) before returning
Err((self, e)); also replace the manual Err((self, e)).wrap_with_cost(cost)
pattern with the cost_return_on_error! macro to ensure consistent cost
accumulation and reduce drift across the similar paths in
just_in_time_tree_node_value_update and the surrounding methods (apply this same
change to the other affected branches around lines 947–966, 1003–1020, and
1062–1082).
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 38b29f68-b3f7-4544-8f66-fd8123a2ffc7
📒 Files selected for processing (3)
merk/src/owner.rsmerk/src/tree/mod.rsmerk/src/tree/walk/mod.rs
Address CodeRabbit review: a panic inside the closure still leaves Owner in a poisoned state since the value is moved out before the call. Update docs to distinguish error-safety (Ok/Err paths) from unwind-safety. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Closing: analysis confirmed this is not exploitable in the current codebase. All error paths drop the Walker without accessing the poisoned Owner. Will add documentation explaining why this is a non-issue instead. |
Summary
Owner::own_resultmethod previously consumed the inner value and left theOwnerin a poisoned state (inner = None) when the closure returnedErrFnOnce(T) -> Result<T, (T, E)>, requiring closures to return the value alongside the error so the Owner can restore it on failureTreeNode::attachandTreeNode::put_valueto returnSelfin error tuplesTest plan
owner::tests::test_own_result_success_preserves_value— verifies value preserved on successowner::tests::test_own_result_error_restores_value— verifies value restored on errorowner::tests::test_own_result_error_restores_modified_value— verifies modified value returned in error is restored🤖 Generated with Claude Code
Summary by CodeRabbit