feat(parser): bound single-file gzip by ratio guard instead of fixed byte cap

Plain gzipped logs (nvidia-bug-report-*.log.gz) routinely exceed the old
50MB decompression cap, which silently dropped the tail. Replace it with a
decompression-ratio bomb guard plus a 1GB absolute memory ceiling.

See ADL-054.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Mikhail Chusavitin
2026-08-27 17:05:33 +03:00
co-authored by Claude Sonnet 5
parent 632067fef5
commit 9d701885da
2 changed files with 141 additions and 15 deletions
+76
View File
@@ -3,6 +3,8 @@ package parser
import (
"archive/tar"
"bytes"
"compress/gzip"
"fmt"
"os"
"path/filepath"
"strings"
@@ -71,6 +73,80 @@ func TestExtractArchiveFromReaderTXT_TruncatedWhenTooLarge(t *testing.T) {
}
}
// TestExtractArchiveFromReaderGZ_NoLongerCapsAt50MB is a regression test for
// a real nvidia-bug-report-*.log.gz dump (single gzipped log file, not a tar)
// whose decompressed content was ~53MB and got silently truncated by the old
// fixed 50MB cap, dropping the tail of the log. A single gzipped log with a
// realistic (low) compression ratio should now come through whole.
func TestExtractArchiveFromReaderGZ_NoLongerCapsAt50MB(t *testing.T) {
var plain bytes.Buffer
for i := 0; plain.Len() < 60*1024*1024; i++ {
fmt.Fprintf(&plain, "Aug 24 13:%02d:%02d avi-hgx-b200-ef01 kernel: some log line %d with varying content xyzxyzxyz\n", i%60, (i*7)%60, i)
}
want := plain.Len()
var gz bytes.Buffer
gw := gzip.NewWriter(&gz)
if _, err := gw.Write(plain.Bytes()); err != nil {
t.Fatalf("write gzip content: %v", err)
}
if err := gw.Close(); err != nil {
t.Fatalf("close gzip writer: %v", err)
}
files, err := ExtractArchiveFromReader(bytes.NewReader(gz.Bytes()), "nvidia-bug-report-host.log.gz")
if err != nil {
t.Fatalf("extract gzip from reader: %v", err)
}
if len(files) != 1 {
t.Fatalf("expected 1 file, got %d", len(files))
}
f := files[0]
if f.Truncated {
t.Fatalf("expected file NOT to be truncated, got message %q", f.TruncatedMessage)
}
if len(f.Content) != want {
t.Fatalf("expected full %d bytes decompressed, got %d", want, len(f.Content))
}
}
// TestExtractArchiveFromReaderGZ_AbortsOnDecompressionBomb verifies the
// ratio-based guard that replaced the fixed byte cap still protects against
// a pathological gzip bomb (tiny compressed input, huge decompressed output)
// without requiring the whole bomb to be decompressed first.
func TestExtractArchiveFromReaderGZ_AbortsOnDecompressionBomb(t *testing.T) {
zeros := make([]byte, 50*1024*1024)
var gz bytes.Buffer
gw := gzip.NewWriter(&gz)
if _, err := gw.Write(zeros); err != nil {
t.Fatalf("write gzip content: %v", err)
}
if err := gw.Close(); err != nil {
t.Fatalf("close gzip writer: %v", err)
}
files, err := ExtractArchiveFromReader(bytes.NewReader(gz.Bytes()), "bomb.log.gz")
if err != nil {
t.Fatalf("extract gzip from reader: %v", err)
}
if len(files) != 1 {
t.Fatalf("expected 1 file, got %d", len(files))
}
f := files[0]
if !f.Truncated {
t.Fatalf("expected bomb to be caught and file marked truncated")
}
if f.TruncatedMessage == "" {
t.Fatalf("expected a truncation message")
}
if len(f.Content) >= len(zeros) {
t.Fatalf("expected bomb guard to abort well before full %d bytes, got %d", len(zeros), len(f.Content))
}
}
func TestIsSupportedArchiveFilename(t *testing.T) {
cases := []struct {
name string