Line data Source code
1 : /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2 :
3 : /***
4 : This file is part of systemd.
5 :
6 : Copyright 2014 Lennart Poettering
7 :
8 : systemd is free software; you can redistribute it and/or modify it
9 : under the terms of the GNU Lesser General Public License as published by
10 : the Free Software Foundation; either version 2.1 of the License, or
11 : (at your option) any later version.
12 :
13 : systemd is distributed in the hope that it will be useful, but
14 : WITHOUT ANY WARRANTY; without even the implied warranty of
15 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 : Lesser General Public License for more details.
17 :
18 : You should have received a copy of the GNU Lesser General Public License
19 : along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 : ***/
21 :
22 : #include <signal.h>
23 : #include <sys/mman.h>
24 :
25 : #include "macro.h"
26 : #include "util.h"
27 : #include "sigbus.h"
28 :
29 : #define SIGBUS_QUEUE_MAX 64
30 :
31 : static struct sigaction old_sigaction;
32 : static unsigned n_installed = 0;
33 :
34 : /* We maintain a fixed size list of page addresses that triggered a
35 : SIGBUS. We access with list with atomic operations, so that we
36 : don't have to deal with locks between signal handler and main
37 : programs in possibly multiple threads. */
38 :
39 : static void* volatile sigbus_queue[SIGBUS_QUEUE_MAX];
40 : static volatile sig_atomic_t n_sigbus_queue = 0;
41 :
42 2 : static void sigbus_push(void *addr) {
43 : unsigned u;
44 :
45 2 : assert(addr);
46 :
47 : /* Find a free place, increase the number of entries and leave, if we can */
48 3 : for (u = 0; u < SIGBUS_QUEUE_MAX; u++)
49 3 : if (__sync_bool_compare_and_swap(&sigbus_queue[u], NULL, addr)) {
50 2 : __sync_fetch_and_add(&n_sigbus_queue, 1);
51 2 : return;
52 : }
53 :
54 : /* If we can't, make sure the queue size is out of bounds, to
55 : * mark it as overflow */
56 : for (;;) {
57 : unsigned c;
58 :
59 0 : __sync_synchronize();
60 0 : c = n_sigbus_queue;
61 :
62 0 : if (c > SIGBUS_QUEUE_MAX) /* already overflow */
63 0 : return;
64 :
65 0 : if (__sync_bool_compare_and_swap(&n_sigbus_queue, c, c + SIGBUS_QUEUE_MAX))
66 0 : return;
67 0 : }
68 : }
69 :
70 84366 : int sigbus_pop(void **ret) {
71 84366 : assert(ret);
72 :
73 : for (;;) {
74 : unsigned u, c;
75 :
76 84366 : __sync_synchronize();
77 84366 : c = n_sigbus_queue;
78 :
79 84366 : if (_likely_(c == 0))
80 84364 : return 0;
81 :
82 2 : if (_unlikely_(c >= SIGBUS_QUEUE_MAX))
83 0 : return -EOVERFLOW;
84 :
85 3 : for (u = 0; u < SIGBUS_QUEUE_MAX; u++) {
86 : void *addr;
87 :
88 3 : addr = sigbus_queue[u];
89 3 : if (!addr)
90 1 : continue;
91 :
92 2 : if (__sync_bool_compare_and_swap(&sigbus_queue[u], addr, NULL)) {
93 2 : __sync_fetch_and_sub(&n_sigbus_queue, 1);
94 2 : *ret = addr;
95 2 : return 1;
96 : }
97 : }
98 0 : }
99 : }
100 :
101 2 : static void sigbus_handler(int sn, siginfo_t *si, void *data) {
102 : unsigned long ul;
103 : void *aligned;
104 :
105 2 : assert(sn == SIGBUS);
106 2 : assert(si);
107 :
108 2 : if (si->si_code != BUS_ADRERR || !si->si_addr) {
109 0 : assert_se(sigaction(SIGBUS, &old_sigaction, NULL) == 0);
110 0 : raise(SIGBUS);
111 0 : return;
112 : }
113 :
114 2 : ul = (unsigned long) si->si_addr;
115 2 : ul = ul / page_size();
116 2 : ul = ul * page_size();
117 2 : aligned = (void*) ul;
118 :
119 : /* Let's remember which address failed */
120 2 : sigbus_push(aligned);
121 :
122 : /* Replace mapping with an anonymous page, so that the
123 : * execution can continue, however with a zeroed out page */
124 2 : assert_se(mmap(aligned, page_size(), PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, -1, 0) == aligned);
125 : }
126 :
127 1 : void sigbus_install(void) {
128 1 : struct sigaction sa = {
129 : .sa_sigaction = sigbus_handler,
130 : .sa_flags = SA_SIGINFO,
131 : };
132 :
133 1 : n_installed++;
134 :
135 1 : if (n_installed == 1)
136 1 : assert_se(sigaction(SIGBUS, &sa, &old_sigaction) == 0);
137 :
138 1 : return;
139 : }
140 :
141 1 : void sigbus_reset(void) {
142 :
143 1 : if (n_installed <= 0)
144 0 : return;
145 :
146 1 : n_installed--;
147 :
148 1 : if (n_installed == 0)
149 1 : assert_se(sigaction(SIGBUS, &old_sigaction, NULL) == 0);
150 :
151 1 : return;
152 : }
|