@@ -34,6 +34,7 @@
#include "gdbarch.h"
#include "objfiles.h"
#include "extract-store-integer.h"
+#include "gdbsupport/selftest.h"
/* This holds gdbarch-specific types used by the DWARF expression
evaluator. See comments in execute_stack_op. */
@@ -1370,6 +1371,57 @@ safe_skip_leb128 (const gdb_byte *buf, const gdb_byte *buf_end)
error (_("DWARF expression error: ran off end of buffer reading leb128 value"));
return buf;
}
+
+/* Return BUF advanced by BYTES bytes. Throw an error if fewer than BYTES
+ bytes remain before BUF_END. */
+
+static const gdb_byte *
+safe_skip_bytes (const gdb_byte *buf, const gdb_byte *buf_end,
+ ULONGEST bytes)
+{
+ gdb_assert (buf <= buf_end);
+ if (bytes > (ULONGEST) (buf_end - buf))
+ error (_("DWARF expression error: ran off end of buffer reading bytes"));
+ return buf + bytes;
+}
+
+/* Read a LEN-byte unsigned integer at BUF, assign it to RESULT, and return
+ BUF advanced by LEN bytes. "Unsigned" describes how the bytes are decoded;
+ RESULT's type is chosen by the caller. Throw an error if fewer than LEN
+ bytes remain before BUF_END. */
+
+template<typename T>
+static const gdb_byte *
+safe_read_unsigned_integer (const gdb_byte *buf, const gdb_byte *buf_end,
+ int len, bfd_endian byte_order, T &result)
+{
+ gdb_assert (len >= 0);
+ gdb_assert ((size_t) len <= sizeof (T));
+
+ /* Validate the source range before extract_unsigned_integer reads it. */
+ const gdb_byte *next = safe_skip_bytes (buf, buf_end, len);
+ result = static_cast<T> (extract_unsigned_integer (buf, len, byte_order));
+ return next;
+}
+
+/* Read a LEN-byte signed integer at BUF, assign it to RESULT, and return BUF
+ advanced by LEN bytes. "Signed" describes how the bytes are decoded;
+ RESULT's type is chosen by the caller. Throw an error if fewer than LEN
+ bytes remain before BUF_END. */
+
+template<typename T>
+static const gdb_byte *
+safe_read_signed_integer (const gdb_byte *buf, const gdb_byte *buf_end,
+ int len, bfd_endian byte_order, T &result)
+{
+ gdb_assert (len >= 0);
+ gdb_assert ((size_t) len <= sizeof (T));
+
+ /* Validate the source range before extract_signed_integer reads it. */
+ const gdb_byte *next = safe_skip_bytes (buf, buf_end, len);
+ result = static_cast<T> (extract_signed_integer (buf, len, byte_order));
+ return next;
+}
/* Check that the current operator is either at the end of an
@@ -1488,6 +1540,9 @@ dwarf_block_to_dwarf_reg_deref (gdb::array_view<const gdb_byte> block,
return -1;
if (offset != 0)
return -1;
+ gdb_assert (buf <= buf_end);
+ if (buf == buf_end)
+ return -1;
if (*buf == DW_OP_deref)
{
@@ -1688,9 +1743,9 @@ dwarf_expr_context::execute_stack_op (gdb::array_view<const gdb_byte> expr)
break;
case DW_OP_addr:
- result = extract_unsigned_integer (op_ptr,
- this->m_addr_size, byte_order);
- op_ptr += this->m_addr_size;
+ op_ptr = safe_read_unsigned_integer (op_ptr, op_end,
+ this->m_addr_size,
+ byte_order, result);
/* Some versions of GCC emit DW_OP_addr before
DW_OP_GNU_push_tls_address. In this case the value is an
index, not an address. We don't support things like
@@ -1723,44 +1778,44 @@ dwarf_expr_context::execute_stack_op (gdb::array_view<const gdb_byte> expr)
break;
case DW_OP_const1u:
- result = extract_unsigned_integer (op_ptr, 1, byte_order);
+ op_ptr = safe_read_unsigned_integer (op_ptr, op_end, 1,
+ byte_order, result);
result_val = value_from_ulongest (address_type, result);
- op_ptr += 1;
break;
case DW_OP_const1s:
- result = extract_signed_integer (op_ptr, 1, byte_order);
+ op_ptr = safe_read_signed_integer (op_ptr, op_end, 1,
+ byte_order, result);
result_val = value_from_ulongest (address_type, result);
- op_ptr += 1;
break;
case DW_OP_const2u:
- result = extract_unsigned_integer (op_ptr, 2, byte_order);
+ op_ptr = safe_read_unsigned_integer (op_ptr, op_end, 2,
+ byte_order, result);
result_val = value_from_ulongest (address_type, result);
- op_ptr += 2;
break;
case DW_OP_const2s:
- result = extract_signed_integer (op_ptr, 2, byte_order);
+ op_ptr = safe_read_signed_integer (op_ptr, op_end, 2,
+ byte_order, result);
result_val = value_from_ulongest (address_type, result);
- op_ptr += 2;
break;
case DW_OP_const4u:
- result = extract_unsigned_integer (op_ptr, 4, byte_order);
+ op_ptr = safe_read_unsigned_integer (op_ptr, op_end, 4,
+ byte_order, result);
result_val = value_from_ulongest (address_type, result);
- op_ptr += 4;
break;
case DW_OP_const4s:
- result = extract_signed_integer (op_ptr, 4, byte_order);
+ op_ptr = safe_read_signed_integer (op_ptr, op_end, 4,
+ byte_order, result);
result_val = value_from_ulongest (address_type, result);
- op_ptr += 4;
break;
case DW_OP_const8u:
- result = extract_unsigned_integer (op_ptr, 8, byte_order);
+ op_ptr = safe_read_unsigned_integer (op_ptr, op_end, 8,
+ byte_order, result);
result_val = value_from_ulongest (address_type, result);
- op_ptr += 8;
break;
case DW_OP_const8s:
- result = extract_signed_integer (op_ptr, 8, byte_order);
+ op_ptr = safe_read_signed_integer (op_ptr, op_end, 8,
+ byte_order, result);
result_val = value_from_ulongest (address_type, result);
- op_ptr += 8;
break;
case DW_OP_constu:
op_ptr = safe_read_uleb128 (op_ptr, op_end, &uoffset);
@@ -1836,12 +1891,10 @@ dwarf_expr_context::execute_stack_op (gdb::array_view<const gdb_byte> expr)
uint64_t len;
op_ptr = safe_read_uleb128 (op_ptr, op_end, &len);
- if (op_ptr + len > op_end)
- error (_("DW_OP_implicit_value: too few bytes available."));
this->m_len = len;
this->m_data = op_ptr;
this->m_location = DWARF_VALUE_LITERAL;
- op_ptr += len;
+ op_ptr = safe_skip_bytes (op_ptr, op_end, len);
dwarf_expr_require_composition (op_ptr, op_end,
"DW_OP_implicit_value");
}
@@ -1861,9 +1914,9 @@ dwarf_expr_context::execute_stack_op (gdb::array_view<const gdb_byte> expr)
int ref_addr_size = this->m_per_cu->ref_addr_size ();
/* The referred-to DIE of sect_offset kind. */
- this->m_len = extract_unsigned_integer (op_ptr, ref_addr_size,
- byte_order);
- op_ptr += ref_addr_size;
+ op_ptr = safe_read_unsigned_integer (op_ptr, op_end,
+ ref_addr_size, byte_order,
+ this->m_len);
/* The byte offset into the data. */
op_ptr = safe_read_sleb128 (op_ptr, op_end, &len);
@@ -1972,7 +2025,8 @@ dwarf_expr_context::execute_stack_op (gdb::array_view<const gdb_byte> expr)
goto no_push;
case DW_OP_pick:
- offset = *op_ptr++;
+ op_ptr = safe_read_unsigned_integer (op_ptr, op_end, 1,
+ byte_order, offset);
result_val = fetch (offset);
in_stack_memory = fetch_in_stack_memory (offset);
break;
@@ -2016,7 +2070,16 @@ dwarf_expr_context::execute_stack_op (gdb::array_view<const gdb_byte> expr)
case DW_OP_deref_type:
case DW_OP_GNU_deref_type:
{
- int addr_size = (op == DW_OP_deref ? this->m_addr_size : *op_ptr++);
+ int addr_size;
+
+ if (op == DW_OP_deref)
+ addr_size = this->m_addr_size;
+ else
+ {
+ op_ptr = safe_read_unsigned_integer (op_ptr, op_end, 1,
+ byte_order, addr_size);
+ }
+
CORE_ADDR addr = fetch_address (0);
struct type *type;
@@ -2249,8 +2312,8 @@ dwarf_expr_context::execute_stack_op (gdb::array_view<const gdb_byte> expr)
break;
case DW_OP_skip:
- offset = extract_signed_integer (op_ptr, 2, byte_order);
- op_ptr += 2;
+ op_ptr = safe_read_signed_integer (op_ptr, op_end, 2, byte_order,
+ offset);
op_ptr += offset;
goto no_push;
@@ -2258,8 +2321,8 @@ dwarf_expr_context::execute_stack_op (gdb::array_view<const gdb_byte> expr)
{
struct value *val;
- offset = extract_signed_integer (op_ptr, 2, byte_order);
- op_ptr += 2;
+ op_ptr = safe_read_signed_integer (op_ptr, op_end, 2, byte_order,
+ offset);
val = fetch (0);
dwarf_require_integral (val->type ());
if (value_as_long (val) != 0)
@@ -2313,18 +2376,18 @@ dwarf_expr_context::execute_stack_op (gdb::array_view<const gdb_byte> expr)
case DW_OP_call2:
{
- cu_offset cu_off
- = (cu_offset) extract_unsigned_integer (op_ptr, 2, byte_order);
- op_ptr += 2;
+ cu_offset cu_off;
+ op_ptr = safe_read_unsigned_integer (op_ptr, op_end, 2,
+ byte_order, cu_off);
this->dwarf_call (cu_off);
}
goto no_push;
case DW_OP_call4:
{
- cu_offset cu_off
- = (cu_offset) extract_unsigned_integer (op_ptr, 4, byte_order);
- op_ptr += 4;
+ cu_offset cu_off;
+ op_ptr = safe_read_unsigned_integer (op_ptr, op_end, 4,
+ byte_order, cu_off);
this->dwarf_call (cu_off);
}
goto no_push;
@@ -2334,11 +2397,10 @@ dwarf_expr_context::execute_stack_op (gdb::array_view<const gdb_byte> expr)
ensure_have_per_cu (this->m_per_cu, "DW_OP_GNU_variable_value");
int ref_addr_size = this->m_per_cu->ref_addr_size ();
- sect_offset sect_off
- = (sect_offset) extract_unsigned_integer (op_ptr,
- ref_addr_size,
- byte_order);
- op_ptr += ref_addr_size;
+ sect_offset sect_off;
+ op_ptr = safe_read_unsigned_integer (op_ptr, op_end,
+ ref_addr_size, byte_order,
+ sect_off);
result_val = sect_variable_value (sect_off, this->m_per_cu,
this->m_per_objfile);
result_val = value_cast (address_type, result_val);
@@ -2353,15 +2415,13 @@ dwarf_expr_context::execute_stack_op (gdb::array_view<const gdb_byte> expr)
union call_site_parameter_u kind_u;
op_ptr = safe_read_uleb128 (op_ptr, op_end, &len);
- if (op_ptr + len > op_end)
- error (_("DW_OP_entry_value: too few bytes available."));
+ const gdb_byte *expr_ptr = op_ptr;
+ op_ptr = safe_skip_bytes (op_ptr, op_end, len);
- auto entry_value_expr = gdb::make_array_view (op_ptr, len);
+ auto entry_value_expr = gdb::make_array_view (expr_ptr, len);
kind_u.dwarf_reg = dwarf_block_to_dwarf_reg (entry_value_expr);
if (kind_u.dwarf_reg != -1)
{
- op_ptr += len;
-
if (trivial_entry_value (this->m_frame))
{
/* We can assume that DW_OP_entry_value (expr) == expr.
@@ -2384,7 +2444,6 @@ dwarf_expr_context::execute_stack_op (gdb::array_view<const gdb_byte> expr)
{
if (deref_size == -1)
deref_size = this->m_addr_size;
- op_ptr += len;
if (trivial_entry_value (this->m_frame))
{
@@ -2410,9 +2469,9 @@ dwarf_expr_context::execute_stack_op (gdb::array_view<const gdb_byte> expr)
{
union call_site_parameter_u kind_u;
- kind_u.param_cu_off
- = (cu_offset) extract_unsigned_integer (op_ptr, 4, byte_order);
- op_ptr += 4;
+ op_ptr = safe_read_unsigned_integer (op_ptr, op_end, 4,
+ byte_order,
+ kind_u.param_cu_off);
this->push_dwarf_reg_entry_value (CALL_SITE_PARAMETER_PARAM_OFFSET,
kind_u,
-1 /* deref_size */);
@@ -2429,9 +2488,10 @@ dwarf_expr_context::execute_stack_op (gdb::array_view<const gdb_byte> expr)
op_ptr = safe_read_uleb128 (op_ptr, op_end, &uoffset);
cu_offset type_die_cu_off = (cu_offset) uoffset;
- n = *op_ptr++;
+ op_ptr = safe_read_unsigned_integer (op_ptr, op_end, 1,
+ byte_order, n);
data = op_ptr;
- op_ptr += n;
+ op_ptr = safe_skip_bytes (op_ptr, op_end, n);
type = get_base_type (type_die_cu_off);
@@ -2524,3 +2584,61 @@ dwarf_expr_context::execute_stack_op (gdb::array_view<const gdb_byte> expr)
this->m_recursion_depth--;
gdb_assert (this->m_recursion_depth >= 0);
}
+
+#if GDB_SELF_TEST
+namespace selftests {
+
+static void
+test_dwarf_block_to_dwarf_reg ()
+{
+ const gdb_byte reg0[] = { DW_OP_reg0 };
+ SELF_CHECK (dwarf_block_to_dwarf_reg (reg0) == 0);
+
+ const gdb_byte regx[] = { DW_OP_regx, 32 };
+ SELF_CHECK (dwarf_block_to_dwarf_reg (regx) == 32);
+
+ const gdb_byte trailing_op[] = { DW_OP_reg0, DW_OP_stack_value };
+ SELF_CHECK (dwarf_block_to_dwarf_reg (trailing_op) == -1);
+}
+
+static void
+test_dwarf_block_to_dwarf_reg_deref ()
+{
+ CORE_ADDR deref_size;
+
+ const gdb_byte deref[] = { DW_OP_breg0, 0, DW_OP_deref };
+ SELF_CHECK (dwarf_block_to_dwarf_reg_deref (deref, &deref_size) == 0);
+ SELF_CHECK (deref_size == (CORE_ADDR) -1);
+
+ const gdb_byte deref_size_4[]
+ = { DW_OP_bregx, 32, 0, DW_OP_deref_size, 4 };
+ SELF_CHECK (dwarf_block_to_dwarf_reg_deref (deref_size_4, &deref_size)
+ == 32);
+ SELF_CHECK (deref_size == 4);
+
+ const gdb_byte missing_deref[] = { DW_OP_breg0, 0 };
+ SELF_CHECK (dwarf_block_to_dwarf_reg_deref (missing_deref, &deref_size)
+ == -1);
+
+ const gdb_byte missing_size[]
+ = { DW_OP_breg0, 0, DW_OP_deref_size };
+ SELF_CHECK (dwarf_block_to_dwarf_reg_deref (missing_size, &deref_size)
+ == -1);
+
+ const gdb_byte nonzero_offset[] = { DW_OP_breg0, 1, DW_OP_deref };
+ SELF_CHECK (dwarf_block_to_dwarf_reg_deref (nonzero_offset, &deref_size)
+ == -1);
+}
+
+} /* namespace selftests. */
+#endif /* GDB_SELF_TEST. */
+
+INIT_GDB_FILE (dwarf2expr)
+{
+#if GDB_SELF_TEST
+ selftests::register_test ("dwarf_block_to_dwarf_reg",
+ selftests::test_dwarf_block_to_dwarf_reg);
+ selftests::register_test ("dwarf_block_to_dwarf_reg_deref",
+ selftests::test_dwarf_block_to_dwarf_reg_deref);
+#endif /* GDB_SELF_TEST. */
+}
new file mode 100644
@@ -0,0 +1,174 @@
+# Copyright 2026 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# Test malformed DWARF expressions whose opcodes have truncated operands.
+
+load_lib dwarf.exp
+
+require dwarf2_support
+
+standard_testfile main.c -dw.S
+
+set asm_file [standard_output_file $srcfile2]
+
+Dwarf::assemble $asm_file {
+ global srcfile
+
+ declare_labels int_label
+
+ cu {label cu_label version 5} {
+ DW_TAG_compile_unit {
+ DW_AT_name $srcfile
+ DW_AT_language @DW_LANG_C
+ } {
+ int_label: DW_TAG_base_type {
+ DW_AT_name "int"
+ DW_AT_encoding @DW_ATE_signed
+ DW_AT_byte_size 4 DW_FORM_sdata
+ }
+
+ foreach {var op} {
+ truncated_addr DW_OP_addr
+ truncated_const1u DW_OP_const1u
+ truncated_const1s DW_OP_const1s
+ truncated_const2u DW_OP_const2u
+ truncated_const2s DW_OP_const2s
+ truncated_const4u DW_OP_const4u
+ truncated_const4s DW_OP_const4s
+ truncated_const8u DW_OP_const8u
+ truncated_const8s DW_OP_const8s
+ truncated_implicit_pointer DW_OP_implicit_pointer
+ truncated_gnu_implicit_pointer DW_OP_GNU_implicit_pointer
+ truncated_pick DW_OP_pick
+ truncated_deref_size DW_OP_deref_size
+ truncated_deref_type DW_OP_deref_type
+ truncated_gnu_deref_type DW_OP_GNU_deref_type
+ truncated_skip DW_OP_skip
+ truncated_bra DW_OP_bra
+ truncated_parameter_ref DW_OP_GNU_parameter_ref
+ } {
+ DW_TAG_variable {
+ DW_AT_name $var
+ DW_AT_type :$int_label
+ DW_AT_location {
+ variable _constants
+
+ _op .byte $_constants($op) $op
+ } SPECIAL_expr
+ }
+ }
+
+ DW_TAG_variable {
+ DW_AT_name "truncated_implicit_value"
+ DW_AT_type :$int_label
+ DW_AT_location {
+ variable _constants
+
+ _op .byte $_constants(DW_OP_implicit_value) \
+ DW_OP_implicit_value
+ _op .uleb128 4 "implicit value length"
+ _op .2byte 0 "truncated implicit value bytes"
+ } SPECIAL_expr
+ }
+
+ foreach op {DW_OP_entry_value DW_OP_GNU_entry_value} {
+ set var "truncated_[string tolower $op]"
+ DW_TAG_variable {
+ DW_AT_name $var
+ DW_AT_type :$int_label
+ DW_AT_location {
+ variable _constants
+
+ _op .byte $_constants($op) $op
+ _op .uleb128 4 "entry value expression length"
+ _op .2byte 0 "truncated entry value expression"
+ } SPECIAL_expr
+ }
+ }
+
+ foreach op {DW_OP_const_type DW_OP_GNU_const_type} {
+ set op_name [string tolower $op]
+
+ DW_TAG_variable {
+ DW_AT_name "truncated_${op_name}_size"
+ DW_AT_type :$int_label
+ DW_AT_location {
+ variable _constants
+ variable _cu_label
+
+ _op .byte $_constants($op) $op
+ _op .uleb128 "$int_label - $_cu_label" \
+ "type DIE offset"
+ } SPECIAL_expr
+ }
+
+ DW_TAG_variable {
+ DW_AT_name "truncated_${op_name}_payload"
+ DW_AT_type :$int_label
+ DW_AT_location {
+ variable _constants
+ variable _cu_label
+
+ _op .byte $_constants($op) $op
+ _op .uleb128 "$int_label - $_cu_label" \
+ "type DIE offset"
+ _op .byte 4 "constant block length"
+ _op .2byte 0 "truncated constant block"
+ } SPECIAL_expr
+ }
+ }
+ }
+ }
+}
+
+if {[prepare_for_testing "failed to prepare" ${testfile} \
+ [list $srcfile $asm_file] nodebug]} {
+ return
+}
+
+if {![runto_main]} {
+ return
+}
+
+foreach_with_prefix var {
+ truncated_addr
+ truncated_const1u
+ truncated_const1s
+ truncated_const2u
+ truncated_const2s
+ truncated_const4u
+ truncated_const4s
+ truncated_const8u
+ truncated_const8s
+ truncated_implicit_value
+ truncated_implicit_pointer
+ truncated_gnu_implicit_pointer
+ truncated_pick
+ truncated_deref_size
+ truncated_deref_type
+ truncated_gnu_deref_type
+ truncated_skip
+ truncated_bra
+ truncated_dw_op_entry_value
+ truncated_dw_op_gnu_entry_value
+ truncated_parameter_ref
+ truncated_dw_op_const_type_size
+ truncated_dw_op_const_type_payload
+ truncated_dw_op_gnu_const_type_size
+ truncated_dw_op_gnu_const_type_payload
+} {
+ gdb_test "print $var" \
+ "DWARF expression error: ran off end of buffer reading bytes"
+}