changeset 17400:a10dbb80768a

quotearg: do not read beyond end of buffer * lib/quotearg.c (quotearg_buffer_restyled): Do not read beyond the end of an ARG for which no length was specified. With an N-byte quote string, (e.g., N is 3 in the fr_FR.UTF-8 locale), this function would read N-2 bytes beyond ARG's trailing NUL. This was triggered via coreutils' misc/sort-debug-keys.sh test and detected by running the test against a binary compiled with gcc-4.8.0's -fsanitize=address. * tests/test-quotearg-simple.c (main): Add a test to trigger the bug. * modules/quotearg-simple-tests (Files): Add tests/zerosize-ptr.h. Introduced via the 2000-01-15 commit, c4b7f3f8, "Quote multibyte characters correctly."
author Jim Meyering <meyering@fb.com>
date Sat, 11 May 2013 18:43:50 -0700
parents c2f3bfaf5fce
children 7afa5abee433
files ChangeLog lib/quotearg.c modules/quotearg-simple-tests tests/test-quotearg-simple.c
diffstat 4 files changed, 61 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,17 @@
+2013-05-11  Jim Meyering  <meyering@fb.com>
+
+	quotearg: do not read beyond end of buffer
+	* lib/quotearg.c (quotearg_buffer_restyled): Do not read beyond the
+	end of an ARG for which no length was specified.  With an N-byte
+	quote string, (e.g., N is 3 in the fr_FR.UTF-8 locale), this function
+	would read N-2 bytes beyond ARG's trailing NUL.  This was triggered
+	via coreutils' misc/sort-debug-keys.sh test and detected by running
+	the test against a binary compiled with gcc-4.8.0's -fsanitize=address.
+	* tests/test-quotearg-simple.c (main): Add a test to trigger the bug.
+	* modules/quotearg-simple-tests (Files): Add tests/zerosize-ptr.h.
+	Introduced via the 2000-01-15 commit, c4b7f3f8, "Quote multibyte
+	characters correctly."
+
 2013-05-11  Daiki Ueno  <ueno@gnu.org>
 
 	lock: work around pthread recursive mutexes bug in Mac OS X 10.6
--- a/lib/quotearg.c
+++ b/lib/quotearg.c
@@ -348,7 +348,12 @@
 
       if (backslash_escapes
           && quote_string_len
-          && i + quote_string_len <= argsize
+          && (i + quote_string_len
+              <= (argsize == SIZE_MAX && 1 < quote_string_len
+                  /* Use strlen only if we must: when argsize is SIZE_MAX,
+                     and when the quote string is more than 1 byte long.
+                     If we do call strlen, save the result.  */
+                  ? (argsize = strlen (arg)) : argsize))
           && memcmp (arg + i, quote_string, quote_string_len) == 0)
         {
           if (elide_outer_quotes)
--- a/modules/quotearg-simple-tests
+++ b/modules/quotearg-simple-tests
@@ -2,12 +2,18 @@
 tests/test-quotearg-simple.c
 tests/test-quotearg.h
 tests/macros.h
+tests/zerosize-ptr.h
 
 Depends-on:
 progname
 stdint
 
 configure.ac:
+dnl Check for prerequisites for memory fence checks.
+dnl FIXME: zerosize-ptr.h requires these: make a module for it
+gl_FUNC_MMAP_ANON
+AC_CHECK_HEADERS_ONCE([sys/mman.h])
+AC_CHECK_FUNCS_ONCE([mprotect])
 
 Makefile.am:
 TESTS += test-quotearg-simple
--- a/tests/test-quotearg-simple.c
+++ b/tests/test-quotearg-simple.c
@@ -29,6 +29,7 @@
 #include "localcharset.h"
 #include "progname.h"
 #include "macros.h"
+#include "zerosize-ptr.h"
 
 #include "test-quotearg.h"
 
@@ -297,6 +298,40 @@
                        ascii_only);
     }
 
+  {
+    /* Trigger the bug whereby quotearg_buffer would read beyond the NUL
+       that defines the end of the string being quoted.  Use an input
+       string whose NUL is the last byte before an unreadable page.  */
+    char *z = zerosize_ptr ();
+
+    if (z)
+      {
+        size_t q_len = 1024;
+        char *q = malloc (q_len + 1);
+        char buf[10];
+        memset (q, 'Q', q_len);
+        q[q_len] = 0;
+
+        /* Z points to the boundary between a readable/writable page
+           and one that is neither readable nor writable.  Position
+           our string so its NUL is at the end of the writable one.  */
+        char const *str = "____";
+        size_t s_len = strlen (str);
+        z -= s_len + 1;
+        memcpy (z, str, s_len + 1);
+
+        set_custom_quoting (NULL, q, q);
+        /* Whether this actually triggers a SEGV depends on the
+           implementation of memcmp: whether it compares only byte-at-
+           a-time, and from left to right (no SEGV) or some other way.  */
+        size_t n = quotearg_buffer (buf, sizeof buf, z, SIZE_MAX, NULL);
+        ASSERT (n == s_len + 2 * q_len);
+        ASSERT (memcmp (buf, q, sizeof buf) == 0);
+        free (q);
+      }
+  }
+
   quotearg_free ();
+
   return 0;
 }