Skip to content

security(hello): gate outbound FetchTipSet on strictly-heavier peer weight - #13701

Open
MattHintz wants to merge 1 commit into
filecoin-project:masterfrom
MattHintz:security/hello-weight-gate
Open

security(hello): gate outbound FetchTipSet on strictly-heavier peer weight#13701
MattHintz wants to merge 1 commit into
filecoin-project:masterfrom
MattHintz:security/hello-weight-gate

Conversation

@MattHintz

Copy link
Copy Markdown

Summary

Gate the outbound FetchTipSet dispatched from the hello handler on the peer's advertised HeaviestTipSetWeight being strictly greater than the node's local chain weight. Without the gate any peer that can dial the node can pin one handler goroutine and one outbound-stream slot for the full FetchTipSet deadline (5 s) per hello, by advertising an arbitrary weight.

The gate uses the same hs.cs.GetHeaviestTipSet() + hs.cs.Weight(ctx, ...) primitives already used by SayHello in the same file, so this is a two-primitive local change with no new dependencies.

Motivation and evidence

Full disclosure article, source-code audit, and 4-phase paired-control measurements are published in the following gist:

Headline measurements on a mainnet-state Lotus v1.36.0 daemon (Forest snapshot at height 6,049,680), 50 attacker peer IDs, no rcmgr rejections across ~5.77M attacker streams:

  • 433× amplification on ChainGetBlockMessages p50 (1.46 ms baseline → 633 ms under attack)
  • 1,905× amplification at p95 (2.07 ms → 3,944 ms)
  • Peak RSS 170 GB on real mainnet chain state
  • OOM-kill in 7 s under an 8 GB cgroup cap
  • Chain-sync amplification exceeds the 30 s epoch budget; a recovering node under attack cannot catch up

Coordinated disclosure ran May 26 – July 6 2026 via Immunefi (reports #79973, #80341). The case closed under the program's Sybil-attacks exclusion clause, not on technical merits. The two code paths involved (node/hello/hello.go and chain/exchange/server.go) are unchanged at v1.36.0 HEAD. This PR addresses the first of three fixes proposed in the disclosure article; the other two (per-peer hello concurrency cap; cost-aware chain-exchange throttle) will follow as separate PRs.

What this PR does not do

This PR closes the hello arm of the compound attack. It does not close the chain-exchange arm on its own. A chain-exchange-server work-budget cap is required to fully close the amplification; that will be submitted as a separate change.

Correctness

  • The comparison uses GreaterThan, matching the existing pattern in chain/sync_manager.go (isHeavier, line 495) and chain/sync.go.
  • The gate uses !hmsg.HeaviestTipSetWeight.GreaterThan(ourWeight) so equal weights are also skipped, matching the semantics of the existing SayHello weight-report path.
  • If local weight computation fails, the handler returns early rather than dispatching an unbounded fetch — fail-closed on the error path.
  • ourHead uses hs.cs.GetHeaviestTipSet() which is a cheap in-memory read against the chain store head; no additional I/O is introduced on the fast path.

Backwards compatibility

No protocol wire changes. No changes to the HelloMessage struct or to SayHello. Legitimate peers that advertise heavier weight are unaffected. Peers that advertise equal-or-lighter weight now skip the outbound fetch on the receiving side — this matches what the receiving node would do anyway once FetchTipSet returned, because InformNewHead is only called when ts.TipSet().Height() > 0 and the sync manager only advances on strictly-heavier weight.

Alternatives considered

  1. Per-peer hello concurrency cap. Complementary, submitted as a follow-up PR. Guards against races where the local chain weight briefly lags advertised weight from a legitimate peer during head advancement.
  2. libp2p resource-manager per-protocol cost weighting. Correct systemic fix but requires upstream go-libp2p changes; out of scope for this PR.
  3. Peer-reputation gate before dispatch. Would require a durable scoring store, which the hello handler intentionally avoids to preserve stateless behaviour.

Testing

The disclosure gist includes:

  • measure_sync_stall.py — measurement harness for ChainGetBlockMessages latency, CPU, RSS
  • run_refile_phases.sh — 4-phase experiment orchestrator (P0 baseline, P1 legit-only, P2 sybil-only, P3 compound+legit)
  • isolated_config.toml — isolated-daemon config

I would recommend maintainers reproduce phase P0 vs P3 against a mainnet-state daemon before and after this patch. The expected result with the patch applied: hello-arm handler goroutines no longer block on FetchTipSet, and the RSS/CPU/latency amplification collapses substantially. The chain-exchange arm remains until Fix 3 lands.

Related

Reported by @MattHintz (Immunefi handle: Venator).

…eight

Before this change, the hello handler dispatched an outbound chain-exchange
FetchTipSet whenever any peer's HelloMessage decoded successfully, without
verifying that the peer's advertised HeaviestTipSetWeight was actually
heavier than the node's own local chain weight. An unauthenticated attacker
advertising an arbitrary weight could therefore pin one handler goroutine
and one outbound-stream slot per hello for the full FetchTipSet deadline,
repeated across N attacker-controlled peer IDs.

With the gate in place, a peer that legitimately advertises heavier weight
must have real storage power backing that weight; a Sybil cannot fabricate
weight without producing blocks.

See: https://gist.github.com/MattHintz/3f4a1a82ef60cea6ea2df0fc9751426d
@github-project-automation github-project-automation Bot moved this to 📌 Triage in FilOz Jul 7, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e71ddbf4d8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread node/hello/hello.go
"peer", s.Conn().RemotePeer(), "err", werr)
return
}
if !hmsg.HeaviestTipSetWeight.GreaterThan(ourWeight) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Do not trust the advertised weight for this gate

Because HeaviestTipSetWeight comes directly from the peer's hello message and is not tied to HeaviestTipSet before this check, a malicious peer can advertise any value greater than ourWeight while using a random or unfetchable tipset key. That still passes this branch and reaches FetchTipSet, so the handler and outbound stream can be pinned in the same untrusted-peer scenario this change is intended to block.

Useful? React with 👍 / 👎.

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

Labels

None yet

Projects

Status: ⌨️ In Progress

Development

Successfully merging this pull request may close these issues.

1 participant