Skip to content

Conversation

PastaPastaPasta
Copy link
Member

@PastaPastaPasta PastaPastaPasta commented Sep 30, 2025

Issue being fixed or feature implemented

Avoid potentially undefined behavior in llmq logic by using Assume to crash in debug, and skip in release

What was done?

Describe your changes in detail

How Has This Been Tested?

Please describe in detail how you tested your changes.

Include details of your testing environment, and the tests you ran
to see how your change affects other areas of the code, etc.

Breaking Changes

Please describe any breaking changes your code introduces

Checklist:

Go over all the following points, and put an x in all the boxes that apply.

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated relevant unit/integration/functional/e2e tests
  • I have made corresponding changes to the documentation
  • I have assigned this pull request to a milestone (for repository code-owners and collaborators only)

@PastaPastaPasta PastaPastaPasta added this to the 23 milestone Sep 30, 2025
Copy link

github-actions bot commented Sep 30, 2025

✅ No Merge Conflicts Detected

This PR currently has no conflicts with other open PRs.

Copy link

coderabbitai bot commented Sep 30, 2025

Walkthrough

Adds defensive bounds checks around index-based writes when marking sigShares as announced, batched, or known in src/llmq/signing_shares.cpp. Direct assignments to inv.inv and session.knows.inv are replaced with conditional writes guarded by Assume(...) and explicit index checks to prevent out-of-bounds access. Core logic for building/merging invocation strings and session state is preserved, with state mutations now conditional on valid indices. Also adds an include for util/check.h. No new public interfaces or signature changes.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title clearly and concisely summarizes the main changes to the llmq/signing_shares code by guarding quorumMember indexing with Assume() to prevent out-of-bounds writes and enhancing ToInvString() safety, giving a teammate immediate insight into the PR’s focus.
Description Check ✅ Passed The description identifies the fix to avoid undefined behavior by using Assume() in debug and skip in release and thus relates directly to the changeset, providing meaningful context even though some sections remain unfilled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 969677d and b14d509.

📒 Files selected for processing (1)
  • src/llmq/signing_shares.cpp (4 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
src/**/*.{cpp,h,cc,cxx,hpp}

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.{cpp,h,cc,cxx,hpp}: Dash Core C++ codebase must be written in C++20 and require at least Clang 16 or GCC 11.1
Dash uses unordered_lru_cache for efficient caching with LRU eviction

Files:

  • src/llmq/signing_shares.cpp
🧬 Code graph analysis (1)
src/llmq/signing_shares.cpp (1)
src/test/util_tests.cpp (1)
  • Assume (77-82)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: mac-build / Build source
  • GitHub Check: linux64_nowallet-build / Build source
  • GitHub Check: win64-build / Build source
  • GitHub Check: arm-linux-build / Build source
  • GitHub Check: linux64_tsan-build / Build source
  • GitHub Check: linux64_sqlite-build / Build source
  • GitHub Check: linux64_ubsan-build / Build source
  • GitHub Check: linux64-build / Build source
  • GitHub Check: linux64_fuzz-build / Build source
🔇 Additional comments (2)
src/llmq/signing_shares.cpp (2)

97-99: Good defensive guard against OOB writes.

The Assume() check correctly prevents out-of-bounds writes when sigShare.first exceeds the initialized vector size. In debug builds, this will trigger an assertion failure for early detection. In release builds, the write is safely skipped, preventing memory corruption.


1094-1110: No extra Assume() guards required The writes to session.announced.inv and inv.inv occur inside irange::range(session.announced.inv.size()) loops, which inherently constrain indices to valid bounds.


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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 54e2588 and 969677d.

📒 Files selected for processing (1)
  • src/llmq/signing_shares.cpp (4 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
src/**/*.{cpp,h,cc,cxx,hpp}

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.{cpp,h,cc,cxx,hpp}: Dash Core C++ codebase must be written in C++20 and require at least Clang 16 or GCC 11.1
Dash uses unordered_lru_cache for efficient caching with LRU eviction

Files:

  • src/llmq/signing_shares.cpp
🧬 Code graph analysis (1)
src/llmq/signing_shares.cpp (1)
src/test/util_tests.cpp (1)
  • Assume (77-82)
🪛 GitHub Actions: Clang Diff Format Check
src/llmq/signing_shares.cpp

[error] 17-17: Clang format differences found: expected changes detected by clang-format-diff.py. Please apply formatting (e.g., run the formatter or re-run the diff) to fix code style issues.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: linux64_multiprocess-build / Build source
  • GitHub Check: mac-build / Build source
  • GitHub Check: linux64_tsan-build / Build source
  • GitHub Check: linux64_ubsan-build / Build source
  • GitHub Check: linux64_sqlite-build / Build source
  • GitHub Check: linux64_fuzz-build / Build source
  • GitHub Check: linux64-build / Build source
  • GitHub Check: linux64_nowallet-build / Build source
  • GitHub Check: win64-build / Build source
  • GitHub Check: arm-linux-build / Build source
🔇 Additional comments (3)
src/llmq/signing_shares.cpp (3)

21-21: LGTM! Include added for Assume() macro.

The include is correctly placed and necessary for the bounds-checking guards introduced in this PR.


97-99: LGTM! Bounds check prevents OOB writes.

The Assume() guard correctly prevents out-of-bounds access to inv.inv. In debug builds, this will crash if the condition fails; in release builds, the write is skipped, avoiding undefined behavior.


1094-1110: LGTM! Comprehensive bounds checks prevent OOB access.

The multiple Assume() guards correctly protect all vector accesses using quorumMember as an index:

  • Line 1094: Guards read access to session.knows.inv
  • Lines 1105-1107: Guards write access to inv.inv
  • Lines 1108-1110: Guards write access to session.knows.inv

This defensive approach prevents undefined behavior from out-of-bounds access while allowing the code to skip invalid operations in release builds.

auto& session = nodeState.GetOrCreateSessionFromShare(*sigShare);

if (session.knows.inv[quorumMember]) {
if (Assume(quorumMember < session.knows.inv.size()) && session.knows.inv[quorumMember]) {
Copy link

Choose a reason for hiding this comment

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

Not sure how this can ever happen because invalid sigshare received from another peer shouldn't get this far. We check it in ProcessMessageSigShare:

    if (sigShare.getQuorumMember() >= quorum->members.size()) {
        LogPrint(BCLog::LLMQ_SIGS, "CSigSharesManager::%s -- quorumMember out of bounds\n", __func__);
        BanNode(fromId, peerman);
        return;
    }

We shouldn't create invalid sigshares ourselves either. But anyway... I think we should continue on Assume failure regardless of session.knows.inv. Should probably also drop the invalid session. And maybe also log it so that it wouldn't be a completely silent failure on release nodes. Smth like

Suggested change
if (Assume(quorumMember < session.knows.inv.size()) && session.knows.inv[quorumMember]) {
if (!Assume(quorumMember < session.knows.inv.size())) {
LogPrintf("%s -- BUG! quorumMember >= session.knows.inv.size(), %d >= %d\n", __func__, quorumMember,
session.knows.inv.size());
nodeState.RemoveSession(session.signHash.Get());
continue;
}
if (session.knows.inv[quorumMember]) {

Copy link
Member Author

Choose a reason for hiding this comment

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

Now that I'm looking at it again; what if we wrap these raw datastructs like session.knows.inv in some structure or class and then just expose a method; and internally, it'll either crash or log on out of bounds attempts, and just return false.

Would this be better?

Copy link

Choose a reason for hiding this comment

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

Internal logic won't be able to clean things up though so we could end up in a half-processed state which can potentially cause issues down the line I guess.

Comment on lines +1105 to +1107
if (Assume(quorumMember < inv.inv.size())) {
inv.inv[quorumMember] = true;
}
Copy link

Choose a reason for hiding this comment

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

Same here. Would rather drop all potentially invalid data and continue:

Suggested change
if (Assume(quorumMember < inv.inv.size())) {
inv.inv[quorumMember] = true;
}
if (!Assume(quorumMember < inv.inv.size())) {
LogPrintf("%s -- BUG! quorumMember >= inv.inv.size(), %d >= %d\n", __func__, quorumMember, inv.inv.size());
nodeState.RemoveSession(session.signHash.Get());
sigSharesToAnnounce[nodeId].erase(signHash);
continue;
}
inv.inv[quorumMember] = true;

Comment on lines +1108 to +1110
if (Assume(quorumMember < session.knows.inv.size())) {
session.knows.inv[quorumMember] = true;
}
Copy link

Choose a reason for hiding this comment

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

We just checked it above, no extra Assume is needed

Suggested change
if (Assume(quorumMember < session.knows.inv.size())) {
session.knows.inv[quorumMember] = true;
}
session.knows.inv[quorumMember] = true;

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.

2 participants