/*
 * 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;
}
