1/***
2  This file is part of PulseAudio.
3
4  Copyright 2009 Lennart Poettering
5
6  PulseAudio is free software; you can redistribute it and/or modify
7  it under the terms of the GNU Lesser General Public License as
8  published by the Free Software Foundation; either version 2.1 of the
9  License, or (at your option) any later version.
10
11  PulseAudio is distributed in the hope that it will be useful, but
12  WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14  General Public License for more details.
15
16  You should have received a copy of the GNU Lesser General Public
17  License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
18***/
19
20#ifdef HAVE_CONFIG_H
21#include <config.h>
22#endif
23
24#include <signal.h>
25
26#ifdef HAVE_SYS_MMAN_H
27#include <sys/mman.h>
28#endif
29
30/* This is deprecated on glibc but is still used by FreeBSD */
31#if !defined(MAP_ANONYMOUS) && defined(MAP_ANON)
32# define MAP_ANONYMOUS MAP_ANON
33#endif
34
35#include <pulse/xmalloc.h>
36
37#include <pulsecore/core-util.h>
38#include <pulsecore/aupdate.h>
39#include <pulsecore/atomic.h>
40#include <pulsecore/once.h>
41#include <pulsecore/mutex.h>
42
43#include "memtrap.h"
44
45struct pa_memtrap {
46    void *start;
47    size_t size;
48    pa_atomic_t bad;
49    pa_memtrap *next[2], *prev[2];
50};
51
52static pa_memtrap *memtraps[2] = { NULL, NULL };
53static pa_aupdate *aupdate;
54static pa_static_mutex mutex = PA_STATIC_MUTEX_INIT; /* only required to serialize access to the write side */
55
56static void allocate_aupdate(void) {
57    PA_ONCE_BEGIN {
58        aupdate = pa_aupdate_new();
59    } PA_ONCE_END;
60}
61
62bool pa_memtrap_is_good(pa_memtrap *m) {
63    pa_assert(m);
64
65    return !pa_atomic_load(&m->bad);
66}
67
68#ifdef HAVE_SIGACTION
69static void sigsafe_error(const char *s) {
70    size_t ret PA_GCC_UNUSED;
71    ret = write(STDERR_FILENO, s, strlen(s));
72}
73
74static void signal_handler(int sig, siginfo_t* si, void *data) {
75    unsigned j;
76    pa_memtrap *m;
77    void *r;
78
79    j = pa_aupdate_read_begin(aupdate);
80
81    for (m = memtraps[j]; m; m = m->next[j])
82        if (si->si_addr >= m->start &&
83            (uint8_t*) si->si_addr < (uint8_t*) m->start + m->size)
84            break;
85
86    if (!m)
87        goto fail;
88
89    pa_atomic_store(&m->bad, 1);
90
91    /* Remap anonymous memory into the bad segment */
92    if ((r = mmap(m->start, m->size, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_FIXED|MAP_PRIVATE, -1, 0)) == MAP_FAILED) {
93        sigsafe_error("mmap() failed.\n");
94        goto fail;
95    }
96
97    pa_assert(r == m->start);
98
99    pa_aupdate_read_end(aupdate);
100    return;
101
102fail:
103    pa_aupdate_read_end(aupdate);
104
105    sigsafe_error("Failed to handle SIGBUS.\n");
106    abort();
107}
108#endif
109
110static void memtrap_link(pa_memtrap *m, unsigned j) {
111    pa_assert(m);
112
113    m->prev[j] = NULL;
114
115    if ((m->next[j] = memtraps[j]))
116        m->next[j]->prev[j] = m;
117
118    memtraps[j] = m;
119}
120
121static void memtrap_unlink(pa_memtrap *m, unsigned j) {
122    pa_assert(m);
123
124    if (m->next[j])
125        m->next[j]->prev[j] = m->prev[j];
126
127    if (m->prev[j])
128        m->prev[j]->next[j] = m->next[j];
129    else
130        memtraps[j] = m->next[j];
131}
132
133pa_memtrap* pa_memtrap_add(const void *start, size_t size) {
134    pa_memtrap *m = NULL;
135    unsigned j;
136    pa_mutex *mx;
137
138    pa_assert(start);
139    pa_assert(size > 0);
140
141    start = PA_PAGE_ALIGN_PTR(start);
142    size = PA_PAGE_ALIGN(size);
143
144    m = pa_xnew(pa_memtrap, 1);
145    m->start = (void*) start;
146    m->size = size;
147    pa_atomic_store(&m->bad, 0);
148
149    allocate_aupdate();
150
151    mx = pa_static_mutex_get(&mutex, false, true);
152    pa_mutex_lock(mx);
153
154    j = pa_aupdate_write_begin(aupdate);
155    memtrap_link(m, j);
156    j = pa_aupdate_write_swap(aupdate);
157    memtrap_link(m, j);
158    pa_aupdate_write_end(aupdate);
159
160    pa_mutex_unlock(mx);
161
162    return m;
163}
164
165void pa_memtrap_remove(pa_memtrap *m) {
166    unsigned j;
167    pa_mutex *mx;
168
169    pa_assert(m);
170
171    allocate_aupdate();
172
173    mx = pa_static_mutex_get(&mutex, false, true);
174    pa_mutex_lock(mx);
175
176    j = pa_aupdate_write_begin(aupdate);
177    memtrap_unlink(m, j);
178    j = pa_aupdate_write_swap(aupdate);
179    memtrap_unlink(m, j);
180    pa_aupdate_write_end(aupdate);
181
182    pa_mutex_unlock(mx);
183
184    pa_xfree(m);
185}
186
187pa_memtrap *pa_memtrap_update(pa_memtrap *m, const void *start, size_t size) {
188    unsigned j;
189    pa_mutex *mx;
190
191    pa_assert(m);
192
193    pa_assert(start);
194    pa_assert(size > 0);
195
196    start = PA_PAGE_ALIGN_PTR(start);
197    size = PA_PAGE_ALIGN(size);
198
199    allocate_aupdate();
200
201    mx = pa_static_mutex_get(&mutex, false, true);
202    pa_mutex_lock(mx);
203
204    j = pa_aupdate_write_begin(aupdate);
205
206    if (m->start == start && m->size == size)
207        goto unlock;
208
209    memtrap_unlink(m, j);
210    pa_aupdate_write_swap(aupdate);
211
212    m->start = (void*) start;
213    m->size = size;
214    pa_atomic_store(&m->bad, 0);
215
216    pa_assert_se(pa_aupdate_write_swap(aupdate) == j);
217    memtrap_link(m, j);
218
219unlock:
220    pa_aupdate_write_end(aupdate);
221
222    pa_mutex_unlock(mx);
223
224    return m;
225}
226
227void pa_memtrap_install(void) {
228#ifdef HAVE_SIGACTION
229    struct sigaction sa;
230
231    allocate_aupdate();
232
233    memset(&sa, 0, sizeof(sa));
234    sa.sa_sigaction = signal_handler;
235    sa.sa_flags = SA_RESTART|SA_SIGINFO;
236
237    pa_assert_se(sigaction(SIGBUS, &sa, NULL) == 0);
238#ifdef __FreeBSD_kernel__
239    pa_assert_se(sigaction(SIGSEGV, &sa, NULL) == 0);
240#endif
241#endif
242}
243