Date: Tue, 17 Dec 2002 18:12:45 +0000 From: "Richard Dawe" Sender: rich AT phekda DOT freeserve DOT co DOT uk To: djgpp-workers AT delorie DOT com X-Mailer: Emacs 21.3.50 (via feedmail 8.3.emacs20_6 I) and Blat ver 1.8.6 Subject: strlcat, strlcpy [PATCH] Message-Id: Reply-To: djgpp-workers AT delorie DOT com Hello. Below is a patch to add the strlcat and strlcpy functions to DJGPP. These have a better interface than their strn* equivalents, which makes it easier to write code without buffer overflows. These interfaces were designed by the OpenBSD team. You can read the original paper here: http://www.openbsd.org/papers/strlcpy-paper.ps OK to commit? Bye, Rich =] Index: include/string.h =================================================================== RCS file: /cvs/djgpp/djgpp/include/string.h,v retrieving revision 1.5 diff -p -c -3 -r1.5 string.h *** include/string.h 5 Dec 2000 14:05:53 -0000 1.5 --- include/string.h 17 Dec 2002 18:09:26 -0000 *************** *** 1,3 **** --- 1,4 ---- + /* Copyright (C) 2002 DJ Delorie, see COPYING.DJ for details */ /* Copyright (C) 2000 DJ Delorie, see COPYING.DJ for details */ /* Copyright (C) 1998 DJ Delorie, see COPYING.DJ for details */ /* Copyright (C) 1995 DJ Delorie, see COPYING.DJ for details */ *************** char * rindex(const char *_string, int *** 62,67 **** --- 63,70 ---- char * stpcpy(char *_dest, const char *_src); char * stpncpy(char *_dest, const char *_src, size_t _n); char * strdup(const char *_s); + size_t strlcat(char *_dest, const char *_src, size_t _size); + size_t strlcpy(char *_dest, const char *_src, size_t _size); char * strlwr(char *_s); int strcasecmp(const char *_s1, const char *_s2); int stricmp(const char *_s1, const char *_s2); Index: src/libc/compat/string/makefile =================================================================== RCS file: /cvs/djgpp/djgpp/src/libc/compat/string/makefile,v retrieving revision 1.6 diff -p -c -3 -r1.6 makefile *** src/libc/compat/string/makefile 17 Oct 2002 23:00:25 -0000 1.6 --- src/libc/compat/string/makefile 17 Dec 2002 18:09:26 -0000 *************** *** 1,3 **** --- 1,4 ---- + # Copyright (C) 2002 DJ Delorie, see COPYING.DJ for details # Copyright (C) 2000 DJ Delorie, see COPYING.DJ for details # Copyright (C) 1999 DJ Delorie, see COPYING.DJ for details # Copyright (C) 1995 DJ Delorie, see COPYING.DJ for details *************** SRC += stpncpy.c *** 11,16 **** --- 12,19 ---- SRC += strcasec.S SRC += strdup.c SRC += stricmp.c + SRC += strlcat.c + SRC += strlcpy.c SRC += strlwr.c SRC += strncase.S SRC += strnicmp.c Index: src/docs/kb/wc204.txi =================================================================== RCS file: /cvs/djgpp/djgpp/src/docs/kb/wc204.txi,v retrieving revision 1.128 diff -p -c -3 -r1.128 wc204.txi *** src/docs/kb/wc204.txi 15 Dec 2002 09:30:52 -0000 1.128 --- src/docs/kb/wc204.txi 17 Dec 2002 18:09:40 -0000 *************** Windows 2000 and XP. *** 820,822 **** --- 820,826 ---- The functions @code{imaxabs}, @code{imaxdiv}, @code{strtoimax} and @code{strtoumax} were added. The @code{PRI} and @code{SCN} families of macros were added. + + @findex strlcat + @findex strlcpy + The functions @code{strlcat} and @code{strlcpy} were added. Index: tests/libc/compat/string/makefile =================================================================== RCS file: /cvs/djgpp/djgpp/tests/libc/compat/string/makefile,v retrieving revision 1.2 diff -p -c -3 -r1.2 makefile *** tests/libc/compat/string/makefile 3 Aug 2000 11:17:12 -0000 1.2 --- tests/libc/compat/string/makefile 17 Dec 2002 18:09:41 -0000 *************** TOP=../.. *** 3,7 **** --- 3,9 ---- SRC += ffs.c SRC += stpcpy.c SRC += stpncpy.c + SRC += t-stlcat.c + SRC += t-stlcpy.c include $(TOP)/../makefile.inc *** /dev/null Tue Dec 17 18:10:21 2002 --- src/libc/compat/string/strlcat.c Tue Dec 10 21:05:14 2002 *************** *** 0 **** --- 1,26 ---- + /* Copyright (C) 2002 DJ Delorie, see COPYING.DJ for details */ + #include + + size_t + strlcat (char *dst, const char *src, size_t size) + { + const size_t srclen = strlen(src); + const size_t dstlen = strlen(dst); + const size_t ret = srclen + dstlen; + + /* No space at all or dst is already full. size should be > dstlen, + * otherwise it's likely we have an invalid string. */ + if (!size || (dstlen >= size)) + return(ret); + + if (ret < size) { + /* Enough space - just copy the string. */ + strcpy(dst + dstlen, src); + } else { + /* Truncate the string to fit. */ + memcpy(dst + dstlen, src, size - dstlen - 1); + dst[size - 1] = '\0'; + } + + return(ret); + } *** /dev/null Tue Dec 17 18:10:21 2002 --- src/libc/compat/string/strlcat.txh Tue Dec 17 17:07:50 2002 *************** *** 0 **** --- 1,50 ---- + @node strlcat, string + @subheading Syntax + + @example + #include + + size_t strlcat (char *dest, const char *src, size_t size); + @end example + + @subheading Description + + Concatenate characters from @var{src} to @var{dest} and nul-terminate + the resulting string. As much of @var{src} is copied into @var{dest} + as there is space for. + + @var{size} should be the size of the destination string buffer @var{dest} + plus the space for the nul-terminator. @var{size} may be computed + in many cases using the @code{sizeof} operator. + + @code{strlcat} may be used as a less ambiguous alternative + to @code{strncat} (@pxref{strncat}). Unlike @code{strncat}, + @code{strlcat} @emph{always} nul-terminates the destination @var{dest} + for non-zero sizes @var{size}. + + @subheading Return Value + + The length of the string that @code{strlcat} tried to create is returned, + whether or not @code{strlcat} could store it in @var{dest}. If all + of @var{src} was concatenated to @var{dst}, the return value will be less + than @var{size}. + + @subheading Portability + + @portability !ansi, !posix + + @subheading Example + + The following example shows how you can check that + the destination string buffer was large enough to store + the source string concatenated to the destination string. + In this case @code{somestring} is truncated, when it is concatenated + to @code{buf}. + + @example + const char somestring[] = "bar"; + char buf[5] = "foo"; + + if (strlcat(buf, somestring, sizeof(buf)) >= sizeof(buf)) + puts("somestring was truncated, when concatenating to buf."); + @end example *** /dev/null Tue Dec 17 18:10:21 2002 --- src/libc/compat/string/strlcpy.c Tue Dec 10 21:05:32 2002 *************** *** 0 **** --- 1,23 ---- + /* Copyright (C) 2002 DJ Delorie, see COPYING.DJ for details */ + #include + + size_t + strlcpy (char *dst, const char *src, size_t size) + { + const size_t srclen = strlen(src); + const size_t ret = srclen; + + if (!size) + return(ret); /* No space at all. */ + + if (srclen < size) { + /* Enough space - just copy the string. */ + strcpy(dst, src); + } else { + /* Truncate the string to fit. */ + memcpy(dst, src, size - 1); + dst[size - 1] = '\0'; + } + + return(ret); + } *** /dev/null Tue Dec 17 18:10:22 2002 --- src/libc/compat/string/strlcpy.txh Tue Dec 17 16:57:06 2002 *************** *** 0 **** --- 1,46 ---- + @node strlcpy, string + @subheading Syntax + + @example + #include + + size_t strlcpy (char *dest, const char *src, size_t size); + @end example + + @subheading Description + + Copy characters from @var{src} to @var{dest} and nul-terminate + the resulting string. Up to @code{@var{size} - 1} characters are + copied to @var{dest}. + + @var{size} should be the size of the destination string buffer @var{dest} + plus the space for the nul-terminator. @var{size} may be computed + in many cases using the @code{sizeof} operator. + + @code{strlcpy} is a less ambiguous version of @code{strncpy} + (@pxref{strncpy}). Unlike @code{strncpy}, @code{strlcpy} @emph{always} + nul-terminates the destination @var{dest} for non-zero sizes @var{size}. + + @subheading Return Value + + The length of the string that @code{strlcpy} tried to create is returned, + whether or not @code{strlcpy} could store it in @var{dest}. If all + of @var{src} was copied, the return value will be less than @var{size}. + + @subheading Portability + + @portability !ansi, !posix + + @subheading Example + + The following example shows how you can check that + the destination string buffer was large enough to store the source string. + In this case @code{somestring} is truncated to fit into @code{buf}. + + @example + const char somestring[] = "foo"; + char buf[3]; + + if (strlcpy(buf, somestring, sizeof(buf)) >= sizeof(buf)) + puts("somestring was truncated, when copying to buf."); + @end example *** /dev/null Tue Dec 17 18:10:22 2002 --- tests/libc/compat/string/t-stlcat.c Tue Dec 17 18:08:06 2002 *************** *** 0 **** --- 1,82 ---- + #include + #include + #include + #include + + #define FILL_CHAR 'X' + + static void + check_too_small (const char *somestring, + char *buf2, const size_t buf2size, + const size_t fakesize) + { + const size_t len_somestring = strlen(somestring); + int i; + + assert(fakesize < buf2size); + + memset(buf2, FILL_CHAR, buf2size); + strcpy(buf2, somestring); + + assert(strlcat(buf2, somestring, fakesize) >= fakesize); + assert(strncmp(buf2, somestring, len_somestring) == 0); + assert(strncmp(buf2 + len_somestring, somestring, + fakesize - len_somestring - 1) == 0); + assert(buf2[fakesize - 1] == '\0'); + for (i = fakesize; i < buf2size; i++) { + assert(buf2[i] == FILL_CHAR); + } + } + + int + main (void) + { + const char somestring[] = "somestring"; + const size_t len_somestring = strlen(somestring); + char buf[22]; /* More than big enough to contain two somestrings. */ + char buf2[20]; /* Just too small to contain two somestrings. */ + int i; + + /* Try a zero size. Check that the buffer is untouched. */ + memset(buf, FILL_CHAR, sizeof(buf)); + buf[0] = '\0'; /* Make buf a valid string. */ + + assert(strlcat(buf, somestring, 0) == strlen(somestring)); + for (i = 1; i < sizeof(buf); i++) { + assert(buf[i] == FILL_CHAR); + } + + /* Check that two strings fit into buf. Check that it hasn't overrun. */ + memset(buf, FILL_CHAR, sizeof(buf)); + strcpy(buf, somestring); + + assert(strlcat(buf, somestring, sizeof(buf)) < sizeof(buf)); + assert(strncmp(buf, somestring, len_somestring) == 0); + assert(strncmp(buf + len_somestring, somestring, len_somestring) == 0); + assert(buf[sizeof(buf) - 1] == FILL_CHAR); + + /* Check that two strings just fail to fit into buf2. Check + * its nul-termination. */ + memset(buf2, FILL_CHAR, sizeof(buf2)); + strcpy(buf2, somestring); + + assert(strlcat(buf2, somestring, sizeof(buf2)) >= sizeof(buf2)); + assert(strncmp(buf2, somestring, len_somestring) == 0); + assert(strncmp(buf2 + len_somestring, somestring, + sizeof(buf2) - len_somestring - 1) == 0); + assert(buf2[sizeof(buf2) - 1] == '\0'); + + /* Copy somestring into a way-too-small buffer. Lie about the size + * of the buffer and check for buffer overrun. */ + + /* Case 1: None of the source string will fit. */ + check_too_small(somestring, buf2, sizeof(buf2), sizeof(somestring)); + + /* Case 2: Some of the source string will fit. */ + check_too_small(somestring, buf2, sizeof(buf2), sizeof(somestring) + 1); + check_too_small(somestring, buf2, sizeof(buf2), sizeof(somestring) + 2); + check_too_small(somestring, buf2, sizeof(buf2), sizeof(somestring) + 4); + + puts("PASS"); + return(EXIT_SUCCESS); + } *** /dev/null Tue Dec 17 18:10:22 2002 --- tests/libc/compat/string/t-stlcpy.c Tue Dec 17 18:00:22 2002 *************** *** 0 **** --- 1,57 ---- + #include + #include + #include + #include + + #define FILL_CHAR 'X' + + int + main (void) + { + const char somestring[] = "somestring"; + char buf[12]; /* More than big enough to contain somestring */ + char buf2[10]; /* Just too small to contain somestring. */ + int i; + + /* Try a zero size. Check that the buffer is untouched. */ + memset(buf, FILL_CHAR, sizeof(buf)); + + assert(strlcpy(buf, somestring, 0) == strlen(somestring)); + for (i = 0; i < sizeof(buf); i++) { + assert(buf[i] == FILL_CHAR); + } + + /* Copy somestring into a large-enough buffer. Check that it hasn't + * overrun. */ + memset(buf, FILL_CHAR, sizeof(buf)); + + assert(strlcpy(buf, somestring, sizeof(buf)) < sizeof(buf)); + assert(strcmp(buf, somestring) == 0); + assert(buf[sizeof(buf) - 1] == FILL_CHAR); + + /* Copy somestring into a just-too-small buffer. Check + * its nul-termination. */ + memset(buf2, FILL_CHAR, sizeof(buf2)); + + assert(strlcpy(buf2, somestring, sizeof(buf2)) == sizeof(buf2)); + assert(strncmp(buf2, somestring, sizeof(buf2) - 1) == 0); + assert(buf2[sizeof(buf2) - 1] == '\0'); + + /* Copy somestring into a way-too-small buffer. Lie about the size + * of the buffer and check for buffer overrun. */ + #define MADE_UP_SIZE 3 + assert(MADE_UP_SIZE < sizeof(buf2)); + + memset(buf2, FILL_CHAR, sizeof(buf2)); + + assert(strlcpy(buf2, somestring, MADE_UP_SIZE) >= MADE_UP_SIZE); + assert(strncmp(buf2, somestring, MADE_UP_SIZE - 1) == 0); + assert(buf2[MADE_UP_SIZE - 1] == '\0'); + for (i = MADE_UP_SIZE; i < sizeof(buf2); i++) { + assert(buf2[i] == FILL_CHAR); + } + #undef MADE_UP_SIZE + + puts("PASS"); + return(EXIT_SUCCESS); + }