Skip to content

Commit d4ebfa8

Browse files
authored
Merge pull request #9880 from squeek502/deflate-construct-errors
deflate: Better Huffman.construct errors and error handling
2 parents 66204b7 + 36f1f4f commit d4ebfa8

File tree

1 file changed

+48
-7
lines changed

1 file changed

+48
-7
lines changed

lib/std/compress/deflate.zig

+48-7
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@ const Huffman = struct {
4545

4646
min_code_len: u16,
4747

48-
fn construct(self: *Huffman, code_length: []const u16) !void {
48+
const ConstructError = error{ Oversubscribed, IncompleteSet };
49+
50+
fn construct(self: *Huffman, code_length: []const u16) ConstructError!void {
4951
for (self.count) |*val| {
5052
val.* = 0;
5153
}
@@ -70,7 +72,7 @@ const Huffman = struct {
7072
// Make sure the number of codes with this length isn't too high.
7173
left -= @as(isize, @bitCast(i16, val));
7274
if (left < 0)
73-
return error.InvalidTree;
75+
return error.Oversubscribed;
7476
}
7577

7678
// Compute the offset of the first symbol represented by a code of a
@@ -125,6 +127,9 @@ const Huffman = struct {
125127

126128
self.last_code = codes[PREFIX_LUT_BITS + 1];
127129
self.last_index = offset[PREFIX_LUT_BITS + 1] - self.count[PREFIX_LUT_BITS + 1];
130+
131+
if (left > 0)
132+
return error.IncompleteSet;
128133
}
129134
};
130135

@@ -324,7 +329,13 @@ pub fn InflateStream(comptime ReaderType: type) type {
324329
try lencode.construct(len_lengths[0..]);
325330

326331
const dist_lengths = [_]u16{5} ** MAXDCODES;
327-
try distcode.construct(dist_lengths[0..]);
332+
distcode.construct(dist_lengths[0..]) catch |err| switch (err) {
333+
// This error is expected because we only compute distance codes
334+
// 0-29, which is fine since "distance codes 30-31 will never actually
335+
// occur in the compressed data" (from section 3.2.6 of RFC1951).
336+
error.IncompleteSet => {},
337+
else => return err,
338+
};
328339
}
329340

330341
self.hlen = &lencode;
@@ -359,7 +370,7 @@ pub fn InflateStream(comptime ReaderType: type) type {
359370
lengths[val] = @intCast(u16, try self.readBits(3));
360371
}
361372

362-
try lencode.construct(lengths[0..]);
373+
lencode.construct(lengths[0..]) catch return error.InvalidTree;
363374
}
364375

365376
// Read the length/literal and distance code length tables.
@@ -408,8 +419,24 @@ pub fn InflateStream(comptime ReaderType: type) type {
408419
if (lengths[256] == 0)
409420
return error.MissingEOBCode;
410421

411-
try self.huffman_tables[0].construct(lengths[0..nlen]);
412-
try self.huffman_tables[1].construct(lengths[nlen .. nlen + ndist]);
422+
self.huffman_tables[0].construct(lengths[0..nlen]) catch |err| switch (err) {
423+
error.Oversubscribed => return error.InvalidTree,
424+
error.IncompleteSet => {
425+
// incomplete code ok only for single length 1 code
426+
if (nlen != self.huffman_tables[0].count[0] + self.huffman_tables[0].count[1]) {
427+
return error.InvalidTree;
428+
}
429+
},
430+
};
431+
self.huffman_tables[1].construct(lengths[nlen .. nlen + ndist]) catch |err| switch (err) {
432+
error.Oversubscribed => return error.InvalidTree,
433+
error.IncompleteSet => {
434+
// incomplete code ok only for single length 1 code
435+
if (ndist != self.huffman_tables[1].count[0] + self.huffman_tables[1].count[1]) {
436+
return error.InvalidTree;
437+
}
438+
},
439+
};
413440

414441
self.hlen = &self.huffman_tables[0];
415442
self.hdist = &self.huffman_tables[1];
@@ -684,8 +711,22 @@ test "distance past beginning of output stream" {
684711

685712
test "inflateStream fuzzing" {
686713
// see https://github.com/ziglang/zig/issues/9842
687-
try std.testing.expectError(error.EndOfStream, testInflate("\x950000"));
714+
try std.testing.expectError(error.EndOfStream, testInflate("\x95\x90=o\xc20\x10\x86\xf30"));
688715
try std.testing.expectError(error.OutOfCodes, testInflate("\x950\x00\x0000000"));
716+
717+
// Huffman.construct errors
718+
// lencode
719+
try std.testing.expectError(error.InvalidTree, testInflate("\x950000"));
720+
try std.testing.expectError(error.InvalidTree, testInflate("\x05000"));
721+
// hlen
722+
try std.testing.expectError(error.InvalidTree, testInflate("\x05\xea\x01\t\x00\x00\x00\x01\x00\\\xbf.\t\x00"));
723+
// hdist
724+
try std.testing.expectError(error.InvalidTree, testInflate("\x05\xe0\x01A\x00\x00\x00\x00\x10\\\xbf."));
725+
726+
// Huffman.construct -> error.IncompleteSet returns that shouldn't give error.InvalidTree
727+
// (like the "empty distance alphabet" test but for ndist instead of nlen)
728+
try std.testing.expectError(error.EndOfStream, testInflate("\x05\xe0\x01\t\x00\x00\x00\x00\x10\\\xbf\xce"));
729+
try testInflate("\x15\xe0\x01\t\x00\x00\x00\x00\x10\\\xbf.0");
689730
}
690731

691732
fn testInflate(data: []const u8) !void {

0 commit comments

Comments
 (0)