Mirror of the gdb-patches mailing list
 help / color / mirror / Atom feed
* [PATCH] [gdb-17-branch, gdb/stabs] Fix out-of-bounds write in read_member_functions
@ 2026-09-02  9:05 Tom de Vries
  2026-09-02 12:57 ` Simon Marchi
  0 siblings, 1 reply; 3+ messages in thread
From: Tom de Vries @ 2026-09-02  9:05 UTC (permalink / raw)
  To: gdb-patches

[ This is related to the STABS reader, which has been removed in gdb 18, so
the fix is on top of gdb-17-branch. ]

CVE-2026-13732 reports:
...
gdb: out-of-bounds write in stabs parser read_member_functions() via crafted elf

A flaw was found in GDB's STABS debug format parser. The read_member_functions()
function in gdb/stabsread.c contains a linked list removal bug in the code that
separates destructor and non-destructor member functions of C++ classes. The bug
causes the destructor entries to remain in the main function list while the list
length counter is decremented, resulting in an out-of-bounds write when the
function list is copied to its final allocated array. An attacker can craft an
ELF binary with malicious .stab and .stabstr sections that triggers this
out-of-bounds write when a user opens the file in GDB and performs any
symbol-inspection operation such as setting a breakpoint. The inferior process
does not need to be executed. Under controlled conditions, this was demonstrated
to achieve execution of arbitrary commands within the GDB process.
...

AFAIU from gdb/SECURITY.txt:
...
  There are known bugs in GDB related to loading malformed executables
  and parsing the debug information, a consequence of these bugs is
  that a malicious program could trigger undefined behavior in GDB,
  which could be used to trigger arbitrary code execution.

  Given these risks, the advice of the GDB project is that, when using
  GDB with an untrusted binary, always do so in a secure, sandboxed
  environment.

  As there are already known bugs in GDB relating to undefined
  behavior triggered from malformed programs, further bugs in this
  area should still be reported, but are unlikely to be given high
  priority.  Bugs in GDB that are triggered by well-formed programs
  should also be reported, and are likely to be treated as higher
  priority as these are more likely to impact normal use of GDB.
...
this is not a high priority.  Still, it's worthwhile fixing this.

The problem is single-linked list update logic in this loop:
...
              tmp_sublist = sublist;
              last_sublist = NULL;
              i = 0;
              while (tmp_sublist != NULL)
                {
                  if (!is_destructor_name (tmp_sublist->fn_field.physname))
                    {
                      tmp_sublist = tmp_sublist->next;
                      continue;
                    }

                  destr_fnlist->fn_fieldlist.fn_fields[i++]
                    = tmp_sublist->fn_field;
                  if (last_sublist)
                    last_sublist->next = tmp_sublist->next;
                  else
                    sublist = tmp_sublist->next;
                  last_sublist = tmp_sublist;
                  tmp_sublist = tmp_sublist->next;
                }
...

I wrote a stand alone reproducer containing fix and unit test:
...
 #include <stdio.h>
 #include <assert.h>

 #ifndef FIX
 #define FIX 0
 #endif

 #ifndef VERBOSE
 #define VERBOSE 0
 #endif

struct s
{
  int i;
  struct s *next;
};

static int test_mode = 0;
static int max_test_mode = 11;

static int
test (int i)
{
  switch (test_mode)
    {
    case 0:
      return 0;
    case 1:
      return 1;
    case 2:
      return i % 2 == 0;
    case 3:
      return i % 2 == 1;
    case 4:
      return i == 1;
    case 5:
      return i != 1;
    case 6:
      return i == 6;
    case 7:
      return i != 6;
    case 8:
      return i >= 5;
    case 9:
      return !(i >= 5);
    case 10:
      return (i == 3 || i == 4);
    case 11:
      return !(i == 3 || i == 4);
    default:
      assert (0);
    }
}

static struct s *
filter (struct s *sublist)
{
  struct s *tmp_sublist;
  struct s *last_sublist;

  /* Current element = start of list.
     Previous element = before start of list == NULL.

     Legend:
     l: last_sublist (Previous element)
     t: tmp_sublist  (Current element)
     s: sublist      (Head of list)
     x: NULL

     After:
       l   s/t
       x   1 -> 2 -> 3
  */
  tmp_sublist = sublist;
  last_sublist = NULL;
  while (tmp_sublist != NULL)
    {
      if (test (tmp_sublist->i))
	{
	  /* Keep element.
	     Required: Update both pointers to their next element.
	     Bug: last_sublist is not updated.

	     Before:
	       l   s/t
	       x   1 -> 2 -> 3
	     After (without fix):
	       l   s    t
	       x   1 -> 2 -> 3
	     After (with fix):
	           s/l  t
	           1 -> 2 -> 3
	   */
	  if (FIX)
	    last_sublist = tmp_sublist;
	  tmp_sublist = tmp_sublist->next;
	  continue;
	}

      if (last_sublist)
	/* Remove element other than first.
	   Before:
	     s/l  t
	     1 -> 2 -> 3
	   After:
	     s/l       t
	     1 -> 3    2
	 */
	last_sublist->next = tmp_sublist->next;
      else
	/* Remove first element.
	   Before:
	     l    s/t
	     x    1 -> 2 -> 3
	   After:
	     l    s         t
	     x    2 -> 3    1
	 */
	sublist = tmp_sublist->next;

      /* Update current element.
	 Required: Because we just removed the current element, we're
	           not updating the previous element.
	 Bug: last_sublist is updated.

	 Before:
	   s/l       t
	   1 -> 3    2
	 After (without fix):
	   s    t    l
	   1 -> 3    2
	 After (with fix):
	   s/l  t
	   1 -> 3
      */
      if (!FIX)
	last_sublist = tmp_sublist;
      tmp_sublist = tmp_sublist->next;
    }

  return sublist;
}

struct s s1, s2, s3, s4, s5, s6;

static int
init (int i)
{
  s1.i = 1;
  s2.i = 2;
  s3.i = 3;
  s4.i = 4;
  s5.i = 5;
  s6.i = 6;

  s1.next = NULL;
  s2.next = &s1;
  s3.next = &s2;
  s4.next = &s3;
  s5.next = &s4;
  s6.next = &s5;

  switch (i)
    {
    case 0:
      return 6;
    case 1:
      s5.next = NULL;
      return 2;
    case 2:
      s6.next = NULL;
      return 1;
    default:
      assert (false);
    }
}

int
main ()
{
  assert (filter (NULL) == NULL);

  for (int i = 0; i < 3; ++i)
    for (test_mode = 0; test_mode <= max_test_mode; ++test_mode)
      {
	int len = init (i);

	struct s *res = filter (&s6);

	if (VERBOSE)
	  {
	    printf ("EXPECTED: ");
	    for (int i = 6; i > 6 - len; --i)
	      if (test (i))
		printf (" %d", i);
	    printf ("\n");

	    printf ("ACTUAL  : ");
	    for (struct s *elem = res; elem != NULL; elem = elem->next)
	      printf (" %d", elem->i);
	    printf ("\n");
	  }

	for (struct s *elem = res; elem != NULL; elem = elem->next)
	  assert (test (elem->i));

	struct s *elem = res;
	for (int i = 6; i > 6 - len; --i)
	  if (test (i))
	    {
	      assert (i == elem->i);
	      elem = elem->next;
	    }
	assert (elem == NULL);
      }

  return 0;
}
...

This patch applies the same fix in read_member_functions.

Relevant links:
- http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2026-13732
- https://www.cve.org/CVERecord?id=CVE-2026-13732
- https://access.redhat.com/security/cve/CVE-2026-13732
- https://bugzilla.redhat.com/show_bug.cgi?id=2494416
---
 gdb/stabsread.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/gdb/stabsread.c b/gdb/stabsread.c
index 2d1411e9146..6cc3c5c7e02 100644
--- a/gdb/stabsread.c
+++ b/gdb/stabsread.c
@@ -5112,6 +5112,7 @@ read_member_functions (struct stab_field_info *fip, const char **pp,
 		{
 		  if (!is_destructor_name (tmp_sublist->fn_field.physname))
 		    {
+		      last_sublist = tmp_sublist;
 		      tmp_sublist = tmp_sublist->next;
 		      continue;
 		    }
@@ -5122,7 +5123,6 @@ read_member_functions (struct stab_field_info *fip, const char **pp,
 		    last_sublist->next = tmp_sublist->next;
 		  else
 		    sublist = tmp_sublist->next;
-		  last_sublist = tmp_sublist;
 		  tmp_sublist = tmp_sublist->next;
 		}
 

base-commit: 2636da31af44fab38c22cee0fe771761173ea64b
-- 
2.51.0


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

end of thread, other threads:[~2026-09-09 13:11 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-02  9:05 [PATCH] [gdb-17-branch, gdb/stabs] Fix out-of-bounds write in read_member_functions Tom de Vries
2026-09-02 12:57 ` Simon Marchi
2026-09-09 13:10   ` Tom de Vries

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