Skip to content

Commit 6019633

Browse files
author
=
committed
std.process.Child: fix double path normalization in spawnWindows
besides simply being redundant work, the now removed normalize call would cause spawn to errantly fail (BadPath) when passing a relative path which traversed 'above' the current working directory. This case is already handled by leaving normalization to the windows.wToPrefixedFileW call in windowsCreateProcessPathExt
1 parent f0fec95 commit 6019633

File tree

3 files changed

+69
-10
lines changed

3 files changed

+69
-10
lines changed

lib/std/process/Child.zig

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -901,11 +901,6 @@ fn spawnWindows(self: *ChildProcess) SpawnError!void {
901901
if (dir_buf.items.len > 0) try dir_buf.append(self.allocator, fs.path.sep);
902902
try dir_buf.appendSlice(self.allocator, app_dir);
903903
}
904-
if (dir_buf.items.len > 0) {
905-
// Need to normalize the path, openDirW can't handle things like double backslashes
906-
const normalized_len = windows.normalizePath(u16, dir_buf.items) catch return error.BadPathName;
907-
dir_buf.shrinkRetainingCapacity(normalized_len);
908-
}
909904

910905
windowsCreateProcessPathExt(self.allocator, &dir_buf, &app_buf, PATHEXT, &cmd_line_cache, envp_ptr, cwd_w_ptr, flags, &siStartInfo, &piProcInfo) catch |no_path_err| {
911906
const original_err = switch (no_path_err) {
@@ -930,10 +925,6 @@ fn spawnWindows(self: *ChildProcess) SpawnError!void {
930925
while (it.next()) |search_path| {
931926
dir_buf.clearRetainingCapacity();
932927
try dir_buf.appendSlice(self.allocator, search_path);
933-
// Need to normalize the path, some PATH values can contain things like double
934-
// backslashes which openDirW can't handle
935-
const normalized_len = windows.normalizePath(u16, dir_buf.items) catch continue;
936-
dir_buf.shrinkRetainingCapacity(normalized_len);
937928

938929
if (windowsCreateProcessPathExt(self.allocator, &dir_buf, &app_buf, PATHEXT, &cmd_line_cache, envp_ptr, cwd_w_ptr, flags, &siStartInfo, &piProcInfo)) {
939930
break :run;

test/standalone/child_process/main.zig

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ pub fn main() !void {
3939
},
4040
else => |term| testError("abnormal child exit: {}", .{term}),
4141
}
42-
return if (parent_test_error) error.ParentTestError else {};
42+
if (parent_test_error) return error.ParentTestError;
43+
44+
try testSpawnRelative(gpa, child_path);
4345
}
4446

4547
var parent_test_error = false;
@@ -54,3 +56,29 @@ fn testError(comptime fmt: []const u8, args: anytype) void {
5456
}
5557
parent_test_error = true;
5658
}
59+
60+
fn testSpawnRelative(allocator: std.mem.Allocator, child_path: []const u8) !void {
61+
// regression test: ensure we can launch processes "above" the current working directory.
62+
const deeper_dir_path = "./1/2/3/4/5";
63+
const child_dir_path = std.fs.path.dirname(child_path) orelse return error.BadChildPath;
64+
var child_dir = try std.fs.cwd().openDir(child_dir_path, .{});
65+
defer child_dir.close();
66+
67+
try child_dir.setAsCwd();
68+
try child_dir.makePath(deeper_dir_path);
69+
var deeper_dir = try child_dir.openDir(deeper_dir_path, .{});
70+
defer deeper_dir.close();
71+
try deeper_dir.setAsCwd();
72+
const rel_path = try std.fs.path.relative(allocator, deeper_dir_path, std.fs.path.basename(child_path));
73+
defer allocator.free(rel_path);
74+
75+
const result = try std.process.Child.run(.{ .allocator = allocator, .argv = &.{rel_path} });
76+
77+
allocator.free(result.stdout);
78+
allocator.free(result.stderr);
79+
80+
const res = std.process.Child.run(.{ .allocator = allocator, .argv = &.{"../../notafile"} });
81+
82+
// would expect this to be the same error on all platforms. pre-fix windows would return BadPathName instead.
83+
try std.testing.expectError(error.FileNotFound, res);
84+
}

test/standalone/windows_spawn/main.zig

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const std = @import("std");
2+
23
const windows = std.os.windows;
34
const utf16Literal = std.unicode.utf8ToUtf16LeStringLiteral;
45

@@ -39,6 +40,9 @@ pub fn main() anyerror!void {
3940
// No PATH, so it should fail to find anything not in the cwd
4041
try testExecError(error.FileNotFound, allocator, "something_missing");
4142

43+
// Regression test - make sure we don't get error.BadPath traversing out of cwd with a relative path
44+
try testExecError(error.FileNotFound, allocator, "..\\.\\.\\.\\\\..\\more_missing");
45+
4246
std.debug.assert(windows.kernel32.SetEnvironmentVariableW(
4347
utf16Literal("PATH"),
4448
tmp_absolute_path_w,
@@ -149,6 +153,42 @@ pub fn main() anyerror!void {
149153
// If we try to exec but provide a cwd that is an absolute path, the PATH
150154
// should still be searched and the goodbye.exe in something should be found.
151155
try testExecWithCwd(allocator, "goodbye", tmp_absolute_path, "hello from exe\n");
156+
157+
// introduce some extra path separators into the path which is dealt with inside the spawn call.
158+
const denormed_something_subdir_size = std.mem.replacementSize(u16, something_subdir_abs_path, utf16Literal("\\"), utf16Literal("\\\\\\\\"));
159+
160+
const denormed_something_subdir_abs_path = try allocator.allocSentinel(u16, denormed_something_subdir_size, 0);
161+
defer allocator.free(denormed_something_subdir_abs_path);
162+
163+
_ = std.mem.replace(u16, something_subdir_abs_path, utf16Literal("\\"), utf16Literal("\\\\\\\\"), denormed_something_subdir_abs_path);
164+
165+
const denormed_something_subdir_wtf8 = try std.unicode.wtf16LeToWtf8Alloc(allocator, denormed_something_subdir_abs_path);
166+
defer allocator.free(denormed_something_subdir_wtf8);
167+
168+
try testExecWithCwd(allocator, "goodbye", denormed_something_subdir_wtf8, "hello from exe\n");
169+
170+
// normalization should also work if the non-normalized path is found in the PATH var.
171+
std.debug.assert(windows.kernel32.SetEnvironmentVariableW(
172+
utf16Literal("PATH"),
173+
denormed_something_subdir_abs_path,
174+
) == windows.TRUE);
175+
try testExec(allocator, "goodbye", "hello from exe\n");
176+
177+
// now make sure we can launch executables "outside" of the cwd
178+
var subdir_cwd = try tmp.dir.openDir(denormed_something_subdir_wtf8, .{});
179+
defer subdir_cwd.close();
180+
181+
try tmp.dir.rename("something/goodbye.exe", "hello.exe");
182+
try subdir_cwd.setAsCwd();
183+
184+
// clear the PATH again
185+
std.debug.assert(windows.kernel32.SetEnvironmentVariableW(
186+
utf16Literal("PATH"),
187+
null,
188+
) == windows.TRUE);
189+
190+
// while we're at it make sure non-windows separators work fine
191+
try testExec(allocator, "..\\hello", "hello from exe\n");
152192
}
153193

154194
fn testExecError(err: anyerror, allocator: std.mem.Allocator, command: []const u8) !void {

0 commit comments

Comments
 (0)