Skip to content

fix(l1): reject short discv5 packet authdata instead of panicking#6964

Open
ElFantasma wants to merge 2 commits into
mainfrom
fix/l1-discv5-short-authdata-panic
Open

fix(l1): reject short discv5 packet authdata instead of panicking#6964
ElFantasma wants to merge 2 commits into
mainfrom
fix/l1-discv5-short-authdata-panic

Conversation

@ElFantasma

Copy link
Copy Markdown
Contributor

Motivation

The discv5 packet-decode path read fixed-size fields out of the peer-controlled authdata before validating its length, so a malformed packet could panic. Because the discv5 server runs as an actor whose runtime catches a handler panic and stops the loop (no restart), a single malformed packet could take the discv5 discovery actor down. The masking key is the destination node id (public), so an ordinary packet can be forged without a session — making the ordinary path reachable unauthenticated.

Two sites:

  • discv5_handle_ordinary extracted the 32-byte src-id via H256::from_slice(&authdata) before the session lookup — panics on any authdata.len() != 32. Reachable unauthenticated (primary).
  • WhoAreYou::decode sliced authdata[..16] before checking length — panics on authdata.len() < 16. Reachable when a matching pending nonce exists.

Description

  • Added Ordinary::src_id(&Packet) -> Result<H256, PacketCodecError> that returns InvalidSize unless authdata.len() == 32, and routed discv5_handle_ordinary through it (?). Ordinary::decode now uses the same helper, so its length check is shared.
  • WhoAreYou::decode now rejects authdata.len() != 24 (16-byte id-nonce + 8-byte enr-seq) up front, before slicing.

Both return an error instead of panicking; the packet dispatch already logs-and-drops handler errors (inspect_err), so a malformed packet is now discarded and the actor keeps running. Handshake decode was already length-guarded.

Adds unit tests: short WHOAREYOU authdata and short ordinary authdata are rejected (previously panicked), and a well-formed 32-byte ordinary authdata still yields the src-id.

Checklist

  • cargo test -p ethrex-p2p discv5 (incl. new authdata tests) and cargo clippy -p ethrex-p2p pass.

@ElFantasma ElFantasma requested a review from a team as a code owner July 6, 2026 19:03
@github-actions github-actions Bot added the L1 Ethereum client label Jul 6, 2026
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown

🤖 Kimi Code Review

This PR correctly addresses a critical DoS vulnerability in the discv5 implementation where malformed packets with short authdata could panic the node via H256::from_slice() and slice indexing operations.

Security Assessment

Vulnerability Class: Unauthenticated remote DoS (panic/crash)
Root Cause: Unchecked buffer lengths before fixed-size array conversions and slicing
Fix Quality: Correct and well-tested

Code Review

crates/networking/p2p/discovery/discv5_handlers.rs

  • Lines 71-74: The switch from direct H256::from_slice() to Ordinary::src_id() properly prevents the panic path. The comment accurately documents the threat model (single-packet DoS against the discovery actor).

crates/networking/p2p/discv5/messages.rs

  • Lines 267-277: The new Ordinary::src_id() method is the correct abstraction—length-checked extraction that returns a proper error instead of panicking.
  • Lines 279-280: Reordering to extract src_id before decryption is safe (authdata is unencrypted header content) and necessary for the session lookup use case.
  • Lines 353-360: The WhoAreYou::decode() length check (24 bytes = 16-byte id-nonce + 8-byte enr-seq) prevents the slicing panic on authdata[..16]. The removal of unnecessary .clone() is a nice cleanup.

Tests

The new test module covers:

  1. Short authdata rejection for WhoAreYou (8 bytes < 24)
  2. Short authdata rejection for Ordinary (8 bytes < 32)
  3. Valid 32-byte acceptance for Ordinary

Suggestion: Add a test for WhoAreYou with excess authdata (e.g., 32 bytes) to ensure the != 24 check rejects over-length as well as under-length data.

Verdict

Approve. The fix is minimal, correct, and security-critical. The length validation aligns with the discv5 specification (EIP-868/EIP-2124) requirements for fixed-size authdata fields.


Automated review by Kimi (Moonshot AI) · kimi-k2.5 · custom prompt

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown

🤖 Claude Code Review

Review: fix(l1): reject short discv5 packet authdata instead of panicking

This is a well-scoped, correct fix for a real unauthenticated DoS. Summary of what I verified:

  • discv5_handle_ordinary (discv5_handlers.rs:74) previously called H256::from_slice(&packet.header.authdata) before the session lookup, so any peer could crash the discv5 actor with a single malformed ordinary packet (flag 0x00, arbitrary-length authdata) — no session/handshake required. This path is now routed through Ordinary::src_id, which explicitly checks authdata.len() != 32 and returns PacketCodecError::InvalidSize (messages.rs:265-271).
  • WhoAreYou::decode (messages.rs:350-361) previously sliced authdata[..16] / authdata[16..] without checking length; a length check (!= 24) is now added up front, making the subsequent try_into() calls infallible in practice.
  • The error propagates via ? through DiscoveryServerError::Discv5Decode(#[from] PacketCodecError) (server.rs:56), and the actor's dispatch loop already wraps discv5_handle_packet with .inspect_err(...) and discards the error (let _ = ... await.inspect_err(...) at server.rs:364), so the fix correctly converts a process-killing panic into a logged-and-dropped packet.
  • Ordinary::decode was refactored to reuse Ordinary::src_id instead of duplicating the length check and slice, which is a nice reduction in duplication and keeps the two call sites (handler + full decode) in sync.
  • Added unit tests cover: short WHOAREYOU authdata, short ordinary authdata, and a well-formed 32-byte case — good coverage of the boundary that was previously unguarded. Test fixture construction matches the real Packet/PacketHeader struct layout.

Minor observations (non-blocking)

  1. Comment verbosity (messages.rs:265-269, messages.rs:351-353, discv5_handlers.rs:71-73): each fix site has a multi-line comment restating the same rationale (peer-controlled length, DoS risk, "before slicing"). Given the PR body already explains this well, one shorter comment per site would suffice — but this is a style nit, not a correctness issue.
  2. In WhoAreYou::decode, since the length is now hard-checked to be exactly 24, the try_into() calls on the two sub-slices can never fail, and the TryFromSliceError variant/#[from] path for this function is effectively dead code. Not a bug, just worth noting the check makes those ?s vestigial (they still compile fine and cause no harm).
  3. Double-check whether any other discv5 code path reads authdata at fixed offsets without a prior length check (e.g. HandshakeAuthdata decode, mentioned in the PR body as "already length-guarded") — the PR asserts this was already safe; I did not independently audit that path in this review pass, so worth a final confirmation before merge if not already done.

Overall: correct, minimal, well-tested fix for a legitimate unauthenticated panic/DoS vector. No consensus, gas, or state-trie implications since this is purely discovery-layer packet parsing.


Automated review by Claude (Anthropic) · sonnet · custom prompt

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown

🤖 Codex Code Review

No findings.

The patch correctly closes two peer-controlled panic paths in discv5 authdata parsing:

Residual gap: I could not run cargo test in this environment because rustup tries to write under /home/runner/.rustup, which is read-only here, so this review is based on static inspection only.


Automated review by OpenAI Codex · gpt-5.4 · custom prompt

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown

Lines of code report

Total lines added: 51
Total lines removed: 0
Total lines changed: 51

Detailed view
+-------------------------------------------------+-------+------+
| File                                            | Lines | Diff |
+-------------------------------------------------+-------+------+
| ethrex/crates/networking/p2p/discv5/messages.rs | 730   | +51  |
+-------------------------------------------------+-------+------+

@greptile-apps

greptile-apps Bot commented Jul 6, 2026

Copy link
Copy Markdown

Greptile Summary

This PR fixes two unauthenticated panic sites in the discv5 packet-decode path where peer-controlled authdata was sliced before its length was validated, allowing a single malformed UDP packet to crash the discv5 discovery actor with no restart.

  • Introduces Ordinary::src_id(packet) -> Result<H256, PacketCodecError> that gates on authdata.len() == 32, routes both discv5_handle_ordinary and Ordinary::decode through it, eliminating the panic in H256::from_slice.
  • WhoAreYou::decode now rejects any authdata whose length is not exactly 24 bytes before slicing, preventing the panic on authdata[..16].
  • Adds three unit tests: short WHOAREYOU authdata, short ordinary authdata (both must return InvalidSize), and a well-formed 32-byte ordinary authdata (must succeed).

Confidence Score: 4/5

Safe to merge — both panic-inducing slice operations are now gated behind explicit length checks that return errors, and the actor's existing error-handling path discards malformed packets without restarting the actor.

The fix correctly closes both panic sites with minimal, targeted changes and includes tests that cover the previously-crashing inputs. The one rough edge is that Ordinary::src_id is called twice per successfully-decrypted packet (once in discv5_handle_ordinary for the session lookup, and again inside Ordinary::decode), introducing a small redundancy. This does not affect correctness or security.

No files require special attention; both changed files are straightforward and the new src_id helper is the single source of truth for the length guard.

Important Files Changed

Filename Overview
crates/networking/p2p/discovery/discv5_handlers.rs Routes ordinary-packet src-id extraction through the new length-checked Ordinary::src_id, preventing unauthenticated panic-based DoS of the discv5 actor; minor redundancy in that src_id is now validated twice per decrypted packet.
crates/networking/p2p/discv5/messages.rs Introduces Ordinary::src_id with an upfront length guard, refactors Ordinary::decode to reuse it, adds an identical guard in WhoAreYou::decode, and adds three targeted unit tests covering short and well-formed authdata; all three panic sites are now error-returning.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[UDP packet received] --> B{flag?}
    B -- 0x00 ordinary --> C[discv5_handle_ordinary]
    B -- 0x01 WHOAREYOU --> D[discv5_handle_who_are_you]
    B -- 0x02 handshake --> E[discv5_handle_handshake]

    C --> F[Ordinary::src_id checks authdata len == 32]
    F -- len != 32 --> G[Err InvalidSize - was panic before fix]
    G --> H[actor logs and drops packet, keeps running]

    F -- len == 32 --> I[session lookup by src_id]
    I -- no session --> J[send WhoAreYou]
    I -- has session --> K[Ordinary::decode]
    K --> L[decrypt and parse message]

    D --> M[WhoAreYou::decode checks authdata len == 24]
    M -- len != 24 --> N[Err InvalidSize - was panic before fix]
    N --> H
    M -- len == 24 --> O[parse id_nonce and enr_seq]

    E --> P[HandshakeAuthdata::decode checks len >= min - already guarded]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[UDP packet received] --> B{flag?}
    B -- 0x00 ordinary --> C[discv5_handle_ordinary]
    B -- 0x01 WHOAREYOU --> D[discv5_handle_who_are_you]
    B -- 0x02 handshake --> E[discv5_handle_handshake]

    C --> F[Ordinary::src_id checks authdata len == 32]
    F -- len != 32 --> G[Err InvalidSize - was panic before fix]
    G --> H[actor logs and drops packet, keeps running]

    F -- len == 32 --> I[session lookup by src_id]
    I -- no session --> J[send WhoAreYou]
    I -- has session --> K[Ordinary::decode]
    K --> L[decrypt and parse message]

    D --> M[WhoAreYou::decode checks authdata len == 24]
    M -- len != 24 --> N[Err InvalidSize - was panic before fix]
    N --> H
    M -- len == 24 --> O[parse id_nonce and enr_seq]

    E --> P[HandshakeAuthdata::decode checks len >= min - already guarded]
Loading

Comments Outside Diff (1)

  1. crates/networking/p2p/discovery/discv5_handlers.rs, line 74-85 (link)

    P2 Redundant src_id() call in decode path

    Ordinary::src_id(&packet) is called here to extract the src-id for the session lookup (line 74), and then Ordinary::decode(&packet, &key) is called later (line 85), which itself immediately calls Self::src_id(packet)? again internally. The length validation and H256 construction happen twice for every packet that reaches the decode path. This is harmless but wasteful; consider threading the already-extracted src_id into Ordinary::decode or adding an internal variant that accepts a pre-validated src-id.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: crates/networking/p2p/discovery/discv5_handlers.rs
    Line: 74-85
    
    Comment:
    **Redundant `src_id()` call in decode path**
    
    `Ordinary::src_id(&packet)` is called here to extract the src-id for the session lookup (line 74), and then `Ordinary::decode(&packet, &key)` is called later (line 85), which itself immediately calls `Self::src_id(packet)?` again internally. The length validation and `H256` construction happen twice for every packet that reaches the decode path. This is harmless but wasteful; consider threading the already-extracted `src_id` into `Ordinary::decode` or adding an internal variant that accepts a pre-validated src-id.
    
    How can I resolve this? If you propose a fix, please make it concise.

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
crates/networking/p2p/discovery/discv5_handlers.rs:74-85
**Redundant `src_id()` call in decode path**

`Ordinary::src_id(&packet)` is called here to extract the src-id for the session lookup (line 74), and then `Ordinary::decode(&packet, &key)` is called later (line 85), which itself immediately calls `Self::src_id(packet)?` again internally. The length validation and `H256` construction happen twice for every packet that reaches the decode path. This is harmless but wasteful; consider threading the already-extracted `src_id` into `Ordinary::decode` or adding an internal variant that accepts a pre-validated src-id.

Reviews (1): Last reviewed commit: "fix(l1): reject short discv5 packet auth..." | Re-trigger Greptile

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

Labels

L1 Ethereum client

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

2 participants