fix: resolve signed integer overflow UB in CoinJoin priority and timeout#7236
fix: resolve signed integer overflow UB in CoinJoin priority and timeout#7236thepastaclaw wants to merge 3 commits intodashpay:developfrom
Conversation
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
✅ No Merge Conflicts DetectedThis PR currently has no conflicts with other open PRs. |
WalkthroughThe pull request contains two safety and optimization improvements to the coinjoin module. The first change simplifies timeout validation in Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes 🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
📝 Coding Plan
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.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/coinjoin/coinjoin.cpp`:
- Around line 57-61: Run clang-format on the indicated block (or the whole file)
to fix the whitespace/line-wrapping so the ternary expression and return
statement match project style; reformat the block containing current_time, nTime
and COINJOIN_QUEUE_TIMEOUT (the diff calculation and return) using your
project's clang-format config and commit the changes so the Clang Diff Format
Check passes.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 82b1eff3-1f1e-4510-a19f-8c06e26a762b
📒 Files selected for processing (2)
src/coinjoin/coinjoin.cppsrc/coinjoin/common.h
| { | ||
| return current_time - nTime > COINJOIN_QUEUE_TIMEOUT || | ||
| nTime - current_time > COINJOIN_QUEUE_TIMEOUT; | ||
| const uint64_t diff = (current_time > nTime) | ||
| ? static_cast<uint64_t>(current_time) - static_cast<uint64_t>(nTime) | ||
| : static_cast<uint64_t>(nTime) - static_cast<uint64_t>(current_time); | ||
| return diff > static_cast<uint64_t>(COINJOIN_QUEUE_TIMEOUT); |
There was a problem hiding this comment.
Run clang-format on this block to unblock CI.
The pipeline reports formatting differences at Line 57 in this function. Please apply clang-format to this region/file so the Clang Diff Format Check passes.
🧰 Tools
🪛 GitHub Actions: Clang Diff Format Check
[error] 57-57: Clang format differences detected in this file. Please run clang-format to fix formatting issues as shown by the diff.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/coinjoin/coinjoin.cpp` around lines 57 - 61, Run clang-format on the
indicated block (or the whole file) to fix the whitespace/line-wrapping so the
ternary expression and return statement match project style; reformat the block
containing current_time, nTime and COINJOIN_QUEUE_TIMEOUT (the diff calculation
and return) using your project's clang-format config and commit the changes so
the Clang Diff Format Check passes.
4248612 to
dbfbbe6
Compare
CalculateAmountPriority in common.h could overflow when assigning a negated int64_t division result to an int return type with extreme CAmount values. Clamp the result to INT_MIN/INT_MAX before returning. IsTimeOutOfBounds in coinjoin.cpp could overflow on signed subtraction when current_time and nTime differ by more than INT64_MAX. Use unsigned arithmetic to compute the absolute difference safely. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
cb601cd to
ab4bea6
Compare
thepastaclaw
left a comment
There was a problem hiding this comment.
Code Review
Clean, correct two-line fix for signed integer overflow UB in CoinJoin timeout and priority. Both arithmetic changes are mathematically sound and preserve original semantics. The only gap is the absence of regression tests exercising the extreme-value inputs that motivated these fixes.
Reviewed commit: ab4bea6
🟡 2 suggestion(s) | 💬 1 nitpick(s)
1 additional finding
💬 nitpick: Pre-existing: float-to-int implicit narrowing on denomination priority return
src/coinjoin/common.h (line 125)
Line 125 returns (float)COIN / *optDenom * 10000, which undergoes implicit float-to-int conversion. With current denominations the maximum result is ~10,000,000 (for the smallest denomination COIN/1000+1), safely within INT_MAX. But this is the same class of narrowing issue this PR fixes for the nondenom path. Not blocking since current values are safe, but worth noting for consistency.
🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.
In `src/coinjoin/coinjoin.cpp`:
- [SUGGESTION] lines 60-62: No regression test for extreme timestamp values in IsTimeOutOfBounds
The fix is correct: unsigned subtraction after a signed comparison produces the exact mathematical absolute difference for all int64_t pairs, including mixed-sign and extreme values. However, existing tests in coinjoin_queue_tests.cpp (deltas of ±15, ±60) and coinjoin_inouts_tests.cpp (delta of COINJOIN_QUEUE_TIMEOUT+1) only cover small time deltas around realistic timestamps. No test exercises the scenario that triggered the UBSan finding — e.g., current_time = INT64_MAX and nTime = INT64_MIN, or vice versa. A targeted test would prevent regressions and document the fix motivation.
In `src/coinjoin/common.h`:
- [SUGGESTION] lines 132-133: No regression test for CalculateAmountPriority clamp boundary
The clamp is correct. Note that with valid money amounts, the old code would not have overflowed int: -(MAX_MONEY / COIN) = -21000000, well within INT_MIN..INT_MAX. The UB requires nInputAmount > INT_MAX * COIN (~2.1×10¹⁷), which exceeds MAX_MONEY but is representable in int64_t. Since CalculateAmountPriority accepts CAmount (int64_t) without a MoneyRange guard, fuzz testing can reach these values. There are no tests for this function at all — the function is constexpr, so a static_assert or compile-time test could verify the clamping directly.
| const uint64_t diff = (current_time > nTime) ? static_cast<uint64_t>(current_time) - static_cast<uint64_t>(nTime) | ||
| : static_cast<uint64_t>(nTime) - static_cast<uint64_t>(current_time); | ||
| return diff > static_cast<uint64_t>(COINJOIN_QUEUE_TIMEOUT); |
There was a problem hiding this comment.
🟡 Suggestion: No regression test for extreme timestamp values in IsTimeOutOfBounds
The fix is correct: unsigned subtraction after a signed comparison produces the exact mathematical absolute difference for all int64_t pairs, including mixed-sign and extreme values. However, existing tests in coinjoin_queue_tests.cpp (deltas of ±15, ±60) and coinjoin_inouts_tests.cpp (delta of COINJOIN_QUEUE_TIMEOUT+1) only cover small time deltas around realistic timestamps. No test exercises the scenario that triggered the UBSan finding — e.g., current_time = INT64_MAX and nTime = INT64_MIN, or vice versa. A targeted test would prevent regressions and document the fix motivation.
source: ['claude', 'codex']
🤖 Fix this with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.
In `src/coinjoin/coinjoin.cpp`:
- [SUGGESTION] lines 60-62: No regression test for extreme timestamp values in IsTimeOutOfBounds
The fix is correct: unsigned subtraction after a signed comparison produces the exact mathematical absolute difference for all int64_t pairs, including mixed-sign and extreme values. However, existing tests in coinjoin_queue_tests.cpp (deltas of ±15, ±60) and coinjoin_inouts_tests.cpp (delta of COINJOIN_QUEUE_TIMEOUT+1) only cover small time deltas around realistic timestamps. No test exercises the scenario that triggered the UBSan finding — e.g., current_time = INT64_MAX and nTime = INT64_MIN, or vice versa. A targeted test would prevent regressions and document the fix motivation.
| const int64_t val = -(nInputAmount / COIN); | ||
| return int(std::clamp<int64_t>(val, std::numeric_limits<int>::min(), std::numeric_limits<int>::max())); |
There was a problem hiding this comment.
🟡 Suggestion: No regression test for CalculateAmountPriority clamp boundary
The clamp is correct. Note that with valid money amounts, the old code would not have overflowed int: -(MAX_MONEY / COIN) = -21000000, well within INT_MIN..INT_MAX. The UB requires nInputAmount > INT_MAX * COIN (~2.1×10¹⁷), which exceeds MAX_MONEY but is representable in int64_t. Since CalculateAmountPriority accepts CAmount (int64_t) without a MoneyRange guard, fuzz testing can reach these values. There are no tests for this function at all — the function is constexpr, so a static_assert or compile-time test could verify the clamping directly.
source: ['claude', 'codex']
🤖 Fix this with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.
In `src/coinjoin/common.h`:
- [SUGGESTION] lines 132-133: No regression test for CalculateAmountPriority clamp boundary
The clamp is correct. Note that with valid money amounts, the old code would not have overflowed int: -(MAX_MONEY / COIN) = -21000000, well within INT_MIN..INT_MAX. The UB requires nInputAmount > INT_MAX * COIN (~2.1×10¹⁷), which exceeds MAX_MONEY but is representable in int64_t. Since CalculateAmountPriority accepts CAmount (int64_t) without a MoneyRange guard, fuzz testing can reach these values. There are no tests for this function at all — the function is constexpr, so a static_assert or compile-time test could verify the clamping directly.
thepastaclaw
left a comment
There was a problem hiding this comment.
Code Review
Test-only follow-up commit adding comprehensive regression tests for both UB fixes. All prior review suggestions are now addressed: queue_timestamp_extreme_values covers INT64_MIN/INT64_MAX edge cases for IsTimeOutOfBounds, and calculate_amount_priority_clamp (plus two static_asserts) verifies the clamping boundary for CalculateAmountPriority. Tests are well-structured and exercise the exact scenarios that motivated the fixes.
Reviewed commit: 1053f37
1053f37 to
4ef1992
Compare
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
4ef1992 to
4cc88ea
Compare
|
Updated per knst's feedback: replaced magic numbers in tests with symbolic expressions ( |
thepastaclaw
left a comment
There was a problem hiding this comment.
Incremental Review (push 4cc88eaf vs prior reviewed 1053f373)
Delta: 1 file changed — src/test/coinjoin_queue_tests.cpp (4 insertions, 5 deletions)
Test-only commit replacing hardcoded magic numbers with symbolic constants: -21000000 → -(MAX_MONEY / COIN), -2147483647 → -INT_MAX. Pure readability improvement, no behavioral change.
Prior review stands — no new findings. ✅
Summary
Fix two signed integer overflow UB issues in CoinJoin code, found during fuzz testing.
CalculateAmountPriority(common.h)The return type is
intbut the computation-(nInputAmount / COIN)operates onint64_tvalues. WhennInputAmountis extremely large (e.g. nearMAX_MONEY),the result exceeds
INT_MAXand the implicit narrowing tointis undefinedbehavior under UBSan.
Fix: Clamp the
int64_tresult to[INT_MIN, INT_MAX]before returning.This preserves the existing sort ordering for all realistic inputs while making
extreme values well-defined.
IsTimeOutOfBounds(coinjoin.cpp)The expression
current_time - nTimeoverflows when the twoint64_tvaluesdiffer by more than
INT64_MAX(e.g. one large positive, one large negative).Fix: Compute the absolute difference using unsigned arithmetic, which is
well-defined for all inputs.
Validation
and the priority function only affects local sort order
ci/fuzz-regressionbranch