Commit 08f0780cb2

Ryan Liptak <squeek502@hotmail.com>
2025-08-14 08:45:23
zstd.Decompress.stream: Fix handling of skippable frames in new_frame state
The previous code assumed that `initFrame` during the `new_frame` state would always result in the `in_frame` state, but that's not always the case. `initFrame` can also result in the `skippable_frame` state, which would lead to access of union field 'in_frame' while field 'skipping_frame' is active. Now, the switch is re-entered with the updated state so either case is handled appropriately. Fixes the crashes from https://github.com/ziglang/zig/issues/24817
1 parent e252e6c
Changed files (2)
lib
std
lib/std/compress/zstd/Decompress.zig
@@ -155,7 +155,7 @@ fn stream(r: *Reader, w: *Writer, limit: Limit) Reader.StreamError!usize {
     const d: *Decompress = @alignCast(@fieldParentPtr("reader", r));
     const in = d.input;
 
-    switch (d.state) {
+    state: switch (d.state) {
         .new_frame => {
             // Only return EndOfStream when there are exactly 0 bytes remaining on the
             // frame magic. Any partial magic bytes should be considered a failure.
@@ -174,14 +174,7 @@ fn stream(r: *Reader, w: *Writer, limit: Limit) Reader.StreamError!usize {
                 d.err = err;
                 return error.ReadFailed;
             };
-            return readInFrame(d, w, limit, &d.state.in_frame) catch |err| switch (err) {
-                error.ReadFailed => return error.ReadFailed,
-                error.WriteFailed => return error.WriteFailed,
-                else => |e| {
-                    d.err = e;
-                    return error.ReadFailed;
-                },
-            };
+            continue :state d.state;
         },
         .in_frame => |*in_frame| {
             return readInFrame(d, w, limit, in_frame) catch |err| switch (err) {
lib/std/compress/zstd.zig
@@ -156,3 +156,12 @@ test "declared raw literals size too large" {
     // block can't be valid as it is a raw literals block.
     try testExpectDecompressError(error.MalformedLiteralsSection, input_raw);
 }
+
+test "skippable frame" {
+    const input_raw =
+        "\x50\x2a\x4d\x18" ++ // min magic number for a skippable frame
+        "\x02\x00\x00\x00" ++ // number of bytes to skip
+        "\xFF\xFF"; // the bytes that are skipped
+
+    try testExpectDecompress("", input_raw);
+}