DF-3708 / 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 | /* * DF-3708 harness: arcmsr_action() frees an SRB that belongs to the * bus_dmamem_alloc() coherent pool with kfree() -- an invalid free. * * Kernel source replicated: * sys/dev/raid/arcmsr/arcmsr.c:3013 srb = arcmsr_get_freesrb(acb) * sys/dev/raid/arcmsr/arcmsr.c:3053 if ((flags & CAM_SG_LIST_PHYS) == 0 * || (flags & CAM_DATA_PHYS) != 0) { * sys/dev/raid/arcmsr/arcmsr.c:3057 kfree(srb, M_DEVBUF); <-- BUG * * The SRB pointers handed out by arcmsr_get_freesrb() are interior * pointers of the single coherent allocation made at attach time: * arcmsr.c:4413 bus_dmamem_alloc(..., &acb->uncacheptr, ...) * arcmsr.c:4231 acb->srbworkingQ[i] = acb->psrb_pool[i] = srb_tmp; * arcmsr.c:4233 srb_tmp = (void *)((unsigned long)srb_tmp + SRB_SIZE); * * A userland model of the same allocator discipline (one big allocation, * hand out interior pointers at SRB_SIZE strides, then free() an interior * pointer exactly as the driver does) shows why the free is invalid: * the allocator (glibc here, kern_slaballoc.c in the kernel) never * received that pointer from a matching allocation. * * DFly kernel consequence (sys/kern/kern_slaballoc.c:_kfree): * - kup > 0 path (1430-1468): treats pool bytes as an oversized-allocation * size, kmem_slab_free() on a pointer kmem never allocated -> kernel_map * corruption; * - zone path (1475-1478): KKASSERT(*kup < 0)/z_Magic checks panic on * INVARIANTS kernels, or treat pool contents as SLZone metadata on * production kernels -> slab freelist corruption. * Additionally the SRB is consumed from srbworkingQ and never returned * (arcmsr_free_srb is not called) -> queue slot leak. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <signal.h> #include <sys/wait.h> #define ARCMSR_MAX_FREESRB_NUM 384 /* SRB_SIZE = (sizeof(CCB)+0x1f) & ~0x1f ; struct is ~544+ bytes -> 576 */ #define SRB_SIZE 576 struct pool_hdr { void *pool; /* acb->uncacheptr */ void *slot[ARCMSR_MAX_FREESRB_NUM]; }; static struct pool_hdr P; static int workingsrb_startindex; static void map_free_srb(void) { size_t i; P.pool = malloc((size_t)SRB_SIZE * ARCMSR_MAX_FREESRB_NUM); if (P.pool == NULL) exit(2); memset(P.pool, 0, (size_t)SRB_SIZE * ARCMSR_MAX_FREESRB_NUM); for (i = 0; i < ARCMSR_MAX_FREESRB_NUM; i++) P.slot[i] = (char *)P.pool + i * SRB_SIZE; /* arcmsr.c:4231/4233 */ workingsrb_startindex = 0; } /* model of arcmsr_get_freesrb (arcmsr.c:2581) */ static void *get_freesrb(void) { return P.slot[workingsrb_startindex++ % ARCMSR_MAX_FREESRB_NUM]; } /* CAM flag bits used by the vulnerable branch (arcmsr.c:3049-3058) */ #define CAM_SCATTER_VALID 0x00000010U #define CAM_SG_LIST_PHYS 0x00000400U #define CAM_DATA_PHYS 0x00000200U static int arcmsr_action_sg_errorpath(unsigned int ccb_h_flags) { void *srb = get_freesrb(); /* arcmsr.c:3013 */ int aborted = 0; if ((ccb_h_flags & CAM_SG_LIST_PHYS) == 0 || (ccb_h_flags & CAM_DATA_PHYS) != 0) { /* * arcmsr.c:3055-3058: xpt_done(pccb); kfree(srb, M_DEVBUF); * The free below is the userland stand-in for kfree(). */ printf("error-path taken: freeing srb=%p (pool=%p + %zu bytes)\n", srb, P.pool, (size_t)((char *)srb - (char *)P.pool)); fflush(stdout); free(srb); /* == kfree(srb, M_DEVBUF) */ aborted = 1; } return aborted; } int main(void) { pid_t pid; int status; setvbuf(stdout, NULL, _IONBF, 0); /* keep fork() buffers clean */ map_free_srb(); /* * Consume a few SRBs first so the srb being freed is an INTERIOR * pointer of the pool allocation (pool + n*SRB_SIZE), like every * real kfree(srb) call in the driver error path. (In the kernel * even slot 0 is not a kmalloc pointer -- the pool comes from * bus_dmamem_alloc(), not the slab allocator.) */ (void)get_freesrb(); (void)get_freesrb(); (void)get_freesrb(); printf("pool base = %p\n", P.pool); printf("SRB handed out = %p (pool + 3*SRB_SIZE, interior pointer)\n", P.slot[3]); printf("flag combo: CAM_SCATTER_VALID set, virtual SG list " "(no CAM_SG_LIST_PHYS) -- legal CAM API input\n\n"); /* * Fork: the child performs the invalid free exactly as the driver * does. glibc aborts the child ("free(): invalid pointer/size"), * the kernel analogue is the slab/INVARIANTS panic or silent slab * corruption described above. */ pid = fork(); if (pid == 0) { arcmsr_action_sg_errorpath(CAM_SCATTER_VALID); /* not reached on aborting allocators */ printf("child: free() returned without abort " "(allocator accepted interior pointer -- on DFly this is " "the silent-corruption case)\n"); _exit(0); } waitpid(pid, &status, 0); printf("child wait status: raw=0x%x WIFSIGNALED=%d WIFEXITED=%d\n", status, WIFSIGNALED(status), WIFEXITED(status)); if (WIFSIGNALED(status) || (WIFEXITED(status) && WEXITSTATUS(status) != 0)) { /* * DFly's slab-based libc malloc detects the interior-pointer * free and dies with: * assertion: z->z_Magic == ZALLOC_SLAB_MAGIC in _slabfree * (exit(1) from libc's assertion handler on this system). * The kernel's _kfree (kern_slaballoc.c:1478) makes the * identical z_Magic check -- panic on INVARIANTS kernels, * silent slab-metadata corruption on production kernels. */ printf("\n[+] DF-3708 CONFIRMED: allocator rejected the " "interior-pointer free (child %s %d).\n" " The kernel kfree() of a bus_dmamem pool interior " "pointer trips the same slab checks -> panic on " "INVARIANTS, silent slab corruption otherwise.\n", WIFSIGNALED(status) ? "killed by signal" : "exited with status", WIFSIGNALED(status) ? WTERMSIG(status) : WEXITSTATUS(status)); return 0; } printf("\n[+] DF-3708 PARTIAL: allocator did not abort, but the freed " "pointer is provably an interior pointer of the pool allocation " "(pool=%p, freed offset=%zu)\n", P.pool, (size_t)3 * SRB_SIZE); return 1; } |