---
id: "GHSA-2wrx-84qj-4pcg"
kind: "vulnerability"
aliases: ["GHSA-2wrx-84qj-4pcg"]
title: "anacrolix torrent has unbounded recursion in the bencode decoder that causes stack-overflow crash and CPU-exhaustion DoS"
severity: "medium"
cvss: 6.5
epss: null
exploited: false
status: "open"
category: "package"
products: [{"name": "github.com/anacrolix/torrent", "fixed": ["1.61.1-0.20260911233437-76452a2c8a2f"], "affected": "< 1.61.1-0.20260911233437-76452a2c8a2f", "ecosystem": "go"}]
ecosystems: ["go"]
tags: ["bittorrent", "cwe-407", "cwe-674", "go", "package", "self-hosted", "torrent"]
not_affected_if: ["no-torrent-parsing", "trusted-input-only"]
published_at: "2026-10-30T22:55:09+00:00"
first_seen_at: "2026-10-30T22:56:53+00:00"
updated_at: "2026-10-30T22:58:44+00:00"
sources: [{"source": "ghsa", "url": "https://github.com/advisories/GHSA-2wrx-84qj-4pcg", "first_seen_at": "2026-10-30T22:56:53+00:00"}]
---

# anacrolix torrent has unbounded recursion in the bencode decoder that causes stack-overflow crash and CPU-exhaustion DoS

The Go library anacrolix/torrent decodes nested bencode lists and dictionaries with no depth limit, so a crafted .torrent file or peer-supplied metadata can overflow the goroutine stack and kill the process. A deeply nested v2 file tree also makes decoding quadratic, pinning a CPU core for a long time.

_Summary, check and fix written by a model from the sources; labelled as such._

## Affected

- go:github.com/anacrolix/torrent affected < 1.61.1-0.20260911233437-76452a2c8a2f fixed 1.61.1-0.20260911233437-76452a2c8a2f

## How to check

- Check whether the project depends on `github.com/anacrolix/torrent` in `go.mod` and note the version.
- Run `go list -m github.com/anacrolix/torrent` to confirm the resolved version.
- Look for code that calls `metainfo.Load`, `metainfo.LoadFromFile`, `AddTorrent` or `AddTorrentFromFile` on untrusted input.

## Fix

- `github.com/anacrolix/torrent` at `1.61.1-0.20260911233437-76452a2c8a2f` or later

## Timeline

- 2026-10-30T22:56:53+00:00 ghsa https://github.com/advisories/GHSA-2wrx-84qj-4pcg

## Documents

- [advisory] Maintainer's advisory: api.github.com https://api.github.com/repos/anacrolix/torrent/security-advisories/GHSA-2wrx-84qj-4pcg
- [advisory] Maintainer's advisory: github.com https://github.com/anacrolix/torrent/security/advisories/GHSA-2wrx-84qj-4pcg
- [fix] Patch: github.com https://github.com/anacrolix/torrent/commit/76452a2c8a2fd9c59d4d4f10fc3495723d538c7b

## From the sources

## Summary

anacrolix/torrent's bencode decoder (`bencode/`) applies **no limit on the nesting depth** of nested dictionaries and lists. Two distinct denial-of-service mechanisms follow from that single root cause, both reachable from a single crafted `.torrent` file or from metadata obtained from a peer on a magnet-link/v2 download:

1. **Stack-overflow process crash.** `Decoder.readOneValue` recurses once per nesting level with no depth bound. A `.torrent` whose `info` value is a list nested a few million levels deep (≈8 MB file) drives the recursion past the 1 GiB goroutine-stack limit and the process dies with an unrecoverable `fatal error: stack overflow`. 2. **Quadratic CPU exhaustion.** `metainfo.FileTree.UnmarshalBencode` (BitTorrent v2) re-parses each subtree at every level, making parse time grow ~O(n²). A v2 info dict with a ~30,000-level file tree (≈0.2 MB) takes ~66 s to decode; 60,000 levels exceeds 90 s of decode time.

Affected: every application embedding `github.com/anacrolix/torrent` that ingests attacker-supplied metainfo - the CLI tools (`cmd/torrent`, `cmd/torrent2`), any server that opens `.torrent` files, or any client that accepts metadata from a peer for a magnet/v2 download. No depth limit has ever existed in the decoder.

## Vulnerability Details

All file:line references are against the pinned commit `4ad31c5170787215c51c6ddaa54abc06b623c11c`.

### Root cause - no recursion/depth limit in the bencode decoder

`bencode/decode.go` implements three independently unbounded recursive parsers. There is no depth counter and no `MaxDepth`/nesting guard anywhere in `bencode/` or `metainfo/`.

**Defect 1 - `readOneValue` recursion (`bencode/decode.go:546-549`)**

```go
case 'd', 'l':
    // read until there is nothing to read
    for d.readOneValue() {
    }                       // <- recurses once per nested dict/list level, unbounded
```

`readOneValue` is invoked by `Decoder.parseUnmarshaler` (`bencode/decode.go:591`) whenever a decoded field implements `bencode.Unmarshaler`, which includes `bencode.Bytes` - the type of `MetaInfo.InfoBytes` (`metainfo/metainfo.go:17`). So decoding a normal `.torrent` routes the entire `info` value through this recursion. Stack usage is ~256 bytes per level (consistent with the observed ~4M-level crash at the 1 GiB goroutine-stack limit), so nesting `l` a few million levels deep exhausts the 1 GiB default goroutine-stack limit. Go's stack overflow is a `fatal error` - it is not a recoverable panic, so the `recover()` in `Decoder.Decode` (`bencode/decode.go:35-55`) cannot intercept it, and the whole process terminates.

Entry points that reach `readOneValue` with attacker-controlled bytes:
- `metainfo.Load` (`metainfo/metainfo.go:35`) / `metainfo.LoadFromFile` (`metainfo.go:50`) - decoding a `.torrent` file; the `info` value becomes a `bencode.Bytes` unmarshaler field.
- `Client.AddTorrent` / `Client.AddTorrentFromFile` -> `metainfo.LoadFromFile` (`client.go:1776` inside `AddTorrentFromFile`), and `TorrentSpec.InfoBytes` -> `setInfoBytesLocked` -> `bencode.Unmarshal(b, &info)` (`torrent.go:638`) - peer-fetched metadata.

For the **remote-peer path** (magnet-link/v2 download), peer-supplied metadata pieces are reassembled and passed to `setInfoBytesLocked` once complete (`torrent.go:1874`). The accepted metadata size is capped at `maxMetadataSize = 16 MiB` (`global.go:18`), which comfortably admits the ≈8 MB Defect-1 payload and the ≈0.2 MB Defect-2 payload, so both defects are reachable from an untrusted peer, not only from a locally-opened `.torrent`.

**Defect 2 - `FileTree.UnmarshalBencode` recursive re-parse (`metainfo/file-tree.go:42-49`)**

```go
for key, bytes := range dir {
    var sub FileTree
    err = sub.UnmarshalBencode(bytes)   // <- recurses once per file-tree level, and each call re-runs
                                        //   bencode.Unmarshal over its entire subtree
```

`FileTree` implements `bencode.Unmarshaler` (`file-tree.go:53`), so the `file tree` field of a v2 info dict (`metainfo/info.go:33`) routes through it. Each level calls `sub.UnmarshalBencode`, which starts with `bencode.Unmarshal(bytes, &dir)` over the *whole remaining subtree*. A file tree nested `D` levels deep therefore performs O(D) `bencode.Unmarshal` calls, each over an O(D)-sized payload - O(D²) total parse work. The steady state is a CPU-bound single goroutine, so the embedding process simply hangs.

Reachability: any `bencode.Unmarshal` into `metainfo.Info`, including the peer-metadata path `setInfoBytesLocked` (`torrent.go:638`) for a magnet/v2 download, and `MetaInfo.UnmarshalInfo` (`metainfo/metainfo.go:61-64`).

### Sanitizers / existing limits that do NOT apply

- `Decoder.MaxStrLen` (`bencode/decode.go:787-791`, default 128 MiB) bounds a *single string value*; it is never consulted by `readOneValue` (`decode.go:564` does `io.CopyN(&d.buf, d.r, length)` with no `MaxStrLen` check) and does not bound dict/list nesting depth.
- The existing fuzzers (`bencode/fuzz_test.go`) only round-trip `interface{}` values; small fuzz inputs cannot synthesize the millions of nesting levels needed to trip the stack limit, which is why this has evaded them.

## Impact

- **Denial of service / availability (complete process crash).** A single ~8 MB `.torrent` file, or ~8 MB of metadata from a malicious peer, crashes the embedding application with `fatal error: stack overflow` (Defect 1). The crash is not recoverable in-process. For a server that ingests user-submitted torrents or runs a long-lived swarm, this is a trivial remote DoS: seed a torrent whose metainfo nests lists deeply, and any client that fetches the metadata dies.
- **Denial of service / availability (CPU starvation).** A ~0.2-0.6 MB v2 info dict with a deeply nested file tree pins a core for ~66 s to minutes and blocks any other decode work sharing the process (Defect 2).

Threat actor: an unauthenticated remote peer (v2/magnet metadata, within the allowed 16 MiB metadata size - `global.go:18`) or a local user opening a malicious `.torrent`. The remote-peer case is the higher-reachability/cloud scenario; the local-file case requires the victim to open one file (conservative `UI:R`). Headline CVSS 3.1 base `CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H` = **6.5** (remote, low-complexity, unauthenticated, requires the victim to open a torrent or receive peer metadata, single-process availability loss); for applications that ingest torrents with no user interaction (programmatic server ingestion), UI becomes `N`, raising it to 7.5. CWE-674 (`Uncontrolled Recursion`) is the precise root cause for Defect 1; CWE-407 (`Inefficient Algorithmic Complexity`) covers Defect 2.

Per-metric justification (headline 6.5 vector, `UI:R`):
- **Attack Vector (AV): Network** - the trigger is peer-supplied metadata (magnet/v2 download, within the 16 MiB metadata budget) or a `.torrent` file opened by any client of the library; no local access required.
- **Attack Complexity (AC): Low** - the payload is a deterministic list-nesting pattern in metainfo; no race, heap grooming, or special environment.
- **Privileges Required (PR): None** - feeding metainfo to the decoder is the unprivileged, public API usage.
- **User Interaction (UI): Required** - the conservative remote scenario needs the victim to open a magnet/torrent (or accept a peer's metadata); programmatic server ingestion removes this (UI:N -> 7.5).
- **Scope (S): Unchanged** - the impacted security authority is the embedding process itself.
- **Confidentiality (C): None** - no data disclosure.
- **Integrity (I): None** - no data modification.
- **Availability (A): High** - Defect 1 terminates the process with `fatal error: stack overflow` (unrecoverable); Defect 2 starves a core for ~66 s+ per 0.2 MB v2 info dict with clean quadratic scaling.

## Reproduction Steps

All runs are local-only within the test host; nothing contacts a network.

### Environment and single harness

The harness is a single Go program (

---

_Free to read at a human pace. An API with keys and an MCP server are in preparation; early access: hello@infrastack.ai_
