mirror of
https://github.com/luxfi/consensus.git
synced 2026-09-24 00:26:58 +00:00
Re-targeted onto SHIPPED consensus v1.35.30 (cloud go.mod pin: pulsar
v1.9.0, threshold v1.12.1), the surface cloud actually embeds.
Item7(a) - unbounded allocation DoS (MINIMAL, NON-BREAKING):
PulsarRoundSigner.Finalize built the signer bitmap via
pulsarlib.CanonicalSignerSet + singletonBitmap, both of which size a
[]byte at (PartyID/8+1) from an attacker-controlled uint32 PartyID with
no upper bound. A single Round2 partial naming PartyID near
math.MaxUint32 drives a ~512MiB allocation. Fix, entirely consensus-side
(pulsarlib's CanonicalSignerSet signature is left untouched so v1.9.0 need
not change and nothing has to propagate):
- Finalize pre-filters the WHOLE partials slice, rejecting any
PartyID >= s.ValidatorSetSize with the new typed ErrPartyIDOutOfRange
BEFORE CanonicalSignerSet or any allocation. The whole slice (not just
the threshold-chosen subset) is checked so a hostile PartyID cannot
hide behind the first-threshold cut.
- singletonBitmap now takes validatorSetSize and bound-checks before its
allocation (defense-in-depth for any future direct caller).
- PulsarRoundSigner.ValidatorSetSize already existed as a declared field
for exactly this bound; it is now load-bearing.
Severity is LOW for cloud: the embedding's ingestLeg (driver.go) already
rejects any leg unless idx==PartyID and the author is a registered voter,
so PartyID is bounded upstream — this is library hygiene closing the gap
for any caller without that upstream guard.
New tests (dos_regression_test.go): oversized PartyID rejected on the real
Round2 -> Finalize path; rejected even when it would be excluded from the
chosen subset; in-range happy path still succeeds with a bitmap bounded by
ValidatorSetSize.
Item7(b) - structural Verify is not the finalization authority: confirmed
the shipped v1.35.30 surface is already SOUND — QuasarCert.VerifyUnderPolicy
(cert_policy_verify.go) is the policy-driven cryptographic authority and is
the path pkg/wire quantum finality calls (policies.go). The structural
QuasarCert.Verify(validators) has exactly one non-test caller, engine.go's
processBlock self-consistency gate on a cert THIS process just generated —
not a trust decision on adversarial bytes. Hardened its doc comment to say
so and to point at VerifyUnderPolicy, and added a regression lock
(verify_authority_test.go): a forged-but-shaped cert passes structural
Verify but is rejected by VerifyUnderPolicy (reaching and failing the BLS
signature check under a (PQ-off,Hybrid) policy) and by VerifyWithRealKeys /
VerifyWithRealKeysPolaris.
No go.mod/go.sum change. Built and tested green against the clean v1.35.30
dependency set (threshold v1.12.1 / pulsar v1.9.0) — no age go.sum
landmine on this surface.