diff mbox series

[1/2] Implement strlcpy and strlcat [BZ #178]

Message ID f2b8dcd6c84557b79f1398c0ca3e55d0f3b10584.1680693362.git.fweimer@redhat.com
State New
Headers show
Series strlcpy/strlcat/wcslcpy/wcscat implementation | expand

Commit Message

Florian Weimer April 5, 2023, 11:20 a.m. UTC
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
inheritis the non-failing regular strlcat behavior.  Maybe this
should be changed to catch secondary overflows because the string
appears longer than the buffer.
---
 NEWS                                          |  5 ++
 debug/Makefile                                |  1 +
 debug/Versions                                |  4 +
 debug/strlcat_chk.c                           | 32 +++++++
 debug/strlcpy_chk.c                           | 32 +++++++
 debug/tst-fortify.c                           | 31 +++++++
 include/string.h                              |  4 +
 manual/string.texi                            | 54 ++++++++++++
 string/Makefile                               |  4 +
 string/Versions                               |  4 +
 string/bits/string_fortified.h                | 36 ++++++++
 string/string.h                               | 13 +++
 string/strlcat.c                              | 61 ++++++++++++++
 string/strlcpy.c                              | 48 +++++++++++
 string/tst-strlcat.c                          | 84 +++++++++++++++++++
 string/tst-strlcpy.c                          | 68 +++++++++++++++
 sysdeps/mach/hurd/i386/libc.abilist           |  4 +
 sysdeps/unix/sysv/linux/aarch64/libc.abilist  |  4 +
 sysdeps/unix/sysv/linux/alpha/libc.abilist    |  4 +
 sysdeps/unix/sysv/linux/arc/libc.abilist      |  4 +
 sysdeps/unix/sysv/linux/arm/be/libc.abilist   |  4 +
 sysdeps/unix/sysv/linux/arm/le/libc.abilist   |  4 +
 sysdeps/unix/sysv/linux/csky/libc.abilist     |  4 +
 sysdeps/unix/sysv/linux/hppa/libc.abilist     |  4 +
 sysdeps/unix/sysv/linux/i386/libc.abilist     |  4 +
 sysdeps/unix/sysv/linux/ia64/libc.abilist     |  4 +
 .../sysv/linux/loongarch/lp64/libc.abilist    |  4 +
 .../sysv/linux/m68k/coldfire/libc.abilist     |  4 +
 .../unix/sysv/linux/m68k/m680x0/libc.abilist  |  4 +
 .../sysv/linux/microblaze/be/libc.abilist     |  4 +
 .../sysv/linux/microblaze/le/libc.abilist     |  4 +
 .../sysv/linux/mips/mips32/fpu/libc.abilist   |  4 +
 .../sysv/linux/mips/mips32/nofpu/libc.abilist |  4 +
 .../sysv/linux/mips/mips64/n32/libc.abilist   |  4 +
 .../sysv/linux/mips/mips64/n64/libc.abilist   |  4 +
 sysdeps/unix/sysv/linux/nios2/libc.abilist    |  4 +
 sysdeps/unix/sysv/linux/or1k/libc.abilist     |  4 +
 .../linux/powerpc/powerpc32/fpu/libc.abilist  |  4 +
 .../powerpc/powerpc32/nofpu/libc.abilist      |  4 +
 .../linux/powerpc/powerpc64/be/libc.abilist   |  4 +
 .../linux/powerpc/powerpc64/le/libc.abilist   |  4 +
 .../unix/sysv/linux/riscv/rv32/libc.abilist   |  4 +
 .../unix/sysv/linux/riscv/rv64/libc.abilist   |  4 +
 .../unix/sysv/linux/s390/s390-32/libc.abilist |  4 +
 .../unix/sysv/linux/s390/s390-64/libc.abilist |  4 +
 sysdeps/unix/sysv/linux/sh/be/libc.abilist    |  4 +
 sysdeps/unix/sysv/linux/sh/le/libc.abilist    |  4 +
 .../sysv/linux/sparc/sparc32/libc.abilist     |  4 +
 .../sysv/linux/sparc/sparc64/libc.abilist     |  4 +
 .../unix/sysv/linux/x86_64/64/libc.abilist    |  4 +
 .../unix/sysv/linux/x86_64/x32/libc.abilist   |  4 +
 51 files changed, 621 insertions(+)
 create mode 100644 debug/strlcat_chk.c
 create mode 100644 debug/strlcpy_chk.c
 create mode 100644 string/strlcat.c
 create mode 100644 string/strlcpy.c
 create mode 100644 string/tst-strlcat.c
 create mode 100644 string/tst-strlcpy.c

Comments

Adhemerval Zanella Netto April 5, 2023, 1:18 p.m. UTC | #1
On 05/04/23 08:20, Florian Weimer via Libc-alpha wrote:
> 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
> inheritis the non-failing regular strlcat behavior.  Maybe this
> should be changed to catch secondary overflows because the string
> appears longer than the buffer.

This is not a full review, just nits I found skimming the patch.

> diff --git a/NEWS b/NEWS
> index 83d082afad..60b40fabcf 100644
> --- a/NEWS
> +++ b/NEWS
> @@ -21,6 +21,9 @@ Major new features:
>  
>  * PRIb* and PRIB* macros from C2X have been added to <inttypes.h>.
>  
> +* The strlcpy and strlcat functions have been added.  They are derived
> +  from OpenBSD, and are expected to be added to a future POSIX versions.
> +
>  Deprecated and removed features, and other changes affecting compatibility:
>  
>  * In the Linux kernel for the hppa/parisc architecture some of the
> @@ -223,6 +226,8 @@ Major new features:
>  
>    The LoongArch ABI is 64-bit little-endian.
>  
> +* The functions strlcpy and strlcat have been added.

This NEWS entry seems to be a left-over from refactoring.

> +
>  Deprecated and removed features, and other changes affecting compatibility:
>  
>  * Support for prelink will be removed in the next release; this includes
> diff --git a/debug/Makefile b/debug/Makefile
> index 52f9a7852c..404f93002f 100644
> --- a/debug/Makefile
> +++ b/debug/Makefile
> @@ -31,6 +31,7 @@ headers	:= execinfo.h
>  routines  = backtrace backtracesyms backtracesymsfd noophooks \
>  	    memcpy_chk memmove_chk mempcpy_chk memset_chk stpcpy_chk \
>  	    strcat_chk strcpy_chk strncat_chk strncpy_chk stpncpy_chk \
> +	    strlcpy_chk strlcat_chk \
>  	    sprintf_chk vsprintf_chk snprintf_chk vsnprintf_chk \
>  	    printf_chk fprintf_chk vprintf_chk vfprintf_chk \
>  	    gets_chk chk_fail readonly-area fgets_chk fgets_u_chk \
> diff --git a/debug/Versions b/debug/Versions
> index a6628db356..94dfa5f428 100644
> --- a/debug/Versions
> +++ b/debug/Versions
> @@ -58,6 +58,10 @@ libc {
>    GLIBC_2.25 {
>      __explicit_bzero_chk;
>    }
> +  GLIBC_2.38 {
> +    __strlcat_chk;
> +    __strlcpy_chk;
> +  }
>    GLIBC_PRIVATE {
>      __fortify_fail;
>    }
> diff --git a/debug/strlcat_chk.c b/debug/strlcat_chk.c
> new file mode 100644
> index 0000000000..be6e1942af
> --- /dev/null
> +++ b/debug/strlcat_chk.c
> @@ -0,0 +1,32 @@
> +/* Fortified version of strlcat.
> +   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 <memcopy.h>

It does not require this header.

> +
> +/* Check that the user-supplied size does not exceed the
> +   compiler-determined size, and then forward to strlcat.  */
> +size_t
> +__strlcat_chk (char *__restrict s1, const char *__restrict s2,
> +	       size_t n, size_t s1len)
> +{
> +  if (__glibc_unlikely (s1len < n))
> +    __chk_fail ();
> +
> +  return __strlcat (s1, s2, n);
> +}
> diff --git a/debug/strlcpy_chk.c b/debug/strlcpy_chk.c
> new file mode 100644
> index 0000000000..0137886e91
> --- /dev/null
> +++ b/debug/strlcpy_chk.c
> @@ -0,0 +1,32 @@
> +/* Fortified version of strlcpy.
> +   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 <memcopy.h>

Ditto.

> diff --git a/string/strlcat.c b/string/strlcat.c
> new file mode 100644
> index 0000000000..5b64072004
> --- /dev/null
> +++ b/string/strlcat.c
> @@ -0,0 +1,61 @@
> +/* 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>
> +
> +#undef strlcat
> +
> +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");

Ins't garanteed by POSIX?
Florian Weimer April 6, 2023, 9:18 a.m. UTC | #2
* Adhemerval Zanella Netto:

> On 05/04/23 08:20, Florian Weimer via Libc-alpha wrote:
>> 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
>> inheritis the non-failing regular strlcat behavior.  Maybe this
>> should be changed to catch secondary overflows because the string
>> appears longer than the buffer.
>
> This is not a full review, just nits I found skimming the patch.

I applied your suggestions locally.

>> +  /* 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");
>
> Ins't garanteed by POSIX?

Sure, but I felt it added additional documentation.

Thanks,
Florian
Siddhesh Poyarekar April 6, 2023, 2:22 p.m. UTC | #3
On 2023-04-05 07:20, Florian Weimer via Libc-alpha wrote:
> 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
> inheritis the non-failing regular strlcat behavior.  Maybe this
> should be changed to catch secondary overflows because the string
> appears longer than the buffer.
> ---
>   NEWS                                          |  5 ++
>   debug/Makefile                                |  1 +
>   debug/Versions                                |  4 +
>   debug/strlcat_chk.c                           | 32 +++++++
>   debug/strlcpy_chk.c                           | 32 +++++++
>   debug/tst-fortify.c                           | 31 +++++++
>   include/string.h                              |  4 +
>   manual/string.texi                            | 54 ++++++++++++
>   string/Makefile                               |  4 +
>   string/Versions                               |  4 +
>   string/bits/string_fortified.h                | 36 ++++++++
>   string/string.h                               | 13 +++
>   string/strlcat.c                              | 61 ++++++++++++++
>   string/strlcpy.c                              | 48 +++++++++++
>   string/tst-strlcat.c                          | 84 +++++++++++++++++++
>   string/tst-strlcpy.c                          | 68 +++++++++++++++
>   sysdeps/mach/hurd/i386/libc.abilist           |  4 +
>   sysdeps/unix/sysv/linux/aarch64/libc.abilist  |  4 +
>   sysdeps/unix/sysv/linux/alpha/libc.abilist    |  4 +
>   sysdeps/unix/sysv/linux/arc/libc.abilist      |  4 +
>   sysdeps/unix/sysv/linux/arm/be/libc.abilist   |  4 +
>   sysdeps/unix/sysv/linux/arm/le/libc.abilist   |  4 +
>   sysdeps/unix/sysv/linux/csky/libc.abilist     |  4 +
>   sysdeps/unix/sysv/linux/hppa/libc.abilist     |  4 +
>   sysdeps/unix/sysv/linux/i386/libc.abilist     |  4 +
>   sysdeps/unix/sysv/linux/ia64/libc.abilist     |  4 +
>   .../sysv/linux/loongarch/lp64/libc.abilist    |  4 +
>   .../sysv/linux/m68k/coldfire/libc.abilist     |  4 +
>   .../unix/sysv/linux/m68k/m680x0/libc.abilist  |  4 +
>   .../sysv/linux/microblaze/be/libc.abilist     |  4 +
>   .../sysv/linux/microblaze/le/libc.abilist     |  4 +
>   .../sysv/linux/mips/mips32/fpu/libc.abilist   |  4 +
>   .../sysv/linux/mips/mips32/nofpu/libc.abilist |  4 +
>   .../sysv/linux/mips/mips64/n32/libc.abilist   |  4 +
>   .../sysv/linux/mips/mips64/n64/libc.abilist   |  4 +
>   sysdeps/unix/sysv/linux/nios2/libc.abilist    |  4 +
>   sysdeps/unix/sysv/linux/or1k/libc.abilist     |  4 +
>   .../linux/powerpc/powerpc32/fpu/libc.abilist  |  4 +
>   .../powerpc/powerpc32/nofpu/libc.abilist      |  4 +
>   .../linux/powerpc/powerpc64/be/libc.abilist   |  4 +
>   .../linux/powerpc/powerpc64/le/libc.abilist   |  4 +
>   .../unix/sysv/linux/riscv/rv32/libc.abilist   |  4 +
>   .../unix/sysv/linux/riscv/rv64/libc.abilist   |  4 +
>   .../unix/sysv/linux/s390/s390-32/libc.abilist |  4 +
>   .../unix/sysv/linux/s390/s390-64/libc.abilist |  4 +
>   sysdeps/unix/sysv/linux/sh/be/libc.abilist    |  4 +
>   sysdeps/unix/sysv/linux/sh/le/libc.abilist    |  4 +
>   .../sysv/linux/sparc/sparc32/libc.abilist     |  4 +
>   .../sysv/linux/sparc/sparc64/libc.abilist     |  4 +
>   .../unix/sysv/linux/x86_64/64/libc.abilist    |  4 +
>   .../unix/sysv/linux/x86_64/x32/libc.abilist   |  4 +
>   51 files changed, 621 insertions(+)
>   create mode 100644 debug/strlcat_chk.c
>   create mode 100644 debug/strlcpy_chk.c
>   create mode 100644 string/strlcat.c
>   create mode 100644 string/strlcpy.c
>   create mode 100644 string/tst-strlcat.c
>   create mode 100644 string/tst-strlcpy.c
> 
> diff --git a/NEWS b/NEWS
> index 83d082afad..60b40fabcf 100644
> --- a/NEWS
> +++ b/NEWS
> @@ -21,6 +21,9 @@ Major new features:
>   
>   * PRIb* and PRIB* macros from C2X have been added to <inttypes.h>.
>   
> +* The strlcpy and strlcat functions have been added.  They are derived
> +  from OpenBSD, and are expected to be added to a future POSIX versions.
> +
>   Deprecated and removed features, and other changes affecting compatibility:
>   
>   * In the Linux kernel for the hppa/parisc architecture some of the
> @@ -223,6 +226,8 @@ Major new features:
>   
>     The LoongArch ABI is 64-bit little-endian.
>   
> +* The functions strlcpy and strlcat have been added.
> +
>   Deprecated and removed features, and other changes affecting compatibility:
>   
>   * Support for prelink will be removed in the next release; this includes
> diff --git a/debug/Makefile b/debug/Makefile
> index 52f9a7852c..404f93002f 100644
> --- a/debug/Makefile
> +++ b/debug/Makefile
> @@ -31,6 +31,7 @@ headers	:= execinfo.h
>   routines  = backtrace backtracesyms backtracesymsfd noophooks \
>   	    memcpy_chk memmove_chk mempcpy_chk memset_chk stpcpy_chk \
>   	    strcat_chk strcpy_chk strncat_chk strncpy_chk stpncpy_chk \
> +	    strlcpy_chk strlcat_chk \

Do you want to commit an obvious change first to update this routine 
list to be one per line?

>   	    sprintf_chk vsprintf_chk snprintf_chk vsnprintf_chk \
>   	    printf_chk fprintf_chk vprintf_chk vfprintf_chk \
>   	    gets_chk chk_fail readonly-area fgets_chk fgets_u_chk \
> diff --git a/debug/Versions b/debug/Versions
> index a6628db356..94dfa5f428 100644
> --- a/debug/Versions
> +++ b/debug/Versions
> @@ -58,6 +58,10 @@ libc {
>     GLIBC_2.25 {
>       __explicit_bzero_chk;
>     }
> +  GLIBC_2.38 {
> +    __strlcat_chk;
> +    __strlcpy_chk;
> +  }
>     GLIBC_PRIVATE {
>       __fortify_fail;
>     }
> diff --git a/debug/strlcat_chk.c b/debug/strlcat_chk.c
> new file mode 100644
> index 0000000000..be6e1942af
> --- /dev/null
> +++ b/debug/strlcat_chk.c
> @@ -0,0 +1,32 @@
> +/* Fortified version of strlcat.
> +   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 <memcopy.h>
> +
> +/* Check that the user-supplied size does not exceed the
> +   compiler-determined size, and then forward to strlcat.  */
> +size_t
> +__strlcat_chk (char *__restrict s1, const char *__restrict s2,
> +	       size_t n, size_t s1len)
> +{
> +  if (__glibc_unlikely (s1len < n))
> +    __chk_fail ();
> +
> +  return __strlcat (s1, s2, n);
> +}

OK.

> diff --git a/debug/strlcpy_chk.c b/debug/strlcpy_chk.c
> new file mode 100644
> index 0000000000..0137886e91
> --- /dev/null
> +++ b/debug/strlcpy_chk.c
> @@ -0,0 +1,32 @@
> +/* Fortified version of strlcpy.
> +   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 <memcopy.h>
> +
> +/* Check that the user-supplied size does not exceed the
> +   compiler-determined size, and then forward to strlcpy.  */
> +size_t
> +__strlcpy_chk (char *__restrict s1, const char *__restrict s2,
> +	       size_t n, size_t s1len)
> +{
> +  if (__glibc_unlikely (s1len < n))
> +    __chk_fail ();
> +
> +  return __strlcpy (s1, s2, n);
> +}

OK.

> diff --git a/debug/tst-fortify.c b/debug/tst-fortify.c
> index 7850a4e558..f74a5e04dc 100644
> --- a/debug/tst-fortify.c
> +++ b/debug/tst-fortify.c
> @@ -535,6 +535,20 @@ do_test (void)
>     strncpy (a.buf1 + (O + 6), "X", l0 + 4);
>     CHK_FAIL_END
>   
> +  CHK_FAIL_START
> +  strlcpy (a.buf1 + (O + 6), "X", 4);
> +  CHK_FAIL_END
> +
> +  CHK_FAIL_START
> +  strlcpy (a.buf1 + (O + 6), "X", l0 + 4);
> +  CHK_FAIL_END
> +
> +  {
> +    char *volatile buf2 = buf;
> +    if (strlcpy (buf2, "a", sizeof (buf) + 1) != 1)
> +      FAIL ();
> +  }
> +
>   # if !defined __cplusplus || defined __va_arg_pack
>     CHK_FAIL_START
>     sprintf (a.buf1 + (O + 7), "%d", num1);
> @@ -558,6 +572,23 @@ do_test (void)
>     CHK_FAIL_START
>     strncat (a.buf1, "ZYXWV", l0 + 3);
>     CHK_FAIL_END
> +
> +  memset (a.buf1, 0, sizeof (a.buf1));
> +  CHK_FAIL_START
> +  strlcat (a.buf1 + (O + 6), "X", 4);
> +  CHK_FAIL_END
> +
> +  memset (a.buf1, 0, sizeof (a.buf1));
> +  CHK_FAIL_START
> +  strlcat (a.buf1 + (O + 6), "X", l0 + 4);
> +  CHK_FAIL_END
> +
> +  {
> +    buf[0] = '\0';
> +    char *volatile buf2 = buf;
> +    if (strlcat (buf2, "a", sizeof (buf) + 1) != 1)
> +      FAIL ();
> +  }
>   #endif
>   
>   
> diff --git a/include/string.h b/include/string.h
> index 673cfd7272..0c78ad2539 100644
> --- a/include/string.h
> +++ b/include/string.h
> @@ -88,6 +88,10 @@ libc_hidden_proto (__stpcpy)
>   # define __stpcpy(dest, src) __builtin_stpcpy (dest, src)
>   #endif
>   libc_hidden_proto (__stpncpy)
> +extern __typeof (strlcpy) __strlcpy;
> +libc_hidden_proto (__strlcpy)
> +extern __typeof (strlcat) __strlcat;
> +libc_hidden_proto (__strlcat)
>   libc_hidden_proto (__rawmemchr)
>   libc_hidden_proto (__strcasecmp)
>   libc_hidden_proto (__strcasecmp_l)

Do we want to delay doing this until we have an actual internal use of 
these interfaces?

> diff --git a/manual/string.texi b/manual/string.texi
> index e06433187e..e3979f1d0f 100644
> --- a/manual/string.texi
> +++ b/manual/string.texi
> @@ -1068,6 +1068,60 @@ processing text.  Also, this function has significant performance
>   issues.  @xref{Concatenating Strings}.
>   @end deftypefun
>   
> +@deftypefun size_t strlcpy (char *restrict @var{to}, const char *restrict @var{from}, size_t @var{size})
> +@standards{BSD, string.h}

Should we also mention POSIX here?

> +@safety{@prelim{}@mtsafe{}@assafe{}@acsafe{}}
> +This function is similar to @code{strcpy}, but copies at most
> +@var{size} bytes from the string @var{from} into the destination
> +array @var{to}, including a terminating null byte.
> +
> +If @var{size} is greater than the length of the string @var{from},
> +this function copies all of the string @var{from} to the destination
> +array @var{to}, including the terminating null byte.  Like other
> +string functions such as @code{strcpy}, but unlike @code{strncpy}, any
> +remaining bytes in the destination array remain unchanged.
> +
> +If @var{size} is nonzero and less than or equal to the the length of the string
> +@var{from}, this function copies only the first @samp{@var{size} - 1}
> +bytes to the destination array @var{to}, and writes a terminating null
> +byte to the last byte of the array.
> +
> +The return value @var{result} of @code{strlcpy} is the length of the
> +string @var{from}.  This means that @samp{@var{result} >= @var{size}} is
> +true whenever truncation occurs.
> +
> +The behavior of @code{strlcpy} is undefined if @var{size} is zero, or if
> +the source string and the first @var{size} bytes of the destination
> +array overlap. > +
> +This function is derived from OpenBSD 2.4.
> +@end deftypefun
> +
> +@deftypefun size_t strlcat (char *restrict @var{to}, const char *restrict @var{from}, size_t @var{size})
> +@standards{BSD, string.h}

Likewise.

> +@safety{@prelim{}@mtsafe{}@assafe{}@acsafe{}}
> +This function appends the string @var{from} to the
> +string @var{to}, limiting the total size of the result string at
> +@var{to} (including the null terminator) to @var{size}.
> +
> +This function copies as much as possible of the string @var{from} into
> +the array at @var{to} of @var{size} bytes, starting at the terminating
> +null byte of the original string @var{to}.  In effect, this appends
> +the string @var{from} to the string @var{to}.  Although the resulting
> +string will contain a null terminator, it can be truncated (not all
> +bytes in @var{from} may be copied).
> +
> +This function returns the sum of the original length of @var{to} and
> +the length of @var{from}.  This means that truncation occurs unless
> +the returned value is less than @var{size}.
> +
> +The behavior is undefined if the array at @var{to} does not contain a
> +null byte in its first @var{size} bytes, or if the source string and the
> +first @var{size} bytes of @var{to} overlap. > +This function is derived from OpenBSD 2.4.
> +@end deftypefun
> +
>   Because these functions can abruptly truncate strings or wide strings,
>   they are generally poor choices for processing text.  When coping or
>   concatening multibyte strings, they can truncate within a multibyte
> diff --git a/string/Makefile b/string/Makefile
> index c84b49aaa5..c746ee1792 100644
> --- a/string/Makefile
> +++ b/string/Makefile
> @@ -92,6 +92,8 @@ routines := \
>     strerrorname_np \
>     strfry \
>     string-inlines \
> +  strlcat \
> +  strlcpy \
>     strlen \
>     strncase \
>     strncase_l \
> @@ -175,6 +177,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 */
> +

Couldn't we use the __glibc_fortify macros here?

>   #endif /* bits/string_fortified.h */
> diff --git a/string/string.h b/string/string.h
> index 7f0f600224..fcfb3fce74 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..5b64072004
> --- /dev/null
> +++ b/string/strlcat.c
> @@ -0,0 +1,61 @@
> +/* 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>
> +
> +#undef strlcat
> +
> +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;
> +}

OK.

> +libc_hidden_def (__strlcat)
> +weak_alias (__strlcat, strlcat)
> diff --git a/string/strlcpy.c b/string/strlcpy.c
> new file mode 100644
> index 0000000000..b863a4762b
> --- /dev/null
> +++ b/string/strlcpy.c
> @@ -0,0 +1,48 @@
> +/* 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>
> +
> +#undef strlcpy
> +
> +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);

size == 0 is undefined anyway; we return without touching the dest 
because that's convenient for us.  OK.

> +  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>
> diff --git a/sysdeps/mach/hurd/i386/libc.abilist b/sysdeps/mach/hurd/i386/libc.abilist
> index da1cad6777..e195853f09 100644
> --- a/sysdeps/mach/hurd/i386/libc.abilist
> +++ b/sysdeps/mach/hurd/i386/libc.abilist
> @@ -2324,6 +2324,10 @@ GLIBC_2.38 __isoc23_wcstoull F
>   GLIBC_2.38 __isoc23_wcstoull_l F
>   GLIBC_2.38 __isoc23_wcstoumax F
>   GLIBC_2.38 __isoc23_wscanf F
> +GLIBC_2.38 __strlcat_chk F
> +GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 strlcat F
> +GLIBC_2.38 strlcpy F
>   GLIBC_2.4 __confstr_chk F
>   GLIBC_2.4 __fgets_chk F
>   GLIBC_2.4 __fgets_unlocked_chk F
> diff --git a/sysdeps/unix/sysv/linux/aarch64/libc.abilist b/sysdeps/unix/sysv/linux/aarch64/libc.abilist
> index 0e2d9c3045..cf51b88932 100644
> --- a/sysdeps/unix/sysv/linux/aarch64/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/aarch64/libc.abilist
> @@ -2665,3 +2665,7 @@ GLIBC_2.38 __isoc23_wcstoull F
>   GLIBC_2.38 __isoc23_wcstoull_l F
>   GLIBC_2.38 __isoc23_wcstoumax F
>   GLIBC_2.38 __isoc23_wscanf F
> +GLIBC_2.38 __strlcat_chk F
> +GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 strlcat F
> +GLIBC_2.38 strlcpy F
> diff --git a/sysdeps/unix/sysv/linux/alpha/libc.abilist b/sysdeps/unix/sysv/linux/alpha/libc.abilist
> index f1bec1978d..4b25f343b8 100644
> --- a/sysdeps/unix/sysv/linux/alpha/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/alpha/libc.abilist
> @@ -2774,6 +2774,10 @@ GLIBC_2.38 __nldbl___isoc23_vsscanf F
>   GLIBC_2.38 __nldbl___isoc23_vswscanf F
>   GLIBC_2.38 __nldbl___isoc23_vwscanf F
>   GLIBC_2.38 __nldbl___isoc23_wscanf F
> +GLIBC_2.38 __strlcat_chk F
> +GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 strlcat F
> +GLIBC_2.38 strlcpy F
>   GLIBC_2.4 _IO_fprintf F
>   GLIBC_2.4 _IO_printf F
>   GLIBC_2.4 _IO_sprintf F
> diff --git a/sysdeps/unix/sysv/linux/arc/libc.abilist b/sysdeps/unix/sysv/linux/arc/libc.abilist
> index aa874b88d0..5a58cc0477 100644
> --- a/sysdeps/unix/sysv/linux/arc/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/arc/libc.abilist
> @@ -2426,3 +2426,7 @@ GLIBC_2.38 __isoc23_wcstoull F
>   GLIBC_2.38 __isoc23_wcstoull_l F
>   GLIBC_2.38 __isoc23_wcstoumax F
>   GLIBC_2.38 __isoc23_wscanf F
> +GLIBC_2.38 __strlcat_chk F
> +GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 strlcat F
> +GLIBC_2.38 strlcpy F
> diff --git a/sysdeps/unix/sysv/linux/arm/be/libc.abilist b/sysdeps/unix/sysv/linux/arm/be/libc.abilist
> index afbd57da6f..99ce948c5c 100644
> --- a/sysdeps/unix/sysv/linux/arm/be/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/arm/be/libc.abilist
> @@ -546,6 +546,10 @@ GLIBC_2.38 __isoc23_wcstoull F
>   GLIBC_2.38 __isoc23_wcstoull_l F
>   GLIBC_2.38 __isoc23_wcstoumax F
>   GLIBC_2.38 __isoc23_wscanf F
> +GLIBC_2.38 __strlcat_chk F
> +GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 strlcat F
> +GLIBC_2.38 strlcpy F
>   GLIBC_2.4 _Exit F
>   GLIBC_2.4 _IO_2_1_stderr_ D 0xa0
>   GLIBC_2.4 _IO_2_1_stdin_ D 0xa0
> diff --git a/sysdeps/unix/sysv/linux/arm/le/libc.abilist b/sysdeps/unix/sysv/linux/arm/le/libc.abilist
> index e7364cd3fe..c00bf72ebc 100644
> --- a/sysdeps/unix/sysv/linux/arm/le/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/arm/le/libc.abilist
> @@ -543,6 +543,10 @@ GLIBC_2.38 __isoc23_wcstoull F
>   GLIBC_2.38 __isoc23_wcstoull_l F
>   GLIBC_2.38 __isoc23_wcstoumax F
>   GLIBC_2.38 __isoc23_wscanf F
> +GLIBC_2.38 __strlcat_chk F
> +GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 strlcat F
> +GLIBC_2.38 strlcpy F
>   GLIBC_2.4 _Exit F
>   GLIBC_2.4 _IO_2_1_stderr_ D 0xa0
>   GLIBC_2.4 _IO_2_1_stdin_ D 0xa0
> diff --git a/sysdeps/unix/sysv/linux/csky/libc.abilist b/sysdeps/unix/sysv/linux/csky/libc.abilist
> index 913fa59215..71130f2c6b 100644
> --- a/sysdeps/unix/sysv/linux/csky/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/csky/libc.abilist
> @@ -2702,3 +2702,7 @@ GLIBC_2.38 __isoc23_wcstoull F
>   GLIBC_2.38 __isoc23_wcstoull_l F
>   GLIBC_2.38 __isoc23_wcstoumax F
>   GLIBC_2.38 __isoc23_wscanf F
> +GLIBC_2.38 __strlcat_chk F
> +GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 strlcat F
> +GLIBC_2.38 strlcpy F
> diff --git a/sysdeps/unix/sysv/linux/hppa/libc.abilist b/sysdeps/unix/sysv/linux/hppa/libc.abilist
> index 43af3a9811..5a651c03df 100644
> --- a/sysdeps/unix/sysv/linux/hppa/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/hppa/libc.abilist
> @@ -2651,6 +2651,10 @@ GLIBC_2.38 __isoc23_wcstoull F
>   GLIBC_2.38 __isoc23_wcstoull_l F
>   GLIBC_2.38 __isoc23_wcstoumax F
>   GLIBC_2.38 __isoc23_wscanf F
> +GLIBC_2.38 __strlcat_chk F
> +GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 strlcat F
> +GLIBC_2.38 strlcpy F
>   GLIBC_2.4 __confstr_chk F
>   GLIBC_2.4 __fgets_chk F
>   GLIBC_2.4 __fgets_unlocked_chk F
> diff --git a/sysdeps/unix/sysv/linux/i386/libc.abilist b/sysdeps/unix/sysv/linux/i386/libc.abilist
> index af72f8fab0..12b91ef632 100644
> --- a/sysdeps/unix/sysv/linux/i386/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/i386/libc.abilist
> @@ -2835,6 +2835,10 @@ GLIBC_2.38 __isoc23_wcstoull F
>   GLIBC_2.38 __isoc23_wcstoull_l F
>   GLIBC_2.38 __isoc23_wcstoumax F
>   GLIBC_2.38 __isoc23_wscanf F
> +GLIBC_2.38 __strlcat_chk F
> +GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 strlcat F
> +GLIBC_2.38 strlcpy F
>   GLIBC_2.4 __confstr_chk F
>   GLIBC_2.4 __fgets_chk F
>   GLIBC_2.4 __fgets_unlocked_chk F
> diff --git a/sysdeps/unix/sysv/linux/ia64/libc.abilist b/sysdeps/unix/sysv/linux/ia64/libc.abilist
> index 48cbb0fa50..f223c5e08d 100644
> --- a/sysdeps/unix/sysv/linux/ia64/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/ia64/libc.abilist
> @@ -2600,6 +2600,10 @@ GLIBC_2.38 __isoc23_wcstoull F
>   GLIBC_2.38 __isoc23_wcstoull_l F
>   GLIBC_2.38 __isoc23_wcstoumax F
>   GLIBC_2.38 __isoc23_wscanf F
> +GLIBC_2.38 __strlcat_chk F
> +GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 strlcat F
> +GLIBC_2.38 strlcpy F
>   GLIBC_2.4 __confstr_chk F
>   GLIBC_2.4 __fgets_chk F
>   GLIBC_2.4 __fgets_unlocked_chk F
> diff --git a/sysdeps/unix/sysv/linux/loongarch/lp64/libc.abilist b/sysdeps/unix/sysv/linux/loongarch/lp64/libc.abilist
> index c15884bb0b..b91ed6e704 100644
> --- a/sysdeps/unix/sysv/linux/loongarch/lp64/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/loongarch/lp64/libc.abilist
> @@ -2186,3 +2186,7 @@ GLIBC_2.38 __isoc23_wcstoull F
>   GLIBC_2.38 __isoc23_wcstoull_l F
>   GLIBC_2.38 __isoc23_wcstoumax F
>   GLIBC_2.38 __isoc23_wscanf F
> +GLIBC_2.38 __strlcat_chk F
> +GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 strlcat F
> +GLIBC_2.38 strlcpy F
> diff --git a/sysdeps/unix/sysv/linux/m68k/coldfire/libc.abilist b/sysdeps/unix/sysv/linux/m68k/coldfire/libc.abilist
> index 3738db81df..0d91d7f1ae 100644
> --- a/sysdeps/unix/sysv/linux/m68k/coldfire/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/m68k/coldfire/libc.abilist
> @@ -547,6 +547,10 @@ GLIBC_2.38 __isoc23_wcstoull F
>   GLIBC_2.38 __isoc23_wcstoull_l F
>   GLIBC_2.38 __isoc23_wcstoumax F
>   GLIBC_2.38 __isoc23_wscanf F
> +GLIBC_2.38 __strlcat_chk F
> +GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 strlcat F
> +GLIBC_2.38 strlcpy F
>   GLIBC_2.4 _Exit F
>   GLIBC_2.4 _IO_2_1_stderr_ D 0x98
>   GLIBC_2.4 _IO_2_1_stdin_ D 0x98
> diff --git a/sysdeps/unix/sysv/linux/m68k/m680x0/libc.abilist b/sysdeps/unix/sysv/linux/m68k/m680x0/libc.abilist
> index ed13627752..e87b22747a 100644
> --- a/sysdeps/unix/sysv/linux/m68k/m680x0/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/m68k/m680x0/libc.abilist
> @@ -2778,6 +2778,10 @@ GLIBC_2.38 __isoc23_wcstoull F
>   GLIBC_2.38 __isoc23_wcstoull_l F
>   GLIBC_2.38 __isoc23_wcstoumax F
>   GLIBC_2.38 __isoc23_wscanf F
> +GLIBC_2.38 __strlcat_chk F
> +GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 strlcat F
> +GLIBC_2.38 strlcpy F
>   GLIBC_2.4 __confstr_chk F
>   GLIBC_2.4 __fgets_chk F
>   GLIBC_2.4 __fgets_unlocked_chk F
> diff --git a/sysdeps/unix/sysv/linux/microblaze/be/libc.abilist b/sysdeps/unix/sysv/linux/microblaze/be/libc.abilist
> index 8357738621..f7623d6d72 100644
> --- a/sysdeps/unix/sysv/linux/microblaze/be/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/microblaze/be/libc.abilist
> @@ -2751,3 +2751,7 @@ GLIBC_2.38 __isoc23_wcstoull F
>   GLIBC_2.38 __isoc23_wcstoull_l F
>   GLIBC_2.38 __isoc23_wcstoumax F
>   GLIBC_2.38 __isoc23_wscanf F
> +GLIBC_2.38 __strlcat_chk F
> +GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 strlcat F
> +GLIBC_2.38 strlcpy F
> diff --git a/sysdeps/unix/sysv/linux/microblaze/le/libc.abilist b/sysdeps/unix/sysv/linux/microblaze/le/libc.abilist
> index 58c5da583d..298aa99b42 100644
> --- a/sysdeps/unix/sysv/linux/microblaze/le/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/microblaze/le/libc.abilist
> @@ -2748,3 +2748,7 @@ GLIBC_2.38 __isoc23_wcstoull F
>   GLIBC_2.38 __isoc23_wcstoull_l F
>   GLIBC_2.38 __isoc23_wcstoumax F
>   GLIBC_2.38 __isoc23_wscanf F
> +GLIBC_2.38 __strlcat_chk F
> +GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 strlcat F
> +GLIBC_2.38 strlcpy F
> diff --git a/sysdeps/unix/sysv/linux/mips/mips32/fpu/libc.abilist b/sysdeps/unix/sysv/linux/mips/mips32/fpu/libc.abilist
> index d3741945cd..f83bdc50cd 100644
> --- a/sysdeps/unix/sysv/linux/mips/mips32/fpu/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/mips/mips32/fpu/libc.abilist
> @@ -2743,6 +2743,10 @@ GLIBC_2.38 __isoc23_wcstoull F
>   GLIBC_2.38 __isoc23_wcstoull_l F
>   GLIBC_2.38 __isoc23_wcstoumax F
>   GLIBC_2.38 __isoc23_wscanf F
> +GLIBC_2.38 __strlcat_chk F
> +GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 strlcat F
> +GLIBC_2.38 strlcpy F
>   GLIBC_2.4 __confstr_chk F
>   GLIBC_2.4 __fgets_chk F
>   GLIBC_2.4 __fgets_unlocked_chk F
> diff --git a/sysdeps/unix/sysv/linux/mips/mips32/nofpu/libc.abilist b/sysdeps/unix/sysv/linux/mips/mips32/nofpu/libc.abilist
> index 5319fdc204..611ece2ac4 100644
> --- a/sysdeps/unix/sysv/linux/mips/mips32/nofpu/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/mips/mips32/nofpu/libc.abilist
> @@ -2741,6 +2741,10 @@ GLIBC_2.38 __isoc23_wcstoull F
>   GLIBC_2.38 __isoc23_wcstoull_l F
>   GLIBC_2.38 __isoc23_wcstoumax F
>   GLIBC_2.38 __isoc23_wscanf F
> +GLIBC_2.38 __strlcat_chk F
> +GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 strlcat F
> +GLIBC_2.38 strlcpy F
>   GLIBC_2.4 __confstr_chk F
>   GLIBC_2.4 __fgets_chk F
>   GLIBC_2.4 __fgets_unlocked_chk F
> diff --git a/sysdeps/unix/sysv/linux/mips/mips64/n32/libc.abilist b/sysdeps/unix/sysv/linux/mips/mips64/n32/libc.abilist
> index 1743ea6eb9..0af286fda1 100644
> --- a/sysdeps/unix/sysv/linux/mips/mips64/n32/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/mips/mips64/n32/libc.abilist
> @@ -2749,6 +2749,10 @@ GLIBC_2.38 __isoc23_wcstoull F
>   GLIBC_2.38 __isoc23_wcstoull_l F
>   GLIBC_2.38 __isoc23_wcstoumax F
>   GLIBC_2.38 __isoc23_wscanf F
> +GLIBC_2.38 __strlcat_chk F
> +GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 strlcat F
> +GLIBC_2.38 strlcpy F
>   GLIBC_2.4 __confstr_chk F
>   GLIBC_2.4 __fgets_chk F
>   GLIBC_2.4 __fgets_unlocked_chk F
> diff --git a/sysdeps/unix/sysv/linux/mips/mips64/n64/libc.abilist b/sysdeps/unix/sysv/linux/mips/mips64/n64/libc.abilist
> index 9b1f53c6ac..8285f2196e 100644
> --- a/sysdeps/unix/sysv/linux/mips/mips64/n64/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/mips/mips64/n64/libc.abilist
> @@ -2651,6 +2651,10 @@ GLIBC_2.38 __isoc23_wcstoull F
>   GLIBC_2.38 __isoc23_wcstoull_l F
>   GLIBC_2.38 __isoc23_wcstoumax F
>   GLIBC_2.38 __isoc23_wscanf F
> +GLIBC_2.38 __strlcat_chk F
> +GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 strlcat F
> +GLIBC_2.38 strlcpy F
>   GLIBC_2.4 __confstr_chk F
>   GLIBC_2.4 __fgets_chk F
>   GLIBC_2.4 __fgets_unlocked_chk F
> diff --git a/sysdeps/unix/sysv/linux/nios2/libc.abilist b/sysdeps/unix/sysv/linux/nios2/libc.abilist
> index ae1c6ca1b5..c7144d7cd8 100644
> --- a/sysdeps/unix/sysv/linux/nios2/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/nios2/libc.abilist
> @@ -2790,3 +2790,7 @@ GLIBC_2.38 __isoc23_wcstoull F
>   GLIBC_2.38 __isoc23_wcstoull_l F
>   GLIBC_2.38 __isoc23_wcstoumax F
>   GLIBC_2.38 __isoc23_wscanf F
> +GLIBC_2.38 __strlcat_chk F
> +GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 strlcat F
> +GLIBC_2.38 strlcpy F
> diff --git a/sysdeps/unix/sysv/linux/or1k/libc.abilist b/sysdeps/unix/sysv/linux/or1k/libc.abilist
> index a7c572c947..bb43247795 100644
> --- a/sysdeps/unix/sysv/linux/or1k/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/or1k/libc.abilist
> @@ -2172,3 +2172,7 @@ GLIBC_2.38 __isoc23_wcstoull F
>   GLIBC_2.38 __isoc23_wcstoull_l F
>   GLIBC_2.38 __isoc23_wcstoumax F
>   GLIBC_2.38 __isoc23_wscanf F
> +GLIBC_2.38 __strlcat_chk F
> +GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 strlcat F
> +GLIBC_2.38 strlcpy F
> diff --git a/sysdeps/unix/sysv/linux/powerpc/powerpc32/fpu/libc.abilist b/sysdeps/unix/sysv/linux/powerpc/powerpc32/fpu/libc.abilist
> index 074fa031a7..7cc5660830 100644
> --- a/sysdeps/unix/sysv/linux/powerpc/powerpc32/fpu/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/powerpc/powerpc32/fpu/libc.abilist
> @@ -2817,6 +2817,10 @@ GLIBC_2.38 __nldbl___isoc23_vsscanf F
>   GLIBC_2.38 __nldbl___isoc23_vswscanf F
>   GLIBC_2.38 __nldbl___isoc23_vwscanf F
>   GLIBC_2.38 __nldbl___isoc23_wscanf F
> +GLIBC_2.38 __strlcat_chk F
> +GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 strlcat F
> +GLIBC_2.38 strlcpy F
>   GLIBC_2.4 _IO_fprintf F
>   GLIBC_2.4 _IO_printf F
>   GLIBC_2.4 _IO_sprintf F
> diff --git a/sysdeps/unix/sysv/linux/powerpc/powerpc32/nofpu/libc.abilist b/sysdeps/unix/sysv/linux/powerpc/powerpc32/nofpu/libc.abilist
> index dfcb4bd2d5..dd290af782 100644
> --- a/sysdeps/unix/sysv/linux/powerpc/powerpc32/nofpu/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/powerpc/powerpc32/nofpu/libc.abilist
> @@ -2850,6 +2850,10 @@ GLIBC_2.38 __nldbl___isoc23_vsscanf F
>   GLIBC_2.38 __nldbl___isoc23_vswscanf F
>   GLIBC_2.38 __nldbl___isoc23_vwscanf F
>   GLIBC_2.38 __nldbl___isoc23_wscanf F
> +GLIBC_2.38 __strlcat_chk F
> +GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 strlcat F
> +GLIBC_2.38 strlcpy F
>   GLIBC_2.4 _IO_fprintf F
>   GLIBC_2.4 _IO_printf F
>   GLIBC_2.4 _IO_sprintf F
> diff --git a/sysdeps/unix/sysv/linux/powerpc/powerpc64/be/libc.abilist b/sysdeps/unix/sysv/linux/powerpc/powerpc64/be/libc.abilist
> index 63bbccf3f9..f2b001402c 100644
> --- a/sysdeps/unix/sysv/linux/powerpc/powerpc64/be/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/powerpc/powerpc64/be/libc.abilist
> @@ -2571,6 +2571,10 @@ GLIBC_2.38 __nldbl___isoc23_vsscanf F
>   GLIBC_2.38 __nldbl___isoc23_vswscanf F
>   GLIBC_2.38 __nldbl___isoc23_vwscanf F
>   GLIBC_2.38 __nldbl___isoc23_wscanf F
> +GLIBC_2.38 __strlcat_chk F
> +GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 strlcat F
> +GLIBC_2.38 strlcpy F
>   GLIBC_2.4 _IO_fprintf F
>   GLIBC_2.4 _IO_printf F
>   GLIBC_2.4 _IO_sprintf F
> diff --git a/sysdeps/unix/sysv/linux/powerpc/powerpc64/le/libc.abilist b/sysdeps/unix/sysv/linux/powerpc/powerpc64/le/libc.abilist
> index ab85fd61ef..9cc431666e 100644
> --- a/sysdeps/unix/sysv/linux/powerpc/powerpc64/le/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/powerpc/powerpc64/le/libc.abilist
> @@ -2885,3 +2885,7 @@ GLIBC_2.38 __nldbl___isoc23_vsscanf F
>   GLIBC_2.38 __nldbl___isoc23_vswscanf F
>   GLIBC_2.38 __nldbl___isoc23_vwscanf F
>   GLIBC_2.38 __nldbl___isoc23_wscanf F
> +GLIBC_2.38 __strlcat_chk F
> +GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 strlcat F
> +GLIBC_2.38 strlcpy F
> diff --git a/sysdeps/unix/sysv/linux/riscv/rv32/libc.abilist b/sysdeps/unix/sysv/linux/riscv/rv32/libc.abilist
> index b716f5c763..b9b725f913 100644
> --- a/sysdeps/unix/sysv/linux/riscv/rv32/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/riscv/rv32/libc.abilist
> @@ -2428,3 +2428,7 @@ GLIBC_2.38 __isoc23_wcstoull F
>   GLIBC_2.38 __isoc23_wcstoull_l F
>   GLIBC_2.38 __isoc23_wcstoumax F
>   GLIBC_2.38 __isoc23_wscanf F
> +GLIBC_2.38 __strlcat_chk F
> +GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 strlcat F
> +GLIBC_2.38 strlcpy F
> diff --git a/sysdeps/unix/sysv/linux/riscv/rv64/libc.abilist b/sysdeps/unix/sysv/linux/riscv/rv64/libc.abilist
> index 774e777b65..e0f4863856 100644
> --- a/sysdeps/unix/sysv/linux/riscv/rv64/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/riscv/rv64/libc.abilist
> @@ -2628,3 +2628,7 @@ GLIBC_2.38 __isoc23_wcstoull F
>   GLIBC_2.38 __isoc23_wcstoull_l F
>   GLIBC_2.38 __isoc23_wcstoumax F
>   GLIBC_2.38 __isoc23_wscanf F
> +GLIBC_2.38 __strlcat_chk F
> +GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 strlcat F
> +GLIBC_2.38 strlcpy F
> diff --git a/sysdeps/unix/sysv/linux/s390/s390-32/libc.abilist b/sysdeps/unix/sysv/linux/s390/s390-32/libc.abilist
> index 8625135c48..8db68fcea7 100644
> --- a/sysdeps/unix/sysv/linux/s390/s390-32/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/s390/s390-32/libc.abilist
> @@ -2815,6 +2815,10 @@ GLIBC_2.38 __nldbl___isoc23_vsscanf F
>   GLIBC_2.38 __nldbl___isoc23_vswscanf F
>   GLIBC_2.38 __nldbl___isoc23_vwscanf F
>   GLIBC_2.38 __nldbl___isoc23_wscanf F
> +GLIBC_2.38 __strlcat_chk F
> +GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 strlcat F
> +GLIBC_2.38 strlcpy F
>   GLIBC_2.4 _IO_fprintf F
>   GLIBC_2.4 _IO_printf F
>   GLIBC_2.4 _IO_sprintf F
> diff --git a/sysdeps/unix/sysv/linux/s390/s390-64/libc.abilist b/sysdeps/unix/sysv/linux/s390/s390-64/libc.abilist
> index d00c7eb262..ec9747b7ea 100644
> --- a/sysdeps/unix/sysv/linux/s390/s390-64/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/s390/s390-64/libc.abilist
> @@ -2608,6 +2608,10 @@ GLIBC_2.38 __nldbl___isoc23_vsscanf F
>   GLIBC_2.38 __nldbl___isoc23_vswscanf F
>   GLIBC_2.38 __nldbl___isoc23_vwscanf F
>   GLIBC_2.38 __nldbl___isoc23_wscanf F
> +GLIBC_2.38 __strlcat_chk F
> +GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 strlcat F
> +GLIBC_2.38 strlcpy F
>   GLIBC_2.4 _IO_fprintf F
>   GLIBC_2.4 _IO_printf F
>   GLIBC_2.4 _IO_sprintf F
> diff --git a/sysdeps/unix/sysv/linux/sh/be/libc.abilist b/sysdeps/unix/sysv/linux/sh/be/libc.abilist
> index b63037241d..9576b818d8 100644
> --- a/sysdeps/unix/sysv/linux/sh/be/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/sh/be/libc.abilist
> @@ -2658,6 +2658,10 @@ GLIBC_2.38 __isoc23_wcstoull F
>   GLIBC_2.38 __isoc23_wcstoull_l F
>   GLIBC_2.38 __isoc23_wcstoumax F
>   GLIBC_2.38 __isoc23_wscanf F
> +GLIBC_2.38 __strlcat_chk F
> +GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 strlcat F
> +GLIBC_2.38 strlcpy F
>   GLIBC_2.4 __confstr_chk F
>   GLIBC_2.4 __fgets_chk F
>   GLIBC_2.4 __fgets_unlocked_chk F
> diff --git a/sysdeps/unix/sysv/linux/sh/le/libc.abilist b/sysdeps/unix/sysv/linux/sh/le/libc.abilist
> index d80055617d..b67b1b2bb5 100644
> --- a/sysdeps/unix/sysv/linux/sh/le/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/sh/le/libc.abilist
> @@ -2655,6 +2655,10 @@ GLIBC_2.38 __isoc23_wcstoull F
>   GLIBC_2.38 __isoc23_wcstoull_l F
>   GLIBC_2.38 __isoc23_wcstoumax F
>   GLIBC_2.38 __isoc23_wscanf F
> +GLIBC_2.38 __strlcat_chk F
> +GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 strlcat F
> +GLIBC_2.38 strlcpy F
>   GLIBC_2.4 __confstr_chk F
>   GLIBC_2.4 __fgets_chk F
>   GLIBC_2.4 __fgets_unlocked_chk F
> diff --git a/sysdeps/unix/sysv/linux/sparc/sparc32/libc.abilist b/sysdeps/unix/sysv/linux/sparc/sparc32/libc.abilist
> index 5be55c11d2..b251fc9c69 100644
> --- a/sysdeps/unix/sysv/linux/sparc/sparc32/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/sparc/sparc32/libc.abilist
> @@ -2810,6 +2810,10 @@ GLIBC_2.38 __nldbl___isoc23_vsscanf F
>   GLIBC_2.38 __nldbl___isoc23_vswscanf F
>   GLIBC_2.38 __nldbl___isoc23_vwscanf F
>   GLIBC_2.38 __nldbl___isoc23_wscanf F
> +GLIBC_2.38 __strlcat_chk F
> +GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 strlcat F
> +GLIBC_2.38 strlcpy F
>   GLIBC_2.4 _IO_fprintf F
>   GLIBC_2.4 _IO_printf F
>   GLIBC_2.4 _IO_sprintf F
> diff --git a/sysdeps/unix/sysv/linux/sparc/sparc64/libc.abilist b/sysdeps/unix/sysv/linux/sparc/sparc64/libc.abilist
> index 475fdaae15..5ef9bbec34 100644
> --- a/sysdeps/unix/sysv/linux/sparc/sparc64/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/sparc/sparc64/libc.abilist
> @@ -2623,6 +2623,10 @@ GLIBC_2.38 __isoc23_wcstoull F
>   GLIBC_2.38 __isoc23_wcstoull_l F
>   GLIBC_2.38 __isoc23_wcstoumax F
>   GLIBC_2.38 __isoc23_wscanf F
> +GLIBC_2.38 __strlcat_chk F
> +GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 strlcat F
> +GLIBC_2.38 strlcpy F
>   GLIBC_2.4 __confstr_chk F
>   GLIBC_2.4 __fgets_chk F
>   GLIBC_2.4 __fgets_unlocked_chk F
> diff --git a/sysdeps/unix/sysv/linux/x86_64/64/libc.abilist b/sysdeps/unix/sysv/linux/x86_64/64/libc.abilist
> index 6cfb928bc8..9ad800b62e 100644
> --- a/sysdeps/unix/sysv/linux/x86_64/64/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/x86_64/64/libc.abilist
> @@ -2574,6 +2574,10 @@ GLIBC_2.38 __isoc23_wcstoull F
>   GLIBC_2.38 __isoc23_wcstoull_l F
>   GLIBC_2.38 __isoc23_wcstoumax F
>   GLIBC_2.38 __isoc23_wscanf F
> +GLIBC_2.38 __strlcat_chk F
> +GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 strlcat F
> +GLIBC_2.38 strlcpy F
>   GLIBC_2.4 __confstr_chk F
>   GLIBC_2.4 __fgets_chk F
>   GLIBC_2.4 __fgets_unlocked_chk F
> diff --git a/sysdeps/unix/sysv/linux/x86_64/x32/libc.abilist b/sysdeps/unix/sysv/linux/x86_64/x32/libc.abilist
> index c735097172..6a3a66c5d4 100644
> --- a/sysdeps/unix/sysv/linux/x86_64/x32/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/x86_64/x32/libc.abilist
> @@ -2680,3 +2680,7 @@ GLIBC_2.38 __isoc23_wcstoull F
>   GLIBC_2.38 __isoc23_wcstoull_l F
>   GLIBC_2.38 __isoc23_wcstoumax F
>   GLIBC_2.38 __isoc23_wscanf F
> +GLIBC_2.38 __strlcat_chk F
> +GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 strlcat F
> +GLIBC_2.38 strlcpy F
Florian Weimer April 6, 2023, 3:09 p.m. UTC | #4
* Siddhesh Poyarekar:

> On 2023-04-05 07:20, Florian Weimer via Libc-alpha wrote:
>> --- a/debug/Makefile
>> +++ b/debug/Makefile
>> @@ -31,6 +31,7 @@ headers	:= execinfo.h
>>   routines  = backtrace backtracesyms backtracesymsfd noophooks \
>>   	    memcpy_chk memmove_chk mempcpy_chk memset_chk stpcpy_chk \
>>   	    strcat_chk strcpy_chk strncat_chk strncpy_chk stpncpy_chk \
>> +	    strlcpy_chk strlcat_chk \
>
> Do you want to commit an obvious change first to update this routine
> list to be one per line?

I supoose I could do that, also for string and wcsmbs.

>> diff --git a/manual/string.texi b/manual/string.texi
>> index e06433187e..e3979f1d0f 100644
>> --- a/manual/string.texi
>> +++ b/manual/string.texi
>> @@ -1068,6 +1068,60 @@ processing text.  Also, this function has significant performance
>>   issues.  @xref{Concatenating Strings}.
>>   @end deftypefun
>>   +@deftypefun size_t strlcpy (char *restrict @var{to}, const char
>> *restrict @var{from}, size_t @var{size})
>> +@standards{BSD, string.h}
>
> Should we also mention POSIX here?

It would be misleading because it's not possible to enable this using
_POSIX_SOURCE, I think.

Thanks,
Florian
Alejandro Colomar April 6, 2023, 9:21 p.m. UTC | #5
Hi Florian,

On 4/5/23 13:20, Florian Weimer via Libc-alpha wrote:
> 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
> inheritis the non-failing regular strlcat behavior.  Maybe this
> should be changed to catch secondary overflows because the string
> appears longer than the buffer.
> ---

[...]

> +size_t
> +__strlcat (char *__restrict dest, const char *__restrict src, size_t size)
> +{

strlcat(3) is a slow call that shouldn't be emitted by an optimizing
compiler (similar to how GCC optimizes strcat(3) to stpcpy(3)).

With that in mind, do we really want all this code?  Or maybe we just
want to implement it in the simplest possible way?

I suggest the following alternative:


size_t
strlcat(char *restrict dst, const char *restrict src, size_t dsize)
{
    size_t  dlen = strlen(dst);

    return strlcpy(dst + dlen, src, dsize - dlen) + dlen;
}


In fact, that's the code I would expect that GCC should emit instead
of calls to the actual strlcat(3).

(Disclaimer: I didn't test the code; it may likely have bugs)

> +  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);

The OpenBSD contract of strlcat(3) includes that _both_ the source
string and the destination strings are NULL-terminated.  I guess
POSIX has kept that contract.  If that's the case, we can just call
strlen(3) here.

> +  if (dest_length != size)

This should always happen.  Else, it is undefined, because it means
that the destination string wasn't terminated.

Relevant quote of BSD's man page:

[
                               ...  Also note  that  strlcpy()  and  strlcat()
       only  operate  on  true “C” strings.  This means that ...
                              ... for strlcat() both src and dst must be  NUL‐
       terminated.
]

So the conditional can be omitted.


> +    {
> +      /* 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)

[...]

> +
> +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;
> +}

L(very)GTM. :)


Cheers,
Alex
Alejandro Colomar April 6, 2023, 9:29 p.m. UTC | #6
Hi Siddhesh,

On 4/6/23 16:22, Siddhesh Poyarekar wrote:
[...]

>> +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);
> 
> size == 0 is undefined anyway; we return without touching the dest 
> because that's convenient for us.  OK.

Is it really convenient?  What real code benefits of that behavior?
If we remove the conditional it's one less op.

Cheers,

Alex

> 
>> +  return src_length;
>> +}
Florian Weimer April 6, 2023, 9:35 p.m. UTC | #7
* Alejandro Colomar via Libc-alpha:

>> +  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);
>
> The OpenBSD contract of strlcat(3) includes that _both_ the source
> string and the destination strings are NULL-terminated.  I guess
> POSIX has kept that contract.  If that's the case, we can just call
> strlen(3) here.

NetBSD says this:

| Note however, that if strlcat() traverses size characters without
| finding a NUL, the length of the string is considered to be size and
| the destination string will not be NUL-terminated (since there was
| no space for the NUL).  This keeps strlcat() from running off the
| end of a string.  In practice this should not happen (as it means
| that either size is incorrect or that dst is not a proper ``C''
| string).  The check exists to prevent potential security problems in
| incorrect code.

<https://man.netbsd.org/strlcat.3>

OpenBSD alludes to this as well:

| strlcat() appends string src to the end of dst. It will append at
| most dstsize - strlen(dst) - 1 characters. It will then
| NUL-terminate, unless dstsize is 0 or the original dst string was
| longer than dstsize (in practice this should not happen as it means
| that either dstsize is incorrect or that dst is not a proper
| string).

<https://man.openbsd.org/strlcat>

So I think we should be calling strnlen here.  If we call strlen
instead, we'd have to bound the result.

Thanks,
Florian
Alejandro Colomar April 6, 2023, 10:15 p.m. UTC | #8
Hi Florian,

On 4/6/23 23:35, Florian Weimer wrote:
[...]

> 
> OpenBSD alludes to this as well:
> 
> | strlcat() appends string src to the end of dst. It will append at
> | most dstsize - strlen(dst) - 1 characters. It will then
> | NUL-terminate, unless dstsize is 0 or the original dst string was
> | longer than dstsize (in practice this should not happen as it means
> | that either dstsize is incorrect or that dst is not a proper
> | string).

Hmm, that text replaced the one I quoted (which has been kept in
libbsd's page), in this commit:


commit 5df0f979487d1950d0e8885be683158e55ff4ae3
Author: deraadt <deraadt@openbsd.org>
Date:   Mon Apr 2 17:33:11 2012 +0000

    simplify the strlcpy/strlcat manual page substantially.  do less
    explaining of "what a C string is", and make it more clear that these
    functiosn BEHAVE EXACTLY LIKE snprintf with "%s"!  (anyone who wants
    to write a 'strlcpy considered harmful' paper should probably write a
    'strlcpy and snprintf considered harmful' paper instead).
    
    note to those from other projects reading this commit message: It would
    be very good if this new manual was picked up in your project.
    
    ok jmc millert krw

> 
> <https://man.openbsd.org/strlcat>
> 
> So I think we should be calling strnlen here.  If we call strlen
> instead, we'd have to bound the result.

Hmm, it depends on what we decide to do with the conditional right
after the strnlen(3) call.  Either we remove the conditional, or
transform strnlen(3) to strlen(3), but not both.  I guess strnlen(3)
is more readable, since it's just one line.

Anyway, as I said, I would just burn it all and make this function
be a two-liner.  :)

Cheers,
Alex

> 
> Thanks,
> Florian
Alejandro Colomar April 6, 2023, 10:19 p.m. UTC | #9
On 4/6/23 23:35, Florian Weimer wrote:
> NetBSD says this:
> 
> | Note however, that if strlcat() traverses size characters without
> | finding a NUL, the length of the string is considered to be size and
> | the destination string will not be NUL-terminated (since there was
> | no space for the NUL).  This keeps strlcat() from running off the
> | end of a string.  In practice this should not happen (as it means
> | that either size is incorrect or that dst is not a proper ``C''
> | string).  The check exists to prevent potential security problems in
> | incorrect code.
> 
> <https://man.netbsd.org/strlcat.3>
> 
> OpenBSD alludes to this as well:
> 
> | strlcat() appends string src to the end of dst. It will append at
> | most dstsize - strlen(dst) - 1 characters. It will then
> | NUL-terminate, unless dstsize is 0 or the original dst string was
> | longer than dstsize (in practice this should not happen as it means
> | that either dstsize is incorrect or that dst is not a proper
> | string).
> 
> <https://man.openbsd.org/strlcat>
> 
> So I think we should be calling strnlen here.  If we call strlen
> instead, we'd have to bound the result.

AFAIR, the design behind strlcpy(3) and cat(3) was that they would
intentionally overrun the buffers (read-only) to force crashes as
much as possible, which would uncover bugs in the code, rather than
silently continuing.  Don't know why they changed that.  Since it's
just reading the string without writing to it, I don't think anything
worse than a crash could possibly happen.

Cheers,
Alex

> 
> Thanks,
> Florian
Alejandro Colomar April 6, 2023, 10:34 p.m. UTC | #10
On 4/6/23 23:21, Alejandro Colomar wrote:
> strlcat(3) is a slow call that shouldn't be emitted by an optimizing
> compiler (similar to how GCC optimizes strcat(3) to stpcpy(3)).
> 
> With that in mind, do we really want all this code?  Or maybe we just
> want to implement it in the simplest possible way?
> 
> I suggest the following alternative:
> 
> 
> size_t
> strlcat(char *restrict dst, const char *restrict src, size_t dsize)
> {
>     size_t  dlen = strlen(dst);

This needs to be strnlen(dst, dsize), to avoid passing a huge size to
strlcpy(3).  This seems a good reason for having defined behavior for
strlcpy(x, y, 0).

> 
>     return strlcpy(dst + dlen, src, dsize - dlen) + dlen;
> }
> 
> 
> In fact, that's the code I would expect that GCC should emit instead
> of calls to the actual strlcat(3).
> 
> (Disclaimer: I didn't test the code; it may likely have bugs)
>
Paul Eggert April 8, 2023, 10:08 p.m. UTC | #11
On 2023-04-05 04:20, Florian Weimer via Libc-alpha wrote:

> The fortified strlcat implementation does not raise SIGABRT if the
> destination buffer does not contain a null terminator, it just
> inheritis the non-failing regular strlcat behavior.  Maybe this
> should be changed to catch secondary overflows because the string
> appears longer than the buffer.

Shouldn't it should work like fortified strcat, which raises SIGABRT if 
the destination is not null-terminated? (Admittedly this isn't important.)


> +* The strlcpy and strlcat functions have been added.  They are derived
> +  from OpenBSD, and are expected to be added to a future POSIX versions.

versions → version

Please see the attached patch for a fix for this.


> +* The functions strlcpy and strlcat have been added.

That's a duplicate in NEWS.


> +extern __typeof (strlcpy) __strlcpy;
> +libc_hidden_proto (__strlcpy)
> +extern __typeof (strlcat) __strlcat;
> +libc_hidden_proto (__strlcat)

Glibc shouldn't call these functions internally, so let's not export 
them to elsewhere in glibc.

manual/maint.texi needs changing too (see where it discusses strncpy).


> --- a/manual/string.texi
> +++ b/manual/string.texi

When the manual discusses strcat/strncat's O(N**2) drawback it should do 
the same for strlcat.


> +This function is similar to @code{strcpy}, but copies at most
> +@var{size} bytes from the string @var{from} into the destination
> +array @var{to}, including a terminating null byte.

This should mention draft POSIX's recommendation that the caller should 
ensure that SIZE has room for the terminating null. Also, it shouldn't 
say it copies at most SIZE bytes because it's really at most (MAX (1, 
SIZE) - 1). There are several errors of this sort later on - for 
example, the overlapping move constraint doesn't apply to the null 
terminator. I think I caught the errors in the attached patch but 
another pair of eyes wouldn't hurt.

Also, let's use similar wording for strlcpy and strlcat to avoid having 
the readers think that there are differences when there aren't any.


> +The return value @var{result} of @code{strlcpy} is the length of the
> +string @var{from}.  This means that @samp{@var{result} >= @var{size}} is
> +true whenever truncation occurs.

It's not just "whenever" (i.e., if); it's if and only if.


> +The behavior of @code{strlcpy} is undefined if @var{size} is zero, or if
> +the source string and the first @var{size} bytes of the destination
> +array overlap.

Actually, the behavior is well-defined if SIZE is zero. It's also 
well-defined in some cases when the first SIZE bytes overlap: the only 
constraint is that the region copied from cannot overlap with the region 
copied to (in both cases, not counting any terminating null). However, 
behavior is undefined if either pointer is null, or if the destination 
array isn't big enough.

The description should allude to the truncation-warning discussion below.

There are similar issues for the strlcat doc, which the attached patch 
also attempts to fix.


> +  /* 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");

Change the "==" to "<=" (since that's what is meant here) and clarify 
the comment which is a bit confusing. Better yet, let's omit this and 
all related code and go with the OpenBSD implementation.


> +	  /* 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';

This micro-optimization is incorrect, as it's valid for dest to equal 
src + size - 1, and that means the memcpy overlaps which is undefined. 
Change it to memcpy (dest, src, size - 1) and lose the comment. Or 
change it to memmove and lengthen the comment. Or better yet, get rid of 
all code like this (there are other instances), and use the simple 
OpenBSD implementation which will more likely match what callers expect 
(in the rare cases where the behaviors differ) and will possibly be 
faster despite not using memcpy.


Proposed patch for NEWS and manual attached; the other problems remain 
unfixed.
Paul Eggert April 9, 2023, 3:29 p.m. UTC | #12
On 2023-04-08 15:08, Paul Eggert wrote:

> the overlapping move constraint doesn't apply to the null 
> terminator.

Oh, sorry, scratch that: it does apply, because of the 'restrict' 
constraints on the argument pointers. Hence (contrary to my previous 
assertion) PATCH 1/2's code does conform to draft POSIX.

However, it's still worrisome that PATCH 1/2's behavior disagrees with 
OpenBSD's for some nonconforming calls. If the main point of the patch 
is compatibility, surely it is better to be compatible with existing 
practice, not merely with draft POSIX.

Anyway, please ignore the documentation patch I sent yesterday in this 
thread, and look instead at the attached. This is the same patch, except 
with fixed wording under strlcpy and strlcat as to whether behavior is 
undefined.
Siddhesh Poyarekar April 11, 2023, 2:28 p.m. UTC | #13
On 2023-04-06 17:29, Alejandro Colomar wrote:
> Hi Siddhesh,
> 
> On 4/6/23 16:22, Siddhesh Poyarekar wrote:
> [...]
> 
>>> +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);
>>
>> size == 0 is undefined anyway; we return without touching the dest
>> because that's convenient for us.  OK.
> 
> Is it really convenient?  What real code benefits of that behavior?
> If we remove the conditional it's one less op.

Hmm, and then maybe abort in __strlcpy_chk for size == 0.

Thanks,
Sid
Florian Weimer April 13, 2023, 11:37 a.m. UTC | #14
* Paul Eggert:

> +The behavior is undefined if @var{to} or @var{from} is a null pointer,
> +or if the destination array's size is both less than @var{size} and
> +less than or equal to the length of the string @var{from}, or if
> +the string @var{from} overlaps the result (that is, if @var{from}
> +overlaps the first @samp{MIN (@var{size}, strlen (@var{from}) + 1)}
> +bytes of the the destination array @var{to}).

This makes it defined to call strlcpy with an incorrect (too small)
destination buffer size as long as the source string is short enough.
Is this really necessary?  The fortified implementation checks the
destination buffer size unconditionally (like we do for other
functions), and it does not match this documentation as a result.

Thanks,
Florian
Paul Eggert April 13, 2023, 2:39 p.m. UTC | #15
On 4/13/23 04:37, Florian Weimer wrote:
> This makes it defined to call strlcpy with an incorrect (too small)
> destination buffer size as long as the source string is short enough.
> Is this really necessary?  The fortified implementation checks the
> destination buffer size unconditionally (like we do for other
> functions), and it does not match this documentation as a result.

Oh, good point. Revised patch attached. It also says behavior is 
undefined if the source string overlaps the destination buffer. Although 
the fortified implementation doesn't check that, we should preserve the 
ability to check for that in the future, as it's a true danger sign.
Paul Eggert April 13, 2023, 5:59 p.m. UTC | #16
On 4/13/23 07:39, Paul Eggert wrote:
> Oh, good point. Revised patch attached. It also says behavior is 
> undefined if the source string overlaps the destination buffer.

Ouch, I sent the wrong patch. Sorry about that. Please look at the 
attached instead; it matches the intent stated above (plus it's shorter 
and easier to read).
Florian Weimer April 20, 2023, 8:07 a.m. UTC | #17
* Paul Eggert:

>> +extern __typeof (strlcpy) __strlcpy;
>> +libc_hidden_proto (__strlcpy)
>> +extern __typeof (strlcat) __strlcat;
>> +libc_hidden_proto (__strlcat)
>
> Glibc shouldn't call these functions internally, so let's not export
> them to elsewhere in glibc.

strlcpy looks like it could be called for implementing %s in snprintf.
That seems like a reasonable optimization.  We would even use the
returned length in case the string does not fit.

Less sure about strlcat, we could drop the PLT avoidance for that, I
assume.

>> +	  /* 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';
>
> This micro-optimization is incorrect, as it's valid for dest to equal

I think we concluded that this optimization is in fact correct, right?

> src + size - 1, and that means the memcpy overlaps which is
> undefined. Change it to memcpy (dest, src, size - 1) and lose the
> comment. Or change it to memmove and lengthen the comment. Or better
> yet, get rid of all code like this (there are other instances), and
> use the simple OpenBSD implementation which will more likely match
> what callers expect (in the rare cases where the behaviors differ) and
> will possibly be faster despite not using memcpy.

I expect someone to rewrite this using word-size accesses fairly soon.
I think using strlen and memcpy more clearly documents the intent than
the explicit loops.

Thanks,
Florian
Florian Weimer April 20, 2023, 10:55 a.m. UTC | #18
* Siddhesh Poyarekar:

>> index 673cfd7272..0c78ad2539 100644
>> --- a/include/string.h
>> +++ b/include/string.h
>> @@ -88,6 +88,10 @@ libc_hidden_proto (__stpcpy)
>>   # define __stpcpy(dest, src) __builtin_stpcpy (dest, src)
>>   #endif
>>   libc_hidden_proto (__stpncpy)
>> +extern __typeof (strlcpy) __strlcpy;
>> +libc_hidden_proto (__strlcpy)
>> +extern __typeof (strlcat) __strlcat;
>> +libc_hidden_proto (__strlcat)
>>   libc_hidden_proto (__rawmemchr)
>>   libc_hidden_proto (__strcasecmp)
>>   libc_hidden_proto (__strcasecmp_l)
>
> Do we want to delay doing this until we have an actual internal use of
> these interfaces?

The *_chk functions need these aliases today.

>> +__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 */
>> +
>
> Couldn't we use the __glibc_fortify macros here?

Do you have a concrete proposal?  I was just following what we have for
stpncpy and other functions.

I don't think it's possible to use the generic macros for
wcslcpy/wcslcat because of the bytes vs wide characters distinction.

>> +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);
>
> size == 0 is undefined anyway; we return without touching the dest
> because that's convenient for us.  OK.

size == 0 is defined in OpenBSD via snprintf equivalence, but maybe
that's over-interpreting the manual page.

Thanks,
Florian
Siddhesh Poyarekar April 20, 2023, 11:45 a.m. UTC | #19
On 2023-04-20 06:55, Florian Weimer wrote:
> * Siddhesh Poyarekar:
> 
>>> index 673cfd7272..0c78ad2539 100644
>>> --- a/include/string.h
>>> +++ b/include/string.h
>>> @@ -88,6 +88,10 @@ libc_hidden_proto (__stpcpy)
>>>    # define __stpcpy(dest, src) __builtin_stpcpy (dest, src)
>>>    #endif
>>>    libc_hidden_proto (__stpncpy)
>>> +extern __typeof (strlcpy) __strlcpy;
>>> +libc_hidden_proto (__strlcpy)
>>> +extern __typeof (strlcat) __strlcat;
>>> +libc_hidden_proto (__strlcat)
>>>    libc_hidden_proto (__rawmemchr)
>>>    libc_hidden_proto (__strcasecmp)
>>>    libc_hidden_proto (__strcasecmp_l)
>>
>> Do we want to delay doing this until we have an actual internal use of
>> these interfaces?
> 
> The *_chk functions need these aliases today.

Ack.

>>> +__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 */
>>> +
>>
>> Couldn't we use the __glibc_fortify macros here?
> 
> Do you have a concrete proposal?  I was just following what we have for
> stpncpy and other functions.

I don't, but I'll give it a shot.

> I don't think it's possible to use the generic macros for
> wcslcpy/wcslcat because of the bytes vs wide characters distinction.

Hmm, there's __glibc_fortify_n for wide chars which __wcsncpy_chk uses, 
or have I misunderstood your comment?

>>> +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);
>>
>> size == 0 is undefined anyway; we return without touching the dest
>> because that's convenient for us.  OK.
> 
> size == 0 is defined in OpenBSD via snprintf equivalence, but maybe
> that's over-interpreting the manual page.

Doesn't seem like over-interpreting to me:

"The strlcpy() and strlcat() functions copy and concatenate strings with 
the same input parameters and output result as snprintf(3)"

We should probably just pick a lane then (and specify it in the manual), 
and snprintf equivalence is probably a reasonable take in terms of safety.

Thanks,
Sid
Florian Weimer April 21, 2023, 5:45 p.m. UTC | #20
* Siddhesh Poyarekar:

>> I don't think it's possible to use the generic macros for
>> wcslcpy/wcslcat because of the bytes vs wide characters distinction.
>
> Hmm, there's __glibc_fortify_n for wide chars which __wcsncpy_chk
> uses, or have I misunderstood your comment?

The new w*_chk APIs have both byte-size and character-size arguments,
based on an expectation that it simplifies passing dynamic object sizes
(because allocators measure sizes in bytes).

>> size == 0 is defined in OpenBSD via snprintf equivalence, but maybe
>> that's over-interpreting the manual page.
>
> Doesn't seem like over-interpreting to me:
>
> "The strlcpy() and strlcat() functions copy and concatenate strings
> with the same input parameters and output result as snprintf(3)"

Well, that definitely requires *some* interpretation because you cannot
implement strlcat directly with snprintf because snprintf inputs and the
output buffer cannot overlap. 8-)  So it's difficult for me to see how
the size == 0 case would be covered.

Thanks,
Florian
Paul Eggert April 21, 2023, 7 p.m. UTC | #21
On 2023-04-20 01:07, Florian Weimer wrote:
> * Paul Eggert:
> 
>>> +extern __typeof (strlcpy) __strlcpy;
>>> +libc_hidden_proto (__strlcpy)
>>> +extern __typeof (strlcat) __strlcat;
>>> +libc_hidden_proto (__strlcat)
>>
>> Glibc shouldn't call these functions internally, so let's not export
>> them to elsewhere in glibc.
> 
> strlcpy looks like it could be called for implementing %s in snprintf.
> That seems like a reasonable optimization.

No, because strlcpy must return the length of the source even when it's 
longer than INT_MAX. (This is a botch in the spec which we apparently 
cannot fix.) So there's no way snprintf could use strlcpy without 
hurting worst-case performance.


> Less sure about strlcat, we could drop the PLT avoidance for that, I
> assume.

Let's drop it for both. If there's ever a real need for either (which I 
doubt) we can add it as needed.


> I expect someone to rewrite this using word-size accesses fairly soon.

That would be headed in the wrong direction. We should not waste time 
trying to optimize these functions' copying actions, as the destinations 
are invariably so small that our attempts to "optimize" will likely hurt 
performance.
Florian Weimer April 28, 2023, 8:49 a.m. UTC | #22
* Paul Eggert:

> On 2023-04-20 01:07, Florian Weimer wrote:
>> * Paul Eggert:
>> 
>>>> +extern __typeof (strlcpy) __strlcpy;
>>>> +libc_hidden_proto (__strlcpy)
>>>> +extern __typeof (strlcat) __strlcat;
>>>> +libc_hidden_proto (__strlcat)
>>>
>>> Glibc shouldn't call these functions internally, so let's not export
>>> them to elsewhere in glibc.
>> strlcpy looks like it could be called for implementing %s in
>> snprintf.
>> That seems like a reasonable optimization.
>
> No, because strlcpy must return the length of the source even when
> it's longer than INT_MAX. (This is a botch in the spec which we
> apparently cannot fix.) So there's no way snprintf could use strlcpy
> without hurting worst-case performance.

The possible shortcut only exists in the EOVERFLOW case, otherwise we
need the exact length for the snprintf return value.  I think optimizing
EOVERFLOW is not important.  We will get more benefit from sharing the
strlcpy implementation.

>> I expect someone to rewrite this using word-size accesses fairly soon.
>
> That would be headed in the wrong direction. We should not waste time
> trying to optimize these functions' copying actions, as the
> destinations are invariably so small that our attempts to "optimize"
> will likely hurt performance.

I expect that these optimizations will benefit short strings as well,
similar to the other string functions.

Thanks,
Florian
diff mbox series

Patch

diff --git a/NEWS b/NEWS
index 83d082afad..60b40fabcf 100644
--- a/NEWS
+++ b/NEWS
@@ -21,6 +21,9 @@  Major new features:
 
 * PRIb* and PRIB* macros from C2X have been added to <inttypes.h>.
 
+* The strlcpy and strlcat functions have been added.  They are derived
+  from OpenBSD, and are expected to be added to a future POSIX versions.
+
 Deprecated and removed features, and other changes affecting compatibility:
 
 * In the Linux kernel for the hppa/parisc architecture some of the
@@ -223,6 +226,8 @@  Major new features:
 
   The LoongArch ABI is 64-bit little-endian.
 
+* The functions strlcpy and strlcat have been added.
+
 Deprecated and removed features, and other changes affecting compatibility:
 
 * Support for prelink will be removed in the next release; this includes
diff --git a/debug/Makefile b/debug/Makefile
index 52f9a7852c..404f93002f 100644
--- a/debug/Makefile
+++ b/debug/Makefile
@@ -31,6 +31,7 @@  headers	:= execinfo.h
 routines  = backtrace backtracesyms backtracesymsfd noophooks \
 	    memcpy_chk memmove_chk mempcpy_chk memset_chk stpcpy_chk \
 	    strcat_chk strcpy_chk strncat_chk strncpy_chk stpncpy_chk \
+	    strlcpy_chk strlcat_chk \
 	    sprintf_chk vsprintf_chk snprintf_chk vsnprintf_chk \
 	    printf_chk fprintf_chk vprintf_chk vfprintf_chk \
 	    gets_chk chk_fail readonly-area fgets_chk fgets_u_chk \
diff --git a/debug/Versions b/debug/Versions
index a6628db356..94dfa5f428 100644
--- a/debug/Versions
+++ b/debug/Versions
@@ -58,6 +58,10 @@  libc {
   GLIBC_2.25 {
     __explicit_bzero_chk;
   }
+  GLIBC_2.38 {
+    __strlcat_chk;
+    __strlcpy_chk;
+  }
   GLIBC_PRIVATE {
     __fortify_fail;
   }
diff --git a/debug/strlcat_chk.c b/debug/strlcat_chk.c
new file mode 100644
index 0000000000..be6e1942af
--- /dev/null
+++ b/debug/strlcat_chk.c
@@ -0,0 +1,32 @@ 
+/* Fortified version of strlcat.
+   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 <memcopy.h>
+
+/* Check that the user-supplied size does not exceed the
+   compiler-determined size, and then forward to strlcat.  */
+size_t
+__strlcat_chk (char *__restrict s1, const char *__restrict s2,
+	       size_t n, size_t s1len)
+{
+  if (__glibc_unlikely (s1len < n))
+    __chk_fail ();
+
+  return __strlcat (s1, s2, n);
+}
diff --git a/debug/strlcpy_chk.c b/debug/strlcpy_chk.c
new file mode 100644
index 0000000000..0137886e91
--- /dev/null
+++ b/debug/strlcpy_chk.c
@@ -0,0 +1,32 @@ 
+/* Fortified version of strlcpy.
+   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 <memcopy.h>
+
+/* Check that the user-supplied size does not exceed the
+   compiler-determined size, and then forward to strlcpy.  */
+size_t
+__strlcpy_chk (char *__restrict s1, const char *__restrict s2,
+	       size_t n, size_t s1len)
+{
+  if (__glibc_unlikely (s1len < n))
+    __chk_fail ();
+
+  return __strlcpy (s1, s2, n);
+}
diff --git a/debug/tst-fortify.c b/debug/tst-fortify.c
index 7850a4e558..f74a5e04dc 100644
--- a/debug/tst-fortify.c
+++ b/debug/tst-fortify.c
@@ -535,6 +535,20 @@  do_test (void)
   strncpy (a.buf1 + (O + 6), "X", l0 + 4);
   CHK_FAIL_END
 
+  CHK_FAIL_START
+  strlcpy (a.buf1 + (O + 6), "X", 4);
+  CHK_FAIL_END
+
+  CHK_FAIL_START
+  strlcpy (a.buf1 + (O + 6), "X", l0 + 4);
+  CHK_FAIL_END
+
+  {
+    char *volatile buf2 = buf;
+    if (strlcpy (buf2, "a", sizeof (buf) + 1) != 1)
+      FAIL ();
+  }
+
 # if !defined __cplusplus || defined __va_arg_pack
   CHK_FAIL_START
   sprintf (a.buf1 + (O + 7), "%d", num1);
@@ -558,6 +572,23 @@  do_test (void)
   CHK_FAIL_START
   strncat (a.buf1, "ZYXWV", l0 + 3);
   CHK_FAIL_END
+
+  memset (a.buf1, 0, sizeof (a.buf1));
+  CHK_FAIL_START
+  strlcat (a.buf1 + (O + 6), "X", 4);
+  CHK_FAIL_END
+
+  memset (a.buf1, 0, sizeof (a.buf1));
+  CHK_FAIL_START
+  strlcat (a.buf1 + (O + 6), "X", l0 + 4);
+  CHK_FAIL_END
+
+  {
+    buf[0] = '\0';
+    char *volatile buf2 = buf;
+    if (strlcat (buf2, "a", sizeof (buf) + 1) != 1)
+      FAIL ();
+  }
 #endif
 
 
diff --git a/include/string.h b/include/string.h
index 673cfd7272..0c78ad2539 100644
--- a/include/string.h
+++ b/include/string.h
@@ -88,6 +88,10 @@  libc_hidden_proto (__stpcpy)
 # define __stpcpy(dest, src) __builtin_stpcpy (dest, src)
 #endif
 libc_hidden_proto (__stpncpy)
+extern __typeof (strlcpy) __strlcpy;
+libc_hidden_proto (__strlcpy)
+extern __typeof (strlcat) __strlcat;
+libc_hidden_proto (__strlcat)
 libc_hidden_proto (__rawmemchr)
 libc_hidden_proto (__strcasecmp)
 libc_hidden_proto (__strcasecmp_l)
diff --git a/manual/string.texi b/manual/string.texi
index e06433187e..e3979f1d0f 100644
--- a/manual/string.texi
+++ b/manual/string.texi
@@ -1068,6 +1068,60 @@  processing text.  Also, this function has significant performance
 issues.  @xref{Concatenating Strings}.
 @end deftypefun
 
+@deftypefun size_t strlcpy (char *restrict @var{to}, const char *restrict @var{from}, size_t @var{size})
+@standards{BSD, string.h}
+@safety{@prelim{}@mtsafe{}@assafe{}@acsafe{}}
+This function is similar to @code{strcpy}, but copies at most
+@var{size} bytes from the string @var{from} into the destination
+array @var{to}, including a terminating null byte.
+
+If @var{size} is greater than the length of the string @var{from},
+this function copies all of the string @var{from} to the destination
+array @var{to}, including the terminating null byte.  Like other
+string functions such as @code{strcpy}, but unlike @code{strncpy}, any
+remaining bytes in the destination array remain unchanged.
+
+If @var{size} is nonzero and less than or equal to the the length of the string
+@var{from}, this function copies only the first @samp{@var{size} - 1}
+bytes to the destination array @var{to}, and writes a terminating null
+byte to the last byte of the array.
+
+The return value @var{result} of @code{strlcpy} is the length of the
+string @var{from}.  This means that @samp{@var{result} >= @var{size}} is
+true whenever truncation occurs.
+
+The behavior of @code{strlcpy} is undefined if @var{size} is zero, or if
+the source string and the first @var{size} bytes of the destination
+array overlap.
+
+This function is derived from OpenBSD 2.4.
+@end deftypefun
+
+@deftypefun size_t strlcat (char *restrict @var{to}, const char *restrict @var{from}, size_t @var{size})
+@standards{BSD, string.h}
+@safety{@prelim{}@mtsafe{}@assafe{}@acsafe{}}
+This function appends the string @var{from} to the
+string @var{to}, limiting the total size of the result string at
+@var{to} (including the null terminator) to @var{size}.
+
+This function copies as much as possible of the string @var{from} into
+the array at @var{to} of @var{size} bytes, starting at the terminating
+null byte of the original string @var{to}.  In effect, this appends
+the string @var{from} to the string @var{to}.  Although the resulting
+string will contain a null terminator, it can be truncated (not all
+bytes in @var{from} may be copied).
+
+This function returns the sum of the original length of @var{to} and
+the length of @var{from}.  This means that truncation occurs unless
+the returned value is less than @var{size}.
+
+The behavior is undefined if the array at @var{to} does not contain a
+null byte in its first @var{size} bytes, or if the source string and the
+first @var{size} bytes of @var{to} overlap.
+
+This function is derived from OpenBSD 2.4.
+@end deftypefun
+
 Because these functions can abruptly truncate strings or wide strings,
 they are generally poor choices for processing text.  When coping or
 concatening multibyte strings, they can truncate within a multibyte
diff --git a/string/Makefile b/string/Makefile
index c84b49aaa5..c746ee1792 100644
--- a/string/Makefile
+++ b/string/Makefile
@@ -92,6 +92,8 @@  routines := \
   strerrorname_np \
   strfry \
   string-inlines \
+  strlcat \
+  strlcpy \
   strlen \
   strncase \
   strncase_l \
@@ -175,6 +177,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 7f0f600224..fcfb3fce74 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..5b64072004
--- /dev/null
+++ b/string/strlcat.c
@@ -0,0 +1,61 @@ 
+/* 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>
+
+#undef strlcat
+
+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..b863a4762b
--- /dev/null
+++ b/string/strlcpy.c
@@ -0,0 +1,48 @@ 
+/* 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>
+
+#undef strlcpy
+
+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>
diff --git a/sysdeps/mach/hurd/i386/libc.abilist b/sysdeps/mach/hurd/i386/libc.abilist
index da1cad6777..e195853f09 100644
--- a/sysdeps/mach/hurd/i386/libc.abilist
+++ b/sysdeps/mach/hurd/i386/libc.abilist
@@ -2324,6 +2324,10 @@  GLIBC_2.38 __isoc23_wcstoull F
 GLIBC_2.38 __isoc23_wcstoull_l F
 GLIBC_2.38 __isoc23_wcstoumax F
 GLIBC_2.38 __isoc23_wscanf F
+GLIBC_2.38 __strlcat_chk F
+GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 strlcat F
+GLIBC_2.38 strlcpy F
 GLIBC_2.4 __confstr_chk F
 GLIBC_2.4 __fgets_chk F
 GLIBC_2.4 __fgets_unlocked_chk F
diff --git a/sysdeps/unix/sysv/linux/aarch64/libc.abilist b/sysdeps/unix/sysv/linux/aarch64/libc.abilist
index 0e2d9c3045..cf51b88932 100644
--- a/sysdeps/unix/sysv/linux/aarch64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/aarch64/libc.abilist
@@ -2665,3 +2665,7 @@  GLIBC_2.38 __isoc23_wcstoull F
 GLIBC_2.38 __isoc23_wcstoull_l F
 GLIBC_2.38 __isoc23_wcstoumax F
 GLIBC_2.38 __isoc23_wscanf F
+GLIBC_2.38 __strlcat_chk F
+GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 strlcat F
+GLIBC_2.38 strlcpy F
diff --git a/sysdeps/unix/sysv/linux/alpha/libc.abilist b/sysdeps/unix/sysv/linux/alpha/libc.abilist
index f1bec1978d..4b25f343b8 100644
--- a/sysdeps/unix/sysv/linux/alpha/libc.abilist
+++ b/sysdeps/unix/sysv/linux/alpha/libc.abilist
@@ -2774,6 +2774,10 @@  GLIBC_2.38 __nldbl___isoc23_vsscanf F
 GLIBC_2.38 __nldbl___isoc23_vswscanf F
 GLIBC_2.38 __nldbl___isoc23_vwscanf F
 GLIBC_2.38 __nldbl___isoc23_wscanf F
+GLIBC_2.38 __strlcat_chk F
+GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 strlcat F
+GLIBC_2.38 strlcpy F
 GLIBC_2.4 _IO_fprintf F
 GLIBC_2.4 _IO_printf F
 GLIBC_2.4 _IO_sprintf F
diff --git a/sysdeps/unix/sysv/linux/arc/libc.abilist b/sysdeps/unix/sysv/linux/arc/libc.abilist
index aa874b88d0..5a58cc0477 100644
--- a/sysdeps/unix/sysv/linux/arc/libc.abilist
+++ b/sysdeps/unix/sysv/linux/arc/libc.abilist
@@ -2426,3 +2426,7 @@  GLIBC_2.38 __isoc23_wcstoull F
 GLIBC_2.38 __isoc23_wcstoull_l F
 GLIBC_2.38 __isoc23_wcstoumax F
 GLIBC_2.38 __isoc23_wscanf F
+GLIBC_2.38 __strlcat_chk F
+GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 strlcat F
+GLIBC_2.38 strlcpy F
diff --git a/sysdeps/unix/sysv/linux/arm/be/libc.abilist b/sysdeps/unix/sysv/linux/arm/be/libc.abilist
index afbd57da6f..99ce948c5c 100644
--- a/sysdeps/unix/sysv/linux/arm/be/libc.abilist
+++ b/sysdeps/unix/sysv/linux/arm/be/libc.abilist
@@ -546,6 +546,10 @@  GLIBC_2.38 __isoc23_wcstoull F
 GLIBC_2.38 __isoc23_wcstoull_l F
 GLIBC_2.38 __isoc23_wcstoumax F
 GLIBC_2.38 __isoc23_wscanf F
+GLIBC_2.38 __strlcat_chk F
+GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 strlcat F
+GLIBC_2.38 strlcpy F
 GLIBC_2.4 _Exit F
 GLIBC_2.4 _IO_2_1_stderr_ D 0xa0
 GLIBC_2.4 _IO_2_1_stdin_ D 0xa0
diff --git a/sysdeps/unix/sysv/linux/arm/le/libc.abilist b/sysdeps/unix/sysv/linux/arm/le/libc.abilist
index e7364cd3fe..c00bf72ebc 100644
--- a/sysdeps/unix/sysv/linux/arm/le/libc.abilist
+++ b/sysdeps/unix/sysv/linux/arm/le/libc.abilist
@@ -543,6 +543,10 @@  GLIBC_2.38 __isoc23_wcstoull F
 GLIBC_2.38 __isoc23_wcstoull_l F
 GLIBC_2.38 __isoc23_wcstoumax F
 GLIBC_2.38 __isoc23_wscanf F
+GLIBC_2.38 __strlcat_chk F
+GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 strlcat F
+GLIBC_2.38 strlcpy F
 GLIBC_2.4 _Exit F
 GLIBC_2.4 _IO_2_1_stderr_ D 0xa0
 GLIBC_2.4 _IO_2_1_stdin_ D 0xa0
diff --git a/sysdeps/unix/sysv/linux/csky/libc.abilist b/sysdeps/unix/sysv/linux/csky/libc.abilist
index 913fa59215..71130f2c6b 100644
--- a/sysdeps/unix/sysv/linux/csky/libc.abilist
+++ b/sysdeps/unix/sysv/linux/csky/libc.abilist
@@ -2702,3 +2702,7 @@  GLIBC_2.38 __isoc23_wcstoull F
 GLIBC_2.38 __isoc23_wcstoull_l F
 GLIBC_2.38 __isoc23_wcstoumax F
 GLIBC_2.38 __isoc23_wscanf F
+GLIBC_2.38 __strlcat_chk F
+GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 strlcat F
+GLIBC_2.38 strlcpy F
diff --git a/sysdeps/unix/sysv/linux/hppa/libc.abilist b/sysdeps/unix/sysv/linux/hppa/libc.abilist
index 43af3a9811..5a651c03df 100644
--- a/sysdeps/unix/sysv/linux/hppa/libc.abilist
+++ b/sysdeps/unix/sysv/linux/hppa/libc.abilist
@@ -2651,6 +2651,10 @@  GLIBC_2.38 __isoc23_wcstoull F
 GLIBC_2.38 __isoc23_wcstoull_l F
 GLIBC_2.38 __isoc23_wcstoumax F
 GLIBC_2.38 __isoc23_wscanf F
+GLIBC_2.38 __strlcat_chk F
+GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 strlcat F
+GLIBC_2.38 strlcpy F
 GLIBC_2.4 __confstr_chk F
 GLIBC_2.4 __fgets_chk F
 GLIBC_2.4 __fgets_unlocked_chk F
diff --git a/sysdeps/unix/sysv/linux/i386/libc.abilist b/sysdeps/unix/sysv/linux/i386/libc.abilist
index af72f8fab0..12b91ef632 100644
--- a/sysdeps/unix/sysv/linux/i386/libc.abilist
+++ b/sysdeps/unix/sysv/linux/i386/libc.abilist
@@ -2835,6 +2835,10 @@  GLIBC_2.38 __isoc23_wcstoull F
 GLIBC_2.38 __isoc23_wcstoull_l F
 GLIBC_2.38 __isoc23_wcstoumax F
 GLIBC_2.38 __isoc23_wscanf F
+GLIBC_2.38 __strlcat_chk F
+GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 strlcat F
+GLIBC_2.38 strlcpy F
 GLIBC_2.4 __confstr_chk F
 GLIBC_2.4 __fgets_chk F
 GLIBC_2.4 __fgets_unlocked_chk F
diff --git a/sysdeps/unix/sysv/linux/ia64/libc.abilist b/sysdeps/unix/sysv/linux/ia64/libc.abilist
index 48cbb0fa50..f223c5e08d 100644
--- a/sysdeps/unix/sysv/linux/ia64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/ia64/libc.abilist
@@ -2600,6 +2600,10 @@  GLIBC_2.38 __isoc23_wcstoull F
 GLIBC_2.38 __isoc23_wcstoull_l F
 GLIBC_2.38 __isoc23_wcstoumax F
 GLIBC_2.38 __isoc23_wscanf F
+GLIBC_2.38 __strlcat_chk F
+GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 strlcat F
+GLIBC_2.38 strlcpy F
 GLIBC_2.4 __confstr_chk F
 GLIBC_2.4 __fgets_chk F
 GLIBC_2.4 __fgets_unlocked_chk F
diff --git a/sysdeps/unix/sysv/linux/loongarch/lp64/libc.abilist b/sysdeps/unix/sysv/linux/loongarch/lp64/libc.abilist
index c15884bb0b..b91ed6e704 100644
--- a/sysdeps/unix/sysv/linux/loongarch/lp64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/loongarch/lp64/libc.abilist
@@ -2186,3 +2186,7 @@  GLIBC_2.38 __isoc23_wcstoull F
 GLIBC_2.38 __isoc23_wcstoull_l F
 GLIBC_2.38 __isoc23_wcstoumax F
 GLIBC_2.38 __isoc23_wscanf F
+GLIBC_2.38 __strlcat_chk F
+GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 strlcat F
+GLIBC_2.38 strlcpy F
diff --git a/sysdeps/unix/sysv/linux/m68k/coldfire/libc.abilist b/sysdeps/unix/sysv/linux/m68k/coldfire/libc.abilist
index 3738db81df..0d91d7f1ae 100644
--- a/sysdeps/unix/sysv/linux/m68k/coldfire/libc.abilist
+++ b/sysdeps/unix/sysv/linux/m68k/coldfire/libc.abilist
@@ -547,6 +547,10 @@  GLIBC_2.38 __isoc23_wcstoull F
 GLIBC_2.38 __isoc23_wcstoull_l F
 GLIBC_2.38 __isoc23_wcstoumax F
 GLIBC_2.38 __isoc23_wscanf F
+GLIBC_2.38 __strlcat_chk F
+GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 strlcat F
+GLIBC_2.38 strlcpy F
 GLIBC_2.4 _Exit F
 GLIBC_2.4 _IO_2_1_stderr_ D 0x98
 GLIBC_2.4 _IO_2_1_stdin_ D 0x98
diff --git a/sysdeps/unix/sysv/linux/m68k/m680x0/libc.abilist b/sysdeps/unix/sysv/linux/m68k/m680x0/libc.abilist
index ed13627752..e87b22747a 100644
--- a/sysdeps/unix/sysv/linux/m68k/m680x0/libc.abilist
+++ b/sysdeps/unix/sysv/linux/m68k/m680x0/libc.abilist
@@ -2778,6 +2778,10 @@  GLIBC_2.38 __isoc23_wcstoull F
 GLIBC_2.38 __isoc23_wcstoull_l F
 GLIBC_2.38 __isoc23_wcstoumax F
 GLIBC_2.38 __isoc23_wscanf F
+GLIBC_2.38 __strlcat_chk F
+GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 strlcat F
+GLIBC_2.38 strlcpy F
 GLIBC_2.4 __confstr_chk F
 GLIBC_2.4 __fgets_chk F
 GLIBC_2.4 __fgets_unlocked_chk F
diff --git a/sysdeps/unix/sysv/linux/microblaze/be/libc.abilist b/sysdeps/unix/sysv/linux/microblaze/be/libc.abilist
index 8357738621..f7623d6d72 100644
--- a/sysdeps/unix/sysv/linux/microblaze/be/libc.abilist
+++ b/sysdeps/unix/sysv/linux/microblaze/be/libc.abilist
@@ -2751,3 +2751,7 @@  GLIBC_2.38 __isoc23_wcstoull F
 GLIBC_2.38 __isoc23_wcstoull_l F
 GLIBC_2.38 __isoc23_wcstoumax F
 GLIBC_2.38 __isoc23_wscanf F
+GLIBC_2.38 __strlcat_chk F
+GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 strlcat F
+GLIBC_2.38 strlcpy F
diff --git a/sysdeps/unix/sysv/linux/microblaze/le/libc.abilist b/sysdeps/unix/sysv/linux/microblaze/le/libc.abilist
index 58c5da583d..298aa99b42 100644
--- a/sysdeps/unix/sysv/linux/microblaze/le/libc.abilist
+++ b/sysdeps/unix/sysv/linux/microblaze/le/libc.abilist
@@ -2748,3 +2748,7 @@  GLIBC_2.38 __isoc23_wcstoull F
 GLIBC_2.38 __isoc23_wcstoull_l F
 GLIBC_2.38 __isoc23_wcstoumax F
 GLIBC_2.38 __isoc23_wscanf F
+GLIBC_2.38 __strlcat_chk F
+GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 strlcat F
+GLIBC_2.38 strlcpy F
diff --git a/sysdeps/unix/sysv/linux/mips/mips32/fpu/libc.abilist b/sysdeps/unix/sysv/linux/mips/mips32/fpu/libc.abilist
index d3741945cd..f83bdc50cd 100644
--- a/sysdeps/unix/sysv/linux/mips/mips32/fpu/libc.abilist
+++ b/sysdeps/unix/sysv/linux/mips/mips32/fpu/libc.abilist
@@ -2743,6 +2743,10 @@  GLIBC_2.38 __isoc23_wcstoull F
 GLIBC_2.38 __isoc23_wcstoull_l F
 GLIBC_2.38 __isoc23_wcstoumax F
 GLIBC_2.38 __isoc23_wscanf F
+GLIBC_2.38 __strlcat_chk F
+GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 strlcat F
+GLIBC_2.38 strlcpy F
 GLIBC_2.4 __confstr_chk F
 GLIBC_2.4 __fgets_chk F
 GLIBC_2.4 __fgets_unlocked_chk F
diff --git a/sysdeps/unix/sysv/linux/mips/mips32/nofpu/libc.abilist b/sysdeps/unix/sysv/linux/mips/mips32/nofpu/libc.abilist
index 5319fdc204..611ece2ac4 100644
--- a/sysdeps/unix/sysv/linux/mips/mips32/nofpu/libc.abilist
+++ b/sysdeps/unix/sysv/linux/mips/mips32/nofpu/libc.abilist
@@ -2741,6 +2741,10 @@  GLIBC_2.38 __isoc23_wcstoull F
 GLIBC_2.38 __isoc23_wcstoull_l F
 GLIBC_2.38 __isoc23_wcstoumax F
 GLIBC_2.38 __isoc23_wscanf F
+GLIBC_2.38 __strlcat_chk F
+GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 strlcat F
+GLIBC_2.38 strlcpy F
 GLIBC_2.4 __confstr_chk F
 GLIBC_2.4 __fgets_chk F
 GLIBC_2.4 __fgets_unlocked_chk F
diff --git a/sysdeps/unix/sysv/linux/mips/mips64/n32/libc.abilist b/sysdeps/unix/sysv/linux/mips/mips64/n32/libc.abilist
index 1743ea6eb9..0af286fda1 100644
--- a/sysdeps/unix/sysv/linux/mips/mips64/n32/libc.abilist
+++ b/sysdeps/unix/sysv/linux/mips/mips64/n32/libc.abilist
@@ -2749,6 +2749,10 @@  GLIBC_2.38 __isoc23_wcstoull F
 GLIBC_2.38 __isoc23_wcstoull_l F
 GLIBC_2.38 __isoc23_wcstoumax F
 GLIBC_2.38 __isoc23_wscanf F
+GLIBC_2.38 __strlcat_chk F
+GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 strlcat F
+GLIBC_2.38 strlcpy F
 GLIBC_2.4 __confstr_chk F
 GLIBC_2.4 __fgets_chk F
 GLIBC_2.4 __fgets_unlocked_chk F
diff --git a/sysdeps/unix/sysv/linux/mips/mips64/n64/libc.abilist b/sysdeps/unix/sysv/linux/mips/mips64/n64/libc.abilist
index 9b1f53c6ac..8285f2196e 100644
--- a/sysdeps/unix/sysv/linux/mips/mips64/n64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/mips/mips64/n64/libc.abilist
@@ -2651,6 +2651,10 @@  GLIBC_2.38 __isoc23_wcstoull F
 GLIBC_2.38 __isoc23_wcstoull_l F
 GLIBC_2.38 __isoc23_wcstoumax F
 GLIBC_2.38 __isoc23_wscanf F
+GLIBC_2.38 __strlcat_chk F
+GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 strlcat F
+GLIBC_2.38 strlcpy F
 GLIBC_2.4 __confstr_chk F
 GLIBC_2.4 __fgets_chk F
 GLIBC_2.4 __fgets_unlocked_chk F
diff --git a/sysdeps/unix/sysv/linux/nios2/libc.abilist b/sysdeps/unix/sysv/linux/nios2/libc.abilist
index ae1c6ca1b5..c7144d7cd8 100644
--- a/sysdeps/unix/sysv/linux/nios2/libc.abilist
+++ b/sysdeps/unix/sysv/linux/nios2/libc.abilist
@@ -2790,3 +2790,7 @@  GLIBC_2.38 __isoc23_wcstoull F
 GLIBC_2.38 __isoc23_wcstoull_l F
 GLIBC_2.38 __isoc23_wcstoumax F
 GLIBC_2.38 __isoc23_wscanf F
+GLIBC_2.38 __strlcat_chk F
+GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 strlcat F
+GLIBC_2.38 strlcpy F
diff --git a/sysdeps/unix/sysv/linux/or1k/libc.abilist b/sysdeps/unix/sysv/linux/or1k/libc.abilist
index a7c572c947..bb43247795 100644
--- a/sysdeps/unix/sysv/linux/or1k/libc.abilist
+++ b/sysdeps/unix/sysv/linux/or1k/libc.abilist
@@ -2172,3 +2172,7 @@  GLIBC_2.38 __isoc23_wcstoull F
 GLIBC_2.38 __isoc23_wcstoull_l F
 GLIBC_2.38 __isoc23_wcstoumax F
 GLIBC_2.38 __isoc23_wscanf F
+GLIBC_2.38 __strlcat_chk F
+GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 strlcat F
+GLIBC_2.38 strlcpy F
diff --git a/sysdeps/unix/sysv/linux/powerpc/powerpc32/fpu/libc.abilist b/sysdeps/unix/sysv/linux/powerpc/powerpc32/fpu/libc.abilist
index 074fa031a7..7cc5660830 100644
--- a/sysdeps/unix/sysv/linux/powerpc/powerpc32/fpu/libc.abilist
+++ b/sysdeps/unix/sysv/linux/powerpc/powerpc32/fpu/libc.abilist
@@ -2817,6 +2817,10 @@  GLIBC_2.38 __nldbl___isoc23_vsscanf F
 GLIBC_2.38 __nldbl___isoc23_vswscanf F
 GLIBC_2.38 __nldbl___isoc23_vwscanf F
 GLIBC_2.38 __nldbl___isoc23_wscanf F
+GLIBC_2.38 __strlcat_chk F
+GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 strlcat F
+GLIBC_2.38 strlcpy F
 GLIBC_2.4 _IO_fprintf F
 GLIBC_2.4 _IO_printf F
 GLIBC_2.4 _IO_sprintf F
diff --git a/sysdeps/unix/sysv/linux/powerpc/powerpc32/nofpu/libc.abilist b/sysdeps/unix/sysv/linux/powerpc/powerpc32/nofpu/libc.abilist
index dfcb4bd2d5..dd290af782 100644
--- a/sysdeps/unix/sysv/linux/powerpc/powerpc32/nofpu/libc.abilist
+++ b/sysdeps/unix/sysv/linux/powerpc/powerpc32/nofpu/libc.abilist
@@ -2850,6 +2850,10 @@  GLIBC_2.38 __nldbl___isoc23_vsscanf F
 GLIBC_2.38 __nldbl___isoc23_vswscanf F
 GLIBC_2.38 __nldbl___isoc23_vwscanf F
 GLIBC_2.38 __nldbl___isoc23_wscanf F
+GLIBC_2.38 __strlcat_chk F
+GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 strlcat F
+GLIBC_2.38 strlcpy F
 GLIBC_2.4 _IO_fprintf F
 GLIBC_2.4 _IO_printf F
 GLIBC_2.4 _IO_sprintf F
diff --git a/sysdeps/unix/sysv/linux/powerpc/powerpc64/be/libc.abilist b/sysdeps/unix/sysv/linux/powerpc/powerpc64/be/libc.abilist
index 63bbccf3f9..f2b001402c 100644
--- a/sysdeps/unix/sysv/linux/powerpc/powerpc64/be/libc.abilist
+++ b/sysdeps/unix/sysv/linux/powerpc/powerpc64/be/libc.abilist
@@ -2571,6 +2571,10 @@  GLIBC_2.38 __nldbl___isoc23_vsscanf F
 GLIBC_2.38 __nldbl___isoc23_vswscanf F
 GLIBC_2.38 __nldbl___isoc23_vwscanf F
 GLIBC_2.38 __nldbl___isoc23_wscanf F
+GLIBC_2.38 __strlcat_chk F
+GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 strlcat F
+GLIBC_2.38 strlcpy F
 GLIBC_2.4 _IO_fprintf F
 GLIBC_2.4 _IO_printf F
 GLIBC_2.4 _IO_sprintf F
diff --git a/sysdeps/unix/sysv/linux/powerpc/powerpc64/le/libc.abilist b/sysdeps/unix/sysv/linux/powerpc/powerpc64/le/libc.abilist
index ab85fd61ef..9cc431666e 100644
--- a/sysdeps/unix/sysv/linux/powerpc/powerpc64/le/libc.abilist
+++ b/sysdeps/unix/sysv/linux/powerpc/powerpc64/le/libc.abilist
@@ -2885,3 +2885,7 @@  GLIBC_2.38 __nldbl___isoc23_vsscanf F
 GLIBC_2.38 __nldbl___isoc23_vswscanf F
 GLIBC_2.38 __nldbl___isoc23_vwscanf F
 GLIBC_2.38 __nldbl___isoc23_wscanf F
+GLIBC_2.38 __strlcat_chk F
+GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 strlcat F
+GLIBC_2.38 strlcpy F
diff --git a/sysdeps/unix/sysv/linux/riscv/rv32/libc.abilist b/sysdeps/unix/sysv/linux/riscv/rv32/libc.abilist
index b716f5c763..b9b725f913 100644
--- a/sysdeps/unix/sysv/linux/riscv/rv32/libc.abilist
+++ b/sysdeps/unix/sysv/linux/riscv/rv32/libc.abilist
@@ -2428,3 +2428,7 @@  GLIBC_2.38 __isoc23_wcstoull F
 GLIBC_2.38 __isoc23_wcstoull_l F
 GLIBC_2.38 __isoc23_wcstoumax F
 GLIBC_2.38 __isoc23_wscanf F
+GLIBC_2.38 __strlcat_chk F
+GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 strlcat F
+GLIBC_2.38 strlcpy F
diff --git a/sysdeps/unix/sysv/linux/riscv/rv64/libc.abilist b/sysdeps/unix/sysv/linux/riscv/rv64/libc.abilist
index 774e777b65..e0f4863856 100644
--- a/sysdeps/unix/sysv/linux/riscv/rv64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/riscv/rv64/libc.abilist
@@ -2628,3 +2628,7 @@  GLIBC_2.38 __isoc23_wcstoull F
 GLIBC_2.38 __isoc23_wcstoull_l F
 GLIBC_2.38 __isoc23_wcstoumax F
 GLIBC_2.38 __isoc23_wscanf F
+GLIBC_2.38 __strlcat_chk F
+GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 strlcat F
+GLIBC_2.38 strlcpy F
diff --git a/sysdeps/unix/sysv/linux/s390/s390-32/libc.abilist b/sysdeps/unix/sysv/linux/s390/s390-32/libc.abilist
index 8625135c48..8db68fcea7 100644
--- a/sysdeps/unix/sysv/linux/s390/s390-32/libc.abilist
+++ b/sysdeps/unix/sysv/linux/s390/s390-32/libc.abilist
@@ -2815,6 +2815,10 @@  GLIBC_2.38 __nldbl___isoc23_vsscanf F
 GLIBC_2.38 __nldbl___isoc23_vswscanf F
 GLIBC_2.38 __nldbl___isoc23_vwscanf F
 GLIBC_2.38 __nldbl___isoc23_wscanf F
+GLIBC_2.38 __strlcat_chk F
+GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 strlcat F
+GLIBC_2.38 strlcpy F
 GLIBC_2.4 _IO_fprintf F
 GLIBC_2.4 _IO_printf F
 GLIBC_2.4 _IO_sprintf F
diff --git a/sysdeps/unix/sysv/linux/s390/s390-64/libc.abilist b/sysdeps/unix/sysv/linux/s390/s390-64/libc.abilist
index d00c7eb262..ec9747b7ea 100644
--- a/sysdeps/unix/sysv/linux/s390/s390-64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/s390/s390-64/libc.abilist
@@ -2608,6 +2608,10 @@  GLIBC_2.38 __nldbl___isoc23_vsscanf F
 GLIBC_2.38 __nldbl___isoc23_vswscanf F
 GLIBC_2.38 __nldbl___isoc23_vwscanf F
 GLIBC_2.38 __nldbl___isoc23_wscanf F
+GLIBC_2.38 __strlcat_chk F
+GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 strlcat F
+GLIBC_2.38 strlcpy F
 GLIBC_2.4 _IO_fprintf F
 GLIBC_2.4 _IO_printf F
 GLIBC_2.4 _IO_sprintf F
diff --git a/sysdeps/unix/sysv/linux/sh/be/libc.abilist b/sysdeps/unix/sysv/linux/sh/be/libc.abilist
index b63037241d..9576b818d8 100644
--- a/sysdeps/unix/sysv/linux/sh/be/libc.abilist
+++ b/sysdeps/unix/sysv/linux/sh/be/libc.abilist
@@ -2658,6 +2658,10 @@  GLIBC_2.38 __isoc23_wcstoull F
 GLIBC_2.38 __isoc23_wcstoull_l F
 GLIBC_2.38 __isoc23_wcstoumax F
 GLIBC_2.38 __isoc23_wscanf F
+GLIBC_2.38 __strlcat_chk F
+GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 strlcat F
+GLIBC_2.38 strlcpy F
 GLIBC_2.4 __confstr_chk F
 GLIBC_2.4 __fgets_chk F
 GLIBC_2.4 __fgets_unlocked_chk F
diff --git a/sysdeps/unix/sysv/linux/sh/le/libc.abilist b/sysdeps/unix/sysv/linux/sh/le/libc.abilist
index d80055617d..b67b1b2bb5 100644
--- a/sysdeps/unix/sysv/linux/sh/le/libc.abilist
+++ b/sysdeps/unix/sysv/linux/sh/le/libc.abilist
@@ -2655,6 +2655,10 @@  GLIBC_2.38 __isoc23_wcstoull F
 GLIBC_2.38 __isoc23_wcstoull_l F
 GLIBC_2.38 __isoc23_wcstoumax F
 GLIBC_2.38 __isoc23_wscanf F
+GLIBC_2.38 __strlcat_chk F
+GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 strlcat F
+GLIBC_2.38 strlcpy F
 GLIBC_2.4 __confstr_chk F
 GLIBC_2.4 __fgets_chk F
 GLIBC_2.4 __fgets_unlocked_chk F
diff --git a/sysdeps/unix/sysv/linux/sparc/sparc32/libc.abilist b/sysdeps/unix/sysv/linux/sparc/sparc32/libc.abilist
index 5be55c11d2..b251fc9c69 100644
--- a/sysdeps/unix/sysv/linux/sparc/sparc32/libc.abilist
+++ b/sysdeps/unix/sysv/linux/sparc/sparc32/libc.abilist
@@ -2810,6 +2810,10 @@  GLIBC_2.38 __nldbl___isoc23_vsscanf F
 GLIBC_2.38 __nldbl___isoc23_vswscanf F
 GLIBC_2.38 __nldbl___isoc23_vwscanf F
 GLIBC_2.38 __nldbl___isoc23_wscanf F
+GLIBC_2.38 __strlcat_chk F
+GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 strlcat F
+GLIBC_2.38 strlcpy F
 GLIBC_2.4 _IO_fprintf F
 GLIBC_2.4 _IO_printf F
 GLIBC_2.4 _IO_sprintf F
diff --git a/sysdeps/unix/sysv/linux/sparc/sparc64/libc.abilist b/sysdeps/unix/sysv/linux/sparc/sparc64/libc.abilist
index 475fdaae15..5ef9bbec34 100644
--- a/sysdeps/unix/sysv/linux/sparc/sparc64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/sparc/sparc64/libc.abilist
@@ -2623,6 +2623,10 @@  GLIBC_2.38 __isoc23_wcstoull F
 GLIBC_2.38 __isoc23_wcstoull_l F
 GLIBC_2.38 __isoc23_wcstoumax F
 GLIBC_2.38 __isoc23_wscanf F
+GLIBC_2.38 __strlcat_chk F
+GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 strlcat F
+GLIBC_2.38 strlcpy F
 GLIBC_2.4 __confstr_chk F
 GLIBC_2.4 __fgets_chk F
 GLIBC_2.4 __fgets_unlocked_chk F
diff --git a/sysdeps/unix/sysv/linux/x86_64/64/libc.abilist b/sysdeps/unix/sysv/linux/x86_64/64/libc.abilist
index 6cfb928bc8..9ad800b62e 100644
--- a/sysdeps/unix/sysv/linux/x86_64/64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/x86_64/64/libc.abilist
@@ -2574,6 +2574,10 @@  GLIBC_2.38 __isoc23_wcstoull F
 GLIBC_2.38 __isoc23_wcstoull_l F
 GLIBC_2.38 __isoc23_wcstoumax F
 GLIBC_2.38 __isoc23_wscanf F
+GLIBC_2.38 __strlcat_chk F
+GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 strlcat F
+GLIBC_2.38 strlcpy F
 GLIBC_2.4 __confstr_chk F
 GLIBC_2.4 __fgets_chk F
 GLIBC_2.4 __fgets_unlocked_chk F
diff --git a/sysdeps/unix/sysv/linux/x86_64/x32/libc.abilist b/sysdeps/unix/sysv/linux/x86_64/x32/libc.abilist
index c735097172..6a3a66c5d4 100644
--- a/sysdeps/unix/sysv/linux/x86_64/x32/libc.abilist
+++ b/sysdeps/unix/sysv/linux/x86_64/x32/libc.abilist
@@ -2680,3 +2680,7 @@  GLIBC_2.38 __isoc23_wcstoull F
 GLIBC_2.38 __isoc23_wcstoull_l F
 GLIBC_2.38 __isoc23_wcstoumax F
 GLIBC_2.38 __isoc23_wscanf F
+GLIBC_2.38 __strlcat_chk F
+GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 strlcat F
+GLIBC_2.38 strlcpy F