From: Florian Weimer <fweimer@redhat.com>
To: libc-alpha@sourceware.org
Subject: [PATCH v2 01/32] support: Add <support/memprobe.h> for protection flags probing
Date: Fri, 07 Jul 2023 20:47:32 +0200 [thread overview]
Message-ID: <1b310e022cd15b29e8f799f5494b471a6ab82f49.1688741159.git.fweimer@redhat.com> (raw)
In-Reply-To: <cover.1688741159.git.fweimer@redhat.com>
---
support/Makefile | 2 +
support/memprobe.h | 43 ++++++
support/support_memprobe.c | 251 +++++++++++++++++++++++++++++++++
support/tst-support_memprobe.c | 118 ++++++++++++++++
4 files changed, 414 insertions(+)
create mode 100644 support/memprobe.h
create mode 100644 support/support_memprobe.c
create mode 100644 support/tst-support_memprobe.c
diff --git a/support/Makefile b/support/Makefile
index 917a858bd1..c2873c4b24 100644
--- a/support/Makefile
+++ b/support/Makefile
@@ -65,6 +65,7 @@ libsupport-routines = \
support_format_hostent \
support_format_netent \
support_isolate_in_subprocess \
+ support_memprobe \
support_mutex_pi_monotonic \
support_need_proc \
support_openpty \
@@ -319,6 +320,7 @@ tests = \
tst-support_capture_subprocess \
tst-support_descriptors \
tst-support_format_dns_packet \
+ tst-support_memprobe \
tst-support_quote_blob \
tst-support_quote_blob_wide \
tst-support_quote_string \
diff --git a/support/memprobe.h b/support/memprobe.h
new file mode 100644
index 0000000000..13295e7b8d
--- /dev/null
+++ b/support/memprobe.h
@@ -0,0 +1,43 @@
+/* Probing memory for protection state.
+ Copyright (C) 2023 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/>. */
+
+#ifndef SUPPORT_MEMPROBE_H
+#define SUPPORT_MEMPROBE_H
+
+/* Probe access status of memory ranges. These functions record a
+ failure (but do not terminate the process) if the memory range does
+ not match the expected protection flags. */
+
+#include <stddef.h>
+
+/* Asserts that SIZE bytes at ADDRESS are inaccessible. CONTEXT
+ is used for reporting errors. */
+void support_memprobe_noaccess (const char *context, const void *address,
+ size_t size);
+
+/* Asserts that SIZE bytes at ADDRESS read read-only. CONTEXT is used
+ for reporting errors. */
+void support_memprobe_readonly (const char *context, const void *address,
+ size_t size);
+
+/* Asserts that SIZE bytes at ADDRESS are readable and writable.
+ CONTEXT is used for reporting errors. */
+void support_memprobe_readwrite (const char *context, const void *address,
+ size_t size);
+
+#endif /* SUPPORT_MEMPROBE_H */
diff --git a/support/support_memprobe.c b/support/support_memprobe.c
new file mode 100644
index 0000000000..b599f9c70e
--- /dev/null
+++ b/support/support_memprobe.c
@@ -0,0 +1,251 @@
+/* Probing memory for protection state.
+ Copyright (C) 2023 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/>. */
+
+/* The implementation uses vfork for probing. As a result, it can be
+ used for testing page protections controlled by memory protection
+ keys, despite their problematic interaction with signal handlers
+ (bug 22396). */
+
+#include <support/memprobe.h>
+
+#include <atomic.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <support/check.h>
+#include <support/support.h>
+#include <support/xunistd.h>
+#include <sys/param.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <sys/resource.h>
+
+#ifdef __linux__
+# include <sys/prctl.h>
+#endif
+
+/* Make are more complete attempt to disable core dumps, even in the
+ presence of core catchers that ignore RLIMIT_CORE. Used after
+ vfork. */
+static void
+disable_coredumps (void)
+{
+#ifdef __linux__
+ prctl (PR_SET_DUMPABLE, 0 /* SUID_DUMP_DISABLE */, 0, 0);
+#endif
+ struct rlimit rl = {};
+ setrlimit (RLIMIT_CORE, &rl);
+}
+
+/* Restores all signals to SIG_DFL and unblocks them. */
+static void
+memprobe_sig_dfl_unblock (void)
+{
+ for (int sig = 1; sig < _NSIG; ++sig)
+ /* Ignore errors for those signals whose handler cannot be changed. */
+ (void) signal (sig, SIG_DFL);
+ sigset_t sigallset;
+ sigfillset (&sigallset);
+ sigprocmask (SIG_UNBLOCK, &sigallset, NULL);
+}
+
+/* Performs a 4-byte probe at the address aligned down. The internal
+ glibc atomics do not necessarily support one-byte access.
+ Accessing more bytes with a no-op write results in the same page
+ fault effects because of the alignment. */
+static inline void
+write_probe_at (volatile char *address)
+{
+ /* Used as an argument to force the compiler to emit an actual no-op
+ atomic instruction. */
+ static volatile uint32_t zero = 0;
+ uint32_t *ptr = (uint32_t *) ((uintptr_t) address & ~(uintptr_t) 3);
+ atomic_fetch_add_relaxed (ptr, zero);
+}
+
+/* Attempt to read or write the entire range in one go. If DO_WRITE,
+ perform a no-op write with an atomic OR with a zero second operand,
+ otherwise just a read. */
+static void
+memprobe_expect_access (const char *context, volatile char *address,
+ size_t size, volatile size_t *pindex, bool do_write)
+{
+ pid_t pid = vfork ();
+ TEST_VERIFY_EXIT (pid >= 0);
+ if (pid == 0)
+ {
+ memprobe_sig_dfl_unblock ();
+ disable_coredumps ();
+ /* *pindex is a volatile access, so the parent process can read
+ the correct index after an unexpected fault. */
+ if (do_write)
+ for (*pindex = 0; *pindex < size; *pindex += 4)
+ write_probe_at (address + *pindex);
+ else
+ for (*pindex = 0; *pindex < size; *pindex += 1)
+ address[*pindex]; /* Triggers volatile read. */
+ _exit (0);
+ }
+ int status;
+ xwaitpid (pid, &status, 0);
+ if (*pindex < size)
+ {
+ support_record_failure ();
+ printf ("error: %s: unexpected %s fault at address %p"
+ " (%zu bytes after %p, wait status %d)\n",
+ context, do_write ? "write" : "read", address + *pindex,
+ *pindex, address, status);
+ }
+ else
+ {
+ TEST_VERIFY (WIFEXITED (status));
+ TEST_COMPARE (WEXITSTATUS (status), 0);
+ }
+}
+
+/* Probe one byte for lack of access. Attempt a write for DO_WRITE,
+ otherwise a read. Returns false on failure. */
+static bool
+memprobe_expect_noaccess_1 (const char *context, volatile char *address,
+ size_t size, size_t index, bool do_write)
+{
+ pid_t pid = vfork ();
+ TEST_VERIFY_EXIT (pid >= 0);
+ if (pid == 0)
+ {
+ memprobe_sig_dfl_unblock ();
+ disable_coredumps ();
+ if (do_write)
+ write_probe_at (address + index);
+ else
+ address[index]; /* Triggers volatile read. */
+ _exit (0); /* Should not be executed due to fault. */
+ }
+
+ int status;
+ xwaitpid (pid, &status, 0);
+ if (WIFSIGNALED (status))
+ {
+ /* Accept SIGSEGV or SIGBUS. */
+ if (WTERMSIG (status) != SIGSEGV)
+ TEST_COMPARE (WTERMSIG (status), SIGBUS);
+ }
+ else
+ {
+ support_record_failure ();
+ printf ("error: %s: unexpected %s success at address %p"
+ " (%zu bytes after %p, wait status %d)\n",
+ context, do_write ? "write" : "read", address + index,
+ index, address, status);
+ return false;
+ }
+ return true;
+}
+
+/* Probe each byte individually because we expect a fault.
+
+ The implementation skips over bytes on the same page, so it assumes
+ that the subpage_prot system call is not used. */
+static void
+memprobe_expect_noaccess (const char *context, volatile char *address,
+ size_t size, bool do_write)
+{
+ if (size == 0)
+ return;
+
+ if (!memprobe_expect_noaccess_1 (context, address, size, 0, do_write))
+ return;
+
+ /* Round up to the next page. */
+ long int page_size = sysconf (_SC_PAGE_SIZE);
+ TEST_VERIFY_EXIT (page_size > 0);
+ size_t index;
+ {
+ uintptr_t next_page = roundup ((uintptr_t) address, page_size);
+ if (next_page < (uintptr_t) address
+ || next_page >= (uintptr_t) address + size)
+ /* Wrap around or after the end of the region. */
+ return;
+ index = next_page - (uintptr_t) address;
+ }
+
+ /* Probe in page increments. */
+ while (true)
+ {
+ if (!memprobe_expect_noaccess_1 (context, address, size, index,
+ do_write))
+ break;
+ size_t next_index = index + page_size;
+ if (next_index < index || next_index >= size)
+ /* Wrap around or after the end of the region. */
+ break;
+ index = next_index;
+ }
+}
+
+static void
+memprobe_range (const char *context, volatile char *address, size_t size,
+ bool expect_read, bool expect_write)
+{
+ /* Do not rely on the sharing nature of vfork because it could be
+ implemented as fork. */
+ size_t *pindex = support_shared_allocate (sizeof *pindex);
+
+ sigset_t oldset;
+ {
+ sigset_t sigallset;
+ sigfillset (&sigallset);
+ sigprocmask (SIG_BLOCK, &sigallset, &oldset);
+ }
+
+ if (expect_read)
+ {
+ memprobe_expect_access (context, address, size, pindex, false);
+ if (expect_write)
+ memprobe_expect_access (context, address, size, pindex, true);
+ else
+ memprobe_expect_noaccess (context, address, size, true);
+ }
+ else
+ {
+ memprobe_expect_noaccess (context, address, size, false);
+ TEST_VERIFY (!expect_write); /* Write-only probing not supported. */
+ }
+
+ sigprocmask (SIG_SETMASK, NULL, &oldset);
+ support_shared_free (pindex);
+}
+
+void support_memprobe_noaccess (const char *context, const void *address,
+ size_t size)
+{
+ memprobe_range (context, (volatile char *) address, size, false, false);
+}
+
+void support_memprobe_readonly (const char *context, const void *address,
+ size_t size)
+{
+ memprobe_range (context, (volatile char *) address, size, true, false);
+}
+
+void support_memprobe_readwrite (const char *context, const void *address,
+ size_t size)
+{
+ memprobe_range (context, (volatile char *) address, size, true, true);
+}
diff --git a/support/tst-support_memprobe.c b/support/tst-support_memprobe.c
new file mode 100644
index 0000000000..51c1b7812f
--- /dev/null
+++ b/support/tst-support_memprobe.c
@@ -0,0 +1,118 @@
+/* Tests for <support/memprobe.h>.
+ Copyright (C) 2023 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <support/memprobe.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <support/check.h>
+#include <support/next_to_fault.h>
+
+/* Used to exit early on error, to avoid masking them. */
+static void
+check_barrier (void)
+{
+ if (support_record_failure_is_failed ())
+ exit (1);
+}
+
+/* Expect a failed state in the test harness. */
+static void
+expect_failure (const char *context)
+{
+ if (!support_record_failure_is_failed ())
+ {
+ printf ("error: expected failure missing: %s\n", context);
+ exit (1);
+ }
+ support_record_failure_reset ();
+}
+
+static int
+do_test (void)
+{
+ static char rw_byte = 1;
+ support_memprobe_readwrite ("rw_byte", &rw_byte, 1);
+ check_barrier ();
+
+ puts ("info: expected error for read-only to rw_byte");
+ support_memprobe_readonly ("rw_byte", &rw_byte, 1);
+
+ puts ("info: expected error for no-access to rw_byte");
+ support_memprobe_noaccess ("rw_byte", &rw_byte, 1);
+ expect_failure ("no-access rw_byte");
+
+ static const char const_byte = 1;
+ support_memprobe_readonly ("const_byte", &const_byte, 1);
+ check_barrier ();
+
+ puts ("info: expected error for no-access to const_byte");
+ support_memprobe_noaccess ("const_byte", &const_byte, 1);
+ expect_failure ("no-access const_byte");
+
+ puts ("info: expected error for read-write access to const_byte");
+ support_memprobe_readwrite ("const_byte", &const_byte, 1);
+ expect_failure ("read-write const_byte");
+
+ struct support_next_to_fault ntf = support_next_to_fault_allocate (3);
+ void *ntf_trailing = ntf.buffer + ntf.length;
+
+ /* The initial 3 bytes are accessible. */
+ support_memprobe_readwrite ("ntf init", ntf.buffer, ntf.length);
+ check_barrier ();
+
+ puts ("info: expected error for read-only to ntf init");
+ support_memprobe_readonly ("ntf init", ntf.buffer, ntf.length);
+ expect_failure ("read-only ntf init");
+
+ puts ("info: expected error for no-access to ntf init");
+ support_memprobe_noaccess ("ntf init", ntf.buffer, ntf.length);
+ expect_failure ("no-access ntf init");
+
+ /* The trailing part after the allocated area is inaccessible. */
+ support_memprobe_noaccess ("ntf trailing", ntf_trailing, 1);
+ check_barrier ();
+
+ puts ("info: expected error for read-only to ntf trailing");
+ support_memprobe_readonly ("ntf trailing", ntf_trailing, 1);
+ expect_failure ("read-only ntf trailing");
+
+ puts ("info: expected error for no-access to ntf trailing");
+ support_memprobe_readwrite ("ntf trailing", ntf_trailing, 1);
+ expect_failure ("read-write ntf trailing");
+
+ /* Both areas combined fail all checks due to inconsistent results. */
+ puts ("info: expected error for no-access to ntf overlap");
+ support_memprobe_noaccess ("ntf overlap ", ntf.buffer, ntf.length + 1);
+ expect_failure ("no-access ntf overlap");
+
+ puts ("info: expected error for read-only to ntf overlap");
+ support_memprobe_readonly ("ntf overlap", ntf.buffer, ntf.length + 1);
+ expect_failure ("read-only ntf overlap");
+
+ puts ("info: expected error for read-write to ntf overlap");
+ support_memprobe_readwrite ("ntf overlap", ntf.buffer, ntf.length + 1);
+ expect_failure ("read-write ntf overlap");
+
+
+ support_next_to_fault_free (&ntf);
+
+ return 0;
+}
+
+#include <support/test-driver.c>
--
2.41.0
next prev parent reply other threads:[~2023-07-07 18:47 UTC|newest]
Thread overview: 33+ messages / expand[flat|nested] mbox.gz Atom feed top
2023-07-07 18:47 [PATCH v2 00/32] RELRO link maps Florian Weimer
2023-07-07 18:47 ` Florian Weimer [this message]
2023-07-07 18:47 ` [PATCH v2 02/32] misc: Enable internal use of memory protection keys Florian Weimer
2023-07-07 18:47 ` [PATCH v2 03/32] elf: Remove _dl_sysdep_open_object hook function Florian Weimer
2023-07-07 18:47 ` [PATCH v2 04/32] elf: Eliminate second loop in find_version in dl-version.c Florian Weimer
2023-07-07 18:47 ` [PATCH v2 05/32] elf: In rtld_setup_main_map, assume ld.so has a DYNAMIC segment Florian Weimer
2023-07-07 18:47 ` [PATCH v2 06/32] elf: Remove version assert in check_match in elf/dl-lookup.c Florian Weimer
2023-07-07 18:48 ` [PATCH v2 07/32] elf: Disambiguate some failures in _dl_load_cache_lookup Florian Weimer
2023-07-07 18:48 ` [PATCH v2 08/32] elf: Eliminate alloca in open_verify Florian Weimer
2023-07-07 18:48 ` [PATCH v2 09/32] Do not export <alloc_buffer.h> functions from libc Florian Weimer
2023-07-07 18:48 ` [PATCH v2 10/32] elf: Make <alloc_buffer.h> usable in ld.so Florian Weimer
2023-07-07 18:48 ` [PATCH v2 11/32] elf: Merge the three implementations of _dl_dst_substitute Florian Weimer
2023-07-07 18:48 ` [PATCH v2 12/32] elf: Move __rtld_malloc_init_stubs call into _dl_start_final Florian Weimer
2023-07-07 18:48 ` [PATCH v2 13/32] elf: Merge __dl_libc_freemem into __rtld_libc_freeres Florian Weimer
2023-07-07 18:48 ` [PATCH v2 14/32] elf: Use struct link_map_private for the internal link map Florian Weimer
2023-07-07 18:48 ` [PATCH v2 15/32] elf: Remove run-time-writable fields from struct link_map_private Florian Weimer
2023-07-07 18:48 ` [PATCH v2 16/32] elf: Move l_tls_offset into read-write part of link map Florian Weimer
2023-07-07 18:48 ` [PATCH v2 17/32] elf: Allocate auditor state after read-write " Florian Weimer
2023-07-07 18:48 ` [PATCH v2 18/32] elf: Move link map fields used by dependency sorting to writable part Florian Weimer
2023-07-07 18:48 ` [PATCH v2 19/32] elf: Split _dl_lookup_map, _dl_map_new_object from _dl_map_object Florian Weimer
2023-07-07 18:48 ` [PATCH v2 20/32] elf: Add l_soname accessor function for DT_SONAME values Florian Weimer
2023-07-07 18:49 ` [PATCH v2 21/32] elf: _dl_rtld_map should not exist in static builds Florian Weimer
2023-07-07 18:49 ` [PATCH v2 22/32] elf: Introduce GLPM accessor for the protected memory area Florian Weimer
2023-07-07 18:49 ` [PATCH v2 23/32] elf: Bootstrap allocation for future protected memory allocator Florian Weimer
2023-07-07 18:49 ` [PATCH v2 24/32] elf: Implement a basic " Florian Weimer
2023-07-07 18:49 ` [PATCH v2 25/32] elf: Move most of the _dl_find_object data to the protected heap Florian Weimer
2023-07-07 18:49 ` [PATCH v2 26/32] elf: Switch to a region-based protected memory allocator Florian Weimer
2023-07-07 18:49 ` [PATCH v2 27/32] elf: Determine the caller link map in _dl_open Florian Weimer
2023-07-07 18:49 ` [PATCH v2 28/32] elf: Add fast path to dlopen for fully-opened maps Florian Weimer
2023-07-07 18:49 ` [PATCH v2 29/32] elf: Use _dl_find_object instead of _dl_find_dso_for_object in dlopen Florian Weimer
2023-07-07 18:50 ` [PATCH v2 30/32] elf: Put critical _dl_find_object pointers into protected memory area Florian Weimer
2023-07-07 19:08 ` [PATCH v2 31/32] elf: Add hash tables to speed up DT_NEEDED, dlopen lookups Florian Weimer
2023-07-07 19:08 ` [PATCH v2 32/32] elf: Use memory protection keys for the protected memory allocator Florian Weimer
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=1b310e022cd15b29e8f799f5494b471a6ab82f49.1688741159.git.fweimer@redhat.com \
--to=fweimer@redhat.com \
--cc=libc-alpha@sourceware.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).