88939 bc medium unauthenticated pq handshakes increase validator cpu consumption by 39 before peer authentication and established connection accounting
Submitted on Aug 15th 2026 at 19:59:19 UTC by @coinsspor for Audit Comp | Quantus
Report ID: #88939
Report Type: Blockchain/DLT
Report severity: Medium
Target: https://github.com/immunefi-team/audit-comp-quantus-chain
Impacts:
Increasing network processing node resource consumption by at least 30% without brute force actions, compared to the preceding 24 hours
Description
Brief/Intro
An unauthenticated remote host can make a Quantus validator perform expensive post-quantum handshake work before peer authentication and before the connection is represented in the established incoming-connection accounting. Sending only the canonical 28-byte multistream negotiation for /multistream/1.0.0 followed by /noise is sufficient to trigger the frozen node's per-connection PQ handshake materialization. Under the default non-reserved P2P policy on the frozen production-profile node, the final ten-pair experiment measured a 39.3084% mean CPU increase, with a Student-t 95% confidence interval of 38.2422%–40.3745%. The conservative lower bound, 38.2422%, remains materially above the selected 30% Medium threshold.
Vulnerability Details
Pre-authentication PQ work
The frozen in-scope chain commit is:
3b243b870a5442a6e5f443056f67bbba87d472f5
The incoming P2P path constructs a new NoiseContext before reading the remote peer's first cryptographic Noise/KEM handshake message.
At construction time, Quantus performs the expensive local PQ preparation used by the handshake, including fresh ML-KEM state and ML-DSA identity/signature materialization.
The attacker-controlled trigger is only the normal multistream protocol selection:
which encodes:
After these 28 unauthenticated bytes are sent, the attacker can stop before providing a valid cryptographic handshake message.
The attacker does not need to generate an ML-KEM key, create an ML-DSA signature, authenticate a peer identity, own a Quantus account, construct a blockchain transaction, or guess/search any credential or secret.
Established-connection accounting does not reserve pending handshakes
Quantus has an incoming-connection limit, but the relevant limiter accounts for entries in the established incoming_connections set.
The effective ordering is:
The expensive PQ work therefore occurs while the remote peer is still unauthenticated and before that peer consumes a slot in the established incoming-connection accounting.
Fresh pending-handshake checks completed 10, 100, and 1,000 simultaneous probes with zero failures. The final impact experiment requires only 400 concurrent sockets.
Normal validator P2P reachability
The issue does not depend on optional public RPC exposure.
The frozen Quantus node's normal P2P configuration exposes TCP P2P listeners for network participation. The final authoritative experiment uses the default non-reserved P2P policy; --reserved-only is not used by the final submission PoC.
Quantus-specific root cause
This report is not asserting a generic TCP or upstream libp2p DoS.
Quantus PR #574 / commit:
d4579e063b281615ca9301b599e9f47620c07b41
vendored litep2p, made it the default networking backend, and moved Quantus PQ handshake materialization into each newly created NoiseContext.
As a supplementary same-machine historical control, the preceding Quantus PQ transport at:
a735879a76d34811c6631b64f529b01b2c195247
was built with:
and tested on the same machine with the identical 28-byte protocol negotiation. It showed a 3.6651% mean CPU increase, with 95% CI [3.4708%, 3.8593%].
Because this historical control is a different commit and was built in release profile, it is not used to establish the selected 30% threshold. It is supporting evidence only, providing evidence that generic TCP/multistream handling alone does not explain the frozen target's approximately 39% increase.
Attacker/victim cost
The final workload opens 4,000 partial handshakes in batches of 400 and sends only:
A separate production-profile cost measurement recorded:
attacker CPU: 5.41 CPU-seconds
incremental victim CPU above baseline: 17.80 CPU-seconds
incremental victim/attacker CPU ratio: 3.29×
The primitive is deterministic protocol-level resource amplification. It does not perform brute-force guessing or search.
Impact Details
Selected in-scope impact:
Increasing network processing node resource consumption by at least 30% without brute force actions, compared to the preceding 24 hours
The resource measured is CPU consumption of the real frozen Quantus node process.
The only attack-series measurement used to establish the selected impact is the final default-policy production-profile experiment.
It alternates ten times on the same long-lived node and CPU affinity:
For every pair:
Final default-policy result:
Mean: 39.3084%
Median: 39.6833%
Sample SD: 1.4905 percentage points
Student-t 95% CI: [38.2422%, 40.3745%]
Conservative lower bound: 38.2422% > 30%
The exact ten raw pairs are supplied as:
default-policy-paired.csv
The corresponding terminal output is supplied as:
default-policy-output.txt
The PoC does not claim that a literal 24-hour capture was performed. It measures the attack against repeated immediately preceding baselines on the same long-lived process. Because the workload is continuously recyclable, the measured steady-state delta can be maintained against a node whose preceding 24-hour workload is its normal baseline.
No stronger impact is claimed. This report does not claim node shutdown, network shutdown, mempool overflow, block stuffing, or unbounded memory growth.
References
Frozen in-scope asset: https://github.com/immunefi-team/audit-comp-quantus-chain/tree/3b243b870a5442a6e5f443056f67bbba87d472f5
PQ handshake implementation: https://github.com/immunefi-team/audit-comp-quantus-chain/blob/3b243b870a5442a6e5f443056f67bbba87d472f5/client/litep2p/src/crypto/noise/mod.rs
Incoming connection-limit accounting: https://github.com/immunefi-team/audit-comp-quantus-chain/blob/3b243b870a5442a6e5f443056f67bbba87d472f5/client/litep2p/src/transport/manager/limits.rs
Default P2P listener configuration: https://github.com/immunefi-team/audit-comp-quantus-chain/blob/3b243b870a5442a6e5f443056f67bbba87d472f5/client/cli/src/params/network_params.rs
Quantus PR #574: https://github.com/Quantus-Network/chain/pull/574
Competition scope: https://immunefi.com/audit-competition/audit-comp-quantus/scope/
Proof of Concept
Scope and Safety
This PoC runs locally only against the frozen in-scope Quantus chain source at:
3b243b870a5442a6e5f443056f67bbba87d472f5
It starts the real Quantus node on 127.0.0.1 and does not contact public/mainnet/testnet infrastructure.
The PoC contains exactly three executable files:
reproduce.shpoc/partial_handshake.pypoc/paired_final.py
No separate attachment is required to understand or reproduce the finding: the full source of all three files and the measured results are included below.
The final authoritative reproduction uses:
the frozen SHA;
--profile production;the default non-reserved P2P policy;
the real Quantus node;
10 alternating baseline/attack pairs;
45-second baseline windows;
4,000 partial
/noisehandshakes per attack window;concurrency 400;
four-second holds;
a Student-t 95% confidence interval;
a hard failure unless the lower 95% CI bound exceeds 30%.
The attacker sends only the canonical 28-byte multistream negotiation selecting /noise and then stops before providing a valid cryptographic handshake message.
1. reproduce.sh
Create reproduce.sh with the following contents:
Make it executable and run it:
The canonical checkout passed to the script must be clean and at:
A cold production-profile build may take a long time.
If a SHA-pinned production binary already exists, the same script supports a faster path:
A successful final run terminates with all of the following markers:
2. poc/partial_handshake.py
Create the directory:
Create poc/partial_handshake.py with the following contents:
This is the final noise-only client.
For every connection it sends only:
The encoded payload is 28 bytes:
The client then deliberately stops before sending a valid Noise/KEM cryptographic handshake message.
It does not contain the discarded development-only TCP-only or large-length/u16max modes.
3. poc/paired_final.py
Create poc/paired_final.py with the following contents:
This harness performs the actual impact measurement:
The percentage for each pair is calculated as:
4. Final Measured Result — Default Non-Reserved P2P Policy
This is the only attack-series result used to establish the selected Medium impact.
The final production-parity run used:
Measured result:
The conservative lower confidence bound exceeds the selected Medium threshold by:
The final submission does not rely on the older development-stage runs or on the earlier reserved-only configuration.
5. Same-Machine Historical Control
As a supplementary same-machine historical control, the preceding Quantus PQ transport at:
was built on the same machine using:
and tested with the same 28-byte protocol negotiation.
Measured result:
This control is supporting evidence only because it is a different commit and was built in release profile rather than the frozen target's production profile.
It is not used as the numerical oracle for the selected >=30% impact.
It provides evidence that generic TCP/multistream handling alone does not explain the frozen target's approximately 39% CPU increase.
6. Attacker/Victim CPU Cost Measurement
A separate production-profile cost experiment measured both attacker and victim CPU.
Measured output:
Therefore:
The attacker sends only:
during the attack window.
No ML-KEM key generation, ML-DSA signing, peer authentication, blockchain account, transaction, password, nonce search, or secret guessing is required from the attacker.
7. Pending-Handshake Concurrency Measurement
Fresh pending-handshake checks produced:
The final selected-impact workload uses only:
The test therefore remains below the successfully demonstrated 1,000-pending-probe level.
8. Why the Established Incoming-Connection Limit Does Not Prevent the Primitive
The relevant order is:
The expensive Quantus-specific PQ work is therefore triggered while the remote peer is still unauthenticated and before the connection consumes a slot in the established incoming-connection accounting.
9. Why This Is Not Brute Force
The PoC does not guess or search for:
a password;
a private key;
a peer identity;
a signature;
a nonce;
a hash preimage;
or any other secret/search-space value.
Each connection deterministically sends the same public 28-byte protocol negotiation and stops before authentication.
Repetition is used only to exercise the protocol-level pre-authentication resource asymmetry.
10. 24-Hour Comparator Statement
The selected program impact is:
Increasing network processing node resource consumption by at least 30% without brute force actions, compared to the preceding 24 hours
The PoC does not claim that a literal 24-hour capture was performed.
It measures the attack against repeated immediately preceding baselines on the same long-lived process. Because the workload is continuously recyclable, the measured steady-state delta can be maintained against a node whose preceding 24-hour workload is its normal baseline.
The final default-policy result is:
which remains materially above the 30% threshold.
11. Clean Reproduction Properties
The final reproduce.sh performs the following checks:
Expected final success markers:
12. Recommended Fix
The primary mitigation is to bound pending handshakes before expensive PQ materialization.
12.1 Reserve pending-handshake capacity before NoiseContext::new()
Introduce a bounded pending-handshake semaphore/counter, optionally combined with a per-source/IP limit, and acquire it before the expensive per-connection PQ setup.
Conceptually:
The security-relevant ordering should become:
rather than:
12.2 Reduce or delay expensive work caused only by /noise selection
Where compatible with the intended PQ protocol, avoid performing all expensive local identity/signature work immediately after only an unauthenticated protocol selector.
If fresh per-connection KEM material is required, retain that property but perform it under the pending-handshake admission bound and delay additional expensive identity/signature materialization until the remote peer has supplied a structurally valid first cryptographic handshake message.
12.3 Keep connection-opening timeout as defense in depth
The existing opening timeout should remain, but it is not sufficient by itself because closed or expired partial sessions can be continuously replaced.
12.4 Add regression tests
Add tests/benchmarks proving that:
pending capacity is reserved before expensive PQ work;
the global pending cap is enforced;
optional per-source limits are enforced;
pending slots are released on success, parse error, remote close, and timeout;
established-connection accounting remains correct;
valid PQ handshakes continue to work; and
repeated partial
/noisenegotiations cannot reproduce the previous >=30% CPU increase outside the configured pending-handshake budget.
13. Final Reproduction Summary
The complete PoC above demonstrates the following chain using the real frozen production node:
The strongest evidence is therefore:
Was this helpful?