Commit a31950aa57

mlugg <mlugg@mlugg.co.uk>
2025-08-27 22:35:45
std.debug: remove `@frameAddress()` "UAF"
We can't call `@frameAddress()` and then immediately `return`! That invalidates the frame. This *usually* isn't a problem, because the stack walk `next` call will *probably* have a stack frame and it will *probably* be at the exact same address, but neither of those is a guarantee. On powerpc, presumably some unfortunate inlining was going on, so this frame was indeed invalidated when we started walking frames. We need to explicitly pass `@frameAddress` into any function which will return before we actually walk the stack. Pretty simple patch. Resolves: #24970
1 parent 151c7dc
Changed files (2)
lib
test
standalone
stack_iterator
lib/std/debug.zig
@@ -440,7 +440,7 @@ pub fn dumpStackTraceFromBase(context: *ThreadContext, stderr: *Writer) void {
             return;
         }
 
-        var it = StackIterator.initWithContext(null, debug_info, context) catch return;
+        var it = StackIterator.initWithContext(null, debug_info, context, @frameAddress()) catch return;
         defer it.deinit();
 
         // DWARF unwinding on aarch64-macos is not complete so we need to get pc address from mcontext
@@ -499,12 +499,7 @@ pub fn captureStackTrace(first_address: ?usize, stack_trace: *std.builtin.StackT
         // TODO: This should use the DWARF unwinder if .eh_frame_hdr is available (so that full debug info parsing isn't required).
         //       A new path for loading SelfInfo needs to be created which will only attempt to parse in-memory sections, because
         //       stopping to load other debug info (ie. source line info) from disk here is not required for unwinding.
-        if (builtin.cpu.arch == .powerpc64) {
-            // https://github.com/ziglang/zig/issues/24970
-            stack_trace.index = 0;
-            return;
-        }
-        var it = StackIterator.init(first_address, null);
+        var it = StackIterator.init(first_address, @frameAddress());
         defer it.deinit();
         for (stack_trace.instruction_addresses, 0..) |*addr, i| {
             addr.* = it.next() orelse {
@@ -787,7 +782,7 @@ pub const StackIterator = struct {
         failed: bool = false,
     } else void = if (have_ucontext) null else {},
 
-    pub fn init(first_address: ?usize, fp: ?usize) StackIterator {
+    pub fn init(first_address: ?usize, fp: usize) StackIterator {
         if (native_arch.isSPARC()) {
             // Flush all the register windows on stack.
             asm volatile (if (builtin.cpu.has(.sparc, .v9))
@@ -799,23 +794,18 @@ pub const StackIterator = struct {
 
         return .{
             .first_address = first_address,
-            // TODO: this is a workaround for #16876
-            //.fp = fp orelse @frameAddress(),
-            .fp = fp orelse blk: {
-                const fa = @frameAddress();
-                break :blk fa;
-            },
+            .fp = fp,
         };
     }
 
-    pub fn initWithContext(first_address: ?usize, debug_info: *SelfInfo, context: *posix.ucontext_t) !StackIterator {
+    pub fn initWithContext(first_address: ?usize, debug_info: *SelfInfo, context: *posix.ucontext_t, fp: usize) !StackIterator {
         // The implementation of DWARF unwinding on aarch64-macos is not complete. However, Apple mandates that
         // the frame pointer register is always used, so on this platform we can safely use the FP-based unwinder.
         if (builtin.target.os.tag.isDarwin() and native_arch == .aarch64)
             return init(first_address, @truncate(context.mcontext.ss.fp));
 
         if (SelfInfo.supports_unwinding) {
-            var iterator = init(first_address, null);
+            var iterator = init(first_address, fp);
             iterator.unwind_state = .{
                 .debug_info = debug_info,
                 .dwarf_context = try SelfInfo.UnwindContext.init(debug_info.allocator, context),
@@ -823,7 +813,7 @@ pub const StackIterator = struct {
             return iterator;
         }
 
-        return init(first_address, null);
+        return init(first_address, fp);
     }
 
     pub fn deinit(it: *StackIterator) void {
@@ -981,8 +971,8 @@ pub fn writeCurrentStackTrace(
     const has_context = getContext(&context);
 
     var it = (if (has_context) blk: {
-        break :blk StackIterator.initWithContext(start_addr, debug_info, &context) catch null;
-    } else null) orelse StackIterator.init(start_addr, null);
+        break :blk StackIterator.initWithContext(start_addr, debug_info, &context, @frameAddress()) catch null;
+    } else null) orelse StackIterator.init(start_addr, @frameAddress());
     defer it.deinit();
 
     while (it.next()) |return_address| {
test/standalone/stack_iterator/unwind.zig
@@ -10,7 +10,7 @@ noinline fn frame3(expected: *[4]usize, unwound: *[4]usize) void {
     testing.expect(debug.getContext(&context)) catch @panic("failed to getContext");
 
     const debug_info = debug.getSelfDebugInfo() catch @panic("failed to openSelfDebugInfo");
-    var it = debug.StackIterator.initWithContext(expected[0], debug_info, &context) catch @panic("failed to initWithContext");
+    var it = debug.StackIterator.initWithContext(expected[0], debug_info, &context, @frameAddress()) catch @panic("failed to initWithContext");
     defer it.deinit();
 
     for (unwound) |*addr| {