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
