DF-1503 / harness.c
/* * DF-1503 harness โ SETFKEY signed-flen heap/static overflow * * sys/dev/misc/kbd/kbd.c:1090 kbd->kb_fkeytab[keynum].len = * imin(fkeyp->flen, MAXFK); * ^ fkeyarg_t.flen is plain `char` (signed on * x86_64). flen=-1 -> imin(-1,16)=-1; assigned * to u_char .len -> wraps to 255. * :1091 bcopy(fkeyp->keydef, * kbd->kb_fkeytab[keynum].str, * kbd->kb_fkeytab[keynum].len); // 255! * ^ source keydef[16] (kbio.h:227) and dest * str[16] (kbio.h:220) are both 16 bytes; * bcopy(255) => 239 bytes OOB read of source * AND 239 bytes OOB write of dest. * * Reachability: gated by caps_priv_check_self(SYSCAP_RESTRICTEDROOT) at * kbd.c:672 and by the keyboard device being openable. /dev/kbd0 on the guest * is already kbd_allocate()d by syscons (the system console), so even root * gets EBUSY โ the in-kernel path is not exercisable here. Harness replicates * the genuine signed-flen arithmetic and the bcopy length outcome. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #define MAXFK 16 /* kbio.h:104 */ struct fkeytab { /* kbio.h:219 */ uint8_t str[MAXFK]; uint8_t len; }; struct fkeyarg { /* kbio.h:225 */ uint16_t keynum; char keydef[MAXFK]; char flen; }; /* imin from libkern: minimum of two ints. */ static int imin(int a, int b) { return a < b ? a : b; } int main(void) { int bad = 0; char tests[] = { 0, 1, 5, 15, 16, 17, 254, 255, (char)-1, (char)-2, (char)-128 }; printf("%-12s %12s %22s\n", "flen (char)", "imin result", "stored .len (u_char)"); for (size_t i = 0; i < sizeof(tests); i++) { struct fkeyarg a; memset(&a, 0, sizeof(a)); a.flen = tests[i]; /* line 1090 โ the bug: signed char in, u_char out */ int im = imin(a.flen, MAXFK); uint8_t stored = (uint8_t)im; printf("%-12d %12d %22u\n", (int)a.flen, im, (unsigned)stored); if (stored > MAXFK) bad++; } /* Demonstrate the exact buggy case */ struct fkeytab entry; memset(&entry, 0xcc, sizeof(entry)); /* poison */ entry.len = 0; struct fkeyarg a; memset(&a, 0, sizeof(a)); a.flen = -1; /* signed */ memset(a.keydef, 'A', sizeof(a.keydef)); /* 16 bytes user-supplied */ /* line 1090 */ entry.len = (uint8_t)imin(a.flen, MAXFK); printf("\nBad case flen=-1:\n"); printf(" imin(-1, %d) = %d\n", MAXFK, imin(a.flen, MAXFK)); printf(" stored entry.len = %u (wraps via u_char)\n", (unsigned)entry.len); /* Now bcopy(keydef, str, entry.len=255) โ both arrays are 16 bytes */ int bcopy_len = entry.len; int src_oob = bcopy_len - (int)sizeof(a.keydef); /* read past source */ int dst_oob = bcopy_len - (int)sizeof(entry.str); /* write past dest */ printf(" bcopy length = %d\n", bcopy_len); printf(" source keydef OOB = %d bytes (leaks user-side stack/heap)\n", src_oob); printf(" dest str OOB = %d bytes (overwrites adjacent fkeytab / " "kobj heap)\n", dst_oob); if (bad > 0 && entry.len == 255 && dst_oob > 0) { printf("\nCONFIRMED: SETFKEY with flen=-1 (or any negative char) " "yields entry.len=255 and bcopy(255) into 16-byte arrays -> " "%d-byte OOB heap/static write. atkbd uses static " "default_fkeytab[96] (.bss corruption); kbdmux/ukbd/vkbd use " "kmalloc (heap corruption). Trigger: open /dev/kbdN (root + " "RESTRICTEDROOT pass) and SETFKEY.\n", dst_oob); return 0; } fprintf(stderr,"NOT CONFIRMED\n"); return 1; } |