// SPDX-License-Identifier: GPL-2.0-or-later // // memfdtest -- two processes map the same memfd, one writes, the other sees it. // // That sentence is docs/plan/foundations.md's own acceptance criterion for // item 2, and it is the check a permissive implementation cannot pass: an // mmap that quietly returns private zeroed pages satisfies every return code // here and fails the one assertion that matters, because the reader is a // *different process* and the pages would not be the same pages. // // The lifecycle is the one a Wayland client performs, in order: // // fd = memfd_create("buf", 0); // ftruncate(fd, size); // p = mmap(0, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); // // and the reason it had to wait for SCM_RIGHTS is that the *other* process // gets the same object by being handed the descriptor, not by naming it. #include "core/types.h" #include "uapi/user.h" #include "uapi/fcntl.h" #include "uapi/stat.h" #include "uapi/posix.h" #include "net/socket.h" static int failures; static void puts_(const char *s) { int n = 0; while (s[n]) n++; write(1, (void *)s, n); } // mmap returns -errno in the pointer on this system's own ABI -- musl turns // that into MAP_FAILED, but a first-party binary sees the raw value. Anything // in the top page is an error, not an address. static int mmap_failed(void *p) { return (uint64)p >= (uint64)-4096; } static void check(const char *what, int ok) { puts_(ok ? "[ok] " : "[FAIL] "); puts_(what); puts_("\n"); if (!ok) failures++; } #define SIZE 8192 static void memset_(void *d, int c, int n) { char *p = d; while (n-- > 0) *p++ = (char)c; } static void memmove_(void *d, const void *s, int n) { char *a = d; const char *b = s; while (n-- > 0) *a++ = *b++; } // Send one descriptor over a socket, the way a Wayland client hands its buffer // to the compositor. Same shape as cmd/socktest's helper; kept local so this // test stands on its own. static int sendfd(int sock, int fd) { struct msghdr m; struct iovec iv; char cbuf[CMSG_SPACE(sizeof(int))]; struct cmsghdr *c; char tag = 'x'; memset_(&m, 0, sizeof(m)); memset_(cbuf, 0, sizeof(cbuf)); iv.iov_base = &tag; iv.iov_len = 1; m.msg_iov = &iv; m.msg_iovlen = 1; m.msg_control = cbuf; m.msg_controllen = sizeof(cbuf); c = (struct cmsghdr *)cbuf; c->cmsg_len = CMSG_LEN(sizeof(int)); c->cmsg_level = SOL_SOCKET; c->cmsg_type = SCM_RIGHTS; memmove_(CMSG_DATA(c), &fd, sizeof(int)); return sendmsg(sock, &m, 0); } static int recvfd(int sock) { struct msghdr m; struct iovec iv; char cbuf[CMSG_SPACE(sizeof(int))]; struct cmsghdr *c; char tag = 0; int got = -1; memset_(&m, 0, sizeof(m)); memset_(cbuf, 0, sizeof(cbuf)); iv.iov_base = &tag; iv.iov_len = 1; m.msg_iov = &iv; m.msg_iovlen = 1; m.msg_control = cbuf; m.msg_controllen = CMSG_LEN(sizeof(int)); if (recvmsg(sock, &m, 0) < 0) return -1; c = CMSG_FIRSTHDR(&m); if (c && c->cmsg_level == SOL_SOCKET && c->cmsg_type == SCM_RIGHTS) memmove_(&got, CMSG_DATA(c), sizeof(int)); return got; } int main(void) { int fd, pfd[2], st = 0; char *p; struct stat sb; puts_("memfdtest: a shareable memory object\n\n"); fd = memfd_create("test-buffer", 0); check("memfd_create returns a descriptor", fd >= 0); if (fd < 0) { puts_("\nmemfdtest: FAILED\n"); return 1; } // A fresh memfd is empty. Mapping it before it has a size must fail -- // there are no pages yet, and handing back anonymous ones would be the // lie this whole object exists to avoid. check("a new memfd has size 0", fstat(fd, &sb) == 0 && sb.size == 0); check("mapping a memfd that has no size yet is refused", mmap_failed(mmap(0, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0))); check("ftruncate gives it a size", ftruncate(fd, SIZE) == 0); check("and fstat reports it back", fstat(fd, &sb) == 0 && sb.size == SIZE); p = mmap(0, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); check("mmap(MAP_SHARED) returns a mapping", !mmap_failed(p)); if (mmap_failed(p)) { puts_("\nmemfdtest: FAILED\n"); return 1; } // Readable and writable by this process at all, before anything about // sharing is claimed. p[0] = 'A'; p[SIZE - 1] = 'Z'; check("the mapping is writable and reads back", p[0] == 'A' && p[SIZE - 1] == 'Z'); // MAP_PRIVATE is refused rather than silently copied. check("MAP_PRIVATE on a memfd is refused", mmap_failed(mmap(0, SIZE, PROT_READ, MAP_PRIVATE, fd, 0))); // --- the assertion this test exists for ------------------------------ // // fork(), then have the child map the *same* descriptor independently and // write through its own mapping. The parent must see it through its own. // A private mapping passes everything above and fails here. if (pipe(pfd) < 0) { puts_("[FAIL] pipe\n"); return 1; } int pid = fork(); if (pid == 0) { char *q = mmap(0, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); char ok = mmap_failed(q) ? 0 : 1; if (ok) { // Prove the child sees what the parent wrote before the fork... if (q[0] != 'A' || q[SIZE - 1] != 'Z') ok = 0; // ...then write something of its own, in a different place. q[100] = 'C'; q[SIZE / 2] = 'M'; } write(pfd[1], &ok, 1); close(pfd[1]); exit(); } close(pfd[1]); { char ok = 0; read(pfd[0], &ok, 1); close(pfd[0]); check("a second process maps the same fd and sees the parent's bytes", ok == 1); } wait(); (void)st; check("and the parent sees what the child wrote", p[100] == 'C' && p[SIZE / 2] == 'M'); check("without disturbing anything else in the mapping", p[0] == 'A' && p[SIZE - 1] == 'Z' && p[101] == 0); // --- read()/write() reach the same bytes ------------------------------- { char buf[4] = { 0, 0, 0, 0 }; lseek(fd, 100, 0 /* SEEK_SET */); check("read() sees what mmap wrote", read(fd, buf, 1) == 1 && buf[0] == 'C'); lseek(fd, 200, 0 /* SEEK_SET */); buf[0] = 'W'; check("write() is visible through the mapping", write(fd, buf, 1) == 1 && p[200] == 'W'); } // --- shrinking while mapped is refused, not faked ---------------------- check("shrinking a mapped memfd is refused", ftruncate(fd, 4096) < 0); check("growing it is allowed", ftruncate(fd, SIZE * 2) == 0); // --- the combination this was all for --------------------------------- // // A forked child inherits the descriptor, which proves the *pages* are // shared but not that the *object* travels. The Wayland case is an // unrelated process that is handed the descriptor over a socket and has // no other way to name it. So: send the memfd through a socketpair, and // have the receiver close every inherited copy before it maps. { int sv[2], cpid, rp[2]; char ok = 0; if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) < 0 || pipe(rp) < 0) { check("socketpair for the descriptor-passing check", 0); } else if ((cpid = fork()) == 0) { int got; char *q; close(sv[0]); close(rp[0]); got = recvfd(sv[1]); // Close the inherited copy, so the only route to the object is // the descriptor that arrived over the socket. close(fd); q = (got >= 0) ? mmap(0, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, got, 0) : (char *)-1; ok = (got >= 0 && !mmap_failed(q) && q[100] == 'C') ? 1 : 0; if (ok) q[300] = 'S'; write(rp[1], &ok, 1); close(rp[1]); exit(); } else { close(sv[1]); close(rp[1]); sendfd(sv[0], fd); read(rp[0], &ok, 1); close(rp[0]); close(sv[0]); wait(); check("a memfd passed over a socket maps in the receiver", ok == 1); check("and the sender sees what the receiver wrote", p[300] == 'S'); } } close(fd); puts_("\n"); puts_(failures ? "memfdtest: FAILED\n" : "memfdtest: all checks passed\n"); return failures ? 1 : 0; }