Fix and add tests for printf fortification against %n in writeable memory (RHEL-119431)

Resolves: RHEL-119431
This commit is contained in:
DJ Delorie 2025-11-18 21:51:07 -05:00
parent b6c93d5187
commit 1239437396
6 changed files with 1045 additions and 0 deletions

130
glibc-RHEL-119431-1.patch Normal file
View File

@ -0,0 +1,130 @@
commit 127cefd84dc551a43d0ac58b4619503d04869042
Author: Adhemerval Zanella <adhemerval.zanella@linaro.org>
Date: Tue Sep 10 09:22:17 2024 -0300
Do not use -Wp to disable fortify (BZ 31928)
The -Wp does not work properly if the compiler is configured to enable
fortify by default, since it bypasses the compiler driver (which defines
the fortify flags in this case).
This patch is similar to the one used on Ubuntu [1].
I checked with a build for x86_64-linux-gnu, i686-linux-gnu,
aarch64-linux-gnu, s390x-linux-gnu, and riscv64-linux-gnu with
gcc-13 that enables the fortify by default.
Co-authored-by: Matthias Klose <matthias.klose@canonical.com>
[1] https://git.launchpad.net/~ubuntu-core-dev/ubuntu/+source/glibc/tree/debian/patches/ubuntu/fix-fortify-source.patch
Reviewed-by: DJ Delorie <dj@redhat.com>
diff --git a/configure b/configure
index ec0b62db36..58bfb2917f 100755
--- a/configure
+++ b/configure
@@ -7525,7 +7525,7 @@ printf "%s\n" "#define HAVE_LIBCAP 1" >>confdefs.h
fi
-no_fortify_source="-Wp,-U_FORTIFY_SOURCE"
+no_fortify_source="-U_FORTIFY_SOURCE"
fortify_source="${no_fortify_source}"
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for maximum supported _FORTIFY_SOURCE level" >&5
@@ -7577,7 +7577,7 @@ esac
if test "$libc_cv_fortify_source" = yes
then :
- fortify_source="${fortify_source},-D_FORTIFY_SOURCE=${enable_fortify_source}"
+ fortify_source="${fortify_source} -D_FORTIFY_SOURCE=${enable_fortify_source}"
fi
diff --git a/configure.ac b/configure.ac
index 7c9b57789e..5af742ce41 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1537,7 +1537,7 @@ dnl If not, then don't use it.
dnl Note that _FORTIFY_SOURCE may have been set through FLAGS too.
dnl _FORTIFY_SOURCE value will be selectively disabled for function that can't
dnl support it
-no_fortify_source="-Wp,-U_FORTIFY_SOURCE"
+no_fortify_source="-U_FORTIFY_SOURCE"
fortify_source="${no_fortify_source}"
AC_CACHE_CHECK([for maximum supported _FORTIFY_SOURCE level],
@@ -1556,7 +1556,7 @@ AS_CASE([$enable_fortify_source],
[libc_cv_fortify_source=no])
AS_IF([test "$libc_cv_fortify_source" = yes],
- [fortify_source="${fortify_source},-D_FORTIFY_SOURCE=${enable_fortify_source}"]
+ [fortify_source="${fortify_source} -D_FORTIFY_SOURCE=${enable_fortify_source}"]
)
AC_SUBST(enable_fortify_source)
diff --git a/debug/Makefile b/debug/Makefile
index 3903cc97a3..89ee80bf4d 100644
--- a/debug/Makefile
+++ b/debug/Makefile
@@ -175,16 +175,16 @@ CFLAGS-recvfrom_chk.c += -fexceptions -fasynchronous-unwind-tables
# set up for us, so keep the CFLAGS/CPPFLAGS split logical as the order is:
# <user CFLAGS> <test CFLAGS> <user CPPFLAGS> <test CPPFLAGS>
CFLAGS-tst-longjmp_chk.c += -fexceptions -fasynchronous-unwind-tables
-CPPFLAGS-tst-longjmp_chk.c += $(no-fortify-source),-D_FORTIFY_SOURCE=1
+CPPFLAGS-tst-longjmp_chk.c += $(no-fortify-source) -D_FORTIFY_SOURCE=1
CFLAGS-tst-longjmp_chk2.c += -fexceptions -fasynchronous-unwind-tables
-CPPFLAGS-tst-longjmp_chk2.c += $(no-fortify-source),-D_FORTIFY_SOURCE=1
+CPPFLAGS-tst-longjmp_chk2.c += $(no-fortify-source) -D_FORTIFY_SOURCE=1
CFLAGS-tst-longjmp_chk3.c += -fexceptions -fasynchronous-unwind-tables
-CPPFLAGS-tst-longjmp_chk3.c += $(no-fortify-source),-D_FORTIFY_SOURCE=1
-CPPFLAGS-tst-realpath-chk.c += $(no-fortify-source),-D_FORTIFY_SOURCE=2
-CPPFLAGS-tst-chk-cancel.c += $(no-fortify-source),-D_FORTIFY_SOURCE=2
-CFLAGS-tst-sprintf-fortify-rdonly.c += $(no-fortify-source),-D_FORTIFY_SOURCE=2
-CFLAGS-tst-fortify-syslog.c += $(no-fortify-source),-D_FORTIFY_SOURCE=2
-CFLAGS-tst-fortify-wide.c += $(no-fortify-source),-D_FORTIFY_SOURCE=2
+CPPFLAGS-tst-longjmp_chk3.c += $(no-fortify-source) -D_FORTIFY_SOURCE=1
+CPPFLAGS-tst-realpath-chk.c += $(no-fortify-source) -D_FORTIFY_SOURCE=2
+CPPFLAGS-tst-chk-cancel.c += $(no-fortify-source) -D_FORTIFY_SOURCE=2
+CFLAGS-tst-sprintf-fortify-rdonly.c += $(no-fortify-source) -D_FORTIFY_SOURCE=2
+CFLAGS-tst-fortify-syslog.c += $(no-fortify-source) -D_FORTIFY_SOURCE=2
+CFLAGS-tst-fortify-wide.c += $(no-fortify-source) -D_FORTIFY_SOURCE=2
# _FORTIFY_SOURCE tests.
# Auto-generate tests for _FORTIFY_SOURCE for different levels, compilers and
@@ -222,7 +222,8 @@ src-chk-nongnu = \#undef _GNU_SOURCE
# cannot be disabled via pragmas, so require -Wno-error to be used.
define gen-chk-test
tests-$(1)-$(4)-chk += tst-fortify-$(1)-$(2)-$(3)-$(4)
-CFLAGS-tst-fortify-$(1)-$(2)-$(3)-$(4).$(1) += $(no-fortify-source),-D_FORTIFY_SOURCE=$(3) -Wno-format \
+CFLAGS-tst-fortify-$(1)-$(2)-$(3)-$(4).$(1) += $(no-fortify-source) -D_FORTIFY_SOURCE=$(3) \
+ -Wno-format \
-Wno-deprecated-declarations \
-Wno-error
$(eval $(call cflags-$(2),$(1),$(3),$(4)))
diff --git a/stdio-common/Makefile b/stdio-common/Makefile
index a1db859684..44165a9c59 100644
--- a/stdio-common/Makefile
+++ b/stdio-common/Makefile
@@ -662,7 +662,7 @@ CFLAGS-tst-gets.c += -Wno-deprecated-declarations
# BZ #11319 was first fixed for regular vdprintf, then reopened because
# the fortified version had the same bug.
-CFLAGS-tst-bz11319-fortify2.c += $(no-fortify-source),-D_FORTIFY_SOURCE=2
+CFLAGS-tst-bz11319-fortify2.c += $(no-fortify-source) -D_FORTIFY_SOURCE=2
CFLAGS-tst-memstream-string.c += -fno-builtin-fprintf
diff --git a/wcsmbs/Makefile b/wcsmbs/Makefile
index 63adf0e8ef..37a44e62c3 100644
--- a/wcsmbs/Makefile
+++ b/wcsmbs/Makefile
@@ -263,7 +263,7 @@ CFLAGS-wcstod_l.c += $(strtox-CFLAGS) $(config-cflags-wno-ignored-attributes)
CFLAGS-wcstold_l.c += $(strtox-CFLAGS) $(config-cflags-wno-ignored-attributes)
CFLAGS-wcstof128_l.c += $(strtox-CFLAGS)
CFLAGS-wcstof_l.c += $(strtox-CFLAGS) $(config-cflags-wno-ignored-attributes)
-CPPFLAGS-tst-wchar-h.c += $(no-fortify-source),-D_FORTIFY_SOURCE=2
+CPPFLAGS-tst-wchar-h.c += $(no-fortify-source) -D_FORTIFY_SOURCE=2
CFLAGS-wcschr.c += $(config-cflags-wno-ignored-attributes)
CFLAGS-wmemchr.c += $(config-cflags-wno-ignored-attributes)

696
glibc-RHEL-119431-2.patch Normal file
View File

@ -0,0 +1,696 @@
commit ed6a68bac7cd056abda9008019c71b167f0362dc
Author: Adhemerval Zanella <adhemerval.zanella@linaro.org>
Date: Fri Mar 14 16:09:57 2025 -0300
debug: Improve '%n' fortify detection (BZ 30932)
The 7bb8045ec0 path made the '%n' fortify check ignore EMFILE errors
while trying to open /proc/self/maps, and this added a security
issue where EMFILE can be attacker-controlled thus making it
ineffective for some cases.
The EMFILE failure is reinstated but with a different error
message. Also, to improve the false positive of the hardening for
the cases where no new files can be opened, the
_dl_readonly_area now uses _dl_find_object to check if the
memory area is within a writable ELF segment. The procfs method is
still used as fallback.
Checked on x86_64-linux-gnu and i686-linux-gnu.
Reviewed-by: Arjun Shankar <arjun@redhat.com>
diff --git a/Makeconfig b/Makeconfig
index aa547a443f..bf50406656 100644
--- a/Makeconfig
+++ b/Makeconfig
@@ -633,7 +633,7 @@ link-libc-printers-tests = $(link-libc-rpath) \
$(link-libc-tests-after-rpath-link)
# This is how to find at build-time things that will be installed there.
-rpath-dirs = math elf dlfcn nss nis rt resolv mathvec support
+rpath-dirs = math elf dlfcn nss nis rt resolv mathvec support debug
rpath-link = \
$(common-objdir):$(subst $(empty) ,:,$(patsubst ../$(subdir),.,$(rpath-dirs:%=$(common-objpfx)%)))
else # build-static
diff --git a/debug/Makefile b/debug/Makefile
index 905f2bf7e0..2484580cd2 100644
--- a/debug/Makefile
+++ b/debug/Makefile
@@ -74,6 +74,7 @@ routines = \
readlink_chk \
readlinkat_chk \
readonly-area \
+ readonly-area-fallback \
realpath_chk \
recv_chk \
recvfrom_chk \
@@ -183,9 +184,15 @@ CPPFLAGS-tst-longjmp_chk3.c += $(no-fortify-source) -D_FORTIFY_SOURCE=1
CPPFLAGS-tst-realpath-chk.c += $(no-fortify-source) -D_FORTIFY_SOURCE=2
CPPFLAGS-tst-chk-cancel.c += $(no-fortify-source) -D_FORTIFY_SOURCE=2
CFLAGS-tst-sprintf-fortify-rdonly.c += $(no-fortify-source) -D_FORTIFY_SOURCE=2
+CFLAGS-tst-sprintf-fortify-rdonly-mod.c += $(no-fortify-source) -D_FORTIFY_SOURCE=2
+CFLAGS-tst-sprintf-fortify-rdonly-dlopen.c += $(no-fortify-source) -D_FORTIFY_SOURCE=2
CFLAGS-tst-fortify-syslog.c += $(no-fortify-source) -D_FORTIFY_SOURCE=2
CFLAGS-tst-fortify-wide.c += $(no-fortify-source) -D_FORTIFY_SOURCE=2
+$(objpfx)tst-sprintf-fortify-rdonly: \
+ $(objpfx)tst-sprintf-fortify-rdonly-mod.so \
+ $(objpfx)tst-sprintf-fortify-rdonly-dlopen.so
+
# _FORTIFY_SOURCE tests.
# Auto-generate tests for _FORTIFY_SOURCE for different levels, compilers and
# preprocessor conditions based on tst-fortify.c.
@@ -305,6 +312,11 @@ tests-container += \
tst-fortify-syslog \
# tests-container
+modules-names += \
+ tst-sprintf-fortify-rdonly-dlopen \
+ tst-sprintf-fortify-rdonly-mod \
+ # modules-names
+
ifeq ($(have-ssp),yes)
tests += tst-ssp-1
endif
diff --git a/debug/readonly-area.c b/debug/readonly-area.c
index 04b437ed8d..4311b8214a 100644
--- a/debug/readonly-area.c
+++ b/debug/readonly-area.c
@@ -16,18 +16,19 @@
<https://www.gnu.org/licenses/>. */
#include <stdlib.h>
+#include <ldsodefs.h>
-/* Return 1 if the whole area PTR .. PTR+SIZE is not writable.
- Return -1 if it is writable. */
-
-int
+enum readonly_error_type
__readonly_area (const void *ptr, size_t size)
{
- /* We cannot determine in general whether memory is writable or not.
- This must be handled in a system-dependent manner. to not
- unconditionally break code we need to return here a positive
- answer. This disables this security measure but that is the
- price people have to pay for using systems without a real
- implementation of this interface. */
- return 1;
+ switch (GLRO(dl_readonly_area (ptr, size)))
+ {
+ case dl_readonly_area_rdonly:
+ return readonly_noerror;
+ case dl_readonly_area_writable:
+ return readonly_area_writable;
+ default:
+ break;
+ }
+ return __readonly_area_fallback (ptr, size);
}
diff --git a/debug/tst-sprintf-fortify-rdonly-dlopen.c b/debug/tst-sprintf-fortify-rdonly-dlopen.c
new file mode 100644
index 0000000000..7da3f51f16
--- /dev/null
+++ b/debug/tst-sprintf-fortify-rdonly-dlopen.c
@@ -0,0 +1 @@
+#include "tst-sprintf-fortify-rdonly-mod.c"
diff --git a/debug/tst-sprintf-fortify-rdonly-mod.c b/debug/tst-sprintf-fortify-rdonly-mod.c
new file mode 100644
index 0000000000..3655f27b32
--- /dev/null
+++ b/debug/tst-sprintf-fortify-rdonly-mod.c
@@ -0,0 +1,56 @@
+/* Testcase for BZ 30932.
+ 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 <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+static const char *str2 = "F";
+static char writeable_format[10] = "%s";
+static char relro_format[10] __attribute__ ((section (".data.rel.ro"))) =
+ "%s%n%s%n";
+
+void
+init_writable (void)
+{
+ strcpy (writeable_format + 2, "%n%s%n");
+}
+
+int
+sprintf_writable (int *n1, int *n2)
+{
+ char buf[128];
+ return sprintf (buf, writeable_format, str2, n1, str2, n2);
+}
+
+int
+sprintf_relro (int *n1, int *n2)
+{
+ char buf[128];
+ return sprintf (buf, relro_format, str2, n1, str2, n2);
+}
+
+int
+sprintf_writable_malloc (int *n1, int *n2)
+{
+ char buf[128];
+ char *buf2_malloc = strdup (writeable_format);
+ if (buf2_malloc == NULL)
+ abort ();
+ return sprintf (buf, buf2_malloc, str2, n1, str2, n2);
+}
diff --git a/debug/tst-sprintf-fortify-rdonly.c b/debug/tst-sprintf-fortify-rdonly.c
index ba5a791020..fafc8340ea 100644
--- a/debug/tst-sprintf-fortify-rdonly.c
+++ b/debug/tst-sprintf-fortify-rdonly.c
@@ -27,16 +27,64 @@
#include <support/check.h>
#include <support/support.h>
#include <support/temp_file.h>
+#include <support/xdlfcn.h>
-jmp_buf chk_fail_buf;
-bool chk_fail_ok;
+static sigjmp_buf chk_fail_buf;
+static volatile int ret;
+static bool chk_fail_ok;
-const char *str2 = "F";
-char buf2[10] = "%s";
+static void
+handler (int sig)
+{
+ if (chk_fail_ok)
+ {
+ chk_fail_ok = false;
+ longjmp (chk_fail_buf, 1);
+ }
+ else
+ _exit (127);
+}
+
+#define FORTIFY_FAIL \
+ do { printf ("Failure on line %d\n", __LINE__); ret = 1; } while (0)
+#define CHK_FAIL_START \
+ chk_fail_ok = true; \
+ if (! sigsetjmp (chk_fail_buf, 1)) \
+ {
+#define CHK_FAIL_END \
+ chk_fail_ok = false; \
+ FORTIFY_FAIL; \
+ }
+
+static const char *str2 = "F";
+static char writeable_format[10] = "%s";
+static char relro_format[10] __attribute__ ((section (".data.rel.ro"))) =
+ "%s%n%s%n";
+
+extern void init_writable (void);
+extern int sprintf_writable (int *, int *);
+extern int sprintf_relro (int *, int *);
+extern int sprintf_writable_malloc (int *, int *);
+
+#define str(__x) # __x
+void (*init_writable_dlopen)(void);
+int (*sprintf_writable_dlopen)(int *, int *);
+int (*sprintf_rdonly_dlopen)(int *, int *);
+int (*sprintf_writable_malloc_dlopen)(int *, int *);
static int
do_test (void)
{
+ set_fortify_handler (handler);
+
+ {
+ void *h = xdlopen ("tst-sprintf-fortify-rdonly-dlopen.so", RTLD_NOW);
+ init_writable_dlopen = xdlsym (h, str(init_writable));
+ sprintf_writable_dlopen = xdlsym (h, str(sprintf_writable));
+ sprintf_rdonly_dlopen = xdlsym (h, str(sprintf_relro));
+ sprintf_writable_malloc_dlopen = xdlsym (h, str(sprintf_writable_malloc));
+ }
+
struct rlimit rl;
int max_fd = 24;
@@ -63,20 +111,94 @@ do_test (void)
}
TEST_VERIFY_EXIT (nfiles != 0);
- /* When the format string is writable and contains %n,
- with -D_FORTIFY_SOURCE=2 it causes __chk_fail. However, if libc can not
- open procfs to check if the input format string in within a writable
- memory segment, the fortify version can not perform the check. */
- char buf[128];
- int n1;
- int n2;
-
- strcpy (buf2 + 2, "%n%s%n");
- if (sprintf (buf, buf2, str2, &n1, str2, &n2) != 2
- || n1 != 1 || n2 != 2)
- FAIL_EXIT1 ("sprintf failed: %s %d %d", buf, n1, n2);
-
- return 0;
+ strcpy (writeable_format + 2, "%n%s%n");
+ init_writable ();
+ init_writable_dlopen ();
+
+ /* writeable_format is at a writable part of .bss segment, so libc should be
+ able to check it without resorting to procfs. */
+ {
+ char buf[128];
+ int n1;
+ int n2;
+ CHK_FAIL_START
+ sprintf (buf, writeable_format, str2, &n1, str2, &n2);
+ CHK_FAIL_END
+ }
+
+ /* Same as before, but from an library. */
+ {
+ int n1;
+ int n2;
+ CHK_FAIL_START
+ sprintf_writable (&n1, &n2);
+ CHK_FAIL_END
+ }
+
+ {
+ int n1;
+ int n2;
+ CHK_FAIL_START
+ sprintf_writable_dlopen (&n1, &n2);
+ CHK_FAIL_END
+ }
+
+ /* relro_format is at a readonly part of .bss segment, so '%n' in format input
+ should not trigger a fortify failure. */
+ {
+ char buf[128];
+ int n1;
+ int n2;
+ if (sprintf (buf, relro_format, str2, &n1, str2, &n2) != 2
+ || n1 != 1 || n2 != 2)
+ FAIL_EXIT1 ("sprintf failed: %s %d %d", buf, n1, n2);
+ }
+
+ /* Same as before, but from an library. */
+ {
+ int n1;
+ int n2;
+ if (sprintf_relro (&n1, &n2) != 2 || n1 != 1 || n2 != 2)
+ FAIL_EXIT1 ("sprintf failed: %d %d", n1, n2);
+ }
+
+ {
+ int n1;
+ int n2;
+ if (sprintf_rdonly_dlopen (&n1, &n2) != 2 || n1 != 1 || n2 != 2)
+ FAIL_EXIT1 ("sprintf failed: %d %d", n1, n2);
+ }
+
+ /* However if the format string is placed on a writable memory not covered
+ by ELF segments, libc needs to resort to procfs. */
+ {
+ char buf[128];
+ int n1;
+ int n2;
+ char *buf2_malloc = xstrdup (writeable_format);
+ CHK_FAIL_START
+ sprintf (buf, buf2_malloc, str2, &n1, str2, &n2);
+ CHK_FAIL_END
+ }
+
+ /* Same as before, but from an library. */
+ {
+ int n1;
+ int n2;
+ CHK_FAIL_START
+ sprintf_writable_malloc (&n1, &n2);
+ CHK_FAIL_END
+ }
+
+ {
+ int n1;
+ int n2;
+ CHK_FAIL_START
+ sprintf_writable_malloc_dlopen (&n1, &n2);
+ CHK_FAIL_END
+ }
+
+ return ret;
}
#include <support/test-driver.c>
diff --git a/elf/Makefile b/elf/Makefile
index 3d60000ec9..5566d39dac 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -73,6 +73,7 @@ dl-routines = \
dl-open \
dl-origin \
dl-printf \
+ dl-readonly-area \
dl-reloc \
dl-runtime \
dl-scope \
diff --git a/elf/dl-readonly-area.c b/elf/dl-readonly-area.c
new file mode 100644
index 0000000000..22769ec9d9
--- /dev/null
+++ b/elf/dl-readonly-area.c
@@ -0,0 +1,86 @@
+/* Check if range is within a read-only from a loaded ELF object.
+ 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 <ldsodefs.h>
+
+static bool
+check_relro (const struct link_map *l, uintptr_t start, uintptr_t end)
+{
+ if (l->l_relro_addr != 0)
+ {
+ uintptr_t relro_start = ALIGN_DOWN (l->l_addr + l->l_relro_addr,
+ GLRO(dl_pagesize));
+ uintptr_t relro_end = ALIGN_DOWN (l->l_addr + l->l_relro_addr
+ + l->l_relro_size,
+ GLRO(dl_pagesize));
+ /* RELRO is caved out from a RW segment, so the next range is either
+ RW or nonexistent. */
+ return relro_start <= start && end <= relro_end
+ ? dl_readonly_area_rdonly : dl_readonly_area_writable;
+
+ }
+ return dl_readonly_area_writable;
+}
+
+enum dl_readonly_area_error_type
+_dl_readonly_area (const void *ptr, size_t size)
+{
+ struct dl_find_object dlfo;
+ if (_dl_find_object ((void *)ptr, &dlfo) != 0)
+ return dl_readonly_area_not_found;
+
+ const struct link_map *l = dlfo.dlfo_link_map;
+ uintptr_t ptr_start = (uintptr_t) ptr;
+ uintptr_t ptr_end = ptr_start + size;
+
+ for (const ElfW(Phdr) *ph = l->l_phdr; ph < &l->l_phdr[l->l_phnum]; ++ph)
+ if (ph->p_type == PT_LOAD)
+ {
+ /* For segments with alignment larger than the page size,
+ _dl_map_segment allocates additional space that is mark as
+ PROT_NONE (so we can ignore). */
+ uintptr_t from = l->l_addr
+ + ALIGN_DOWN (ph->p_vaddr, GLRO(dl_pagesize));
+ uintptr_t to = l->l_addr
+ + ALIGN_UP (ph->p_vaddr + ph->p_filesz, GLRO(dl_pagesize));
+
+ /* Found an entry that at least partially covers the area. */
+ if (from < ptr_end && to > ptr_start)
+ {
+ if (ph->p_flags & PF_W)
+ return check_relro (l, ptr_start, ptr_end);
+
+ if ((ph->p_flags & PF_R) == 0)
+ return dl_readonly_area_writable;
+
+ if (from <= ptr_start && to >= ptr_end)
+ return dl_readonly_area_rdonly;
+ else if (from <= ptr_start)
+ size -= to - ptr_start;
+ else if (to >= ptr_end)
+ size -= ptr_end - from;
+ else
+ size -= to - from;
+
+ if (size == 0)
+ break;
+ }
+ }
+
+ return size == 0 ? dl_readonly_area_rdonly : dl_readonly_area_not_found;
+}
diff --git a/elf/rtld.c b/elf/rtld.c
index 00b25c1a73..099c447e80 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -372,6 +372,7 @@ struct rtld_global_ro _rtld_global_ro attribute_relro =
._dl_error_free = _dl_error_free,
._dl_tls_get_addr_soft = _dl_tls_get_addr_soft,
._dl_libc_freeres = __rtld_libc_freeres,
+ ._dl_readonly_area = _dl_readonly_area,
};
/* If we would use strong_alias here the compiler would see a
non-hidden definition. This would undo the effect of the previous
diff --git a/include/stdlib.h b/include/stdlib.h
index 57f4ab8545..b7147ba590 100644
--- a/include/stdlib.h
+++ b/include/stdlib.h
@@ -361,6 +361,21 @@ struct abort_msg_s
extern struct abort_msg_s *__abort_msg;
libc_hidden_proto (__abort_msg)
+enum readonly_error_type
+{
+ readonly_noerror,
+ readonly_area_writable,
+ readonly_procfs_inaccessible,
+ readonly_procfs_open_fail,
+};
+
+extern enum readonly_error_type __readonly_area (const void *ptr,
+ size_t size)
+ attribute_hidden;
+extern enum readonly_error_type __readonly_area_fallback (const void *ptr,
+ size_t size)
+ attribute_hidden;
+
# if IS_IN (rtld)
extern __typeof (unsetenv) unsetenv attribute_hidden;
extern __typeof (__strtoul_internal) __strtoul_internal attribute_hidden;
diff --git a/stdio-common/vfprintf-internal.c b/stdio-common/vfprintf-internal.c
index aa9708bff5..fa41e1b242 100644
--- a/stdio-common/vfprintf-internal.c
+++ b/stdio-common/vfprintf-internal.c
@@ -576,7 +576,8 @@ static const uint8_t jump_table[] =
/* Handle positional format specifiers. */
static void printf_positional (struct Xprintf_buffer *buf,
- const CHAR_T *format, int readonly_format,
+ const CHAR_T *format,
+ enum readonly_error_type readonly_format,
va_list ap, va_list *ap_savep,
int nspecs_done, const UCHAR_T *lead_str_end,
CHAR_T *work_buffer, int save_errno,
@@ -626,9 +627,7 @@ Xprintf_buffer (struct Xprintf_buffer *buf, const CHAR_T *format,
/* For the %m format we may need the current `errno' value. */
int save_errno = errno;
- /* 1 if format is in read-only memory, -1 if it is in writable memory,
- 0 if unknown. */
- int readonly_format = 0;
+ enum readonly_error_type readonly_format = readonly_noerror;
/* Initialize local variables. */
grouping = (const char *) -1;
@@ -1045,7 +1044,7 @@ do_positional:
static void
printf_positional (struct Xprintf_buffer * buf, const CHAR_T *format,
- int readonly_format,
+ enum readonly_error_type readonly_format,
va_list ap, va_list *ap_savep, int nspecs_done,
const UCHAR_T *lead_str_end,
CHAR_T *work_buffer, int save_errno,
diff --git a/stdio-common/vfprintf-process-arg.c b/stdio-common/vfprintf-process-arg.c
index 8d20493766..90b5e61ceb 100644
--- a/stdio-common/vfprintf-process-arg.c
+++ b/stdio-common/vfprintf-process-arg.c
@@ -324,16 +324,24 @@ LABEL (form_pointer):
LABEL (form_number):
if ((mode_flags & PRINTF_FORTIFY) != 0)
{
- if (! readonly_format)
- {
- extern int __readonly_area (const void *, size_t)
- attribute_hidden;
- readonly_format
- = __readonly_area (format, ((STR_LEN (format) + 1)
- * sizeof (CHAR_T)));
- }
- if (readonly_format < 0)
- __libc_fatal ("*** %n in writable segment detected ***\n");
+ if (readonly_format == readonly_noerror)
+ readonly_format = __readonly_area (format, ((STR_LEN (format) + 1)
+ * sizeof (CHAR_T)));
+ switch (readonly_format)
+ {
+ case readonly_area_writable:
+ __libc_fatal ("*** %n in writable segments detected ***\n");
+ /* The format is not within ELF segments and opening /proc/self/maps
+ failed because there are too many files. */
+ case readonly_procfs_open_fail:
+ __libc_fatal ("*** procfs could not open ***\n");
+ /* The /proc/self/maps can not be opened either because it is not
+ available or the process does not have the right permission. Since
+ it should not be attacker-controlled we can avoid failure. */
+ case readonly_procfs_inaccessible:
+ case readonly_noerror:
+ break;
+ }
}
/* Answer the count of characters written. */
void *ptrptr = process_arg_pointer ();
diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h
index 19494b82ee..5b12a41a18 100644
--- a/sysdeps/generic/ldsodefs.h
+++ b/sysdeps/generic/ldsodefs.h
@@ -264,6 +264,12 @@ struct audit_ifaces
struct audit_ifaces *next;
};
+enum dl_readonly_area_error_type
+{
+ dl_readonly_area_rdonly,
+ dl_readonly_area_writable,
+ dl_readonly_area_not_found,
+};
/* Test whether given NAME matches any of the names of the given object. */
extern int _dl_name_match_p (const char *__name, const struct link_map *__map)
@@ -685,6 +691,10 @@ struct rtld_global_ro
dlopen. */
int (*_dl_find_object) (void *, struct dl_find_object *);
+ /* Implementation of _dl_readonly_area, used in fortify routines to check
+ if memory area is within a read-only ELF segment. */
+ enum dl_readonly_area_error_type (*_dl_readonly_area) (const void *, size_t);
+
/* Dynamic linker operations used after static dlopen. */
const struct dlfcn_hook *_dl_dlfcn_hook;
@@ -1297,6 +1307,10 @@ extern void _dl_show_scope (struct link_map *new, int from)
extern struct link_map *_dl_find_dso_for_object (const ElfW(Addr) addr);
rtld_hidden_proto (_dl_find_dso_for_object)
+extern enum dl_readonly_area_error_type _dl_readonly_area (const void *ptr,
+ size_t size)
+ attribute_hidden;
+
/* Initialization which is normally done by the dynamic linker. */
extern void _dl_non_dynamic_init (void)
attribute_hidden;
diff --git a/sysdeps/mach/readonly-area.c b/sysdeps/mach/readonly-area-fallback.c
similarity index 90%
rename from sysdeps/mach/readonly-area.c
rename to sysdeps/mach/readonly-area-fallback.c
index fb89413fe6..3fb873298b 100644
--- a/sysdeps/mach/readonly-area.c
+++ b/sysdeps/mach/readonly-area-fallback.c
@@ -20,11 +20,8 @@
#include <stdint.h>
#include <mach.h>
-/* Return 1 if the whole area PTR .. PTR+SIZE is not writable.
- Return -1 if it is writable. */
-
-int
-__readonly_area (const char *ptr, size_t size)
+enum readonly_error_type
+__readonly_area_fallback (const void *ptr, size_t size)
{
vm_address_t region_address = (uintptr_t) ptr;
vm_size_t region_length = size;
@@ -46,11 +43,11 @@ __readonly_area (const char *ptr, size_t size)
continue;
if (protection & VM_PROT_WRITE)
- return -1;
+ return readonly_area_writable;
if (region_address - (uintptr_t) ptr >= size)
break;
}
- return 1;
+ return readonly_noerror;
}
diff --git a/sysdeps/unix/sysv/linux/readonly-area.c b/sysdeps/unix/sysv/linux/readonly-area-fallback.c
similarity index 84%
rename from sysdeps/unix/sysv/linux/readonly-area.c
rename to sysdeps/unix/sysv/linux/readonly-area-fallback.c
index 62d2070c72..c93ad2a598 100644
--- a/sysdeps/unix/sysv/linux/readonly-area.c
+++ b/sysdeps/unix/sysv/linux/readonly-area-fallback.c
@@ -23,11 +23,8 @@
#include <string.h>
#include "libio/libioP.h"
-/* Return 1 if the whole area PTR .. PTR+SIZE is not writable.
- Return -1 if it is writable. */
-
-int
-__readonly_area (const char *ptr, size_t size)
+enum readonly_error_type
+__readonly_area_fallback (const void *ptr, size_t size)
{
const void *ptr_end = ptr + size;
@@ -42,11 +39,11 @@ __readonly_area (const char *ptr, size_t size)
to the /proc filesystem if it is set[ug]id. There has
been no willingness to change this in the kernel so
far. */
- || errno == EACCES
- /* Process has reached the maximum number of open files. */
- || errno == EMFILE)
- return 1;
- return -1;
+ || errno == EACCES)
+ return readonly_procfs_inaccessible;
+ /* Process has reached the maximum number of open files or another
+ unusual error. */
+ return readonly_procfs_open_fail;
}
/* We need no locking. */
@@ -98,7 +95,5 @@ __readonly_area (const char *ptr, size_t size)
fclose (fp);
free (line);
- /* If the whole area between ptr and ptr_end is covered by read-only
- VMAs, return 1. Otherwise return -1. */
- return size == 0 ? 1 : -1;
+ return size == 0 ? readonly_noerror : readonly_area_writable;
}

28
glibc-RHEL-119431-3.patch Normal file
View File

@ -0,0 +1,28 @@
commit 3a9c1497e320d77b2453ee88d7d6b40e162e85d4
Author: Florian Weimer <fweimer@redhat.com>
Date: Fri Jul 18 19:58:56 2025 +0200
debug: Do not link tst-sprintf-fortify-rdonly against the dlopen module
The test intends to load tst-sprintf-fortify-rdonly-dlopen.so via
dlopen, and directly linking against it prevents that.
Reviewed-by: Adhemerval Zanella Netto <adhemerval.zanella@linaro.org>
diff --git a/debug/Makefile b/debug/Makefile
index f29bd20b97..13f638e344 100644
--- a/debug/Makefile
+++ b/debug/Makefile
@@ -189,9 +189,9 @@ CFLAGS-tst-sprintf-fortify-rdonly-dlopen.c += $(no-fortify-source) -D_FORTIFY_SO
CFLAGS-tst-fortify-syslog.c += $(no-fortify-source) -D_FORTIFY_SOURCE=2
CFLAGS-tst-fortify-wide.c += $(no-fortify-source) -D_FORTIFY_SOURCE=2
-$(objpfx)tst-sprintf-fortify-rdonly: \
- $(objpfx)tst-sprintf-fortify-rdonly-mod.so \
- $(objpfx)tst-sprintf-fortify-rdonly-dlopen.so
+$(objpfx)tst-sprintf-fortify-rdonly: $(objpfx)tst-sprintf-fortify-rdonly-mod.so
+$(objpfx)tst-sprintf-fortify-rdonly.out: \
+ $(objpfx)tst-sprintf-fortify-rdonly-dlopen.so
# _FORTIFY_SOURCE tests.
# Auto-generate tests for _FORTIFY_SOURCE for different levels, compilers and

93
glibc-RHEL-119431-4.patch Normal file
View File

@ -0,0 +1,93 @@
commit 01196393c257c59f63e0e14fa1bfe8d2a699bf2d
Author: Florian Weimer <fweimer@redhat.com>
Date: Fri Jul 18 19:58:59 2025 +0200
elf: Initialize GLRO (dl_read_only_area) after static dlopen (bug 33139)
The _dl_read_only_area function in the uninitialized ld.so after
static dlopen is not able to find anything. Instead, we need to
redirect to the code from the statically linked main program.
Fixes commit d60fffe28a46b2a41fc308c1804ff02375d27408 ("debug:
Improve '%n' fortify detection (BZ 30932)").
Reviewed-by: Adhemerval Zanella Netto <adhemerval.zanella@linaro.org>
diff --git a/debug/Makefile b/debug/Makefile
index 13f638e344..919bf91c42 100644
--- a/debug/Makefile
+++ b/debug/Makefile
@@ -186,12 +186,19 @@ CPPFLAGS-tst-chk-cancel.c += $(no-fortify-source) -D_FORTIFY_SOURCE=2
CFLAGS-tst-sprintf-fortify-rdonly.c += $(no-fortify-source) -D_FORTIFY_SOURCE=2
CFLAGS-tst-sprintf-fortify-rdonly-mod.c += $(no-fortify-source) -D_FORTIFY_SOURCE=2
CFLAGS-tst-sprintf-fortify-rdonly-dlopen.c += $(no-fortify-source) -D_FORTIFY_SOURCE=2
+CFLAGS-tst-sprintf-fortify-rdonly-static.c += $(no-fortify-source) -D_FORTIFY_SOURCE=2
CFLAGS-tst-fortify-syslog.c += $(no-fortify-source) -D_FORTIFY_SOURCE=2
CFLAGS-tst-fortify-wide.c += $(no-fortify-source) -D_FORTIFY_SOURCE=2
$(objpfx)tst-sprintf-fortify-rdonly: $(objpfx)tst-sprintf-fortify-rdonly-mod.so
$(objpfx)tst-sprintf-fortify-rdonly.out: \
$(objpfx)tst-sprintf-fortify-rdonly-dlopen.so
+$(objpfx)tst-sprintf-fortify-rdonly-static: \
+ $(objpfx)tst-sprintf-fortify-rdonly-dlopen.o
+$(objpfx)tst-sprintf-fortify-rdonly-static.out: \
+ $(objpfx)tst-sprintf-fortify-rdonly-dlopen.so
+tst-sprintf-fortify-rdonly-static-ENV = \
+ LD_LIBRARY_PATH=$(objpfx):$(common-objpfx):$(common-objpfx)/elf
# _FORTIFY_SOURCE tests.
# Auto-generate tests for _FORTIFY_SOURCE for different levels, compilers and
@@ -302,9 +309,14 @@ tests = \
tst-longjmp_chk3 \
tst-realpath-chk \
tst-sprintf-fortify-rdonly \
+ tst-sprintf-fortify-rdonly-static \
tst-sprintf-fortify-unchecked \
# tests
+tests-static = \
+ tst-sprintf-fortify-rdonly-static \
+ # tests-static
+
tests-time64 += \
$(tests-all-time64-chk) \
# tests-time64
diff --git a/debug/tst-sprintf-fortify-rdonly-static.c b/debug/tst-sprintf-fortify-rdonly-static.c
new file mode 100644
index 0000000000..ca5bbd5aff
--- /dev/null
+++ b/debug/tst-sprintf-fortify-rdonly-static.c
@@ -0,0 +1 @@
+#include "tst-sprintf-fortify-rdonly.c"
diff --git a/elf/rtld.c b/elf/rtld.c
index f889f01b4a..493f9696ea 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -372,7 +372,6 @@ struct rtld_global_ro _rtld_global_ro attribute_relro =
._dl_error_free = _dl_error_free,
._dl_tls_get_addr_soft = _dl_tls_get_addr_soft,
._dl_libc_freeres = __rtld_libc_freeres,
- ._dl_readonly_area = _dl_readonly_area,
};
/* If we would use strong_alias here the compiler would see a
non-hidden definition. This would undo the effect of the previous
@@ -457,6 +456,7 @@ _dl_start_final (void *arg, struct dl_start_final_info *info)
/* Do not use an initializer for these members because it would
interfere with __rtld_static_init. */
GLRO (dl_find_object) = &_dl_find_object;
+ GLRO (dl_readonly_area) = &_dl_readonly_area;
/* If it hasn't happen yet record the startup time. */
rtld_timer_start (&start_time);
diff --git a/elf/rtld_static_init.c b/elf/rtld_static_init.c
index 642379076b..9c56180f88 100644
--- a/elf/rtld_static_init.c
+++ b/elf/rtld_static_init.c
@@ -79,6 +79,7 @@ __rtld_static_init (struct link_map *map)
attribute_hidden;
dl->_dl_tls_static_size = _dl_tls_static_size;
dl->_dl_find_object = _dl_find_object;
+ dl->_dl_readonly_area = _dl_readonly_area;
__rtld_static_init_arch (map, dl);
}

93
glibc-RHEL-119431-5.patch Normal file
View File

@ -0,0 +1,93 @@
Downstream-only patch to restore internal GLIBC_PRIVATE ABI change due
to glibc-RHEL-119431-2.patch. Adding an entry to rtld_global_ro struct
changes offsets. This patch corrects that.
RHEL does not support fortify inside shared objects loaded from
statically linked programs, so that test is xfail'd.
diff --git a/debug/readonly-area.c b/debug/readonly-area.c
index 0648121846cdd5d1..86e3e9b9c3b03958 100644
--- a/debug/readonly-area.c
+++ b/debug/readonly-area.c
@@ -21,7 +21,7 @@
enum readonly_error_type
__readonly_area (const void *ptr, size_t size)
{
- switch (GLRO(dl_readonly_area (ptr, size)))
+ switch (_dl_readonly_area (ptr, size))
{
case dl_readonly_area_rdonly:
return readonly_noerror;
diff --git a/elf/Versions b/elf/Versions
index 4b8b155d8e2bef70..17834c7d1c371e1d 100644
--- a/elf/Versions
+++ b/elf/Versions
@@ -79,5 +70,8 @@ ld {
# Set value of a tunable.
__tunable_is_initialized;
__tunable_get_val;
+
+ # Check if an address range within a loaded ELF object is read-only.
+ _dl_readonly_area;
}
}
diff --git a/elf/rtld.c b/elf/rtld.c
index d421750734e113e6..d02ecc834c9a4d43 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -456,7 +456,6 @@ _dl_start_final (void *arg, struct dl_st
/* Do not use an initializer for these members because it would
interfere with __rtld_static_init. */
GLRO (dl_find_object) = &_dl_find_object;
- GLRO (dl_readonly_area) = &_dl_readonly_area;
/* If it hasn't happen yet record the startup time. */
rtld_timer_start (&start_time);
diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h
index 15c21491a291d1ae..537d1293c7b5543b 100644
--- a/sysdeps/generic/ldsodefs.h
+++ b/sysdeps/generic/ldsodefs.h
@@ -691,10 +691,6 @@ struct rtld_global_ro
dlopen. */
int (*_dl_find_object) (void *, struct dl_find_object *);
- /* Implementation of _dl_readonly_area, used in fortify routines to check
- if memory area is within a read-only ELF segment. */
- enum dl_readonly_area_error_type (*_dl_readonly_area) (const void *, size_t);
-
/* Dynamic linker operations used after static dlopen. */
const struct dlfcn_hook *_dl_dlfcn_hook;
@@ -1308,8 +1304,7 @@ extern struct link_map *_dl_find_dso_for_object (const ElfW(Addr) addr);
rtld_hidden_proto (_dl_find_dso_for_object)
extern enum dl_readonly_area_error_type _dl_readonly_area (const void *ptr,
- size_t size)
- attribute_hidden;
+ size_t size);
/* Initialization which is normally done by the dynamic linker. */
extern void _dl_non_dynamic_init (void)
diff -rup a/debug/Makefile b/debug/Makefile
--- a/debug/Makefile 2025-11-13 15:43:52.493437313 -0500
+++ b/debug/Makefile 2025-11-18 14:18:05.311656186 -0500
@@ -190,6 +190,8 @@ CFLAGS-tst-sprintf-fortify-rdonly-static
CFLAGS-tst-fortify-syslog.c += $(no-fortify-source) -D_FORTIFY_SOURCE=2
CFLAGS-tst-fortify-wide.c += $(no-fortify-source) -D_FORTIFY_SOURCE=2
+test-xfail-tst-sprintf-fortify-rdonly-static = yes
+
$(objpfx)tst-sprintf-fortify-rdonly: $(objpfx)tst-sprintf-fortify-rdonly-mod.so
$(objpfx)tst-sprintf-fortify-rdonly.out: \
$(objpfx)tst-sprintf-fortify-rdonly-dlopen.so
diff -rup a/elf/rtld_static_init.c b/elf/rtld_static_init.c
--- a/elf/rtld_static_init.c 2025-11-18 15:48:15.355621059 -0500
+++ b/elf/rtld_static_init.c 2025-11-18 17:35:21.427465597 -0500
@@ -79,7 +79,6 @@ __rtld_static_init (struct link_map *map
attribute_hidden;
dl->_dl_tls_static_size = _dl_tls_static_size;
dl->_dl_find_object = _dl_find_object;
- dl->_dl_readonly_area = _dl_readonly_area;
__rtld_static_init_arch (map, dl);
}

View File

@ -4,3 +4,8 @@
type_kind = struct
name = _IO_FILE
accessed_through = pointer
[suppress_function]
# From glibc-2.39-98.
name = _dl_readonly_area
parameter = '0 void *
parameter = '1 size_t