1 /***
2 This file is part of PulseAudio.
3
4 Copyright 2006 Lennart Poettering
5 Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
6
7 PulseAudio is free software; you can redistribute it and/or modify
8 it under the terms of the GNU Lesser General Public License as
9 published by the Free Software Foundation; either version 2.1 of the
10 License, or (at your option) any later version.
11
12 PulseAudio is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public
18 License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
19 ***/
20
21 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
24
25 #ifndef LOG_TAG
26 #define LOG_TAG "Shm"
27 #endif
28
29 #include <stdlib.h>
30 #include <unistd.h>
31 #include <fcntl.h>
32 #include <stdio.h>
33 #include <errno.h>
34 #include <string.h>
35 #include <sys/stat.h>
36 #include <sys/types.h>
37 #include <dirent.h>
38 #include <signal.h>
39
40 #ifdef HAVE_SYS_MMAN_H
41 #include <sys/mman.h>
42 #endif
43
44 /* This is deprecated on glibc but is still used by FreeBSD */
45 #if !defined(MAP_ANONYMOUS) && defined(MAP_ANON)
46 # define MAP_ANONYMOUS MAP_ANON
47 #endif
48
49 #include <pulse/xmalloc.h>
50 #include <pulse/gccmacro.h>
51
52 #include <pulsecore/memfd-wrappers.h>
53 #include <pulsecore/core-error.h>
54 #include <pulsecore/log.h>
55 #include <pulsecore/random.h>
56 #include <pulsecore/core-util.h>
57 #include <pulsecore/macro.h>
58 #include <pulsecore/atomic.h>
59 #include <pulsecore/mem.h>
60
61 #include "shm.h"
62
63 #if defined(__linux__) && !defined(MADV_REMOVE)
64 #define MADV_REMOVE 9
65 #endif
66
67 /* 1 GiB at max */
68 #define MAX_SHM_SIZE (PA_ALIGN(1024*1024*1024))
69
70 #ifdef __linux__
71 /* On Linux we know that the shared memory blocks are files in
72 * /dev/shm. We can use that information to list all blocks and
73 * cleanup unused ones */
74 #define SHM_PATH "/dev/shm/"
75 #define SHM_ID_LEN 10
76 #elif defined(__sun)
77 #define SHM_PATH "/tmp"
78 #define SHM_ID_LEN 15
79 #else
80 #undef SHM_PATH
81 #undef SHM_ID_LEN
82 #endif
83
84 #define SHM_MARKER ((int) 0xbeefcafe)
85
86 /* We now put this SHM marker at the end of each segment. It's
87 * optional, to not require a reboot when upgrading, though. Note that
88 * on multiarch systems 32bit and 64bit processes might access this
89 * region simultaneously. The header fields need to be independent
90 * from the process' word with */
91 struct shm_marker {
92 pa_atomic_t marker; /* 0xbeefcafe */
93 pa_atomic_t pid;
94 uint64_t _reserved1;
95 uint64_t _reserved2;
96 uint64_t _reserved3;
97 uint64_t _reserved4;
98 };
99
100 // Ensure struct is appropriately packed
101 static_assert(sizeof(struct shm_marker) == 8 * 5, "`struct shm_marker` is not tightly packed");
102
shm_marker_size(pa_mem_type_t type)103 static inline size_t shm_marker_size(pa_mem_type_t type) {
104 if (type == PA_MEM_TYPE_SHARED_POSIX)
105 return PA_ALIGN(sizeof(struct shm_marker));
106
107 return 0;
108 }
109
110 #ifdef HAVE_SHM_OPEN
segment_name(char * fn,size_t l,unsigned id)111 static char *segment_name(char *fn, size_t l, unsigned id) {
112 pa_snprintf(fn, l, "/pulse-shm-%u", id);
113 return fn;
114 }
115 #endif
116
privatemem_create(pa_shm * m,size_t size)117 static int privatemem_create(pa_shm *m, size_t size) {
118 pa_assert(m);
119 pa_assert(size > 0);
120
121 m->type = PA_MEM_TYPE_PRIVATE;
122 m->id = 0;
123 m->size = size;
124 m->do_unlink = false;
125 m->fd = -1;
126
127 #ifdef MAP_ANONYMOUS
128 if ((m->ptr = mmap(NULL, m->size, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, (off_t) 0)) == MAP_FAILED) {
129 pa_log_error("mmap() failed: %s", pa_cstrerror(errno));
130 return -1;
131 }
132 #elif defined(HAVE_POSIX_MEMALIGN)
133 {
134 int r;
135
136 if ((r = posix_memalign(&m->ptr, pa_page_size(), size)) < 0) {
137 pa_log_error("posix_memalign() failed: %s", pa_cstrerror(r));
138 return r;
139 }
140 }
141 #else
142 m->ptr = pa_xmalloc(m->size);
143 #endif
144
145 return 0;
146 }
147
sharedmem_create(pa_shm * m,pa_mem_type_t type,size_t size,mode_t mode)148 static int sharedmem_create(pa_shm *m, pa_mem_type_t type, size_t size, mode_t mode) {
149 #if defined(HAVE_SHM_OPEN) || defined(HAVE_MEMFD)
150 char fn[32];
151 int fd = -1;
152 struct shm_marker *marker;
153 bool do_unlink = false;
154
155 /* Each time we create a new SHM area, let's first drop all stale
156 * ones */
157 pa_shm_cleanup();
158
159 pa_random(&m->id, sizeof(m->id));
160
161 switch (type) {
162 #ifdef HAVE_SHM_OPEN
163 case PA_MEM_TYPE_SHARED_POSIX:
164 segment_name(fn, sizeof(fn), m->id);
165 fd = shm_open(fn, O_RDWR|O_CREAT|O_EXCL, mode);
166 do_unlink = true;
167 break;
168 #endif
169 #ifdef HAVE_MEMFD
170 case PA_MEM_TYPE_SHARED_MEMFD:
171 /* For linux >= 6.3 create fd with MFD_NOEXEC_SEAL flag */
172 fd = memfd_create("pulseaudio", MFD_ALLOW_SEALING|MFD_CLOEXEC|MFD_NOEXEC_SEAL);
173 /* Retry creating fd without MFD_NOEXEC_SEAL to support linux < 6.3 */
174 if (fd < 0)
175 fd = memfd_create("pulseaudio", MFD_ALLOW_SEALING|MFD_CLOEXEC);
176 break;
177 #endif
178 default:
179 goto fail;
180 }
181
182 if (fd < 0) {
183 pa_log_error("%s open() failed: %s", pa_mem_type_to_string(type), pa_cstrerror(errno));
184 goto fail;
185 }
186
187 m->type = type;
188 m->size = size + shm_marker_size(type);
189 m->do_unlink = do_unlink;
190
191 if (ftruncate(fd, (off_t) m->size) < 0) {
192 pa_log_error("ftruncate() failed: %s", pa_cstrerror(errno));
193 goto fail;
194 }
195
196 #ifndef MAP_NORESERVE
197 #define MAP_NORESERVE 0
198 #endif
199
200 if ((m->ptr = mmap(NULL, PA_PAGE_ALIGN(m->size), PROT_READ|PROT_WRITE, MAP_SHARED|MAP_NORESERVE, fd, (off_t) 0)) == MAP_FAILED) {
201 pa_log_error("mmap() failed: %s", pa_cstrerror(errno));
202 goto fail;
203 }
204
205 if (type == PA_MEM_TYPE_SHARED_POSIX) {
206 /* We store our PID at the end of the shm block, so that we
207 * can check for dead shm segments later */
208 marker = (struct shm_marker*) ((uint8_t*) m->ptr + m->size - shm_marker_size(type));
209 pa_atomic_store(&marker->pid, (int) getpid());
210 pa_atomic_store(&marker->marker, SHM_MARKER);
211 }
212
213 /* For memfds, we keep the fd open until we pass it
214 * to the other PA endpoint over unix domain socket. */
215 if (type != PA_MEM_TYPE_SHARED_MEMFD) {
216 pa_assert_se(pa_close(fd) == 0);
217 m->fd = -1;
218 }
219 #ifdef HAVE_MEMFD
220 else
221 m->fd = fd;
222 #endif
223
224 return 0;
225
226 fail:
227 if (fd >= 0) {
228 #ifdef HAVE_SHM_OPEN
229 if (type == PA_MEM_TYPE_SHARED_POSIX)
230 shm_unlink(fn);
231 #endif
232 pa_close(fd);
233 }
234 #endif /* defined(HAVE_SHM_OPEN) || defined(HAVE_MEMFD) */
235
236 return -1;
237 }
238
pa_shm_create_rw(pa_shm * m,pa_mem_type_t type,size_t size,mode_t mode)239 int pa_shm_create_rw(pa_shm *m, pa_mem_type_t type, size_t size, mode_t mode) {
240 pa_assert(m);
241 pa_assert(size > 0);
242 pa_assert(size <= MAX_SHM_SIZE);
243 pa_assert(!(mode & ~0777));
244 pa_assert(mode >= 0600);
245
246 /* Round up to make it page aligned */
247 size = PA_PAGE_ALIGN(size);
248
249 if (type == PA_MEM_TYPE_PRIVATE)
250 return privatemem_create(m, size);
251
252 return sharedmem_create(m, type, size, mode);
253 }
254
privatemem_free(pa_shm * m)255 static void privatemem_free(pa_shm *m) {
256 pa_assert(m);
257 pa_assert(m->ptr);
258 pa_assert(m->size > 0);
259
260 #ifdef MAP_ANONYMOUS
261 if (munmap(m->ptr, m->size) < 0)
262 pa_log_error("munmap() failed: %s", pa_cstrerror(errno));
263 #elif defined(HAVE_POSIX_MEMALIGN)
264 free(m->ptr);
265 #else
266 pa_xfree(m->ptr);
267 #endif
268 }
269
pa_shm_free(pa_shm * m)270 void pa_shm_free(pa_shm *m) {
271 pa_assert(m);
272 pa_assert(m->ptr);
273 pa_assert(m->size > 0);
274
275 #ifdef MAP_FAILED
276 pa_assert(m->ptr != MAP_FAILED);
277 #endif
278
279 if (m->type == PA_MEM_TYPE_PRIVATE) {
280 privatemem_free(m);
281 goto finish;
282 }
283
284 pa_log_info("mem type: %d", m->type);
285
286 #if defined(HAVE_SHM_OPEN) || defined(HAVE_MEMFD)
287 if (munmap(m->ptr, PA_PAGE_ALIGN(m->size)) < 0)
288 pa_log_error("munmap() failed: %s", pa_cstrerror(errno));
289
290 #ifdef HAVE_SHM_OPEN
291 if (m->type == PA_MEM_TYPE_SHARED_POSIX && m->do_unlink) {
292 char fn[32];
293
294 segment_name(fn, sizeof(fn), m->id);
295 if (shm_unlink(fn) < 0)
296 pa_log_error(" shm_unlink(%s) failed: %s", fn, pa_cstrerror(errno));
297 }
298 #endif
299 #ifdef HAVE_MEMFD
300 if (m->type == PA_MEM_TYPE_SHARED_MEMFD && m->fd != -1)
301 pa_assert_se(pa_close(m->fd) == 0);
302 #endif
303
304 #else
305 /* We shouldn't be here without shm or memfd support */
306 pa_log_error("remove pa_assert_not_reached call");
307 #endif
308
309 finish:
310 pa_zero(*m);
311 }
312
pa_shm_punch(pa_shm * m,size_t offset,size_t size)313 void pa_shm_punch(pa_shm *m, size_t offset, size_t size) {
314 void *ptr;
315 size_t o;
316 const size_t page_size = pa_page_size();
317
318 pa_assert(m);
319 pa_assert(m->ptr);
320 pa_assert(m->size > 0);
321 pa_assert(offset+size <= m->size);
322
323 #ifdef MAP_FAILED
324 pa_assert(m->ptr != MAP_FAILED);
325 #endif
326
327 /* You're welcome to implement this as NOOP on systems that don't
328 * support it */
329
330 /* Align the pointer up to multiples of the page size */
331 ptr = (uint8_t*) m->ptr + offset;
332 o = (size_t) ((uint8_t*) ptr - (uint8_t*) PA_PAGE_ALIGN_PTR(ptr));
333
334 if (o > 0) {
335 size_t delta = page_size - o;
336 ptr = (uint8_t*) ptr + delta;
337 size -= delta;
338 }
339
340 /* Align the size down to multiples of page size */
341 size = (size / page_size) * page_size;
342
343 #ifdef MADV_REMOVE
344 if (madvise(ptr, size, MADV_REMOVE) >= 0)
345 return;
346 #endif
347
348 #ifdef MADV_FREE
349 if (madvise(ptr, size, MADV_FREE) >= 0)
350 return;
351 #endif
352
353 #ifdef MADV_DONTNEED
354 madvise(ptr, size, MADV_DONTNEED);
355 #elif defined(POSIX_MADV_DONTNEED)
356 posix_madvise(ptr, size, POSIX_MADV_DONTNEED);
357 #endif
358 }
359
shm_attach(pa_shm * m,pa_mem_type_t type,unsigned id,int memfd_fd,bool writable,bool for_cleanup)360 static int shm_attach(pa_shm *m, pa_mem_type_t type, unsigned id, int memfd_fd, bool writable, bool for_cleanup) {
361 #if defined(HAVE_SHM_OPEN) || defined(HAVE_MEMFD)
362 char fn[32];
363 int fd = -1;
364 int prot;
365 struct stat st;
366
367 pa_assert(m);
368
369 switch (type) {
370 #ifdef HAVE_SHM_OPEN
371 case PA_MEM_TYPE_SHARED_POSIX:
372 pa_assert(memfd_fd == -1);
373 segment_name(fn, sizeof(fn), id);
374 if ((fd = shm_open(fn, writable ? O_RDWR : O_RDONLY, 0)) < 0) {
375 if ((errno != EACCES && errno != ENOENT) || !for_cleanup)
376 pa_log_error("shm_open() failed: %s", pa_cstrerror(errno));
377 goto fail;
378 }
379 break;
380 #endif
381 #ifdef HAVE_MEMFD
382 case PA_MEM_TYPE_SHARED_MEMFD:
383 pa_assert(memfd_fd != -1);
384 fd = memfd_fd;
385 break;
386 #endif
387 default:
388 goto fail;
389 }
390
391 if (fstat(fd, &st) < 0) {
392 pa_log_error("fstat() failed: %s", pa_cstrerror(errno));
393 goto fail;
394 }
395
396 if (st.st_size <= 0 ||
397 st.st_size > (off_t) MAX_SHM_SIZE + (off_t) shm_marker_size(type) ||
398 PA_ALIGN((size_t) st.st_size) != (size_t) st.st_size) {
399 pa_log_error("Invalid shared memory segment size");
400 goto fail;
401 }
402
403 prot = writable ? PROT_READ | PROT_WRITE : PROT_READ;
404 if ((m->ptr = mmap(NULL, PA_PAGE_ALIGN(st.st_size), prot, MAP_SHARED, fd, (off_t) 0)) == MAP_FAILED) {
405 pa_log_error("mmap() failed: %s", pa_cstrerror(errno));
406 goto fail;
407 }
408
409 /* In case of attaching to memfd areas, _the caller_ maintains
410 * ownership of the passed fd and has the sole responsibility
411 * of closing it down.. For other types, we're the code path
412 * which created the fd in the first place and we're thus the
413 * ones responsible for closing it down */
414 if (type != PA_MEM_TYPE_SHARED_MEMFD)
415 pa_assert_se(pa_close(fd) == 0);
416
417 pa_log_info("shm_attach set mem type %d", type);
418 m->type = type;
419 m->id = id;
420 m->size = (size_t) st.st_size;
421 m->do_unlink = false;
422 m->fd = -1;
423
424 return 0;
425
426 fail:
427 /* In case of memfds, caller maintains fd ownership */
428 if (fd >= 0 && type != PA_MEM_TYPE_SHARED_MEMFD)
429 pa_close(fd);
430
431 #endif /* defined(HAVE_SHM_OPEN) || defined(HAVE_MEMFD) */
432
433 return -1;
434 }
435
436 /* Caller owns passed @memfd_fd and must close it down when appropriate. */
pa_shm_attach(pa_shm * m,pa_mem_type_t type,unsigned id,int memfd_fd,bool writable)437 int pa_shm_attach(pa_shm *m, pa_mem_type_t type, unsigned id, int memfd_fd, bool writable) {
438 return shm_attach(m, type, id, memfd_fd, writable, false);
439 }
440
pa_shm_cleanup(void)441 int pa_shm_cleanup(void) {
442 pa_log_info("start pa_shm_cleanup");
443 #ifdef HAVE_SHM_OPEN
444 #ifdef SHM_PATH
445 DIR *d;
446 struct dirent *de;
447
448 if (!(d = opendir(SHM_PATH))) {
449 pa_log_warn("Failed to read "SHM_PATH": %s", pa_cstrerror(errno));
450 return -1;
451 }
452
453 while ((de = readdir(d))) {
454 pa_shm seg;
455 unsigned id;
456 pid_t pid;
457 char fn[128];
458 struct shm_marker *m;
459
460 #if defined(__sun)
461 if (strncmp(de->d_name, ".SHMDpulse-shm-", SHM_ID_LEN))
462 #else
463 if (strncmp(de->d_name, "pulse-shm-", SHM_ID_LEN))
464 #endif
465 continue;
466
467 if (pa_atou(de->d_name + SHM_ID_LEN, &id) < 0)
468 continue;
469
470 if (shm_attach(&seg, PA_MEM_TYPE_SHARED_POSIX, id, -1, false, true) < 0)
471 continue;
472
473 if (seg.size < shm_marker_size(seg.type)) {
474 pa_shm_free(&seg);
475 continue;
476 }
477
478 m = (struct shm_marker*) ((uint8_t*) seg.ptr + seg.size - shm_marker_size(seg.type));
479
480 if (pa_atomic_load(&m->marker) != SHM_MARKER) {
481 pa_shm_free(&seg);
482 continue;
483 }
484
485 if (!(pid = (pid_t) pa_atomic_load(&m->pid))) {
486 pa_shm_free(&seg);
487 continue;
488 }
489
490 if (kill(pid, 0) == 0 || errno != ESRCH) {
491 pa_shm_free(&seg);
492 continue;
493 }
494
495 pa_shm_free(&seg);
496
497 /* Ok, the owner of this shms segment is dead, so, let's remove the segment */
498 segment_name(fn, sizeof(fn), id);
499
500 if (shm_unlink(fn) < 0 && errno != EACCES && errno != ENOENT)
501 pa_log_warn("Failed to remove SHM segment %s: %s", fn, pa_cstrerror(errno));
502 }
503
504 closedir(d);
505 #endif /* SHM_PATH */
506 #endif /* HAVE_SHM_OPEN */
507
508 return 0;
509 }
510