/* * Copyright (C) 2017 Red Hat, Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it would be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #define TST_NO_DEFAULT_MAIN #include "tst_test.h" #include "lapi/fallocate.h" #include "lapi/fcntl.h" #include "lapi/memfd.h" #include "lapi/syscalls.h" #include "memfd_create_common.h" int sys_memfd_create(const char *name, unsigned int flags) { return tst_syscall(__NR_memfd_create, name, flags); } int check_fallocate(const char *filename, const int lineno, int fd, int mode, off_t offset, off_t len) { int r; r = fallocate(fd, mode, offset, len); if (r < 0) { tst_brk_(filename, lineno, TFAIL | TERRNO, "fallocate(%d, %d, %ld, %ld) failed", fd, mode, offset, len); } tst_res_(filename, lineno, TPASS, "fallocate(%d, %d, %ld, %ld) succeeded", fd, mode, offset, len); return r; } int check_fallocate_fail(const char *filename, const int lineno, int fd, int mode, off_t offset, off_t len) { int r; r = fallocate(fd, mode, offset, len); if (r >= 0) { tst_res_(filename, lineno, TFAIL, "fallocate(%d, %d, %ld, %ld) succeeded unexpectedly", fd, mode, offset, len); return r; } tst_res_(filename, lineno, TPASS | TERRNO, "fallocate(%d, %d, %ld, %ld) failed as expected", fd, mode, offset, len); return r; } void check_ftruncate(const char *filename, const int lineno, int fd, off_t length) { safe_ftruncate(filename, lineno, fd, length); tst_res_(filename, lineno, TPASS, "ftruncate(%d, %ld) succeeded", fd, length); } void check_ftruncate_fail(const char *filename, const int lineno, int fd, off_t length) { if (ftruncate(fd, length) >= 0) { tst_res_(filename, lineno, TFAIL, "ftruncate(%d, %ld) succeeded unexpectedly", fd, length); return; } tst_res_(filename, lineno, TPASS | TERRNO, "ftruncate(%d, %ld) failed as expected", fd, length); } int get_mfd_all_available_flags(const char *filename, const int lineno) { unsigned int i; int flag; int flags2test[] = FLAGS_ALL_ARRAY_INITIALIZER; int flags_available = 0; if (!MFD_FLAGS_AVAILABLE(0)) { tst_brk_(filename, lineno, TCONF, "memfd_create(0) not implemented"); } for (i = 0; i < ARRAY_SIZE(flags2test); i++) { flag = flags2test[i]; if (MFD_FLAGS_AVAILABLE(flag)) flags_available |= flag; } return flags_available; } int mfd_flags_available(const char *filename, const int lineno, unsigned int flags) { TEST(sys_memfd_create("dummy_call", flags)); if (TST_RET < 0) { if (TST_ERR != EINVAL) { tst_brk_(filename, lineno, TBROK | TTERRNO, "memfd_create() failed"); } return 0; } SAFE_CLOSE(TST_RET); return 1; } int check_mfd_new(const char *filename, const int lineno, const char *name, loff_t sz, int flags) { int fd; fd = sys_memfd_create(name, flags); if (fd < 0) { tst_brk_(filename, lineno, TBROK | TERRNO, "memfd_create(%s, %d) failed", name, flags); } tst_res_(filename, lineno, TPASS, "memfd_create(%s, %d) succeeded", name, flags); check_ftruncate(filename, lineno, fd, sz); return fd; } void check_mfd_fail_new(const char *filename, const int lineno, const char *name, int flags) { int fd; fd = sys_memfd_create(name, flags); if (fd >= 0) { safe_close(filename, lineno, NULL, fd); tst_brk_(filename, lineno, TFAIL, "memfd_create(%s, %d) succeeded unexpectedly", name, flags); } tst_res_(filename, lineno, TPASS | TERRNO, "memfd_create(%s, %d) failed as expected", name, flags); } void *check_mmap(const char *file, const int lineno, void *addr, size_t length, int prot, int flags, int fd, off_t offset) { void *p; p = safe_mmap(file, lineno, addr, length, prot, flags, fd, offset); tst_res_(file, lineno, TPASS, "mmap(%p, %zu, %i, %i, %i, %li) succeeded", addr, length, prot, flags, fd, (long)offset); return p; } void check_mmap_fail(const char *file, const int lineno, void *addr, size_t length, int prot, int flags, int fd, off_t offset) { if (mmap(addr, length, prot, flags, fd, offset) != MAP_FAILED) { safe_munmap(file, lineno, NULL, addr, length); tst_res_(file, lineno, TFAIL, "mmap(%p, %zu, %i, %i, %i, %li) succeeded unexpectedly", addr, length, prot, flags, fd, (long)offset); return; } tst_res_(file, lineno, TPASS | TERRNO, "mmap(%p, %zu, %i, %i, %i, %li) failed as expected", addr, length, prot, flags, fd, (long)offset); } void check_munmap(const char *file, const int lineno, void *p, size_t length) { safe_munmap(file, lineno, NULL, p, length); tst_res_(file, lineno, TPASS, "munmap(%p, %zu) succeeded", p, length); } void check_mfd_has_seals(const char *file, const int lineno, int fd, int seals) { int ret = SAFE_FCNTL((fd), F_GET_SEALS); if (ret != seals) { tst_brk_(file, lineno, TFAIL, "fd %d doesn't have expected seals (%d expected %d)", fd, ret, seals); } tst_res_(file, lineno, TPASS, "fd %d has expected seals (%d)", fd, seals); } void check_mprotect(const char *file, const int lineno, void *addr, size_t length, int prot) { if (mprotect(addr, length, prot) < 0) { tst_brk_(file, lineno, TFAIL | TERRNO, "mprotect(%p, %zu, %d) failed", addr, length, prot); } tst_res_(file, lineno, TPASS, "mprotect(%p, %zu, %d) succeeded", addr, length, prot); } void check_mfd_fail_add_seals(const char *filename, const int lineno, int fd, int seals) { if (fcntl(fd, F_ADD_SEALS, seals) >= 0) { tst_brk_(filename, lineno, TFAIL, "fcntl(%d, F_ADD_SEALS) succeeded unexpectedly", fd); } tst_res_(filename, lineno, TPASS | TERRNO, "fcntl(%d, F_ADD_SEALS, %d) failed as expected", (fd), (seals)); } void check_mfd_size(const char *filename, const int lineno, int fd, size_t size) { struct stat st; safe_fstat(filename, lineno, fd, &st); if (st.st_size != (long)size) { tst_brk_(filename, lineno, TFAIL, "fstat(%d, &st): unexpected file size", fd); } tst_res_(filename, lineno, TPASS, "fstat(%d, &st): file size is correct", fd); } int check_mfd_open(const char *filename, const int lineno, int fd, int flags, mode_t mode) { int r; char buf[512]; sprintf(buf, "/proc/self/fd/%d", fd); r = safe_open(filename, lineno, NULL, buf, flags, mode); tst_res_(filename, lineno, TPASS, "open(%s, %d, %d) succeeded", buf, flags, mode); return r; } void check_mfd_fail_open(const char *filename, const int lineno, int fd, int flags, mode_t mode) { char buf[512]; sprintf(buf, "/proc/self/fd/%d", fd); fd = open(buf, flags, mode); if (fd > 0) { safe_close(filename, lineno, NULL, fd); tst_res_(filename, lineno, TFAIL, "open(%s, %d, %d) succeeded unexpectedly", buf, flags, mode); } else { tst_res_(filename, lineno, TPASS | TERRNO, "open(%s, %d, %d) failed as expected", buf, flags, mode); } } void check_mfd_readable(const char *filename, const int lineno, int fd) { char buf[16]; void *p; safe_read(filename, lineno, NULL, 1, fd, buf, sizeof(buf)); tst_res_(filename, lineno, TPASS, "read(%d, %s, %zu) succeeded", fd, buf, sizeof(buf)); /* verify PROT_READ *is* allowed */ p = check_mmap(filename, lineno, NULL, MFD_DEF_SIZE, PROT_READ, MAP_PRIVATE, fd, 0); check_munmap(filename, lineno, p, MFD_DEF_SIZE); /* verify MAP_PRIVATE is *always* allowed (even writable) */ p = check_mmap(filename, lineno, NULL, MFD_DEF_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); check_munmap(filename, lineno, p, MFD_DEF_SIZE); } void check_mfd_writeable(const char *filename, const int lineno, int fd) { void *p; /* verify write() succeeds */ safe_write(filename, lineno, NULL, 1, fd, "\0\0\0\0", 4); tst_res_(filename, lineno, TPASS, "write(%d, %s, %d) succeeded", fd, "\\0\\0\\0\\0", 4); /* verify PROT_READ | PROT_WRITE is allowed */ p = check_mmap(filename, lineno, NULL, MFD_DEF_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); *(char *)p = 0; check_munmap(filename, lineno, p, MFD_DEF_SIZE); /* verify PROT_WRITE is allowed */ p = check_mmap(filename, lineno, NULL, MFD_DEF_SIZE, PROT_WRITE, MAP_SHARED, fd, 0); *(char *)p = 0; check_munmap(filename, lineno, p, MFD_DEF_SIZE); /* verify PROT_READ with MAP_SHARED is allowed and a following * mprotect(PROT_WRITE) allows writing */ p = check_mmap(filename, lineno, NULL, MFD_DEF_SIZE, PROT_READ, MAP_SHARED, fd, 0); check_mprotect(filename, lineno, p, MFD_DEF_SIZE, PROT_READ | PROT_WRITE); *(char *)p = 0; check_munmap(filename, lineno, p, MFD_DEF_SIZE); /* verify PUNCH_HOLE works */ check_fallocate(filename, lineno, fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, 0, MFD_DEF_SIZE); } void check_mfd_non_writeable(const char *filename, const int lineno, int fd) { void *p; /* verify write() fails */ TEST(write(fd, "data", 4)); if (TST_RET < 0) { if (TST_ERR != EPERM) { tst_brk_(filename, lineno, TFAIL | TTERRNO, "write() didn't fail as expected"); } } else { tst_brk_(filename, lineno, TFAIL, "write() succeeded unexpectedly"); } tst_res_(filename, lineno, TPASS | TTERRNO, "write failed as expected"); /* verify PROT_READ | PROT_WRITE is not allowed */ check_mmap_fail(filename, lineno, NULL, MFD_DEF_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); /* verify PROT_WRITE is not allowed */ check_mmap_fail(filename, lineno, NULL, MFD_DEF_SIZE, PROT_WRITE, MAP_SHARED, fd, 0); /* Verify PROT_READ with MAP_SHARED with a following mprotect is not * allowed. Note that for r/w the kernel already prevents the mmap. */ p = mmap(NULL, MFD_DEF_SIZE, PROT_READ, MAP_SHARED, fd, 0); if (p != MAP_FAILED) { if (mprotect(p, MFD_DEF_SIZE, PROT_READ | PROT_WRITE) >= 0) { tst_brk_(filename, lineno, TFAIL | TERRNO, "mmap()+mprotect() succeeded unexpectedly"); } } /* verify PUNCH_HOLE fails */ check_fallocate_fail(filename, lineno, fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, 0, MFD_DEF_SIZE); } void check_mfd_shrinkable(const char *filename, const int lineno, int fd) { int fd2; check_ftruncate(filename, lineno, fd, MFD_DEF_SIZE / 2); check_mfd_size(filename, lineno, fd, MFD_DEF_SIZE / 2); fd2 = check_mfd_open(filename, lineno, fd, O_RDWR | O_CREAT | O_TRUNC, 0600); safe_close(filename, lineno, NULL, fd2); check_mfd_size(filename, lineno, fd, 0); } void check_mfd_non_shrinkable(const char *filename, const int lineno, int fd) { check_ftruncate_fail(filename, lineno, fd, MFD_DEF_SIZE / 2); check_mfd_fail_open(filename, lineno, fd, O_RDWR | O_CREAT | O_TRUNC, 0600); } void check_mfd_growable(const char *filename, const int lineno, int fd) { check_ftruncate(filename, lineno, fd, MFD_DEF_SIZE * 2); check_mfd_size(filename, lineno, fd, MFD_DEF_SIZE * 2); check_fallocate(filename, lineno, fd, 0, 0, MFD_DEF_SIZE * 4); check_mfd_size(filename, lineno, fd, MFD_DEF_SIZE * 4); } void check_mfd_non_growable(const char *filename, const int lineno, int fd) { check_ftruncate_fail(filename, lineno, fd, MFD_DEF_SIZE * 2); check_fallocate_fail(filename, lineno, fd, 0, 0, MFD_DEF_SIZE * 4); } void check_mfd_growable_by_write(const char *filename, const int lineno, int fd) { char buf[MFD_DEF_SIZE * 8]; if (pwrite(fd, buf, sizeof(buf), 0) != sizeof(buf)) { tst_res_(filename, lineno, TFAIL | TERRNO, "pwrite(%d, %s, %zu, %d) failed", fd, buf, sizeof(buf), 0); return; } tst_res_(filename, lineno, TPASS, "pwrite(%d, %s, %zu, %d) succeeded", fd, buf, sizeof(buf), 0); check_mfd_size(filename, lineno, fd, MFD_DEF_SIZE * 8); } void check_mfd_non_growable_by_write(const char *filename, const int lineno, int fd) { char buf[MFD_DEF_SIZE * 8]; if (pwrite(fd, buf, sizeof(buf), 0) == sizeof(buf)) { tst_res_(filename, lineno, TFAIL, "pwrite(%d, %s, %zu, %d) didn't fail as expected", fd, buf, sizeof(buf), 0); return; } tst_res_(filename, lineno, TPASS, "pwrite(%d, %s, %zu, %d) succeeded", fd, buf, sizeof(buf), 0); }