rfc822: prevent infinite loop on partial folded header lines #3

Closed
opened 2026-07-21 17:20:15 +02:00 by heiko · 1 comment
Owner

Problem

internal/rfc822/parser.go computes the next newline position as:

i := p.offset + bytes.Index(data[p.offset:], []byte{'\n'})
if i < 0 { ... }

When p.offset > 0 and bytes.Index returns -1, adding the offset can produce a nonnegative i. The “incomplete input” branch is skipped. For a folded header whose continuation bytes have arrived but whose next newline has not, the split function can repeatedly reuse the same offset and spin indefinitely.

This parser processes attacker-controlled email in the intended MTA deployment, so the bug is an availability/CPU-denial-of-service risk.

The unconditional log.Println("XXX:", p.offset) also leaks parser internals to stderr whenever this state occurs.

Checklist: #18 (unsafe integer/sentinel arithmetic).

Proposed fix

Check the relative index before adding the offset:

relative := bytes.IndexByte(data[p.offset:], '\n')
if relative < 0 {
    if atEOF { ... }
    return 0, nil, nil
}
i := p.offset + relative

Re-evaluate offset reset semantics for EOF and malformed headers, and remove the unconditional diagnostic.

Acceptance criteria

  • A reader that supplies a folded header in one-byte or deliberately awkward chunks completes without spinning.
  • EOF in the middle of a continuation line has documented behavior.
  • CRLF and LF fixtures still round-trip.
  • Add a regression test with a timeout and fuzz Parse/scanLines with chunked readers.
  • go test -race -shuffle=on ./... passes.

Reviewed against 948b7a9 on master (Go 1.26.2).

## Problem `internal/rfc822/parser.go` computes the next newline position as: ```go i := p.offset + bytes.Index(data[p.offset:], []byte{'\n'}) if i < 0 { ... } ``` When `p.offset > 0` and `bytes.Index` returns `-1`, adding the offset can produce a nonnegative `i`. The “incomplete input” branch is skipped. For a folded header whose continuation bytes have arrived but whose next newline has not, the split function can repeatedly reuse the same offset and spin indefinitely. This parser processes attacker-controlled email in the intended MTA deployment, so the bug is an availability/CPU-denial-of-service risk. The unconditional `log.Println("XXX:", p.offset)` also leaks parser internals to stderr whenever this state occurs. Checklist: #18 (unsafe integer/sentinel arithmetic). ## Proposed fix Check the relative index before adding the offset: ```go relative := bytes.IndexByte(data[p.offset:], '\n') if relative < 0 { if atEOF { ... } return 0, nil, nil } i := p.offset + relative ``` Re-evaluate offset reset semantics for EOF and malformed headers, and remove the unconditional diagnostic. ## Acceptance criteria - A reader that supplies a folded header in one-byte or deliberately awkward chunks completes without spinning. - EOF in the middle of a continuation line has documented behavior. - CRLF and LF fixtures still round-trip. - Add a regression test with a timeout and fuzz `Parse`/`scanLines` with chunked readers. - `go test -race -shuffle=on ./...` passes. Reviewed against `948b7a9` on `master` (Go 1.26.2).
Author
Owner

The partial folded-header spin is fixed, race-safe regression coverage is present, and the parser fuzz target with seeds is complete.

Closing commit: 403c6ef92db2

The partial folded-header spin is fixed, race-safe regression coverage is present, and the parser fuzz target with seeds is complete. Closing commit: [`403c6ef92db2`](https://forgejo.schlittermann.de/heiko/mail-seal/commit/403c6ef92db2e8e78b998aa6bf78a0f5d1664c43)
heiko closed this issue 2026-07-21 23:07:49 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
heiko/mailseal#3
No description provided.