Skip to content

Add LangSec bencode validator with bounded grammar#3

Merged
iksnerd merged 1 commit into
mainfrom
langsec-bencode-validator
May 2, 2026
Merged

Add LangSec bencode validator with bounded grammar#3
iksnerd merged 1 commit into
mainfrom
langsec-bencode-validator

Conversation

@iksnerd
Copy link
Copy Markdown
Owner

@iksnerd iksnerd commented May 2, 2026

Summary

Introduces internal/bencode/Validate — a structural walker that bounds nesting depth, string length, list length, dict key count, and total input size before any permissive decoder allocates memory or recurses on the payload. Applied to every untrusted bencode decode site in the codebase.

This is Priority 2 of 3 from the LangSec analysis. P1 (announce recognizer, #2) and P3 (peer wire-protocol state machine) are independent.

What's new

  • internal/bencode/validate.go — pure structural validator that walks bencode bytes without allocating the decoded tree. Three preset Limits:
    • TorrentLimits (16 MiB, depth 64) for .torrent files
    • PeerMessageLimits (1 MiB, depth 16) for BEP 9/10 peer messages
    • TrackerResponseLimits (2 MiB) for tracker replies
  • internal/bencode/validate_test.go — ~50 subtests covering happy paths, malformed bencode (empty, trailing data, leading-zero ints, declared-only oversize strings), and each constraint violation.

Where it's wired

Site Limits Threat
internal/torrent/parse.go (.torrent decode) TorrentLimits Malicious torrent file → unbounded walkFileTree recursion + arbitrary-length string allocation
internal/client/peer.go (BEP 10 ext handshake) PeerMessageLimits Hostile peer → oversized handshake dict
internal/client/metadata.go (BEP 9 metadata header) PeerMessageLimits Hostile peer → malformed metadata-piece header
internal/client/tracker.go (tracker response) TrackerResponseLimits Hostile/compromised tracker → oversized peers blob

walkFileTree also gains an explicit maxFileTreeDepth = 64 guard as defense-in-depth — even if a future caller skips Validate, the recursion can't blow the stack.

Test plan

  • go test ./... — all packages green (bencode, torrent, client, tracker, wl)
  • go vet ./... — clean
  • New negative tests in internal/torrent/parse_test.go — empty input, trailing garbage, unterminated dict, non-bencode, deeply nested all rejected
  • Reviewer: confirm preset Limits are appropriately sized for real-world workloads (esp. TorrentLimits.MaxStrLen = 8 MiB for very long pieces strings)
  • Reviewer: confirm wbencode import alias convention works for the project (avoids package-name collision with github.com/zeebo/bencode)

🤖 Generated with Claude Code

Introduces internal/bencode/Validate — a structural walker that bounds nesting
depth, string length, list length, dict key count, and total input size before
any permissive decoder allocates memory or recurses on the payload. Inspired
by Sassaman & Patterson, "The Science of Insecurity": recognize completely
against a bounded grammar, then execute.

- internal/bencode/validate.go: pure structural validator that walks bencode
  bytes without allocating the decoded tree. Configurable Limits with three
  presets sized for their threat model: TorrentLimits (16 MiB, depth 64),
  PeerMessageLimits (1 MiB, depth 16), TrackerResponseLimits (2 MiB).
- internal/bencode/validate_test.go: ~50 subtests covering happy paths,
  malformed bencode (empty, trailing data, leading-zero ints, declared-only
  oversize strings), and each constraint violation.
- internal/torrent/parse.go: Validate against TorrentLimits before
  bencode.DecodeBytes. walkFileTree gains an explicit depth guard
  (maxFileTreeDepth = 64) as defense-in-depth.
- internal/client/peer.go (BEP 10 extension handshake) and
  internal/client/metadata.go (BEP 9 metadata header): Validate against
  PeerMessageLimits before decoding — these come from attacker-controllable
  peer connections.
- internal/client/tracker.go: Validate against TrackerResponseLimits before
  decoding the tracker reply.
- internal/torrent/parse_test.go: new tests for malformed bencode and
  deeply-nested input rejection.

Closes the unbounded-recursion vector in walkFileTree and kills the
oversized-string-allocation vector in every bencode decode site.
@iksnerd iksnerd merged commit 3a67b9f into main May 2, 2026
1 check passed
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.

1 participant