diff options
author | Florian Weimer <fweimer@redhat.com> | 2023-06-14 18:10:08 +0200 |
---|---|---|
committer | Florian Weimer <fweimer@redhat.com> | 2023-06-14 18:10:08 +0200 |
commit | 454a20c8756c9c1d55419153255fc7692b3d2199 (patch) | |
tree | a65ad84288a247995183089f4400e4fd080ecc9d /string | |
parent | 7ba426a1115318fc11f4355f3161f35817a06ba4 (diff) | |
download | glibc-454a20c8756c9c1d55419153255fc7692b3d2199.tar.gz glibc-454a20c8756c9c1d55419153255fc7692b3d2199.tar.xz glibc-454a20c8756c9c1d55419153255fc7692b3d2199.zip |
Implement strlcpy and strlcat [BZ #178]
These functions are about to be added to POSIX, under Austin Group issue 986. The fortified strlcat implementation does not raise SIGABRT if the destination buffer does not contain a null terminator, it just inherits the non-failing regular strlcat behavior. Reviewed-by: Siddhesh Poyarekar <siddhesh@sourceware.org>
Diffstat (limited to 'string')
-rw-r--r-- | string/Makefile | 4 | ||||
-rw-r--r-- | string/Versions | 4 | ||||
-rw-r--r-- | string/bits/string_fortified.h | 36 | ||||
-rw-r--r-- | string/string.h | 13 | ||||
-rw-r--r-- | string/strlcat.c | 59 | ||||
-rw-r--r-- | string/strlcpy.c | 46 | ||||
-rw-r--r-- | string/tst-strlcat.c | 84 | ||||
-rw-r--r-- | string/tst-strlcpy.c | 68 |
8 files changed, 314 insertions, 0 deletions
diff --git a/string/Makefile b/string/Makefile index 2eef6f1dfc..0ad276cc6a 100644 --- a/string/Makefile +++ b/string/Makefile @@ -92,6 +92,8 @@ routines := \ strerrorname_np \ strfry \ string-inlines \ + strlcat \ + strlcpy \ strlen \ strncase \ strncase_l \ @@ -177,6 +179,8 @@ tests := \ tst-inlcall \ tst-memmove-overflow \ tst-strfry \ + tst-strlcat \ + tst-strlcpy \ tst-strlen \ tst-strtok \ tst-strtok_r \ diff --git a/string/Versions b/string/Versions index 864c4cf7a4..c56e372a3c 100644 --- a/string/Versions +++ b/string/Versions @@ -92,4 +92,8 @@ libc { GLIBC_2.35 { __memcmpeq; } + GLIBC_2.38 { + strlcat; + strlcpy; + } } diff --git a/string/bits/string_fortified.h b/string/bits/string_fortified.h index 9900df6104..23ef064168 100644 --- a/string/bits/string_fortified.h +++ b/string/bits/string_fortified.h @@ -139,4 +139,40 @@ __NTH (strncat (char *__restrict __dest, const char *__restrict __src, __glibc_objsize (__dest)); } +#ifdef __USE_MISC +extern size_t __strlcpy_chk (char *__dest, const char *__src, size_t __n, + size_t __destlen) __THROW; +extern size_t __REDIRECT_NTH (__strlcpy_alias, + (char *__dest, const char *__src, size_t __n), + strlcpy); + +__fortify_function size_t +__NTH (strlcpy (char *__restrict __dest, const char *__restrict __src, + size_t __n)) +{ + if (__glibc_objsize (__dest) != (size_t) -1 + && (!__builtin_constant_p (__n > __glibc_objsize (__dest)) + || __n > __glibc_objsize (__dest))) + return __strlcpy_chk (__dest, __src, __n, __glibc_objsize (__dest)); + return __strlcpy_alias (__dest, __src, __n); +} + +extern size_t __strlcat_chk (char *__dest, const char *__src, size_t __n, + size_t __destlen) __THROW; +extern size_t __REDIRECT_NTH (__strlcat_alias, + (char *__dest, const char *__src, size_t __n), + strlcat); + +__fortify_function size_t +__NTH (strlcat (char *__restrict __dest, const char *__restrict __src, + size_t __n)) +{ + if (__glibc_objsize (__dest) != (size_t) -1 + && (!__builtin_constant_p (__n > __glibc_objsize (__dest)) + || __n > __glibc_objsize (__dest))) + return __strlcat_chk (__dest, __src, __n, __glibc_objsize (__dest)); + return __strlcat_alias (__dest, __src, __n); +} +#endif /* __USE_MISC */ + #endif /* bits/string_fortified.h */ diff --git a/string/string.h b/string/string.h index 4927879ecf..c0773d11d8 100644 --- a/string/string.h +++ b/string/string.h @@ -501,6 +501,19 @@ extern char *stpncpy (char *__restrict __dest, __THROW __nonnull ((1, 2)); #endif +#ifdef __USE_MISC +/* Copy at most N - 1 characters from SRC to DEST. */ +extern size_t strlcpy (char *__restrict __dest, + const char *__restrict __src, size_t __n) + __THROW __nonnull ((1, 2)) __attr_access ((__write_only__, 1, 3)); + +/* Append SRC to DEST, possibly with truncation to keep the total size + below N. */ +extern size_t strlcat (char *__restrict __dest, + const char *__restrict __src, size_t __n) + __THROW __nonnull ((1, 2)) __attr_access ((__read_write__, 1, 3)); +#endif + #ifdef __USE_GNU /* Compare S1 and S2 as strings holding name & indices/version numbers. */ extern int strverscmp (const char *__s1, const char *__s2) diff --git a/string/strlcat.c b/string/strlcat.c new file mode 100644 index 0000000000..dce4c255d1 --- /dev/null +++ b/string/strlcat.c @@ -0,0 +1,59 @@ +/* Append a null-terminated string to another string, with length checking. + Copyright (C) 2023 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library 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 2.1 of the License, or (at your option) any later version. + + The GNU C Library 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 the GNU C Library; if not, see + <https://www.gnu.org/licenses/>. */ + +#include <stdint.h> +#include <string.h> + +size_t +__strlcat (char *__restrict dest, const char *__restrict src, size_t size) +{ + size_t src_length = strlen (src); + + /* Our implementation strlcat supports dest == NULL if size == 0 + (for consistency with snprintf and strlcpy), but strnlen does + not, so we have to cover this case explicitly. */ + if (size == 0) + return src_length; + + size_t dest_length = __strnlen (dest, size); + if (dest_length != size) + { + /* Copy at most the remaining number of characters in the + destination buffer. Leave for the NUL terminator. */ + size_t to_copy = size - dest_length - 1; + /* But not more than what is available in the source string. */ + if (to_copy > src_length) + to_copy = src_length; + + char *target = dest + dest_length; + memcpy (target, src, to_copy); + target[to_copy] = '\0'; + } + + /* If the sum wraps around, we have more than SIZE_MAX + 2 bytes in + the two input strings (including both null terminators). If each + byte in the address space can be assigned a unique size_t value + (which the static_assert checks), then by the pigeonhole + principle, the two input strings must overlap, which is + undefined. */ + _Static_assert (sizeof (uintptr_t) == sizeof (size_t), + "theoretical maximum object size covers address space"); + return dest_length + src_length; +} +libc_hidden_def (__strlcat) +weak_alias (__strlcat, strlcat) diff --git a/string/strlcpy.c b/string/strlcpy.c new file mode 100644 index 0000000000..7a0df3ebb6 --- /dev/null +++ b/string/strlcpy.c @@ -0,0 +1,46 @@ +/* Copy a null-terminated string to a fixed-size buffer, with length checking. + Copyright (C) 2023 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library 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 2.1 of the License, or (at your option) any later version. + + The GNU C Library 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 the GNU C Library; if not, see + <https://www.gnu.org/licenses/>. */ + +#include <string.h> + +size_t +__strlcpy (char *__restrict dest, const char *__restrict src, size_t size) +{ + size_t src_length = strlen (src); + + if (__glibc_unlikely (src_length >= size)) + { + if (size > 0) + { + /* Copy the leading portion of the string. The last + character is subsequently overwritten with the NUL + terminator, but the destination size is usually a + multiple of a small power of two, so writing it twice + should be more efficient than copying an odd number of + bytes. */ + memcpy (dest, src, size); + dest[size - 1] = '\0'; + } + } + else + /* Copy the string and its terminating NUL character. */ + memcpy (dest, src, src_length + 1); + return src_length; +} +libc_hidden_def (__strlcpy) +weak_alias (__strlcpy, strlcpy) diff --git a/string/tst-strlcat.c b/string/tst-strlcat.c new file mode 100644 index 0000000000..f8c716373e --- /dev/null +++ b/string/tst-strlcat.c @@ -0,0 +1,84 @@ +/* Test the strlcat function. + Copyright (C) 2023 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library 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 2.1 of the License, or (at your option) any later version. + + The GNU C Library 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 the GNU C Library; if not, see + <https://www.gnu.org/licenses/>. */ + +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <support/check.h> + +static int +do_test (void) +{ + struct { + char buf1[16]; + char buf2[16]; + } s; + + /* Nothing is written to the destination if its size is 0. */ + memset (&s, '@', sizeof (s)); + TEST_COMPARE (strlcat (s.buf1, "", 0), 0); + TEST_COMPARE_BLOB (&s, sizeof (s), "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", 32); + TEST_COMPARE (strlcat (s.buf1, "Hello!", 0), 6); + TEST_COMPARE_BLOB (&s, sizeof (s), "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", 32); + + /* No bytes are are modified in the target buffer if the source + string is short enough. */ + memset (&s, '@', sizeof (s)); + strcpy (s.buf1, "He"); + TEST_COMPARE (strlcat (s.buf1, "llo!", sizeof (s.buf1)), 6); + TEST_COMPARE_BLOB (&s, sizeof (s), "Hello!\0@@@@@@@@@@@@@@@@@@@@@@@@@", 32); + + /* A source string which fits exactly into the destination buffer is + not truncated. */ + memset (&s, '@', sizeof (s)); + strcpy (s.buf1, "H"); + TEST_COMPARE (strlcat (s.buf1, "ello, world!!!", sizeof (s.buf1)), 15); + TEST_COMPARE_BLOB (&s, sizeof (s), + "Hello, world!!!\0@@@@@@@@@@@@@@@@@@@@@@@@@", 32); + + /* A source string one character longer than the destination buffer + is truncated by one character. The total length is returned. */ + memset (&s, '@', sizeof (s)); + strcpy (s.buf1, "Hello"); + TEST_COMPARE (strlcat (s.buf1, ", world!!!!", sizeof (s.buf1)), 16); + TEST_COMPARE_BLOB (&s, sizeof (s), + "Hello, world!!!\0@@@@@@@@@@@@@@@@@@@@@@@@@", 32); + + /* An even longer source string is truncated as well, and the total + length is returned. */ + memset (&s, '@', sizeof (s)); + strcpy (s.buf1, "Hello,"); + TEST_COMPARE (strlcat (s.buf1, " world!!!!!!!!", sizeof (s.buf1)), 20); + TEST_COMPARE_BLOB (&s, sizeof (s), + "Hello, world!!!\0@@@@@@@@@@@@@@@@@@@@@@@@@", 32); + + /* A destination string which is not NUL-terminated does not result + in any changes to the buffer. */ + memset (&s, '@', sizeof (s)); + memset (s.buf1, '$', sizeof (s.buf1)); + TEST_COMPARE (strlcat (s.buf1, "", sizeof (s.buf1)), 16); + TEST_COMPARE_BLOB (&s, sizeof (s), "$$$$$$$$$$$$$$$$@@@@@@@@@@@@@@@@", 32); + TEST_COMPARE (strlcat (s.buf1, "Hello!", sizeof (s.buf1)), 22); + TEST_COMPARE_BLOB (&s, sizeof (s), "$$$$$$$$$$$$$$$$@@@@@@@@@@@@@@@@", 32); + TEST_COMPARE (strlcat (s.buf1, "Hello, world!!!!!!!!", sizeof (s.buf1)), 36); + TEST_COMPARE_BLOB (&s, sizeof (s), "$$$$$$$$$$$$$$$$@@@@@@@@@@@@@@@@", 32); + + return 0; +} + +#include <support/test-driver.c> diff --git a/string/tst-strlcpy.c b/string/tst-strlcpy.c new file mode 100644 index 0000000000..0063c43f5c --- /dev/null +++ b/string/tst-strlcpy.c @@ -0,0 +1,68 @@ +/* Test the strlcpy function. + Copyright (C) 2023 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library 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 2.1 of the License, or (at your option) any later version. + + The GNU C Library 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 the GNU C Library; if not, see + <https://www.gnu.org/licenses/>. */ + +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <support/check.h> + +static int +do_test (void) +{ + struct { + char buf1[16]; + char buf2[16]; + } s; + + /* Nothing is written to the destination if its size is 0. */ + memset (&s, '@', sizeof (s)); + TEST_COMPARE (strlcpy (s.buf1, "Hello!", 0), 6); + TEST_COMPARE_BLOB (&s, sizeof (s), "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", 32); + + /* No bytes are are modified in the target buffer if the source + string is short enough. */ + memset (&s, '@', sizeof (s)); + TEST_COMPARE (strlcpy (s.buf1, "Hello!", sizeof (s.buf1)), 6); + TEST_COMPARE_BLOB (&s, sizeof (s), "Hello!\0@@@@@@@@@@@@@@@@@@@@@@@@@", 32); + + /* A source string which fits exactly into the destination buffer is + not truncated. */ + memset (&s, '@', sizeof (s)); + TEST_COMPARE (strlcpy (s.buf1, "Hello, world!!!", sizeof (s.buf1)), 15); + TEST_COMPARE_BLOB (&s, sizeof (s), + "Hello, world!!!\0@@@@@@@@@@@@@@@@@@@@@@@@@", 32); + + /* A source string one character longer than the destination buffer + is truncated by one character. The untruncated source length is + returned. */ + memset (&s, '@', sizeof (s)); + TEST_COMPARE (strlcpy (s.buf1, "Hello, world!!!!", sizeof (s.buf1)), 16); + TEST_COMPARE_BLOB (&s, sizeof (s), + "Hello, world!!!\0@@@@@@@@@@@@@@@@@@@@@@@@@", 32); + + /* An even longer source string is truncated as well, and the + original length is returned. */ + memset (&s, '@', sizeof (s)); + TEST_COMPARE (strlcpy (s.buf1, "Hello, world!!!!!!!!", sizeof (s.buf1)), 20); + TEST_COMPARE_BLOB (&s, sizeof (s), + "Hello, world!!!\0@@@@@@@@@@@@@@@@@@@@@@@@@", 32); + + return 0; +} + +#include <support/test-driver.c> |