]> git.ipfire.org Git - thirdparty/glibc.git/commitdiff
stdlib: Fix __libc_message_impl iovec size (BZ 32947)
authorAdhemerval Zanella <adhemerval.zanella@linaro.org>
Wed, 25 Jun 2025 19:33:24 +0000 (16:33 -0300)
committerAdhemerval Zanella <adhemerval.zanella@linaro.org>
Mon, 30 Jun 2025 16:51:41 +0000 (13:51 -0300)
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 <carlos@redhat.com>
Tested-by: Carlos O'Donell <carlos@redhat.com>
Reviewed-by: Carlos O'Donell <carlos@redhat.com>
include/stdio.h
posix/Makefile
posix/tst-libc-message.c [new file with mode: 0644]
sysdeps/posix/libc_fatal.c

index e48d709919496e29db7e873656462d80460b6a69..3d917dba5baf96b072e772196604373ea499c3dd 100644 (file)
@@ -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)));
 
index c0e224236a1991411522808be2cd81c186c50d57..36b8b14c46f0d0178db1694fdadb08860670a6c9 100644 (file)
@@ -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 (file)
index 0000000..b85195e
--- /dev/null
@@ -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
+   <https://www.gnu.org/licenses/>.  */
+
+#include <signal.h>
+#include <stdio.h>
+
+#include <support/check.h>
+#include <support/capture_subprocess.h>
+
+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 <support/test-driver.c>
index d90cc6c681af8e91ac6bbbfe9972d9f78c3c26f1..6f75197e96629c4b3ee4b2df3ee512cda3e920b4 100644 (file)
    License along with the GNU C Library; if not, see
    <https://www.gnu.org/licenses/>.  */
 
-#include <atomic.h>
-#include <errno.h>
-#include <fcntl.h>
+#include <assert.h>
 #include <ldsodefs.h>
-#include <libc-pointer-arith.h>
-#include <paths.h>
+#include <setvmaname.h>
 #include <stdarg.h>
-#include <stdbool.h>
 #include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sysdep.h>
-#include <unistd.h>
-#include <sys/mman.h>
 #include <sys/uio.h>
-#include <not-cancel.h>
-#include <setvmaname.h>
+#include <unistd.h>
 
 #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);