From eeb7b079d5d8785e760ca08c3692ffa579bbb737 Mon Sep 17 00:00:00 2001 From: Adhemerval Zanella Date: Wed, 25 Jun 2025 16:33:24 -0300 Subject: [PATCH] stdlib: Fix __libc_message_impl iovec size (BZ 32947) The iovec size should account for all substrings between each conversion specification. For the format: "abc %s efg" The list of substrings are: ["abc ", arg, " efg] which is 2 times the number of maximum arguments *plus* one. This issue triggered 'out of bounds' errors by stdlib/tst-bz20544 when glibc is built with experimental UBSAN support [1]. Besides adjusting the iovec size, a new runtime and check is added to avoid wrong __libc_message_impl usage. Checked on x86_64-linux-gnu. [1] https://sourceware.org/git/?p=glibc.git;a=shortlog;h=refs/heads/azanella/ubsan-undef Co-authored-by: Carlos O'Donell Tested-by: Carlos O'Donell Reviewed-by: Carlos O'Donell --- include/stdio.h | 4 ++++ posix/Makefile | 2 ++ posix/tst-libc-message.c | 48 ++++++++++++++++++++++++++++++++++++++ sysdeps/posix/libc_fatal.c | 36 +++++++++++++++------------- 4 files changed, 74 insertions(+), 16 deletions(-) create mode 100644 posix/tst-libc-message.c diff --git a/include/stdio.h b/include/stdio.h index e48d709919..3d917dba5b 100644 --- a/include/stdio.h +++ b/include/stdio.h @@ -173,6 +173,10 @@ libc_hidden_proto (__fortify_fail) /* The maximum number of varargs allowed in a __libc_message format string */ #define LIBC_MESSAGE_MAX_ARGS 4 +#define IOVEC_MAX_ERR_MSG "Fatal glibc error: Internal " \ + "__libc_message error. Too many arguments.\n" +#define IOVEC_MAX_ERR_MSG_LEN (sizeof (IOVEC_MAX_ERR_MSG) - 1) + _Noreturn void __libc_message_impl (const char *__fnt, ...) attribute_hidden __attribute__ ((__format__ (__printf__, 1, 2))); diff --git a/posix/Makefile b/posix/Makefile index c0e224236a..36b8b14c46 100644 --- a/posix/Makefile +++ b/posix/Makefile @@ -348,6 +348,7 @@ tests-internal := \ bug-regex5 \ bug-regex20 \ bug-regex33 \ + tst-libc-message \ # tests-internal tests-container := \ @@ -391,6 +392,7 @@ endif tests-static = \ tst-exec-static \ + tst-libc-message \ tst-spawn-static \ # tests-static diff --git a/posix/tst-libc-message.c b/posix/tst-libc-message.c new file mode 100644 index 0000000000..b85195e338 --- /dev/null +++ b/posix/tst-libc-message.c @@ -0,0 +1,48 @@ +/* Internal test to verify __libc_fatal. + Copyright (C) 2025 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 + . */ + +#include +#include + +#include +#include + +static _Noreturn void +run_libc_message (void *closure) +{ + /* We only support 4 arguments. Call with 5 to trigger failure. */ + __libc_message_impl ("%s %s %s %s %s\n", "1", "2", "3", "4", "5"); + __builtin_unreachable (); +} + +static int +do_test (void) +{ + struct support_capture_subprocess result + = support_capture_subprocess (run_libc_message, NULL); + support_capture_subprocess_check (&result, "libc_message", -SIGABRT, + sc_allow_stderr); + + TEST_COMPARE_STRING (result.err.buffer, IOVEC_MAX_ERR_MSG); + + support_capture_subprocess_free (&result); + + return 0; +} + +#include diff --git a/sysdeps/posix/libc_fatal.c b/sysdeps/posix/libc_fatal.c index d90cc6c681..6f75197e96 100644 --- a/sysdeps/posix/libc_fatal.c +++ b/sysdeps/posix/libc_fatal.c @@ -16,23 +16,13 @@ License along with the GNU C Library; if not, see . */ -#include -#include -#include +#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include +#include +#include +#include +#include #ifdef FATAL_PREPARE_INCLUDE #include FATAL_PREPARE_INCLUDE @@ -47,6 +37,10 @@ writev_for_fatal (int fd, const struct iovec *iov, size_t niov, size_t total) } #endif +/* At most a substring before each conversion specification and the + trailing substring (the plus one). */ +#define IOVEC_MAX (LIBC_MESSAGE_MAX_ARGS * 2 + 1) + /* Abort with an error message. */ void __libc_message_impl (const char *fmt, ...) @@ -61,7 +55,7 @@ __libc_message_impl (const char *fmt, ...) if (fd == -1) fd = STDERR_FILENO; - struct iovec iov[LIBC_MESSAGE_MAX_ARGS * 2 - 1]; + struct iovec iov[IOVEC_MAX]; int iovcnt = 0; ssize_t total = 0; @@ -99,6 +93,16 @@ __libc_message_impl (const char *fmt, ...) iov[iovcnt].iov_len = len; total += len; iovcnt++; + + if (__glibc_unlikely (iovcnt > IOVEC_MAX)) + { + len = IOVEC_MAX_ERR_MSG_LEN; + iov[0].iov_base = (char *) IOVEC_MAX_ERR_MSG; + iov[0].iov_len = len; + total = len; + iovcnt = 1; + break; + } } va_end (ap);