Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 | #include <darwintest.h> #include <sys/types.h> #include <pthread.h> #include <mach/mach_types.h> #include <unistd.h> #include <stdlib.h> #include <dispatch/dispatch.h> #include <platform/string.h> typedef enum { PTHREAD, WORKQUEUE } thread_type_t; typedef enum { NO_CORRUPTION = 0x0, SIG_CORRUPTION = 0x1, // the pthread_t::sig FULL_CORRUPTION = 0x2, // the pthread_t::sig and also TSDs STACK_CORRUPTION = 0x4, // the stack protector cookie } corrupt_type_t; __attribute__((noinline)) static void induce_stack_check_failure(void) { char buf[20]; // _platform_memset() sidesteps the -fstack-check rewrite of regular // memset() to __memset_chk(), which doesn't take the abort path we want to // exercise _platform_memset(buf, 'a', 28); } static void body(void *ctx) { corrupt_type_t corrupt_type = (corrupt_type_t)ctx; pthread_t self = pthread_self(); T_LOG("Helper thread running: %d", corrupt_type); // The pthread_t is stored at the top of the stack and could be // corrupted because of a stack overflow. To make the test more // reliable, we will manually smash the pthread struct directly. if (corrupt_type & FULL_CORRUPTION) { memset(self, 0x41, 4096); } else if (corrupt_type & SIG_CORRUPTION) { memset(self, 0x41, 128); } if (corrupt_type & STACK_CORRUPTION) { induce_stack_check_failure(); T_ASSERT_FAIL("Should have aborted"); } else { // Expected behavior is that if a thread calls abort, the process should // abort promptly. abort(); T_FAIL("Abort didn't?"); } } static void * body_thr(void *ctx) { body(ctx); /* UNREACHABLE */ return (NULL); } static void abort_test(thread_type_t type, corrupt_type_t corrupt_type) { pid_t child = fork(); if (child == 0) { T_LOG("Child running"); switch (type) { case PTHREAD: { pthread_t tid; T_QUIET; T_ASSERT_POSIX_ZERO( pthread_create(&tid, NULL, body_thr, (void *)corrupt_type), NULL); break; } case WORKQUEUE: { dispatch_async_f(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), (void *)corrupt_type, body); break; } } sleep(5); T_FAIL("Child didn't abort"); exit(-1); } // Wait and check the exit status of the child int status = 0; pid_t pid = wait(&status); T_QUIET; T_ASSERT_EQ(pid, child, NULL); T_QUIET; T_EXPECT_FALSE(WIFEXITED(status), "WIFEXITED Status: %x", status); T_QUIET; T_EXPECT_TRUE(WIFSIGNALED(status), "WIFSIGNALED Status: %x", status); T_QUIET; T_EXPECT_FALSE(WIFSTOPPED(status), "WIFSTOPPED Status: %x", status); // This test is successful if we trigger a SIGSEGV|SIGBUS or SIGABRT // since both will promptly terminate the program int signal = WTERMSIG(status); #if defined(__i386__) || defined(__x86_64__) // on intel pthread_self() reads a TSD so FULL corruption results // in SIGSEGV/SIGBUS if (corrupt_type == FULL_CORRUPTION) { // any of these signals may happen depending on which libpthread // you're running on. if (signal == SIGBUS) { T_LOG("Converting %d to SIGSEGV", signal); signal = SIGSEGV; } T_EXPECT_EQ(signal, SIGSEGV, NULL); T_END; } #endif #if defined(__arm64e__) // on arm64e pthread_self() checks a ptrauth signature so it is // likely to die of either SIGTRAP or SIGBUS. // // On FPAC hardware with certain configurations, PAC exceptions // are fatal, which means the delivered signal is SIGKILL. if ((corrupt_type & (FULL_CORRUPTION | SIG_CORRUPTION))) { switch(signal) { case SIGBUS: T_EXPECT_EQ(signal, SIGBUS, NULL); break; case SIGTRAP: T_EXPECT_EQ(signal, SIGTRAP, NULL); break; default: // If the delivered signal is not SIGTRAP or SIGBUS, // it has to be because the exception is fatal, // which means the signal is SIGKILL. T_EXPECT_EQ(signal, SIGKILL, NULL); break; } T_END; } #endif /* pthread calls abort_with_reason if only the signature is corrupt */ T_EXPECT_EQ(signal, SIGABRT, NULL); } static void signal_handler(int signo) { // The user's signal handler should not be called during abort T_FAIL("Unexpected signal: %d\n", signo); } T_DECL(abort_pthread_corrupt_test_full, "Tests abort", T_META_IGNORECRASHES(".*abort_tests.*")) { abort_test(PTHREAD, FULL_CORRUPTION); } T_DECL(abort_workqueue_corrupt_test_full, "Tests abort", T_META_IGNORECRASHES(".*abort_tests.*")) { abort_test(WORKQUEUE, FULL_CORRUPTION); } T_DECL(abort_pthread_handler_test_full, "Tests abort", T_META_IGNORECRASHES(".*abort_tests.*")) { // rdar://52892057 T_SKIP("Abort hangs if the user registers their own SIGSEGV handler"); signal(SIGSEGV, signal_handler); abort_test(PTHREAD, FULL_CORRUPTION); } T_DECL(abort_workqueue_handler_test_full, "Tests abort", T_META_IGNORECRASHES(".*abort_tests.*")) { // rdar://52892057 T_SKIP("Abort hangs if the user registers their own SIGSEGV handler"); signal(SIGSEGV, signal_handler); abort_test(WORKQUEUE, FULL_CORRUPTION); } T_DECL(abort_pthread_corrupt_test_sig, "Tests abort", T_META_IGNORECRASHES(".*abort_tests.*")) { abort_test(PTHREAD, SIG_CORRUPTION); } T_DECL(abort_workqueue_corrupt_test_sig, "Tests abort", T_META_IGNORECRASHES(".*abort_tests.*")) { abort_test(WORKQUEUE, SIG_CORRUPTION); } T_DECL(abort_pthread_test, "Tests abort", T_META_IGNORECRASHES(".*abort_tests.*")) { abort_test(PTHREAD, NO_CORRUPTION); } T_DECL(abort_workqueue_test, "Tests abort", T_META_IGNORECRASHES(".*abort_tests.*")) { abort_test(WORKQUEUE, NO_CORRUPTION); } T_DECL(abort_pthread_stack_check_test, "Tests __stack_chk_fail() abort", T_META_IGNORECRASHES(".*abort_tests.*")) { abort_test(PTHREAD, STACK_CORRUPTION); } T_DECL(abort_pthread_stack_check_corrupt_sig_test, "Tests __stack_chk_fail() abort with a corrupt pthread_t", T_META_IGNORECRASHES(".*abort_tests.*")) { abort_test(PTHREAD, (corrupt_type_t)(STACK_CORRUPTION | SIG_CORRUPTION)); } |