diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile.am | 5 | ||||
-rw-r--r-- | src/pulsecore/memfd-wrappers.h | 68 | ||||
-rw-r--r-- | src/pulsecore/shm.c | 191 | ||||
-rw-r--r-- | src/pulsecore/shm.h | 12 |
4 files changed, 211 insertions, 65 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 62cac9c5c..63e59c58f 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -729,6 +729,11 @@ libpulsecommon_@PA_MAJORMINOR@_la_CFLAGS = $(AM_CFLAGS) $(LIBJSON_CFLAGS) $(LIBS libpulsecommon_@PA_MAJORMINOR@_la_LDFLAGS = $(AM_LDFLAGS) $(AM_LIBLDFLAGS) -avoid-version libpulsecommon_@PA_MAJORMINOR@_la_LIBADD = $(AM_LIBADD) $(LIBJSON_LIBS) $(LIBWRAP_LIBS) $(WINSOCK_LIBS) $(LTLIBICONV) $(LIBSNDFILE_LIBS) +if HAVE_MEMFD +libpulsecommon_@PA_MAJORMINOR@_la_SOURCES += \ + pulsecore/memfd-wrappers.h +endif + if HAVE_X11 libpulsecommon_@PA_MAJORMINOR@_la_SOURCES += \ pulse/client-conf-x11.c pulse/client-conf-x11.h \ diff --git a/src/pulsecore/memfd-wrappers.h b/src/pulsecore/memfd-wrappers.h new file mode 100644 index 000000000..3bed9b2b1 --- /dev/null +++ b/src/pulsecore/memfd-wrappers.h @@ -0,0 +1,68 @@ +#ifndef foopulsememfdwrappershfoo +#define foopulsememfdwrappershfoo + +/*** + This file is part of PulseAudio. + + Copyright 2016 Ahmed S. Darwish <darwish.07@gmail.com> + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. +***/ + +#ifdef HAVE_MEMFD + +#include <sys/syscall.h> +#include <fcntl.h> + +/* + * No glibc wrappers exist for memfd_create(2), so provide our own. + * + * Also define memfd fcntl sealing macros. While they are already + * defined in the kernel header file <linux/fcntl.h>, that file as + * a whole conflicts with the original glibc header <fnctl.h>. + */ + +static inline int memfd_create(const char *name, unsigned int flags) { + return syscall(SYS_memfd_create, name, flags); +} + +/* memfd_create(2) flags */ + +#ifndef MFD_CLOEXEC +#define MFD_CLOEXEC 0x0001U +#endif + +#ifndef MFD_ALLOW_SEALING +#define MFD_ALLOW_SEALING 0x0002U +#endif + +/* fcntl() seals-related flags */ + +#ifndef F_LINUX_SPECIFIC_BASE +#define F_LINUX_SPECIFIC_BASE 1024 +#endif + +#ifndef F_ADD_SEALS +#define F_ADD_SEALS (F_LINUX_SPECIFIC_BASE + 9) +#define F_GET_SEALS (F_LINUX_SPECIFIC_BASE + 10) + +#define F_SEAL_SEAL 0x0001 /* prevent further seals from being set */ +#define F_SEAL_SHRINK 0x0002 /* prevent file from shrinking */ +#define F_SEAL_GROW 0x0004 /* prevent file from growing */ +#define F_SEAL_WRITE 0x0008 /* prevent writes */ +#endif + +#endif /* HAVE_MEMFD */ + +#endif diff --git a/src/pulsecore/shm.c b/src/pulsecore/shm.c index c3306b775..c2cf8ffc4 100644 --- a/src/pulsecore/shm.c +++ b/src/pulsecore/shm.c @@ -45,6 +45,7 @@ #include <pulse/xmalloc.h> #include <pulse/gccmacro.h> +#include <pulsecore/memfd-wrappers.h> #include <pulsecore/core-error.h> #include <pulsecore/log.h> #include <pulsecore/random.h> @@ -92,7 +93,12 @@ struct shm_marker { uint64_t _reserved4; } PA_GCC_PACKED; -#define SHM_MARKER_SIZE PA_ALIGN(sizeof(struct shm_marker)) +static inline size_t shm_marker_size(pa_shm *m) { + if (m->type == PA_MEM_TYPE_SHARED_POSIX) + return PA_ALIGN(sizeof(struct shm_marker)); + + return 0; +} #ifdef HAVE_SHM_OPEN static char *segment_name(char *fn, size_t l, unsigned id) { @@ -105,9 +111,11 @@ static int privatemem_create(pa_shm *m, size_t size) { pa_assert(m); pa_assert(size > 0); + m->type = PA_MEM_TYPE_PRIVATE; m->id = 0; m->size = size; m->do_unlink = false; + m->fd = -1; #ifdef MAP_ANONYMOUS if ((m->ptr = mmap(NULL, m->size, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, (off_t) 0)) == MAP_FAILED) { @@ -130,41 +138,44 @@ static int privatemem_create(pa_shm *m, size_t size) { return 0; } -int pa_shm_create_rw(pa_shm *m, pa_mem_type_t type, size_t size, mode_t mode) { -#ifdef HAVE_SHM_OPEN +static int sharedmem_create(pa_shm *m, pa_mem_type_t type, size_t size, mode_t mode) { +#if defined(HAVE_SHM_OPEN) || defined(HAVE_MEMFD) char fn[32]; int fd = -1; struct shm_marker *marker; -#endif - - pa_assert(m); - pa_assert(size > 0); - pa_assert(size <= MAX_SHM_SIZE); - pa_assert(!(mode & ~0777)); - pa_assert(mode >= 0600); + bool do_unlink = false; /* Each time we create a new SHM area, let's first drop all stale * ones */ pa_shm_cleanup(); - /* Round up to make it page aligned */ - size = PA_PAGE_ALIGN(size); - - m->type = type; - - if (type == PA_MEM_TYPE_PRIVATE) - return privatemem_create(m, size); + pa_random(&m->id, sizeof(m->id)); + switch (type) { #ifdef HAVE_SHM_OPEN - pa_random(&m->id, sizeof(m->id)); - segment_name(fn, sizeof(fn), m->id); + case PA_MEM_TYPE_SHARED_POSIX: + segment_name(fn, sizeof(fn), m->id); + fd = shm_open(fn, O_RDWR|O_CREAT|O_EXCL, mode); + do_unlink = true; + break; +#endif +#ifdef HAVE_MEMFD + case PA_MEM_TYPE_SHARED_MEMFD: + fd = memfd_create("pulseaudio", MFD_ALLOW_SEALING); + break; +#endif + default: + goto fail; + } - if ((fd = shm_open(fn, O_RDWR|O_CREAT|O_EXCL, mode)) < 0) { - pa_log("shm_open() failed: %s", pa_cstrerror(errno)); + if (fd < 0) { + pa_log("%s open() failed: %s", pa_mem_type_to_string(type), pa_cstrerror(errno)); goto fail; } - m->size = size + SHM_MARKER_SIZE; + m->type = type; + m->size = size + shm_marker_size(m); + m->do_unlink = do_unlink; if (ftruncate(fd, (off_t) m->size) < 0) { pa_log("ftruncate() failed: %s", pa_cstrerror(errno)); @@ -180,32 +191,54 @@ int pa_shm_create_rw(pa_shm *m, pa_mem_type_t type, size_t size, mode_t mode) { goto fail; } - /* We store our PID at the end of the shm block, so that we - * can check for dead shm segments later */ - marker = (struct shm_marker*) ((uint8_t*) m->ptr + m->size - SHM_MARKER_SIZE); - pa_atomic_store(&marker->pid, (int) getpid()); - pa_atomic_store(&marker->marker, SHM_MARKER); + if (type == PA_MEM_TYPE_SHARED_POSIX) { + /* We store our PID at the end of the shm block, so that we + * can check for dead shm segments later */ + marker = (struct shm_marker*) ((uint8_t*) m->ptr + m->size - shm_marker_size(m)); + pa_atomic_store(&marker->pid, (int) getpid()); + pa_atomic_store(&marker->marker, SHM_MARKER); + } - pa_assert_se(pa_close(fd) == 0); - m->do_unlink = true; -#else - goto fail; -#endif + /* For memfds, we keep the fd open until we pass it + * to the other PA endpoint over unix domain socket. */ + if (type == PA_MEM_TYPE_SHARED_MEMFD) + m->fd = fd; + else { + pa_assert_se(pa_close(fd) == 0); + m->fd = -1; + } return 0; fail: - -#ifdef HAVE_SHM_OPEN if (fd >= 0) { - shm_unlink(fn); +#ifdef HAVE_SHM_OPEN + if (type == PA_MEM_TYPE_SHARED_POSIX) + shm_unlink(fn); +#endif pa_close(fd); } -#endif +#endif /* defined(HAVE_SHM_OPEN) || defined(HAVE_MEMFD) */ return -1; } +int pa_shm_create_rw(pa_shm *m, pa_mem_type_t type, size_t size, mode_t mode) { + pa_assert(m); + pa_assert(size > 0); + pa_assert(size <= MAX_SHM_SIZE); + pa_assert(!(mode & ~0777)); + pa_assert(mode >= 0600); + + /* Round up to make it page aligned */ + size = PA_PAGE_ALIGN(size); + + if (type == PA_MEM_TYPE_PRIVATE) + return privatemem_create(m, size); + + return sharedmem_create(m, type, size, mode); +} + static void privatemem_free(pa_shm *m) { pa_assert(m); pa_assert(m->ptr); @@ -235,19 +268,26 @@ void pa_shm_free(pa_shm *m) { goto finish; } -#ifdef HAVE_SHM_OPEN +#if defined(HAVE_SHM_OPEN) || defined(HAVE_MEMFD) if (munmap(m->ptr, PA_PAGE_ALIGN(m->size)) < 0) pa_log("munmap() failed: %s", pa_cstrerror(errno)); - if (m->do_unlink) { +#ifdef HAVE_SHM_OPEN + if (m->type == PA_MEM_TYPE_SHARED_POSIX && m->do_unlink) { char fn[32]; segment_name(fn, sizeof(fn), m->id); if (shm_unlink(fn) < 0) pa_log(" shm_unlink(%s) failed: %s", fn, pa_cstrerror(errno)); } +#endif +#ifdef HAVE_MEMFD + if (m->type == PA_MEM_TYPE_SHARED_MEMFD && m->fd != -1) + pa_assert_se(pa_close(m->fd) == 0); +#endif + #else - /* We shouldn't be here without shm support */ + /* We shouldn't be here without shm or memfd support */ pa_assert_not_reached(); #endif @@ -301,9 +341,8 @@ void pa_shm_punch(pa_shm *m, size_t offset, size_t size) { #endif } -#ifdef HAVE_SHM_OPEN - -static int shm_attach(pa_shm *m, unsigned id, bool writable, bool for_cleanup) { +static int shm_attach(pa_shm *m, pa_mem_type_t type, unsigned id, int memfd_fd, bool writable, bool for_cleanup) { +#if defined(HAVE_SHM_OPEN) || defined(HAVE_MEMFD) char fn[32]; int fd = -1; int prot; @@ -311,11 +350,25 @@ static int shm_attach(pa_shm *m, unsigned id, bool writable, bool for_cleanup) { pa_assert(m); - segment_name(fn, sizeof(fn), m->id = id); - - if ((fd = shm_open(fn, writable ? O_RDWR : O_RDONLY, 0)) < 0) { - if ((errno != EACCES && errno != ENOENT) || !for_cleanup) - pa_log("shm_open() failed: %s", pa_cstrerror(errno)); + switch (type) { +#ifdef HAVE_SHM_OPEN + case PA_MEM_TYPE_SHARED_POSIX: + pa_assert(memfd_fd == -1); + segment_name(fn, sizeof(fn), id); + if ((fd = shm_open(fn, writable ? O_RDWR : O_RDONLY, 0)) < 0) { + if ((errno != EACCES && errno != ENOENT) || !for_cleanup) + pa_log("shm_open() failed: %s", pa_cstrerror(errno)); + goto fail; + } + break; +#endif +#ifdef HAVE_MEMFD + case PA_MEM_TYPE_SHARED_MEMFD: + pa_assert(memfd_fd != -1); + fd = memfd_fd; + break; +#endif + default: goto fail; } @@ -325,46 +378,54 @@ static int shm_attach(pa_shm *m, unsigned id, bool writable, bool for_cleanup) { } if (st.st_size <= 0 || - st.st_size > (off_t) (MAX_SHM_SIZE+SHM_MARKER_SIZE) || + st.st_size > (off_t) MAX_SHM_SIZE + (off_t) shm_marker_size(m) || PA_ALIGN((size_t) st.st_size) != (size_t) st.st_size) { pa_log("Invalid shared memory segment size"); goto fail; } - m->size = (size_t) st.st_size; - prot = writable ? PROT_READ | PROT_WRITE : PROT_READ; - if ((m->ptr = mmap(NULL, PA_PAGE_ALIGN(m->size), prot, MAP_SHARED, fd, (off_t) 0)) == MAP_FAILED) { + if ((m->ptr = mmap(NULL, PA_PAGE_ALIGN(st.st_size), prot, MAP_SHARED, fd, (off_t) 0)) == MAP_FAILED) { pa_log("mmap() failed: %s", pa_cstrerror(errno)); goto fail; } - m->do_unlink = false; - m->type = PA_MEM_TYPE_SHARED_POSIX; + /* In case of attaching to memfd areas, _the caller_ maintains + * ownership of the passed fd and has the sole responsibility + * of closing it down.. For other types, we're the code path + * which created the fd in the first place and we're thus the + * ones responsible for closing it down */ + if (type != PA_MEM_TYPE_SHARED_MEMFD) + pa_assert_se(pa_close(fd) == 0); - pa_assert_se(pa_close(fd) == 0); + m->type = type; + m->id = id; + m->size = (size_t) st.st_size; + m->do_unlink = false; + m->fd = -1; return 0; fail: - if (fd >= 0) + /* In case of memfds, caller maintains fd ownership */ + if (fd >= 0 && type != PA_MEM_TYPE_SHARED_MEMFD) pa_close(fd); +#endif /* defined(HAVE_SHM_OPEN) || defined(HAVE_MEMFD) */ + return -1; } -int pa_shm_attach(pa_shm *m, unsigned id, bool writable) { - return shm_attach(m, id, writable, false); +/* Caller owns passed @memfd_fd and must close it down when appropriate. */ +static int NEW_API_pa_shm_attach(pa_shm *m, pa_mem_type_t type, unsigned id, int memfd_fd, bool writable) { + return shm_attach(m, type, id, memfd_fd, writable, false); } -#else /* HAVE_SHM_OPEN */ - +/* Compatibility version until the new API is used in external sources */ int pa_shm_attach(pa_shm *m, unsigned id, bool writable) { - return -1; + return NEW_API_pa_shm_attach(m, PA_MEM_TYPE_SHARED_POSIX, id, -1, writable); } -#endif /* HAVE_SHM_OPEN */ - int pa_shm_cleanup(void) { #ifdef HAVE_SHM_OPEN @@ -394,15 +455,15 @@ int pa_shm_cleanup(void) { if (pa_atou(de->d_name + SHM_ID_LEN, &id) < 0) continue; - if (shm_attach(&seg, id, false, true) < 0) + if (shm_attach(&seg, PA_MEM_TYPE_SHARED_POSIX, id, -1, false, true) < 0) continue; - if (seg.size < SHM_MARKER_SIZE) { + if (seg.size < shm_marker_size(&seg)) { pa_shm_free(&seg); continue; } - m = (struct shm_marker*) ((uint8_t*) seg.ptr + seg.size - SHM_MARKER_SIZE); + m = (struct shm_marker*) ((uint8_t*) seg.ptr + seg.size - shm_marker_size(&seg)); if (pa_atomic_load(&m->marker) != SHM_MARKER) { pa_shm_free(&seg); diff --git a/src/pulsecore/shm.h b/src/pulsecore/shm.h index f0fda9151..10df9c1f8 100644 --- a/src/pulsecore/shm.h +++ b/src/pulsecore/shm.h @@ -30,7 +30,19 @@ typedef struct pa_shm { unsigned id; void *ptr; size_t size; + + /* Only for type = PA_MEM_TYPE_SHARED_POSIX */ bool do_unlink:1; + + /* Only for type = PA_MEM_TYPE_SHARED_MEMFD + * + * To avoid fd leaks, we keep this fd open only until we pass it + * to the other PA endpoint over unix domain socket. + * + * When we don't have ownership for the memfd fd in question (e.g. + * pa_shm_attach()), or the file descriptor has now been closed, + * this is set to -1. */ + int fd; } pa_shm; int pa_shm_create_rw(pa_shm *m, pa_mem_type_t type, size_t size, mode_t mode); |