From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by sourceware.org (Postfix) with ESMTPS id 013973857714 for ; Tue, 4 Jul 2023 20:02:22 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 013973857714 Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1688500942; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: in-reply-to:in-reply-to:references:references; bh=y9wuXk0Evgy/gByx6cOQ/dh1DFPVBOPYNebUdiUUQkc=; b=Qv/d36RBZfj/zl3K2Jfg8k/AeOUFgWuOvxyXClsKxvUlA16wVnqrqRKHMe95Rri4U3U6y8 0yhM5d6jzwSm47iGMkmYSL6L34Djg8PitoRmX5qEJVjYe0+LvunYZ4vkVZRk81RJcaewBG nGomVy4iyi6fwNJHZcOTc8DiIjnu6AA= Received: from mimecast-mx02.redhat.com (mimecast-mx02.redhat.com [66.187.233.88]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id us-mta-56-k4PDd5tCOfqcsQxiCSYWGw-1; Tue, 04 Jul 2023 16:02:21 -0400 X-MC-Unique: k4PDd5tCOfqcsQxiCSYWGw-1 Received: from smtp.corp.redhat.com (int-mx05.intmail.prod.int.rdu2.redhat.com [10.11.54.5]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx02.redhat.com (Postfix) with ESMTPS id CF10B8037BA for ; Tue, 4 Jul 2023 20:02:20 +0000 (UTC) Received: from oldenburg.str.redhat.com (unknown [10.2.16.19]) by smtp.corp.redhat.com (Postfix) with ESMTPS id 96034F6424 for ; Tue, 4 Jul 2023 20:02:19 +0000 (UTC) From: Florian Weimer To: libc-alpha@sourceware.org Subject: [PATCH 01/33] support: Add for protection flags probing In-Reply-To: Message-ID: References: X-From-Line: f5bbf9786cd8f910310f555424426f9ec69967ae Mon Sep 17 00:00:00 2001 Date: Tue, 04 Jul 2023 22:02:18 +0200 User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/28.2 (gnu/linux) MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.1 on 10.11.54.5 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Content-Type: text/plain X-Spam-Status: No, score=-10.5 required=5.0 tests=BAYES_00,DKIMWL_WL_HIGH,DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,GIT_PATCH_0,KAM_SHORT,RCVD_IN_DNSWL_NONE,RCVD_IN_MSPIKE_H4,RCVD_IN_MSPIKE_WL,SPF_HELO_NONE,SPF_NONE,TXREP,T_SCC_BODY_TEXT_LINE autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org List-Id: --- 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 + . */ + +#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 + +/* 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 + . */ + +/* 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __linux__ +# include +#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 . + 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 + . */ + +#include + +#include +#include +#include +#include + +/* 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 -- 2.41.0