[pulseaudio-discuss] [PATCH v2 05/12] pulsecore: SHM: Introduce memfd support

David Henningsson david.henningsson at canonical.com
Fri Feb 12 15:49:23 UTC 2016



On 2016-02-12 01:14, Ahmed S. Darwish wrote:
> Memfd is a simple memory sharing mechanism, added by the systemd/kdbus
> developers, to share pages between processes in an anonymous, no global
> registry needed, no mount-point required, relatively secure, manner.
>
> This patch introduces the necessary building blocks for using memfd
> shared memory transfers in PulseAudio.
>
> Memfd support shall also help us in laying out the necessary (but not
> yet sufficient) groundwork for application sandboxing, protecting PA
> from ts clients, and protecting clients data from each other.
>
> We plan to exclusively use memfds, instead of POSIX SHM, on the way
> forward.
>
> Signed-off-by: Ahmed S. Darwish <darwish.07 at gmail.com>
> ---
>   configure.ac                   |  19 ++++++
>   src/Makefile.am                |   5 ++
>   src/pulsecore/memfd-wrappers.h |  68 +++++++++++++++++++
>   src/pulsecore/shm.c            | 146 ++++++++++++++++++++++++++++-------------
>   src/pulsecore/shm.h            |  16 ++++-
>   5 files changed, 209 insertions(+), 45 deletions(-)
>   create mode 100644 src/pulsecore/memfd-wrappers.h
>
> diff --git a/configure.ac b/configure.ac
> index dcd0110..256a196 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -607,6 +607,23 @@ AC_DEFINE(HAVE_DLADDR, [1], [Have dladdr?])
>
>   AM_ICONV
>
> +#### Linux memfd_create(2) SHM support ####
> +
> +AC_ARG_ENABLE([memfd],
> +    AS_HELP_STRING([--disable-memfd], [Disable Linux memfd shared memory]))
> +
> +AS_IF([test "x$enable_memfd" != "xno"],
> +    AC_CHECK_DECL(SYS_memfd_create, [HAVE_MEMFD=1], [HAVE_MEMFD=0], [#include <sys/syscall.h>]),
> +    [HAVE_MEMFD=0])
> +
> +AS_IF([test "x$enable_memfd" = "xyes" && test "x$HAVE_MEMFD" = "x0"],
> +    [AC_MSG_ERROR([*** Your Linux kernel does not support memfd shared memory.
> +                  *** Use linux v3.17 or higher for such a feature.])])
> +
> +AC_SUBST(HAVE_MEMFD)
> +AM_CONDITIONAL([HAVE_MEMFD], [test "x$HAVE_MEMFD" = x1])
> +AS_IF([test "x$HAVE_MEMFD" = "x1"], AC_DEFINE([HAVE_MEMFD], 1, [Have memfd shared memory.]))
> +
>   #### X11 (optional) ####
>
>   AC_ARG_ENABLE([x11],
> @@ -1539,6 +1556,7 @@ AC_OUTPUT
>
>   # ==========================================================================
>
> +AS_IF([test "x$HAVE_MEMFD" = "x1"], ENABLE_MEMFD=yes, ENABLE_MEMFD=no)
>   AS_IF([test "x$HAVE_X11" = "x1"], ENABLE_X11=yes, ENABLE_X11=no)
>   AS_IF([test "x$HAVE_OSS_OUTPUT" = "x1"], ENABLE_OSS_OUTPUT=yes, ENABLE_OSS_OUTPUT=no)
>   AS_IF([test "x$HAVE_OSS_WRAPPER" = "x1"], ENABLE_OSS_WRAPPER=yes, ENABLE_OSS_WRAPPER=no)
> @@ -1600,6 +1618,7 @@ echo "
>       CPPFLAGS:                      ${CPPFLAGS}
>       LIBS:                          ${LIBS}
>
> +    Enable memfd shared memory:    ${ENABLE_MEMFD}
>       Enable X11:                    ${ENABLE_X11}
>       Enable OSS Output:             ${ENABLE_OSS_OUTPUT}
>       Enable OSS Wrapper:            ${ENABLE_OSS_WRAPPER}
> diff --git a/src/Makefile.am b/src/Makefile.am
> index f3a62fb..be8d3d7 100644
> --- a/src/Makefile.am
> +++ b/src/Makefile.am
> @@ -729,6 +729,11 @@ libpulsecommon_ at PA_MAJORMINOR@_la_CFLAGS = $(AM_CFLAGS) $(LIBJSON_CFLAGS) $(LIBS
>   libpulsecommon_ at PA_MAJORMINOR@_la_LDFLAGS = $(AM_LDFLAGS) $(AM_LIBLDFLAGS) -avoid-version
>   libpulsecommon_ at PA_MAJORMINOR@_la_LIBADD = $(AM_LIBADD) $(LIBJSON_LIBS)  $(LIBWRAP_LIBS) $(WINSOCK_LIBS) $(LTLIBICONV) $(LIBSNDFILE_LIBS)
>
> +if HAVE_MEMFD
> +libpulsecommon_ at PA_MAJORMINOR@_la_SOURCES += \
> +		pulsecore/memfd-wrappers.h
> +endif
> +
>   if HAVE_X11
>   libpulsecommon_ at 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 0000000..3bed9b2
> --- /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 at 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 4c0ecf1..0ca4fb0 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>
> @@ -88,7 +89,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) {
> @@ -100,8 +106,8 @@ static char *segment_name(char *fn, size_t l, unsigned id) {
>   int pa_shm_create(pa_shm *m, pa_mem_type_t type, size_t size, mode_t mode) {
>   #ifdef HAVE_SHM_OPEN
>       char fn[32];
> -    int fd = -1;
>   #endif
> +    int fd = -1;
>       struct shm_marker *marker;
>
>       pa_assert(m);
> @@ -118,16 +124,33 @@ int pa_shm_create(pa_shm *m, pa_mem_type_t type, size_t size, mode_t mode) {
>       /* Round up to make it page aligned */
>       size = PA_PAGE_ALIGN(size);
>
> -#ifdef HAVE_SHM_OPEN
>       pa_random(&m->id, sizeof(m->id));
> -    segment_name(fn, sizeof(fn), m->id);
>
> -    if ((fd = shm_open(fn, O_RDWR|O_CREAT|O_EXCL, mode)) < 0) {
> -        pa_log("shm_open() failed: %s", pa_cstrerror(errno));
> +    switch (type) {
> +#ifdef HAVE_SHM_OPEN
> +    case PA_MEM_TYPE_SHARED_POSIX:
> +        segment_name(fn, sizeof(fn), m->id);
> +        fd = shm_open(fn, O_RDWR|O_CREAT|O_EXCL, mode);
> +        m->per_type.posix_shm.do_unlink = true;
> +        break;
> +#endif
> +#ifdef HAVE_MEMFD
> +    case PA_MEM_TYPE_SHARED_MEMFD:
> +        fd = memfd_create("memfd", MFD_ALLOW_SEALING);
> +        m->per_type.memfd.fd = fd;

These failure paths look a bit hairy. If mmap() fails e g, will this not 
lead to double close of the same fd, first in this function and then 
later in pa_shm_free?

I e, we should not set "m->per_type.memfd.fd = fd" until after all "goto 
fail"s?

> +        break;
> +#endif
> +    default:
>           goto fail;
>       }
>
> -    m->mem.size = size + SHM_MARKER_SIZE;
> +    if (fd < 0) {
> +        pa_log("%s open() failed: %s", pa_mem_type_to_string(type), pa_cstrerror(errno));
> +        goto fail;
> +    }
> +
> +    m->type = type;
> +    m->mem.size = size + shm_marker_size(m);
>
>       if (ftruncate(fd, (off_t) m->mem.size) < 0) {
>           pa_log("ftruncate() failed: %s", pa_cstrerror(errno));
> @@ -143,25 +166,29 @@ int pa_shm_create(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->mem.ptr + m->mem.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->mem.ptr + m->mem.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 the unix domain socket */
> +    if (type != PA_MEM_TYPE_SHARED_MEMFD)
> +        pa_assert_se(pa_close(fd) == 0);
>
>       return 0;
>
>   fail:
>
> -#ifdef HAVE_SHM_OPEN
> +#if defined(HAVE_SHM_OPEN) || defined(HAVE_MEMFD)
>       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
> @@ -178,19 +205,28 @@ void pa_shm_free(pa_shm *m) {
>       pa_assert(m->mem.ptr != MAP_FAILED);
>   #endif
>
> -#ifdef HAVE_SHM_OPEN
> +#if defined(HAVE_SHM_OPEN) || defined(HAVE_MEMFD)
>       if (munmap(m->mem.ptr, PA_PAGE_ALIGN(m->mem.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->per_type.posix_shm.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
> +    /* Memfds are the only SHM type where the file descriptor is not
> +     * directly closed after memory mapping. */
> +    if (m->type == PA_MEM_TYPE_SHARED_MEMFD && m->per_type.memfd.fd != -1)
> +        pa_assert_se(pa_close(m->per_type.memfd.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
>
> @@ -243,9 +279,9 @@ void pa_shm_punch(pa_shm *m, size_t offset, size_t size) {
>   #endif
>   }
>
> -#ifdef HAVE_SHM_OPEN
> +#if defined(HAVE_SHM_OPEN) || defined(HAVE_MEMFD)
>
> -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) {
>       char fn[32];
>       int fd = -1;
>       int prot;
> @@ -253,11 +289,27 @@ 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;
> +        }
> +        m->per_type.posix_shm.do_unlink = false;
> +        break;
> +#endif
> +#ifdef HAVE_MEMFD
> +    case PA_MEM_TYPE_SHARED_MEMFD:
> +        pa_assert(memfd_fd != -1);
> +        fd = memfd_fd;
> +        m->per_type.memfd.fd = -1;      /* -1; check comments above pa_shm_attch() for rationale */
> +        break;
> +#endif
> +    default:
>           goto fail;
>       }
>
> @@ -267,13 +319,15 @@ static int shm_attach(pa_shm *m, unsigned id, bool writable, bool for_cleanup) {
>       }
>
>       if (st.st_size <= 0 ||
> -        st.st_size > (off_t) (PA_MAX_SHM_SIZE+SHM_MARKER_SIZE) ||
> +        st.st_size > (off_t) PA_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->type = type;
>       m->mem.size = (size_t) st.st_size;
> +    m->id = id;
>
>       prot = writable ? PROT_READ | PROT_WRITE : PROT_READ;
>       if ((m->mem.ptr = mmap(NULL, PA_PAGE_ALIGN(m->mem.size), prot, MAP_SHARED, fd, (off_t) 0)) == MAP_FAILED) {
> @@ -281,9 +335,9 @@ static int shm_attach(pa_shm *m, unsigned id, bool writable, bool for_cleanup) {
>           goto fail;
>       }
>
> -    m->do_unlink = false;
> -
> -    pa_assert_se(pa_close(fd) == 0);
> +    /* Do not close file descriptors which are not our own */
> +    if (type != PA_MEM_TYPE_SHARED_MEMFD)
> +        pa_assert_se(pa_close(fd) == 0);

I believe the fd should be closed regardless of type?

Now you seem to leak an fd if type == PA_MEM_TYPE_SHARED_MEMFD?

>
>       return 0;
>
> @@ -293,18 +347,22 @@ fail:
>
>       return -1;
>   }
> +#endif
>
> -int pa_shm_attach(pa_shm *m, unsigned id, bool writable) {
> -    return shm_attach(m, id, writable, false);
> -}
> -
> -#else /* HAVE_SHM_OPEN */
> -
> -int pa_shm_attach(pa_shm *m, unsigned id, bool writable) {
> +/* Note: In case of attaching memfd SHM areas, the caller maintains ownership
> + * of the passed fd and has the responsibility of closing it when appropriate. */
> +static int NEW_API_pa_shm_attach(pa_shm *m, pa_mem_type_t type, unsigned id, int memfd_fd, bool writable) {

It's more common to just do _pa_shm_attach instead of 
NEW_API_pa_shm_attach, but since you remove it again in the next patch, 
it doesn't matter.

> +#if defined(HAVE_SHM_OPEN) || defined(HAVE_MEMFD)
> +    return shm_attach(m, type, id, memfd_fd, writable, false);
> +#else
>       return -1;
> +#endif
>   }
>
> -#endif /* 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 NEW_API_pa_shm_attach(m, PA_MEM_TYPE_SHARED_POSIX, id, -1, writable);
> +}
>
>   int pa_shm_cleanup(void) {
>
> @@ -335,15 +393,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.mem.size < SHM_MARKER_SIZE) {
> +        if (seg.mem.size < shm_marker_size(&seg)) {
>               pa_shm_free(&seg);
>               continue;
>           }
>
> -        m = (struct shm_marker*) ((uint8_t*) seg.mem.ptr + seg.mem.size - SHM_MARKER_SIZE);
> +        m = (struct shm_marker*) ((uint8_t*) seg.mem.ptr + seg.mem.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 887f516..1c8ce83 100644
> --- a/src/pulsecore/shm.h
> +++ b/src/pulsecore/shm.h
> @@ -32,7 +32,21 @@ typedef struct pa_shm {
>       pa_mem mem;             /* Parent; must be first */
>       pa_mem_type_t type;
>       unsigned id;
> -    bool do_unlink:1;
> +
> +    union {
> +        struct {
> +            bool do_unlink;
> +        } posix_shm;
> +        struct {
> +            /* To avoid fd leaks, we keep this fd open only until we pass it
> +             * to the other PA endpoint over the unix domain socket.
> +             *
> +             * When we don't have ownership for the memfd fd in question (e.g.
> +             * memfd segment attachment), or the file descriptor has now been
> +             * closed, this is set to -1. */
> +            int fd;
> +        } memfd;
> +    } per_type;
>   } pa_shm;
>
>   int pa_shm_create(pa_shm *m, pa_mem_type_t type, size_t size, mode_t mode);
>
>
> Regards,
>

-- 
David Henningsson, Canonical Ltd.
https://launchpad.net/~diwic


More information about the pulseaudio-discuss mailing list