From 60b0b2129664d6e97822c8ce815518c6a48f9203 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Thu, 14 Aug 2025 01:12:58 -0700 Subject: [PATCH 1/2] zstd.Decompress: Treat a partial magic number as a failure Previously, the "allow EndOfStream" part of this logic was too permissive. If there are a few dangling bytes at the end of the stream, that should be treated as a bad magic number. The only case where EndOfStream is allowed is when the stream is truly at the end, with exactly zero bytes available. --- lib/std/compress/zstd.zig | 6 ++++++ lib/std/compress/zstd/Decompress.zig | 13 ++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/lib/std/compress/zstd.zig b/lib/std/compress/zstd.zig index 0352a0e1f4ad39bd5d3e1d22e022c65ab554ae6d..dbfb8fce96f7813abb1e815cc621fec2124b10ef 100644 --- a/lib/std/compress/zstd.zig +++ b/lib/std/compress/zstd.zig @@ -121,6 +121,12 @@ test Decompress { try testExpectDecompress(uncompressed, compressed19); } +test "partial magic number" { + const input_raw = + "\x28\xb5\x2f"; // 3 bytes of the 4-byte zstandard frame magic number + try testExpectDecompressError(error.BadMagic, input_raw); +} + test "zero sized raw block" { const input_raw = "\x28\xb5\x2f\xfd" ++ // zstandard frame magic number diff --git a/lib/std/compress/zstd/Decompress.zig b/lib/std/compress/zstd/Decompress.zig index bcb8e3b0dabae2e7c8e7c37bb7b8e6f382177233..6892115ad6ff62fd11470f4ab4de252241a0038b 100644 --- a/lib/std/compress/zstd/Decompress.zig +++ b/lib/std/compress/zstd/Decompress.zig @@ -158,7 +158,18 @@ fn stream(r: *Reader, w: *Writer, limit: Limit) Reader.StreamError!usize { switch (d.state) { .new_frame => { - // Allow error.EndOfStream only on the frame magic. + // Only return EndOfStream when there are exactly 0 bytes remaining on the + // frame magic. Any partial magic bytes should be considered a failure. + in.fill(@sizeOf(Frame.Magic)) catch |err| switch (err) { + error.EndOfStream => { + if (in.bufferedLen() != 0) { + d.err = error.BadMagic; + return error.ReadFailed; + } + return err; + }, + else => |e| return e, + }; const magic = try in.takeEnumNonexhaustive(Frame.Magic, .little); initFrame(d, w.buffer.len, magic) catch |err| { d.err = err; -- 2.54.0 From 353cf1f671b9127e7f425aaf3dcba655117ee45b Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Thu, 14 Aug 2025 01:17:52 -0700 Subject: [PATCH 2/2] zstd.Decompress: Delete unused/impossible "end" state --- lib/std/compress/zstd/Decompress.zig | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/std/compress/zstd/Decompress.zig b/lib/std/compress/zstd/Decompress.zig index 6892115ad6ff62fd11470f4ab4de252241a0038b..0b5a28356794bfd6da71900eb1dcf06b2f7a5394 100644 --- a/lib/std/compress/zstd/Decompress.zig +++ b/lib/std/compress/zstd/Decompress.zig @@ -17,7 +17,6 @@ const State = union(enum) { new_frame, in_frame: InFrame, skipping_frame: usize, - end, const InFrame = struct { frame: Frame, @@ -203,7 +202,6 @@ fn stream(r: *Reader, w: *Writer, limit: Limit) Reader.StreamError!usize { if (remaining.* == 0) d.state = .new_frame; return 0; }, - .end => return error.EndOfStream, } } -- 2.54.0