Commit 60b0b21296

Ryan Liptak <squeek502@hotmail.com>
2025-08-14 10:12:58
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.
1 parent 59de7e3
Changed files (2)
lib
std
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;
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