authorgravatar for 189900@gmail.comnone <189900@gmail.com> 2023-08-27 20:35:33+03:00
committergravatar for git@vexu.euVeikka Tuominen <git@vexu.eu> 2023-09-08 21:55:14+03:00
log39a98dc4261cf6106954aae1570fe1861311ff08
tree25accda6eee9902c320354c2f3087909c9d7ce83
parentfa46750a841c3e91f4445e794fc2d96088b159e7

std.tar: add support for file path in pax attributes

Handles .extended_header type to parse PAX attributes and check if they override the path of the next file. Increases file path limit to std.fs.MAX_PATH_BYTES. Fixes #15342

1 files changed, 137 insertions(+), 40 deletions(-)

lib/std/tar.zig+137-40
...@@ -48,7 +48,7 @@ pub const Header = struct {...@@ -48,7 +48,7 @@ pub const Header = struct {
48 /// Return value may point into Header buffer, or might point into the48 /// Return value may point into Header buffer, or might point into the
49 /// argument buffer.49 /// argument buffer.
50 /// TODO: check against "../" and other nefarious things50 /// TODO: check against "../" and other nefarious things
51 pub fn fullFileName(header: Header, buffer: *[255]u8) ![]const u8 {51 pub fn fullFileName(header: Header, buffer: *[std.fs.MAX_PATH_BYTES]u8) ![]const u8 {
52 const n = name(header);52 const n = name(header);
53 if (!is_ustar(header))53 if (!is_ustar(header))
54 return n;54 return n;
...@@ -83,6 +83,44 @@ pub const Header = struct {...@@ -83,6 +83,44 @@ pub const Header = struct {
83 }83 }
84};84};
8585
86const Buffer = struct {
87 buffer: [512 * 8]u8 = undefined,
88 start: usize = 0,
89 end: usize = 0,
90
91 pub fn readChunk(b: *Buffer, reader: anytype, count: usize) ![]const u8 {
92 b.ensureCapacity(1024);
93
94 const ask = @min(b.buffer.len - b.end, count -| (b.end - b.start));
95 b.end += try reader.readAtLeast(b.buffer[b.end..], ask);
96
97 return b.buffer[b.start..b.end];
98 }
99
100 pub fn advance(b: *Buffer, count: usize) void {
101 b.start += count;
102 assert(b.start <= b.end);
103 }
104
105 pub fn skip(b: *Buffer, reader: anytype, count: usize) !void {
106 if (b.start + count > b.end) {
107 try reader.skipBytes(b.start + count - b.end, .{});
108 b.start = b.end;
109 } else {
110 b.advance(count);
111 }
112 }
113
114 inline fn ensureCapacity(b: *Buffer, count: usize) void {
115 if (b.buffer.len - b.start < count) {
116 const dest_end = b.end - b.start;
117 @memcpy(b.buffer[0..dest_end], b.buffer[b.start..b.end]);
118 b.end = dest_end;
119 b.start = 0;
120 }
121 }
122};
123
86pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: Options) !void {124pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: Options) !void {
87 switch (options.mode_mode) {125 switch (options.mode_mode) {
88 .ignore => {},126 .ignore => {},
...@@ -95,30 +133,27 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: Options) !voi...@@ -95,30 +133,27 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: Options) !voi
95 @panic("TODO: unimplemented: tar ModeMode.executable_bit_only");133 @panic("TODO: unimplemented: tar ModeMode.executable_bit_only");
96 },134 },
97 }135 }
98 var file_name_buffer: [255]u8 = undefined;136 var file_name_buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined;
99 var buffer: [512 * 8]u8 = undefined;137 var file_name_override_len: usize = 0;
100 var start: usize = 0;138 var buffer: Buffer = .{};
101 var end: usize = 0;
102 header: while (true) {139 header: while (true) {
103 if (buffer.len - start < 1024) {140 const chunk = try buffer.readChunk(reader, 1024);
104 const dest_end = end - start;141 switch (chunk.len) {
105 @memcpy(buffer[0..dest_end], buffer[start..end]);
106 end = dest_end;
107 start = 0;
108 }
109 const ask_header = @min(buffer.len - end, 1024 -| (end - start));
110 end += try reader.readAtLeast(buffer[end..], ask_header);
111 switch (end - start) {
112 0 => return,142 0 => return,
113 1...511 => return error.UnexpectedEndOfStream,143 1...511 => return error.UnexpectedEndOfStream,
114 else => {},144 else => {},
115 }145 }
116 const header: Header = .{ .bytes = buffer[start..][0..512] };146 buffer.advance(512);
117 start += 512;147
148 const header: Header = .{ .bytes = chunk[0..512] };
118 const file_size = try header.fileSize();149 const file_size = try header.fileSize();
119 const rounded_file_size = std.mem.alignForward(u64, file_size, 512);150 const rounded_file_size = std.mem.alignForward(u64, file_size, 512);
120 const pad_len = @as(usize, @intCast(rounded_file_size - file_size));151 const pad_len = @as(usize, @intCast(rounded_file_size - file_size));
121 const unstripped_file_name = try header.fullFileName(&file_name_buffer);152 const unstripped_file_name = if (file_name_override_len > 0)
153 file_name_buffer[0..file_name_override_len]
154 else
155 try header.fullFileName(&file_name_buffer);
156 file_name_override_len = 0;
122 switch (header.fileType()) {157 switch (header.fileType()) {
123 .directory => {158 .directory => {
124 const file_name = try stripComponents(unstripped_file_name, options.strip_components);159 const file_name = try stripComponents(unstripped_file_name, options.strip_components);
...@@ -138,36 +173,59 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: Options) !voi...@@ -138,36 +173,59 @@ pub fn pipeToFileSystem(dir: std.fs.Dir, reader: anytype, options: Options) !voi
138173
139 var file_off: usize = 0;174 var file_off: usize = 0;
140 while (true) {175 while (true) {
141 if (buffer.len - start < 1024) {176 const temp = try buffer.readChunk(reader, @intCast(rounded_file_size + 512 - file_off));
142 const dest_end = end - start;177 if (temp.len == 0) return error.UnexpectedEndOfStream;
143 @memcpy(buffer[0..dest_end], buffer[start..end]);178 const slice = temp[0..@as(usize, @intCast(@min(file_size - file_off, temp.len)))];
144 end = dest_end;
145 start = 0;
146 }
147 // Ask for the rounded up file size + 512 for the next header.
148 // TODO: https://github.com/ziglang/zig/issues/14039
149 const ask = @as(usize, @intCast(@min(
150 buffer.len - end,
151 rounded_file_size + 512 - file_off -| (end - start),
152 )));
153 end += try reader.readAtLeast(buffer[end..], ask);
154 if (end - start < ask) return error.UnexpectedEndOfStream;
155 // TODO: https://github.com/ziglang/zig/issues/14039
156 const slice = buffer[start..@as(usize, @intCast(@min(file_size - file_off + start, end)))];
157 try file.writeAll(slice);179 try file.writeAll(slice);
180
158 file_off += slice.len;181 file_off += slice.len;
159 start += slice.len;182 buffer.advance(slice.len);
160 if (file_off >= file_size) {183 if (file_off >= file_size) {
161 start += pad_len;184 buffer.advance(pad_len);
162 // Guaranteed since we use a buffer divisible by 512.
163 assert(start <= end);
164 continue :header;185 continue :header;
165 }186 }
166 }187 }
167 },188 },
168 .global_extended_header, .extended_header => {189 .extended_header => {
169 if (start + rounded_file_size > end) return error.TarHeadersTooBig;190 if (file_size == 0) {
170 start = @as(usize, @intCast(start + rounded_file_size));191 buffer.advance(@intCast(rounded_file_size));
192 continue;
193 }
194
195 const chunk_size: usize = @intCast(rounded_file_size + 512);
196 var data_off: usize = 0;
197 file_name_override_len = while (data_off < file_size) {
198 const slice = try buffer.readChunk(reader, chunk_size - data_off);
199 if (slice.len == 0) return error.UnexpectedEndOfStream;
200 const remaining_size: usize = @intCast(file_size - data_off);
201 const attr_info = try parsePaxAttribute(slice[0..@min(remaining_size, slice.len)], remaining_size);
202
203 if (std.mem.eql(u8, attr_info.key, "path")) {
204 if (attr_info.value_len > file_name_buffer.len) return error.NameTooLong;
205 buffer.advance(attr_info.value_off);
206 data_off += attr_info.value_off;
207 break attr_info.value_len;
208 }
209
210 try buffer.skip(reader, attr_info.size);
211 data_off += attr_info.size;
212 } else 0;
213
214 var i: usize = 0;
215 while (i < file_name_override_len) {
216 const slice = try buffer.readChunk(reader, chunk_size - data_off - i);
217 if (slice.len == 0) return error.UnexpectedEndOfStream;
218 const copy_size: usize = @intCast(@min(file_name_override_len - i, slice.len));
219 @memcpy(file_name_buffer[i .. i + copy_size], slice[0..copy_size]);
220 buffer.advance(copy_size);
221 i += copy_size;
222 }
223
224 try buffer.skip(reader, @intCast(rounded_file_size - data_off - file_name_override_len));
225 continue :header;
226 },
227 .global_extended_header => {
228 buffer.skip(reader, @intCast(rounded_file_size)) catch return error.TarHeadersTooBig;
171 },229 },
172 .hard_link => return error.TarUnsupportedFileType,230 .hard_link => return error.TarUnsupportedFileType,
173 .symbolic_link => return error.TarUnsupportedFileType,231 .symbolic_link => return error.TarUnsupportedFileType,
...@@ -196,5 +254,44 @@ test stripComponents {...@@ -196,5 +254,44 @@ test stripComponents {
196 try expectEqualStrings("c", try stripComponents("a/b/c", 2));254 try expectEqualStrings("c", try stripComponents("a/b/c", 2));
197}255}
198256
257const PaxAttributeInfo = struct {
258 size: usize,
259 key: []const u8,
260 value_off: usize,
261 value_len: usize,
262};
263
264fn parsePaxAttribute(data: []const u8, max_size: usize) !PaxAttributeInfo {
265 const pos_space = std.mem.indexOfScalar(u8, data, ' ') orelse return error.InvalidPaxAttribute;
266 const pos_equals = std.mem.indexOfScalarPos(u8, data, pos_space, '=') orelse return error.InvalidPaxAttribute;
267 const kv_size = try std.fmt.parseInt(usize, data[0..pos_space], 10);
268 if (kv_size > max_size) {
269 return error.InvalidPaxAttribute;
270 }
271 return .{
272 .size = kv_size,
273 .key = data[pos_space + 1 .. pos_equals],
274 .value_off = pos_equals + 1,
275 .value_len = kv_size - pos_equals - 2,
276 };
277}
278
279test parsePaxAttribute {
280 const expectEqual = std.testing.expectEqual;
281 const expectEqualStrings = std.testing.expectEqualStrings;
282 const expectError = std.testing.expectError;
283 const prefix = "1011 path=";
284 const file_name = "0123456789" ** 100;
285 const header = prefix ++ file_name ++ "\n";
286 const attr_info = try parsePaxAttribute(header, 1011);
287 try expectEqual(@as(usize, 1011), attr_info.size);
288 try expectEqualStrings("path", attr_info.key);
289 try expectEqual(prefix.len, attr_info.value_off);
290 try expectEqual(file_name.len, attr_info.value_len);
291 try expectEqual(attr_info, try parsePaxAttribute(header, 1012));
292 try expectError(error.InvalidPaxAttribute, parsePaxAttribute(header, 1010));
293 try expectError(error.InvalidPaxAttribute, parsePaxAttribute("", 0));
294}
295
199const std = @import("std.zig");296const std = @import("std.zig");
200const assert = std.debug.assert;297const assert = std.debug.assert;