Branch data Line data Source code
1 : : /* SPDX-License-Identifier: LGPL-2.1+ */ 2 : : 3 : : #include <errno.h> 4 : : #include <signal.h> 5 : : #include <stddef.h> 6 : : #include <sys/mman.h> 7 : : 8 : : #include "macro.h" 9 : : #include "memory-util.h" 10 : : #include "sigbus.h" 11 : : 12 : : #define SIGBUS_QUEUE_MAX 64 13 : : 14 : : static struct sigaction old_sigaction; 15 : : static unsigned n_installed = 0; 16 : : 17 : : /* We maintain a fixed size list of page addresses that triggered a 18 : : SIGBUS. We access with list with atomic operations, so that we 19 : : don't have to deal with locks between signal handler and main 20 : : programs in possibly multiple threads. */ 21 : : 22 : : static void* volatile sigbus_queue[SIGBUS_QUEUE_MAX]; 23 : : static volatile sig_atomic_t n_sigbus_queue = 0; 24 : : 25 : 8 : static void sigbus_push(void *addr) { 26 : : unsigned u; 27 : : 28 [ - + ]: 8 : assert(addr); 29 : : 30 : : /* Find a free place, increase the number of entries and leave, if we can */ 31 [ + - ]: 12 : for (u = 0; u < SIGBUS_QUEUE_MAX; u++) 32 [ + + ]: 12 : if (__sync_bool_compare_and_swap(&sigbus_queue[u], NULL, addr)) { 33 : 8 : __sync_fetch_and_add(&n_sigbus_queue, 1); 34 : 8 : return; 35 : : } 36 : : 37 : : /* If we can't, make sure the queue size is out of bounds, to 38 : : * mark it as overflow */ 39 : 0 : for (;;) { 40 : : unsigned c; 41 : : 42 : 0 : __sync_synchronize(); 43 : 0 : c = n_sigbus_queue; 44 : : 45 [ # # ]: 0 : if (c > SIGBUS_QUEUE_MAX) /* already overflow */ 46 : 0 : return; 47 : : 48 [ # # ]: 0 : if (__sync_bool_compare_and_swap(&n_sigbus_queue, c, c + SIGBUS_QUEUE_MAX)) 49 : 0 : return; 50 : : } 51 : : } 52 : : 53 : 552872 : int sigbus_pop(void **ret) { 54 [ - + ]: 552872 : assert(ret); 55 : : 56 : 0 : for (;;) { 57 : : unsigned u, c; 58 : : 59 : 552872 : __sync_synchronize(); 60 : 552872 : c = n_sigbus_queue; 61 : : 62 [ + + ]: 552872 : if (_likely_(c == 0)) 63 : 552864 : return 0; 64 : : 65 [ - + ]: 8 : if (_unlikely_(c >= SIGBUS_QUEUE_MAX)) 66 : 0 : return -EOVERFLOW; 67 : : 68 [ + - ]: 12 : for (u = 0; u < SIGBUS_QUEUE_MAX; u++) { 69 : : void *addr; 70 : : 71 : 12 : addr = sigbus_queue[u]; 72 [ + + ]: 12 : if (!addr) 73 : 4 : continue; 74 : : 75 [ + - ]: 8 : if (__sync_bool_compare_and_swap(&sigbus_queue[u], addr, NULL)) { 76 : 8 : __sync_fetch_and_sub(&n_sigbus_queue, 1); 77 : 8 : *ret = addr; 78 : 8 : return 1; 79 : : } 80 : : } 81 : : } 82 : : } 83 : : 84 : 8 : static void sigbus_handler(int sn, siginfo_t *si, void *data) { 85 : : unsigned long ul; 86 : : void *aligned; 87 : : 88 [ - + ]: 8 : assert(sn == SIGBUS); 89 [ - + ]: 8 : assert(si); 90 : : 91 [ + - - + ]: 8 : if (si->si_code != BUS_ADRERR || !si->si_addr) { 92 [ # # ]: 0 : assert_se(sigaction(SIGBUS, &old_sigaction, NULL) == 0); 93 : 0 : raise(SIGBUS); 94 : 0 : return; 95 : : } 96 : : 97 : 8 : ul = (unsigned long) si->si_addr; 98 : 8 : ul = ul / page_size(); 99 : 8 : ul = ul * page_size(); 100 : 8 : aligned = (void*) ul; 101 : : 102 : : /* Let's remember which address failed */ 103 : 8 : sigbus_push(aligned); 104 : : 105 : : /* Replace mapping with an anonymous page, so that the 106 : : * execution can continue, however with a zeroed out page */ 107 [ - + ]: 8 : assert_se(mmap(aligned, page_size(), PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, -1, 0) == aligned); 108 : : } 109 : : 110 : 52 : void sigbus_install(void) { 111 : 52 : struct sigaction sa = { 112 : : .sa_sigaction = sigbus_handler, 113 : : .sa_flags = SA_SIGINFO, 114 : : }; 115 : : 116 : : /* make sure that sysconf() is not called from a signal handler because 117 : : * it is not guaranteed to be async-signal-safe since POSIX.1-2008 */ 118 : 52 : (void) page_size(); 119 : : 120 : 52 : n_installed++; 121 : : 122 [ + - ]: 52 : if (n_installed == 1) 123 [ - + ]: 52 : assert_se(sigaction(SIGBUS, &sa, &old_sigaction) == 0); 124 : : 125 : 52 : return; 126 : : } 127 : : 128 : 4 : void sigbus_reset(void) { 129 : : 130 [ - + ]: 4 : if (n_installed <= 0) 131 : 0 : return; 132 : : 133 : 4 : n_installed--; 134 : : 135 [ + - ]: 4 : if (n_installed == 0) 136 [ - + ]: 4 : assert_se(sigaction(SIGBUS, &old_sigaction, NULL) == 0); 137 : : 138 : 4 : return; 139 : : }