#include #include #include #include #include #include #include #include #include #include #include #include #include static void pread_all(int fd, unsigned char *buf, size_t len) { size_t done; ssize_t nr; for (done = 0; done < len; done += nr) { nr = pread(fd, buf + done, len - done, done); if (nr < 0) err(1, "pread"); if (nr == 0) errx(1, "unexpected EOF at %zu", done); } } static uint64_t fnv1a64(const unsigned char *buf, size_t len) { uint64_t hash; hash = UINT64_C(14695981039346656037); for (size_t i = 0; i < len; i++) { hash ^= buf[i]; hash *= UINT64_C(1099511628211); } return (hash); } static uint32_t xorshift32(uint32_t *state) { uint32_t value; value = *state; value ^= value << 13; value ^= value >> 17; value ^= value << 5; *state = value; return (value); } static void verify_random_faults(const unsigned char *map, const unsigned char *expected, size_t size, size_t pagesize) { size_t *order; size_t npages, page, span; uint32_t state; npages = (size + pagesize - 1) / pagesize; order = calloc(npages, sizeof(*order)); if (order == NULL) err(1, "calloc page order"); for (size_t i = 0; i < npages; i++) order[i] = i; state = UINT32_C(0x5eed22); for (size_t i = npages; i > 1; i--) { size_t other; other = xorshift32(&state) % i; page = order[i - 1]; order[i - 1] = order[other]; order[other] = page; } for (size_t i = 0; i < npages; i++) { page = order[i] * pagesize; span = size - page < pagesize ? size - page : pagesize; if (memcmp(map + page, expected + page, span) != 0) errx(1, "random page mismatch at offset %zu", page); } free(order); } static void verify_sigbus(const unsigned char *map, size_t eof_page) { pid_t child; int status; child = fork(); if (child < 0) err(1, "fork"); if (child == 0) { volatile unsigned char value; value = map[eof_page]; _exit(value == 0 ? 0 : 1); } if (waitpid(child, &status, 0) != child) err(1, "waitpid"); if (!WIFSIGNALED(status) || WTERMSIG(status) != SIGBUS) errx(1, "full page beyond EOF did not raise SIGBUS (status=%#x)", status); } int main(int argc, char **argv) { unsigned char *expected, *map, *private_map, byte; struct stat sb; size_t eof_page, map_len, pagesize, private_offset; uint64_t hash; int fd, rwfd, shared_errno; if (argc != 2) errx(2, "usage: mmap_fault file"); pagesize = (size_t)getpagesize(); fd = open(argv[1], O_RDONLY); if (fd < 0) err(1, "open %s", argv[1]); if (fstat(fd, &sb) != 0) err(1, "fstat %s", argv[1]); if (sb.st_size <= (off_t)(pagesize * 3)) errx(1, "fixture must exceed three VM pages"); if ((uintmax_t)sb.st_size > SIZE_MAX - pagesize * 2) errx(1, "fixture is too large"); eof_page = ((size_t)sb.st_size + pagesize - 1) / pagesize * pagesize; map_len = eof_page + pagesize; expected = malloc((size_t)sb.st_size); if (expected == NULL) err(1, "malloc expected data"); pread_all(fd, expected, (size_t)sb.st_size); map = mmap(NULL, map_len, PROT_READ, MAP_PRIVATE, fd, 0); if (map == MAP_FAILED) err(1, "mmap read-only private"); if (madvise(map, map_len, MADV_DONTNEED) != 0) err(1, "madvise MADV_DONTNEED"); verify_random_faults(map, expected, (size_t)sb.st_size, pagesize); if (memcmp(map, expected, (size_t)sb.st_size) != 0) errx(1, "sequential mmap data differs from pread"); for (size_t i = (size_t)sb.st_size; i < eof_page; i++) { if (map[i] != 0) errx(1, "non-zero byte in partial EOF page at %zu", i); } verify_sigbus(map, eof_page); hash = fnv1a64(map, (size_t)sb.st_size); errno = 0; private_map = mmap(NULL, eof_page, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); shared_errno = errno; if (private_map != MAP_FAILED) { munmap(private_map, eof_page); errx(1, "writable MAP_SHARED unexpectedly succeeded"); } if (shared_errno != EACCES) errx(1, "writable MAP_SHARED returned %s, expected Permission denied", strerror(shared_errno)); private_map = mmap(NULL, eof_page, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); if (private_map == MAP_FAILED) err(1, "mmap writable private"); private_offset = (size_t)sb.st_size / 2; byte = expected[private_offset]; private_map[private_offset] ^= UINT8_C(0xff); if (private_map[private_offset] == byte) errx(1, "private mapping did not change"); if (pread(fd, &byte, 1, private_offset) != 1) err(1, "pread private verification"); if (byte != expected[private_offset]) errx(1, "MAP_PRIVATE modified the EROFS file"); errno = 0; rwfd = open(argv[1], O_RDWR); if (rwfd >= 0) { close(rwfd); errx(1, "O_RDWR unexpectedly succeeded"); } if (errno != EROFS) errx(1, "O_RDWR returned %s, expected Read-only file system", strerror(errno)); if (munmap(private_map, eof_page) != 0 || munmap(map, map_len) != 0) err(1, "munmap"); free(expected); if (close(fd) != 0) err(1, "close"); printf("PASS size=%jd pages=%zu fnv1a64=%016" PRIx64 " random-faults=%zu eof-zero=PASS sigbus=PASS private-cow=PASS\n", (intmax_t)sb.st_size, eof_page / pagesize, hash, ((size_t)sb.st_size + pagesize - 1) / pagesize); return (0); }