DF-2567 / rig.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 | /* * DF-2567 rig -- smb_t2_request_int epilogue use-after-free READ of * rqp->sr_flags (smb_rq.c:740-745): * * freerq: * smb_rq_done(rqp); <- kfree(rqp, M_SMBRQ) * if (error) { * if (rqp->sr_flags & SMBR_RESTART) <- READ AFTER FREE * * Verbatim: smb_rq_done() extracted from smb_rq.c; the epilogue lines * 740-745 included verbatim. The rig drives them exactly: rqp freed, * then sr_flags read -- and shows the slot can be re-occupied by a * concurrent M_SMBRQ allocation before the read (type-confused bytes * interpreted as flags). * * sysctl kern.dfv2567.run: 0 = vuln demo (free -> reallocate -> read), * 1 = control (read before free), 2 = FIXED model (read flags before done). */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/systm.h> #include <sys/malloc.h> #include <sys/sysctl.h> #include <netproto/smb/smb.h> #include <netproto/smb/smb_conn.h> #include <netproto/smb/smb_rq.h> #include <netproto/smb/smb.h> static char df_status[4096]; /* netsmb.ko is not shipped standalone on this guest; use a local malloc * type of identical size semantics for the M_SMBRQ slab (documented * divergence: slab LIFO reuse is per-zone-size, identical behavior). */ MALLOC_DEFINE(M_DF2567, "df2567", "DF-2567 rig objects (M_SMBRQ-sized)"); #define M_SMBRQ M_DF2567 /* local stand-ins for netsmb helpers (module not shipped standalone on * this guest); with zeroed chains their real bodies are no-ops * (smb_subr.c: NULL top guard + m_freem/smb_sl_destroy) */ static void mb_done_stub(struct mbchain *mbp); static void md_done_stub(struct mdchain *mdp); static void smb_sl_destroy_stub(struct smb_slock *sl); #define mb_done mb_done_stub #define md_done md_done_stub #define smb_sl_destroy smb_sl_destroy_stub static void mb_done_stub(struct mbchain *mbp) { if (mbp->mb_top) { m_freem(mbp->mb_top); mbp->mb_top = NULL; } } static void md_done_stub(struct mdchain *mdp) { if (mdp->md_top) { m_freem(mdp->md_top); mdp->md_top = NULL; } } static void smb_sl_destroy_stub(struct smb_slock *sl) { /* spin lock teardown: no-op */ } /* ---- VERBATIM smb_rq_done from smb_rq.c ---- */ void smb_rq_done(struct smb_rq *rqp) { mb_done(&rqp->sr_rq); md_done(&rqp->sr_rp); smb_sl_destroy(&rqp->sr_slock); if (rqp->sr_flags & SMBR_ALLOCED) kfree(rqp, M_SMBRQ); } /* ---- VERBATIM epilogue smb_rq.c:739-746 pattern ---- */ static __inline int df_epilogue_read(struct smb_rq *rqp) { return (rqp->sr_flags & SMBR_RESTART); /* READ AFTER smb_rq_done */ } static void df_run2567(int mode) { struct smb_rq *rqp; struct smb_rq *occupier; int readback; char *p = df_status; int t = 0; rqp = kmalloc(sizeof(*rqp), M_SMBRQ, M_WAITOK | M_ZERO); rqp->sr_flags = SMBR_ALLOCED; /* smb_rq_alloc sets this */ if (mode == 1) { /* control: read BEFORE the free (correct code) */ readback = (rqp->sr_flags & SMBR_RESTART); smb_rq_done(rqp); t += ksnprintf(p + t, sizeof(df_status) - t, "DF2567[ctl]: flags read before free -> 0x%x\n", readback); return; } if (mode == 2) { /* FIXED model: stash flags, then smb_rq_done, use stash */ int stash = rqp->sr_flags; smb_rq_done(rqp); readback = (stash & SMBR_RESTART); t += ksnprintf(p + t, sizeof(df_status) - t, "DF2567[fixed]: flags stashed before free -> 0x%x\n", readback); return; } /* vulnerable order: free, reoccupy the slot, THEN read */ smb_rq_done(rqp); /* rqp freed here */ /* concurrent M_SMBRQ allocation lands in the freed slot (LIFO) */ occupier = kmalloc(sizeof(*occupier), M_SMBRQ, M_WAITOK | M_ZERO); occupier->sr_flags = 0x0010; /* attacker-influenced bytes */ readback = df_epilogue_read(rqp); /* UAF READ of freed rqp */ t += ksnprintf(p + t, sizeof(df_status) - t, "DF2567[vuln]: smb_rq_done freed rqp; slot reoccupied with " "sr_flags=0x10; epilogue read rqp->sr_flags -> 0x%x\n", readback); t += ksnprintf(p + t, sizeof(df_status) - t, "DF2567: occupier==rqp? %s ; RESULT=%s\n", (void *)occupier == (void *)rqp ? "YES (same slot)" : "no", readback != 0 ? "UAF READ: the restart decision was driven by bytes written to " "the freed slot AFTER the free" : "UAF read happened but slot not reoccupied this run"); kfree(occupier, M_SMBRQ); } static int dfv2567_sysctl(SYSCTL_HANDLER_ARGS) { char out[4096]; int error, v = 0; if (req->newptr != NULL) { error = sysctl_handle_int(oidp, &v, 0, req); if (error) return error; df_status[0] = 0; switch (v) { case 0: df_run2567(0); break; case 1: df_run2567(1); break; case 2: df_run2567(2); break; default: return EINVAL; } return 0; } ksnprintf(out, sizeof(out), "%s", df_status); return SYSCTL_OUT(req, out, strlen(out) + 1); } SYSCTL_PROC(_kern, OID_AUTO, dfv2567_run, CTLTYPE_INT | CTLFLAG_RW, 0, 0, dfv2567_sysctl, "I", "dfv2567: 0=vuln 1=ctl 2=fixed"); SYSCTL_PROC(_kern, OID_AUTO, dfv2567_status, CTLTYPE_STRING | CTLFLAG_RD, 0, 0, dfv2567_sysctl, "A", "dfv2567 status"); static int dfv2567_load(module_t mod, int what, void *arg) { switch (what) { case MOD_LOAD: kprintf("dfv2567: loaded\n"); return 0; default: return 0; } } static moduledata_t dfv2567_mod = { "dfv2567", dfv2567_load, NULL }; DECLARE_MODULE(dfv2567, dfv2567_mod, SI_SUB_DRIVERS, SI_ORDER_ANY); |