Skip to content

core/parsigex: bound work triggered by received messages - #4637

Open
pinebit wants to merge 2 commits into
mainfrom
pinebit/parsigex-dos-hardening
Open

core/parsigex: bound work triggered by received messages#4637
pinebit wants to merge 2 commits into
mainfrom
pinebit/parsigex-dos-hardening

Conversation

@pinebit

@pinebit pinebit commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

Add three guards before the expensive decode and signature verification in the parsigex handler, so a peer cannot make a node do unbounded work.

  • Message size. Cap the wire size at 32MB via p2p.WithReadLimit. Parsigex registered with no options and so used the 128MB default, while consensus (32MB) and peerinfo (1MB) already set tighter limits. The reader allocates the full declared length before reading the body, so this bounds allocation before parsing.
  • Set cardinality. Reject sets holding more partial signatures than the cluster has validators. ParSignedDataSetFromProto SSZ/JSON unmarshals every entry before any of it is authenticated, so cardinality — not signature verification — drove decode and allocation cost.
  • Expired duties. Drop duties whose deadline has passed. NewDutyGater only bounds how far in the future a duty may be (its doc notes past duties are the Deadliner's job), but the deadliner runs in parsigdb.StoreExternal, downstream of verification. Partial signatures for arbitrarily old slots therefore reached full verification before being discarded, which also meant the work was not rate-limited by the duty schedule.

Duty types that never expire (exits, builder registrations) are exempt from the deadline check. DKG duty slots are sigType constants rather than beacon slots, so the DKG exchanger passes a never-expires deadline func and the number of validators as the set bound.

Note this hardens parsigex only. The same 128MB default applies to the priority protocol and the DKG handlers, and charon disables the libp2p resource manager entirely (libp2p.ResourceManager(new(network.NullResourceManager)) in app/app.go), which leaves inbound concurrent streams unbounded since yamux delegates that limit to the resource manager. Both are worth addressing separately.

category: bug
ticket: none

Add three guards before the expensive decode and signature verification
in the parsigex handler:

- Cap the wire size of a parsigex message at 32MB via p2p.WithReadLimit,
  matching the treatment consensus and peerinfo already receive. The
  protocol previously used the 128MB default.
- Reject sets holding more partial signatures than the cluster has
  validators. Every entry is SSZ/JSON unmarshalled before any of it is
  authenticated, so set cardinality drove decode and allocation cost.
- Drop duties whose deadline has passed. The duty gater only bounds how
  far in the future a duty may be, so partial signatures for arbitrarily
  old slots reached full verification before parsigdb discarded them.

Duty types that never expire (exits, builder registrations) are exempt
from the deadline check. DKG duty slots are sigType constants rather
than beacon slots, so the DKG exchanger passes a never-expires deadline.

category: bug
ticket: none

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR hardens the core/parsigex libp2p handler against inbound-work DoS by bounding allocation and decode/verify work for received messages, and updates DKG/core wiring and tests to provide the new guard parameters (deadline + max set size).

Changes:

  • Add a 32MB per-message read limit for parsigex traffic and introduce pre-decode guards (expired-duty drop + max set cardinality bound).
  • Extend NewParSigEx to accept a duty deadline function and a maximum received set size (cluster validator count), and thread these through the core workflow and DKG exchanger.
  • Add internal tests covering the new handler guard behavior.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
dkg/protocolsteps_internal_test.go Update DKG test to pass validator-bound to the exchanger.
dkg/protocol_reshare.go Pass validator count into the DKG exchanger.
dkg/protocol_replaceoperator.go Pass validator count into the DKG exchanger.
dkg/protocol_removeoperators.go Pass validator count into the DKG exchanger.
dkg/protocol_addoperators.go Pass validator count into the DKG exchanger.
dkg/exchanger.go Extend exchanger constructor, provide non-expiring deadline func for DKG duties, and pass max set size into parsigex.
dkg/exchanger_internal_test.go Update DKG exchanger tests for new constructor signature.
dkg/dkg.go Pass total validator count into exchanger creation.
core/parsigex/parsigex.go Add message read limit + pre-decode guards; extend constructor signature and internal state.
core/parsigex/parsigex_test.go Update integration-style parsigex test to provide deadline/maxSetSize args.
core/parsigex/parsigex_internal_test.go Add unit tests verifying expired-duty and oversized-set handling occurs before verification.
app/app.go Wire deadline function + validator-count bound into core workflow parsigex creation.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread core/parsigex/parsigex.go
Comment thread core/parsigex/parsigex_internal_test.go
Comment thread dkg/exchanger.go Outdated
Apply the read limit last so it holds regardless of caller options,
restore the noopDeadliner doc comment, and assert the test helper builds
a set of the requested cardinality.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

@pinebit
pinebit requested a review from KaloyanTanev August 10, 2026 15:09
@codecov

codecov Bot commented Aug 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.00000% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 58.21%. Comparing base (aea5631) to head (519196b).

Files with missing lines Patch % Lines
app/app.go 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4637      +/-   ##
==========================================
+ Coverage   58.16%   58.21%   +0.05%     
==========================================
  Files         247      247              
  Lines       34056    34066      +10     
==========================================
+ Hits        19807    19830      +23     
+ Misses      11779    11761      -18     
- Partials     2470     2475       +5     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@KaloyanTanev

KaloyanTanev commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

Cap the wire size at 32MB

The duty that can differ the most in size between consensus and parsigex is probably the attestation. Other duties are either the same data or infrequent enough / with small amount of validators. Attestations though agree on 1 single small piece of data during consensus and multiple validators send their partial signatures during parsigex. Have we checked the size of a parsigex for attestations when the cluster runs 1000 validators? And 20 000? I know 20k is not a cluster we have atm, but we have seen inquiries for that big clusters before.

With that said, I'd rather we do this research first, before merging that.

Drop duties whose deadline has passed

This is simply... an optimisation to fail faster, is that right?

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.

3 participants