From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from simark.ca by simark.ca with LMTP id 2TPWDtYPw2qTmTkAWB0awg (envelope-from ) for ; Sun, 04 Oct 2026 22:47:50 -0400 Authentication-Results: simark.ca; dkim=pass (2048-bit key; unprotected) header.d=polymtl.ca header.i=@polymtl.ca header.a=rsa-sha256 header.s=oct2025 header.b=utfMwQE6; dkim-atps=neutral Received: by simark.ca (Postfix, from userid 112) id 365941E050; Sun, 04 Oct 2026 22:47:50 -0400 (EDT) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-25) on simark.ca X-Spam-Level: X-Spam-Status: No, score=-5.4 required=5.0 tests=ARC_SIGNED,ARC_VALID,BAYES_00, DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,MAILING_LIST_MULTI, RCVD_IN_DNSWL_MED autolearn=ham autolearn_force=no version=4.0.1 Received: from vm01.sourceware.org (vm01.sourceware.org [IPv6:2620:52:6:3111::32]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange x25519 server-signature ECDSA (prime256v1) server-digest SHA256) (No client certificate requested) by simark.ca (Postfix) with ESMTPS id 30DE11E047 for ; Sun, 04 Oct 2026 22:47:49 -0400 (EDT) Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 2C62A4BA23D9 for ; Mon, 5 Oct 2026 02:47:47 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 2C62A4BA23D9 Authentication-Results: sourceware.org; dkim=pass (2048-bit key, unprotected) header.d=polymtl.ca header.i=@polymtl.ca header.a=rsa-sha256 header.s=oct2025 header.b=utfMwQE6 Received: from smtp.polymtl.ca (smtp.polymtl.ca [132.207.4.11]) by sourceware.org (Postfix) with ESMTPS id F21324BA23C0 for ; Mon, 5 Oct 2026 02:47:09 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org F21324BA23C0 Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=polymtl.ca Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=polymtl.ca ARC-Filter: OpenARC Filter v1.0.0 sourceware.org F21324BA23C0 Authentication-Results: sourceware.org; arc=none smtp.remote-ip=132.207.4.11 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1791168430; cv=none; b=J14ofPGtozoZDH8gFt10wid2wTicsloTXgvqBNkc9SQrGGdvMXSpS5SdQ8ogciTc7oeyRpUh85qQ3t+djjSc0YYCr3U/gDYcnNDtn/Wjg8xO9m9GFXML7Yi7XiXzKMYML7+vAdHNnuB7GUhapZ7IZS9aWOYiCoEB9AToNFJl3zU= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1791168430; c=relaxed/simple; bh=vWWzh+VMJiE4ikMtHAn4IrxmUkl6UWQYGTy3EuX4LQY=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=cCz5TK9RmaFMuguwyMKMq+v7p9ogab8lDCLXUexqtB79oCs74Wf3K7qbmyOMQokZLqZPY//YZ9aqjJc+n1JBNFO3LnCxtTROvKgGfBUjvyVJUd5SdIgfxy8iQ/+iSswJ+Flh7nPEfDBUM9EnLfy4IfSeUwqvNbyTeMtZCegZS4U= ARC-Authentication-Results: i=1; sourceware.org; dkim=pass (2048-bit key, unprotected) header.d=polymtl.ca header.i=@polymtl.ca header.a=rsa-sha256 header.s=oct2025 header.b=utfMwQE6 DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org F21324BA23C0 Received: from simark.ca (simark.ca [158.69.221.121]) (authenticated bits=0) by smtp.polymtl.ca (8.14.7/8.14.7) with ESMTP id 6952l0Zg012128 (version=TLSv1/SSLv3 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Sun, 4 Oct 2026 22:47:05 -0400 DKIM-Filter: OpenDKIM Filter v2.11.0 smtp.polymtl.ca 6952l0Zg012128 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=polymtl.ca; s=oct2025; t=1791168426; bh=xD9VdhpDaDFOtqyfDsJluRJYatdtktQSn6TZAiT4CNQ=; h=From:To:Cc:Subject:Date:From; b=utfMwQE6fcaTvs9UX1/60ajDGIKMnUHEDDGlQ8n2WebKw2ETyn1kVNMxAMKr3842t fUaT8moworuxdoX1YTGnU4bfq5IdVbmCfqPqzBnUynuWy5eV7aEUzleuWNdhlZAlz1 EK6JaQxIPWRX23+IFvn8d3SPXUIZgieElcdQ92pz+0NK8nuOua5aEzUann51jX8wdT VRX7nveIfGfH9nao3Y7gegLFdZUyUJpCRQCPCCrXA8xMcO267x18C4kUB+4bztRhks ux/UzcDITQ3ooHESDtEhWBf7OhujrcOvO12gENXJ6xY3YaCONOAFWeAx86YUjtE9+I Ch9vLqL9sNvDg== Received: by simark.ca (Postfix) id 1D9F91E047; Sun, 04 Oct 2026 22:46:59 -0400 (EDT) From: simon.marchi@polymtl.ca To: gdb-patches@sourceware.org Cc: Simon Marchi Subject: [PATCH] gdb: fix out-of-bounds read when printing a pointer with a format Date: Sun, 4 Oct 2026 22:46:46 -0400 Message-ID: <20261005024658.927684-1-simon.marchi@polymtl.ca> X-Mailer: git-send-email 2.55.0 MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Poly-FromMTA: (simark.ca [158.69.221.121]) at Mon, 5 Oct 2026 02:47:00 +0000 X-BeenThere: gdb-patches@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Gdb-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: gdb-patches-bounces~public-inbox=simark.ca@sourceware.org From: Simon Marchi 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