authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2020-07-19 22:25:00+02:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2020-07-22 08:51:22+02:00
loge0b77a6b77614538d18b9f0b9e3e7e434ccee4ff
tree04b40728d9b672ef160431fe550e389863ce666d
parent3c8ceb674eb00ac0a70d6a0742234ce520eccf06

Ensure Dir.deleteTree does not dereference symlinks

Otherwise, the behaviour can lead to unexpected results, resulting in removing an entire tree that's not necessarily under the root. Furthermore, this change is needed if are to properly handle dir symlinks on Windows. Without explicitly requiring that a directory or file is opened with `FILE_OPEN_REPARSE_POINT`, Windows automatically dereferences all symlinks along the way. This commit adds another option to `OpenDirOptions`, namely `.no_follow`, which defaults to `false` and can be used to specifically open a directory symlink on Windows or call `openat` with `O_NOFOLLOW` flag in POSIX.

4 files changed, 25 insertions(+), 22 deletions(-)

lib/std/fs.zig+18-9
......@@ -951,6 +951,9 @@ pub const Dir = struct {
951951 /// `true` means the opened directory can be scanned for the files and sub-directories
952952 /// of the result. It means the `iterate` function can be called.
953953 iterate: bool = false,
954
955 /// `true` means it won't dereference the symlink.
956 no_follow: bool = false,
954957 };
955958
956959 /// Opens a directory at the given path. The directory is a system resource that remains
......@@ -994,10 +997,11 @@ pub const Dir = struct {
994997 w.RIGHT_PATH_REMOVE_DIRECTORY |
995998 w.RIGHT_PATH_UNLINK_FILE;
996999 }
1000 const symlink_flags: w.lookupflags_t = if (args.no_follow) 0x0 else w.LOOKUP_SYMLINK_FOLLOW;
9971001 // TODO do we really need all the rights here?
9981002 const inheriting: w.rights_t = w.RIGHT_ALL ^ w.RIGHT_SOCK_SHUTDOWN;
9991003
1000 const result = os.openatWasi(self.fd, sub_path, w.O_DIRECTORY, 0x0, base, inheriting);
1004 const result = os.openatWasi(self.fd, sub_path, w.O_DIRECTORY, symlink_flags, base, inheriting);
10011005 const fd = result catch |err| switch (err) {
10021006 error.FileTooBig => unreachable, // can't happen for directories
10031007 error.IsDir => unreachable, // we're providing O_DIRECTORY
......@@ -1014,11 +1018,13 @@ pub const Dir = struct {
10141018 if (builtin.os.tag == .windows) {
10151019 const sub_path_w = try os.windows.cStrToPrefixedFileW(sub_path_c);
10161020 return self.openDirW(sub_path_w.span().ptr, args);
1017 } else if (!args.iterate) {
1021 }
1022 const symlink_flags: u32 = if (args.no_follow) os.O_NOFOLLOW else 0x0;
1023 if (!args.iterate) {
10181024 const O_PATH = if (@hasDecl(os, "O_PATH")) os.O_PATH else 0;
1019 return self.openDirFlagsZ(sub_path_c, os.O_DIRECTORY | os.O_RDONLY | os.O_CLOEXEC | O_PATH);
1025 return self.openDirFlagsZ(sub_path_c, os.O_DIRECTORY | os.O_RDONLY | os.O_CLOEXEC | O_PATH | symlink_flags);
10201026 } else {
1021 return self.openDirFlagsZ(sub_path_c, os.O_DIRECTORY | os.O_RDONLY | os.O_CLOEXEC);
1027 return self.openDirFlagsZ(sub_path_c, os.O_DIRECTORY | os.O_RDONLY | os.O_CLOEXEC | symlink_flags);
10221028 }
10231029 }
10241030
......@@ -1030,7 +1036,7 @@ pub const Dir = struct {
10301036 const base_flags = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA |
10311037 w.SYNCHRONIZE | w.FILE_TRAVERSE;
10321038 const flags: u32 = if (args.iterate) base_flags | w.FILE_LIST_DIRECTORY else base_flags;
1033 return self.openDirAccessMaskW(sub_path_w, flags);
1039 return self.openDirAccessMaskW(sub_path_w, flags, args.no_follow);
10341040 }
10351041
10361042 /// `flags` must contain `os.O_DIRECTORY`.
......@@ -1050,7 +1056,7 @@ pub const Dir = struct {
10501056 return Dir{ .fd = fd };
10511057 }
10521058
1053 fn openDirAccessMaskW(self: Dir, sub_path_w: [*:0]const u16, access_mask: u32) OpenError!Dir {
1059 fn openDirAccessMaskW(self: Dir, sub_path_w: [*:0]const u16, access_mask: u32, no_follow: bool) OpenError!Dir {
10541060 const w = os.windows;
10551061
10561062 var result = Dir{
......@@ -1080,6 +1086,7 @@ pub const Dir = struct {
10801086 // implement this: https://git.midipix.org/ntapi/tree/src/fs/ntapi_tt_open_physical_parent_directory.c
10811087 @panic("TODO opening '..' with a relative directory handle is not yet implemented on Windows");
10821088 }
1089 const open_reparse_point: w.DWORD = if (no_follow) w.FILE_OPEN_REPARSE_POINT else 0x0;
10831090 var io: w.IO_STATUS_BLOCK = undefined;
10841091 const rc = w.ntdll.NtCreateFile(
10851092 &result.fd,
......@@ -1090,7 +1097,7 @@ pub const Dir = struct {
10901097 0,
10911098 w.FILE_SHARE_READ | w.FILE_SHARE_WRITE,
10921099 w.FILE_OPEN,
1093 w.FILE_DIRECTORY_FILE | w.FILE_SYNCHRONOUS_IO_NONALERT | w.FILE_OPEN_FOR_BACKUP_INTENT,
1100 w.FILE_DIRECTORY_FILE | w.FILE_SYNCHRONOUS_IO_NONALERT | w.FILE_OPEN_FOR_BACKUP_INTENT | open_reparse_point,
10941101 null,
10951102 0,
10961103 );
......@@ -1277,6 +1284,7 @@ pub const Dir = struct {
12771284 pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
12781285 start_over: while (true) {
12791286 var got_access_denied = false;
1287
12801288 // First, try deleting the item as a file. This way we don't follow sym links.
12811289 if (self.deleteFile(sub_path)) {
12821290 return;
......@@ -1297,7 +1305,8 @@ pub const Dir = struct {
12971305 error.Unexpected,
12981306 => |e| return e,
12991307 }
1300 var dir = self.openDir(sub_path, .{ .iterate = true }) catch |err| switch (err) {
1308
1309 var dir = self.openDir(sub_path, .{ .iterate = true, .no_follow = true }) catch |err| switch (err) {
13011310 error.NotDir => {
13021311 if (got_access_denied) {
13031312 return error.AccessDenied;
......@@ -1364,7 +1373,7 @@ pub const Dir = struct {
13641373 => |e| return e,
13651374 }
13661375
1367 const new_dir = dir.openDir(entry.name, .{ .iterate = true }) catch |err| switch (err) {
1376 const new_dir = dir.openDir(entry.name, .{ .iterate = true, .no_follow = true }) catch |err| switch (err) {
13681377 error.NotDir => {
13691378 if (got_access_denied) {
13701379 return error.AccessDenied;
lib/std/fs/test.zig+3-3
......@@ -36,7 +36,7 @@ test "readLinkAbsolute" {
3636
3737 // Create symbolic link by path
3838 try fs.symLinkAbsolute(target_path, symlink_path, .{});
39 try testReadlinkAbsolute(target_path, symlink_path);
39 try testReadLinkAbsolute(target_path, symlink_path);
4040 }
4141 {
4242 const target_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "subdir" });
......@@ -44,11 +44,11 @@ test "readLinkAbsolute" {
4444
4545 // Create symbolic link by path
4646 try fs.symLinkAbsolute(target_path, symlink_path, .{ .is_directory = true });
47 try testReadlinkAbsolute(target_path, symlink_path);
47 try testReadLinkAbsolute(target_path, symlink_path);
4848 }
4949}
5050
51fn testReadlinkAbsolute(target_path: []const u8, symlink_path: []const u8) !void {
51fn testReadLinkAbsolute(target_path: []const u8, symlink_path: []const u8) !void {
5252 var buffer: [fs.MAX_PATH_BYTES]u8 = undefined;
5353 const given = try fs.readLinkAbsolute(symlink_path, buffer[0..]);
5454 testing.expect(mem.eql(u8, target_path, given));
lib/std/os.zig+3-1
......@@ -1815,7 +1815,7 @@ pub fn unlinkatW(dirfd: fd_t, sub_path_w: [*:0]const u16, flags: u32) UnlinkatEr
18151815
18161816 const want_rmdir_behavior = (flags & AT_REMOVEDIR) != 0;
18171817 const create_options_flags = if (want_rmdir_behavior)
1818 @as(w.ULONG, w.FILE_DELETE_ON_CLOSE | w.FILE_DIRECTORY_FILE)
1818 @as(w.ULONG, w.FILE_DELETE_ON_CLOSE | w.FILE_DIRECTORY_FILE | w.FILE_OPEN_REPARSE_POINT)
18191819 else
18201820 @as(w.ULONG, w.FILE_DELETE_ON_CLOSE | w.FILE_NON_DIRECTORY_FILE | w.FILE_OPEN_REPARSE_POINT); // would we ever want to delete the target instead?
18211821
......@@ -2390,9 +2390,11 @@ pub fn readlinkW(file_path: [*:0]const u16, out_buffer: []u8) ReadLinkError![]u8
23902390 else => |e| return e,
23912391 }
23922392 };
2393 defer w.CloseHandle(handle);
23932394
23942395 var reparse_buf: [w.MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 = undefined;
23952396 _ = try w.DeviceIoControl(handle, w.FSCTL_GET_REPARSE_POINT, null, reparse_buf[0..], null);
2397
23962398 const reparse_struct = @ptrCast(*const w.REPARSE_DATA_BUFFER, @alignCast(@alignOf(w.REPARSE_DATA_BUFFER), &reparse_buf[0]));
23972399 switch (reparse_struct.ReparseTag) {
23982400 w.IO_REPARSE_TAG_SYMLINK => {
lib/std/os/windows.zig+1-9
......@@ -602,15 +602,7 @@ pub fn GetCurrentDirectory(buffer: []u8) GetCurrentDirectoryError![]u8 {
602602 return buffer[0..end_index];
603603}
604604
605pub const CreateSymbolicLinkError = error{
606 AccessDenied,
607 PathAlreadyExists,
608 FileNotFound,
609 NameTooLong,
610 InvalidUtf8,
611 BadPathName,
612 Unexpected
613};
605pub const CreateSymbolicLinkError = error{ AccessDenied, PathAlreadyExists, FileNotFound, NameTooLong, InvalidUtf8, BadPathName, Unexpected };
614606
615607pub fn CreateSymbolicLink(
616608 sym_link_path: []const u8,