Zig Version
0.16.0-dev.38+37f4bee92
Steps to Reproduce and Observed Behavior
flate.Decompress uses direct_vtable (which sets .discard = discardDirect) only when r.buffer has a length of 0:
|
.vtable = if (buffer.len == 0) &direct_vtable else &indirect_vtable, |
This means that nothing about the discardDirect implementation makes sense, since it all assumes that r.buffer has a length > 0:
|
fn discardDirect(r: *Reader, limit: std.Io.Limit) Reader.Error!usize { |
|
if (r.end + flate.history_len > r.buffer.len) rebase(r, flate.history_len); |
|
var writer: Writer = .{ |
|
.vtable = &.{ |
|
.drain = std.Io.Writer.Discarding.drain, |
|
.sendFile = std.Io.Writer.Discarding.sendFile, |
|
}, |
|
.buffer = r.buffer, |
|
.end = r.end, |
|
}; |
|
defer { |
|
assert(writer.end != 0); |
|
r.end = writer.end; |
|
r.seek = r.end; |
|
} |
|
const n = r.stream(&writer, limit) catch |err| switch (err) { |
|
error.WriteFailed => unreachable, |
|
error.ReadFailed => return error.ReadFailed, |
|
error.EndOfStream => return error.EndOfStream, |
|
}; |
|
assert(n <= @intFromEnum(limit)); |
|
return n; |
|
} |
For example: no matter what, the first if will be true and call into rebase which is guaranteed to hit integer overflow when evaluating this assertion (0 - flate.history_len):
|
assert(capacity <= r.buffer.len - flate.history_len); |
This also means that the rebase implementation doesn't make sense when buffer.len == 0, so direct_vtable using rebaseFallible is also guaranteed illegal behavior if it ever gets called. And the same problem exists for the readVec implementation.
Any Reader function that calls into vtable.discard will reproduce this, but here's one example:
head -c 4096 /dev/zero | gzip > test.gz
const std = @import("std");
test "flate discard direct" {
const compressed = @embedFile("test.gz");
var in: std.Io.Reader = .fixed(compressed);
var decompress: std.compress.flate.Decompress = .init(&in, .gzip, &.{});
_ = try decompress.reader.discardRemaining();
}
$ zig test flate-discard.zig
thread 374932 panic: integer overflow
/home/ryan/Programming/zig/zig/lib/std/compress/flate/Decompress.zig:106:37: 0x1199ca9 in rebase (std.zig)
assert(capacity <= r.buffer.len - flate.history_len);
^
/home/ryan/Programming/zig/zig/lib/std/compress/flate/Decompress.zig:118:57: 0x119926b in discardDirect (std.zig)
if (r.end + flate.history_len > r.buffer.len) rebase(r, flate.history_len);
^
/home/ryan/Programming/zig/zig/lib/std/Io/Reader.zig:273:35: 0x102b6d2 in discardRemaining (std.zig)
offset += r.vtable.discard(r, .unlimited) catch |err| switch (err) {
^
/home/ryan/Programming/zig/tmp/flate-discard.zig:10:47: 0x102b0ec in test.flate streamExact direct (flate-discard.zig)
_ = try decompress.reader.discardRemaining();
^
Expected Behavior
flate.Decompress.direct_vtable to have functions that take buffer.len == 0 into account
Zig Version
0.16.0-dev.38+37f4bee92
Steps to Reproduce and Observed Behavior
flate.Decompressusesdirect_vtable(which sets.discard = discardDirect) only whenr.bufferhas a length of 0:zig/lib/std/compress/flate/Decompress.zig
Line 84 in 9399fcd
This means that nothing about the
discardDirectimplementation makes sense, since it all assumes thatr.bufferhas a length > 0:zig/lib/std/compress/flate/Decompress.zig
Lines 117 to 139 in 9399fcd
For example: no matter what, the first
ifwill be true and call intorebasewhich is guaranteed to hit integer overflow when evaluating this assertion (0 - flate.history_len):zig/lib/std/compress/flate/Decompress.zig
Line 106 in 9399fcd
This also means that the
rebaseimplementation doesn't make sense whenbuffer.len == 0, sodirect_vtableusingrebaseFallibleis also guaranteed illegal behavior if it ever gets called. And the same problem exists for thereadVecimplementation.Any
Readerfunction that calls intovtable.discardwill reproduce this, but here's one example:Expected Behavior
flate.Decompress.direct_vtableto have functions that takebuffer.len == 0into account