https://github.com/tanderson-google updated 
https://github.com/llvm/llvm-project/pull/220662

>From d083f672aaf45340fb5c8717ba644008df1d515e Mon Sep 17 00:00:00 2001
From: Tom Anderson <[email protected]>
Date: Wed, 2 Sep 2026 16:59:07 +0000
Subject: [PATCH] [libunwind] Bound the DWARF expression evaluator operand
 stack

`evaluateExpression` interprets DW_OP_* bytecode against a fixed-size
`pint_t stack[100]` operand stack without checking whether `sp` stays
within array bounds. Malformed or hostile DWARF expressions (such as
those injected via dynamic FDE registration) pushing more than 100 values
or underflowing the stack could corrupt the native call stack.

Add bounds checking on `sp` within `evaluateExpression`:
- Bound `sp` to `[&stack[1], &stack[98]]` at each iteration in the opcode
  loop, as single-step opcodes push at most 1 value and net-pop at most 1.
- Add index/depth validation for `DW_OP_pick` and `DW_OP_rot`.
- Ensure `sp >= &stack[1]` on exit from the evaluator.
- Abort execution via `_LIBUNWIND_ABORT` when bounds are violated.
- Add a regression test verifying that an overflowing DWARF expression
  aborts safely rather than overflowing the stack.
---
 libunwind/src/DwarfInstructions.hpp           | 18 ++++-
 .../test/dwarf_expression_stack.pass.cpp      | 81 +++++++++++++++++++
 2 files changed, 98 insertions(+), 1 deletion(-)
 create mode 100644 libunwind/test/dwarf_expression_stack.pass.cpp

diff --git a/libunwind/src/DwarfInstructions.hpp 
b/libunwind/src/DwarfInstructions.hpp
index 8868932d6821f..b06877d45f689 100644
--- a/libunwind/src/DwarfInstructions.hpp
+++ b/libunwind/src/DwarfInstructions.hpp
@@ -447,11 +447,21 @@ DwarfInstructions<A, R>::evaluateExpression(pint_t 
expression, A &addressSpace,
   if (log)
     fprintf(stderr, "evaluateExpression(): length=%" PRIu64 "\n",
             (uint64_t)length);
-  pint_t stack[100];
+  constexpr size_t kStackSize = 100;
+  pint_t stack[kStackSize];
   pint_t *sp = stack;
   *(++sp) = initialStackValue;
 
   while (p < expressionEnd) {
+    // Bounds-check the operand stack. Every opcode below pushes at most one
+    // value (writing at most sp[1]) and, except for DW_OP_pick and DW_OP_rot
+    // (checked at their use), reads/writes no deeper than sp[-1]. Keeping sp
+    // within [&stack[1], &stack[kStackSize - 2]] here therefore bounds every
+    // access to the fixed-size array. Compiler-emitted CFI expressions use 
tiny
+    // stack depths; violating these bounds means corrupted or malicious unwind
+    // data (e.g. a hostile FDE registered via __register_frame()).
+    if (sp < &stack[1] || sp > &stack[kStackSize - 2])
+      _LIBUNWIND_ABORT("DWARF expression operand stack out of bounds");
     if (log) {
       for (pint_t *t = sp; t > stack; --t) {
         fprintf(stderr, "sp[] = 0x%" PRIx64 "\n", (uint64_t)(*t));
@@ -594,6 +604,8 @@ DwarfInstructions<A, R>::evaluateExpression(pint_t 
expression, A &addressSpace,
       // pick from
       reg = addressSpace.get8(p);
       p += 1;
+      if (sp - (int)reg < &stack[1])
+        _LIBUNWIND_ABORT("DW_OP_pick index out of bounds");
       value = sp[-(int)reg];
       *(++sp) = value;
       if (log)
@@ -611,6 +623,8 @@ DwarfInstructions<A, R>::evaluateExpression(pint_t 
expression, A &addressSpace,
 
     case DW_OP_rot:
       // rotate top three
+      if (sp < &stack[3])
+        _LIBUNWIND_ABORT("DW_OP_rot with fewer than three stack entries");
       value = sp[0];
       sp[0] = sp[-1];
       sp[-1] = sp[-2];
@@ -970,6 +984,8 @@ DwarfInstructions<A, R>::evaluateExpression(pint_t 
expression, A &addressSpace,
     }
 
   }
+  if (sp < &stack[1])
+    _LIBUNWIND_ABORT("DWARF expression operand stack out of bounds");
   if (log)
     fprintf(stderr, "expression evaluates to 0x%" PRIx64 "\n", (uint64_t)*sp);
   return *sp;
diff --git a/libunwind/test/dwarf_expression_stack.pass.cpp 
b/libunwind/test/dwarf_expression_stack.pass.cpp
new file mode 100644
index 0000000000000..5a6c84492fb66
--- /dev/null
+++ b/libunwind/test/dwarf_expression_stack.pass.cpp
@@ -0,0 +1,81 @@
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM 
Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// Ensure that evaluateExpression bounds checks its internal operand stack and
+// aborts on malformed DWARF expressions rather than overflowing the stack.
+// REQUIRES: target={{(aarch64|x86_64)-.+}}
+// UNSUPPORTED: target={{.*-windows.*}}
+// UNSUPPORTED: target={{.*-apple.*}}
+
+// GCC doesn't support __attribute__((naked)) on AArch64.
+// UNSUPPORTED: gcc
+
+// Inline assembly is incompatible with MSAN.
+// UNSUPPORTED: msan
+
+#undef NDEBUG
+#include <assert.h>
+#include <libunwind.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+extern "C" void stepper() {
+  unw_cursor_t cursor;
+  unw_context_t uc;
+  unw_getcontext(&uc);
+  unw_init_local(&cursor, &uc);
+  // Step to bad_cfa_expression (frame 1).
+  unw_step(&cursor);
+  // Step past bad_cfa_expression (frame 2). This evaluates the CFA expression
+  // in bad_cfa_expression and triggers _LIBUNWIND_ABORT due to stack overflow.
+  unw_step(&cursor);
+}
+
+__attribute__((naked)) void bad_cfa_expression() {
+#if defined(__aarch64__)
+  __asm__(
+      "stp     x29, x30, [sp, #-16]!\n"
+      "mov     x29, sp\n"
+      // DW_CFA_def_cfa_expression (0x0f), length 4, expression: DW_OP_dup 
(0x12), DW_OP_skip (0x2f) -4 (0xfc, 0xff)
+      ".cfi_escape 0x0f, 0x04, 0x12, 0x2f, 0xfc, 0xff\n"
+      "bl      stepper\n"
+      "ldp     x29, x30, [sp], #16\n"
+      "ret\n");
+#elif defined(__x86_64__)
+  __asm__(
+      "pushq   %rbp\n"
+      "movq    %rsp, %rbp\n"
+      // DW_CFA_def_cfa_expression (0x0f), length 4, expression: DW_OP_dup 
(0x12), DW_OP_skip (0x2f) -4 (0xfc, 0xff)
+      ".cfi_escape 0x0f, 0x04, 0x12, 0x2f, 0xfc, 0xff\n"
+      "callq   stepper\n"
+      "popq    %rbp\n"
+      "ret\n");
+#else
+#error This test is only supported on aarch64 or x86-64
+#endif
+}
+
+int main(int, char **) {
+  pid_t pid = fork();
+  assert(pid >= 0);
+  if (pid == 0) {
+    bad_cfa_expression();
+    exit(0);
+  }
+
+  int status = 0;
+  waitpid(pid, &status, 0);
+  // The child process should abort due to operand stack bounds violation.
+  assert(WIFSIGNALED(status) && WTERMSIG(status) == SIGABRT);
+  return 0;
+}

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to