@@ -7465,6 +7465,39 @@ have-z-memory-seal = $libc_cv_z_memory_seal"
config_vars="$config_vars
enable-memory-seal = $enable_memory_sealing"
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking PT_GNU_MUTABLE support" >&5
+printf %s "checking PT_GNU_MUTABLE support... " >&6; }
+if test ${libc_cv_gnu_mutable+y}
+then :
+ printf %s "(cached) " >&6
+else case e in #(
+ e) cat > conftest.c <<EOF
+int bar __attribute__ ((section (".gnu.mutable")));
+int main (void) { return bar; }
+EOF
+libc_cv_gnu_mutable=no
+if { ac_try='${CC-cc} $CFLAGS $CPPFLAGS $LDFLAGS
+ $no_ssp -o conftest conftest.c
+ -nostdlib -nostartfiles
+ 1>&5'
+ { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_try\""; } >&5
+ (eval $ac_try) 2>&5
+ ac_status=$?
+ printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }; }
+then
+ if $READELF -lW conftest | grep 'GNU_MUTABLE' > /dev/null; then
+ libc_cv_gnu_mutable=yes
+ fi
+fi
+rm -r conftest* ;;
+esac
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $libc_cv_gnu_mutable" >&5
+printf "%s\n" "$libc_cv_gnu_mutable" >&6; }
+config_vars="$config_vars
+have-pt-gnu-mutable = $libc_cv_gnu_mutable"
+
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for GLOB_DAT reloc" >&5
printf %s "checking for GLOB_DAT reloc... " >&6; }
@@ -1378,6 +1378,25 @@ fi
LIBC_CONFIG_VAR([have-z-memory-seal], [$libc_cv_z_memory_seal])
LIBC_CONFIG_VAR([enable-memory-seal], [$enable_memory_sealing])
+AC_CACHE_CHECK([PT_GNU_MUTABLE support],
+ libc_cv_gnu_mutable, [dnl
+cat > conftest.c <<EOF
+int bar __attribute__ ((section (".gnu.mutable")));
+int main (void) { return bar; }
+EOF
+libc_cv_gnu_mutable=no
+if AC_TRY_COMMAND([${CC-cc} $CFLAGS $CPPFLAGS $LDFLAGS
+ $no_ssp -o conftest conftest.c
+ -nostdlib -nostartfiles
+ 1>&AS_MESSAGE_LOG_FD])
+then
+ if $READELF -lW conftest | grep 'GNU_MUTABLE' > /dev/null; then
+ libc_cv_gnu_mutable=yes
+ fi
+fi
+rm -r conftest*])
+LIBC_CONFIG_VAR([have-pt-gnu-mutable], [$libc_cv_gnu_mutable])
+
AC_CACHE_CHECK(for GLOB_DAT reloc,
libc_cv_has_glob_dat, [dnl
@@ -1220,6 +1220,11 @@ _dl_map_object_from_fd (const char *name, const char *origname, int fd,
l->l_relro_addr = ph->p_vaddr;
l->l_relro_size = ph->p_memsz;
break;
+
+ case PT_GNU_MUTABLE:
+ l->l_mutable_addr = ph->p_vaddr;
+ l->l_mutable_size = ph->p_memsz;
+ break;
}
if (__glibc_unlikely (nloadcmds == 0))
@@ -37,7 +37,6 @@
# define bump_num_cache_relocations() ((void) 0)
#endif
-
/* We are trying to perform a static TLS relocation in MAP, but it was
dynamically loaded. This can only work if there is enough surplus in
the static TLS area already allocated for each running thread. If this
@@ -371,6 +370,29 @@ cannot apply additional memory protection after relocation");
}
}
+static void
+_dl_mseal_map_2 (const struct link_map *l, ElfW(Addr) map_start,
+ ElfW(Addr) map_end)
+{
+ ElfW(Addr) mutable_start = 0, mutable_end = 0;
+ if (l->l_mutable_size != 0)
+ {
+ mutable_start = l->l_addr + l->l_mutable_addr;
+ mutable_end = mutable_start + l->l_mutable_size;
+ }
+
+ if (mutable_start >= map_start && mutable_end < map_end)
+ {
+ size_t seg1_size = mutable_start - map_start;
+ size_t seg2_size = map_end - mutable_end;
+ _dl_mseal ((void *) map_start, seg1_size, l->l_name);
+ if (seg2_size != 0)
+ _dl_mseal ((void *) mutable_end, seg2_size, l->l_name);
+ }
+ else
+ _dl_mseal ((void *) map_start, map_end - map_start, l->l_name);
+}
+
static void
_dl_mseal_map_1 (struct link_map *l, bool dep)
{
@@ -388,8 +410,7 @@ _dl_mseal_map_1 (struct link_map *l, bool dep)
return;
if (l->l_contiguous)
- _dl_mseal ((void *) l->l_map_start, l->l_map_end - l->l_map_start,
- l->l_name);
+ _dl_mseal_map_2 (l, l->l_map_start, l->l_map_end);
else
{
/* We can use the PT_LOAD segments because even if relro splits the
@@ -404,7 +425,7 @@ _dl_mseal_map_1 (struct link_map *l, bool dep)
ElfW(Addr) mapstart = l->l_addr
+ (ph->p_vaddr & ~(GLRO(dl_pagesize) - 1));
ElfW(Addr) allocend = l->l_addr + ph->p_vaddr + ph->p_memsz;
- _dl_mseal ((void *) mapstart, allocend - mapstart, l->l_name);
+ _dl_mseal_map_2 (l, mapstart, allocend);
}
break;
}
@@ -334,6 +334,11 @@ _dl_non_dynamic_init (void)
_dl_main_map.l_relro_addr = ph->p_vaddr;
_dl_main_map.l_relro_size = ph->p_memsz;
break;
+
+ case PT_GNU_MUTABLE:
+ _dl_main_map.l_mutable_addr = ph->p_vaddr;
+ _dl_main_map.l_mutable_size = ph->p_memsz;
+ break;
}
/* Process program headers again, but scan them backwards so
that PT_NOTE can be skipped if PT_GNU_PROPERTY exits. */
@@ -729,6 +729,7 @@ typedef struct
#define PT_GNU_RELRO 0x6474e552 /* Read-only after relocation */
#define PT_GNU_PROPERTY 0x6474e553 /* GNU property */
#define PT_GNU_SFRAME 0x6474e554 /* SFrame segment. */
+#define PT_GNU_MUTABLE 0x6474f555 /* Like bss, but not immutable. */
#define PT_LOSUNW 0x6ffffffa
#define PT_SUNWBSS 0x6ffffffa /* Sun Specific segment */
#define PT_SUNWSTACK 0x6ffffffb /* Stack segment */
@@ -1352,6 +1353,7 @@ typedef struct
/* Note section name of program property. */
#define NOTE_GNU_PROPERTY_SECTION_NAME ".note.gnu.property"
+#define GNU_MUTABLE_SECTION_NAME ".gnu.mutable"
/* Values used in GNU .note.gnu.property notes (NT_GNU_PROPERTY_TYPE_0). */
@@ -1209,6 +1209,11 @@ rtld_setup_main_map (struct link_map *main_map)
main_map->l_relro_addr = ph->p_vaddr;
main_map->l_relro_size = ph->p_memsz;
break;
+
+ case PT_GNU_MUTABLE:
+ main_map->l_mutable_addr = ph->p_vaddr;
+ main_map->l_mutable_size = ph->p_memsz;
+ break;
}
/* Process program headers again, but scan them backwards so
that PT_NOTE can be skipped if PT_GNU_PROPERTY exits. */
@@ -353,6 +353,10 @@ struct link_map
ElfW(Addr) l_relro_addr;
size_t l_relro_size;
+ /* Information used to not memory seal after relocations are done. */
+ ElfW(Addr) l_mutable_addr;
+ size_t l_mutable_size;
+
unsigned long long int l_serial;
};
@@ -708,6 +708,8 @@ modules-names += \
tst-dl_mseal-dlopen-2-1 \
tst-dl_mseal-mod-1 \
tst-dl_mseal-mod-2 \
+ tst-dl_mseal-mutable-dlopen \
+ tst-dl_mseal-mutable-mod \
tst-dl_mseal-preload \
# modules-names
@@ -731,6 +733,10 @@ $(objpfx)tst-dl_mseal-noseal.out: \
$(objpfx)tst-dl_mseal-dlopen-2.so \
$(objpfx)tst-dl_mseal-dlopen-2-1.so
+$(objpfx)tst-dl_mseal-mutable.out: \
+ $(objpfx)tst-dl_mseal-mutable-mod.so \
+ $(objpfx)tst-dl_mseal-mutable-dlopen.so
+
ifeq ($(enable-memory-seal),yes)
CFLAGS-tst-dl_mseal.c += -DDEFAULT_MEMORY_SEAL
CFLAGS-tst-dl_mseal-noseal.c += -DDEFAULT_MEMORY_SEAL
@@ -738,6 +744,8 @@ endif
LDFLAGS-tst-dl_mseal = -Wl,--no-as-needed -Wl,-z,memory-seal
LDFLAGS-tst-dl_mseal-static = -Wl,--no-as-needed -Wl,-z,memory-seal
+LDFLAGS-tst-dl_mseal-mutable = -Wl,--no-as-needed -Wl,-z,memory-seal
+LDFLAGS-tst-dl_mseal-mutable-static = -Wl,-z,memory-seal
LDFLAGS-tst-dl_mseal-mod-1.so = -Wl,--no-as-needed -Wl,-z,memory-seal
LDFLAGS-tst-dl_mseal-mod-2.so = -Wl,-z,memory-seal
LDFLAGS-tst-dl_mseal-dlopen-1.so = -Wl,--no-as-needed
@@ -763,6 +771,21 @@ tst-dl_mseal-ARGS = -- $(host-test-program-cmd)
tst-dl_mseal-static-ARGS = -- $(host-test-program-cmd)
tst-dl_mseal-noseal-ARGS = -- $(host-test-program-cmd)
tst-dl_mseal-static-noseal-ARGS = -- $(host-test-program-cmd)
+
+ifeq ($(have-pt-gnu-mutable),yes)
+tests-static += \
+ tst-dl_mseal-mutable-static \
+ # tests-static
+
+tests += \
+ tst-dl_mseal-mutable \
+ # tests
+
+LDFLAGS-tst-dl_mseal-mutable-mod.so = -Wl,-z,memory-seal
+LDFLAGS-tst-dl_mseal-mutable-dlopen.so = -Wl,-z,memory-seal
+
+$(objpfx)tst-dl_mseal-mutable: $(objpfx)tst-dl_mseal-mutable-mod.so
+endif # $(have-pt-gnu-mutable) == yes
endif
endif # $(subdir) == elf
new file mode 100644
@@ -0,0 +1 @@
+#include "tst-dl_mseal-mutable-mod.c"
new file mode 100644
@@ -0,0 +1,47 @@
+/* Check if PT_OPENBSD_MUTABLE is correctly applied.
+ 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 <elf.h>
+#include "tst-dl_mseal-mutable-mod.h"
+
+static unsigned char mutable_array1[128]
+ __attribute__ ((section (GNU_MUTABLE_SECTION_NAME)))
+ = { 0 };
+static unsigned char mutable_array2[256]
+ __attribute__ ((section (GNU_MUTABLE_SECTION_NAME)))
+ = { 0 };
+
+static unsigned char immutable_array[256];
+
+struct array_t
+get_mutable_array1 (void)
+{
+ return (struct array_t) { mutable_array1, sizeof (mutable_array1) };
+}
+
+struct array_t
+get_mutable_array2 (void)
+{
+ return (struct array_t) { mutable_array2, sizeof (mutable_array2) };
+}
+
+struct array_t
+get_immutable_array (void)
+{
+ return (struct array_t) { immutable_array, sizeof (immutable_array) };
+}
new file mode 100644
@@ -0,0 +1,33 @@
+/* Check if PT_OPENBSD_MUTABLE is correctly applied.
+ 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 <stddef.h>
+
+#define LIB_DLOPEN "tst-dl_mseal-mutable-dlopen.so"
+
+struct array_t
+{
+ unsigned char *arr;
+ size_t size;
+};
+
+typedef struct array_t (*get_array_t)(void);
+
+struct array_t get_mutable_array1 (void);
+struct array_t get_mutable_array2 (void);
+struct array_t get_immutable_array (void);
new file mode 100644
@@ -0,0 +1,2 @@
+#define TEST_STATIC 1
+#include "tst-dl_mseal-mutable.c"
new file mode 100644
@@ -0,0 +1,242 @@
+/* Check if PT_OPENBSD_MUTABLE is correctly applied.
+ 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 <dlfcn.h>
+#include <errno.h>
+#include <link.h>
+#include <setjmp.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/mman.h>
+
+#include <libc-pointer-arith.h>
+#include <support/check.h>
+#include <support/test-driver.h>
+#include <support/xdlfcn.h>
+#include <support/xsignal.h>
+#include <support/xunistd.h>
+
+#include "tst-dl_mseal-mutable-mod.h"
+
+static long int pagesize;
+
+/* To check if the protection flags are correctly set, the thread tries
+ read/writes on it and checks if a SIGSEGV is generated. */
+
+static volatile sig_atomic_t signal_jump_set;
+static sigjmp_buf signal_jmp_buf;
+
+static void
+sigsegv_handler (int sig)
+{
+ if (signal_jump_set == 0)
+ return;
+
+ siglongjmp (signal_jmp_buf, sig);
+}
+
+static bool
+try_access_buf (unsigned char *ptr, bool write)
+{
+ signal_jump_set = true;
+
+ bool failed = sigsetjmp (signal_jmp_buf, 0) != 0;
+ if (!failed)
+ {
+ if (write)
+ *(volatile unsigned char *)(ptr) = 'x';
+ else
+ *(volatile unsigned char *)(ptr);
+ }
+
+ signal_jump_set = false;
+ return !failed;
+}
+
+struct range_t
+{
+ const char *name;
+ unsigned char *start;
+ size_t size;
+ bool found;
+};
+
+static int
+callback (struct dl_phdr_info *info, size_t size, void *data)
+{
+ struct range_t *range = data;
+ if (strcmp (info->dlpi_name, range->name) != 0)
+ return 0;
+
+ for (size_t i = 0; i < info->dlpi_phnum; i++)
+ if (info->dlpi_phdr[i].p_type == PT_GNU_MUTABLE)
+ {
+ range->start = (unsigned char *) info->dlpi_phdr[i].p_vaddr;
+ range->size = info->dlpi_phdr[i].p_memsz;
+ range->found = true;
+ break;
+ }
+
+ return 0;
+}
+
+static bool
+find_mutable_range (void *addr, struct range_t *range)
+{
+ struct dl_find_object dlfo;
+ if (_dl_find_object (addr, &dlfo) != 0)
+ return false;
+
+ range->name = dlfo.dlfo_link_map->l_name;
+ range->found = false;
+ dl_iterate_phdr (callback, range);
+ if (range->found)
+ range->start = dlfo.dlfo_link_map->l_addr + range->start;
+
+ return range->found;
+}
+
+static bool
+__attribute_used__
+try_read_buf (unsigned char *ptr)
+{
+ return try_access_buf (ptr, false);
+}
+
+static bool
+__attribute_used__
+try_write_buf (unsigned char *ptr)
+{
+ return try_access_buf (ptr, true);
+}
+
+/* The GNU_MUTABLE_SECTION_NAME section is page-aligned and with a size
+ multiple of page size. */
+
+unsigned char mutable_array1[64]
+ __attribute__ ((section (GNU_MUTABLE_SECTION_NAME)))
+ = { 0 };
+unsigned char mutable_array2[32]
+ __attribute__ ((section (GNU_MUTABLE_SECTION_NAME)))
+ = { 0 };
+
+unsigned char immutable_array[128];
+
+static void
+check_array (struct array_t *arr)
+{
+ TEST_COMPARE (try_write_buf (arr->arr), false);
+ TEST_COMPARE (try_write_buf (&arr->arr[arr->size/2]), false);
+ TEST_COMPARE (try_write_buf (&arr->arr[arr->size-1]), false);
+}
+
+static void
+check_mutable (struct array_t *mut1,
+ struct array_t *mut2,
+ struct array_t *imut)
+{
+ struct range_t range1;
+ struct range_t range2;
+
+ TEST_VERIFY_EXIT (find_mutable_range (mut1->arr, &range1));
+ TEST_VERIFY (mut1->arr >= range1.start);
+ TEST_VERIFY (mut1->arr + mut1->size <= range1.start + range1.size);
+
+ TEST_VERIFY_EXIT (find_mutable_range (mut2->arr, &range2));
+ TEST_VERIFY (mut2->arr >= range2.start);
+ TEST_VERIFY (mut2->arr + mut2->size <= range2.start + range2.size);
+
+ /* Assume that both array will be placed in the same page since their
+ combined size is less than pagesize. */
+ TEST_VERIFY (range1.start == range2.start);
+ TEST_VERIFY (range2.size == range2.size);
+
+ if (test_verbose > 0)
+ printf ("mutable region: %-30s - %p-%p\n",
+ range1.name[0] == '\0' ? "main program" : basename (range1.name),
+ range1.start,
+ range1.start + range1.size);
+
+ memset (mut1->arr, 0xaa, mut1->size);
+ memset (mut2->arr, 0xbb, mut2->size);
+ memset (imut->arr, 0xcc, imut->size);
+
+ /* Sanity check, imut should be immutable. */
+ {
+ void *start = PTR_ALIGN_DOWN (imut->arr, pagesize);
+ TEST_COMPARE (mprotect (start, pagesize, PROT_READ), -1);
+ TEST_COMPARE (errno, EPERM);
+ }
+
+ /* Change permission of mutable region to just allow read. */
+ xmprotect ((void *)range1.start, range1.size, PROT_READ);
+
+ check_array (mut1);
+ check_array (mut2);
+}
+
+static int
+do_test (void)
+{
+ pagesize = xsysconf (_SC_PAGESIZE);
+
+ {
+ struct sigaction sa = {
+ .sa_handler = sigsegv_handler,
+ .sa_flags = SA_NODEFER,
+ };
+ sigemptyset (&sa.sa_mask);
+ xsigaction (SIGSEGV, &sa, NULL);
+ }
+
+#define ARR_TO_RANGE(__arr) \
+ &((struct array_t) { __arr, sizeof (__arr) })
+
+ check_mutable (ARR_TO_RANGE (mutable_array1),
+ ARR_TO_RANGE (mutable_array2),
+ ARR_TO_RANGE (immutable_array));
+
+#ifndef TEST_STATIC
+ {
+ struct array_t mut1 = get_mutable_array1 ();
+ struct array_t mut2 = get_mutable_array2 ();
+ struct array_t imut = get_immutable_array ();
+ check_mutable (&mut1, &mut2, &imut);
+ }
+
+ {
+ void *h = xdlopen (LIB_DLOPEN, RTLD_NOW | RTLD_NODELETE);
+
+#define GET_ARRAY_DLOPEN(__name) \
+ ({ \
+ get_array_t f = xdlsym (h, __name); \
+ f(); \
+ })
+
+ struct array_t mut1 = GET_ARRAY_DLOPEN ("get_mutable_array1");
+ struct array_t mut2 = GET_ARRAY_DLOPEN ("get_mutable_array2");
+ struct array_t imut = GET_ARRAY_DLOPEN ("get_immutable_array");
+ check_mutable (&mut1, &mut2, &imut);
+ }
+#endif
+
+ return 0;
+}
+
+#include <support/test-driver.c>