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 2 : static void sigbus_push(void *addr) {
26 : unsigned u;
27 :
28 2 : assert(addr);
29 :
30 : /* Find a free place, increase the number of entries and leave, if we can */
31 3 : for (u = 0; u < SIGBUS_QUEUE_MAX; u++)
32 3 : if (__sync_bool_compare_and_swap(&sigbus_queue[u], NULL, addr)) {
33 2 : __sync_fetch_and_add(&n_sigbus_queue, 1);
34 2 : 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 138218 : int sigbus_pop(void **ret) {
54 138218 : assert(ret);
55 :
56 0 : for (;;) {
57 : unsigned u, c;
58 :
59 138218 : __sync_synchronize();
60 138218 : c = n_sigbus_queue;
61 :
62 138218 : if (_likely_(c == 0))
63 138216 : return 0;
64 :
65 2 : if (_unlikely_(c >= SIGBUS_QUEUE_MAX))
66 0 : return -EOVERFLOW;
67 :
68 3 : for (u = 0; u < SIGBUS_QUEUE_MAX; u++) {
69 : void *addr;
70 :
71 3 : addr = sigbus_queue[u];
72 3 : if (!addr)
73 1 : continue;
74 :
75 2 : if (__sync_bool_compare_and_swap(&sigbus_queue[u], addr, NULL)) {
76 2 : __sync_fetch_and_sub(&n_sigbus_queue, 1);
77 2 : *ret = addr;
78 2 : return 1;
79 : }
80 : }
81 : }
82 : }
83 :
84 2 : static void sigbus_handler(int sn, siginfo_t *si, void *data) {
85 : unsigned long ul;
86 : void *aligned;
87 :
88 2 : assert(sn == SIGBUS);
89 2 : assert(si);
90 :
91 2 : 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 2 : ul = (unsigned long) si->si_addr;
98 2 : ul = ul / page_size();
99 2 : ul = ul * page_size();
100 2 : aligned = (void*) ul;
101 :
102 : /* Let's remember which address failed */
103 2 : 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 2 : assert_se(mmap(aligned, page_size(), PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, -1, 0) == aligned);
108 : }
109 :
110 13 : void sigbus_install(void) {
111 13 : 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 13 : (void) page_size();
119 :
120 13 : n_installed++;
121 :
122 13 : if (n_installed == 1)
123 13 : assert_se(sigaction(SIGBUS, &sa, &old_sigaction) == 0);
124 :
125 13 : return;
126 : }
127 :
128 1 : void sigbus_reset(void) {
129 :
130 1 : if (n_installed <= 0)
131 0 : return;
132 :
133 1 : n_installed--;
134 :
135 1 : if (n_installed == 0)
136 1 : assert_se(sigaction(SIGBUS, &old_sigaction, NULL) == 0);
137 :
138 1 : return;
139 : }
|