authorgravatar for igor.anic@gmail.comIgor Anić <igor.anic@gmail.com> 2024-02-23 21:57:15+01:00
committergravatar for igor.anic@gmail.comIgor Anić <igor.anic@gmail.com> 2024-02-23 21:57:15+01:00
logf67aa8b9b3cefddddf477850b8d8bffd3c8c26e4
tree0220c6e9d2682e1144e66fd187a5a9dc0410f130
parent256c5934bfc19d3b8a1cf01bc07c9ad86a6c6524

std.tar fix parsing mode field in tar header

Found by fuzzing. Previous numeric function assumed that is is getting buffer of size 12, mode is size 8. Fuzzing found overflow. Fixing and adding test cases.

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

lib/std/tar.zig+75-18
...@@ -140,11 +140,25 @@ pub const Header = struct {...@@ -140,11 +140,25 @@ pub const Header = struct {
140 }140 }
141141
142 pub fn mode(header: Header) !u32 {142 pub fn mode(header: Header) !u32 {
143 return @intCast(try header.numeric(100, 8));143 return @intCast(try header.octal(100, 8));
144 }144 }
145145
146 pub fn size(header: Header) !u64 {146 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] + raw[2] + raw[3] != 0) return error.TarNumericValueTooBig;
159 return std.mem.readInt(u64, raw[4..12], .big);
160 }
161 return try header.octal(start, len);
148 }162 }
149163
150 pub fn chksum(header: Header) !u64 {164 pub fn chksum(header: Header) !u64 {
...@@ -170,22 +184,6 @@ pub const Header = struct {...@@ -170,22 +184,6 @@ pub const Header = struct {
170 return nullStr(header.bytes[start .. start + len]);184 return nullStr(header.bytes[start .. start + len]);
171 }185 }
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
189 fn octal(header: Header, start: usize, len: usize) !u64 {187 fn octal(header: Header, start: usize, len: usize) !u64 {
190 const raw = header.bytes[start..][0..len];188 const raw = header.bytes[start..][0..len];
191 // Zero-filled octal number in ASCII. Each numeric field of width w189 // Zero-filled octal number in ASCII. Each numeric field of width w
...@@ -756,3 +754,62 @@ test "tar PaxIterator" {...@@ -756,3 +754,62 @@ test "tar PaxIterator" {
756test {754test {
757 _ = @import("tar/test.zig");755 _ = @import("tar/test.zig");
758}756}
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
773 // // Test base-8 (octal) encoded values.
774 .{ .in = "00000000227\x00", .want = 0o227 },
775 .{ .in = " 000000227\x00", .want = 0o227 },
776 .{ .in = "00000000228\x00", .err = error.TarHeader },
777 .{ .in = "11111111111\x00", .want = 0o11111111111 },
778 };
779
780 for (cases) |case| {
781 var bytes = [_]u8{0} ** Header.SIZE;
782 @memcpy(bytes[124 .. 124 + case.in.len], case.in);
783 var header = Header{ .bytes = &bytes };
784 if (case.err) |err| {
785 try std.testing.expectError(err, header.size());
786 } else {
787 try std.testing.expectEqual(case.want, try header.size());
788 }
789 }
790}
791
792test "tar header parse mode" {
793 const cases = [_]struct {
794 in: []const u8,
795 want: u64 = 0,
796 err: ?anyerror = null,
797 }{
798 .{ .in = "0000644\x00", .want = 0o644 },
799 .{ .in = "0000777\x00", .want = 0o777 },
800 .{ .in = "7777777\x00", .want = 0o7777777 },
801 .{ .in = "7777778\x00", .err = error.TarHeader },
802 .{ .in = "77777777", .want = 0o77777777 },
803 .{ .in = "777777777777", .want = 0o77777777 },
804 };
805 for (cases) |case| {
806 var bytes = [_]u8{0} ** Header.SIZE;
807 @memcpy(bytes[100 .. 100 + case.in.len], case.in);
808 var header = Header{ .bytes = &bytes };
809 if (case.err) |err| {
810 try std.testing.expectError(err, header.mode());
811 } else {
812 try std.testing.expectEqual(case.want, try header.mode());
813 }
814 }
815}