libiberty's implementations of strndup and xstrndup call strlen on
the input string, and hence can read past the end of the input buffer
if it isn't zero-terminated (such as is the case in PR c/78498, where
the input string is from the input.c line cache).

This patch converts them to use strnlen instead (as glibc's
implementation of them does), avoiding reading more than n bytes
from the input buffer.  strnlen is provided by libiberty.

Successfully bootstrapped&regrtested on x86_64-pc-linux-gnu;
adds 6 PASS results to gcc.sum.

The patch also adds some selftests for this case, which showed
the problem and the fix nicely via "make selftest-valgrind".
Unfortunately I had to put these selftests within the gcc
subdirectory, rather than libiberty, since selftest.h is C++ and
is itself in the gcc subdirectory.  If that's unacceptable, I can
just drop the selftest.c part of the patch (or we somehow support
selftests from within libiberty itself, though I'm not sure how to
do that, if libiberty is meant as a cross-platform compat library,
rather than as a base support layer; the simplest thing to do seemed
to be to put them in the "gcc" subdir).

gcc/ChangeLog:
        PR c/78498
        * selftest.c (selftest::assert_strndup_eq): New function.
        (selftest::test_strndup): New function.
        (selftest::test_libiberty): New function.
        (selftest::selftest_c_tests): Call test_libiberty.

gcc/testsuite/ChangeLog:
        PR c/78498
        * gcc.dg/format/pr78494.c: New test case.

libiberty/ChangeLog:
        PR c/78498
        * strndup.c (strlen): Delete decl.
        (strnlen): Add decl.
        (strndup): Call strnlen rather than strlen.
        * xstrndup.c: Likewise.
---
 gcc/selftest.c                        | 48 +++++++++++++++++++++++++++++++++++
 gcc/testsuite/gcc.dg/format/pr78494.c | 12 +++++++++
 libiberty/strndup.c                   |  7 ++---
 libiberty/xstrndup.c                  |  5 +---
 4 files changed, 63 insertions(+), 9 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/format/pr78494.c

diff --git a/gcc/selftest.c b/gcc/selftest.c
index 2a729be..6df73c2 100644
--- a/gcc/selftest.c
+++ b/gcc/selftest.c
@@ -198,6 +198,53 @@ read_file (const location &loc, const char *path)
   return result;
 }
 
+/* Selftests for libiberty.  */
+
+/* Verify that both strndup and xstrndup generate EXPECTED
+   when called on SRC and N.  */
+
+static void
+assert_strndup_eq (const char *expected, const char *src, size_t n)
+{
+  char *buf = strndup (src, n);
+  if (buf)
+    ASSERT_STREQ (expected, buf);
+  free (buf);
+
+  buf = xstrndup (src, n);
+  ASSERT_STREQ (expected, buf);
+  free (buf);
+}
+
+/* Verify that strndup and xstrndup work as expected.  */
+
+static void
+test_strndup ()
+{
+  assert_strndup_eq ("", "test", 0);
+  assert_strndup_eq ("t", "test", 1);
+  assert_strndup_eq ("te", "test", 2);
+  assert_strndup_eq ("tes", "test", 3);
+  assert_strndup_eq ("test", "test", 4);
+  assert_strndup_eq ("test", "test", 5);
+
+  /* Test on an string without zero termination.  */
+  const char src[4] = {'t', 'e', 's', 't'};
+  assert_strndup_eq ("", src, 0);
+  assert_strndup_eq ("t", src, 1);
+  assert_strndup_eq ("te", src, 2);
+  assert_strndup_eq ("tes", src, 3);
+  assert_strndup_eq ("test", src, 4);
+}
+
+/* Run selftests for libiberty.  */
+
+static void
+test_libiberty ()
+{
+  test_strndup ();
+}
+
 /* Selftests for the selftest system itself.  */
 
 /* Sanity-check the ASSERT_ macros with various passing cases.  */
@@ -245,6 +292,7 @@ test_read_file ()
 void
 selftest_c_tests ()
 {
+  test_libiberty ();
   test_assertions ();
   test_named_temp_file ();
   test_read_file ();
diff --git a/gcc/testsuite/gcc.dg/format/pr78494.c 
b/gcc/testsuite/gcc.dg/format/pr78494.c
new file mode 100644
index 0000000..4b53a68
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/format/pr78494.c
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -Wall -Wextra -fdiagnostics-show-caret" } */
+
+void f (void)
+{
+  __builtin_printf ("%i", ""); /* { dg-warning "expects argument of type" } */
+/* { dg-begin-multiline-output "" }
+   __builtin_printf ("%i", "");
+                      ~^   ~~
+                      %s
+   { dg-end-multiline-output "" } */
+}
diff --git a/libiberty/strndup.c b/libiberty/strndup.c
index 9e9b4e2..4556b96 100644
--- a/libiberty/strndup.c
+++ b/libiberty/strndup.c
@@ -33,7 +33,7 @@ memory was available.  The result is always NUL terminated.
 #include "ansidecl.h"
 #include <stddef.h>
 
-extern size_t  strlen (const char*);
+extern size_t  strnlen (const char *s, size_t maxlen);
 extern PTR     malloc (size_t);
 extern PTR     memcpy (PTR, const PTR, size_t);
 
@@ -41,10 +41,7 @@ char *
 strndup (const char *s, size_t n)
 {
   char *result;
-  size_t len = strlen (s);
-
-  if (n < len)
-    len = n;
+  size_t len = strnlen (s, n);
 
   result = (char *) malloc (len + 1);
   if (!result)
diff --git a/libiberty/xstrndup.c b/libiberty/xstrndup.c
index 0a41f60..c3d2d83 100644
--- a/libiberty/xstrndup.c
+++ b/libiberty/xstrndup.c
@@ -48,10 +48,7 @@ char *
 xstrndup (const char *s, size_t n)
 {
   char *result;
-  size_t len = strlen (s);
-
-  if (n < len)
-    len = n;
+  size_t len = strnlen (s, n);
 
   result = XNEWVEC (char, len + 1);
 
-- 
1.8.5.3

Reply via email to