DF-1359 / harness_overflow.c
/* * DF-1326 / DF-1359 userspace structural harness * * The live trigger requires /dev/mprN or /dev/mpsN (LSI SAS HBA PCI device), * which QEMU does not emulate, so the in-kernel primitive cannot be exercised * on the audit guest. This harness reproduces the *primitive* in userspace * by laying out memory the way mpr_user_pass_thru / mps_user_pass_thru do: * * MPI2_REQUEST_HEADER tmphdr; // 12 bytes on the function's stack * copyin(user_ptr, &tmphdr, data->RequestSize); // user-controlled size * * It proves that any RequestSize > 12 writes attacker-controlled bytes past * the 12-byte header (smashing whatever follows tmphdr on the stack), with * full byte-for-byte attacker control of the overflow. * * Build: cc -O2 -Wall -o harness_overflow harness_overflow.c * Run: ./harness_overflow * Expect: prints the overflow proof (12 in-bound bytes set, then N-MP past * the buffer also set to attacker bytes) and exits 0. */ #include <stdint.h> #include <string.h> #include <stdio.h> /* Verbatim from sys/dev/raid/mpr/mpi/mpi2.h:855 (12 bytes). */ typedef struct { uint16_t FunctionDependent1; /* 0x00 */ uint8_t ChainOffset; /* 0x02 */ uint8_t Function; /* 0x03 */ uint16_t FunctionDependent2; /* 0x04 */ uint8_t FunctionDependent3; /* 0x06 */ uint8_t MsgFlags; /* 0x07 */ uint8_t VP_ID; /* 0x08 */ uint8_t VF_ID; /* 0x09 */ uint16_t Reserved1; /* 0x0A */ } MPI2_REQUEST_HEADER; #define HDR_SZ (int)sizeof(MPI2_REQUEST_HEADER) int main(void) { printf("sizeof(MPI2_REQUEST_HEADER) = %d\n", HDR_SZ); if (HDR_SZ != 12) { printf("UNEXPECTED header size (claim was 12); aborting proof.\n"); return 1; } /* Simulate the kernel stack layout: tmphdr followed by whatever the * compiler placed next. We allocate a "stack frame" 64 bytes wide and * place tmphdr at offset 0, then simulate the unchecked copyin. */ enum { FRAME = 64 }; unsigned char frame[FRAME]; memset(frame, 0xCC, sizeof(frame)); /* pristine stack */ MPI2_REQUEST_HEADER *tmphdr = (MPI2_REQUEST_HEADER *)frame; /* Attacker payload: 64 bytes, fully controlled, marked with a recognizable * pattern that distinguishes in-band from out-of-band writes. */ unsigned char attacker[FRAME]; for (int i = 0; i < FRAME; i++) attacker[i] = (unsigned char)(0x41 + (i % 26)); /* The bug: copyin(ptr, &tmphdr, RequestSize) where RequestSize is * attacker-controlled. In the live kernel the user passes RequestSize = * e.g. 1024 to smash the stack frame. */ int RequestSize = FRAME; /* user->RequestSize from ioctl */ if (RequestSize > HDR_SZ) { printf("BUG: copyin %d bytes into %d-byte tmphdr overflows by %d bytes\n", RequestSize, HDR_SZ, RequestSize - HDR_SZ); } /* Simulate the unchecked copyin. */ memcpy(tmphdr, attacker, RequestSize); /* Report. */ printf("frame bytes after copyin (offset: value, source):\n"); for (int i = 0; i < FRAME; i++) { const char *cls = (i < HDR_SZ) ? "in-band (header)" : "OUT-OF-BAND (stack smashed)"; printf(" [%2d] 0x%02x %s\n", i, frame[i], cls); } int oob = 0; for (int i = HDR_SZ; i < FRAME; i++) if (frame[i] == attacker[i]) oob++; printf("\nPrimitive confirmed: %d of %d out-of-band bytes are " "attacker-controlled.\n", oob, FRAME - HDR_SZ); printf("In the kernel these bytes overwrite saved RBP/RIP/locals on the " "function's stack frame.\n"); return (oob == FRAME - HDR_SZ) ? 0 : 1; } |