* [PATCH] gdb: fix out-of-bounds read when printing a pointer with a format
@ 2026-10-05 2:46 simon.marchi
2026-10-15 10:42 ` Andrew Burgess
0 siblings, 1 reply; 3+ messages in thread
From: simon.marchi @ 2026-10-05 2:46 UTC (permalink / raw)
To: gdb-patches; +Cc: Simon Marchi
From: Simon Marchi <simon.marchi@polymtl.ca>
I stumbled on this while investigating bug 34638 ("Multi-register-reading
oddity / anachronism"), but this patch is not otherwise related to that
bug.
When the value being printed is a pointer, print_scalar_formatted
overrides the number of bytes it reads from the value buffer:
if (type->code () == TYPE_CODE_PTR)
len = gdbarch_addr_bit (gdbarch) / TARGET_CHAR_BIT;
VALADDR only holds type->length() bytes though, and for a pointer type
built by GDB that is `gdbarch_ptr_bit / TARGET_CHAR_BIT` bytes. On targets
where gdbarch_addr_bit is larger than gdbarch_ptr_bit, we therefore read
past the end of the buffer.
AVR is such a target: gdbarch_ptr_bit is 16 while gdbarch_addr_bit is 32,
so printing any pointer with a format reads 4 bytes out of a 2-byte buffer.
With a GDB built with AddressSanitizer:
(gdb) p/x $sp
=================================================================
==433709==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7c11d1239e33 at pc 0x55eb8e8438f0 bp 0x7ffc17c69050 sp 0x7ffc17c69040
It can be reproduced without running a program, simply with a GDB that has
AVR support, by forcing the architecture to be AVR:
(gdb) set architecture avr
The target architecture is set to "avr".
(gdb) p/x (void *) 0x1234
=================================================================
==434356==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7bbca0e21293 at pc 0x55f5d74d08f0 bp 0x7ffe74cba960 sp 0x7ffe74cba950
"info registers SP" on an AVR target goes through the same path, so hits
the same crash:
(gdb) c
Continuing.
Breakpoint 1, main () at test.c:16
16 int local = 42;
(gdb) info registers SP
=================================================================
==432390==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7b764403b413 at pc 0x55e8ef8628f0 bp 0x7fff13994110 sp 0x7fff13994100
The override comes with this comment:
/* If the value is a pointer, and pointers and addresses are not the
same, then at this point, the value's length (in target bytes) is
gdbarch_addr_bit/TARGET_CHAR_BIT, not type->length (). */
This was true when the override was introduced, in commit ef166cf46a1b
(2001). At the time, print_scalar_formatted started by converting the
value to a LONGEST:
val_long = unpack_long (type, valaddr);
For pointers, unpack_long goes through extract_typed_address, then
gdbarch_pointer_to_address, which converts the pointer to a CORE_ADDR
gdbarch_addr_bit wide. On AVR, for instance, data pointer 0x1234 (2 bytes
long) becomes address 0x801234 (4 bytes long). "The value" in the comment
refers to this VAL_LONG, not to the bytes at VALADDR. LEN was then only
used to mask VAL_LONG, and the override made sure that the mask did not
truncate the converted address to gdbarch_ptr_bit bits. VALADDR (2 bytes)
was never read using LEN (4 bytes), so there was no out-of-bounds read.
Commit d9109c8080ac ("Simplify print_scalar_formatted", 2017) changed the
function to print the value using the print_*_chars functions, which
operate directly on the bytes at VALADDR, and removed the conversion to
LONGEST. From then on, the value printed became the raw pointer
representation, type->length() bytes long (2 bytes), not the converted
address (4 bytes). The length override was left in place, and this is
when the out-of-bounds read was introduced.
Note that commit d9109c8080ac therefore changed how `print (void *) 0x1234`
is printed for architectures like AVR, it went from
$1 = 0x801234
to just
$1 = 0x1234
I don't think it's a mistake in itself, but I am not sure it was fully
understood at the time.
Since the LEN override no longer has a reason to be, remove it.
With LEN now always equal to type->length(), the following block becomes
dead code, so remove it. The comment above it is also a relic of the
VAL_LONG time.
I started by adding a very simple test to
gdb.base/all-architectures.exp.tcl to try to print a formatted pointer with
each arch. I then thought it would be useful to test printing a pointer
without formatting, and then the various kinds of pointers (data vs code).
The result is the somewhat complicated procedure in this patch. It did not
find additional bugs, but I think it's still useful to have in the
testsuite, as it captures what GDB does today. If a change in the pointer
printing code ever changes (notably in the address <-> pointer conversion),
we will notice it.
Change-Id: I287eb515a019941b63325e3a258b1eb1b5225e4a
---
gdb/printcmd.c | 16 ----
.../gdb.base/all-architectures.exp.tcl | 76 +++++++++++++++++++
2 files changed, 76 insertions(+), 16 deletions(-)
diff --git a/gdb/printcmd.c b/gdb/printcmd.c
index 4fd369c90c06..09b22ae5010c 100644
--- a/gdb/printcmd.c
+++ b/gdb/printcmd.c
@@ -373,22 +373,6 @@ print_scalar_formatted (const gdb_byte *valaddr, struct type *type,
/* String printing should go through val_print_scalar_formatted. */
gdb_assert (options.format != 's');
- /* If the value is a pointer, and pointers and addresses are not the
- same, then at this point, the value's length (in target bytes) is
- gdbarch_addr_bit/TARGET_CHAR_BIT, not type->length (). */
- if (type->code () == TYPE_CODE_PTR)
- len = gdbarch_addr_bit (gdbarch) / TARGET_CHAR_BIT;
-
- /* If we are printing it as unsigned, truncate it in case it is actually
- a negative signed value (e.g. "print/u (short)-1" should print 65535
- (if shorts are 16 bits) instead of 4294967295). */
- if (options.format != 'c'
- && (options.format != 'd' || type->is_unsigned ()))
- {
- if (len < type->length () && byte_order == BFD_ENDIAN_BIG)
- valaddr += type->length () - len;
- }
-
/* Allow LEN == 0, and in this case, don't assume that VALADDR is
valid. */
const gdb_byte zero = 0;
diff --git a/gdb/testsuite/gdb.base/all-architectures.exp.tcl b/gdb/testsuite/gdb.base/all-architectures.exp.tcl
index 7d2979476286..79f13ba7efa1 100644
--- a/gdb/testsuite/gdb.base/all-architectures.exp.tcl
+++ b/gdb/testsuite/gdb.base/all-architectures.exp.tcl
@@ -130,10 +130,86 @@ proc print_floats {} {
gdb_test_internal "print 1.0f" " = 1" "print, float"
}
+# Exercise printing various kinds of data and code pointers, with and
+# without a format, on architecture ARCH. This is not necessarily a
+# source of truth, the test just matches what GDB happened to output
+# for each arch at the time of writing.
+
+proc print_pointers {arch} {
+ # Without a format, the pointer is converted to an address using
+ # gdbarch_pointer_to_address, which is sometimes in a different
+ # representation.
+ set data_addr 0x1234
+ set code_addr 0x1234
+
+ if {[string match "avr*" $arch]} {
+ # Data pointers point to SRAM, which starts at 0x800000 in the
+ # imaginary unified address space.
+ #
+ # Code pointers are word-addressed and are converted to a byte address
+ # in flash.
+ set data_addr 0x801234
+ set code_addr 0x2468
+ } elseif {[string match "ft32*" $arch]} {
+ # Data pointers point to RAM, which starts at 0x800000.
+ #
+ # Pointers with the flash address class (see the @flash
+ # pointers below) point to flash, so start at 0.
+ #
+ # Function pointers and pointers to @code behave like data pointers,
+ # not sure if it's a bug or not (maybe they should behave like @flash
+ # pointers).
+ set data_addr 0x801234
+ set code_addr 0x801234
+ } elseif {$arch == "rl78"} {
+ # Data pointers point to the 64 KiB region starting at
+ # 0xf0000.
+ set data_addr 0xf1234
+ } elseif {$arch == "iq2000" || $arch == "iq10"} {
+ # Code pointers have the 0x80000000 bit set.
+ set code_addr 0x80001234
+ }
+
+ # Each element is: test prefix, type, type regexp as printed by
+ # GDB, expected address.
+ #
+ # The @code and @data qualifiers apply to the pointed-to type, and
+ # are understood by all architectures.
+ set pointers [list \
+ [list "data pointer" "void *" "void \\*" $data_addr] \
+ [list "code pointer" "void (*) (void)" "void \\(\\*\\)\\(void\\)" $code_addr] \
+ [list "@data pointer" "void @data *" "@data void \\*" $data_addr] \
+ [list "@code pointer" "void @code *" "@code void \\*" $code_addr]]
+
+ # AVR and FT32 have a "flash" address class, which applies to the
+ # pointer type itself.
+ if {[string match "avr*" $arch] || [string match "ft32*" $arch]} {
+ lappend pointers \
+ [list "@flash data pointer" "void * @flash" "void \\* @flash" 0x1234] \
+ [list "@flash code pointer" "void (* @flash) (void)" "void \\(\\* @flash\\)\\(void\\)" 0x1234]
+ }
+
+ foreach ptr $pointers {
+ lassign $ptr kind type_name type_name_re addr
+
+ with_test_prefix $kind {
+ gdb_test_internal "print ($type_name) 0x1234" \
+ " = \\($type_name_re\\) $addr" "print"
+
+ # With a format, the pointer's bytes are printed as is.
+ gdb_test_internal "print/x ($type_name) 0x1234" " = 0x1234" "print/x"
+ gdb_test_internal "print/d ($type_name) 0x1234" " = 4660" "print/d"
+ gdb_test_internal "print/o ($type_name) 0x1234" " = 011064" "print/o"
+ gdb_test_internal "print/t ($type_name) 0x1234" " = 1001000110100" "print/t"
+ }
+ }
+}
+
# Run tests on the current architecture ARCH.
proc do_arch_tests {arch} {
print_floats
+ print_pointers $arch
# When we disassemble using the default architecture then we
# expect that the only error we should get from the disassembler
base-commit: 2ce6c3190773eb22f467446c4fdc7edb123ce82f
--
2.55.0
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [PATCH] gdb: fix out-of-bounds read when printing a pointer with a format
2026-10-05 2:46 [PATCH] gdb: fix out-of-bounds read when printing a pointer with a format simon.marchi
@ 2026-10-15 10:42 ` Andrew Burgess
2026-10-15 12:52 ` Simon Marchi
0 siblings, 1 reply; 3+ messages in thread
From: Andrew Burgess @ 2026-10-15 10:42 UTC (permalink / raw)
To: simon.marchi, gdb-patches; +Cc: Simon Marchi
simon.marchi@polymtl.ca writes:
> From: Simon Marchi <simon.marchi@polymtl.ca>
>
> I stumbled on this while investigating bug 34638 ("Multi-register-reading
> oddity / anachronism"), but this patch is not otherwise related to that
> bug.
>
> When the value being printed is a pointer, print_scalar_formatted
> overrides the number of bytes it reads from the value buffer:
>
> if (type->code () == TYPE_CODE_PTR)
> len = gdbarch_addr_bit (gdbarch) / TARGET_CHAR_BIT;
>
> VALADDR only holds type->length() bytes though, and for a pointer type
> built by GDB that is `gdbarch_ptr_bit / TARGET_CHAR_BIT` bytes. On targets
> where gdbarch_addr_bit is larger than gdbarch_ptr_bit, we therefore read
> past the end of the buffer.
>
> AVR is such a target: gdbarch_ptr_bit is 16 while gdbarch_addr_bit is 32,
> so printing any pointer with a format reads 4 bytes out of a 2-byte buffer.
> With a GDB built with AddressSanitizer:
>
> (gdb) p/x $sp
> =================================================================
> ==433709==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7c11d1239e33 at pc 0x55eb8e8438f0 bp 0x7ffc17c69050 sp 0x7ffc17c69040
>
> It can be reproduced without running a program, simply with a GDB that has
> AVR support, by forcing the architecture to be AVR:
>
> (gdb) set architecture avr
> The target architecture is set to "avr".
> (gdb) p/x (void *) 0x1234
> =================================================================
> ==434356==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7bbca0e21293 at pc 0x55f5d74d08f0 bp 0x7ffe74cba960 sp 0x7ffe74cba950
>
> "info registers SP" on an AVR target goes through the same path, so hits
> the same crash:
>
> (gdb) c
> Continuing.
>
> Breakpoint 1, main () at test.c:16
> 16 int local = 42;
> (gdb) info registers SP
> =================================================================
> ==432390==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7b764403b413 at pc 0x55e8ef8628f0 bp 0x7fff13994110 sp 0x7fff13994100
>
> The override comes with this comment:
>
> /* If the value is a pointer, and pointers and addresses are not the
> same, then at this point, the value's length (in target bytes) is
> gdbarch_addr_bit/TARGET_CHAR_BIT, not type->length (). */
>
> This was true when the override was introduced, in commit ef166cf46a1b
> (2001). At the time, print_scalar_formatted started by converting the
> value to a LONGEST:
>
> val_long = unpack_long (type, valaddr);
>
> For pointers, unpack_long goes through extract_typed_address, then
> gdbarch_pointer_to_address, which converts the pointer to a CORE_ADDR
> gdbarch_addr_bit wide. On AVR, for instance, data pointer 0x1234 (2 bytes
> long) becomes address 0x801234 (4 bytes long). "The value" in the comment
> refers to this VAL_LONG, not to the bytes at VALADDR. LEN was then only
> used to mask VAL_LONG, and the override made sure that the mask did not
> truncate the converted address to gdbarch_ptr_bit bits. VALADDR (2 bytes)
> was never read using LEN (4 bytes), so there was no out-of-bounds read.
>
> Commit d9109c8080ac ("Simplify print_scalar_formatted", 2017) changed the
> function to print the value using the print_*_chars functions, which
> operate directly on the bytes at VALADDR, and removed the conversion to
> LONGEST. From then on, the value printed became the raw pointer
> representation, type->length() bytes long (2 bytes), not the converted
> address (4 bytes). The length override was left in place, and this is
> when the out-of-bounds read was introduced.
>
> Note that commit d9109c8080ac therefore changed how `print (void *) 0x1234`
> is printed for architectures like AVR, it went from
>
> $1 = 0x801234
>
> to just
>
> $1 = 0x1234
>
> I don't think it's a mistake in itself, but I am not sure it was fully
> understood at the time.
>
> Since the LEN override no longer has a reason to be, remove it.
>
> With LEN now always equal to type->length(), the following block becomes
> dead code, so remove it. The comment above it is also a relic of the
> VAL_LONG time.
>
> I started by adding a very simple test to
> gdb.base/all-architectures.exp.tcl to try to print a formatted pointer with
> each arch. I then thought it would be useful to test printing a pointer
> without formatting, and then the various kinds of pointers (data vs code).
> The result is the somewhat complicated procedure in this patch. It did not
> find additional bugs, but I think it's still useful to have in the
> testsuite, as it captures what GDB does today. If a change in the pointer
> printing code ever changes (notably in the address <-> pointer conversion),
> we will notice it.
LGTM.
Approved-By: Andrew Burgess <aburgess@redhat.com>
Thanks,
Andrew
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [PATCH] gdb: fix out-of-bounds read when printing a pointer with a format
2026-10-15 10:42 ` Andrew Burgess
@ 2026-10-15 12:52 ` Simon Marchi
0 siblings, 0 replies; 3+ messages in thread
From: Simon Marchi @ 2026-10-15 12:52 UTC (permalink / raw)
To: Andrew Burgess, gdb-patches
On 10/15/26 6:42 AM, Andrew Burgess wrote:
> simon.marchi@polymtl.ca writes:
>
>> From: Simon Marchi <simon.marchi@polymtl.ca>
>>
>> I stumbled on this while investigating bug 34638 ("Multi-register-reading
>> oddity / anachronism"), but this patch is not otherwise related to that
>> bug.
>>
>> When the value being printed is a pointer, print_scalar_formatted
>> overrides the number of bytes it reads from the value buffer:
>>
>> if (type->code () == TYPE_CODE_PTR)
>> len = gdbarch_addr_bit (gdbarch) / TARGET_CHAR_BIT;
>>
>> VALADDR only holds type->length() bytes though, and for a pointer type
>> built by GDB that is `gdbarch_ptr_bit / TARGET_CHAR_BIT` bytes. On targets
>> where gdbarch_addr_bit is larger than gdbarch_ptr_bit, we therefore read
>> past the end of the buffer.
>>
>> AVR is such a target: gdbarch_ptr_bit is 16 while gdbarch_addr_bit is 32,
>> so printing any pointer with a format reads 4 bytes out of a 2-byte buffer.
>> With a GDB built with AddressSanitizer:
>>
>> (gdb) p/x $sp
>> =================================================================
>> ==433709==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7c11d1239e33 at pc 0x55eb8e8438f0 bp 0x7ffc17c69050 sp 0x7ffc17c69040
>>
>> It can be reproduced without running a program, simply with a GDB that has
>> AVR support, by forcing the architecture to be AVR:
>>
>> (gdb) set architecture avr
>> The target architecture is set to "avr".
>> (gdb) p/x (void *) 0x1234
>> =================================================================
>> ==434356==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7bbca0e21293 at pc 0x55f5d74d08f0 bp 0x7ffe74cba960 sp 0x7ffe74cba950
>>
>> "info registers SP" on an AVR target goes through the same path, so hits
>> the same crash:
>>
>> (gdb) c
>> Continuing.
>>
>> Breakpoint 1, main () at test.c:16
>> 16 int local = 42;
>> (gdb) info registers SP
>> =================================================================
>> ==432390==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7b764403b413 at pc 0x55e8ef8628f0 bp 0x7fff13994110 sp 0x7fff13994100
>>
>> The override comes with this comment:
>>
>> /* If the value is a pointer, and pointers and addresses are not the
>> same, then at this point, the value's length (in target bytes) is
>> gdbarch_addr_bit/TARGET_CHAR_BIT, not type->length (). */
>>
>> This was true when the override was introduced, in commit ef166cf46a1b
>> (2001). At the time, print_scalar_formatted started by converting the
>> value to a LONGEST:
>>
>> val_long = unpack_long (type, valaddr);
>>
>> For pointers, unpack_long goes through extract_typed_address, then
>> gdbarch_pointer_to_address, which converts the pointer to a CORE_ADDR
>> gdbarch_addr_bit wide. On AVR, for instance, data pointer 0x1234 (2 bytes
>> long) becomes address 0x801234 (4 bytes long). "The value" in the comment
>> refers to this VAL_LONG, not to the bytes at VALADDR. LEN was then only
>> used to mask VAL_LONG, and the override made sure that the mask did not
>> truncate the converted address to gdbarch_ptr_bit bits. VALADDR (2 bytes)
>> was never read using LEN (4 bytes), so there was no out-of-bounds read.
>>
>> Commit d9109c8080ac ("Simplify print_scalar_formatted", 2017) changed the
>> function to print the value using the print_*_chars functions, which
>> operate directly on the bytes at VALADDR, and removed the conversion to
>> LONGEST. From then on, the value printed became the raw pointer
>> representation, type->length() bytes long (2 bytes), not the converted
>> address (4 bytes). The length override was left in place, and this is
>> when the out-of-bounds read was introduced.
>>
>> Note that commit d9109c8080ac therefore changed how `print (void *) 0x1234`
>> is printed for architectures like AVR, it went from
>>
>> $1 = 0x801234
>>
>> to just
>>
>> $1 = 0x1234
>>
>> I don't think it's a mistake in itself, but I am not sure it was fully
>> understood at the time.
>>
>> Since the LEN override no longer has a reason to be, remove it.
>>
>> With LEN now always equal to type->length(), the following block becomes
>> dead code, so remove it. The comment above it is also a relic of the
>> VAL_LONG time.
>>
>> I started by adding a very simple test to
>> gdb.base/all-architectures.exp.tcl to try to print a formatted pointer with
>> each arch. I then thought it would be useful to test printing a pointer
>> without formatting, and then the various kinds of pointers (data vs code).
>> The result is the somewhat complicated procedure in this patch. It did not
>> find additional bugs, but I think it's still useful to have in the
>> testsuite, as it captures what GDB does today. If a change in the pointer
>> printing code ever changes (notably in the address <-> pointer conversion),
>> we will notice it.
>
> LGTM.
>
> Approved-By: Andrew Burgess <aburgess@redhat.com>
Thanks, pushed.
Simon
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-10-15 12:52 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-05 2:46 [PATCH] gdb: fix out-of-bounds read when printing a pointer with a format simon.marchi
2026-10-15 10:42 ` Andrew Burgess
2026-10-15 12:52 ` Simon Marchi
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox