std.os.execvpeZ_expandArg0: fix not restoring argv[0]

This function expands argv[0] into the absolute path resolved with PATH
environment variable before making the execve syscall. However, in case
the execve fails, e.g. with ENOENT, it did not restore argv to how it
was before it was passed in. This resulted in the caller performing an
invalid free.

This commit also adds verbose debug info when native system C compiler
detection fails. See #4521.
master
Andrew Kelley 2020-02-22 13:43:48 -05:00
parent dca19b6757
commit 0cd89e9176
No known key found for this signature in database
GPG Key ID: 7C5F548F728501A9
3 changed files with 130 additions and 36 deletions

View File

@ -955,6 +955,7 @@ pub const Arg0Expand = enum {
/// Like `execvpeZ` except if `arg0_expand` is `.expand`, then `argv` is mutable,
/// and `argv[0]` is expanded to be the same absolute path that is passed to the execve syscall.
/// If this function returns with an error, `argv[0]` will be restored to the value it was when it was passed in.
pub fn execvpeZ_expandArg0(
comptime arg0_expand: Arg0Expand,
file: [*:0]const u8,
@ -972,6 +973,14 @@ pub fn execvpeZ_expandArg0(
var it = mem.tokenize(PATH, ":");
var seen_eacces = false;
var err: ExecveError = undefined;
// In case of expanding arg0 we must put it back if we return with an error.
const prev_arg0 = child_argv[0];
defer switch (arg0_expand) {
.expand => child_argv[0] = prev_arg0,
.no_expand => {},
};
while (it.next()) |search_path| {
if (path_buf.len < search_path.len + file_slice.len + 1) return error.NameTooLong;
mem.copy(u8, &path_buf, search_path);

View File

@ -174,16 +174,23 @@ pub const LibCInstallation = struct {
});
}
pub const FindNativeOptions = struct {
allocator: *Allocator,
/// If enabled, will print human-friendly errors to stderr.
verbose: bool = false,
};
/// Finds the default, native libc.
pub fn findNative(allocator: *Allocator) FindError!LibCInstallation {
pub fn findNative(args: FindNativeOptions) FindError!LibCInstallation {
var self: LibCInstallation = .{};
if (is_windows) {
if (is_gnu) {
var batch = Batch(FindError!void, 3, .auto_async).init();
batch.add(&async self.findNativeIncludeDirPosix(allocator));
batch.add(&async self.findNativeCrtDirPosix(allocator));
batch.add(&async self.findNativeStaticCrtDirPosix(allocator));
batch.add(&async self.findNativeIncludeDirPosix(args));
batch.add(&async self.findNativeCrtDirPosix(args));
batch.add(&async self.findNativeStaticCrtDirPosix(args));
try batch.wait();
} else {
var sdk: *ZigWindowsSDK = undefined;
@ -192,11 +199,11 @@ pub const LibCInstallation = struct {
defer zig_free_windows_sdk(sdk);
var batch = Batch(FindError!void, 5, .auto_async).init();
batch.add(&async self.findNativeMsvcIncludeDir(allocator, sdk));
batch.add(&async self.findNativeMsvcLibDir(allocator, sdk));
batch.add(&async self.findNativeKernel32LibDir(allocator, sdk));
batch.add(&async self.findNativeIncludeDirWindows(allocator, sdk));
batch.add(&async self.findNativeCrtDirWindows(allocator, sdk));
batch.add(&async self.findNativeMsvcIncludeDir(args, sdk));
batch.add(&async self.findNativeMsvcLibDir(args, sdk));
batch.add(&async self.findNativeKernel32LibDir(args, sdk));
batch.add(&async self.findNativeIncludeDirWindows(args, sdk));
batch.add(&async self.findNativeCrtDirWindows(args, sdk));
try batch.wait();
},
.OutOfMemory => return error.OutOfMemory,
@ -208,11 +215,11 @@ pub const LibCInstallation = struct {
try blk: {
var batch = Batch(FindError!void, 2, .auto_async).init();
errdefer batch.wait() catch {};
batch.add(&async self.findNativeIncludeDirPosix(allocator));
batch.add(&async self.findNativeIncludeDirPosix(args));
if (is_freebsd or is_netbsd) {
self.crt_dir = try std.mem.dupeZ(allocator, u8, "/usr/lib");
self.crt_dir = try std.mem.dupeZ(args.allocator, u8, "/usr/lib");
} else if (is_linux or is_dragonfly) {
batch.add(&async self.findNativeCrtDirPosix(allocator));
batch.add(&async self.findNativeCrtDirPosix(args));
}
break :blk batch.wait();
};
@ -231,7 +238,8 @@ pub const LibCInstallation = struct {
self.* = undefined;
}
fn findNativeIncludeDirPosix(self: *LibCInstallation, allocator: *Allocator) FindError!void {
fn findNativeIncludeDirPosix(self: *LibCInstallation, args: FindNativeOptions) FindError!void {
const allocator = args.allocator;
const dev_null = if (is_windows) "nul" else "/dev/null";
const cc_exe = std.os.getenvZ("CC") orelse default_cc_exe;
const argv = [_][]const u8{
@ -252,15 +260,24 @@ pub const LibCInstallation = struct {
.expand_arg0 = .expand,
}) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
else => return error.UnableToSpawnCCompiler,
else => {
printVerboseInvocation(&argv, null, args.verbose, null);
return error.UnableToSpawnCCompiler;
},
};
defer {
allocator.free(exec_res.stdout);
allocator.free(exec_res.stderr);
}
switch (exec_res.term) {
.Exited => |code| if (code != 0) return error.CCompilerExitCode,
else => return error.CCompilerCrashed,
.Exited => |code| if (code != 0) {
printVerboseInvocation(&argv, null, args.verbose, exec_res.stderr);
return error.CCompilerExitCode;
},
else => {
printVerboseInvocation(&argv, null, args.verbose, exec_res.stderr);
return error.CCompilerCrashed;
},
}
var it = std.mem.tokenize(exec_res.stderr, "\n\r");
@ -322,9 +339,11 @@ pub const LibCInstallation = struct {
fn findNativeIncludeDirWindows(
self: *LibCInstallation,
allocator: *Allocator,
args: FindNativeOptions,
sdk: *ZigWindowsSDK,
) FindError!void {
const allocator = args.allocator;
var search_buf: [2]Search = undefined;
const searches = fillSearch(&search_buf, sdk);
@ -358,7 +377,13 @@ pub const LibCInstallation = struct {
return error.LibCStdLibHeaderNotFound;
}
fn findNativeCrtDirWindows(self: *LibCInstallation, allocator: *Allocator, sdk: *ZigWindowsSDK) FindError!void {
fn findNativeCrtDirWindows(
self: *LibCInstallation,
args: FindNativeOptions,
sdk: *ZigWindowsSDK,
) FindError!void {
const allocator = args.allocator;
var search_buf: [2]Search = undefined;
const searches = fillSearch(&search_buf, sdk);
@ -398,15 +423,31 @@ pub const LibCInstallation = struct {
return error.LibCRuntimeNotFound;
}
fn findNativeCrtDirPosix(self: *LibCInstallation, allocator: *Allocator) FindError!void {
self.crt_dir = try ccPrintFileName(allocator, "crt1.o", .only_dir);
fn findNativeCrtDirPosix(self: *LibCInstallation, args: FindNativeOptions) FindError!void {
self.crt_dir = try ccPrintFileName(.{
.allocator = args.allocator,
.search_basename = "crt1.o",
.want_dirname = .only_dir,
.verbose = args.verbose,
});
}
fn findNativeStaticCrtDirPosix(self: *LibCInstallation, allocator: *Allocator) FindError!void {
self.static_crt_dir = try ccPrintFileName(allocator, "crtbegin.o", .only_dir);
fn findNativeStaticCrtDirPosix(self: *LibCInstallation, args: FindNativeOptions) FindError!void {
self.static_crt_dir = try ccPrintFileName(.{
.allocator = args.allocator,
.search_basename = "crtbegin.o",
.want_dirname = .only_dir,
.verbose = args.verbose,
});
}
fn findNativeKernel32LibDir(self: *LibCInstallation, allocator: *Allocator, sdk: *ZigWindowsSDK) FindError!void {
fn findNativeKernel32LibDir(
self: *LibCInstallation,
args: FindNativeOptions,
sdk: *ZigWindowsSDK,
) FindError!void {
const allocator = args.allocator;
var search_buf: [2]Search = undefined;
const searches = fillSearch(&search_buf, sdk);
@ -448,9 +489,11 @@ pub const LibCInstallation = struct {
fn findNativeMsvcIncludeDir(
self: *LibCInstallation,
allocator: *Allocator,
args: FindNativeOptions,
sdk: *ZigWindowsSDK,
) FindError!void {
const allocator = args.allocator;
const msvc_lib_dir_ptr = sdk.msvc_lib_dir_ptr orelse return error.LibCStdLibHeaderNotFound;
const msvc_lib_dir = msvc_lib_dir_ptr[0..sdk.msvc_lib_dir_len];
const up1 = fs.path.dirname(msvc_lib_dir) orelse return error.LibCStdLibHeaderNotFound;
@ -481,9 +524,10 @@ pub const LibCInstallation = struct {
fn findNativeMsvcLibDir(
self: *LibCInstallation,
allocator: *Allocator,
args: FindNativeOptions,
sdk: *ZigWindowsSDK,
) FindError!void {
const allocator = args.allocator;
const msvc_lib_dir_ptr = sdk.msvc_lib_dir_ptr orelse return error.LibCRuntimeNotFound;
self.msvc_lib_dir = try std.mem.dupeZ(allocator, u8, msvc_lib_dir_ptr[0..sdk.msvc_lib_dir_len]);
}
@ -491,14 +535,19 @@ pub const LibCInstallation = struct {
const default_cc_exe = if (is_windows) "cc.exe" else "cc";
/// caller owns returned memory
fn ccPrintFileName(
pub const CCPrintFileNameOptions = struct {
allocator: *Allocator,
o_file: []const u8,
search_basename: []const u8,
want_dirname: enum { full_path, only_dir },
) ![:0]u8 {
verbose: bool = false,
};
/// caller owns returned memory
fn ccPrintFileName(args: CCPrintFileNameOptions) ![:0]u8 {
const allocator = args.allocator;
const cc_exe = std.os.getenvZ("CC") orelse default_cc_exe;
const arg1 = try std.fmt.allocPrint(allocator, "-print-file-name={}", .{o_file});
const arg1 = try std.fmt.allocPrint(allocator, "-print-file-name={}", .{args.search_basename});
defer allocator.free(arg1);
const argv = [_][]const u8{ cc_exe, arg1 };
@ -520,16 +569,22 @@ fn ccPrintFileName(
allocator.free(exec_res.stderr);
}
switch (exec_res.term) {
.Exited => |code| if (code != 0) return error.CCompilerExitCode,
else => return error.CCompilerCrashed,
.Exited => |code| if (code != 0) {
printVerboseInvocation(&argv, args.search_basename, args.verbose, exec_res.stderr);
return error.CCompilerExitCode;
},
else => {
printVerboseInvocation(&argv, args.search_basename, args.verbose, exec_res.stderr);
return error.CCompilerCrashed;
},
}
var it = std.mem.tokenize(exec_res.stdout, "\n\r");
const line = it.next() orelse return error.LibCRuntimeNotFound;
// When this command fails, it returns exit code 0 and duplicates the input file name.
// So we detect failure by checking if the output matches exactly the input.
if (std.mem.eql(u8, line, o_file)) return error.LibCRuntimeNotFound;
switch (want_dirname) {
if (std.mem.eql(u8, line, args.search_basename)) return error.LibCRuntimeNotFound;
switch (args.want_dirname) {
.full_path => return std.mem.dupeZ(allocator, u8, line),
.only_dir => {
const dirname = fs.path.dirname(line) orelse return error.LibCRuntimeNotFound;
@ -538,6 +593,29 @@ fn ccPrintFileName(
}
}
fn printVerboseInvocation(
argv: []const []const u8,
search_basename: ?[]const u8,
verbose: bool,
stderr: ?[]const u8,
) void {
if (!verbose) return;
if (search_basename) |s| {
std.debug.warn("Zig attempted to find the file '{}' by executing this command:\n", .{s});
} else {
std.debug.warn("Zig attempted to find the path to native system libc headers by executing this command:\n", .{});
}
for (argv) |arg, i| {
if (i != 0) std.debug.warn(" ", .{});
std.debug.warn("{}", .{arg});
}
std.debug.warn("\n", .{});
if (stderr) |s| {
std.debug.warn("Output:\n==========\n{}\n==========\n", .{s});
}
}
/// Caller owns returned memory.
pub fn detectNativeDynamicLinker(allocator: *Allocator) error{
OutOfMemory,
@ -617,7 +695,11 @@ pub fn detectNativeDynamicLinker(allocator: *Allocator) error{
for (ld_info_list.toSlice()) |ld_info| {
const standard_ld_basename = fs.path.basename(ld_info.ld_path);
const full_ld_path = ccPrintFileName(allocator, standard_ld_basename, .full_path) catch |err| switch (err) {
const full_ld_path = ccPrintFileName(.{
.allocator = allocator,
.search_basename = standard_ld_basename,
.want_dirname = .full_path,
}) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.LibCRuntimeNotFound,
error.CCompilerExitCode,

View File

@ -899,7 +899,10 @@ export fn stage2_libc_parse(stage1_libc: *Stage2LibCInstallation, libc_file_z: [
// ABI warning
export fn stage2_libc_find_native(stage1_libc: *Stage2LibCInstallation) Error {
var libc = LibCInstallation.findNative(std.heap.c_allocator) catch |err| switch (err) {
var libc = LibCInstallation.findNative(.{
.allocator = std.heap.c_allocator,
.verbose = true,
}) catch |err| switch (err) {
error.OutOfMemory => return .OutOfMemory,
error.FileSystem => return .FileSystem,
error.UnableToSpawnCCompiler => return .UnableToSpawnCCompiler,