authorgravatar for igor.anic@gmail.comIgor Anić <igor.anic@gmail.com> 2024-03-02 00:39:48+01:00
committergravatar for igor.anic@gmail.comIgor Anić <igor.anic@gmail.com> 2024-03-11 12:22:12+01:00
log04e8bbd932c9ce9ac99230a656c8951c467d5b24
treede49098bcca42adc48cab6ffe77490b79d486a7b
parentaf0502f6c4202df0223bcefa5121361c07680108

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.

2 files changed, 51 insertions(+), 8 deletions(-)

lib/std/tar.zig+13-6
......@@ -87,8 +87,8 @@ pub const Options = struct {
8787
8888pub const Header = struct {
8989 const SIZE = 512;
90 const MAX_NAME_SIZE = 100 + 1 + 155; // name(100) + separator(1) + prefix(155)
91 const LINK_NAME_SIZE = 100;
90 pub const MAX_NAME_SIZE = 100 + 1 + 155; // name(100) + separator(1) + prefix(155)
91 pub const LINK_NAME_SIZE = 100;
9292
9393 bytes: *const [SIZE]u8,
9494
......@@ -248,7 +248,13 @@ pub const IteratorOptions = struct {
248248
249249/// Iterates over files in tar archive.
250250/// `next` returns each file in `reader` tar archive.
251pub fn iterator(reader: anytype, options: IteratorOptions) Iterator(@TypeOf(reader)) {
251/// Provided buffers should be at least 256 bytes for file_name and 100 bytes
252/// for link_name.
253pub fn iterator(reader: anytype, options: IteratorOptions) !Iterator(@TypeOf(reader)) {
254 if (options.file_name_buffer.len < Header.MAX_NAME_SIZE or
255 options.link_name_buffer.len < Header.LINK_NAME_SIZE)
256 return error.TarInsufficientBuffer;
257
252258 return .{
253259 .reader = reader,
254260 .diagnostics = options.diagnostics,
......@@ -318,7 +324,7 @@ fn Iterator(comptime ReaderType: type) type {
318324 }
319325
320326 fn readString(self: *Self, size: usize, buffer: []u8) ![]const u8 {
321 if (size > buffer.len) return error.TarCorruptInput;
327 if (size > buffer.len) return error.TarInsufficientBuffer;
322328 const buf = buffer[0..size];
323329 try self.reader.readNoEof(buf);
324330 return nullStr(buf);
......@@ -470,7 +476,8 @@ fn PaxIterator(comptime ReaderType: type) type {
470476 // Copies pax attribute value into destination buffer.
471477 // Must be called with destination buffer of size at least Attribute.len.
472478 pub fn value(self: Attribute, dst: []u8) ![]const u8 {
473 assert(self.len <= dst.len);
479 if (self.len > dst.len) return error.TarInsufficientBuffer;
480 // assert(self.len <= dst.len);
474481 const buf = dst[0..self.len];
475482 const n = try self.reader.readAll(buf);
476483 if (n < self.len) return error.UnexpectedEndOfStream;
......@@ -558,7 +565,7 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: Options) !voi
558565
559566 var file_name_buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined;
560567 var link_name_buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined;
561 var iter = iterator(reader, .{
568 var iter = try iterator(reader, .{
562569 .file_name_buffer = &file_name_buffer,
563570 .link_name_buffer = &link_name_buffer,
564571 .diagnostics = options.diagnostics,
lib/std/tar/test.zig+38-2
......@@ -315,7 +315,7 @@ test "tar run Go test cases" {
315315 },
316316 .{
317317 .data = @embedFile("testdata/fuzz1.tar"),
318 .err = error.TarCorruptInput,
318 .err = error.TarInsufficientBuffer,
319319 },
320320 .{
321321 .data = @embedFile("testdata/fuzz2.tar"),
......@@ -328,7 +328,7 @@ test "tar run Go test cases" {
328328
329329 for (cases) |case| {
330330 var fsb = std.io.fixedBufferStream(case.data);
331 var iter = tar.iterator(fsb.reader(), .{
331 var iter = try tar.iterator(fsb.reader(), .{
332332 .file_name_buffer = &file_name_buffer,
333333 .link_name_buffer = &link_name_buffer,
334334 });
......@@ -359,6 +359,27 @@ test "tar run Go test cases" {
359359 }
360360 try testing.expectEqual(case.files.len, i);
361361 }
362
363 var min_file_name_buffer: [tar.Header.MAX_NAME_SIZE]u8 = undefined;
364 var min_link_name_buffer: [tar.Header.LINK_NAME_SIZE]u8 = undefined;
365 const long_name_cases = [_]Case{ cases[11], cases[25], cases[28] };
366
367 for (long_name_cases) |case| {
368 var fsb = std.io.fixedBufferStream(case.data);
369 var iter = try tar.iterator(fsb.reader(), .{
370 .file_name_buffer = &min_file_name_buffer,
371 .link_name_buffer = &min_link_name_buffer,
372 });
373
374 var iter_err: ?anyerror = null;
375 while (iter.next() catch |err| brk: {
376 iter_err = err;
377 break :brk null;
378 }) |_| {}
379
380 try testing.expect(iter_err != null);
381 try testing.expectEqual(error.TarInsufficientBuffer, iter_err.?);
382 }
362383}
363384
364385// used in test to calculate file chksum
......@@ -490,6 +511,21 @@ test "tar pipeToFileSystem" {
490511 try testing.expectError(error.FileNotFound, root.dir.statFile("empty"));
491512 try testing.expect((try root.dir.statFile("a/file")).kind == .file);
492513 try testing.expect((try root.dir.statFile("b/symlink")).kind == .file); // statFile follows symlink
514
493515 var buf: [32]u8 = undefined;
494516 try testing.expectEqualSlices(u8, "../a/file", try root.dir.readLink("b/symlink", &buf));
495517}
518
519test "insufficient buffer for iterator" {
520 var file_name_buffer: [10]u8 = undefined;
521 var link_name_buffer: [10]u8 = undefined;
522
523 var fsb = std.io.fixedBufferStream("");
524 try testing.expectError(
525 error.TarInsufficientBuffer,
526 tar.iterator(fsb.reader(), .{
527 .file_name_buffer = &file_name_buffer,
528 .link_name_buffer = &link_name_buffer,
529 }),
530 );
531}