assert: Add test for CVE-2025-0395

Message ID 20250131173402.341657-1-siddhesh@sourceware.org (mailing list archive)
State Superseded
Headers
Series assert: Add test for CVE-2025-0395 |

Checks

Context Check Description
redhat-pt-bot/TryBot-apply_patch success Patch applied to master at the time it was sent
linaro-tcwg-bot/tcwg_glibc_build--master-arm success Build passed
linaro-tcwg-bot/tcwg_glibc_check--master-arm fail Test failed
linaro-tcwg-bot/tcwg_glibc_build--master-aarch64 success Build passed
linaro-tcwg-bot/tcwg_glibc_check--master-aarch64 success Test passed
redhat-pt-bot/TryBot-32bit success Build for i686

Commit Message

Siddhesh Poyarekar Jan. 31, 2025, 5:34 p.m. UTC
  Use the __progname symbol to override the program name to induce the
failure that CVE-2025-0395 describes.

Signed-off-by: Siddhesh Poyarekar <siddhesh@sourceware.org>
---
 assert/Makefile                  |  1 +
 assert/tst-assert-sa-2025-0001.c | 91 ++++++++++++++++++++++++++++++++
 2 files changed, 92 insertions(+)
 create mode 100644 assert/tst-assert-sa-2025-0001.c
  

Comments

Adhemerval Zanella Netto Feb. 4, 2025, 4:33 p.m. UTC | #1
On 31/01/25 14:34, Siddhesh Poyarekar wrote:
> Use the __progname symbol to override the program name to induce the
> failure that CVE-2025-0395 describes.
> 
> Signed-off-by: Siddhesh Poyarekar <siddhesh@sourceware.org>
> ---
>  assert/Makefile                  |  1 +
>  assert/tst-assert-sa-2025-0001.c | 91 ++++++++++++++++++++++++++++++++
>  2 files changed, 92 insertions(+)
>  create mode 100644 assert/tst-assert-sa-2025-0001.c
> 
> diff --git a/assert/Makefile b/assert/Makefile
> index 65b9d0768e..3b691e843c 100644
> --- a/assert/Makefile
> +++ b/assert/Makefile
> @@ -39,6 +39,7 @@ tests := \
>    test-assert-perr \
>    tst-assert-c++ \
>    tst-assert-g++ \
> +  tst-assert-sa-2025-0001
>    # tests
>  
>  ifeq ($(have-cxx-thread_local),yes)
> diff --git a/assert/tst-assert-sa-2025-0001.c b/assert/tst-assert-sa-2025-0001.c
> new file mode 100644
> index 0000000000..1a86128da4
> --- /dev/null
> +++ b/assert/tst-assert-sa-2025-0001.c
> @@ -0,0 +1,91 @@
> +/* 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 <signal.h>
> +#include <stdbool.h>
> +#include <stdint.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)
> +{
> +
> +  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;
> +  void *prev_to = NULL;
> +
> +  /* 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))
> +    {
> +      void *from, *to;
> +      char perm[4];
> +
> +      sscanf (line, "%lx-%lx %c%c%c%c ",
> +	      (uintptr_t *) &from, (uintptr_t *) &to,
> +	      &perm[0], &perm[1], &perm[2], &perm[3]);

This fails to build on 32-bit architecture with recent gcc:

tst-assert-sa-2025-0001.c: In function ‘do_test’:
tst-assert-sa-2025-0001.c:70:24: error: format ‘%lx’ expects argument of type ‘long unsigned int *’, but argument 3 has type ‘uintptr_t *’ {aka ‘unsigned int *’} [-Werror=format=]
   70 |       sscanf (line, "%lx-%lx %c%c%c%c ",
      |                      ~~^
      |                        |
      |                        long unsigned int *
      |                      %x
   71 |               (uintptr_t *) &from, (uintptr_t *) &to,
      |               ~~~~~~~~~~~~~~~~~~~
      |               |
      |               uintptr_t * {aka unsigned int *}
tst-assert-sa-2025-0001.c:70:28: error: format ‘%lx’ expects argument of type ‘long unsigned int *’, but argument 4 has type ‘uintptr_t *’ {aka ‘unsigned int *’} [-Werror=format=]
   70 |       sscanf (line, "%lx-%lx %c%c%c%c ",
      |                          ~~^
      |                            |
      |                            long unsigned int *
      |                          %x
   71 |               (uintptr_t *) &from, (uintptr_t *) &to,
      |                                    ~~~~~~~~~~~~~~~~~
      |                                    |
      |                                    uintptr_t * {aka unsigned int *}

I am not sure why the buildbot has not caught it, it might due the either the
gcc version of the missing options).  In any case I think we should avoid the
alias violation ((uintptr_t *) &from) with something like:

diff --git a/assert/tst-assert-sa-2025-0001.c b/assert/tst-assert-sa-2025-0001.c
index 1a86128da4..e87b9b76a3 100644
--- a/assert/tst-assert-sa-2025-0001.c
+++ b/assert/tst-assert-sa-2025-0001.c
@@ -23,6 +23,7 @@
 #include <stdbool.h>
 #include <stdint.h>
 #include <string.h>
+#include <inttypes.h>
 #include <sys/mman.h>
 #include <support/check.h>
 #include <support/support.h>
@@ -56,7 +57,7 @@ do_test (int argc, char **argv)
   FILE *f = xfopen ("/proc/self/maps", "r");
   char *line = NULL;
   size_t len = 0;
-  void *prev_to = NULL;
+  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
@@ -64,17 +65,17 @@ do_test (int argc, char **argv)
      overflow.  */
   while (xgetline (&line, &len, f))
     {
-      void *from, *to;
+      uintptr_t from, to;
       char perm[4];

-      sscanf (line, "%lx-%lx %c%c%c%c ",
-             (uintptr_t *) &from, (uintptr_t *) &to,
+      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 != NULL && from - prev_to > pagesize && writable)
-       xmmap (from - pagesize, pagesize, PROT_NONE,
+      if (prev_to != 0 && from - prev_to > pagesize && writable)
+       xmmap ((void *)(from - pagesize), pagesize, PROT_NONE,
               MAP_ANONYMOUS | MAP_PRIVATE, 0);

       prev_to = to;

I am not sure why the buildbot for arm32 has failed, it only indicates that a
SEGFAULT occurred (error 127).  I have tests on arm64 kernel and with the patch
avoid I did not see any issue.

> +
> +      bool writable = (memchr (perm, 'w', 4) != NULL);
> +
> +      if (prev_to != NULL && from - prev_to > pagesize && writable)
> +	xmmap (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>
  
Siddhesh Poyarekar Feb. 4, 2025, 5:10 p.m. UTC | #2
On 2025-02-04 11:33, Adhemerval Zanella Netto wrote:
> I am not sure why the buildbot has not caught it, it might due the either the
> gcc version of the missing options).  In any case I think we should avoid the
> alias violation ((uintptr_t *) &from) with something like:

Ack, thanks.

> diff --git a/assert/tst-assert-sa-2025-0001.c b/assert/tst-assert-sa-2025-0001.c
> index 1a86128da4..e87b9b76a3 100644
> --- a/assert/tst-assert-sa-2025-0001.c
> +++ b/assert/tst-assert-sa-2025-0001.c
> @@ -23,6 +23,7 @@
>   #include <stdbool.h>
>   #include <stdint.h>
>   #include <string.h>
> +#include <inttypes.h>
>   #include <sys/mman.h>
>   #include <support/check.h>
>   #include <support/support.h>
> @@ -56,7 +57,7 @@ do_test (int argc, char **argv)
>     FILE *f = xfopen ("/proc/self/maps", "r");
>     char *line = NULL;
>     size_t len = 0;
> -  void *prev_to = NULL;
> +  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
> @@ -64,17 +65,17 @@ do_test (int argc, char **argv)
>        overflow.  */
>     while (xgetline (&line, &len, f))
>       {
> -      void *from, *to;
> +      uintptr_t from, to;
>         char perm[4];
> 
> -      sscanf (line, "%lx-%lx %c%c%c%c ",
> -             (uintptr_t *) &from, (uintptr_t *) &to,
> +      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 != NULL && from - prev_to > pagesize && writable)
> -       xmmap (from - pagesize, pagesize, PROT_NONE,
> +      if (prev_to != 0 && from - prev_to > pagesize && writable)
> +       xmmap ((void *)(from - pagesize), pagesize, PROT_NONE,
>                 MAP_ANONYMOUS | MAP_PRIVATE, 0);
> 
>         prev_to = to;
> 
> I am not sure why the buildbot for arm32 has failed, it only indicates that a
> SEGFAULT occurred (error 127).  I have tests on arm64 kernel and with the patch
> avoid I did not see any issue.

I haven't had a chance to dig into that yet, I hope to get an update out 
soon.

Sid
  

Patch

diff --git a/assert/Makefile b/assert/Makefile
index 65b9d0768e..3b691e843c 100644
--- a/assert/Makefile
+++ b/assert/Makefile
@@ -39,6 +39,7 @@  tests := \
   test-assert-perr \
   tst-assert-c++ \
   tst-assert-g++ \
+  tst-assert-sa-2025-0001
   # tests
 
 ifeq ($(have-cxx-thread_local),yes)
diff --git a/assert/tst-assert-sa-2025-0001.c b/assert/tst-assert-sa-2025-0001.c
new file mode 100644
index 0000000000..1a86128da4
--- /dev/null
+++ b/assert/tst-assert-sa-2025-0001.c
@@ -0,0 +1,91 @@ 
+/* 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 <signal.h>
+#include <stdbool.h>
+#include <stdint.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)
+{
+
+  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;
+  void *prev_to = NULL;
+
+  /* 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))
+    {
+      void *from, *to;
+      char perm[4];
+
+      sscanf (line, "%lx-%lx %c%c%c%c ",
+	      (uintptr_t *) &from, (uintptr_t *) &to,
+	      &perm[0], &perm[1], &perm[2], &perm[3]);
+
+      bool writable = (memchr (perm, 'w', 4) != NULL);
+
+      if (prev_to != NULL && from - prev_to > pagesize && writable)
+	xmmap (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>