On Tue, Nov 29, 2016 at 11:08 PM, David Malcolm <dmalc...@redhat.com> wrote: > 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®rtested 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).
Ok. Thanks, Richard. > 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 >