https://sourceware.org/bugzilla/show_bug.cgi?id=34759

            Bug ID: 34759
           Summary: strings -U {locale,escape,...} crashes with a stack
                    overflow on inputs with many strings
           Product: binutils
           Version: 2.48 (HEAD)
            Status: UNCONFIRMED
          Severity: normal
          Priority: P2
         Component: binutils
          Assignee: unassigned at sourceware dot org
          Reporter: martin.mirchev at u dot nus.edu
  Target Milestone: ---

With any -U mode other than "default", strings recurses once per string it
prints: print_unicode_stream_body() (files, stdin) and print_unicode_buffer()
(section contents) end with a tail call to themselves, marked with FIXME
comments. GCC does not turn these into loops, even at -O2, so the stack
depth grows with the number of strings and a few tens of thousands of
strings exhaust the default 8 MiB stack:

$ wc -l /usr/share/dict/words
102401 /usr/share/dict/words
$ strings -U escape /usr/share/dict/words > /dev/null
Segmentation fault (core dumped)
$ strings -U locale /usr/share/dict/words > /dev/null
Segmentation fault (core dumped)

The same happens for large log files or any data with more than about
80,000 strings (e.g. printf 'AAAA\0' repeated 80000 times); -U default and
smaller inputs are fine. Seen with binutils 2.47 built with -O2 on x86_64.

Suggested fix: turn both tail calls into loops (update the parameters and
jump back to the start of the function), so the stack depth no longer
depends on the number of strings:

--- a/binutils/strings.c
+++ b/binutils/strings.c
@@ -810,6 +810,9 @@
       return;
     }

+  /* Each iteration displays at most one string and then continues with the
+     rest of the buffer.  */
+ next_string:
   if (buflen == 0)
     return;

@@ -900,8 +903,12 @@
   else
     putchar ('\n');

-  /* FIXME: Using tail recursion here is lazy programming...  */
-  print_unicode_buffer (filename, address + i, buffer + i, buflen - i);
+  /* Continue with the rest of the buffer.  Looping rather than recursing
+     keeps the stack depth independent of the number of strings.  */
+  address += i;
+  buffer += i;
+  buflen -= i;
+  goto next_string;
 }

 static int
@@ -940,6 +947,7 @@
      might be huge or it might time-locked (eg stdin).  So instead
      we go one byte at a time...  */

+ next_string:;
   file_ptr start_point = 0;
   unsigned int num_read = 0;
   unsigned int num_chars = 0;
@@ -1205,8 +1213,13 @@
     }

   if (c != EOF)
-    /* FIXME: Using tail recursion here is lazy, but it works.  */
-    print_unicode_stream_body (filename, address + num_read, stream,
putback_buf, num_putback, print_buf);
+    {
+      /* Continue with the rest of the stream.  Looping rather than
+        recursing keeps the stack depth independent of the number of
+        strings.  */
+      address += num_read;
+      goto next_string;
+    }
 }

 /* Display strings read in from STREAM.  Treat any UTF-8 encoded characters

With it the examples above complete, and the output is byte-identical to the
unpatched strings (run with an unlimited stack) for -U default, locale,
escape, invalid, hex and highlight, on files with -a, on stdin, and on ELF
section contents without -a.

This issue was found by an LLM-Enhanced Static Analysis Engine and verified
manually.

-- 
You are receiving this mail because:
You are on the CC list for the bug.

Reply via email to