DF-4438 / repro_logic.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 | /* * DF-4438 logic mirror: mrsas_cam_attach() error paths vs mrsas_cam_detach() * unwind — double cam_sim_free() / use-after-free of struct cam_sim. * * This is NOT a kernel exploit; it is a faithful userspace mirror of the * DragonFlyBSD CAM refcount/cleanup choreography exercised by the bug: * * sys/bus/cam/cam_sim.c:cam_sim_alloc() -> cam_devq_reference(queue); * sim->refcount = 1; * sys/bus/cam/cam_sim.c:cam_sim_free() -> cam_sim_release(sim,..): * if (sim->refcount == 1) { * cam_simq_release(sim->devq); * kfree(sim, M_CAMSIM); * } * * sys/dev/raid/mrsas/mrsas_cam.c:150-156 (xpt_bus_register(sim_0) fails) * cam_simq_release(devq); * cam_sim_free(sc->sim_0); <-- frees sim_0, sc->sim_0 DANGLING * return (ENXIO); * * sys/dev/raid/mrsas/mrsas.c:805,838-839 (mrsas_attach: goto attach_fail_cam) * mrsas_cam_detach(sc); * * sys/dev/raid/mrsas/mrsas_cam.c:216-218 (mrsas_cam_detach) * if (sc->sim_0) { * xpt_bus_deregister(cam_sim_path(sc->sim_0)); <-- UAF read * cam_sim_free(sc->sim_0); <-- DOUBLE FREE * } * * Mode "buggy" reproduces the exact current call sequence and detects the * use-after-free (cam_sim_path on freed sim) and the double free * (cam_sim_release on freed sim). Mode "fixed" applies the proposed * sc->sim_0 = NULL cleanup and shows both stay clean. * * Build: cc -O2 -Wall -o repro_logic repro_logic.c * Run: ./repro_logic buggy ; ./repro_logic fixed */ #include <stdio.h> #include <stdlib.h> #include <string.h> static int failures; struct cam_devq { int refcount; int freed; }; struct cam_sim { int refcount; int freed; struct cam_devq *devq; int path_id; }; /* ---- sys/bus/cam/cam_sim.c ---- */ static struct cam_devq *cam_simq_alloc(void) { struct cam_devq *q = calloc(1, sizeof(*q)); q->refcount = 1; return q; } static void cam_devq_reference(struct cam_devq *q) { if (q->freed) { printf(" !! devq UAF: reference on freed devq\n"); failures++; } q->refcount++; } static void cam_simq_release(struct cam_devq *q) { if (q->freed) { printf(" !! devq DOUBLE RELEASE (refcount underflow)\n"); failures++; return; } if (--q->refcount == 0) q->freed = 1; /* kfree(devq) */ } static struct cam_sim *cam_sim_alloc(struct cam_devq *queue) { struct cam_sim *sim = calloc(1, sizeof(*sim)); cam_devq_reference(queue); /* cam_sim.c:182 */ sim->refcount = 1; sim->devq = queue; return sim; } /* cam_sim_path(): plain deref of the sim pointer, as in the kernel */ static int cam_sim_path(struct cam_sim *sim) { if (sim->freed) { printf(" !! sim UAF: cam_sim_path() on freed cam_sim (kernel: read of kfree'd M_CAMSIM)\n"); failures++; } return sim->path_id; } static void xpt_bus_deregister(int path_id) { (void)path_id; /* in-kernel this unlinks the registered bus; here a no-op */ } static void cam_sim_free(struct cam_sim *sim) { /* cam_sim_release(sim, CAM_SIM_SOFTC) */ if (sim->freed) { printf(" !! sim DOUBLE FREE: cam_sim_release()/kfree() on already-freed cam_sim\n"); failures++; return; } if (sim->refcount == 1) { if (sim->devq) { cam_simq_release(sim->devq); sim->devq = NULL; } sim->freed = 1; /* kfree(sim, M_CAMSIM) */ } else { sim->refcount--; } } /* ---- mrsas_softc (only the fields that matter) ---- */ struct mrsas_softc { struct cam_sim *sim_0; struct cam_sim *sim_1; }; /* * mrsas_cam_attach(), reduced to the failing path of interest: * xpt_bus_register(sc->sim_0, 0) fails (mrsas_cam.c:150-156). * Returns ENXIO like the kernel code. */ static int mrsas_cam_attach_sim0_register_fails(struct mrsas_softc *sc, struct cam_devq *devq, int fixed) { sc->sim_0 = cam_sim_alloc(devq); /* mrsas_cam.c:132 */ /* xpt_bus_register(sc->sim_0, 0) != CAM_SUCCESS */ cam_simq_release(devq); /* mrsas_cam.c:152 */ cam_sim_free(sc->sim_0); /* mrsas_cam.c:153 */ if (fixed) sc->sim_0 = NULL; /* THE FIX */ return 1; /* ENXIO */ } /* mrsas_cam_detach(), mrsas_cam.c:216-218 (sim_0 part) */ static void mrsas_cam_detach(struct mrsas_softc *sc) { if (sc->sim_0) { xpt_bus_deregister(cam_sim_path(sc->sim_0)); /* mrsas_cam.c:217 */ cam_sim_free(sc->sim_0); /* mrsas_cam.c:218 */ } } int main(int argc, char **argv) { int fixed = (argc == 2 && strcmp(argv[1], "fixed") == 0); printf("== DF-4438 mirror: mode=%s ==\n", fixed ? "fixed" : "buggy"); struct mrsas_softc *sc = calloc(1, sizeof(*sc)); struct cam_devq *devq = cam_simq_alloc(); /* mrsas_cam.c:123 */ int rc = mrsas_cam_attach_sim0_register_fails(sc, devq, fixed); printf("mrsas_cam_attach returned %d -> mrsas_attach: goto attach_fail_cam -> mrsas_cam_detach()\n", rc); mrsas_cam_detach(sc); /* mrsas.c:839 */ if (failures == 0) printf("RESULT: clean (no UAF / double free detected)\n"); else printf("RESULT: %d kernel-memory-safety violation(s) detected" " (UAF + double free of struct cam_sim / M_CAMSIM)\n", failures); return failures ? 2 : 0; } |