authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-12-18 15:38:38-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-12-18 15:54:01-07:00
log53987c932c9d62cc9cdae3d523fb62756ce83ca9
tree280817d390ef900cf590c40f47cca4230b354b0e
parent2b8dcc76eba92ad44bd88756218de8d2bd8a1c10

std.crypto.random: introduce fork safety

Everybody gets what they want! * AT_RANDOM is completely ignored. * On Linux, MADV_WIPEONFORK is used to provide fork safety. * On pthread systems, `pthread_atfork` is used to provide fork safety. * For systems that do not have the capability to provide fork safety, the implementation falls back to calling getrandom() every time. * If madvise is unavailable or returns an error, or pthread_atfork fails for whatever reason, it falls back to calling getrandom() every time. * Applications may choose to opt-out of fork safety. * Applications may choose to opt-in to unconditionally calling getrandom() for every call to std.crypto.random.fillFn. * Added `std.meta.globalOption`. * Added `std.os.madvise` and related bits. * Bumped up the size of the main thread TLS buffer. See the comment there for justification. * Simpler hot path in TLS initialization.

10 files changed, 241 insertions(+), 71 deletions(-)

lib/std/c.zig+5
...@@ -264,6 +264,11 @@ pub extern "c" fn pthread_attr_setguardsize(attr: *pthread_attr_t, guardsize: us...@@ -264,6 +264,11 @@ pub extern "c" fn pthread_attr_setguardsize(attr: *pthread_attr_t, guardsize: us
264pub extern "c" fn pthread_attr_destroy(attr: *pthread_attr_t) c_int;264pub extern "c" fn pthread_attr_destroy(attr: *pthread_attr_t) c_int;
265pub extern "c" fn pthread_self() pthread_t;265pub extern "c" fn pthread_self() pthread_t;
266pub extern "c" fn pthread_join(thread: pthread_t, arg_return: ?*?*c_void) c_int;266pub extern "c" fn pthread_join(thread: pthread_t, arg_return: ?*?*c_void) c_int;
267pub extern "c" fn pthread_atfork(
268 prepare: ?fn () callconv(.C) void,
269 parent: ?fn () callconv(.C) void,
270 child: ?fn () callconv(.C) void,
271) c_int;
267272
268pub extern "c" fn kqueue() c_int;273pub extern "c" fn kqueue() c_int;
269pub extern "c" fn kevent(274pub extern "c" fn kevent(
lib/std/c/linux.zig+6
...@@ -106,6 +106,12 @@ pub extern "c" fn prlimit(pid: pid_t, resource: rlimit_resource, new_limit: *con...@@ -106,6 +106,12 @@ pub extern "c" fn prlimit(pid: pid_t, resource: rlimit_resource, new_limit: *con
106pub extern "c" fn posix_memalign(memptr: *?*c_void, alignment: usize, size: usize) c_int;106pub extern "c" fn posix_memalign(memptr: *?*c_void, alignment: usize, size: usize) c_int;
107pub extern "c" fn malloc_usable_size(?*const c_void) usize;107pub extern "c" fn malloc_usable_size(?*const c_void) usize;
108108
109pub extern "c" fn madvise(
110 addr: *align(std.mem.page_size) c_void,
111 length: usize,
112 advice: c_uint,
113) c_int;
114
109pub const pthread_attr_t = extern struct {115pub const pthread_attr_t = extern struct {
110 __size: [56]u8,116 __size: [56]u8,
111 __align: c_long,117 __align: c_long,
lib/std/crypto/tlcsprng.zig+123-29
...@@ -16,47 +16,141 @@ const mem = std.mem;...@@ -16,47 +16,141 @@ const mem = std.mem;
16/// We use this as a layer of indirection because global const pointers cannot16/// We use this as a layer of indirection because global const pointers cannot
17/// point to thread-local variables.17/// point to thread-local variables.
18pub var interface = std.rand.Random{ .fillFn = tlsCsprngFill };18pub var interface = std.rand.Random{ .fillFn = tlsCsprngFill };
19pub threadlocal var csprng_state: std.crypto.core.Gimli = undefined;19
20pub threadlocal var csprng_state_initialized = false;20const os_has_fork = switch (std.Target.current.os.tag) {
21fn tlsCsprngFill(r: *const std.rand.Random, buf: []u8) void {21 .dragonfly,
22 .freebsd,
23 .ios,
24 .kfreebsd,
25 .linux,
26 .macos,
27 .netbsd,
28 .openbsd,
29 .solaris,
30 .tvos,
31 .watchos,
32 => true,
33
34 else => false,
35};
36const os_has_arc4random = std.builtin.link_libc and @hasDecl(std.c, "arc4random_buf");
37const want_fork_safety = os_has_fork and !os_has_arc4random and
38 (std.meta.globalOption("crypto_fork_safety", bool) orelse true);
39const maybe_have_wipe_on_fork = std.Target.current.os.isAtLeast(.linux, .{
40 .major = 4,
41 .minor = 14,
42}) orelse true;
43
44const WipeMe = struct {
45 init_state: enum { uninitialized, initialized, failed },
46 gimli: std.crypto.core.Gimli,
47};
48const wipe_align = if (maybe_have_wipe_on_fork) mem.page_size else @alignOf(WipeMe);
49
50threadlocal var wipe_me: WipeMe align(wipe_align) = .{
51 .gimli = undefined,
52 .init_state = .uninitialized,
53};
54
55fn tlsCsprngFill(_: *const std.rand.Random, buffer: []u8) void {
22 if (std.builtin.link_libc and @hasDecl(std.c, "arc4random_buf")) {56 if (std.builtin.link_libc and @hasDecl(std.c, "arc4random_buf")) {
23 // arc4random is already a thread-local CSPRNG.57 // arc4random is already a thread-local CSPRNG.
24 return std.c.arc4random_buf(buf.ptr, buf.len);58 return std.c.arc4random_buf(buffer.ptr, buffer.len);
25 }59 }
26 if (!csprng_state_initialized) {60 // Allow applications to decide they would prefer to have every call to
27 var seed: [seed_len]u8 = undefined;61 // std.crypto.random always make an OS syscall, rather than rely on an
28 // Because we panic on getrandom() failing, we provide the opportunity62 // application implementation of a CSPRNG.
29 // to override the default seed function. This also makes63 if (comptime std.meta.globalOption("crypto_always_getrandom", bool) orelse false) {
30 // `std.crypto.random` available on freestanding targets, provided that64 return fillWithOsEntropy(buffer);
31 // the `cryptoRandomSeed` function is provided.
32 if (@hasDecl(root, "cryptoRandomSeed")) {
33 root.cryptoRandomSeed(&seed);
34 } else {
35 defaultSeed(&seed);
36 }
37 init(seed);
38 }65 }
39 if (buf.len != 0) {66 switch (wipe_me.init_state) {
40 csprng_state.squeeze(buf);67 .uninitialized => {
68 if (want_fork_safety) {
69 if (maybe_have_wipe_on_fork) {
70 if (std.os.madvise(
71 @ptrCast([*]align(mem.page_size) u8, &wipe_me),
72 @sizeOf(@TypeOf(wipe_me)),
73 std.os.MADV_WIPEONFORK,
74 )) |_| {
75 return initAndFill(buffer);
76 } else |_| if (std.Thread.use_pthreads) {
77 return setupPthreadAtforkAndFill(buffer);
78 } else {
79 // Since we failed to set up fork safety, we fall back to always
80 // calling getrandom every time.
81 wipe_me.init_state = .failed;
82 return fillWithOsEntropy(buffer);
83 }
84 } else if (std.Thread.use_pthreads) {
85 return setupPthreadAtforkAndFill(buffer);
86 } else {
87 // We have no mechanism to provide fork safety, but we want fork safety,
88 // so we fall back to calling getrandom every time.
89 wipe_me.init_state = .failed;
90 return fillWithOsEntropy(buffer);
91 }
92 } else {
93 return initAndFill(buffer);
94 }
95 },
96 .initialized => {
97 return fillWithCsprng(buffer);
98 },
99 .failed => {
100 if (want_fork_safety) {
101 return fillWithOsEntropy(buffer);
102 } else {
103 unreachable;
104 }
105 },
106 }
107}
108
109fn setupPthreadAtforkAndFill(buffer: []u8) void {
110 const failed = std.c.pthread_atfork(null, null, childAtForkHandler) != 0;
111 if (failed) {
112 wipe_me.init_state = .failed;
113 return fillWithOsEntropy(buffer);
41 } else {114 } else {
42 csprng_state.permute();115 return initAndFill(buffer);
43 }116 }
44 mem.set(u8, csprng_state.toSlice()[0..std.crypto.core.Gimli.RATE], 0);
45}117}
46118
47fn defaultSeed(buffer: *[seed_len]u8) void {119fn childAtForkHandler() callconv(.C) void {
48 std.os.getrandom(buffer) catch @panic("getrandom() failed to seed thread-local CSPRNG");120 const wipe_slice = @ptrCast([*]u8, &wipe_me)[0..@sizeOf(@TypeOf(wipe_me))];
121 std.crypto.utils.secureZero(u8, wipe_slice);
49}122}
50123
51pub const seed_len = 16;124fn fillWithCsprng(buffer: []u8) void {
125 if (buffer.len != 0) {
126 wipe_me.gimli.squeeze(buffer);
127 } else {
128 wipe_me.gimli.permute();
129 }
130 mem.set(u8, wipe_me.gimli.toSlice()[0..std.crypto.core.Gimli.RATE], 0);
131}
132
133fn fillWithOsEntropy(buffer: []u8) void {
134 std.os.getrandom(buffer) catch @panic("getrandom() failed to provide entropy");
135}
52136
53pub fn init(seed: [seed_len]u8) void {137fn initAndFill(buffer: []u8) void {
54 var initial_state: [std.crypto.core.Gimli.BLOCKBYTES]u8 = undefined;138 var seed: [std.crypto.core.Gimli.BLOCKBYTES]u8 = undefined;
55 mem.copy(u8, initial_state[0..seed_len], &seed);139 // Because we panic on getrandom() failing, we provide the opportunity
56 mem.set(u8, initial_state[seed_len..], 0);140 // to override the default seed function. This also makes
57 csprng_state = std.crypto.core.Gimli.init(initial_state);141 // `std.crypto.random` available on freestanding targets, provided that
142 // the `cryptoRandomSeed` function is provided.
143 if (@hasDecl(root, "cryptoRandomSeed")) {
144 root.cryptoRandomSeed(&seed);
145 } else {
146 fillWithOsEntropy(&seed);
147 }
148
149 wipe_me.gimli = std.crypto.core.Gimli.init(seed);
58150
59 // This is at the end so that accidental recursive dependencies result151 // This is at the end so that accidental recursive dependencies result
60 // in stack overflows instead of invalid random data.152 // in stack overflows instead of invalid random data.
61 csprng_state_initialized = true;153 wipe_me.init_state = .initialized;
154
155 return fillWithCsprng(buffer);
62}156}
lib/std/meta.zig+8
...@@ -9,6 +9,7 @@ const debug = std.debug;...@@ -9,6 +9,7 @@ const debug = std.debug;
9const mem = std.mem;9const mem = std.mem;
10const math = std.math;10const math = std.math;
11const testing = std.testing;11const testing = std.testing;
12const root = @import("root");
1213
13pub const trait = @import("meta/trait.zig");14pub const trait = @import("meta/trait.zig");
14pub const TrailerFlags = @import("meta/trailer_flags.zig").TrailerFlags;15pub const TrailerFlags = @import("meta/trailer_flags.zig").TrailerFlags;
...@@ -1085,3 +1086,10 @@ test "Tuple" {...@@ -1085,3 +1086,10 @@ test "Tuple" {
1085 TupleTester.assertTuple(.{ u32, f16 }, Tuple(&[_]type{ u32, f16 }));1086 TupleTester.assertTuple(.{ u32, f16 }, Tuple(&[_]type{ u32, f16 }));
1086 TupleTester.assertTuple(.{ u32, f16, []const u8, void }, Tuple(&[_]type{ u32, f16, []const u8, void }));1087 TupleTester.assertTuple(.{ u32, f16, []const u8, void }, Tuple(&[_]type{ u32, f16, []const u8, void }));
1087}1088}
1089
1090/// TODO: https://github.com/ziglang/zig/issues/425
1091pub fn globalOption(comptime name: []const u8, comptime T: type) ?T {
1092 if (!@hasDecl(root, name))
1093 return null;
1094 return @as(T, @field(root, name));
1095}
lib/std/os.zig+48
...@@ -5845,3 +5845,51 @@ pub fn setrlimit(resource: rlimit_resource, limits: rlimit) SetrlimitError!void...@@ -5845,3 +5845,51 @@ pub fn setrlimit(resource: rlimit_resource, limits: rlimit) SetrlimitError!void
5845 else => |err| return unexpectedErrno(err),5845 else => |err| return unexpectedErrno(err),
5846 }5846 }
5847}5847}
5848
5849pub const MadviseError = error{
5850 /// advice is MADV_REMOVE, but the specified address range is not a shared writable mapping.
5851 AccessDenied,
5852 /// advice is MADV_HWPOISON, but the caller does not have the CAP_SYS_ADMIN capability.
5853 PermissionDenied,
5854 /// A kernel resource was temporarily unavailable.
5855 SystemResources,
5856 /// One of the following:
5857 /// * addr is not page-aligned or length is negative
5858 /// * advice is not valid
5859 /// * advice is MADV_DONTNEED or MADV_REMOVE and the specified address range
5860 /// includes locked, Huge TLB pages, or VM_PFNMAP pages.
5861 /// * advice is MADV_MERGEABLE or MADV_UNMERGEABLE, but the kernel was not
5862 /// configured with CONFIG_KSM.
5863 /// * advice is MADV_FREE or MADV_WIPEONFORK but the specified address range
5864 /// includes file, Huge TLB, MAP_SHARED, or VM_PFNMAP ranges.
5865 InvalidSyscall,
5866 /// (for MADV_WILLNEED) Paging in this area would exceed the process's
5867 /// maximum resident set size.
5868 WouldExceedMaximumResidentSetSize,
5869 /// One of the following:
5870 /// * (for MADV_WILLNEED) Not enough memory: paging in failed.
5871 /// * Addresses in the specified range are not currently mapped, or
5872 /// are outside the address space of the process.
5873 OutOfMemory,
5874 /// The madvise syscall is not available on this version and configuration
5875 /// of the Linux kernel.
5876 MadviseUnavailable,
5877 /// The operating system returned an undocumented error code.
5878 Unexpected,
5879};
5880
5881/// Give advice about use of memory.
5882/// This syscall is optional and is sometimes configured to be disabled.
5883pub fn madvise(ptr: [*]align(mem.page_size) u8, length: usize, advice: u32) MadviseError!void {
5884 switch (errno(system.madvise(ptr, length, advice))) {
5885 0 => return,
5886 EACCES => return error.AccessDenied,
5887 EAGAIN => return error.SystemResources,
5888 EBADF => unreachable, // The map exists, but the area maps something that isn't a file.
5889 EINVAL => return error.InvalidSyscall,
5890 EIO => return error.WouldExceedMaximumResidentSetSize,
5891 ENOMEM => return error.OutOfMemory,
5892 ENOSYS => return error.MadviseUnavailable,
5893 else => |err| return unexpectedErrno(err),
5894 }
5895}
lib/std/os/bits/linux.zig+22
...@@ -2045,3 +2045,25 @@ pub const rlimit = extern struct {...@@ -2045,3 +2045,25 @@ pub const rlimit = extern struct {
2045 /// Hard limit2045 /// Hard limit
2046 max: rlim_t,2046 max: rlim_t,
2047};2047};
2048
2049pub const MADV_NORMAL = 0;
2050pub const MADV_RANDOM = 1;
2051pub const MADV_SEQUENTIAL = 2;
2052pub const MADV_WILLNEED = 3;
2053pub const MADV_DONTNEED = 4;
2054pub const MADV_FREE = 8;
2055pub const MADV_REMOVE = 9;
2056pub const MADV_DONTFORK = 10;
2057pub const MADV_DOFORK = 11;
2058pub const MADV_MERGEABLE = 12;
2059pub const MADV_UNMERGEABLE = 13;
2060pub const MADV_HUGEPAGE = 14;
2061pub const MADV_NOHUGEPAGE = 15;
2062pub const MADV_DONTDUMP = 16;
2063pub const MADV_DODUMP = 17;
2064pub const MADV_WIPEONFORK = 18;
2065pub const MADV_KEEPONFORK = 19;
2066pub const MADV_COLD = 20;
2067pub const MADV_PAGEOUT = 21;
2068pub const MADV_HWPOISON = 100;
2069pub const MADV_SOFT_OFFLINE = 101;
lib/std/os/linux.zig+4
...@@ -1351,6 +1351,10 @@ pub fn prlimit(pid: pid_t, resource: rlimit_resource, new_limit: ?*const rlimit,...@@ -1351,6 +1351,10 @@ pub fn prlimit(pid: pid_t, resource: rlimit_resource, new_limit: ?*const rlimit,
1351 );1351 );
1352}1352}
13531353
1354pub fn madvise(address: [*]u8, len: usize, advice: u32) usize {
1355 return syscall3(.madvise, @ptrToInt(address), len, advice);
1356}
1357
1354test "" {1358test "" {
1355 if (builtin.os.tag == .linux) {1359 if (builtin.os.tag == .linux) {
1356 _ = @import("linux/test.zig");1360 _ = @import("linux/test.zig");
lib/std/os/linux/tls.zig+24-15
...@@ -327,34 +327,43 @@ pub fn prepareTLS(area: []u8) usize {...@@ -327,34 +327,43 @@ pub fn prepareTLS(area: []u8) usize {
327 if (tls_tp_points_past_tcb) tls_image.data_offset else tls_image.tcb_offset;327 if (tls_tp_points_past_tcb) tls_image.data_offset else tls_image.tcb_offset;
328}328}
329329
330var main_thread_tls_buffer: [256]u8 = undefined;330// The main motivation for the size chosen here is this is how much ends up being
331// requested for the thread local variables of the std.crypto.random implementation.
332// I'm not sure why it ends up being so much; the struct itself is only 64 bytes.
333// I think it has to do with being page aligned and LLVM or LLD is not smart enough
334// to lay out the TLS data in a space conserving way. Anyway I think it's fine
335// because it's less than 3 pages of memory, and putting it in the ELF like this
336// is equivalent to moving the mmap call below into the kernel, avoiding syscall
337// overhead.
338var main_thread_tls_buffer: [0x2100]u8 align(mem.page_size) = undefined;
331339
332pub fn initStaticTLS() void {340pub fn initStaticTLS() void {
333 initTLS();341 initTLS();
334342
335 const alloc_tls_area: []u8 = blk: {343 const tls_area = blk: {
336 const full_alloc_size = tls_image.alloc_size + tls_image.alloc_align - 1;
337
338 // Fast path for the common case where the TLS data is really small,344 // Fast path for the common case where the TLS data is really small,
339 // avoid an allocation and use our local buffer345 // avoid an allocation and use our local buffer.
340 if (full_alloc_size < main_thread_tls_buffer.len)346 if (tls_image.alloc_align <= mem.page_size and
341 break :blk main_thread_tls_buffer[0..];347 tls_image.alloc_size <= main_thread_tls_buffer.len)
348 {
349 break :blk main_thread_tls_buffer[0..tls_image.alloc_size];
350 }
342351
343 break :blk os.mmap(352 const alloc_tls_area = os.mmap(
344 null,353 null,
345 full_alloc_size,354 tls_image.alloc_size + tls_image.alloc_align - 1,
346 os.PROT_READ | os.PROT_WRITE,355 os.PROT_READ | os.PROT_WRITE,
347 os.MAP_PRIVATE | os.MAP_ANONYMOUS,356 os.MAP_PRIVATE | os.MAP_ANONYMOUS,
348 -1,357 -1,
349 0,358 0,
350 ) catch os.abort();359 ) catch os.abort();
351 };
352360
353 // Make sure the slice is correctly aligned361 // Make sure the slice is correctly aligned.
354 const begin_addr = @ptrToInt(alloc_tls_area.ptr);362 const begin_addr = @ptrToInt(alloc_tls_area.ptr);
355 const begin_aligned_addr = mem.alignForward(begin_addr, tls_image.alloc_align);363 const begin_aligned_addr = mem.alignForward(begin_addr, tls_image.alloc_align);
356 const start = begin_aligned_addr - begin_addr;364 const start = begin_aligned_addr - begin_addr;
357 const tls_area = alloc_tls_area[start .. start + tls_image.alloc_size];365 break :blk alloc_tls_area[start .. start + tls_image.alloc_size];
366 };
358367
359 const tp_value = prepareTLS(tls_area);368 const tp_value = prepareTLS(tls_area);
360 setThreadPointer(tp_value);369 setThreadPointer(tp_value);
lib/std/start.zig-26
...@@ -216,12 +216,6 @@ fn posixCallMainAndExit() noreturn {...@@ -216,12 +216,6 @@ fn posixCallMainAndExit() noreturn {
216 std.os.linux.tls.initStaticTLS();216 std.os.linux.tls.initStaticTLS();
217 }217 }
218218
219 if (!@hasDecl(root, "use_AT_RANDOM_auxval") or root.use_AT_RANDOM_auxval) {
220 // Initialize the per-thread CSPRNG since Linux gave us the handy-dandy
221 // AT_RANDOM. This depends on the TLS initialization above.
222 initCryptoSeedFromAuxVal(std.os.linux.getauxval(std.elf.AT_RANDOM));
223 }
224
225 // TODO This is disabled because what should we do when linking libc and this code219 // TODO This is disabled because what should we do when linking libc and this code
226 // does not execute? And also it's causing a test failure in stack traces in release modes.220 // does not execute? And also it's causing a test failure in stack traces in release modes.
227221
...@@ -257,32 +251,12 @@ fn callMainWithArgs(argc: usize, argv: [*][*:0]u8, envp: [][*:0]u8) u8 {...@@ -257,32 +251,12 @@ fn callMainWithArgs(argc: usize, argv: [*][*:0]u8, envp: [][*:0]u8) u8 {
257}251}
258252
259fn main(c_argc: i32, c_argv: [*][*:0]u8, c_envp: [*:null]?[*:0]u8) callconv(.C) i32 {253fn main(c_argc: i32, c_argv: [*][*:0]u8, c_envp: [*:null]?[*:0]u8) callconv(.C) i32 {
260 // By default, we do not attempt to initialize tlcsprng from AT_RANDOM here because
261 // libc owns the start code, not us, and therefore libc owns the random bytes
262 // from AT_RANDOM.
263 if (builtin.os.tag == .linux and
264 @hasDecl(root, "use_AT_RANDOM_auxval") and
265 root.use_AT_RANDOM_auxval)
266 {
267 initCryptoSeedFromAuxVal(std.c.getauxval(std.elf.AT_RANDOM));
268 }
269 var env_count: usize = 0;254 var env_count: usize = 0;
270 while (c_envp[env_count] != null) : (env_count += 1) {}255 while (c_envp[env_count] != null) : (env_count += 1) {}
271 const envp = @ptrCast([*][*:0]u8, c_envp)[0..env_count];256 const envp = @ptrCast([*][*:0]u8, c_envp)[0..env_count];
272 return @call(.{ .modifier = .always_inline }, callMainWithArgs, .{ @intCast(usize, c_argc), c_argv, envp });257 return @call(.{ .modifier = .always_inline }, callMainWithArgs, .{ @intCast(usize, c_argc), c_argv, envp });
273}258}
274259
275fn initCryptoSeedFromAuxVal(addr: usize) void {
276 if (addr == 0) return;
277 // "The address of sixteen bytes containing a random value."
278 const ptr = @intToPtr(*[16]u8, addr);
279 tlcsprng.init(ptr.*);
280 // Clear AT_RANDOM after we use it, otherwise our secure
281 // seed is sitting in memory ready for some other code in the
282 // program to reuse, and hence break our security.
283 std.crypto.utils.secureZero(u8, ptr);
284}
285
286// General error message for a malformed return type260// General error message for a malformed return type
287const bad_main_ret = "expected return type of main to be 'void', '!void', 'noreturn', 'u8', or '!u8'";261const bad_main_ret = "expected return type of main to be 'void', '!void', 'noreturn', 'u8', or '!u8'";
288262
test/stack_traces.zig+1-1
...@@ -282,7 +282,7 @@ pub fn addCases(cases: *tests.StackTracesContext) void {...@@ -282,7 +282,7 @@ pub fn addCases(cases: *tests.StackTracesContext) void {
282 \\source.zig:10:8: [address] in main (test)282 \\source.zig:10:8: [address] in main (test)
283 \\ foo();283 \\ foo();
284 \\ ^284 \\ ^
285 \\start.zig:377:29: [address] in std.start.posixCallMainAndExit (test)285 \\start.zig:342:29: [address] in std.start.posixCallMainAndExit (test)
286 \\ return root.main();286 \\ return root.main();
287 \\ ^287 \\ ^
288 \\start.zig:163:5: [address] in std.start._start (test)288 \\start.zig:163:5: [address] in std.start._start (test)