DF-1090 / df1090_harness.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 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 | /* * DF-1090 — userspace harness for pnp_parse_desc OOB pattern. * * The kernel path (sys/bus/isa/pnpparse.c:pnp_parse_desc) is reachable * only via PnP hardware returning crafted TLV resources. The audit guest * has no PnP cards, so this harness mirrors the vulnerable algorithm * byte-for-byte (offset accesses only — no isa_config writes needed to * show the OOB) to demonstrate the primitive. * * It allocates a tiny "resource" buffer of the declared TLV length, then * exercises each affected descriptor type. With the unpatched algorithm, * the accesses blow straight past the buffer end. With the per-descriptor * length guards (the fix), each branch bails out cleanly. * * Build: cc -O0 -o df1090_harness df1090_harness.c * or: cc -O0 -DFIX -o df1090_harness_fix df1090_harness.c * Run: ./df1090_harness */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #define I16(p) ((p)[0] + ((p)[1] << 8)) #define I32(p) (I16(p) + (I16((p)+2) << 16)) /* "resource buffer" the kernel kmalloc'd; we allocate exactly len bytes so * any access at offset >= len is a real OOB. Malloc gives us a guard page * nearby so runaway reads SIGSEGV loudly. */ static unsigned char * make_res(int len) { unsigned char *p = calloc(1, len); if (!p) { perror("calloc"); exit(2); } return p; } static int oob_detected = 0; /* wrap each access so we can detect OOB without crashing */ #define READ_BYTE(buf, len, off) \ (((off) < (len)) ? (buf)[off] : (oob_detected = 1, 0xFF)) #define READ_I16(buf, len, off) \ (((off)+1 < (len)) ? I16((buf)+(off)) : (oob_detected = 1, 0xFFFF)) /* Mirror pnp_parse_desc for the 8 affected descriptor types. * 'FIX' = apply the per-descriptor len guard (the proposed fix). */ static void parse_desc(unsigned char tag, unsigned char *res, int len) { if (tag == 0) { /* PNP_TAG_COMPAT_DEVICE (small tag 3) needs len >= 4 */ #ifdef FIX if (len < 4) { printf(" [fix] skip COMPAT_DEVICE len=%d\n", len); return; } #endif (void)res[0]; (void)res[1]; (void)res[2]; (void)res[3]; /* bcopy 4 */ READ_BYTE(res, len, 0); READ_BYTE(res, len, 1); READ_BYTE(res, len, 2); READ_BYTE(res, len, 3); printf(" COMPAT_DEVICE len=%d: read [0..3]\n", len); return; } if (tag == 1) { /* PNP_TAG_IRQ_FORMAT needs len >= 2 */ #ifdef FIX if (len < 2) { printf(" [fix] skip IRQ_FORMAT len=%d\n", len); return; } #endif READ_I16(res, len, 0); printf(" IRQ_FORMAT len=%d: read I16(0)\n", len); return; } if (tag == 2) { /* PNP_TAG_DMA_FORMAT needs len >= 1 */ #ifdef FIX if (len < 1) { printf(" [fix] skip DMA_FORMAT len=%d\n", len); return; } #endif READ_BYTE(res, len, 0); printf(" DMA_FORMAT len=%d: read [0]\n", len); return; } if (tag == 3) { /* PNP_TAG_IO_RANGE needs len >= 7; also WRITES res[5]=1 if 0 */ #ifdef FIX if (len < 7) { printf(" [fix] skip IO_RANGE len=%d\n", len); return; } #endif READ_BYTE(res, len, 6); READ_I16(res, len, 1); READ_I16(res, len, 3); READ_BYTE(res, len, 5); /* the WRITE site */ printf(" IO_RANGE len=%d: read [6],I16(1),I16(3),[5] (5 is WRITE)\n", len); return; } if (tag == 4) { /* PNP_TAG_IO_FIXED needs len >= 3 */ #ifdef FIX if (len < 3) { printf(" [fix] skip IO_FIXED len=%d\n", len); return; } #endif READ_BYTE(res, len, 2); READ_I16(res, len, 0); printf(" IO_FIXED len=%d: read [2],I16(0)\n", len); return; } if (tag == 5) { /* PNP_TAG_MEMORY_RANGE needs len >= 9 */ #ifdef FIX if (len < 9) { printf(" [fix] skip MEMORY_RANGE len=%d\n", len); return; } #endif READ_I16(res, len, 7); READ_I16(res, len, 5); printf(" MEMORY_RANGE len=%d: read I16(7),I16(5)\n", len); return; } if (tag == 6) { /* PNP_TAG_MEMORY32_RANGE needs len >= 17 */ #ifdef FIX if (len < 17) { printf(" [fix] skip MEMORY32_RANGE len=%d\n", len); return; } #endif /* I32 reads 4 bytes each */ for (int off = 5; off <= 13; off += 4) for (int i = 0; i < 4; i++) READ_BYTE(res, len, off+i); printf(" MEMORY32_RANGE len=%d: read I32(5),I32(9),I32(13)\n", len); return; } if (tag == 7) { /* PNP_TAG_MEMORY32_FIXED needs len >= 9 */ #ifdef FIX if (len < 9) { printf(" [fix] skip MEMORY32_FIXED len=%d\n", len); return; } #endif for (int off = 1; off <= 5; off += 4) for (int i = 0; i < 4; i++) READ_BYTE(res, len, off+i); printf(" MEMORY32_FIXED len=%d: read I32(1),I32(5)\n", len); return; } } int main(void) { /* Each descriptor type fed a SHORT declared len vs the size the * algorithm actually accesses. The unpatched algorithm reads OOB. */ struct { const char *name; int tag; int decl_len; } cases[] = { { "COMPAT_DEVICE", 0, 1 }, /* needs 4 */ { "IRQ_FORMAT", 1, 1 }, /* needs 2 */ { "DMA_FORMAT", 2, 0 }, /* needs 1 */ { "IO_RANGE", 3, 3 }, /* needs 7 */ { "IO_FIXED", 4, 1 }, /* needs 3 */ { "MEMORY_RANGE", 5, 4 }, /* needs 9 */ { "MEMORY32_RANGE", 6, 5 }, /* needs 17 */ { "MEMORY32_FIXED", 7, 3 }, /* needs 9 */ }; printf("=== DF-1090 pnp_parse_desc OOB harness (%s) ===\n", #ifdef FIX "PATCHED" #else "UNPATCHED" #endif ); for (size_t i = 0; i < sizeof(cases)/sizeof(cases[0]); i++) { unsigned char *res = make_res(cases[i].decl_len); printf("[%s] tag=%d declared_len=%d:\n", cases[i].name, cases[i].tag, cases[i].decl_len); oob_detected = 0; parse_desc(cases[i].tag, res, cases[i].decl_len); printf(" -> %s (OOB %s)\n", oob_detected ? "OOB DETECTED" : "no OOB", oob_detected ? "WOULD HAPPEN on kernel kmalloc'd buffer" : ""); free(res); } return (0); } |