Skip to content

fix: resolve signed integer overflow UB in CoinJoin priority and timeout#7236

Draft
thepastaclaw wants to merge 3 commits intodashpay:developfrom
thepastaclaw:fix-coinjoin-ub
Draft

fix: resolve signed integer overflow UB in CoinJoin priority and timeout#7236
thepastaclaw wants to merge 3 commits intodashpay:developfrom
thepastaclaw:fix-coinjoin-ub

Conversation

@thepastaclaw
Copy link

Summary

Fix two signed integer overflow UB issues in CoinJoin code, found during fuzz testing.

CalculateAmountPriority (common.h)

The return type is int but the computation -(nInputAmount / COIN) operates on
int64_t values. When nInputAmount is extremely large (e.g. near MAX_MONEY),
the result exceeds INT_MAX and the implicit narrowing to int is undefined
behavior under UBSan.

Fix: Clamp the int64_t result 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 - nTime overflows when the two int64_t values
differ 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

  • Both functions are non-consensus (CoinJoin sort priority and queue timeout only)
  • Neither overflow is exploitable — CoinJoin queue entries require valid MN signatures,
    and the priority function only affects local sort order
  • The fixes preserve identical behavior for all realistic inputs
  • Found via UBSan-instrumented fuzz testing on the ci/fuzz-regression branch

@thepastaclaw
Copy link
Author

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Mar 18, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@github-actions
Copy link

github-actions bot commented Mar 18, 2026

✅ No Merge Conflicts Detected

This PR currently has no conflicts with other open PRs.

@coderabbitai
Copy link

coderabbitai bot commented Mar 18, 2026

Walkthrough

The pull request contains two safety and optimization improvements to the coinjoin module. The first change simplifies timeout validation in IsTimeOutOfBounds by replacing a two-branch conditional check with a single unsigned difference computation that checks if the absolute difference between timestamps exceeds the threshold. The second change adds overflow prevention to CalculateAmountPriority by using std::clamp to constrain the computed value within valid integer range before casting. Both changes are localized modifications with no alterations to function signatures or public APIs.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and accurately summarizes the main change: fixing signed integer overflow undefined behavior in CoinJoin priority and timeout functions.
Description check ✅ Passed The description is directly related to the changeset, providing detailed context about the two UB fixes, their causes, solutions, and validation rationale.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 1d212a1 and 4248612.

📒 Files selected for processing (2)
  • src/coinjoin/coinjoin.cpp
  • src/coinjoin/common.h

Comment on lines 57 to +61
{
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);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

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]>
Copy link
Author

@thepastaclaw thepastaclaw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +60 to +62
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);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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.

Comment on lines +132 to +133
const int64_t val = -(nInputAmount / COIN);
return int(std::clamp<int64_t>(val, std::numeric_limits<int>::min(), std::numeric_limits<int>::max()));
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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.

Copy link
Author

@thepastaclaw thepastaclaw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
@thepastaclaw
Copy link
Author

Updated per knst's feedback: replaced magic numbers in tests with symbolic expressions (-(MAX_MONEY / COIN), -INT_MAX) so the expected values are self-documenting.

Copy link
Author

@thepastaclaw thepastaclaw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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. ✅

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant