Commit message: bb #6808
CVE: CVE-2013-2020
CWE: CWE-189
Vulnerability description: Integer underflow in the cli_scanpe function in pe.c in ClamAV before 0.97.8 allows remote attackers to cause a denial of service (crash) via a skewed offset larger than the size of the PE section in a UPX packed executable, which triggers an out-of-bounds read.

Project: clamav-devel
File: sis.c

static inline void seeknext(struct SISTREAM *s) {
  fseek(s->f, s->fnext[s->level], SEEK_SET);
  /*   s->sleft = s->smax = fread(s->buff,1,BUFSIZ,s->f); */
  s->sleft = s->smax = 0;
}

Project: clamav-devel
File: sis.c

static int spamsisnames(FILE *f, uint16_t langs, const char **alangs) {
  uint32_t *lens, *ptrs;
  unsigned int j;

  if (!(lens = cli_malloc(sizeof(uint32_t) * langs * 2))) {
    cli_dbgmsg("SIS: OOM\n");
    return 0;
  }
  if (fread(lens, sizeof(uint32_t) * langs * 2, 1, f)!=1) {
    cli_dbgmsg("SIS: Unable to read lengths and pointers\n");
    free(lens);
    return 1;
  }
  ptrs=&lens[langs];

  for (j=0; j<langs; j++) {
    char *name = getsistring(f,EC32(ptrs[j]),EC32(lens[j]));
    if (name) {
      cli_dbgmsg("\t%s (%s - @%x, len %d)\n", name, alangs[j], EC32(ptrs[j]), EC32(lens[j]));
      free(name);
    }
  }
  free(lens);
  return 1;
}

Project: clamav-devel
File: sis.c

static inline int skipthis(struct SISTREAM *s) {
  return skip(s, ALIGN4(s->fsize[s->level]));
}

Project: clamav-devel
File: sis.c

static inline int getsize(struct SISTREAM *s) {
  uint32_t *fsize = &s->fsize[s->level];
  if(getd(s, fsize) || !*fsize || (*fsize)>>31 || (s->level && *fsize > s->fsize[s->level-1] * 2)) return 1;
  /* To handle crafted archives we allow the content to overflow the container but only up to 2 times the container size */
  s->fnext[s->level] = ftell(s->f) - s->sleft + *fsize;
  return 0;
}

Project: clamav-devel
File: sis.c

static inline int getd(struct SISTREAM *s, uint32_t *v) {
  if (s->sleft<4) {
    memcpy(s->buff, s->buff + s->smax - s->sleft, s->sleft);
    if ((s->sleft=s->smax=fread(&s->buff[s->sleft], 1, BUFSIZ - s->sleft, s->f) + s->sleft)<4) {
      return 1;
    }
  }
  *v = cli_readint32(&s->buff[s->smax - s->sleft]);
  s->sleft-=4;
  return 0;
}

Project: clamav-devel
File: sis.c

int cli_scansis(int desc, cli_ctx *ctx) {
  FILE *f;
  int i;
  char *tmpd;
  uint32_t uid[4];

  cli_dbgmsg("in scansis()\n");

  if (!(tmpd = cli_gentemp(ctx->engine->tmpdir)))    
    return CL_ETMPDIR;
  if (mkdir(tmpd, 0700)) {
    cli_dbgmsg("SIS: Can't create temporary directory %s\n", tmpd);
    free(tmpd);
    return CL_ETMPDIR;
  }
  if (ctx->engine->keeptmp)
    cli_dbgmsg("SIS: Extracting files to %s\n", tmpd);

  if ((i=dup(desc))==-1) {
    cli_dbgmsg("SIS: dup() failed\n");
    cli_rmdirs(tmpd);
    free(tmpd);
    return CL_EDUP;
  }
  if (!(f=fdopen(i, "rb"))) {
    cli_dbgmsg("SIS: fdopen() failed\n");
    close(i);
    cli_rmdirs(tmpd);
    free(tmpd);
    return CL_EOPEN;
  }
  rewind(f);
  if (fread(uid, 16, 1, f)!=1) {
    cli_dbgmsg("SIS: unable to read UIDs\n");
    cli_rmdirs(tmpd);
    free(tmpd);
    fclose(f);
    return CL_EREAD;
  }

  cli_dbgmsg("SIS: UIDS %x %x %x - %x\n", EC32(uid[0]), EC32(uid[1]), EC32(uid[2]), EC32(uid[3]));
  if (uid[2]==le32_to_host(0x10000419)) {
    i=real_scansis(f, ctx, tmpd);
  }
  else if(uid[0]==le32_to_host(0x10201a7a)) {
    i=real_scansis9x(f, ctx, tmpd);
  }
  else {
    cli_dbgmsg("SIS: UIDs failed to match\n");
    i=CL_EFORMAT;
  }

  if (!ctx->engine->keeptmp)
    cli_rmdirs(tmpd);

  free(tmpd);
  fclose(f);
  return i;
}

Project: clamav-devel
File: sis.c

static inline int getfield(struct SISTREAM *s, uint32_t *field) {
  int ret;
  if(!(ret = getd(s, field)))
    ret = getsize(s);
  if(!ret) {
    if (*field<T_MAXVALUE)
      cli_dbgmsg("SIS: %d:Got %s(%x) field with size %x\n", s->level, sisfields[*field], *field, s->fsize[s->level]);
    else
      cli_dbgmsg("SIS: %d:Got invalid(%x) field with size %x\n", s->level, *field, s->fsize[s->level]);
  }
  return ret;
}

Project: clamav-devel
File: sis.c

static inline int skip(struct SISTREAM *s, uint32_t size) {
  long seekto;
  cli_dbgmsg("SIS: skipping %x\n", size);
  if (s->sleft>=size) s->sleft-=size;
  else {
    seekto = size - s->sleft;
    if (seekto<0) /* in case sizeof(long)==sizeof(uint32_t) */
      return 1;
    fseek(s->f, seekto, SEEK_CUR);
    /*     s->sleft = s->smax = fread(s->buff,1,BUFSIZ,s->f); */
    s->sleft = s->smax = 0;
  }
  return 0;
}

Project: clamav-devel
File: sis.c

static char *getsistring(FILE *f, uint32_t ptr, uint32_t len) {
  char *name;
  uint32_t i;

  if (!len) return NULL;
  if (len>400) len=400;
  name = cli_malloc(len+1);
  if (!name) {
    cli_dbgmsg("SIS: OOM\n");
    return NULL;
  }
  fseek(f, ptr, SEEK_SET);
  if (fread(name, len, 1, f)!=1) {
    cli_dbgmsg("SIS: Unable to read string\n");
    free(name);
    return NULL;
  }
  for (i = 0 ; i < len; i+=2) name[i/2] = name[i];
  name[i/2]='\0';
  return name;
}

Project: clamav-devel
File: sis.c

static int real_scansis9x(FILE *f, cli_ctx *ctx, const char *tmpd) {
  struct SISTREAM stream;
  struct SISTREAM *s = &stream;
  uint32_t field, optst[]={T_CONTROLLERCHECKSUM, T_DATACHECKSUM, T_COMPRESSED};
  unsigned int i;

  s->f = f;
  s->smax = 0;
  s->sleft = 0;
  s->level = 0;

  if (getfield(s, &field) || field!=T_CONTENTS)
    return CL_CLEAN;
  s->level++;

  for (i=0;i<3;) {
    if (getfield(s, &field)) return CL_CLEAN;
    for (;i<3;i++) {
      if (field==optst[i]) {
	if (skipthis(s)) return CL_CLEAN;
	i++;
	break;
      }
    }
  }
  if (field!=T_COMPRESSED) return CL_CLEAN;

  i=0;
  while (1) { /* 1DATA */
    if (getfield(s, &field) || field!=T_DATA) break;

    s->level++; 
    while(1) { /* DATA::ARRAY */
      uint32_t atype;
      if (getfield(s, &field) || field!=T_ARRAY || getd(s, &atype) || atype!=T_DATAUNIT || s->fsize[s->level]<4) break;
      s->fsize[s->level]-=4;

      s->level++;
      while (s->fsize[s->level-1] && !getsize(s)) { /* FOREACH DATA::ARRAY::DATAUNITs */
	cli_dbgmsg("SIS: %d:Got dataunit element with size %x\n", s->level, s->fsize[s->level]);
	if (ALIGN4(s->fsize[s->level]) < s->fsize[s->level-1]) 
	  s->fsize[s->level-1]-=ALIGN4(s->fsize[s->level]);
	else
	  s->fsize[s->level-1]=0;

	s->level++;
	while(1) { /* DATA::ARRAY::DATAUNIT[x]::ARRAY */
	  if(getfield(s, &field) || field!=T_ARRAY || getd(s, &atype) || atype!=T_FILEDATA || s->fsize[s->level]<4) break;
	  s->fsize[s->level]-=4;

	  s->level++;
	  while (s->fsize[s->level-1] && !getsize(s)) { /* FOREACH DATA::ARRAY::DATAUNIT[x]::ARRAY::FILEDATA */
	    uint32_t usize, usizeh;
	    void *src, *dst;
	    char tempf[1024];
	    uLongf uusize;
	    int fd;

	    cli_dbgmsg("SIS: %d:Got filedata element with size %x\n", s->level, s->fsize[s->level]);
	    if (ALIGN4(s->fsize[s->level]) < s->fsize[s->level-1]) 
	      s->fsize[s->level-1]-=ALIGN4(s->fsize[s->level]);
	    else
	      s->fsize[s->level-1]=0;

	    s->level++;
	    while(1) { /* DATA::ARRAY::DATAUNIT[x]::ARRAY::FILEDATA[x]::COMPRESSED */
	      if(getfield(s, &field) || field!=T_COMPRESSED || getd(s, &field) || getd(s, &usize) || getd(s, &usizeh) || usizeh) break;
	      s->fsize[s->level]-=12;
	      cli_dbgmsg("SIS: File is%s compressed - size %x -> %x\n", (field)?"":" not", s->fsize[s->level], usize);
	      snprintf(tempf, 1024, "%s"PATHSEP"sis9x%02d", tmpd, i++);
	      tempf[1023]='\0';
	      fseek(s->f, -(long)s->sleft, SEEK_CUR);
	      s->sleft = s->smax = 0;

	      if (cli_checklimits("sis", ctx,ALIGN4(s->fsize[s->level]), 0, 0)!=CL_CLEAN) break;
	      if (!(src=cli_malloc(ALIGN4(s->fsize[s->level])))) break;
	      if (fread(src, ALIGN4(s->fsize[s->level]),1,s->f) != 1) {
		free(src);
		break;
	      }

	      if(field) { /* compressed */
		int zresult;

		if (usize<=s->fsize[s->level]*3 && cli_checklimits("sis", ctx, s->fsize[s->level]*3, 0, 0)==CL_CLEAN)
		  uusize=s->fsize[s->level]*3;
		else if (cli_checklimits("sis", ctx, usize, 0, 0)==CL_CLEAN)
		  uusize=usize;
		else {
		  free(src);
		  break;
		}

		if (!(dst=cli_malloc(uusize))) {
		  free(src);
		  break;
		}
		zresult=uncompress(dst, &uusize, src, s->fsize[s->level]);
		free(src);
		if (zresult!=Z_OK) {
		  cli_dbgmsg("SIS: Inflate failure (%d)\n", zresult);
		  free(dst);
		  break;
		}
		if ((uLongf)usize != uusize)
		  cli_dbgmsg("SIS: Warning: expected size %lx but got %lx\n", (uLongf)usize, uusize);
		else
		  cli_dbgmsg("SIS: File successfully inflated\n");
	      } else { /* not compressed */
		dst = src;
		uusize = s->fsize[s->level];
	      }
	      if ((fd=open(tempf, O_RDWR|O_CREAT|O_TRUNC|O_BINARY, 0600))==-1) {
		cli_errmsg("SIS: unable to create output file %s - aborting.", tempf);
		free(dst);
		break;
	      }
	      if (cli_writen(fd, dst, uusize)!=(int)uusize) {
		free(dst);
		close(fd);
		break;
	      }
	      free(dst);
	      if (cli_magic_scandesc(fd, ctx) == CL_VIRUS) {
		close(fd);
		return CL_VIRUS;
	      }
	      close(fd);
	      break;
	    } /* DATA::ARRAY::DATAUNIT[x]::ARRAY::FILEDATA[x]::COMPRESSED */
	    s->level--;
	    seeknext(s);
	  } /* FOREACH DATA::ARRAY::DATAUNIT[x]::ARRAY::FILEDATAs */
	  s->level--;
	  break;
	} /* DATA::ARRAY::DATAUNIT[x]::ARRAY */
	s->level--;
	seeknext(s);
      } /* FOREACH DATA::ARRAY::DATAUNITs */
      s->level--;
      break;
    } /* DATA::ARRAY */
    s->level--;
    seeknext(s);
  }
  return CL_CLEAN;
}

Project: clamav-devel
File: sis.c

static int real_scansis(FILE *f, cli_ctx *ctx, const char *tmpd) {
  struct {
    uint16_t filesum;
    uint16_t langs;
    uint16_t files;
    uint16_t deps;
    uint16_t ulangs;
    uint16_t instfiles;
    uint16_t drive;
    uint16_t caps;
    uint32_t version;
    uint16_t flags;
    uint16_t type;
    uint16_t verhi;
    uint16_t verlo;
    uint32_t versub;
    uint32_t plangs;
    uint32_t pfiles;
    uint32_t pdeps;
    uint32_t pcerts;
    uint32_t pnames;
    uint32_t psig;
    uint32_t pcaps;
    uint32_t uspace;
    uint32_t nspace;
  } sis;
  const char **alangs;
  uint16_t *llangs;
  unsigned int i, sleft=0, smax=0, umped=0;
  uint8_t compd, buff[BUFSIZ];

  if (fread(&sis,sizeof(sis),1,f)!=1) {
    cli_dbgmsg("SIS: Unable to read header\n");
    return CL_CLEAN;
  }
  /*  cli_dbgmsg("SIS HEADER INFO: \nFile checksum: %x\nLangs: %d\nFiles: %d\nDeps: %d\nUsed langs: %d\nInstalled files: %d\nDest drive: %d\nCapabilities: %d\nSIS Version: %d\nFlags: %x\nType: %d\nVersion: %d.%d.%d\nLangs@: %x\nFiles@: %x\nDeps@: %x\nCerts@: %x\nName@: %x\nSig@: %x\nCaps@: %x\nUspace: %d\nNspace: %d\n\n", sis.filesum, sis.langs, sis.files, sis.deps, sis.ulangs, sis.instfiles, sis.drive, sis.caps, sis.version, sis.flags, sis.type, sis.verhi, sis.verlo, sis.versub, sis.plangs, sis.pfiles, sis.pdeps, sis.pcerts, sis.pnames, sis.psig, sis.pcaps, sis.uspace, sis.nspace);
   */

#if WORDS_BIGENDIAN != 0
  sis.langs=EC16(sis.langs);
  sis.files=EC16(sis.files);
  sis.deps=EC16(sis.deps);
  sis.flags=EC16(sis.flags);
  sis.plangs=EC32(sis.plangs);
  sis.pfiles=EC32(sis.pfiles);
  sis.pdeps=EC32(sis.pdeps);
  sis.pnames=EC32(sis.pnames);
  sis.pcaps=EC32(sis.pcaps);
#endif

  if (!sis.langs || sis.langs>=MAXLANG) {
    cli_dbgmsg("SIS: Too many or too few languages found\n");
    return CL_CLEAN;
  }
  fseek(f, sis.plangs, SEEK_SET);
  if (!(llangs=cli_malloc(sis.langs * sizeof(uint16_t)))) {
    cli_dbgmsg("SIS: OOM\n");
    return CL_CLEAN;
  }
  if (fread(llangs, sis.langs * sizeof(uint16_t), 1, f)!=1) {
    cli_dbgmsg("SIS: Unable to read languages\n");
    free(llangs);
    return CL_CLEAN;
  }
  if (!(alangs=cli_malloc(sis.langs * sizeof(char *)))) {
    cli_dbgmsg("SIS: OOM\n");
    free(llangs);
    return CL_CLEAN;
  }
  for (i = 0; i< sis.langs; i++)
    alangs[i]=EC32(llangs[i])<MAXLANG ? sislangs[EC32(llangs[i])] : sislangs[0];
  free(llangs);

  if (!sis.pnames) {
    cli_dbgmsg("SIS: Application without a name?\n");
  } else {
    fseek(f, sis.pnames, SEEK_SET);
    cli_dbgmsg("SIS: Application name:\n");
    if (!spamsisnames(f, sis.langs, alangs)) {
      free(alangs);
      return CL_EMEM;
    }
  }

  if (!sis.pcaps) {
    cli_dbgmsg("SIS: Application without capabilities?\n");
  } else {
    fseek(f, sis.pcaps, SEEK_SET);
    cli_dbgmsg("SIS: Provides:\n");
    if (!spamsisnames(f, sis.langs, alangs)) {
      free(alangs);
      return CL_EMEM;
    }
  }

  if (!sis.pdeps) {
    cli_dbgmsg("SIS: No dependencies set for this application\n");
  } else {
    cli_dbgmsg("SIS: Depends on:\n");
    for (i = 0; i< sis.deps; i++) {
      struct {
	uint32_t uid;
	uint16_t verhi;
	uint16_t verlo;
	uint32_t versub;
      } dep;
      
      fseek(f, sis.pdeps + i*(sizeof(dep) + sis.langs*2*sizeof(uint32_t)), SEEK_SET);
      if (fread(&dep, sizeof(dep), 1, f)!=1) {
	cli_dbgmsg("SIS: Unable to read dependencies\n");
      } else {
	cli_dbgmsg("\tUID: %x v. %d.%d.%d\n\taka:\n", EC32(dep.uid), EC16(dep.verhi), EC16(dep.verlo), EC32(dep.versub));
	if (!spamsisnames(f, sis.langs, alangs)) {
	  free(alangs);
	  return CL_EMEM;
	}
      }
    }
  }

  compd = !(sis.flags & 0x0008);
  cli_dbgmsg("SIS: Package is%s compressed\n", (compd)?"":" not");

  fseek(f, sis.pfiles, SEEK_SET);
  for (i=0; i<sis.files; i++) {
    uint32_t pkgtype, fcount=1;
    uint32_t j;

    GETD(pkgtype);
    cli_dbgmsg("SIS: Pkgtype: %d\n", pkgtype);
    switch(pkgtype) {
    case PKGlangfile:
      fcount=sis.langs;
    case PKGfile: {
      uint32_t ftype,options,ssname,psname,sdname,pdname;
      const char *sftype;
      uint32_t *ptrs, *lens, *olens;
      char *fn;
      long fpos;

      GETD(ftype);
      GETD(options);
      GETD(ssname);
      GETD(psname);
      GETD(sdname);
      GETD(pdname);
      switch(ftype) {
      case FTsimple:
	sftype = "simple";
	break;
      case FTtext:
	sftype = "text";
	break;
      case FTcomponent:
	sftype = "component";
	break;
      case FTrun:
	sftype = "run";
	break;
      case FTnull:
	sftype = "null";
	break;
      case FTmime:
	sftype = "mime";
	break;
      case FTsubsis:
	sftype = "sub sis";
	break;
      case FTcontsis:
	sftype = "conatiner sis";
	break;
      case FTtextuninst:
	sftype = "uninstall text";
	break;
      case FTnotinst:
	sftype = "not to be intalled";
	break;
      default:
	sftype = "unknown";
      }
      cli_dbgmsg("SIS: File details:\n\tOptions: %d\n\tType: %s\n", options, sftype);
      fpos = ftell(f);
      if ((fn=getsistring(f, psname, ssname))) {
	cli_dbgmsg("\tOriginal filename: %s\n", fn);
	free(fn);
      }
      if ((fn=getsistring(f, pdname, sdname))) {
	cli_dbgmsg("\tInstalled to: %s\n", fn);
	free(fn);
      }
      fseek(f,fpos,SEEK_SET);

      if (!(ptrs=cli_malloc(fcount*sizeof(uint32_t)*3))) {
	cli_dbgmsg("\tOOM\n");
	free(alangs);
	return CL_CLEAN;
      }
      lens=&ptrs[fcount];
      olens=&ptrs[fcount*2];
      for (j=0; j<fcount; j++)
	GETD2(lens[j]);
      for (j=0; j<fcount; j++)
	GETD2(ptrs[j]);
      for (j=0; j<fcount; j++)
	GETD2(olens[j]);
      
      if (ftype!=FTnull) {
	char ofn[1024];
	int fd;

	fpos = ftell(f);

	for (j=0; j<fcount; j++) {
	  void *comp, *decomp;
	  uLongf olen;

	  if (!lens[j]) {
	    cli_dbgmsg("\tSkipping empty file\n");
	    continue;
	  }
	  if (cli_checklimits("sis", ctx,lens[j], 0, 0)!=CL_CLEAN) continue;
	  cli_dbgmsg("\tUnpacking lang#%d - ptr:%x csize:%x osize:%x\n", j, ptrs[j], lens[j], olens[j]);
	  if (!(comp=cli_malloc(lens[j]))) {
	    cli_dbgmsg("\tOOM\n");
	    free(ptrs);
	    free(alangs);
	    return CL_CLEAN;
	  }
	  fseek(f,ptrs[j],SEEK_SET);
	  if (fread(comp, lens[j], 1, f)!=1) {
	    cli_dbgmsg("\tSkipping ghost or otherwise out of archive file\n");
	    free(comp);
	    continue;
	  }
	  if (compd) {
	    if (olens[j]<=lens[j]*3 && cli_checklimits("sis", ctx, lens[j]*3, 0, 0)==CL_CLEAN)
	      olen=lens[j]*3;
	    else if (cli_checklimits("sis", ctx, olens[j], 0, 0)==CL_CLEAN)
	      olen=olens[j];
	    else {
	      free(comp);
	      continue;
	    }
	      
	    if (!(decomp=cli_malloc(olen))) {
	      cli_dbgmsg("\tOOM\n");
	      free(comp);
	      free(ptrs);
	      free(alangs);
	      return CL_CLEAN;
	    }
	    if (uncompress(decomp, &olen, comp, lens[j])!=Z_OK) {
	      cli_dbgmsg("\tUnpacking failure\n");
	      free(comp);
	      free(decomp);
	      continue;
	    }
	    free(comp);
	  } else {
	    olen = lens[j];
	    decomp = comp;
	  }
	  snprintf(ofn, 1024, "%s"PATHSEP"sis%02d", tmpd, umped);
	  ofn[1023]='\0';
	  if ((fd=open(ofn, O_RDWR|O_CREAT|O_TRUNC|O_BINARY, 0600))==-1) {
	    cli_errmsg("SIS: unable to create output file %s - aborting.", ofn);
	    free(decomp);
	    free(ptrs);
	    free(alangs);
	    return CL_ECREAT;
	  }
	  if (cli_writen(fd, decomp, olen)!=(int)olen) {
	    close(fd);
	    free(decomp);
	    free(ptrs);
	    free(alangs);
	    return CL_EWRITE;
	  }
	  free(decomp);
	  if (cli_magic_scandesc(fd, ctx) == CL_VIRUS) {
	    close(fd);
	    free(ptrs);
	    free(alangs);
	    return CL_VIRUS;
	  }
	  close(fd);
	  umped++;
	}
	fseek(f,fpos,SEEK_SET);
      }
      free(ptrs);
      fcount=2*sizeof(uint32_t);
      break;
    }
    case PKGoption:
      cli_dbgmsg("SIS: I'm an option\n");
      GETD(fcount);
      fcount*=sis.langs*2*sizeof(uint32_t);
      break;
    case PKGif:
      cli_dbgmsg("SIS: #if\n");
      GETD(fcount);
      break;
    case PKGelsif:
      cli_dbgmsg("SIS: #elsif\n");
      GETD(fcount);
      break;
    case PKGelse:
      cli_dbgmsg("SIS: #else\n");
      fcount=0;
      break;
    case PKGendif:
      cli_dbgmsg("SIS: #endif\n");
      fcount=0;
      break;
    default:
      cli_dbgmsg("SIS: Unknown PKGtype, expect troubles\n");
      fcount=0;
    }
    SKIP(fcount);
  }

  free(alangs);
  return CL_CLEAN;
}