fix: bound unauthenticated stanza parsing#723
Conversation
| rand.Read(fileKey) | ||
| if _, err := rand.Read(fileKey); err != nil { | ||
| return nil, fmt.Errorf("failed to generate file key: %w", err) | ||
| } |
There was a problem hiding this comment.
crypto/rand.Read never returns an error (see https://pkg.go.dev/crypto/rand#Read). This change is not necessary.
| rand.Read(nonce) | ||
| if _, err := rand.Read(nonce); err != nil { | ||
| return nil, fmt.Errorf("failed to generate nonce: %w", err) | ||
| } |
There was a problem hiding this comment.
crypto/rand.Read never returns an error (see https://pkg.go.dev/crypto/rand#Read). This change is not necessary.
| rand.Read(fileKey) | ||
| if _, err := rand.Read(fileKey); err != nil { | ||
| return nil, fmt.Errorf("failed to generate file key: %w", err) | ||
| } |
There was a problem hiding this comment.
crypto/rand.Read never returns an error (see https://pkg.go.dev/crypto/rand#Read). This change is not necessary.
| rand.Read(nonce) | ||
| if _, err := rand.Read(nonce); err != nil { | ||
| return nil, fmt.Errorf("failed to generate nonce: %w", err) | ||
| } |
There was a problem hiding this comment.
crypto/rand.Read never returns an error (see https://pkg.go.dev/crypto/rand#Read). This change is not necessary.
| rand.Read(s.Body) | ||
| if _, err := rand.Read(s.Body); err != nil { | ||
| return false, fmt.Errorf("failed to generate grease body: %w", err) | ||
| } |
There was a problem hiding this comment.
crypto/rand.Read never returns an error (see https://pkg.go.dev/crypto/rand#Read). This change is not necessary.
Age file headers are necessarily parsed before the header MAC is authenticated, because the recipient stanza is needed to recover the file key. Previously, stanza lines were read with unbounded ReadBytes calls, stanza bodies were appended without a total size limit, and Parse accumulated recipients until a footer appeared. An attacker who can supply an age file, detached header, or malicious plugin output could force the process to allocate large memory before cryptographic authentication fails.
The fixed code now defines explicit limits in internal/format/format.go:111: a 16 MiB maximum header, 64 KiB maximum line, and 16 MiB maximum stanza body. ReadStanza now uses bounded line reads at internal/format/format.go:181 and internal/format/format.go:201, rejects oversized bodies at internal/format/format.go:216, and Parse rejects oversized headers at internal/format/format.go:280 and internal/format/format.go:301.
Regression tests were added at internal/format/format_test.go:48, internal/format/format_test.go:55, and internal/format/format_test.go:70.
An attack schenario could look like this: A service uses this library to decrypt files uploaded by users. A low-privilege user submits a file beginning with a valid age intro and then supplies many megabytes or gigabytes of recipient stanza text before the unauthenticated footer. Before this fix, the process could grow memory while parsing the header, potentially causing container OOM, request failure, or service degradation.
Impact
Local or service-level denial of service. Confidentiality and cryptographic integrity are not directly bypassed, but availability can be affected wherever untrusted age files or untrusted plugins are processed.