DF-3731 / harness.c
/* * DF-3731 harness โ XPT_PATH_INQ reports cpi->max_target = CISS_MAX_LOGICAL * (sys/dev/raid/ciss/ciss.c:2956) but ciss_logical[bus] rows are allocated * with exactly CISS_MAX_LOGICAL (15) entries (ciss.c:1377-1379, indices * 0..14). CAM's bus scan iterates targets 0..max_target INCLUSIVE * (sys/bus/cam/cam_xpt.c:5382: `for (i = 0; i <= max_target; i++)`), so * target 15 is probed on every rescan and * sc->ciss_logical[bus][15].cl_status (ciss_cam_emulate, ciss.c:3159) * sc->ciss_logical[bus][15] in XPT_CALC_GEOMETRY (ciss.c:2923) * read one struct ciss_ldrive PAST the row allocation โ a recurring heap * OOB read at every bus scan/boot. If the adjacent heap happens to hold * CISS_LD_ONLINE (1) at the cl_status offset, the probe proceeds and * ciss_cam_complete_fixup (ciss.c:3321-3325) then dereferences the garbage * cl_ldrive/cl_lstatus pointers. * * Build: cc -O2 -Wall -o harness harness.c * Fixed: cc -DFIXED ... max_target = CISS_MAX_LOGICAL - 1. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #define CISS_MAX_LOGICAL 15 /* cissvar.h:49 */ #define CISS_LD_ONLINE 1 struct ld_status_rep { /* cl_status offset proxy */ unsigned long pad1[2]; /* cl_address, cl_controller */ int cl_status; int pad2; }; static size_t offsetof_cl_status(void); int main(void) { struct ld_status_rep *row; size_t row_bytes = CISS_MAX_LOGICAL * sizeof(*row); #ifdef FIXED int max_target = CISS_MAX_LOGICAL - 1; /* the fix (ciss.c:2956) */ #else int max_target = CISS_MAX_LOGICAL; /* ciss.c:2956 โ off by one */ #endif int t, oob = 0; printf("== DF-3731 ciss max_target off-by-one vs ciss_logical rows ==\n"); printf("ciss_logical[bus] row = %zu bytes (%d entries), cpi->max_target" " = %d\n", row_bytes, CISS_MAX_LOGICAL, max_target); printf("CAM scan: for (i = 0; i <= max_target; i++) (cam_xpt.c:5382)" "\n\n"); row = calloc(1, row_bytes + 16); /* + canary */ memset((char *)row + row_bytes, 0x5A, 16); /* one genuine ONLINE drive at target 3 */ row[3].cl_status = CISS_LD_ONLINE; for (t = 0; t <= max_target; t++) { int in_row = t < CISS_MAX_LOGICAL; int status; /* ciss_cam_emulate: ciss.c:3159 โ read of * sc->ciss_logical[bus][t].cl_status */ status = in_row ? row[t].cl_status : *(int *)((char *)row + row_bytes + offsetof_cl_status()); printf(" probe target %2d: read ciss_logical[0][%2d].cl_status" " = %-3d %s\n", t, t, status, in_row ? "" : "<== OOB (past row alloc)"); if (!in_row) oob++; } printf("\n"); if (oob) { printf("[BUG REPRODUCED] %d OOB read(s) of" " ciss_logical[bus][15] โ one struct past the row\n" "allocation, executed on every CAM bus scan. Deeper" " impact (garbage ONLINE ->\ncl_ldrive/cl_lstatus wild" " deref in ciss_cam_complete_fixup) is" " heap-layout-dependent.\n", oob); return 0; } printf("[FIXED] max_target = CISS_MAX_LOGICAL - 1: all probes in" " bounds.\n"); return 0; } static size_t offsetof_cl_status(void) { struct ld_status_rep r; return (size_t)((char *)&r.cl_status - (char *)&r); } |