Mirror of the gdb-patches mailing list
 help / color / mirror / Atom feed
* [PATCH v3] Fix crash when target-wide-charset doesn't match wchar_t size
@ 2026-10-27 11:21 Aditya Vidyadhar Kamath
  0 siblings, 0 replies; only message in thread
From: Aditya Vidyadhar Kamath @ 2026-10-27 11:21 UTC (permalink / raw)
  To: ulrich.weigand, simon.marchi, tom
  Cc: gdb-patches, Aditya.Kamath1, sangamesh.swamy, Aditya Vidyadhar Kamath

From: Aditya Vidyadhar Kamath <aditya.kamath1@ibm.com>

Tom asked [1] whether setting target-wide-charset to an encoding with
the wrong unit size can crash GDB.  It can.

First I compiled gdb/testsuite/gdb.base/wchar.c

      gcc -g -maix64 -o /tmp/wchar64 gdb/testsuite/gdb.base/wchar.c

Then debug with GDB and we get the crash

      (gdb) b wchar.c:44
      (gdb) run
      (gdb) set target-wide-charset UTF-16
      (gdb) print single

      Fatal signal: Segmentation fault
      ----- Backtrace -----

On 64-bit AIX wchar_t is 4 bytes wide.  Setting target-wide-charset
to UTF-16 causes wchar_iterator::iterate() to be constructed with
m_width=4 but a 2-byte-per-unit charset.  On the EILSEQ path, iconv
consumes only 2 bytes before returning, and then:

  m_input += m_width;   /* advances by 4 */
  m_bytes -= m_width;   /* m_bytes was 4, wraps to a huge size_t */

The loop races past the end of the buffer, corrupting the stack and
crashing inside libiconv.

Two fixes:

1. validate() now checks the unit width implied by the charset name
   against wchar_bit and rejects the setting with a clear error.
   The previous good value is restored on failure since the setshow
   framework stores the new value before calling the sfunc.

2. wchar_iterator::iterate() guards the EILSEQ path: if
   m_bytes < m_width, clamp m_bytes to zero instead of subtracting,
   so the loop terminates cleanly regardless of how the iterator
   was constructed.

With the patch, the same session now
gives:

      (gdb) set target-wide-charset UTF-16
      Character set `UTF-16' has a 2-byte character width but `wchar_t' on this target is 4 bytes wide.
      Use `set target-wide-charset auto' to let GDB choose the correct encoding automatically.
      (gdb) print single
      $1 = 48879 L'\xbeef'

Add tests to wchar.exp for this.

Tested on powerpc64-ibm-aix7.2.0.0 with 32-bit and 64-bit inferiors.

[1]: https://sourceware.org/pipermail/gdb-patches/2026-October/231110.html
---
 gdb/charset.c                    | 68 ++++++++++++++++++++++++++++++--
 gdb/testsuite/gdb.base/wchar.exp | 34 ++++++++++++++++
 2 files changed, 99 insertions(+), 3 deletions(-)

diff --git a/gdb/charset.c b/gdb/charset.c
index 1b0725b5078..327e4f4cf91 100644
--- a/gdb/charset.c
+++ b/gdb/charset.c
@@ -256,6 +256,10 @@ show_target_charset_name (struct ui_file *file, int from_tty,
 }
 
 static const char *target_wide_charset_name = "auto";
+
+/* Last known-good value of target_wide_charset_name, used to roll back
+   if a new setting fails validation.  */
+static const char *target_wide_charset_name_validated = "auto";
 static void
 show_target_wide_charset_name (struct ui_file *file,
 			       int from_tty,
@@ -336,6 +340,21 @@ set_be_le_names (struct gdbarch *gdbarch)
 /* 'Set charset', 'set host-charset', 'set target-charset', 'set
    target-wide-charset', 'set charset' sfunc's.  */
 
+/* Return the character width in bytes implied by a wide charset name:
+   2 for UTF-16/UCS-2, 4 for UTF-32/UCS-4, 0 if unknown.  */
+
+static int
+wide_charset_width (const char *name)
+{
+  if (strncasecmp (name, "UTF-16", 6) == 0
+      || strncasecmp (name, "UCS-2", 5) == 0)
+    return 2;
+  if (strncasecmp (name, "UTF-32", 6) == 0
+      || strncasecmp (name, "UCS-4", 5) == 0)
+    return 4;
+  return 0;
+}
+
 static void
 validate (struct gdbarch *gdbarch)
 {
@@ -346,6 +365,25 @@ validate (struct gdbarch *gdbarch)
 
   if (streq (target_wide_cset, "auto"))
     target_wide_cset = gdbarch_auto_wide_charset (gdbarch);
+  else
+    {
+      /* Reject charsets whose unit size doesn't match wchar_t.
+	 A mismatch (e.g. UTF-16 with a 4-byte wchar_t) makes iconv
+	 consume the wrong number of bytes and can crash GDB.  */
+      int charset_width = wide_charset_width (target_wide_cset);
+      if (charset_width != 0)
+	{
+	  int wchar_bytes = gdbarch_wchar_bit (gdbarch) / 8;
+	  if (charset_width != wchar_bytes)
+	    error (_ ("Character set \"%s\" has a %d-byte character width "
+		      "but \"wchar_t\" on this target is %d bytes wide.\n"
+		      "Use \"%ps\" to let GDB "
+		      "choose the correct encoding automatically."),
+		   target_wide_cset, charset_width, wchar_bytes,
+		   styled_string (command_style.style (),
+				  "set target-wide-charset auto"));
+	}
+    }
 
   desc = iconv_open (target_wide_cset, host_cset);
   if (desc == (iconv_t) -1)
@@ -395,7 +433,18 @@ static void
 set_target_wide_charset_sfunc (const char *charset, int from_tty,
 			       struct cmd_list_element *c)
 {
-  validate (get_current_arch ());
+  /* The setshow framework writes the new value before calling us, so
+     restore the previous setting if validation fails.  */
+  try
+    {
+      validate (get_current_arch ());
+      target_wide_charset_name_validated = target_wide_charset_name;
+    }
+  catch (const gdb_exception_error &)
+    {
+      target_wide_charset_name = target_wide_charset_name_validated;
+      throw;
+    }
 }
 
 /* sfunc for the 'show charset' command.  */
@@ -646,8 +695,21 @@ wchar_iterator::iterate (enum wchar_iterate_result *out_result,
 	      *out_result = wchar_iterate_invalid;
 	      *ptr = m_input;
 	      *len = m_width;
-	      m_input += m_width;
-	      m_bytes -= m_width;
+	      /* Guard against unsigned wrap: m_bytes < m_width can happen
+		 when the charset unit size doesn't match m_width (e.g. a
+		 UTF-16 charset used with a 4-byte wchar_t).  Clamp to zero
+		 rather than letting m_bytes underflow to a huge value and
+		 sending the loop past the end of the buffer.  */
+	      if (m_bytes >= m_width)
+		{
+		  m_input += m_width;
+		  m_bytes -= m_width;
+		}
+	      else
+		{
+		  m_input += m_bytes;
+		  m_bytes = 0;
+		}
 	      return 0;
 
 	    case E2BIG:
diff --git a/gdb/testsuite/gdb.base/wchar.exp b/gdb/testsuite/gdb.base/wchar.exp
index b02f4a78336..801aeb9ee20 100644
--- a/gdb/testsuite/gdb.base/wchar.exp
+++ b/gdb/testsuite/gdb.base/wchar.exp
@@ -75,3 +75,37 @@ gdb_test "printf \"%ls\\n\", 0" "\\(null\\)"
 # From PR exp/33124 - a bug when converting escapes.
 set wbs {L'\\'}
 gdb_test "print $wbs" " = $decimal [string_to_regexp $wbs]"
+
+# A wide charset whose unit size doesn't match wchar_t must be rejected,
+# otherwise printing wide chars can crash GDB.
+# Derive good/bad charsets from the inferior's actual wchar_t size so the
+# test is correct for both 32-bit (2-byte wchar_t -> UTF-16) and 64-bit
+# (4-byte wchar_t -> UTF-32) targets.
+set wchar_bytes 0
+gdb_test_multiple "print sizeof(wchar_t)" "" {
+    -re " = 2.*$gdb_prompt $" { set wchar_bytes 2 }
+    -re " = 4.*$gdb_prompt $" { set wchar_bytes 4 }
+}
+if { $wchar_bytes == 2 } {
+    set good_cs UTF-16
+    set bad_cs  UTF-32
+} elseif { $wchar_bytes == 4 } {
+    set good_cs UTF-32
+    set bad_cs  UTF-16
+} else {
+    return
+}
+
+gdb_test "set target-wide-charset $bad_cs" \
+    "Character set \"$bad_cs\" has a .-byte character width.*" \
+    "reject target-wide-charset $bad_cs"
+
+# The rejected setting must not stick.
+gdb_test "print single" "= 48879 L'\\\\xbeef'" \
+    "print wchar_t after rejected target-wide-charset"
+
+gdb_test_no_output "set target-wide-charset $good_cs" \
+    "accept target-wide-charset $good_cs"
+
+gdb_test "print single" "= 48879 L'\\\\xbeef'" \
+    "print wchar_t with explicit target-wide-charset"
-- 
2.51.2


^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2026-10-27 11:21 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-27 11:21 [PATCH v3] Fix crash when target-wide-charset doesn't match wchar_t size Aditya Vidyadhar Kamath

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