DF-1857 / harness.c
/* * DF-1857 source-confirmation harness (splash_bmp unbounded BMP/RLE read). * * sys/dev/video/fb/bmp/splash_bmp.c bmp_DecodeRLE8/RLE4 (lines ~307-410) * loop reading *info->index and advancing info->index with NO check that * index stays within [data, data+data_size). An RLE stream that omits the * end-of-bitmap escape (0x00 0x01) walks off the end of the splash image * into kernel memory, either (a) painting leaked kernel bytes to the * framebuffer (read back via /dev/fb0) or (b) hitting an unmapped page -> * panic during boot. * * bmp_Start (splash_bmp.c:80) only checks data_size<=0; never checks the * file is large enough for the BITMAPF header, let alone the declared * pixel array. bmp_Init (line 516) computes bmp_info.data = data + * bmf->bmfh.bfOffBits with bfOffBits taken from the attacker file and no * range check. width/height/depth from the header are likewise unchecked * (lines 519-521); only a screen-fit check at 540-547. * * Live trigger requires splash_bmp_load=YES at boot (boot-time) and * write access to the splash asset — not exercisable as maxx on this * guest. The harness models the RLE8 walk and shows it reads past the * data buffer when no end-of-bitmap escape is present. * * Build: cc -O2 -o harness harness.c * Run: ./harness */ #include <stdio.h> #include <stdlib.h> #include <string.h> struct bmp_info { const unsigned char *data; size_t data_size; const unsigned char *index; }; /* Model of bmp_DecodeRLE8 (splash_bmp.c ~373-410). The real loop runs * until it sees escape 0x00 0x01 (end-of-bitmap). With no escape, it * walks past data+data_size into whatever follows in kernel memory. */ static size_t rle8_walk(struct bmp_info *info) { size_t reads = 0; for (;;) { if ((size_t)(info->index - info->data) >= info->data_size + 4096) break; unsigned char cnt = info->index[0]; if (cnt != 0) { info->index += 2; reads += 2; continue; } unsigned char esc = info->index[1]; if (esc == 0) { info->index += 2; continue; } /* end of line */ if (esc == 1) return reads; /* end of bitmap */ if (esc == 2) { info->index += 4; continue; } /* delta */ info->index += 2 + ((esc + 1) & ~1); reads += 2 + ((esc + 1) & ~1); } return reads; } int main(void) { /* Real data: 64 bytes, NO end-of-bitmap escape. */ unsigned char data[64]; memset(data, 0x80, sizeof(data)); /* run/escape pattern that never hits 0x00 0x01 */ struct bmp_info info; info.data = data; info.data_size = sizeof(data); info.index = data; size_t consumed = rle8_walk(&info); long overrun = (long)(info.index - info.data) - (long)info.data_size; printf("DF-1857: bmp_DecodeRLE8 walk (splash_bmp.c:307-410)\n"); printf(" data_size=%zu bytes, no end-of-bitmap escape\n", info.data_size); printf(" final index offset = %ld bytes past data start\n", (long)(info.index - info.data)); printf(" OOB read = %ld bytes past the splash image into kernel memory\n", overrun); printf(" In the kernel this either paints leaked kmem to the framebuffer " "(/dev/fb0 leak) or hits an unmapped page and panics during boot.\n"); printf(" bmp_Start (splash_bmp.c:80) never validates data_size vs header; " "bmp_Init (:516) trusts bmfOffBits with no range check.\n"); return 0; } |