diff --git a/meson.build b/meson.build index 3d5aa5fb..9fa787b9 100644 --- a/meson.build +++ b/meson.build @@ -78,7 +78,7 @@ elif cc.has_header_symbol('sys/mkdev.h', 'major') endif optional_libc_funcs = [ - 'mkostemp', 'strchrnul', 'initgroups', 'posix_fallocate' + 'mkostemp', 'strchrnul', 'initgroups', 'posix_fallocate', 'memfd_create' ] foreach func : optional_libc_funcs if cc.has_function(func) diff --git a/shared/os-compatibility.c b/shared/os-compatibility.c index e19fb61b..aeea780f 100644 --- a/shared/os-compatibility.c +++ b/shared/os-compatibility.c @@ -34,6 +34,10 @@ #include #include +#ifdef HAVE_MEMFD_CREATE +#include +#endif + #include "os-compatibility.h" int @@ -147,6 +151,13 @@ create_tmpfile_cloexec(char *tmpname) * given size. If disk space is insufficient, errno is set to ENOSPC. * If posix_fallocate() is not supported, program may receive * SIGBUS on accessing mmap()'ed file contents instead. + * + * If the C library implements memfd_create(), it is used to create the + * file purely in memory, without any backing file name on the file + * system, and then sealing off the possibility of shrinking it. This + * can then be checked before accessing mmap()'ed file contents, to + * make sure SIGBUS can't happen. It also avoids requiring + * XDG_RUNTIME_DIR. */ int os_create_anonymous_file(off_t size) @@ -157,25 +168,39 @@ os_create_anonymous_file(off_t size) int fd; int ret; - path = getenv("XDG_RUNTIME_DIR"); - if (!path) { - errno = ENOENT; - return -1; - } +#ifdef HAVE_MEMFD_CREATE + fd = memfd_create("weston-shared", MFD_CLOEXEC | MFD_ALLOW_SEALING); + if (fd >= 0) { + /* We can add this seal before calling posix_fallocate(), as + * the file is currently zero-sized anyway. + * + * There is also no need to check for the return value, we + * couldn't do anything with it anyway. + */ + fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK); + } else +#endif + { + path = getenv("XDG_RUNTIME_DIR"); + if (!path) { + errno = ENOENT; + return -1; + } - name = malloc(strlen(path) + sizeof(template)); - if (!name) - return -1; + name = malloc(strlen(path) + sizeof(template)); + if (!name) + return -1; - strcpy(name, path); - strcat(name, template); + strcpy(name, path); + strcat(name, template); - fd = create_tmpfile_cloexec(name); + fd = create_tmpfile_cloexec(name); - free(name); + free(name); - if (fd < 0) - return -1; + if (fd < 0) + return -1; + } #ifdef HAVE_POSIX_FALLOCATE do { diff --git a/tests/bad-buffer-test.c b/tests/bad-buffer-test.c index ee7b2231..b7abda23 100644 --- a/tests/bad-buffer-test.c +++ b/tests/bad-buffer-test.c @@ -26,11 +26,104 @@ #include "config.h" +#include +#include +#include +#include #include #include "shared/os-compatibility.h" #include "weston-test-client-helper.h" +/* These three functions are copied from shared/os-compatibility.c in order to + * behave like older clients, and allow ftruncate() to shrink the file’s size, + * so SIGBUS can still happen. + * + * There is no reason not to use os_create_anonymous_file() otherwise. */ + +#ifndef HAVE_MKOSTEMP +static int +set_cloexec_or_close(int fd) +{ + if (os_fd_set_cloexec(fd) != 0) { + close(fd); + return -1; + } + return fd; +} +#endif + +static int +create_tmpfile_cloexec(char *tmpname) +{ + int fd; + +#ifdef HAVE_MKOSTEMP + fd = mkostemp(tmpname, O_CLOEXEC); + if (fd >= 0) + unlink(tmpname); +#else + fd = mkstemp(tmpname); + if (fd >= 0) { + fd = set_cloexec_or_close(fd); + unlink(tmpname); + } +#endif + + return fd; +} + +static int +create_anonymous_file_without_seals(off_t size) +{ + static const char template[] = "/weston-test-XXXXXX"; + const char *path; + char *name; + int fd; + int ret; + + path = getenv("XDG_RUNTIME_DIR"); + if (!path) { + errno = ENOENT; + return -1; + } + + name = malloc(strlen(path) + sizeof(template)); + if (!name) + return -1; + + strcpy(name, path); + strcat(name, template); + + fd = create_tmpfile_cloexec(name); + + free(name); + + if (fd < 0) + return -1; + +#ifdef HAVE_POSIX_FALLOCATE + do { + ret = posix_fallocate(fd, 0, size); + } while (ret == EINTR); + if (ret != 0) { + close(fd); + errno = ret; + return -1; + } +#else + do { + ret = ftruncate(fd, size); + } while (ret < 0 && errno == EINTR); + if (ret < 0) { + close(fd); + return -1; + } +#endif + + return fd; +} + /* tests, that attempt to crash the compositor on purpose */ static struct wl_buffer * @@ -43,7 +136,7 @@ create_bad_shm_buffer(struct client *client, int width, int height) struct wl_buffer *buffer; int fd; - fd = os_create_anonymous_file(size); + fd = create_anonymous_file_without_seals(size); assert(fd >= 0); pool = wl_shm_create_pool(shm, fd, size);