assert: Add test for CVE-2025-0395
authorSiddhesh Poyarekar <siddhesh@sourceware.org>
Fri, 31 Jan 2025 17:16:30 +0000 (12:16 -0500)
committerSean Whitton <spwhitton@spwhitton.name>
Wed, 30 Apr 2025 01:01:35 +0000 (09:01 +0800)
Use the __progname symbol to override the program name to induce the
failure that CVE-2025-0395 describes.

This is related to BZ #32582

Signed-off-by: Siddhesh Poyarekar <siddhesh@sourceware.org>
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
(cherry picked from commit cdb9ba84191ce72e86346fb8b1d906e7cd930ea2)
(cherry picked from commit 31eb872cb21449832ab47ad5db83281d240e1d03
                and commits adding support_need_proc & xgetline)

Gbp-Pq: Topic any
Gbp-Pq: Name local-CVE-2025-0395-2.diff

assert/Makefile
assert/tst-assert-sa-2025-0001.c [new file with mode: 0644]
support/Makefile
support/support.h
support/support_need_proc.c [new file with mode: 0644]
support/xgetline.c [new file with mode: 0644]
support/xstdio.h

index 17c7ff0ddb40bfc267e7fa11d28221001404dabc..e4c9ead4d48b8e1c7cfebf76285c294a55f35c10 100644 (file)
@@ -25,7 +25,7 @@ include ../Makeconfig
 headers        := assert.h
 
 routines := assert assert-perr __assert
-tests := test-assert test-assert-perr tst-assert-c++ tst-assert-g++
+tests := test-assert test-assert-perr tst-assert-c++ tst-assert-g++ tst-assert-sa-2025-0001
 
 ifeq ($(have-cxx-thread_local),yes)
 CFLAGS-tst-assert-c++.o = -std=c++11
diff --git a/assert/tst-assert-sa-2025-0001.c b/assert/tst-assert-sa-2025-0001.c
new file mode 100644 (file)
index 0000000..102cb00
--- /dev/null
@@ -0,0 +1,92 @@
+/* Test for CVE-2025-0395.
+   Copyright The GNU Toolchain Authors.
+   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/>.  */
+
+/* Test that a large enough __progname does not result in a buffer overflow
+   when printing an assertion failure.  This was CVE-2025-0395.  */
+#include <assert.h>
+#include <inttypes.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <support/check.h>
+#include <support/support.h>
+#include <support/xstdio.h>
+#include <support/xunistd.h>
+
+extern const char *__progname;
+
+int
+do_test (int argc, char **argv)
+{
+
+  support_need_proc ("Reads /proc/self/maps to add guards to writable maps.");
+  ignore_stderr ();
+
+  /* XXX assumes that the assert is on a 2 digit line number.  */
+  const char *prompt = ": %s:99: do_test: Assertion `argc < 1' failed.\n";
+
+  int ret = fprintf (stderr, prompt, __FILE__);
+  if (ret < 0)
+    FAIL_EXIT1 ("fprintf failed: %m\n");
+
+  size_t pagesize = getpagesize ();
+  size_t namesize = pagesize - 1 - ret;
+
+  /* Alter the progname so that the assert message fills the entire page.  */
+  char progname[namesize];
+  memset (progname, 'A', namesize - 1);
+  progname[namesize - 1] = '\0';
+  __progname = progname;
+
+  FILE *f = xfopen ("/proc/self/maps", "r");
+  char *line = NULL;
+  size_t len = 0;
+  uintptr_t prev_to = 0;
+
+  /* Pad the beginning of every writable mapping with a PROT_NONE map.  This
+     ensures that the mmap in the assert_fail path never ends up below a
+     writable map and will terminate immediately in case of a buffer
+     overflow.  */
+  while (xgetline (&line, &len, f))
+    {
+      uintptr_t from, to;
+      char perm[4];
+
+      sscanf (line, "%" SCNxPTR "-%" SCNxPTR " %c%c%c%c ",
+             &from, &to,
+             &perm[0], &perm[1], &perm[2], &perm[3]);
+
+      bool writable = (memchr (perm, 'w', 4) != NULL);
+
+      if (prev_to != 0 && from - prev_to > pagesize && writable)
+       xmmap ((void *) from - pagesize, pagesize, PROT_NONE,
+              MAP_ANONYMOUS | MAP_PRIVATE, 0);
+
+      prev_to = to;
+    }
+
+  xfclose (f);
+
+  assert (argc < 1);
+  return 0;
+}
+
+#define EXPECTED_SIGNAL SIGABRT
+#define TEST_FUNCTION_ARGV do_test
+#include <support/test-driver.c>
index 05e8c292b7292f760605db391b248167faa881ec..a0b7e7becebed46b60560aa481d11e864db0c0ad 100644 (file)
@@ -56,6 +56,7 @@ libsupport-routines = \
   support_format_hostent \
   support_format_netent \
   support_isolate_in_subprocess \
+  support_need_proc \
   support_ptrace \
   support_openpty \
   support_paths \
@@ -98,6 +99,7 @@ libsupport-routines = \
   xfork \
   xftruncate \
   xgetsockname \
+  xgetline \
   xlisten \
   xlseek \
   xmalloc \
index 0536474c41670264d6599de2b3847c525f649136..d0831e2ab727cc837071b071df5ec0067df457d3 100644 (file)
@@ -81,6 +81,11 @@ char *support_quote_string (const char *);
    regular file open for writing, and initially empty.  */
 int support_descriptor_supports_holes (int fd);
 
+/* Predicates that a test requires a working /proc filesystem.  This
+   call will exit with UNSUPPORTED if /proc is not available, printing
+   WHY_MSG as part of the diagnostic.  */
+void support_need_proc (const char *why_msg);
+
 /* Error-checking wrapper functions which terminate the process on
    error.  */
 
diff --git a/support/support_need_proc.c b/support/support_need_proc.c
new file mode 100644 (file)
index 0000000..9b4eab7
--- /dev/null
@@ -0,0 +1,35 @@
+/* Indicate that a test requires a working /proc.
+   Copyright (C) 2022 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 <unistd.h>
+#include <support/check.h>
+#include <support/support.h>
+
+/* We test for /proc/self/maps since that's one of the files that one
+   of our tests actually uses, but the general idea is if Linux's
+   /proc/ (procfs) filesystem is mounted.  If not, the process exits
+   with an UNSUPPORTED result code.  */
+
+void
+support_need_proc (const char *why_msg)
+{
+#ifdef __linux__
+  if (access ("/proc/self/maps", R_OK))
+    FAIL_UNSUPPORTED ("/proc is not available, %s", why_msg);
+#endif
+}
diff --git a/support/xgetline.c b/support/xgetline.c
new file mode 100644 (file)
index 0000000..baf656a
--- /dev/null
@@ -0,0 +1,39 @@
+/* fopen with error checking.
+   Copyright (C) 2020-2021 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 <support/xstdio.h>
+#include <support/check.h>
+
+size_t
+xgetline (char **lineptr, size_t *n, FILE *stream)
+{
+  TEST_VERIFY (!ferror (stream));
+  ssize_t ret = getline (lineptr, n, stream);
+  if (ferror (stream))
+    {
+      TEST_VERIFY (ret < 0);
+      FAIL_EXIT1 ("getline: %m");
+    }
+  if (feof (stream))
+    {
+      TEST_VERIFY (ret <= 0);
+      return 0;
+    }
+  TEST_VERIFY (ret > 0);
+  return ret;
+}
index b62267a2a271506028d03d9d42151983f2ccc750..807c3afc43deeb7a3e533f769a81eb0d8136ea6f 100644 (file)
@@ -27,6 +27,11 @@ __BEGIN_DECLS
 FILE *xfopen (const char *path, const char *mode);
 void xfclose (FILE *);
 
+/* Read a line from FP, using getline.  *BUFFER must be NULL, or a
+   heap-allocated pointer of *LENGTH bytes.  Return the number of
+   bytes in the line if a line was read, or 0 on EOF.  */
+size_t xgetline (char **lineptr, size_t *n, FILE *stream);
+
 __END_DECLS
 
 #endif /* SUPPORT_XSTDIO_H */