authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-02-23 21:11:27-08:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2024-02-23 21:11:27-08:00
log229b87cab66ec2d90ad91be2d99491f26d5a630a
tree9c029b321ceeec3876c9eaba94e054eb96ed3144
parentcfce81f7d5f11ab93b2d5fd26df41edf967f333b
parent0a86b117bf0a29b4996592d6ad29c46833ae44c9
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #19053 from ianic/tar_fuzzing2

std:tar fix two crashes found by fuzzing

1 files changed, 76 insertions(+), 18 deletions(-)

lib/std/tar.zig+76-18
......@@ -140,11 +140,25 @@ pub const Header = struct {
140140 }
141141
142142 pub fn mode(header: Header) !u32 {
143 return @intCast(try header.numeric(100, 8));
143 return @intCast(try header.octal(100, 8));
144144 }
145145
146146 pub fn size(header: Header) !u64 {
147 return header.numeric(124, 12);
147 const start = 124;
148 const len = 12;
149 const raw = header.bytes[start..][0..len];
150 // If the leading byte is 0xff (255), all the bytes of the field
151 // (including the leading byte) are concatenated in big-endian order,
152 // with the result being a negative number expressed in two’s
153 // complement form.
154 if (raw[0] == 0xff) return error.TarNumericValueNegative;
155 // If the leading byte is 0x80 (128), the non-leading bytes of the
156 // field are concatenated in big-endian order.
157 if (raw[0] == 0x80) {
158 if (raw[1] != 0 or raw[2] != 0 or raw[3] != 0) return error.TarNumericValueTooBig;
159 return std.mem.readInt(u64, raw[4..12], .big);
160 }
161 return try header.octal(start, len);
148162 }
149163
150164 pub fn chksum(header: Header) !u64 {
......@@ -170,22 +184,6 @@ pub const Header = struct {
170184 return nullStr(header.bytes[start .. start + len]);
171185 }
172186
173 fn numeric(header: Header, start: usize, len: usize) !u64 {
174 const raw = header.bytes[start..][0..len];
175 // If the leading byte is 0xff (255), all the bytes of the field
176 // (including the leading byte) are concatenated in big-endian order,
177 // with the result being a negative number expressed in two’s
178 // complement form.
179 if (raw[0] == 0xff) return error.TarNumericValueNegative;
180 // If the leading byte is 0x80 (128), the non-leading bytes of the
181 // field are concatenated in big-endian order.
182 if (raw[0] == 0x80) {
183 if (raw[1] + raw[2] + raw[3] != 0) return error.TarNumericValueTooBig;
184 return std.mem.readInt(u64, raw[4..12], .big);
185 }
186 return try header.octal(start, len);
187 }
188
189187 fn octal(header: Header, start: usize, len: usize) !u64 {
190188 const raw = header.bytes[start..][0..len];
191189 // Zero-filled octal number in ASCII. Each numeric field of width w
......@@ -756,3 +754,63 @@ test "tar PaxIterator" {
756754test {
757755 _ = @import("tar/test.zig");
758756}
757
758test "tar header parse size" {
759 const cases = [_]struct {
760 in: []const u8,
761 want: u64 = 0,
762 err: ?anyerror = null,
763 }{
764 // Test base-256 (binary) encoded values.
765 .{ .in = "", .want = 0 },
766 .{ .in = "\x80", .want = 0 },
767 .{ .in = "\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01", .want = 1 },
768 .{ .in = "\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02", .want = 0x0102 },
769 .{ .in = "\x80\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08", .want = 0x0102030405060708 },
770 .{ .in = "\x80\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09", .err = error.TarNumericValueTooBig },
771 .{ .in = "\x80\x00\x00\x00\x07\x76\xa2\x22\xeb\x8a\x72\x61", .want = 537795476381659745 },
772 .{ .in = "\x80\x80\x80\x00\x01\x02\x03\x04\x05\x06\x07\x08", .err = error.TarNumericValueTooBig },
773
774 // // Test base-8 (octal) encoded values.
775 .{ .in = "00000000227\x00", .want = 0o227 },
776 .{ .in = " 000000227\x00", .want = 0o227 },
777 .{ .in = "00000000228\x00", .err = error.TarHeader },
778 .{ .in = "11111111111\x00", .want = 0o11111111111 },
779 };
780
781 for (cases) |case| {
782 var bytes = [_]u8{0} ** Header.SIZE;
783 @memcpy(bytes[124 .. 124 + case.in.len], case.in);
784 var header = Header{ .bytes = &bytes };
785 if (case.err) |err| {
786 try std.testing.expectError(err, header.size());
787 } else {
788 try std.testing.expectEqual(case.want, try header.size());
789 }
790 }
791}
792
793test "tar header parse mode" {
794 const cases = [_]struct {
795 in: []const u8,
796 want: u64 = 0,
797 err: ?anyerror = null,
798 }{
799 .{ .in = "0000644\x00", .want = 0o644 },
800 .{ .in = "0000777\x00", .want = 0o777 },
801 .{ .in = "7777777\x00", .want = 0o7777777 },
802 .{ .in = "7777778\x00", .err = error.TarHeader },
803 .{ .in = "77777777", .want = 0o77777777 },
804 .{ .in = "777777777777", .want = 0o77777777 },
805 };
806 for (cases) |case| {
807 var bytes = [_]u8{0} ** Header.SIZE;
808 @memcpy(bytes[100 .. 100 + case.in.len], case.in);
809 var header = Header{ .bytes = &bytes };
810 if (case.err) |err| {
811 try std.testing.expectError(err, header.mode());
812 } else {
813 try std.testing.expectEqual(case.want, try header.mode());
814 }
815 }
816}