Mirror of the gdb-patches mailing list
 help / color / mirror / Atom feed
* [PATCH][PR gdb/34239][PR gdb/34299] gdb: Check bounds before reading DWARF expression operands
@ 2026-06-18 18:01 Jielun Wu
  2026-06-23 18:56 ` Guinevere Larsen
                   ` (2 more replies)
  0 siblings, 3 replies; 8+ messages in thread
From: Jielun Wu @ 2026-06-18 18:01 UTC (permalink / raw)
  To: gdb-patches; +Cc: Jielun Wu

Some DWARF expression opcodes read fixed-width operands or block payloads
from the expression buffer before checking that the bytes are available.
With a malformed expression, this can read past the end of the expression
buffer.

Add helpers that validate fixed-width integer reads and block payload
skips, and use them in dwarf_expr_context::execute_stack_op.  Also guard
the nested DW_OP_entry_value parser before reading the following opcode.

Add a DWARF assembler test that exercises truncated operands for these
opcodes.

Tested on x86_64-linux.

Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=34239
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=34299
---
 gdb/dwarf2/expr.c                             | 166 +++++++++------
 .../gdb.dwarf2/dw2-bad-dwarf-expr-bounds.exp  | 190 ++++++++++++++++++
 2 files changed, 300 insertions(+), 56 deletions(-)
 create mode 100644 gdb/testsuite/gdb.dwarf2/dw2-bad-dwarf-expr-bounds.exp

diff --git a/gdb/dwarf2/expr.c b/gdb/dwarf2/expr.c
index c71d4725b03..ead918388a4 100644
--- a/gdb/dwarf2/expr.c
+++ b/gdb/dwarf2/expr.c
@@ -1370,7 +1370,45 @@ 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;
 }
-\f
+
+/* Helper to skip BYTES bytes or throw an error.  */
+
+static const gdb_byte *
+safe_skip_bytes (const gdb_byte *buf, const gdb_byte *buf_end,
+		 ULONGEST bytes)
+{
+  if (buf > buf_end || bytes > (ULONGEST) (buf_end - buf))
+    error (_("DWARF expression error: ran off end of buffer reading bytes"));
+  return buf + bytes;
+}
+
+/* Helper to read a fixed-width unsigned integer or throw an error.  */
+
+static const gdb_byte *
+safe_read_unsigned_integer (const gdb_byte *buf, const gdb_byte *buf_end,
+			    int len, bfd_endian byte_order, uint64_t *r)
+{
+  gdb_assert (len >= 0);
+
+  const gdb_byte *data = buf;
+  buf = safe_skip_bytes (buf, buf_end, len);
+  *r = extract_unsigned_integer (data, len, byte_order);
+  return buf;
+}
+
+/* Helper to read a fixed-width signed integer or throw an error.  */
+
+static const gdb_byte *
+safe_read_signed_integer (const gdb_byte *buf, const gdb_byte *buf_end,
+			  int len, bfd_endian byte_order, int64_t *r)
+{
+  gdb_assert (len >= 0);
+
+  const gdb_byte *data = buf;
+  buf = safe_skip_bytes (buf, buf_end, len);
+  *r = extract_signed_integer (data, len, byte_order);
+  return buf;
+}
 
 /* Check that the current operator is either at the end of an
    expression, or that it is followed by a composition operator or by
@@ -1478,7 +1516,7 @@ dwarf_block_to_dwarf_reg_deref (gdb::array_view<const gdb_byte> block,
       if (buf == NULL)
 	return -1;
       if ((int) dwarf_reg != dwarf_reg)
-       return -1;
+	return -1;
     }
   else
     return -1;
@@ -1488,6 +1526,8 @@ dwarf_block_to_dwarf_reg_deref (gdb::array_view<const gdb_byte> block,
     return -1;
   if (offset != 0)
     return -1;
+  if (buf >= buf_end)
+    return -1;
 
   if (*buf == DW_OP_deref)
     {
@@ -1498,7 +1538,7 @@ dwarf_block_to_dwarf_reg_deref (gdb::array_view<const gdb_byte> block,
     {
       buf++;
       if (buf >= buf_end)
-       return -1;
+	return -1;
       *deref_size_return = *buf++;
     }
   else
@@ -1688,9 +1728,10 @@ 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, &uoffset);
+	  result = uoffset;
 	  /* 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 +1764,52 @@ 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, &uoffset);
+	  result = uoffset;
 	  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, &offset);
+	  result = offset;
 	  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, &uoffset);
+	  result = uoffset;
 	  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, &offset);
+	  result = offset;
 	  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, &uoffset);
+	  result = uoffset;
 	  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, &offset);
+	  result = offset;
 	  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, &uoffset);
+	  result = uoffset;
 	  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, &offset);
+	  result = offset;
 	  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 +1885,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 +1908,10 @@ 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,
+						 &uoffset);
+	    this->m_len = uoffset;
 
 	    /* The byte offset into the data.  */
 	    op_ptr = safe_read_sleb128 (op_ptr, op_end, &len);
@@ -1972,7 +2020,9 @@ 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, &uoffset);
+	  offset = uoffset;
 	  result_val = fetch (offset);
 	  in_stack_memory = fetch_in_stack_memory (offset);
 	  break;
@@ -2016,7 +2066,13 @@ 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 = this->m_addr_size;
+	    if (op != DW_OP_deref)
+	      {
+		op_ptr = safe_read_unsigned_integer (op_ptr, op_end, 1,
+						     byte_order, &uoffset);
+		addr_size = uoffset;
+	      }
 	    CORE_ADDR addr = fetch_address (0);
 	    struct type *type;
 
@@ -2249,8 +2305,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 +2314,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 +2369,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;
+	    op_ptr = safe_read_unsigned_integer (op_ptr, op_end, 2,
+						 byte_order, &uoffset);
+	    cu_offset cu_off = (cu_offset) uoffset;
 	    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;
+	    op_ptr = safe_read_unsigned_integer (op_ptr, op_end, 4,
+						 byte_order, &uoffset);
+	    cu_offset cu_off = (cu_offset) uoffset;
 	    this->dwarf_call (cu_off);
 	  }
 	  goto no_push;
@@ -2334,11 +2390,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;
+	    op_ptr = safe_read_unsigned_integer (op_ptr, op_end,
+						 ref_addr_size, byte_order,
+						 &uoffset);
+	    sect_offset sect_off = (sect_offset) uoffset;
 	    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 +2408,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 +2437,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 +2462,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, &uoffset);
+	    kind_u.param_cu_off = (cu_offset) uoffset;
 	    this->push_dwarf_reg_entry_value (CALL_SITE_PARAMETER_PARAM_OFFSET,
 					      kind_u,
 					      -1 /* deref_size */);
@@ -2429,9 +2481,11 @@ 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,
+						 &uoffset);
+	    n = uoffset;
 	    data = op_ptr;
-	    op_ptr += n;
+	    op_ptr = safe_skip_bytes (op_ptr, op_end, n);
 
 	    type = get_base_type (type_die_cu_off);
 
diff --git a/gdb/testsuite/gdb.dwarf2/dw2-bad-dwarf-expr-bounds.exp b/gdb/testsuite/gdb.dwarf2/dw2-bad-dwarf-expr-bounds.exp
new file mode 100644
index 00000000000..7f5fa22388d
--- /dev/null
+++ b/gdb/testsuite/gdb.dwarf2/dw2-bad-dwarf-expr-bounds.exp
@@ -0,0 +1,190 @@
+# 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} {
+	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 {
+			_op .byte $Dwarf::_constants($op) $op
+		    } SPECIAL_expr
+		}
+	    }
+
+	    DW_TAG_variable {
+		DW_AT_name "truncated_implicit_value"
+		DW_AT_type :$int_label
+		DW_AT_location {
+		    _op .byte $Dwarf::_constants(DW_OP_implicit_value) \
+			DW_OP_implicit_value
+		    _op .uleb128 4 "implicit value length"
+		    _op .2byte 0 "truncated implicit value bytes"
+		} SPECIAL_expr
+	    }
+
+	    DW_TAG_variable {
+		DW_AT_name "truncated_entry_value"
+		DW_AT_type :$int_label
+		DW_AT_location {
+		    _op .byte $Dwarf::_constants(DW_OP_entry_value) \
+			DW_OP_entry_value
+		    _op .uleb128 4 "entry value expression length"
+		    _op .2byte 0 "truncated entry value expression"
+		} SPECIAL_expr
+	    }
+
+	    DW_TAG_variable {
+		DW_AT_name "truncated_gnu_entry_value"
+		DW_AT_type :$int_label
+		DW_AT_location {
+		    _op .byte $Dwarf::_constants(DW_OP_GNU_entry_value) \
+			DW_OP_GNU_entry_value
+		    _op .uleb128 4 "entry value expression length"
+		    _op .2byte 0 "truncated entry value expression"
+		} SPECIAL_expr
+	    }
+
+	    DW_TAG_variable {
+		DW_AT_name "truncated_const_type_size"
+		DW_AT_type :$int_label
+		DW_AT_location {
+		    _op .byte $Dwarf::_constants(DW_OP_const_type) \
+			DW_OP_const_type
+		    _op .uleb128 "$int_label - $cu_label" "type DIE offset"
+		} SPECIAL_expr
+	    }
+
+	    DW_TAG_variable {
+		DW_AT_name "truncated_const_type_payload"
+		DW_AT_type :$int_label
+		DW_AT_location {
+		    _op .byte $Dwarf::_constants(DW_OP_const_type) \
+			DW_OP_const_type
+		    _op .uleb128 "$int_label - $cu_label" "type DIE offset"
+		    _op .byte 4 "constant block length"
+		    _op .2byte 0 "truncated constant block"
+		} SPECIAL_expr
+	    }
+
+	    DW_TAG_variable {
+		DW_AT_name "truncated_gnu_const_type_size"
+		DW_AT_type :$int_label
+		DW_AT_location {
+		    _op .byte $Dwarf::_constants(DW_OP_GNU_const_type) \
+			DW_OP_GNU_const_type
+		    _op .uleb128 "$int_label - $cu_label" "type DIE offset"
+		} SPECIAL_expr
+	    }
+
+	    DW_TAG_variable {
+		DW_AT_name "truncated_gnu_const_type_payload"
+		DW_AT_type :$int_label
+		DW_AT_location {
+		    _op .byte $Dwarf::_constants(DW_OP_GNU_const_type) \
+			DW_OP_GNU_const_type
+		    _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 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_entry_value
+    truncated_gnu_entry_value
+    truncated_parameter_ref
+    truncated_const_type_size
+    truncated_const_type_payload
+    truncated_gnu_const_type_size
+    truncated_gnu_const_type_payload
+} {
+    with_test_prefix $var {
+	gdb_test "print $var" \
+	    ".*DWARF expression error: ran off end of buffer reading .*"
+    }
+}
-- 
2.34.1


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH][PR gdb/34239][PR gdb/34299] gdb: Check bounds before reading DWARF expression operands
  2026-06-18 18:01 [PATCH][PR gdb/34239][PR gdb/34299] gdb: Check bounds before reading DWARF expression operands Jielun Wu
@ 2026-06-23 18:56 ` Guinevere Larsen
  2026-06-26  8:31   ` Firmiana
  2026-06-25 15:23 ` Simon Marchi
  2026-06-25 16:36 ` Simon Marchi
  2 siblings, 1 reply; 8+ messages in thread
From: Guinevere Larsen @ 2026-06-23 18:56 UTC (permalink / raw)
  To: Jielun Wu, gdb-patches

On 6/18/26 3:01 PM, Jielun Wu wrote:
> Some DWARF expression opcodes read fixed-width operands or block payloads
> from the expression buffer before checking that the bytes are available.
> With a malformed expression, this can read past the end of the expression
> buffer.
>
> Add helpers that validate fixed-width integer reads and block payload
> skips, and use them in dwarf_expr_context::execute_stack_op.  Also guard
> the nested DW_OP_entry_value parser before reading the following opcode.
>
> Add a DWARF assembler test that exercises truncated operands for these
> opcodes.
>
> Tested on x86_64-linux.
>
> Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=34239
> Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=34299
> ---

Hi! Thanks for working on this!

I'm not too familiar with the dwarf parser, so I can only do a 
superficial review. Most of my comments are questions and nitpicks, I 
don't think you necessarily need to send a new version just from this 
review.

I have tested this and see no regressions, so feel free to add my test 
tag to the git trailers

Tested-By: Guinevere Larsen <guinevere@redhat.com>

>   gdb/dwarf2/expr.c                             | 166 +++++++++------
>   .../gdb.dwarf2/dw2-bad-dwarf-expr-bounds.exp  | 190 ++++++++++++++++++
>   2 files changed, 300 insertions(+), 56 deletions(-)
>   create mode 100644 gdb/testsuite/gdb.dwarf2/dw2-bad-dwarf-expr-bounds.exp
>
> diff --git a/gdb/dwarf2/expr.c b/gdb/dwarf2/expr.c
> index c71d4725b03..ead918388a4 100644
> --- a/gdb/dwarf2/expr.c
> +++ b/gdb/dwarf2/expr.c
> @@ -1370,7 +1370,45 @@ 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;
>   }
> -\f
> +
> +/* Helper to skip BYTES bytes or throw an error.  */
> +
> +static const gdb_byte *
> +safe_skip_bytes (const gdb_byte *buf, const gdb_byte *buf_end,
> +		 ULONGEST bytes)
> +{
> +  if (buf > buf_end || bytes > (ULONGEST) (buf_end - buf))
> +    error (_("DWARF expression error: ran off end of buffer reading bytes"));
> +  return buf + bytes;
> +}
> +
> +/* Helper to read a fixed-width unsigned integer or throw an error.  */
> +
> +static const gdb_byte *
> +safe_read_unsigned_integer (const gdb_byte *buf, const gdb_byte *buf_end,
> +			    int len, bfd_endian byte_order, uint64_t *r)
> +{
> +  gdb_assert (len >= 0);
> +
> +  const gdb_byte *data = buf;
> +  buf = safe_skip_bytes (buf, buf_end, len);
> +  *r = extract_unsigned_integer (data, len, byte_order);
> +  return buf;

Oh, ok, it took me a while of thinking but I think I now understand why 
you wrote it like this.

You advance the buffer first to use that as a check that it is possible 
to read that many bytes from the buffer, instead of needing to double up 
on the if condition, right?

If so, I think it would be nice to have a comment explaining it, because 
I went through 2 incorrect explanations before reaching that conclusion.

> +}
> +
> +/* Helper to read a fixed-width signed integer or throw an error.  */
> +
> +static const gdb_byte *
> +safe_read_signed_integer (const gdb_byte *buf, const gdb_byte *buf_end,
> +			  int len, bfd_endian byte_order, int64_t *r)
> +{
> +  gdb_assert (len >= 0);
> +
> +  const gdb_byte *data = buf;
> +  buf = safe_skip_bytes (buf, buf_end, len);
> +  *r = extract_signed_integer (data, len, byte_order);
> +  return buf;
Similar comment as above
> +}
>   
>   /* Check that the current operator is either at the end of an
>      expression, or that it is followed by a composition operator or by
> @@ -1478,7 +1516,7 @@ dwarf_block_to_dwarf_reg_deref (gdb::array_view<const gdb_byte> block,
>         if (buf == NULL)
>   	return -1;
>         if ((int) dwarf_reg != dwarf_reg)
> -       return -1;
> +	return -1;
>       }
>     else
>       return -1;
> @@ -1488,6 +1526,8 @@ dwarf_block_to_dwarf_reg_deref (gdb::array_view<const gdb_byte> block,
>       return -1;
>     if (offset != 0)
>       return -1;
> +  if (buf >= buf_end)
> +    return -1;
>   
>     if (*buf == DW_OP_deref)
>       {
> @@ -1498,7 +1538,7 @@ dwarf_block_to_dwarf_reg_deref (gdb::array_view<const gdb_byte> block,
>       {
>         buf++;
>         if (buf >= buf_end)
> -       return -1;
> +	return -1;
>         *deref_size_return = *buf++;
>       }
>     else
> @@ -1688,9 +1728,10 @@ 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, &uoffset);

Why not just send "&result" as the parameter here, instead of &uoffset

Same question for all the occurrences below

> +	  result = uoffset;
>   	  /* 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 +1764,52 @@ 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, &uoffset);
> +	  result = uoffset;
>   	  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, &offset);
> +	  result = offset;
>   	  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, &uoffset);
> +	  result = uoffset;
>   	  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, &offset);
> +	  result = offset;
>   	  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, &uoffset);
> +	  result = uoffset;
>   	  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, &offset);
> +	  result = offset;
>   	  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, &uoffset);
> +	  result = uoffset;
>   	  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, &offset);
> +	  result = offset;
>   	  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 +1885,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 +1908,10 @@ 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,
> +						 &uoffset);
> +	    this->m_len = uoffset;
Again, why not send "&this->m_len" ?
>   
>   	    /* The byte offset into the data.  */
>   	    op_ptr = safe_read_sleb128 (op_ptr, op_end, &len);
> @@ -1972,7 +2020,9 @@ 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, &uoffset);
> +	  offset = uoffset;
>   	  result_val = fetch (offset);
>   	  in_stack_memory = fetch_in_stack_memory (offset);
>   	  break;
> @@ -2016,7 +2066,13 @@ 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 = this->m_addr_size;
> +	    if (op != DW_OP_deref)
> +	      {
> +		op_ptr = safe_read_unsigned_integer (op_ptr, op_end, 1,
> +						     byte_order, &uoffset);
> +		addr_size = uoffset;

Maybe I'm missing something, but this seems like an incorrect change?

It seems like you should call safe_skip_bytes instead of 
read_unsigned_integer, and assign addr_size to *op_ptr?

> +	      }
>   	    CORE_ADDR addr = fetch_address (0);
>   	    struct type *type;
>   
> @@ -2249,8 +2305,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;
This should also use safe_skip_bytes
>   	  goto no_push;
>   
> @@ -2258,8 +2314,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 +2369,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;
> +	    op_ptr = safe_read_unsigned_integer (op_ptr, op_end, 2,
> +						 byte_order, &uoffset);
> +	    cu_offset cu_off = (cu_offset) uoffset;
>   	    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;
> +	    op_ptr = safe_read_unsigned_integer (op_ptr, op_end, 4,
> +						 byte_order, &uoffset);
> +	    cu_offset cu_off = (cu_offset) uoffset;
>   	    this->dwarf_call (cu_off);
>   	  }
>   	  goto no_push;
> @@ -2334,11 +2390,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;
> +	    op_ptr = safe_read_unsigned_integer (op_ptr, op_end,
> +						 ref_addr_size, byte_order,
> +						 &uoffset);
> +	    sect_offset sect_off = (sect_offset) uoffset;
>   	    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 +2408,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 +2437,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 +2462,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, &uoffset);
> +	    kind_u.param_cu_off = (cu_offset) uoffset;
>   	    this->push_dwarf_reg_entry_value (CALL_SITE_PARAMETER_PARAM_OFFSET,
>   					      kind_u,
>   					      -1 /* deref_size */);
> @@ -2429,9 +2481,11 @@ 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,
> +						 &uoffset);
> +	    n = uoffset;
>   	    data = op_ptr;
> -	    op_ptr += n;
> +	    op_ptr = safe_skip_bytes (op_ptr, op_end, n);
>   
>   	    type = get_base_type (type_die_cu_off);
>   
> diff --git a/gdb/testsuite/gdb.dwarf2/dw2-bad-dwarf-expr-bounds.exp b/gdb/testsuite/gdb.dwarf2/dw2-bad-dwarf-expr-bounds.exp
> new file mode 100644
> index 00000000000..7f5fa22388d
> --- /dev/null
> +++ b/gdb/testsuite/gdb.dwarf2/dw2-bad-dwarf-expr-bounds.exp
> @@ -0,0 +1,190 @@
> +# 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} {
> +	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 {
> +			_op .byte $Dwarf::_constants($op) $op
> +		    } SPECIAL_expr
> +		}
> +	    }
> +
> +	    DW_TAG_variable {
> +		DW_AT_name "truncated_implicit_value"
> +		DW_AT_type :$int_label
> +		DW_AT_location {
> +		    _op .byte $Dwarf::_constants(DW_OP_implicit_value) \
> +			DW_OP_implicit_value
> +		    _op .uleb128 4 "implicit value length"
> +		    _op .2byte 0 "truncated implicit value bytes"
> +		} SPECIAL_expr
> +	    }
> +
> +	    DW_TAG_variable {
> +		DW_AT_name "truncated_entry_value"
> +		DW_AT_type :$int_label
> +		DW_AT_location {
> +		    _op .byte $Dwarf::_constants(DW_OP_entry_value) \
> +			DW_OP_entry_value
> +		    _op .uleb128 4 "entry value expression length"
> +		    _op .2byte 0 "truncated entry value expression"
> +		} SPECIAL_expr
> +	    }
> +
> +	    DW_TAG_variable {
> +		DW_AT_name "truncated_gnu_entry_value"
> +		DW_AT_type :$int_label
> +		DW_AT_location {
> +		    _op .byte $Dwarf::_constants(DW_OP_GNU_entry_value) \
> +			DW_OP_GNU_entry_value
> +		    _op .uleb128 4 "entry value expression length"
> +		    _op .2byte 0 "truncated entry value expression"
> +		} SPECIAL_expr
> +	    }
> +
> +	    DW_TAG_variable {
> +		DW_AT_name "truncated_const_type_size"
> +		DW_AT_type :$int_label
> +		DW_AT_location {
> +		    _op .byte $Dwarf::_constants(DW_OP_const_type) \
> +			DW_OP_const_type
> +		    _op .uleb128 "$int_label - $cu_label" "type DIE offset"
> +		} SPECIAL_expr
> +	    }
> +
> +	    DW_TAG_variable {
> +		DW_AT_name "truncated_const_type_payload"
> +		DW_AT_type :$int_label
> +		DW_AT_location {
> +		    _op .byte $Dwarf::_constants(DW_OP_const_type) \
> +			DW_OP_const_type
> +		    _op .uleb128 "$int_label - $cu_label" "type DIE offset"
> +		    _op .byte 4 "constant block length"
> +		    _op .2byte 0 "truncated constant block"
> +		} SPECIAL_expr
> +	    }
> +
> +	    DW_TAG_variable {
> +		DW_AT_name "truncated_gnu_const_type_size"
> +		DW_AT_type :$int_label
> +		DW_AT_location {
> +		    _op .byte $Dwarf::_constants(DW_OP_GNU_const_type) \
> +			DW_OP_GNU_const_type
> +		    _op .uleb128 "$int_label - $cu_label" "type DIE offset"
> +		} SPECIAL_expr
> +	    }
> +
> +	    DW_TAG_variable {
> +		DW_AT_name "truncated_gnu_const_type_payload"
> +		DW_AT_type :$int_label
> +		DW_AT_location {
> +		    _op .byte $Dwarf::_constants(DW_OP_GNU_const_type) \
> +			DW_OP_GNU_const_type
> +		    _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 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_entry_value
> +    truncated_gnu_entry_value
> +    truncated_parameter_ref
> +    truncated_const_type_size
> +    truncated_const_type_payload
> +    truncated_gnu_const_type_size
> +    truncated_gnu_const_type_payload
> +} {
> +    with_test_prefix $var {
There exists a "foreach_with_prefix", that does basically what you did 
here.
> +	gdb_test "print $var" \
> +	    ".*DWARF expression error: ran off end of buffer reading .*"
> +    }
> +}


-- 
Cheers,
Guinevere Larsen
it/its
she/her (deprecated)


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH][PR gdb/34239][PR gdb/34299] gdb: Check bounds before reading DWARF expression operands
  2026-06-18 18:01 [PATCH][PR gdb/34239][PR gdb/34299] gdb: Check bounds before reading DWARF expression operands Jielun Wu
  2026-06-23 18:56 ` Guinevere Larsen
@ 2026-06-25 15:23 ` Simon Marchi
  2026-06-25 16:36 ` Simon Marchi
  2 siblings, 0 replies; 8+ messages in thread
From: Simon Marchi @ 2026-06-25 15:23 UTC (permalink / raw)
  To: Jielun Wu, gdb-patches



On 2026-06-18 14:01, Jielun Wu wrote:
> Some DWARF expression opcodes read fixed-width operands or block payloads
> from the expression buffer before checking that the bytes are available.
> With a malformed expression, this can read past the end of the expression
> buffer.
> 
> Add helpers that validate fixed-width integer reads and block payload
> skips, and use them in dwarf_expr_context::execute_stack_op.  Also guard
> the nested DW_OP_entry_value parser before reading the following opcode.
> 
> Add a DWARF assembler test that exercises truncated operands for these
> opcodes.
> 
> Tested on x86_64-linux.
> 
> Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=34239
> Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=34299

I'll look at this patch, but before anything else, do you have a
copyright assignment on file with the FSF?  This is required in order to
submit a non-trivial amount of code to GDB.  See here for details:

https://sourceware.org/gdb/wiki/ContributionChecklist#FSF_copyright_Assignment

Simon

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH][PR gdb/34239][PR gdb/34299] gdb: Check bounds before reading DWARF expression operands
  2026-06-18 18:01 [PATCH][PR gdb/34239][PR gdb/34299] gdb: Check bounds before reading DWARF expression operands Jielun Wu
  2026-06-23 18:56 ` Guinevere Larsen
  2026-06-25 15:23 ` Simon Marchi
@ 2026-06-25 16:36 ` Simon Marchi
  2026-06-26  7:34   ` Firmiana
  2 siblings, 1 reply; 8+ messages in thread
From: Simon Marchi @ 2026-06-25 16:36 UTC (permalink / raw)
  To: Jielun Wu, gdb-patches



On 2026-06-18 14:01, Jielun Wu wrote:
> Some DWARF expression opcodes read fixed-width operands or block payloads
> from the expression buffer before checking that the bytes are available.
> With a malformed expression, this can read past the end of the expression
> buffer.
> 
> Add helpers that validate fixed-width integer reads and block payload
> skips, and use them in dwarf_expr_context::execute_stack_op.  Also guard
> the nested DW_OP_entry_value parser before reading the following opcode.
> 
> Add a DWARF assembler test that exercises truncated operands for these
> opcodes.
> 
> Tested on x86_64-linux.

I noted a few comments below.

> Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=34239
> Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=34299
> ---
>  gdb/dwarf2/expr.c                             | 166 +++++++++------
>  .../gdb.dwarf2/dw2-bad-dwarf-expr-bounds.exp  | 190 ++++++++++++++++++
>  2 files changed, 300 insertions(+), 56 deletions(-)
>  create mode 100644 gdb/testsuite/gdb.dwarf2/dw2-bad-dwarf-expr-bounds.exp
> 
> diff --git a/gdb/dwarf2/expr.c b/gdb/dwarf2/expr.c
> index c71d4725b03..ead918388a4 100644
> --- a/gdb/dwarf2/expr.c
> +++ b/gdb/dwarf2/expr.c
> @@ -1370,7 +1370,45 @@ 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;
>  }
> -\f
> +
> +/* Helper to skip BYTES bytes or throw an error.  */

Please precise the comment, "throw an error" in what situation?

> +
> +static const gdb_byte *
> +safe_skip_bytes (const gdb_byte *buf, const gdb_byte *buf_end,
> +		 ULONGEST bytes)
> +{
> +  if (buf > buf_end || bytes > (ULONGEST) (buf_end - buf))

I would make the "buf > buf_end" condition an assert:

  gdb_assert (buf <= buf_end);

I think that if "buf > buf_end" happens, something would have went wrong
earlier.

> +    error (_("DWARF expression error: ran off end of buffer reading bytes"));
> +  return buf + bytes;
> +}
> +
> +/* Helper to read a fixed-width unsigned integer or throw an error.  */
> +
> +static const gdb_byte *
> +safe_read_unsigned_integer (const gdb_byte *buf, const gdb_byte *buf_end,
> +			    int len, bfd_endian byte_order, uint64_t *r)

Make `r` a reference.

You appear to use this function most of the time like this:

	  op_ptr = safe_read_unsigned_integer (op_ptr, op_end, 1,
					       byte_order, &uoffset);
	  result = uoffset;


where `result` is a ULONGEST and uoffset a uint64_t.  If you made `r` a
pointer or reference to ULONGEST, wouldn't it make things simpler?  You
could just do:

	  op_ptr = safe_read_unsigned_integer (op_ptr, op_end, 1,
					       byte_order, result);

... and avoid the subsequent assignment.

But it is sometimes used to read into other types, like cu_offset:

	    op_ptr = safe_read_unsigned_integer (op_ptr, op_end, 4,
						 byte_order, &uoffset);
	    kind_u.param_cu_off = (cu_offset) uoffset;

So we could perhaps make the function templated, like this?

  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 &r)
  {
    gdb_assert (len >= 0);
    gdb_assert (len <= sizeof (T));
  
    const gdb_byte *data = buf;
    buf = safe_skip_bytes (buf, buf_end, len);
    r = static_cast<T> (extract_unsigned_integer (data, len, byte_order));
    return buf;
  }

Then you'll be able to read the integer into whatever destination
integer type directly:

	op_ptr = safe_read_unsigned_integer (op_ptr, op_end, 4, byte_order,
						 &kind_u.param_cu_off);

> +{
> +  gdb_assert (len >= 0);

I would add an assert at len is <= the size of the destination type, as
shown above in my example.

> +  const gdb_byte *data = buf;
> +  buf = safe_skip_bytes (buf, buf_end, len);
> +  *r = extract_unsigned_integer (data, len, byte_order);
> +  return buf;
> +}
> +
> +/* Helper to read a fixed-width signed integer or throw an error.  */
> +
> +static const gdb_byte *
> +safe_read_signed_integer (const gdb_byte *buf, const gdb_byte *buf_end,
> +			  int len, bfd_endian byte_order, int64_t *r)
> +{
> +  gdb_assert (len >= 0);
> +
> +  const gdb_byte *data = buf;
> +  buf = safe_skip_bytes (buf, buf_end, len);
> +  *r = extract_signed_integer (data, len, byte_order);
> +  return buf;

Same comments as above.

>  /* Check that the current operator is either at the end of an
>     expression, or that it is followed by a composition operator or by
> @@ -1478,7 +1516,7 @@ dwarf_block_to_dwarf_reg_deref (gdb::array_view<const gdb_byte> block,
>        if (buf == NULL)
>  	return -1;
>        if ((int) dwarf_reg != dwarf_reg)
> -       return -1;
> +	return -1;

This is an unrelated whitespace change, I will push an obvious patch to
fix it.

>      }
>    else
>      return -1;
> @@ -1488,6 +1526,8 @@ dwarf_block_to_dwarf_reg_deref (gdb::array_view<const gdb_byte> block,
>      return -1;
>    if (offset != 0)
>      return -1;
> +  if (buf >= buf_end)
> +    return -1;

Can this really happen?  It seems to me like gdb_read_sleb128 guarantees
that it will not read past buf_end.  So it's perhaps possible that `buf
== buf_end`, but not `buf > buf_end`.  If so, I would do:

  gdb_assert (buf <= buf_end);

  if (buf == buf_end)
    return -1;

It should be possible to write "selftest" for
dwarf_block_to_dwarf_reg_deref (and dwarf_block_to_dwarf_reg), they look
like pure functions.  Can you add that?  Look at dwarf2/loc.c for
example to see how selftests are registered, we would do the same in
dwarf2/expr.c.

>    if (*buf == DW_OP_deref)
>      {
> @@ -1498,7 +1538,7 @@ dwarf_block_to_dwarf_reg_deref (gdb::array_view<const gdb_byte> block,
>      {
>        buf++;
>        if (buf >= buf_end)
> -       return -1;
> +	return -1;

Same as above, I'll fix it in an obvious patch.

> @@ -2016,7 +2066,13 @@ 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 = this->m_addr_size;
> +	    if (op != DW_OP_deref)
> +	      {
> +		op_ptr = safe_read_unsigned_integer (op_ptr, op_end, 1,
> +						     byte_order, &uoffset);
> +		addr_size = uoffset;
> +	      }

Please write it with the two branches like this:

	    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, &uoffset);
		addr_size = uoffset;
	      }

Add an empty line after the curly brace that closes the scope.

Simon

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH][PR gdb/34239][PR gdb/34299] gdb: Check bounds before reading DWARF expression operands
  2026-06-25 16:36 ` Simon Marchi
@ 2026-06-26  7:34   ` Firmiana
  2026-06-26 14:23     ` Simon Marchi
  0 siblings, 1 reply; 8+ messages in thread
From: Firmiana @ 2026-06-26  7:34 UTC (permalink / raw)
  To: Simon Marchi; +Cc: gdb-patches

[-- Attachment #1: Type: text/plain, Size: 9822 bytes --]

Hi,

Thanks for the review. Given the amount of changes needed, I plan to
prepare a v2 that
supersedes this version. Before preparing v2, I would like to clarify a few
questions.

First, regarding copyright assignment: I do not currently have an FSF
copyright assignment on file.  I noticed the 2022 binutils announcement
that DCO signed contributions are accepted:

  https://sourceware.org/pipermail/binutils/2022-October/123680.html

and binutils/MAINTAINERS documents DCO as an alternative to FSF copyright
assignment.  Does this DCO path also apply to GDB patches touching gdb/?
If so, I will add:

  Signed-off-by: Jielun Wu <firmiana402@gmail.com>

to the v2 commit.  If GDB still requires FSF copyright assignment for
this patch, I am willing to start that process.

On the technical comments, I agree with most of them and plan to address
them in v2:

  - make the helper comments more precise;
  - change the `buf > buf_end` cases in safe_skip_bytes() to assertions;
  - switch the new fixed-width integer helpers to reference/template
    style, unless you prefer keeping them closer to the existing
    safe_read_uleb128/safe_read_sleb128 style;
  - add assertions that LEN fits in the destination type;
  - remove the unrelated whitespace-only changes;
  - change the dwarf_block_to_dwarf_reg_deref guard to the
    `gdb_assert (buf <= buf_end); if (buf == buf_end) return -1;`
    form;
  - add selftests for dwarf_block_to_dwarf_reg and
    dwarf_block_to_dwarf_reg_deref;
  - rewrite the DW_OP_deref* branch using the explicit if/else form you
    suggested.

For the helper style: in v1 I mirrored the nearby safe_read_uleb128 and
safe_read_sleb128 helpers, which use output pointer parameters and short
"or throw an error" comments.  But I agree that these new helpers can be
clearer, so unless you prefer otherwise I will use the reference/template
style you suggested for v2.

Thanks,
Jielun

On Fri, Jun 26, 2026 at 12:36 AM Simon Marchi <simark@simark.ca> wrote:

>
>
> On 2026-06-18 14:01, Jielun Wu wrote:
> > Some DWARF expression opcodes read fixed-width operands or block payloads
> > from the expression buffer before checking that the bytes are available.
> > With a malformed expression, this can read past the end of the expression
> > buffer.
> >
> > Add helpers that validate fixed-width integer reads and block payload
> > skips, and use them in dwarf_expr_context::execute_stack_op.  Also guard
> > the nested DW_OP_entry_value parser before reading the following opcode.
> >
> > Add a DWARF assembler test that exercises truncated operands for these
> > opcodes.
> >
> > Tested on x86_64-linux.
>
> I noted a few comments below.
>
> > Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=34239
> > Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=34299
> > ---
> >  gdb/dwarf2/expr.c                             | 166 +++++++++------
> >  .../gdb.dwarf2/dw2-bad-dwarf-expr-bounds.exp  | 190 ++++++++++++++++++
> >  2 files changed, 300 insertions(+), 56 deletions(-)
> >  create mode 100644
> gdb/testsuite/gdb.dwarf2/dw2-bad-dwarf-expr-bounds.exp
> >
> > diff --git a/gdb/dwarf2/expr.c b/gdb/dwarf2/expr.c
> > index c71d4725b03..ead918388a4 100644
> > --- a/gdb/dwarf2/expr.c
> > +++ b/gdb/dwarf2/expr.c
> > @@ -1370,7 +1370,45 @@ 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;
> >  }
> > -
> > +
> > +/* Helper to skip BYTES bytes or throw an error.  */
>
> Please precise the comment, "throw an error" in what situation?
>
> > +
> > +static const gdb_byte *
> > +safe_skip_bytes (const gdb_byte *buf, const gdb_byte *buf_end,
> > +              ULONGEST bytes)
> > +{
> > +  if (buf > buf_end || bytes > (ULONGEST) (buf_end - buf))
>
> I would make the "buf > buf_end" condition an assert:
>
>   gdb_assert (buf <= buf_end);
>
> I think that if "buf > buf_end" happens, something would have went wrong
> earlier.
>
> > +    error (_("DWARF expression error: ran off end of buffer reading
> bytes"));
> > +  return buf + bytes;
> > +}
> > +
> > +/* Helper to read a fixed-width unsigned integer or throw an error.  */
> > +
> > +static const gdb_byte *
> > +safe_read_unsigned_integer (const gdb_byte *buf, const gdb_byte
> *buf_end,
> > +                         int len, bfd_endian byte_order, uint64_t *r)
>
> Make `r` a reference.
>
> You appear to use this function most of the time like this:
>
>           op_ptr = safe_read_unsigned_integer (op_ptr, op_end, 1,
>                                                byte_order, &uoffset);
>           result = uoffset;
>
>
> where `result` is a ULONGEST and uoffset a uint64_t.  If you made `r` a
> pointer or reference to ULONGEST, wouldn't it make things simpler?  You
> could just do:
>
>           op_ptr = safe_read_unsigned_integer (op_ptr, op_end, 1,
>                                                byte_order, result);
>
> ... and avoid the subsequent assignment.
>
> But it is sometimes used to read into other types, like cu_offset:
>
>             op_ptr = safe_read_unsigned_integer (op_ptr, op_end, 4,
>                                                  byte_order, &uoffset);
>             kind_u.param_cu_off = (cu_offset) uoffset;
>
> So we could perhaps make the function templated, like this?
>
>   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 &r)
>   {
>     gdb_assert (len >= 0);
>     gdb_assert (len <= sizeof (T));
>
>     const gdb_byte *data = buf;
>     buf = safe_skip_bytes (buf, buf_end, len);
>     r = static_cast<T> (extract_unsigned_integer (data, len, byte_order));
>     return buf;
>   }
>
> Then you'll be able to read the integer into whatever destination
> integer type directly:
>
>         op_ptr = safe_read_unsigned_integer (op_ptr, op_end, 4, byte_order,
>                                                  &kind_u.param_cu_off);
>
> > +{
> > +  gdb_assert (len >= 0);
>
> I would add an assert at len is <= the size of the destination type, as
> shown above in my example.
>
> > +  const gdb_byte *data = buf;
> > +  buf = safe_skip_bytes (buf, buf_end, len);
> > +  *r = extract_unsigned_integer (data, len, byte_order);
> > +  return buf;
> > +}
> > +
> > +/* Helper to read a fixed-width signed integer or throw an error.  */
> > +
> > +static const gdb_byte *
> > +safe_read_signed_integer (const gdb_byte *buf, const gdb_byte *buf_end,
> > +                       int len, bfd_endian byte_order, int64_t *r)
> > +{
> > +  gdb_assert (len >= 0);
> > +
> > +  const gdb_byte *data = buf;
> > +  buf = safe_skip_bytes (buf, buf_end, len);
> > +  *r = extract_signed_integer (data, len, byte_order);
> > +  return buf;
>
> Same comments as above.
>
> >  /* Check that the current operator is either at the end of an
> >     expression, or that it is followed by a composition operator or by
> > @@ -1478,7 +1516,7 @@ dwarf_block_to_dwarf_reg_deref
> (gdb::array_view<const gdb_byte> block,
> >        if (buf == NULL)
> >       return -1;
> >        if ((int) dwarf_reg != dwarf_reg)
> > -       return -1;
> > +     return -1;
>
> This is an unrelated whitespace change, I will push an obvious patch to
> fix it.
>
> >      }
> >    else
> >      return -1;
> > @@ -1488,6 +1526,8 @@ dwarf_block_to_dwarf_reg_deref
> (gdb::array_view<const gdb_byte> block,
> >      return -1;
> >    if (offset != 0)
> >      return -1;
> > +  if (buf >= buf_end)
> > +    return -1;
>
> Can this really happen?  It seems to me like gdb_read_sleb128 guarantees
> that it will not read past buf_end.  So it's perhaps possible that `buf
> == buf_end`, but not `buf > buf_end`.  If so, I would do:
>
>   gdb_assert (buf <= buf_end);
>
>   if (buf == buf_end)
>     return -1;
>
> It should be possible to write "selftest" for
> dwarf_block_to_dwarf_reg_deref (and dwarf_block_to_dwarf_reg), they look
> like pure functions.  Can you add that?  Look at dwarf2/loc.c for
> example to see how selftests are registered, we would do the same in
> dwarf2/expr.c.
>
> >    if (*buf == DW_OP_deref)
> >      {
> > @@ -1498,7 +1538,7 @@ dwarf_block_to_dwarf_reg_deref
> (gdb::array_view<const gdb_byte> block,
> >      {
> >        buf++;
> >        if (buf >= buf_end)
> > -       return -1;
> > +     return -1;
>
> Same as above, I'll fix it in an obvious patch.
>
> > @@ -2016,7 +2066,13 @@ 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 = this->m_addr_size;
> > +         if (op != DW_OP_deref)
> > +           {
> > +             op_ptr = safe_read_unsigned_integer (op_ptr, op_end, 1,
> > +                                                  byte_order, &uoffset);
> > +             addr_size = uoffset;
> > +           }
>
> Please write it with the two branches like this:
>
>             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, &uoffset);
>                 addr_size = uoffset;
>               }
>
> Add an empty line after the curly brace that closes the scope.
>
> Simon
>

[-- Attachment #2: Type: text/html, Size: 12090 bytes --]

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH][PR gdb/34239][PR gdb/34299] gdb: Check bounds before reading DWARF expression operands
  2026-06-23 18:56 ` Guinevere Larsen
@ 2026-06-26  8:31   ` Firmiana
  2026-06-26 14:30     ` Simon Marchi
  0 siblings, 1 reply; 8+ messages in thread
From: Firmiana @ 2026-06-26  8:31 UTC (permalink / raw)
  To: Guinevere Larsen; +Cc: gdb-patches

[-- Attachment #1: Type: text/plain, Size: 27273 bytes --]

Hi,

Thanks for the review and for testing the patch.

Before preparing v2, I would like to respond to a few points.

About the safe_read_* helper implementation: yes, your understanding is
right.  The helper saves the original pointer, advances through
safe_skip_bytes to validate that the requested byte range is available,
and only then calls extract_*_integer on the saved pointer.  I agree that
this was not obvious enough, so I will make the helper comments and/or
implementation clearer in v2.

On the question of reading into `uoffset` and then assigning to the real
destination: that was mainly done to mirror the nearby
safe_read_uleb128/safe_read_sleb128 helper style, which uses fixed output
types and output pointer parameters.  Based on your comment and the other
review, I plan to change the new fixed-width helpers to use references,
probably templated, so many call sites can read directly into `result`,
`this->m_len`, or the relevant field.

For DW_OP_deref_size / DW_OP_deref_type: the v1 code is intended to be
equivalent to:

  addr_size = *op_ptr;
  op_ptr++;

but with a bounds check first.  safe_read_unsigned_integer keeps the
original pointer for extract_unsigned_integer and uses safe_skip_bytes to
validate and compute the next pointer.  Since this was confusing, I will
rewrite the branch more explicitly in v2.

I will also switch the testcase loop to foreach_with_prefix.

Thanks,
Jielun


On Wed, Jun 24, 2026 at 2:56 AM Guinevere Larsen <guinevere@redhat.com>
wrote:

> On 6/18/26 3:01 PM, Jielun Wu wrote:
> > Some DWARF expression opcodes read fixed-width operands or block payloads
> > from the expression buffer before checking that the bytes are available.
> > With a malformed expression, this can read past the end of the expression
> > buffer.
> >
> > Add helpers that validate fixed-width integer reads and block payload
> > skips, and use them in dwarf_expr_context::execute_stack_op.  Also guard
> > the nested DW_OP_entry_value parser before reading the following opcode.
> >
> > Add a DWARF assembler test that exercises truncated operands for these
> > opcodes.
> >
> > Tested on x86_64-linux.
> >
> > Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=34239
> > Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=34299
> > ---
>
> Hi! Thanks for working on this!
>
> I'm not too familiar with the dwarf parser, so I can only do a
> superficial review. Most of my comments are questions and nitpicks, I
> don't think you necessarily need to send a new version just from this
> review.
>
> I have tested this and see no regressions, so feel free to add my test
> tag to the git trailers
>
> Tested-By: Guinevere Larsen <guinevere@redhat.com>
>
> >   gdb/dwarf2/expr.c                             | 166 +++++++++------
> >   .../gdb.dwarf2/dw2-bad-dwarf-expr-bounds.exp  | 190 ++++++++++++++++++
> >   2 files changed, 300 insertions(+), 56 deletions(-)
> >   create mode 100644
> gdb/testsuite/gdb.dwarf2/dw2-bad-dwarf-expr-bounds.exp
> >
> > diff --git a/gdb/dwarf2/expr.c b/gdb/dwarf2/expr.c
> > index c71d4725b03..ead918388a4 100644
> > --- a/gdb/dwarf2/expr.c
> > +++ b/gdb/dwarf2/expr.c
> > @@ -1370,7 +1370,45 @@ 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;
> >   }
> > -
> > +
> > +/* Helper to skip BYTES bytes or throw an error.  */
> > +
> > +static const gdb_byte *
> > +safe_skip_bytes (const gdb_byte *buf, const gdb_byte *buf_end,
> > +              ULONGEST bytes)
> > +{
> > +  if (buf > buf_end || bytes > (ULONGEST) (buf_end - buf))
> > +    error (_("DWARF expression error: ran off end of buffer reading
> bytes"));
> > +  return buf + bytes;
> > +}
> > +
> > +/* Helper to read a fixed-width unsigned integer or throw an error.  */
> > +
> > +static const gdb_byte *
> > +safe_read_unsigned_integer (const gdb_byte *buf, const gdb_byte
> *buf_end,
> > +                         int len, bfd_endian byte_order, uint64_t *r)
> > +{
> > +  gdb_assert (len >= 0);
> > +
> > +  const gdb_byte *data = buf;
> > +  buf = safe_skip_bytes (buf, buf_end, len);
> > +  *r = extract_unsigned_integer (data, len, byte_order);
> > +  return buf;
>
> Oh, ok, it took me a while of thinking but I think I now understand why
> you wrote it like this.
>
> You advance the buffer first to use that as a check that it is possible
> to read that many bytes from the buffer, instead of needing to double up
> on the if condition, right?
>
> If so, I think it would be nice to have a comment explaining it, because
> I went through 2 incorrect explanations before reaching that conclusion.
>
> > +}
> > +
> > +/* Helper to read a fixed-width signed integer or throw an error.  */
> > +
> > +static const gdb_byte *
> > +safe_read_signed_integer (const gdb_byte *buf, const gdb_byte *buf_end,
> > +                       int len, bfd_endian byte_order, int64_t *r)
> > +{
> > +  gdb_assert (len >= 0);
> > +
> > +  const gdb_byte *data = buf;
> > +  buf = safe_skip_bytes (buf, buf_end, len);
> > +  *r = extract_signed_integer (data, len, byte_order);
> > +  return buf;
> Similar comment as above
> > +}
> >
> >   /* Check that the current operator is either at the end of an
> >      expression, or that it is followed by a composition operator or by
> > @@ -1478,7 +1516,7 @@ dwarf_block_to_dwarf_reg_deref
> (gdb::array_view<const gdb_byte> block,
> >         if (buf == NULL)
> >       return -1;
> >         if ((int) dwarf_reg != dwarf_reg)
> > -       return -1;
> > +     return -1;
> >       }
> >     else
> >       return -1;
> > @@ -1488,6 +1526,8 @@ dwarf_block_to_dwarf_reg_deref
> (gdb::array_view<const gdb_byte> block,
> >       return -1;
> >     if (offset != 0)
> >       return -1;
> > +  if (buf >= buf_end)
> > +    return -1;
> >
> >     if (*buf == DW_OP_deref)
> >       {
> > @@ -1498,7 +1538,7 @@ dwarf_block_to_dwarf_reg_deref
> (gdb::array_view<const gdb_byte> block,
> >       {
> >         buf++;
> >         if (buf >= buf_end)
> > -       return -1;
> > +     return -1;
> >         *deref_size_return = *buf++;
> >       }
> >     else
> > @@ -1688,9 +1728,10 @@ 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, &uoffset);
>
> Why not just send "&result" as the parameter here, instead of &uoffset
>
> Same question for all the occurrences below
>
> > +       result = uoffset;
> >         /* 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 +1764,52 @@ 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, &uoffset);
> > +       result = uoffset;
> >         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, &offset);
> > +       result = offset;
> >         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, &uoffset);
> > +       result = uoffset;
> >         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, &offset);
> > +       result = offset;
> >         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, &uoffset);
> > +       result = uoffset;
> >         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, &offset);
> > +       result = offset;
> >         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, &uoffset);
> > +       result = uoffset;
> >         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, &offset);
> > +       result = offset;
> >         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 +1885,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 +1908,10 @@ 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,
> > +                                              &uoffset);
> > +         this->m_len = uoffset;
> Again, why not send "&this->m_len" ?
> >
> >           /* The byte offset into the data.  */
> >           op_ptr = safe_read_sleb128 (op_ptr, op_end, &len);
> > @@ -1972,7 +2020,9 @@ 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, &uoffset);
> > +       offset = uoffset;
> >         result_val = fetch (offset);
> >         in_stack_memory = fetch_in_stack_memory (offset);
> >         break;
> > @@ -2016,7 +2066,13 @@ 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 = this->m_addr_size;
> > +         if (op != DW_OP_deref)
> > +           {
> > +             op_ptr = safe_read_unsigned_integer (op_ptr, op_end, 1,
> > +                                                  byte_order, &uoffset);
> > +             addr_size = uoffset;
>
> Maybe I'm missing something, but this seems like an incorrect change?
>
> It seems like you should call safe_skip_bytes instead of
> read_unsigned_integer, and assign addr_size to *op_ptr?
>
> > +           }
> >           CORE_ADDR addr = fetch_address (0);
> >           struct type *type;
> >
> > @@ -2249,8 +2305,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;
> This should also use safe_skip_bytes
> >         goto no_push;
> >
> > @@ -2258,8 +2314,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 +2369,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;
> > +         op_ptr = safe_read_unsigned_integer (op_ptr, op_end, 2,
> > +                                              byte_order, &uoffset);
> > +         cu_offset cu_off = (cu_offset) uoffset;
> >           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;
> > +         op_ptr = safe_read_unsigned_integer (op_ptr, op_end, 4,
> > +                                              byte_order, &uoffset);
> > +         cu_offset cu_off = (cu_offset) uoffset;
> >           this->dwarf_call (cu_off);
> >         }
> >         goto no_push;
> > @@ -2334,11 +2390,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;
> > +         op_ptr = safe_read_unsigned_integer (op_ptr, op_end,
> > +                                              ref_addr_size, byte_order,
> > +                                              &uoffset);
> > +         sect_offset sect_off = (sect_offset) uoffset;
> >           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 +2408,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 +2437,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 +2462,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, &uoffset);
> > +         kind_u.param_cu_off = (cu_offset) uoffset;
> >           this->push_dwarf_reg_entry_value
> (CALL_SITE_PARAMETER_PARAM_OFFSET,
> >                                             kind_u,
> >                                             -1 /* deref_size */);
> > @@ -2429,9 +2481,11 @@ 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,
> > +                                              &uoffset);
> > +         n = uoffset;
> >           data = op_ptr;
> > -         op_ptr += n;
> > +         op_ptr = safe_skip_bytes (op_ptr, op_end, n);
> >
> >           type = get_base_type (type_die_cu_off);
> >
> > diff --git a/gdb/testsuite/gdb.dwarf2/dw2-bad-dwarf-expr-bounds.exp
> b/gdb/testsuite/gdb.dwarf2/dw2-bad-dwarf-expr-bounds.exp
> > new file mode 100644
> > index 00000000000..7f5fa22388d
> > --- /dev/null
> > +++ b/gdb/testsuite/gdb.dwarf2/dw2-bad-dwarf-expr-bounds.exp
> > @@ -0,0 +1,190 @@
> > +# 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} {
> > +     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 {
> > +                     _op .byte $Dwarf::_constants($op) $op
> > +                 } SPECIAL_expr
> > +             }
> > +         }
> > +
> > +         DW_TAG_variable {
> > +             DW_AT_name "truncated_implicit_value"
> > +             DW_AT_type :$int_label
> > +             DW_AT_location {
> > +                 _op .byte $Dwarf::_constants(DW_OP_implicit_value) \
> > +                     DW_OP_implicit_value
> > +                 _op .uleb128 4 "implicit value length"
> > +                 _op .2byte 0 "truncated implicit value bytes"
> > +             } SPECIAL_expr
> > +         }
> > +
> > +         DW_TAG_variable {
> > +             DW_AT_name "truncated_entry_value"
> > +             DW_AT_type :$int_label
> > +             DW_AT_location {
> > +                 _op .byte $Dwarf::_constants(DW_OP_entry_value) \
> > +                     DW_OP_entry_value
> > +                 _op .uleb128 4 "entry value expression length"
> > +                 _op .2byte 0 "truncated entry value expression"
> > +             } SPECIAL_expr
> > +         }
> > +
> > +         DW_TAG_variable {
> > +             DW_AT_name "truncated_gnu_entry_value"
> > +             DW_AT_type :$int_label
> > +             DW_AT_location {
> > +                 _op .byte $Dwarf::_constants(DW_OP_GNU_entry_value) \
> > +                     DW_OP_GNU_entry_value
> > +                 _op .uleb128 4 "entry value expression length"
> > +                 _op .2byte 0 "truncated entry value expression"
> > +             } SPECIAL_expr
> > +         }
> > +
> > +         DW_TAG_variable {
> > +             DW_AT_name "truncated_const_type_size"
> > +             DW_AT_type :$int_label
> > +             DW_AT_location {
> > +                 _op .byte $Dwarf::_constants(DW_OP_const_type) \
> > +                     DW_OP_const_type
> > +                 _op .uleb128 "$int_label - $cu_label" "type DIE offset"
> > +             } SPECIAL_expr
> > +         }
> > +
> > +         DW_TAG_variable {
> > +             DW_AT_name "truncated_const_type_payload"
> > +             DW_AT_type :$int_label
> > +             DW_AT_location {
> > +                 _op .byte $Dwarf::_constants(DW_OP_const_type) \
> > +                     DW_OP_const_type
> > +                 _op .uleb128 "$int_label - $cu_label" "type DIE offset"
> > +                 _op .byte 4 "constant block length"
> > +                 _op .2byte 0 "truncated constant block"
> > +             } SPECIAL_expr
> > +         }
> > +
> > +         DW_TAG_variable {
> > +             DW_AT_name "truncated_gnu_const_type_size"
> > +             DW_AT_type :$int_label
> > +             DW_AT_location {
> > +                 _op .byte $Dwarf::_constants(DW_OP_GNU_const_type) \
> > +                     DW_OP_GNU_const_type
> > +                 _op .uleb128 "$int_label - $cu_label" "type DIE offset"
> > +             } SPECIAL_expr
> > +         }
> > +
> > +         DW_TAG_variable {
> > +             DW_AT_name "truncated_gnu_const_type_payload"
> > +             DW_AT_type :$int_label
> > +             DW_AT_location {
> > +                 _op .byte $Dwarf::_constants(DW_OP_GNU_const_type) \
> > +                     DW_OP_GNU_const_type
> > +                 _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 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_entry_value
> > +    truncated_gnu_entry_value
> > +    truncated_parameter_ref
> > +    truncated_const_type_size
> > +    truncated_const_type_payload
> > +    truncated_gnu_const_type_size
> > +    truncated_gnu_const_type_payload
> > +} {
> > +    with_test_prefix $var {
> There exists a "foreach_with_prefix", that does basically what you did
> here.
> > +     gdb_test "print $var" \
> > +         ".*DWARF expression error: ran off end of buffer reading .*"
> > +    }
> > +}
>
>
> --
> Cheers,
> Guinevere Larsen
> it/its
> she/her (deprecated)
>
>

[-- Attachment #2: Type: text/html, Size: 34380 bytes --]

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH][PR gdb/34239][PR gdb/34299] gdb: Check bounds before reading DWARF expression operands
  2026-06-26  7:34   ` Firmiana
@ 2026-06-26 14:23     ` Simon Marchi
  0 siblings, 0 replies; 8+ messages in thread
From: Simon Marchi @ 2026-06-26 14:23 UTC (permalink / raw)
  To: Firmiana; +Cc: gdb-patches



On 2026-06-26 03:34, Firmiana wrote:
> Hi,
> 
> Thanks for the review. Given the amount of changes needed, I plan to prepare a v2 that
> supersedes this version. Before preparing v2, I would like to clarify a few questions.
> 
> First, regarding copyright assignment: I do not currently have an FSF
> copyright assignment on file.  I noticed the 2022 binutils announcement
> that DCO signed contributions are accepted:
> 
>   https://sourceware.org/pipermail/binutils/2022-October/123680.html <https://sourceware.org/pipermail/binutils/2022-October/123680.html>
> 
> and binutils/MAINTAINERS documents DCO as an alternative to FSF copyright
> assignment.  Does this DCO path also apply to GDB patches touching gdb/?
> If so, I will add:
> 
>   Signed-off-by: Jielun Wu <firmiana402@gmail.com <mailto:firmiana402@gmail.com>>
> 
> to the v2 commit.  If GDB still requires FSF copyright assignment for
> this patch, I am willing to start that process.

Thanks a lot.

No it doesn't apply to GDB.  There are ongoing discussions to maybe use
DCO for GDB too but we haven't reached a consensus yet.

> On the technical comments, I agree with most of them and plan to address
> them in v2:
> 
>   - make the helper comments more precise;
>   - change the `buf > buf_end` cases in safe_skip_bytes() to assertions;
>   - switch the new fixed-width integer helpers to reference/template
>     style, unless you prefer keeping them closer to the existing
>     safe_read_uleb128/safe_read_sleb128 style;
>   - add assertions that LEN fits in the destination type;
>   - remove the unrelated whitespace-only changes;
>   - change the dwarf_block_to_dwarf_reg_deref guard to the
>     `gdb_assert (buf <= buf_end); if (buf == buf_end) return -1;`
>     form;
>   - add selftests for dwarf_block_to_dwarf_reg and
>     dwarf_block_to_dwarf_reg_deref;
>   - rewrite the DW_OP_deref* branch using the explicit if/else form you
>     suggested.
> 
> For the helper style: in v1 I mirrored the nearby safe_read_uleb128 and
> safe_read_sleb128 helpers, which use output pointer parameters and short
> "or throw an error" comments.  But I agree that these new helpers can be
> clearer, so unless you prefer otherwise I will use the reference/template
> style you suggested for v2.

Ack.  It's good to leave the place a little nicer than how you found it
:).

Simon

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH][PR gdb/34239][PR gdb/34299] gdb: Check bounds before reading DWARF expression operands
  2026-06-26  8:31   ` Firmiana
@ 2026-06-26 14:30     ` Simon Marchi
  0 siblings, 0 replies; 8+ messages in thread
From: Simon Marchi @ 2026-06-26 14:30 UTC (permalink / raw)
  To: Firmiana, Guinevere Larsen; +Cc: gdb-patches



On 2026-06-26 04:31, Firmiana wrote:
> 
> Hi,
> 
> Thanks for the review and for testing the patch.
> 
> Before preparing v2, I would like to respond to a few points.
> 
> About the safe_read_* helper implementation: yes, your understanding is
> right.  The helper saves the original pointer, advances through
> safe_skip_bytes to validate that the requested byte range is available,
> and only then calls extract_*_integer on the saved pointer.  I agree that
> this was not obvious enough, so I will make the helper comments and/or
> implementation clearer in v2.
> 
> On the question of reading into `uoffset` and then assigning to the real
> destination: that was mainly done to mirror the nearby
> safe_read_uleb128/safe_read_sleb128 helper style, which uses fixed output
> types and output pointer parameters.  Based on your comment and the other
> review, I plan to change the new fixed-width helpers to use references,
> probably templated, so many call sites can read directly into `result`,
> `this->m_len`, or the relevant field.

The GDB code used to be C, and is now organically evolving into C++.
When you find some opportunities to use some C++ constructs to make the
code better, feel free to do so.

> For DW_OP_deref_size / DW_OP_deref_type: the v1 code is intended to be
> equivalent to:
> 
>   addr_size = *op_ptr;
>   op_ptr++;
> 
> but with a bounds check first.  safe_read_unsigned_integer keeps the
> original pointer for extract_unsigned_integer and uses safe_skip_bytes to
> validate and compute the next pointer.  Since this was confusing, I will
> rewrite the branch more explicitly in v2.
> 
> I will also switch the testcase loop to foreach_with_prefix.

Good idea.

Simon

^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2026-06-26 14:31 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-06-18 18:01 [PATCH][PR gdb/34239][PR gdb/34299] gdb: Check bounds before reading DWARF expression operands Jielun Wu
2026-06-23 18:56 ` Guinevere Larsen
2026-06-26  8:31   ` Firmiana
2026-06-26 14:30     ` Simon Marchi
2026-06-25 15:23 ` Simon Marchi
2026-06-25 16:36 ` Simon Marchi
2026-06-26  7:34   ` Firmiana
2026-06-26 14:23     ` Simon Marchi

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox