authorgravatar for squeek502@hotmail.comRyan Liptak <squeek502@hotmail.com> 2025-08-13 23:45:23-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-08-14 17:37:51-07:00
log08f0780cb2e33ae4d1f7059b6f173459b46adc25
treee5428115a5ebb4afb1979289b0060b2cafcc512b
parente252e6c696192729e576fffbd26991bd4b19e434

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

2 files changed, 11 insertions(+), 9 deletions(-)

lib/std/compress/zstd.zig+9
...@@ -156,3 +156,12 @@ test "declared raw literals size too large" {...@@ -156,3 +156,12 @@ test "declared raw literals size too large" {
156 // block can't be valid as it is a raw literals block.156 // block can't be valid as it is a raw literals block.
157 try testExpectDecompressError(error.MalformedLiteralsSection, input_raw);157 try testExpectDecompressError(error.MalformedLiteralsSection, input_raw);
158}158}
159
160test "skippable frame" {
161 const input_raw =
162 "\x50\x2a\x4d\x18" ++ // min magic number for a skippable frame
163 "\x02\x00\x00\x00" ++ // number of bytes to skip
164 "\xFF\xFF"; // the bytes that are skipped
165
166 try testExpectDecompress("", input_raw);
167}
lib/std/compress/zstd/Decompress.zig+2-9
...@@ -155,7 +155,7 @@ fn stream(r: *Reader, w: *Writer, limit: Limit) Reader.StreamError!usize {...@@ -155,7 +155,7 @@ fn stream(r: *Reader, w: *Writer, limit: Limit) Reader.StreamError!usize {
155 const d: *Decompress = @alignCast(@fieldParentPtr("reader", r));155 const d: *Decompress = @alignCast(@fieldParentPtr("reader", r));
156 const in = d.input;156 const in = d.input;
157157
158 switch (d.state) {158 state: switch (d.state) {
159 .new_frame => {159 .new_frame => {
160 // Only return EndOfStream when there are exactly 0 bytes remaining on the160 // Only return EndOfStream when there are exactly 0 bytes remaining on the
161 // frame magic. Any partial magic bytes should be considered a failure.161 // 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 {...@@ -174,14 +174,7 @@ fn stream(r: *Reader, w: *Writer, limit: Limit) Reader.StreamError!usize {
174 d.err = err;174 d.err = err;
175 return error.ReadFailed;175 return error.ReadFailed;
176 };176 };
177 return readInFrame(d, w, limit, &d.state.in_frame) catch |err| switch (err) {177 continue :state d.state;
178 error.ReadFailed => return error.ReadFailed,
179 error.WriteFailed => return error.WriteFailed,
180 else => |e| {
181 d.err = e;
182 return error.ReadFailed;
183 },
184 };
185 },178 },
186 .in_frame => |*in_frame| {179 .in_frame => |*in_frame| {
187 return readInFrame(d, w, limit, in_frame) catch |err| switch (err) {180 return readInFrame(d, w, limit, in_frame) catch |err| switch (err) {