diff --git a/glibc-RHEL-119431-1.patch b/glibc-RHEL-119431-1.patch new file mode 100644 index 0000000..5747c2d --- /dev/null +++ b/glibc-RHEL-119431-1.patch @@ -0,0 +1,130 @@ +commit 127cefd84dc551a43d0ac58b4619503d04869042 +Author: Adhemerval Zanella +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 + + [1] https://git.launchpad.net/~ubuntu-core-dev/ubuntu/+source/glibc/tree/debian/patches/ubuntu/fix-fortify-source.patch + Reviewed-by: DJ Delorie + +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: + # + 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) diff --git a/glibc-RHEL-119431-2.patch b/glibc-RHEL-119431-2.patch new file mode 100644 index 0000000..599adc9 --- /dev/null +++ b/glibc-RHEL-119431-2.patch @@ -0,0 +1,696 @@ +commit ed6a68bac7cd056abda9008019c71b167f0362dc +Author: Adhemerval Zanella +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 + +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 @@ + . */ + + #include ++#include + +-/* 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 ++ . */ ++ ++#include ++#include ++#include ++ ++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 + #include + #include ++#include + +-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 +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 ++ . */ ++ ++#include ++ ++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 + #include + +-/* 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 + #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; + } diff --git a/glibc-RHEL-119431-3.patch b/glibc-RHEL-119431-3.patch new file mode 100644 index 0000000..795c67e --- /dev/null +++ b/glibc-RHEL-119431-3.patch @@ -0,0 +1,28 @@ +commit 3a9c1497e320d77b2453ee88d7d6b40e162e85d4 +Author: Florian Weimer +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 + +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 diff --git a/glibc-RHEL-119431-4.patch b/glibc-RHEL-119431-4.patch new file mode 100644 index 0000000..1e78d36 --- /dev/null +++ b/glibc-RHEL-119431-4.patch @@ -0,0 +1,93 @@ +commit 01196393c257c59f63e0e14fa1bfe8d2a699bf2d +Author: Florian Weimer +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 + +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); + } diff --git a/glibc-RHEL-119431-5.patch b/glibc-RHEL-119431-5.patch new file mode 100644 index 0000000..96e22fe --- /dev/null +++ b/glibc-RHEL-119431-5.patch @@ -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); + } diff --git a/glibc.abignore b/glibc.abignore index a7230ab..a7a9706 100644 --- a/glibc.abignore +++ b/glibc.abignore @@ -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