Commit 04e8bbd932

Igor Anić <igor.anic@gmail.com>
2024-03-02 00:39:48
std.tar: test buffers provided to the iterator
Tar header stores name in max 256 bytes and link name in max 100 bytes. Those are minimums for provided buffers. Error is raised during iterator init if buffers are not long enough. Pax and gnu extensions can store longer names. If such extension is reached during unpack and don't fit into provided buffer error is returned.
1 parent af0502f
Changed files (2)
lib
lib/std/tar/test.zig
@@ -315,7 +315,7 @@ test "tar run Go test cases" {
         },
         .{
             .data = @embedFile("testdata/fuzz1.tar"),
-            .err = error.TarCorruptInput,
+            .err = error.TarInsufficientBuffer,
         },
         .{
             .data = @embedFile("testdata/fuzz2.tar"),
@@ -328,7 +328,7 @@ test "tar run Go test cases" {
 
     for (cases) |case| {
         var fsb = std.io.fixedBufferStream(case.data);
-        var iter = tar.iterator(fsb.reader(), .{
+        var iter = try tar.iterator(fsb.reader(), .{
             .file_name_buffer = &file_name_buffer,
             .link_name_buffer = &link_name_buffer,
         });
@@ -359,6 +359,27 @@ test "tar run Go test cases" {
         }
         try testing.expectEqual(case.files.len, i);
     }
+
+    var min_file_name_buffer: [tar.Header.MAX_NAME_SIZE]u8 = undefined;
+    var min_link_name_buffer: [tar.Header.LINK_NAME_SIZE]u8 = undefined;
+    const long_name_cases = [_]Case{ cases[11], cases[25], cases[28] };
+
+    for (long_name_cases) |case| {
+        var fsb = std.io.fixedBufferStream(case.data);
+        var iter = try tar.iterator(fsb.reader(), .{
+            .file_name_buffer = &min_file_name_buffer,
+            .link_name_buffer = &min_link_name_buffer,
+        });
+
+        var iter_err: ?anyerror = null;
+        while (iter.next() catch |err| brk: {
+            iter_err = err;
+            break :brk null;
+        }) |_| {}
+
+        try testing.expect(iter_err != null);
+        try testing.expectEqual(error.TarInsufficientBuffer, iter_err.?);
+    }
 }
 
 // used in test to calculate file chksum
@@ -490,6 +511,21 @@ test "tar pipeToFileSystem" {
     try testing.expectError(error.FileNotFound, root.dir.statFile("empty"));
     try testing.expect((try root.dir.statFile("a/file")).kind == .file);
     try testing.expect((try root.dir.statFile("b/symlink")).kind == .file); // statFile follows symlink
+
     var buf: [32]u8 = undefined;
     try testing.expectEqualSlices(u8, "../a/file", try root.dir.readLink("b/symlink", &buf));
 }
+
+test "insufficient buffer for iterator" {
+    var file_name_buffer: [10]u8 = undefined;
+    var link_name_buffer: [10]u8 = undefined;
+
+    var fsb = std.io.fixedBufferStream("");
+    try testing.expectError(
+        error.TarInsufficientBuffer,
+        tar.iterator(fsb.reader(), .{
+            .file_name_buffer = &file_name_buffer,
+            .link_name_buffer = &link_name_buffer,
+        }),
+    );
+}
lib/std/tar.zig
@@ -87,8 +87,8 @@ pub const Options = struct {
 
 pub const Header = struct {
     const SIZE = 512;
-    const MAX_NAME_SIZE = 100 + 1 + 155; // name(100) + separator(1) + prefix(155)
-    const LINK_NAME_SIZE = 100;
+    pub const MAX_NAME_SIZE = 100 + 1 + 155; // name(100) + separator(1) + prefix(155)
+    pub const LINK_NAME_SIZE = 100;
 
     bytes: *const [SIZE]u8,
 
@@ -248,7 +248,13 @@ pub const IteratorOptions = struct {
 
 /// Iterates over files in tar archive.
 /// `next` returns each file in `reader` tar archive.
-pub fn iterator(reader: anytype, options: IteratorOptions) Iterator(@TypeOf(reader)) {
+/// Provided buffers should be at least 256 bytes for file_name and 100 bytes
+/// for link_name.
+pub fn iterator(reader: anytype, options: IteratorOptions) !Iterator(@TypeOf(reader)) {
+    if (options.file_name_buffer.len < Header.MAX_NAME_SIZE or
+        options.link_name_buffer.len < Header.LINK_NAME_SIZE)
+        return error.TarInsufficientBuffer;
+
     return .{
         .reader = reader,
         .diagnostics = options.diagnostics,
@@ -318,7 +324,7 @@ fn Iterator(comptime ReaderType: type) type {
         }
 
         fn readString(self: *Self, size: usize, buffer: []u8) ![]const u8 {
-            if (size > buffer.len) return error.TarCorruptInput;
+            if (size > buffer.len) return error.TarInsufficientBuffer;
             const buf = buffer[0..size];
             try self.reader.readNoEof(buf);
             return nullStr(buf);
@@ -470,7 +476,8 @@ fn PaxIterator(comptime ReaderType: type) type {
             // Copies pax attribute value into destination buffer.
             // Must be called with destination buffer of size at least Attribute.len.
             pub fn value(self: Attribute, dst: []u8) ![]const u8 {
-                assert(self.len <= dst.len);
+                if (self.len > dst.len) return error.TarInsufficientBuffer;
+                // assert(self.len <= dst.len);
                 const buf = dst[0..self.len];
                 const n = try self.reader.readAll(buf);
                 if (n < self.len) return error.UnexpectedEndOfStream;
@@ -558,7 +565,7 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: Options) !voi
 
     var file_name_buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined;
     var link_name_buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined;
-    var iter = iterator(reader, .{
+    var iter = try iterator(reader, .{
         .file_name_buffer = &file_name_buffer,
         .link_name_buffer = &link_name_buffer,
         .diagnostics = options.diagnostics,