Serenity Operating System
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Kernel: Demonstrate race condition in clock_nanosleep

This adds a test for the race condition in clock_nanosleep.
The crux is that clock_nanosleep verifies that the output buffer
is writable *before* sleeping, and writes to it *after* sleeping.
In the meantime, a concurrent thread can make the output buffer
unwritable, e.g. by deallocating it.

This testcase is needlessly complex because pthread_kill is
not implemented yet. I tried to keep it as simple as possible.

Here is the relevant part of dmesg:
[nanosleep-race-outbuf-munmap(22:22)]: Unblock nanosleep-race-outbuf-munmap(20:20) due to signal
nanosleep-race-outbuf-munmap(20:20) Unrecoverable page fault, write to address 0x02130016
CRASH: Page Fault. Process: nanosleep-race-outbuf-munmap(20)
[nanosleep-race-outbuf-munmap(20:20)]: 0xc01160ff memcpy +44
[nanosleep-race-outbuf-munmap(20:20)]: 0xc014de64 Kernel::Process::crash(int, unsigned int) +782
[nanosleep-race-outbuf-munmap(20:20)]: 0xc01191b5 illegal_instruction_handler +0
[nanosleep-race-outbuf-munmap(20:20)]: 0xc011965b page_fault_handler +649
[nanosleep-race-outbuf-munmap(20:20)]: 0xc0117233 page_fault_asm_entry +22
[nanosleep-race-outbuf-munmap(20:20)]: 0xc011616b copy_to_user +102
[nanosleep-race-outbuf-munmap(20:20)]: 0xc015911f Kernel::Process::sys(Kernel::Syscall::SC_clock_nanosleep_params const*) +457
[nanosleep-race-outbuf-munmap(20:20)]: 0xc015daad syscall_handler +1130
[nanosleep-race-outbuf-munmap(20:20)]: 0xc015d597 syscall_asm_entry +29
[nanosleep-race-outbuf-munmap(20:20)]: 0x08048437 main +146
[nanosleep-race-outbuf-munmap(20:20)]: 0x08048573 _start +94

Most importantly, note that it crashes *inside*
Kernel::Process::sys.
Instead, the correct behavior is to return -EFAULT.

authored by

Ben Wiederhake and committed by
Andreas Kling
28e1da34 a26b63a9

+155
+155
Tests/Kernel/nanosleep-race-outbuf-munmap.cpp
··· 1 + /* 2 + * Copyright (c) 2020, Ben Wiederhake <BenWiederhake.GitHub@gmx.de> 3 + * All rights reserved. 4 + * 5 + * Redistribution and use in source and binary forms, with or without 6 + * modification, are permitted provided that the following conditions are met: 7 + * 8 + * 1. Redistributions of source code must retain the above copyright notice, this 9 + * list of conditions and the following disclaimer. 10 + * 11 + * 2. Redistributions in binary form must reproduce the above copyright notice, 12 + * this list of conditions and the following disclaimer in the documentation 13 + * and/or other materials provided with the distribution. 14 + * 15 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 + */ 26 + 27 + #include <pthread.h> 28 + #include <assert.h> 29 + #include <signal.h> 30 + #include <stdio.h> 31 + #include <time.h> 32 + #include <unistd.h> 33 + 34 + static void signal_printer(int) 35 + { 36 + // no-op 37 + } 38 + 39 + typedef struct yank_shared_t { 40 + timespec* remaining_sleep; 41 + // TODO: Be nice and use thread ID 42 + //pthread_t sleeper_thread; 43 + } yank_shared_t; 44 + 45 + static void* yanker_fn(void* shared_) 46 + { 47 + yank_shared_t* shared = static_cast<yank_shared_t*>(shared_); 48 + 49 + timespec requested_sleep = { 1, 0 }; 50 + int rc = clock_nanosleep(CLOCK_MONOTONIC, 0, &requested_sleep, nullptr); 51 + if (rc != 0) { 52 + printf("Yanker: Failed during sleep: %d\n", rc); 53 + return nullptr; 54 + } 55 + 56 + delete shared->remaining_sleep; // T2 57 + 58 + // Send SIGUSR1. 59 + 60 + // Use pthread: 61 + // pthread_kill(somewhere, SIGUSR1); 62 + // But wait! pthread_kill isn't implemented yet, and therefore causes 63 + // a linker error. It also looks like the corresponding syscall is missing. 64 + 65 + // Use normal IPC syscall: 66 + // kill(getpid(), SIGUSR1); 67 + // But wait! If destination_pid == own_pid, then the signal is sent 68 + // to the calling thread, *no matter what*. 69 + 70 + // So, we have to go the very ugly route of fork(): 71 + // (Thank goodness this is only a demo of a kernel bug!) 72 + pid_t pid_to_kill = getpid(); 73 + 74 + pid_t child_pid = fork(); 75 + if (child_pid < 0) { 76 + printf("Yanker: Fork failed: %d\n", child_pid); 77 + pthread_exit(nullptr); // See below 78 + return nullptr; 79 + } 80 + 81 + if (child_pid > 0) { 82 + // Success. Terminate quickly. T3 83 + // FIXME: LibPthread bug: returning during normal operation causes nullptr deref. 84 + // Workaround: Exit manually. 85 + pthread_exit(nullptr); 86 + return nullptr; 87 + } 88 + 89 + // Give parent *thread* a moment to die. 90 + requested_sleep = { 1, 0 }; 91 + rc = clock_nanosleep(CLOCK_MONOTONIC, 0, &requested_sleep, nullptr); 92 + if (rc != 0) { 93 + printf("Yanker-child: Failed during sleep: %d\n", rc); 94 + return nullptr; 95 + } 96 + 97 + // Prod the parent *process* 98 + kill(pid_to_kill, SIGUSR1); // T4 99 + 100 + // Wait a moment, to ensure the log output is as well-separated as possible. 101 + requested_sleep = { 2, 0 }; 102 + rc = clock_nanosleep(CLOCK_MONOTONIC, 0, &requested_sleep, nullptr); 103 + if (rc != 0) { 104 + printf("Yanker-child: Failed during after-sleep: %d\n", rc); 105 + return nullptr; 106 + } 107 + 108 + pthread_exit(nullptr); 109 + assert(false); 110 + // FIXME: return nullptr; 111 + } 112 + 113 + int main() 114 + { 115 + // Chronological order: 116 + // T0: Main thread allocates region for the outvalue of clock_nanosleep 117 + // T1: Main thread enters clock_nanosleep 118 + // T2: Side thread deallocates that region 119 + // T3: Side thread dies 120 + // T4: A different process sends SIGUSR1, waking up the main thread, 121 + // forcing the kernel to write to the deallocated Region. 122 + 123 + // I'm sorry that both a side *thread* and a side *process* are necessary. 124 + // Maybe in the future this test can be simplified, see above. 125 + 126 + yank_shared_t shared = { nullptr }; 127 + shared.remaining_sleep = new timespec({ 0xbad, 0xf00d }); // T0 128 + 129 + pthread_t yanker_thread; 130 + int rc = pthread_create(&yanker_thread, nullptr, yanker_fn, &shared); 131 + if (rc != 0) { 132 + perror("pthread"); 133 + printf("FAIL\n"); 134 + return 1; 135 + } 136 + 137 + // Set an action for SIGUSR1, so that the sleep can be interrupted: 138 + signal(SIGUSR1, signal_printer); 139 + 140 + // T1: Go to sleep. 141 + const timespec requested_sleep = { 3, 0 }; 142 + rc = clock_nanosleep(CLOCK_MONOTONIC, 0, &requested_sleep, shared.remaining_sleep); 143 + // Now we are beyond T4. 144 + 145 + if (rc == 0) { 146 + // We somehow weren't interrupted. Bad. 147 + printf("Not interrupted.\n"); 148 + printf("FAIL\n"); 149 + return 1; 150 + } 151 + 152 + // nanosleep was interrupted and the kernel didn't crash. Good! 153 + printf("PASS\n"); 154 + return 0; 155 + }