Skip to content

Commit

Permalink
add fuzzing tests
Browse files Browse the repository at this point in the history
Using fuzzing tests from zig-std-lib-fuzzing project:
https://github.com/squeek502/zig-std-lib-fuzzing/tree/master/inputs/deflate

Pointed by squeek502 in issues #1, #2, #3, #4.

Fixes #3, #4
  • Loading branch information
ianic committed Feb 8, 2024
1 parent 5b2f446 commit 38f0844
Show file tree
Hide file tree
Showing 29 changed files with 106 additions and 35 deletions.
30 changes: 15 additions & 15 deletions src/bit_reader.zig
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ pub fn BitReader(comptime ReaderType: type) type {
0 => { // `normal` read
try self.fill(n); // ensure that there are n bits in the buffer
const u: U = @truncate(self.bits); // get n bits
self.shift(n); // advance buffer for n
try self.shift(n); // advance buffer for n
return u;
},
(flag.peek) => { // no shift, leave bits in the buffer
Expand All @@ -112,13 +112,13 @@ pub fn BitReader(comptime ReaderType: type) type {
},
flag.buffered => { // no fill, assume that buffer has enought bits
const u: U = @truncate(self.bits);
self.shift(n);
try self.shift(n);
return u;
},
(flag.reverse) => { // same as 0 with bit reverse
try self.fill(n);
const u: U = @truncate(self.bits);
self.shift(n);
try self.shift(n);
return @bitReverse(u);
},
(flag.peek | flag.reverse) => {
Expand All @@ -127,7 +127,7 @@ pub fn BitReader(comptime ReaderType: type) type {
},
(flag.buffered | flag.reverse) => {
const u: U = @truncate(self.bits);
self.shift(n);
try self.shift(n);
return @bitReverse(u);
},
(flag.peek | flag.buffered) => {
Expand All @@ -151,13 +151,13 @@ pub fn BitReader(comptime ReaderType: type) type {
}
const mask: u16 = (@as(u16, 1) << n) - 1;
const u: u16 = @as(u16, @truncate(self.bits)) & mask;
self.shift(n);
try self.shift(n);
return u;
}

// Advance buffer for n bits.
pub inline fn shift(self: *Self, n: u6) void {
assert(n <= self.nbits);
pub inline fn shift(self: *Self, n: u6) !void {
if (n > self.nbits) return error.EndOfStream;
self.bits >>= n;
self.nbits -= n;
}
Expand All @@ -166,7 +166,7 @@ pub fn BitReader(comptime ReaderType: type) type {
pub inline fn skipBytes(self: *Self, n: u16) !void {
for (0..n) |_| {
try self.fill(8);
self.shift(8);
try self.shift(8);
}
}

Expand All @@ -178,7 +178,7 @@ pub fn BitReader(comptime ReaderType: type) type {
// Align stream to the byte boundary.
pub inline fn alignToByte(self: *Self) void {
const ab = self.alignBits();
if (ab > 0) self.shift(ab);
if (ab > 0) self.shift(ab) catch unreachable;
}

// Skip zero terminated string.
Expand Down Expand Up @@ -232,17 +232,17 @@ test "BitReader" {

try testing.expect(try br.readF(u8, F.peek) == 0b0001_1110);
try testing.expect(try br.readF(u9, F.peek) == 0b1_0001_1110);
br.shift(9);
try br.shift(9);
try testing.expectEqual(@as(u8, 36), br.nbits);
try testing.expectEqual(@as(u3, 4), br.alignBits());

try testing.expect(try br.readF(u4, 0) == 0b0100);
try testing.expectEqual(@as(u8, 32), br.nbits);
try testing.expectEqual(@as(u3, 0), br.alignBits());

br.shift(1);
try br.shift(1);
try testing.expectEqual(@as(u3, 7), br.alignBits());
br.shift(1);
try br.shift(1);
try testing.expectEqual(@as(u3, 6), br.alignBits());
br.alignToByte();
try testing.expectEqual(@as(u3, 0), br.alignBits());
Expand Down Expand Up @@ -285,18 +285,18 @@ test "BitReader init" {
var br = bitReader(fbs.reader());

try testing.expectEqual(@as(u64, 0x08_07_06_05_04_03_02_01), br.bits);
br.shift(8);
try br.shift(8);
try testing.expectEqual(@as(u64, 0x00_08_07_06_05_04_03_02), br.bits);
try br.fill(60); // fill with 1 byte
try testing.expectEqual(@as(u64, 0x01_08_07_06_05_04_03_02), br.bits);
br.shift(8 * 4 + 4);
try br.shift(8 * 4 + 4);
try testing.expectEqual(@as(u64, 0x00_00_00_00_00_10_80_70), br.bits);

try br.fill(60); // fill with 4 bytes (shift by 4)
try testing.expectEqual(@as(u64, 0x00_50_40_30_20_10_80_70), br.bits);
try testing.expectEqual(@as(u8, 8 * 7 + 4), br.nbits);

br.shift(@intCast(br.nbits)); // clear buffer
try br.shift(@intCast(br.nbits)); // clear buffer
try br.fill(8); // refill with the rest of the bytes
try testing.expectEqual(@as(u64, 0x00_00_00_00_00_08_07_06), br.bits);
}
Expand Down
100 changes: 80 additions & 20 deletions src/inflate.zig
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ pub fn Inflate(comptime container: Container, comptime ReaderType: type) type {
}

inline fn decodeLength(self: *Self, code: u8) !u16 {
assert(code <= 28);
if (code > 28) return error.CorruptInput;
const ml = Token.matchLength(code);
return if (ml.extra_bits == 0) // 0 - 5 extra bits
ml.base
Expand All @@ -132,7 +132,7 @@ pub fn Inflate(comptime container: Container, comptime ReaderType: type) type {
}

inline fn decodeDistance(self: *Self, code: u8) !u16 {
assert(code <= 29);
if (code > 29) return error.CorruptInput;
const md = Token.matchDistance(code);
return if (md.extra_bits == 0) // 0 - 13 extra bits
md.base
Expand All @@ -158,7 +158,7 @@ pub fn Inflate(comptime container: Container, comptime ReaderType: type) type {
var pos: usize = 0;
while (pos < hlit) {
const sym = cl_h.find(try self.bits.peekF(u7, F.reverse));
self.bits.shift(sym.code_bits);
try self.bits.shift(sym.code_bits);
pos += try self.dynamicCodeLength(sym.symbol, &lit_l, pos);
}

Expand All @@ -167,7 +167,7 @@ pub fn Inflate(comptime container: Container, comptime ReaderType: type) type {
pos = 0;
while (pos < hdist) {
const sym = cl_h.find(try self.bits.peekF(u7, F.reverse));
self.bits.shift(sym.code_bits);
try self.bits.shift(sym.code_bits);
pos += try self.dynamicCodeLength(sym.symbol, &dst_l, pos);
}

Expand Down Expand Up @@ -234,7 +234,7 @@ pub fn Inflate(comptime container: Container, comptime ReaderType: type) type {
inline fn decodeSymbol(self: *Self, decoder: anytype) !hfd.Symbol {
const sym = decoder.find(try self.bits.peekF(u15, F.buffered | F.reverse));
if (sym.code_bits == 0) return error.CorruptInput;
self.bits.shift(sym.code_bits);
try self.bits.shift(sym.code_bits);
return sym;
}

Expand Down Expand Up @@ -474,21 +474,81 @@ test "zlib decompress" {
}
}

test "lengths overflow" {
const data = "\xed\x1d$\xe9\xff\xff9\x0e";

var fb = std.io.fixedBufferStream(data);
var al = std.ArrayList(u8).init(testing.allocator);
defer al.deinit();

try testing.expectError(error.CorruptInput, decompress(.raw, fb.reader(), al.writer()));
}
test "fuzzing tests" {
const cases = [_]struct {
in: []const u8,
out: []const u8 = "",
err: ?anyerror = null,
}{
.{ .in = @embedFile("testdata/fuzzing/deflate-stream"), .out =
\\[
\\ { id: "brieflz",
\\ name: "BriefLZ",
\\ libraryUrl: "https://github.com/jibsen/brieflz",
\\ license: "MIT",
\\ revision: "bcaa6a1ee7ccf005512b5c23aa92b40cf75f9ed1",
\\ codecs: [ { name: "brieflz" } ], },
\\ { id: "brotli",
\\ name: "Brotli",
\\ libraryUrl: "https://github.com/google/brotli",
\\ license: "Apache 2.0",
\\ revision: "1dd66ef114fd244778d9dcb5da09c28b49a0df33",
\\ codecs: [ { name: "brotli",
\\ levels: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
\\ streaming: true } ], },
\\ { id: "bsc",
\\ name: "bsc",
\\ libraryUrl: "http://libbsc.com/",
\\ license: "Apache 2.0",
\\ revision: "b2b07421381b19b2fada8b291f3cdead10578abc",
\\ codecs: [ { name: "bsc" } ] }
\\]
\\
},
.{ .in = @embedFile("testdata/fuzzing/empty-distance-alphabet01"), .err = error.EndOfStream },
.{ .in = @embedFile("testdata/fuzzing/empty-distance-alphabet02"), .out = "" },
.{ .in = @embedFile("testdata/fuzzing/end-of-stream"), .err = error.EndOfStream },
.{ .in = @embedFile("testdata/fuzzing/invalid-distance"), .err = error.EndOfStream }, // panic
.{ .in = @embedFile("testdata/fuzzing/invalid-tree01"), .err = error.EndOfStream },
.{ .in = @embedFile("testdata/fuzzing/invalid-tree02"), .err = error.EndOfStream },
.{ .in = @embedFile("testdata/fuzzing/invalid-tree03"), .err = error.EndOfStream },
.{ .in = @embedFile("testdata/fuzzing/lengths-overflow"), .err = error.CorruptInput },
//.{ .in = "\xed\x1d$\xe9\xff\xff9\x0e", .err = error.CorruptInput },

.{ .in = @embedFile("testdata/fuzzing/out-of-codes"), .err = error.CorruptInput },
//.{ .in = "\x950\x00\x0000000", .err = error.CorruptInput },
.{ .in = @embedFile("testdata/fuzzing/puff01"), .err = error.DeflateWrongNlen },
.{ .in = @embedFile("testdata/fuzzing/puff02"), .err = error.EndOfStream },
.{ .in = @embedFile("testdata/fuzzing/puff03"), .out = &[_]u8{0xa} },
.{ .in = @embedFile("testdata/fuzzing/puff04"), .err = error.CorruptInput }, // this was panicking
.{ .in = @embedFile("testdata/fuzzing/puff05"), .err = error.EndOfStream },
.{ .in = @embedFile("testdata/fuzzing/puff06"), .err = error.EndOfStream },
//.{ .in = @embedFile("testdata/fuzzing/puff07") }, // panic
.{ .in = @embedFile("testdata/fuzzing/puff08"), .err = error.CorruptInput },
//.{ .in = "\x04\xc0\x81\x08\x00\x00\x00\x00 \x7f\xeb\x0b\x00\x00", .err = error.CorruptInput },
.{ .in = @embedFile("testdata/fuzzing/puff09"), .out = "P" },
.{ .in = @embedFile("testdata/fuzzing/puff10"), .err = error.DeflateInvalidCode },
.{ .in = @embedFile("testdata/fuzzing/puff11"), .err = error.EndOfStream },

.{ .in = @embedFile("testdata/fuzzing/puff12"), .err = error.EndOfStream },
.{ .in = @embedFile("testdata/fuzzing/puff13"), .err = error.CorruptInput },
//.{ .in = "\x04\x00\xfe\xff", .err = error.CorruptInput },
.{ .in = @embedFile("testdata/fuzzing/puff14"), .err = error.CorruptInput },
.{ .in = @embedFile("testdata/fuzzing/puff15"), .err = error.EndOfStream },
.{ .in = @embedFile("testdata/fuzzing/puff16"), .err = error.EndOfStream }, // panic
.{ .in = @embedFile("testdata/fuzzing/puff17"), .err = error.EndOfStream },
};

test "fix: infinite loop during 'out of codes'" {
const data = "\x950\x00\x0000000";
for (cases) |c| {
var fb = std.io.fixedBufferStream(c.in);
var al = std.ArrayList(u8).init(testing.allocator);
defer al.deinit();

var fb = std.io.fixedBufferStream(data);
var al = std.ArrayList(u8).init(testing.allocator);
defer al.deinit();
try testing.expectError(error.CorruptInput, decompress(.raw, fb.reader(), al.writer()));
if (c.err) |expected_err| {
try testing.expectError(expected_err, decompress(.raw, fb.reader(), al.writer()));
} else {
try decompress(.raw, fb.reader(), al.writer());
try testing.expectEqualStrings(c.out, al.items);
}
}
}
3 changes: 3 additions & 0 deletions src/testdata/fuzzing/deflate-stream
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
��=o�0���+N������ڭR��K�}>W!AI@j+�{
�|����<�����F�x[�ش�\�9f;P�%�0৷#��iu���V����UWDQ����L�YF��tTG�U���_�����|S�Q�D��<M�4)�Xk%M��e�SdűK�0 �]Ca s[v������;�ɕMSV����J�@N�5Ea�tJN��Y�$Y�[eѤVs�27��ܺ8���}wӆ��.H1��A�`� c�3P W���%��uY@߮�jN���]$
,<���L�4<K��sa�2�i�s#�p1Z�V�4˵�����؎�o
Binary file added src/testdata/fuzzing/empty-distance-alphabet01
Binary file not shown.
Binary file added src/testdata/fuzzing/empty-distance-alphabet02
Binary file not shown.
1 change: 1 addition & 0 deletions src/testdata/fuzzing/end-of-stream
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
��=o�0��0
Binary file added src/testdata/fuzzing/invalid-distance
Binary file not shown.
1 change: 1 addition & 0 deletions src/testdata/fuzzing/invalid-tree01
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
000
Binary file added src/testdata/fuzzing/invalid-tree02
Binary file not shown.
Binary file added src/testdata/fuzzing/invalid-tree03
Binary file not shown.
1 change: 1 addition & 0 deletions src/testdata/fuzzing/lengths-overflow
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
�$���9
Binary file added src/testdata/fuzzing/out-of-codes
Binary file not shown.
Binary file added src/testdata/fuzzing/puff01
Binary file not shown.
Binary file added src/testdata/fuzzing/puff02
Binary file not shown.
Binary file added src/testdata/fuzzing/puff03
Binary file not shown.
1 change: 1 addition & 0 deletions src/testdata/fuzzing/puff04
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
~��
1 change: 1 addition & 0 deletions src/testdata/fuzzing/puff05
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

1 change: 1 addition & 0 deletions src/testdata/fuzzing/puff06
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
�I�$I�$����
Binary file added src/testdata/fuzzing/puff07
Binary file not shown.
Binary file added src/testdata/fuzzing/puff08
Binary file not shown.
Binary file added src/testdata/fuzzing/puff09
Binary file not shown.
1 change: 1 addition & 0 deletions src/testdata/fuzzing/puff10
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Binary file added src/testdata/fuzzing/puff11
Binary file not shown.
Binary file added src/testdata/fuzzing/puff12
Binary file not shown.
Binary file added src/testdata/fuzzing/puff13
Binary file not shown.
Binary file added src/testdata/fuzzing/puff14
Binary file not shown.
1 change: 1 addition & 0 deletions src/testdata/fuzzing/puff15
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
�I�$I�$���Ä
Binary file added src/testdata/fuzzing/puff16
Binary file not shown.
Binary file added src/testdata/fuzzing/puff17
Binary file not shown.

0 comments on commit 38f0844

Please sign in to comment.