shm: Add mmap+memmove fallback if mremap() does not exist
authorAlex Richardson <Alexander.Richardson@cl.cam.ac.uk>
Mon, 15 Mar 2021 22:25:52 +0000 (22:25 +0000)
committerAlexander Richardson <alexander.richardson@cl.cam.ac.uk>
Fri, 10 Sep 2021 11:35:54 +0000 (11:35 +0000)
Some operating systems (e.g. FreeBSD) do not implement mremap.
In that case we can grow the mapping by trying to map adjacent memory.
If that fails we can fall back to creating a new larger mapping and
moving the old memory contents there.

Co-authored-by: Koop Mast <kwm@rainbow-runner.nl>
Signed-off-by: Alex Richardson <Alexander.Richardson@cl.cam.ac.uk>
meson.build
src/wayland-os.c
src/wayland-os.h
src/wayland-shm.c

index aeb171e..239bff7 100644 (file)
@@ -36,6 +36,7 @@ have_funcs = [
        'posix_fallocate',
        'prctl',
        'memfd_create',
+       'mremap',
        'strndup',
 ]
 foreach f: have_funcs
index 46db267..44d4e72 100644 (file)
@@ -32,7 +32,9 @@
 #include <unistd.h>
 #include <fcntl.h>
 #include <errno.h>
+#include <string.h>
 #include <sys/epoll.h>
+#include <sys/mman.h>
 #include <sys/un.h>
 #ifdef HAVE_SYS_UCRED_H
 #include <sys/ucred.h>
@@ -210,3 +212,31 @@ wl_os_accept_cloexec(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
        fd = accept(sockfd, addr, addrlen);
        return set_cloexec_or_close(fd);
 }
+
+/*
+ * Fallback function for operating systems that don't implement
+ * mremap(MREMAP_MAYMOVE).
+ */
+void *
+wl_os_mremap_maymove(int fd, void *old_data, ssize_t *old_size,
+                    ssize_t new_size, int prot, int flags)
+{
+       void *result;
+       /*
+        * We could try mapping a new block immediately after the current one
+        * with MAP_FIXED, however that is not guaranteed to work and breaks
+        * on CHERI-enabled architectures since the data pointer will still
+        * have the bounds of the previous allocation. As this is not a
+        * performance-critical path, we always map a new region and copy the
+        * old data to the new region.
+        */
+       result = mmap(NULL, new_size, prot, flags, fd, 0);
+       if (result != MAP_FAILED) {
+               /* Copy the data over and unmap the old mapping. */
+               memcpy(result, old_data, *old_size);
+               if (munmap(old_data, *old_size) == 0) {
+                       *old_size = 0; /* successfully unmapped old data. */
+               }
+       }
+       return result;
+}
index ccd76ba..068fd2f 100644 (file)
@@ -47,6 +47,10 @@ wl_os_epoll_create_cloexec(void);
 int
 wl_os_accept_cloexec(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
 
+void *
+wl_os_mremap_maymove(int fd, void *old_data, ssize_t *old_size,
+                    ssize_t new_size, int prot, int flags);
+
 
 /*
  * The following are for wayland-os.c and the unit tests.
index 671549c..5ff794a 100644 (file)
@@ -45,6 +45,7 @@
 #include <errno.h>
 #include <fcntl.h>
 
+#include "wayland-os.h"
 #include "wayland-util.h"
 #include "wayland-private.h"
 #include "wayland-server.h"
@@ -61,8 +62,12 @@ struct wl_shm_pool {
        int internal_refcount;
        int external_refcount;
        char *data;
-       int32_t size;
-       int32_t new_size;
+       ssize_t size;
+       ssize_t new_size;
+       /* The following three fields are needed for mremap() emulation. */
+       int mmap_fd;
+       int mmap_flags;
+       int mmap_prot;
        bool sigbus_is_impossible;
 };
 
@@ -90,6 +95,26 @@ struct wl_shm_sigbus_data {
        int fallback_mapping_used;
 };
 
+static void *
+shm_pool_grow_mapping(struct wl_shm_pool *pool)
+{
+       void *data;
+
+#ifdef MREMAP_MAYMOVE
+       data = mremap(pool->data, pool->size, pool->new_size, MREMAP_MAYMOVE);
+#else
+       data = wl_os_mremap_maymove(pool->mmap_fd, pool->data, &pool->size,
+                                   pool->new_size, pool->mmap_prot,
+                                   pool->mmap_flags);
+       if (pool->size != 0) {
+               wl_resource_post_error(pool->resource,
+                                      WL_SHM_ERROR_INVALID_FD,
+                                      "leaked old mapping");
+       }
+#endif
+       return data;
+}
+
 static void
 shm_pool_finish_resize(struct wl_shm_pool *pool)
 {
@@ -98,7 +123,7 @@ shm_pool_finish_resize(struct wl_shm_pool *pool)
        if (pool->size == pool->new_size)
                return;
 
-       data = mremap(pool->data, pool->size, pool->new_size, MREMAP_MAYMOVE);
+       data = shm_pool_grow_mapping(pool);
        if (data == MAP_FAILED) {
                wl_resource_post_error(pool->resource,
                                       WL_SHM_ERROR_INVALID_FD,
@@ -127,6 +152,7 @@ shm_pool_unref(struct wl_shm_pool *pool, bool external)
                return;
 
        munmap(pool->data, pool->size);
+       close(pool->mmap_fd);
        free(pool);
 }
 
@@ -274,6 +300,8 @@ shm_create_pool(struct wl_client *client, struct wl_resource *resource,
 {
        struct wl_shm_pool *pool;
        int seals;
+       int prot;
+       int flags;
 
        if (size <= 0) {
                wl_resource_post_error(resource,
@@ -301,17 +329,19 @@ shm_create_pool(struct wl_client *client, struct wl_resource *resource,
        pool->external_refcount = 0;
        pool->size = size;
        pool->new_size = size;
-       pool->data = mmap(NULL, size,
-                         PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+       prot = PROT_READ | PROT_WRITE;
+       flags = MAP_SHARED;
+       pool->data = mmap(NULL, size, prot, flags, fd, 0);
        if (pool->data == MAP_FAILED) {
-               wl_resource_post_error(resource,
-                                      WL_SHM_ERROR_INVALID_FD,
+               wl_resource_post_error(resource, WL_SHM_ERROR_INVALID_FD,
                                       "failed mmap fd %d: %s", fd,
                                       strerror(errno));
                goto err_free;
        }
-       close(fd);
-
+       /* We may need to keep the fd, prot and flags to emulate mremap(). */
+       pool->mmap_fd = fd;
+       pool->mmap_prot = prot;
+       pool->mmap_flags = flags;
        pool->resource =
                wl_resource_create(client, &wl_shm_pool_interface, 1, id);
        if (!pool->resource) {