DF-2462 / df_pre.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 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 | /* * DF-2462 verification harness -- PREAMBLE. * * gen.sh (run on the guest) concatenates: * * df_pre.c * + <verbatim sys/dev/disk/iscsi/initiator/isc_sm.c> whole file * + <verbatim isc_cam.c 81-95> ic_lost_target * + <verbatim isc_cam.c 165-309> _inq, _scsi_encap, ic_action * + <verbatim iscsi_subr.c 400-436> dwl * + <verbatim iscsi_subr.c 441-528> scsi_encap * + <verbatim isc_subr.c 153-168> i_freeopt * + df_post.c * * into one TU compiled into the kernel as * dev/disk/iscsi/initiator/dfiscsi_harness.c. The X86_64_GENERIC * kernel does not configure "device iscsi_initiator" (conf/files marks * all six driver files "optional iscsi_initiator scbus"), so the * verbatim copies inside this TU are the only compiled instances. * The driver source files are byte-for-byte the audited tree * (sha256-gated by build.sh against the audit tree). * * What is verified (sys/dev/disk/iscsi/initiator/isc_sm.c:724-777): * ism_stop() tears a session down IN PLACE: * :734 stop receiver, :739-744 wait xmitter (tsleep loop, up to * hz per iteration), :748 iscsi_cleanup, :750 i_pdu_flush, * :752 ic_lost_target (frees the cam path; the xpt_freeze_devq * drain in ic_freeze is #if 0'd at isc_cam.c:57-63), :762 * destroy_dev, :765-769 mtx_uninit x5, :771 i_freeopt (frees the * option strings), :772 sessions[sid]=NULL, :774 * sysctl_ctx_free, :777 kfree(sp). * Concurrent readers that hold sp with no refcount and no lock: * - ic_action() (isc_cam.c:202-309): sp = isp->sessions[target_id] * at :211 with NO lock, dereferenced at :215 (ccb * spriv_ptr0=sp write), :254 (sp->opt.maxluns read), and via * _scsi_encap -> scsi_encap (iscsi_subr.c:441+) -> dwl + * isc_qout (isc_sm.c:303) -> i_nqueue_csnd / i_prepPDU which * WRITE into sp. * - per-session sysctl handlers isc_dump_stats (isc_sm.c:630), * isc_sysctl_targetName/Address (isc_sm.c:656/667) hold raw * sp / &sp->opt.targetName as arg1 with no lifetime guarantee; * the OIDs stay registered until :774. * * Interposition model (all #define renames of FUNCTIONS; the verbatim * text is untouched; iscsivar.h's debug/sdebug MACROS cannot be * renamed because the header defines them inside the verbatim text, * so their kprintf() calls are intercepted instead): * * STALL-2 (teardown parked inside the window, just after the CAM * path teardown starts): ic_lost_target calls * xpt_async(AC_LOST_DEVICE, ...) at isc_cam.c:90 -- the shim parks * there (the real lockmgr(&isp->cam_lock) at :89 is already held; * nothing in this rig or in the unprivileged sysctl read path takes * cam_lock, so parking is safe). * * VICTIM STALL (CAM, between lookup and use): ic_action's * debug(4, "func_code=...") at isc_cam.c:217 compiles (via * iscsivar.h:40-41) to a kprintf() call -- iscsi_debug is set to 4 * so it executes, and kprintf is renamed to dfh_kprintf, which * parks on the "func_code" line. That call sits right after * sp = isp->sessions[target_id] (:211) * ccb_h->spriv_ptr0 = sp (:215) * and right before the sp dereferences (:254 PATH_INQ read / * _scsi_encap for SCSI_IO). Parking there deterministically models * a thread preempted between lookup and use. * * VICTIM STALL (sysctl, entered but not yet touching sp): * isc_dump_stats' first ksnprintf (isc_sm.c:642) is renamed to * dfh_ksnprintf which parks on the first call; the sp->stats.* * reads happen while evaluating that call's arguments after the * resume. * * STALL-0: the isc_stop_receiver shim can park the teardown at * isc_sm.c:734 (top of ism_stop). * * kfree(addr, type) -> dfh_kfree: real _kfree, plus, when armed for * the session chunk, an immediate reclaim of the freed chunk (same * size, same malloc type -- the zone's LIFO behaviour returns the * same address) filled as an attacker-controlled object: * MODE_FIXUP = attacker fully controls the former session * (valid mutexes/queues, chosen field values): * silent UAF read/write proof; * MODE_FAULT = valid mutexes, garbage queue heads: the victim's * TAILQ_INSERT_TAIL writes through 0xBEBEBEBE... -> * kernel fault (the realistic "chunk reused by an * unrelated allocation" case); * MODE_STALE = no reclaim (chunk keeps stale bytes). * (dfh_kfree's body lives in df_post.c -- it needs the complete * isc_session_t definition.) * * xpt_async, xpt_free_path -> no-op shims (ic_lost_target itself is * compiled VERBATIM; cam_path is a fabricated non-NULL sentinel) * xpt_done -> records the CCB status (a fabricated CCB must not * enter the live CAM transport) * isc_stop_receiver, iscsi_cleanup, i_pdu_flush -> no-op shims * release_dev, destroy_dev -> no-op shims (cdev lifecycle is not * part of this finding; the cdev is fabricated for si_name only) * ism_proc externals (isc_sendPDU, iscsi_* PDU workers, ...) -> * link-only shims; ism_proc never runs here (the fabricated * session has ISC_SM_RUN|ISC_SM_RUNNING clear so ism_stop's wait * loop at :740-744 is skipped deterministically). * * Everything else is REAL: the whole ism_stop body, lockmgr on * sc->lock, TAILQ_REMOVE, mtx_init/mtx_uninit (INVARIANTS-checked), * i_freeopt's kfree of the option strings, sysctl_ctx_free (really * deregisters the OIDs), kmalloc/kfree(M_ISCSI), pdu_alloc from the * real freepdu list, isc_qout/i_prepPDU/dwl/i_nqueue_* writes into * sp, the verbatim xdebug() failure prints (ism_stop:774-775), and * the real sysctl(3) userland path for the per-session OIDs. */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/conf.h> #include <sys/systm.h> #include <sys/malloc.h> #include <sys/errno.h> #include <sys/sysctl.h> #include <sys/queue.h> #include <sys/kthread.h> #include <sys/proc.h> #include <sys/thread2.h> #include <sys/spinlock2.h> #include <sys/lock.h> #include <sys/time.h> #include <bus/cam/cam.h> #include <bus/cam/cam_ccb.h> #include <bus/cam/cam_sim.h> #include <bus/cam/cam_xpt_sim.h> #include <bus/cam/cam_periph.h> #include <bus/cam/scsi/scsi_message.h> /* forward decls for the driver types (defined by the verbatim text) */ struct isc_session; struct pduq; /* * iscsivar.h (included by the verbatim text below) DECLARES * iscsi_debug, iscsi_dbg_lock and M_ISCSI; they are DEFINED in * df_post.c (after the verbatim includes, so -Wredundant-decls is * happy). iscsi_debug = 4 makes the verbatim debug(4)/sdebug(<=4) * prints execute -- including the ic_action "func_code" kprintf the * victim stall intercepts, and a natural execution trace in dmesg. */ /* ------------------------------------------------------------------ */ /* ---- harness state ------------------------------------------------ */ /* ------------------------------------------------------------------ */ static struct isc_session *dfh_sp; /* the fabricated session */ static struct cam_sim *dfh_sim; /* fabricated sim (ic_action's) */ static struct cdev *dfh_fake_dev; /* fabricated cdev (si_name) */ /* stall / gate state */ static volatile int dfh_stall0_armed; /* park in isc_stop_receiver shim */ static volatile int dfh_stall2_armed; /* park in xpt_async shim */ static volatile int dfh_camstall_armed; /* park victim in ic_action :217 */ static volatile int dfh_statsstall_armed;/* park victim in isc_dump_stats */ static volatile int dfh_stop_parked; /* teardown thread parked */ static volatile int dfh_victim_parked; /* victim thread parked */ static volatile int dfh_release_stop; /* release the parked teardown */ static volatile int dfh_release_victim; /* release the parked victim */ static volatile int dfh_stop_done; /* ism_stop returned */ static volatile int dfh_victim_done; /* victim returned */ /* poison / reclaim state */ enum { DFH_MODE_STALE = 0, DFH_MODE_FIXUP = 1, DFH_MODE_FAULT = 2 }; static int dfh_poison_mode = DFH_MODE_STALE; static int dfh_plant_garbage_isc; /* FIXUP: isc = 0xAE.. (fault on the stats handler's sc->npdu_alloc deref -- natural-race panic model) */ static int dfh_poison_hits; /* session kfrees intercepted */ static volatile int dfh_free_seen; /* session chunk passed kfree */ static void *dfh_reclaim; /* reclaim chunk (== old sp addr) */ static void *dfh_freepdu_store; /* backing store for freepdu */ /* results */ #define DFH_OUT_MAX 8192 static char dfh_out[DFH_OUT_MAX]; static size_t dfh_out_len; static struct lock dfh_out_lock; static char dfh_req_buf[2048]; static size_t dfh_req_len; static u_int32_t dfh_last_status; /* xpt_done shim record */ static void dfh_outp(const char *fmt, ...) { __va_list ap; lockmgr(&dfh_out_lock, LK_EXCLUSIVE); if (dfh_out_len < DFH_OUT_MAX - 256) { __va_start(ap, fmt); dfh_out_len += kvsnprintf(dfh_out + dfh_out_len, DFH_OUT_MAX - dfh_out_len - 1, fmt, ap); __va_end(ap); } lockmgr(&dfh_out_lock, LK_RELEASE); } /* * Gate helpers: each flag is its own sleep channel, and setters wake * the channel explicitly. (An earlier version slept every waiter on * one channel and prodded it with wakeup() from the wait loop -- with * two waiters parked concurrently the wakeups ping-ponged between * them and burned the iteration budget in milliseconds, so a 30s park * expired instantly. Per-flag channels fix that.) */ static void dfh_gate_wait(volatile int *var, int val, const char *wmesg) { int n; for (n = 0; n < 480 && *var != val; n++) tsleep((void *)(uintptr_t)var, 0, wmesg, hz / 4); kprintf("DF2462DBG gate %s: val=%d want=%d iters=%d\n", wmesg, *var, val, n); } static void dfh_gate_set(volatile int *var, int val) { *var = val; wakeup((void *)(uintptr_t)var); } /* ------------------------------------------------------------------ */ /* ---- shim bodies -------------------------------------------------- */ /* ------------------------------------------------------------------ */ /* * NOTE: shims whose prototypes iscsivar.h also declares * (isc_stop_receiver, isc_start_receiver, isc_sendPDU, i_pdu_flush, * iscsi_*, scsi_decap, ic_fullfeature) are neither declared nor * defined here -- iscsivar.h's prototypes, renamed by the #defines * at the bottom of this file, become their declarations, and the * definitions live in df_post.c (any extra declaration here would * trip -Wredundant-decls). Everything defined below is * self-contained. */ static void dfh_release_dev(cdev_t d) { } static void dfh_destroy_dev(cdev_t d) { } /* * STALL-2: ic_lost_target's xpt_async(AC_LOST_DEVICE, ...) at * isc_cam.c:90. Parking here puts the tearing-down thread inside * ic_lost_target -- after :89's real lockmgr(&isp->cam_lock) -- and * before xpt_free_path / return, i.e. the teardown is at * isc_sm.c:752-in-progress, everything after it (TAILQ_REMOVE, * destroy_dev, mtx_uninit, i_freeopt, sessions[sid]=NULL, * sysctl_ctx_free, kfree) still pending: squarely inside the * published window with sessions[sid] still != NULL. */ static void dfh_xpt_async(int code, struct cam_path *path, void *arg) { if (dfh_stall2_armed) { dfh_stall2_armed = 0; dfh_gate_set(&dfh_stop_parked, 1); dfh_gate_wait(&dfh_release_stop, 1, "dfhs2"); dfh_release_stop = 0; } } static void dfh_xpt_free_path(struct cam_path *path) { } static void dfh_xpt_done(union ccb *ccb) { /* record, do not hand a fabricated CCB to the live transport */ dfh_last_status = ccb->ccb_h.status; } /* declaration only: the body (reclaim/poison) is in df_post.c */ static void dfh_kfree(void *addr, struct malloc_type *type); /* * VICTIM STALL (sysctl): isc_dump_stats' first ksnprintf * (isc_sm.c:642) has not yet read sp->stats.* -- the reads happen * while evaluating THIS call's arguments after the resume. */ static int dfh_ksnprintf(char *str, size_t size, const char *fmt, ...) { __va_list ap; int r; if (dfh_statsstall_armed) { dfh_statsstall_armed = 0; dfh_gate_set(&dfh_victim_parked, 1); dfh_gate_wait(&dfh_release_victim, 1, "dfhsv"); dfh_release_victim = 0; } __va_start(ap, fmt); r = kvsnprintf(str, size, fmt, ap); __va_end(ap); return r; } /* * VICTIM STALL (CAM) + passthrough: ic_action's debug(4, * "func_code=...") at isc_cam.c:217 becomes kprintf("%s: func_code=...") * through iscsivar.h:40-41 (iscsi_debug >= 4). Parking on that * exact line models a thread preempted between the sessions[] lookup * (:211/:215) and the sp dereferences (:254 / _scsi_encap). * Everything else prints normally (the verbatim execution trace). */ static void dfh_kprintf(const char *fmt, ...) { __va_list ap; if (dfh_camstall_armed && strstr(fmt, "func_code") != NULL) { dfh_camstall_armed = 0; dfh_gate_set(&dfh_victim_parked, 1); dfh_gate_wait(&dfh_release_victim, 1, "dfhcv"); dfh_release_victim = 0; } __va_start(ap, fmt); kvprintf(fmt, ap); __va_end(ap); } /* ------------------------------------------------------------------ */ /* ---- the interposition renames ------------------------------------ */ /* ------------------------------------------------------------------ */ #undef kfree #undef kprintf #define kfree(addr, type) dfh_kfree((addr), (type)) #define kprintf(fmt, args...) dfh_kprintf((fmt), ## args) #define ksnprintf(s, z, f, a...) dfh_ksnprintf((s), (z), (f), ## a) #define isc_stop_receiver dfh_isc_stop_receiver #define iscsi_cleanup dfh_iscsi_cleanup #define i_pdu_flush dfh_i_pdu_flush #define release_dev dfh_release_dev #define destroy_dev dfh_destroy_dev #define isc_sendPDU dfh_isc_sendPDU #define iscsi_requeue dfh_iscsi_requeue #define iscsi_async dfh_iscsi_async #define iscsi_reject dfh_iscsi_reject #define iscsi_r2t dfh_iscsi_r2t #define iscsi_done dfh_iscsi_done #define scsi_decap dfh_scsi_decap #define ic_fullfeature dfh_ic_fullfeature #define isc_start_receiver dfh_isc_start_receiver #define xpt_async dfh_xpt_async #define xpt_free_path dfh_xpt_free_path #define xpt_done dfh_xpt_done /* * The verbatim driver text follows in gen.sh's concatenation. */ |