This test failure was seen on Solaris 8/SPARC: > FAIL: test-u8-strncat Since u8_strncat is just a wrapper around strncat, this indicates a bug in the system's strncat function. In fact, strncat(dest,src,n) appears to always access at least n+1 bytes starting at src, when src[0..n-1] don't contain a NUL byte.
Like the similar glibc bug for memchr, this has the potential to crash any program that uses this function. I'm therefore adding a replacement module. 2010-04-05 Bruno Haible <br...@clisp.org> New module 'strncat'. * lib/string.in.h (strncat): New declaration. * lib/strncat.c: New file, based on lib/unistr/u-strncat.h. * m4/strncat.m4: New file, based on m4/memchr.m4. * modules/strncat: New file. * m4/string_h.m4 (gl_HEADER_STRING_H_BODY): Also check whether strncat is declared. (gl_HEADER_STRING_H_DEFAULTS): Initialize GNULIB_STRNCAT, REPLACE_STRNCAT. * modules/string (Makefile.am): Substitute GNULIB_STRNCAT, REPLACE_STRNCAT. * doc/posix-functions/strncat.texi: Mention the Solaris bug and the new module. * tests/test-string-c++.cc: Check signature of strncat. ================================ lib/strncat.c ================================ /* Concatenate strings. Copyright (C) 1999, 2002, 2006, 2010 Free Software Foundation, Inc. Written by Bruno Haible <br...@clisp.org>, 2002. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include <config.h> /* Specification. */ #include <string.h> char * strncat (char *dest, const char *src, size_t n) { char *destptr = dest + strlen (dest); for (; n > 0 && (*destptr = *src) != '\0'; src++, destptr++, n--) ; if (n == 0) *destptr = '\0'; return dest; } ================================ m4/strncat.m4 ================================ # strncat.m4 serial 1 dnl Copyright (C) 2002-2004, 2009-2010 Free Software Foundation, Inc. dnl This file is free software; the Free Software Foundation dnl gives unlimited permission to copy and/or distribute it, dnl with or without modifications, as long as this notice is preserved. AC_DEFUN_ONCE([gl_FUNC_STRNCAT], [ AC_REQUIRE([gl_HEADER_STRING_H_DEFAULTS]) AC_REQUIRE([AC_PROG_CC]) AC_REQUIRE([AC_CANONICAL_HOST]) dnl for cross-compiles dnl Check for prerequisites for memory fence checks. gl_FUNC_MMAP_ANON AC_CHECK_HEADERS_ONCE([sys/mman.h]) AC_CHECK_FUNCS_ONCE([mprotect]) dnl Detect bug in Solaris 8..10 on SPARC: dnl strncat should not dereference more than n bytes, but always dereferences dnl n+1 bytes if the first n bytes don't contain a NUL byte. dnl Assume that strncat works on platforms that lack mprotect. AC_CACHE_CHECK([whether strncat works], [gl_cv_func_strncat_works], [AC_RUN_IFELSE([AC_LANG_PROGRAM([[ #include <string.h> #if HAVE_SYS_MMAN_H # include <fcntl.h> # include <unistd.h> # include <sys/types.h> # include <sys/mman.h> # ifndef MAP_FILE # define MAP_FILE 0 # endif #endif ]], [[ char *fence = NULL; #if HAVE_SYS_MMAN_H && HAVE_MPROTECT # if HAVE_MAP_ANONYMOUS const int flags = MAP_ANONYMOUS | MAP_PRIVATE; const int fd = -1; # else /* !HAVE_MAP_ANONYMOUS */ const int flags = MAP_FILE | MAP_PRIVATE; int fd = open ("/dev/zero", O_RDONLY, 0666); if (fd >= 0) # endif { int pagesize = getpagesize (); char *two_pages = (char *) mmap (NULL, 2 * pagesize, PROT_READ | PROT_WRITE, flags, fd, 0); if (two_pages != (char *)(-1) && mprotect (two_pages + pagesize, pagesize, PROT_NONE) == 0) fence = two_pages + pagesize; } #endif if (fence) { char dest[8]; dest[0] = '*'; dest[1] = 'a'; dest[2] = '\0'; dest[3] = 'w'; dest[4] = 'x'; dest[5] = 'y'; dest[6] = 'z'; *(fence - 3) = '7'; *(fence - 2) = '2'; *(fence - 1) = '9'; if (strncat (dest + 1, fence - 3, 3) != dest + 1) return 1; if (dest[0] != '*') return 2; if (dest[1] != 'a' || dest[2] != '7' || dest[3] != '2' || dest[4] != '9' || dest[5] != '\0') return 3; if (dest[6] != 'z') return 4; } return 0; ]])], [gl_cv_func_strncat_works=yes], [gl_cv_func_strncat_works=no], [ case "$host_os" in # Guess no on Solaris. solaris*) gl_cv_func_strncat_works="guessing no";; # Guess yes otherwise. *) gl_cv_func_strncat_works="guessing yes";; esac ]) ]) case "$gl_cv_func_strncat_works" in *yes) ;; *) REPLACE_STRNCAT=1 AC_LIBOBJ([strncat]) gl_PREREQ_STRNCAT ;; esac ]) # Prerequisites of lib/strncat.c. AC_DEFUN([gl_PREREQ_STRNCAT], [ : ]) =============================== modules/strncat =============================== Description: strncat() function: append part of a string to a string. Files: lib/strncat.c m4/strncat.m4 m4/mmap-anon.m4 Depends-on: string configure.ac: gl_FUNC_STRNCAT gl_STRING_MODULE_INDICATOR([strncat]) Makefile.am: Include: <string.h> License: LGPLv2+ Maintainer: Bruno Haible =============================================================================== --- doc/posix-functions/strncat.texi.orig Mon Apr 5 21:08:09 2010 +++ doc/posix-functions/strncat.texi Mon Apr 5 19:52:18 2010 @@ -4,10 +4,13 @@ POSIX specification: @url{http://www.opengroup.org/onlinepubs/9699919799/functions/strncat.html} -Gnulib module: --- +Gnulib module: strncat Portability problems fixed by Gnulib: @itemize +...@item +This function dereferences too much memory on some platforms: +Solaris 10 on SPARC. @end itemize Portability problems not fixed by Gnulib: --- lib/string.in.h.orig Mon Apr 5 21:08:09 2010 +++ lib/string.in.h Mon Apr 5 20:05:32 2010 @@ -320,6 +320,28 @@ # endif #endif +/* Append no more than N characters from SRC onto DEST. */ +#if @GNULIB_STRNCAT@ +# if @REPLACE_STRNCAT@ +# if !(defined __cplusplus && defined GNULIB_NAMESPACE) +# undef strncat +# define strncat rpl_strncat +# endif +_GL_FUNCDECL_RPL (strncat, char *, (char *dest, const char *src, size_t n) + _GL_ARG_NONNULL ((1, 2))); +_GL_CXXALIAS_RPL (strncat, char *, (char *dest, const char *src, size_t n)); +# else +_GL_CXXALIAS_SYS (strncat, char *, (char *dest, const char *src, size_t n)); +# endif +_GL_CXXALIASWARN (strncat); +#elif defined GNULIB_POSIXCHECK +# undef strncat +# if HAVE_RAW_DECL_STRNCAT +_GL_WARN_ON_USE (strncat, "strncat is unportable - " + "use gnulib module strncat for portability"); +# endif +#endif + /* Return a newly allocated copy of at most N bytes of STRING. */ #if @GNULIB_STRNDUP@ # if @REPLACE_STRNDUP@ --- m4/string_h.m4.orig Mon Apr 5 21:08:09 2010 +++ m4/string_h.m4 Mon Apr 5 20:35:52 2010 @@ -5,7 +5,7 @@ # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. -# serial 15 +# serial 16 # Written by Paul Eggert. @@ -26,8 +26,10 @@ dnl corresponding gnulib module is not in use, and which is not dnl guaranteed by C89. gl_WARN_ON_USE_PREPARE([[#include <string.h> - ]], [memmem mempcpy memrchr rawmemchr stpcpy stpncpy strchrnul strdup - strndup strnlen strpbrk strsep strcasestr strtok_r strsignal strverscmp]) + ]], + [memmem mempcpy memrchr rawmemchr stpcpy stpncpy strchrnul strdup + strncat strndup strnlen strpbrk strsep strcasestr strtok_r strsignal + strverscmp]) ]) AC_DEFUN([gl_STRING_MODULE_INDICATOR], @@ -50,6 +52,7 @@ GNULIB_STPNCPY=0; AC_SUBST([GNULIB_STPNCPY]) GNULIB_STRCHRNUL=0; AC_SUBST([GNULIB_STRCHRNUL]) GNULIB_STRDUP=0; AC_SUBST([GNULIB_STRDUP]) + GNULIB_STRNCAT=0; AC_SUBST([GNULIB_STRNCAT]) GNULIB_STRNDUP=0; AC_SUBST([GNULIB_STRNDUP]) GNULIB_STRNLEN=0; AC_SUBST([GNULIB_STRNLEN]) GNULIB_STRPBRK=0; AC_SUBST([GNULIB_STRPBRK]) @@ -100,6 +103,7 @@ REPLACE_STRSTR=0; AC_SUBST([REPLACE_STRSTR]) REPLACE_STRCASESTR=0; AC_SUBST([REPLACE_STRCASESTR]) REPLACE_STRERROR=0; AC_SUBST([REPLACE_STRERROR]) + REPLACE_STRNCAT=0; AC_SUBST([REPLACE_STRNCAT]) REPLACE_STRNDUP=0; AC_SUBST([REPLACE_STRNDUP]) REPLACE_STRSIGNAL=0; AC_SUBST([REPLACE_STRSIGNAL]) REPLACE_STRTOK_R=0; AC_SUBST([REPLACE_STRTOK_R]) --- modules/string.orig Mon Apr 5 21:08:09 2010 +++ modules/string Mon Apr 5 20:35:38 2010 @@ -50,6 +50,7 @@ -e 's|@''GNULIB_STPNCPY''@|$(GNULIB_STPNCPY)|g' \ -e 's|@''GNULIB_STRCHRNUL''@|$(GNULIB_STRCHRNUL)|g' \ -e 's|@''GNULIB_STRDUP''@|$(GNULIB_STRDUP)|g' \ + -e 's|@''GNULIB_STRNCAT''@|$(GNULIB_STRNCAT)|g' \ -e 's|@''GNULIB_STRNDUP''@|$(GNULIB_STRNDUP)|g' \ -e 's|@''GNULIB_STRNLEN''@|$(GNULIB_STRNLEN)|g' \ -e 's|@''GNULIB_STRPBRK''@|$(GNULIB_STRPBRK)|g' \ @@ -86,6 +87,7 @@ -e 's|@''REPLACE_STRDUP''@|$(REPLACE_STRDUP)|g' \ -e 's|@''REPLACE_STRSTR''@|$(REPLACE_STRSTR)|g' \ -e 's|@''REPLACE_STRERROR''@|$(REPLACE_STRERROR)|g' \ + -e 's|@''REPLACE_STRNCAT''@|$(REPLACE_STRNCAT)|g' \ -e 's|@''REPLACE_STRNDUP''@|$(REPLACE_STRNDUP)|g' \ -e 's|@''REPLACE_STRSIGNAL''@|$(REPLACE_STRSIGNAL)|g' \ -e 's|@''REPLACE_STRTOK_R''@|$(REPLACE_STRTOK_R)|g' \ --- tests/test-string-c++.cc.orig Mon Apr 5 21:08:09 2010 +++ tests/test-string-c++.cc Mon Apr 5 20:32:09 2010 @@ -68,6 +68,11 @@ SIGNATURE_CHECK (GNULIB_NAMESPACE::strdup, char *, (char const *)); #endif +#if GNULIB_TEST_STRNCAT +SIGNATURE_CHECK (GNULIB_NAMESPACE::strncat, char *, + (char *, const char *, size_t)); +#endif + #if GNULIB_TEST_STRNDUP SIGNATURE_CHECK (GNULIB_NAMESPACE::strndup, char *, (char const *, size_t)); #endif