From 7f87f0cc5979e6cf6171e308d65006cc7ce5b30a Mon Sep 17 00:00:00 2001 From: Gavin Hayes Date: Wed, 1 Feb 2023 14:11:52 -0500 Subject: [PATCH] zipos mmap disallow MAP_SHARED, propgate original errno, add more tests --- libc/zipos/mmap.c | 35 ++++++++++++---- test/libc/runtime/mmap_test.c | 76 ++++++++++++++++++++++++++++++++--- 2 files changed, 97 insertions(+), 14 deletions(-) diff --git a/libc/zipos/mmap.c b/libc/zipos/mmap.c index c94077867..67025f4f2 100644 --- a/libc/zipos/mmap.c +++ b/libc/zipos/mmap.c @@ -19,6 +19,7 @@ #include "libc/calls/calls.h" #include "libc/calls/struct/iovec.h" #include "libc/dce.h" +#include "libc/errno.h" #include "libc/intrin/likely.h" #include "libc/intrin/strace.internal.h" #include "libc/sysv/consts/map.h" @@ -29,14 +30,32 @@ #define IP(X) (intptr_t)(X) #define VIP(X) (void *)IP(X) +/** + * Map zipos file into memory. See mmap. + * + * @param addr should be 0 or a compatible address + * @param size must be >0 and will be rounded up to FRAMESIZE + * automatically. + * @param prot can have PROT_READ/PROT_WRITE/PROT_EXEC/PROT_NONE/etc. + * @param flags cannot have `MAP_SHARED` or `MAP_ANONYMOUS`, there is + * no actual file backing for zipos files. `MAP_SHARED` could be + * simulated for non-writable mappings, but that would require + * tracking zipos mappings to prevent making it PROT_WRITE. + * @param h is a zip store object + * @param off specifies absolute byte index of h's file for mapping, + * it does not need to be 64kb aligned. + * @return virtual base address of new mapping, or MAP_FAILED w/ errno + */ void *__zipos_mmap(void *addr, size_t size, int prot, int flags, struct ZiposHandle *h, int64_t off) { if (VERY_UNLIKELY(!!(flags & MAP_ANONYMOUS))) { - STRACE("fd anonymous mismatch"); + STRACE("MAP_ANONYMOUS zipos mismatch"); return VIP(einval()); } - if (VERY_UNLIKELY(!(!!(prot & PROT_WRITE) ^ !!(flags & MAP_SHARED)))) { - STRACE("PROT_WRITE and MAP_SHARED on zipos"); + // MAP_SHARED for non-writeable pages could be implemented, but would require + // keeping track of zipos pages to prevent mprotect(addr,len,PROT_WRITE) + if (VERY_UNLIKELY(!!(flags & MAP_SHARED))) { + STRACE("MAP_SHARED on zipos"); return VIP(eacces()); } @@ -46,13 +65,13 @@ void *__zipos_mmap(void *addr, size_t size, int prot, int flags, struct ZiposHan return MAP_FAILED; } const int64_t beforeOffset = __zipos_lseek(h, 0, SEEK_CUR); - if ((beforeOffset == -1) || (__zipos_read(h, &(struct iovec){outAddr, size}, 1, off) == -1)) { + if ((beforeOffset == -1) || (__zipos_read(h, &(struct iovec){outAddr, size}, 1, off) == -1) || + (__zipos_lseek(h, beforeOffset, SEEK_SET) == -1) || + ((prot != tempProt) && (mprotect(outAddr, size, prot) == -1))) { + const int e = errno; munmap(outAddr, size); + errno = e; return MAP_FAILED; } - __zipos_lseek(h, beforeOffset, SEEK_SET); - if(prot != tempProt) { - mprotect(outAddr, size, prot); - } return outAddr; } diff --git a/test/libc/runtime/mmap_test.c b/test/libc/runtime/mmap_test.c index 876a484b7..605b8b92f 100644 --- a/test/libc/runtime/mmap_test.c +++ b/test/libc/runtime/mmap_test.c @@ -221,17 +221,81 @@ TEST(isheap, mallocOffset) { ASSERT_TRUE(_isheap(p + 100000)); } -TEST(mmap, ziposMapFiles) { +static const char *ziposLifePath = "/zip/life.elf"; +TEST(mmap, ziposCannotBeAnonymous) { int fd; - const char *lifepath = "/zip/life.elf"; void *p; - - ASSERT_NE(-1, (fd = open(lifepath, O_RDONLY), "%s", lifepath)); - ASSERT_NE(NULL, (p = mmap(NULL, 0x00010000, PROT_READ, MAP_SHARED, fd, 0))); - EXPECT_STREQN("ELF", ((const char *)p)+1, 3); + ASSERT_NE(-1, (fd = open(ziposLifePath, O_RDONLY), "%s", ziposLifePath)); + EXPECT_SYS(EINVAL, MAP_FAILED, (p = mmap(NULL, 0x00010000, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, fd, 0))); close(fd); } +TEST(mmap, ziposCannotBeShared) { + int fd; + void *p; + ASSERT_NE(-1, (fd = open(ziposLifePath, O_RDONLY), "%s", ziposLifePath)); + EXPECT_SYS(EACCES, MAP_FAILED, (p = mmap(NULL, 0x00010000, PROT_READ, MAP_SHARED, fd, 0))); + close(fd); +} + +//////////////////////////////////////////////////////////////////////////////// +// zipos NON-SHARED READ-ONLY FILE MEMORY + +TEST(mmap, ziposCow) { + int fd; + void *p; + ASSERT_NE(-1, (fd = open(ziposLifePath, O_RDONLY), "%s", ziposLifePath)); + EXPECT_NE(MAP_FAILED, (p = mmap(NULL, 0x00010000, PROT_READ, MAP_PRIVATE, fd, 0))); + EXPECT_STREQN("ELF", ((const char *)p)+1, 3); + EXPECT_NE(-1, munmap(p, 0x00010000)); + EXPECT_NE(-1, close(fd)); +} + +//////////////////////////////////////////////////////////////////////////////// +// zipos NON-SHARED READ-ONLY FILE MEMORY BETWEEN PROCESSES + +TEST(mmap, ziposCowFileMapReadonlyFork) { + int fd, ws; + void *p; + ASSERT_NE(-1, (fd = open(ziposLifePath, O_RDONLY), "%s", ziposLifePath)); + EXPECT_NE(MAP_FAILED, (p = mmap(NULL, 4, PROT_READ, MAP_PRIVATE, fd, 0))); + EXPECT_STREQN("ELF", ((const char *)p)+1, 3); + ASSERT_NE(-1, (ws = xspawn(0))); + if (ws == -2) { + ASSERT_STREQN("ELF", ((const char *)p)+1, 3); + _exit(0); + } + EXPECT_EQ(0, ws); + EXPECT_STREQN("ELF", ((const char *)p)+1, 3); + EXPECT_NE(-1, munmap(p, 6)); + EXPECT_NE(-1, close(fd)); +} + +//////////////////////////////////////////////////////////////////////////////// +// zipos NON-SHARED READ/WRITE FILE MEMORY BETWEEN PROCESSES + +TEST(mmap, ziposCowFileMapFork) { + int fd, ws; + void *p; + char lol[4]; + ASSERT_NE(-1, (fd = open(ziposLifePath, O_RDONLY), "%s", ziposLifePath)); + EXPECT_NE(MAP_FAILED, (p = mmap(NULL, 6, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0))); + memcpy(p, "parnt", 6); + ASSERT_NE(-1, (ws = xspawn(0))); + if (ws == -2) { + ASSERT_STREQN("parnt", p, 5); + strcpy(p, "child"); + ASSERT_STREQN("child", p, 5); + _exit(0); + } + EXPECT_EQ(0, ws); + EXPECT_STREQN("parnt", p, 5); // child changing memory did not change parent + EXPECT_EQ(4, pread(fd, lol, 4, 0)); + EXPECT_STREQN("ELF", &lol[1], 3); // changing memory did not change file + EXPECT_NE(-1, munmap(p, 6)); + EXPECT_NE(-1, close(fd)); +} + //////////////////////////////////////////////////////////////////////////////// // NON-SHARED READ-ONLY FILE MEMORY