DF-4009 / prp_overwalk.c
/* * DF-4009 userspace replication โ NVMe PRP arena over-walk arithmetic * * mpr_build_nvme_prp() (sys/dev/raid/mpr/mpr.c:2680-2864, entered from * mpr_user.c:970 with fully user-controlled DataSize/DataOutSize) converts a * byte length into a linear walk of 8-byte PRP entries through ONE * contiguous arena: * * arena = PAGE_SIZE * pages_required * NVME_QDEPTH (mpr.c:1595-98) * pages_required = (maxio/PAGE_SIZE / 511) + 1 (mpr.c:1594) * NVME_QDEPTH = MPR_REQ_FRAMES = 2048 (mprvar.h:59,40) * * The walk (while(length) at mpr.c:2757) writes one 8-byte entry per 4096 * bytes of length, plus one extra list-pointer entry every 511 entries * (mpr.c:2765-2783), and NEVER checks the arena end โ it relies on * contiguity. This program computes exactly how far past the arena the * walk runs for a given DataSize and starting chunk index, replicating the * loop's arithmetic verbatim. * * Trigger reachability (no FW cooperation needed): the branch at * mpr_user.c:945 only tests the Function byte from the user header * (MPI2_FUNCTION_NVME_ENCAPSULATED = 0x33); mpr_build_nvme_prp() runs * BEFORE mpr_wait_command(), so the over-walk happens even if the IOC * later rejects the command โ the only requirement is the mpr driver * attached (PRP arena allocated) and ioctl access to /dev/mprN. */ #include <stdio.h> #include <stdint.h> #define PAGE_SIZE_ 4096UL #define PRP_ENTRY_SIZE 8UL #define PRPS_PER_PAGE ((PAGE_SIZE_ / PRP_ENTRY_SIZE) - 1) /* 511 */ #define NVME_QDEPTH 2048UL /* MPR_REQ_FRAMES */ int main(void) { unsigned long maxio_cases[] = { 128*1024, 256*1024, 1024*1024 }; unsigned long lengths[] = { 1UL<<20, 64UL<<20, 1UL<<30, 0xFFFFFFFFUL }; unsigned long chunks[] = { 0, 512, 1024, 1536, 2000 }; unsigned long maxio, arena, L, chunk; for (int m = 0; m < 3; m++) { maxio = maxio_cases[m]; /* mpr.c:1593-1598, verbatim arithmetic */ unsigned long prps_required = maxio / PAGE_SIZE_; unsigned long pages_required = (prps_required / PRPS_PER_PAGE) + 1; unsigned long prp_buffer_size = PAGE_SIZE_ * pages_required; arena = prp_buffer_size * NVME_QDEPTH; printf("maxio=%luKB -> pages_required=%lu arena=%lu bytes " "(%lu entries)\n", maxio/1024, pages_required, arena, arena / PRP_ENTRY_SIZE); for (int l = 0; l < 4; l++) { L = lengths[l]; /* verbatim loop accounting: one entry per page of * length, plus a list pointer per full PRP page */ unsigned long pages = (L + PAGE_SIZE_ - 1) / PAGE_SIZE_; unsigned long entries = pages + pages / PRPS_PER_PAGE; unsigned long walk_bytes = entries * PRP_ENTRY_SIZE; for (int c = 0; c < 5; c++) { chunk = chunks[c]; unsigned long in_arena = arena - chunk * prp_buffer_size; long oob = (long)walk_bytes - (long)in_arena; printf(" DataSize=%-10lu start_chunk=%-4lu " "walk=%-9lu in_arena=%-9lu OOB=%ld%s\n", L, chunk, walk_bytes, in_arena, oob, oob > 0 ? " <== OOB WRITE" : ""); } } printf("\n"); } printf("NOTE: kmalloc(DataSize) failure does NOT gate the walk: a\n" "NULL cm->cm_data only changes the written values (paddr=0),\n" "mpr_user.c:908 result is never checked.\n"); return (0); } |