Commit message: Fixed a GIF decoding bug (divide by zero)

Fixes issue #15
CVE: CVE-2017-7962
CWE: CWE-369
Vulnerability description: The iwgif_read_image function in imagew-gif.c in libimageworsener.a in ImageWorsener 1.3.0 allows remote attackers to cause a denial of service (divide-by-zero error and application crash) via a crafted file.

Project: imageworsener

static int iwgif_init_screen(struct iwgifrcontext *rctx)
{
	struct iw_image *img;
	int bg_visible=0;
	int retval=0;

	if(rctx->screen_initialized) return 1;
	rctx->screen_initialized = 1;

	img = rctx->img;

	if(!rctx->include_screen) {
		rctx->screen_width = rctx->image_width;
		rctx->screen_height = rctx->image_height;
		rctx->image_left = 0;
		rctx->image_top = 0;
	}

	img->width = rctx->screen_width;
	img->height = rctx->screen_height;
	if(!iw_check_image_dimensions(rctx->ctx,img->width,img->height)) {
		return 0;
	}

	if(rctx->image_left>0 || rctx->image_top>0 ||
		(rctx->image_left+rctx->image_width < rctx->screen_width) ||
		(rctx->image_top+rctx->image_height < rctx->screen_height) )
	{
		bg_visible = 1;
	}

	if(rctx->has_transparency || bg_visible) {
		rctx->bytes_per_pixel=4;
		img->imgtype = IW_IMGTYPE_RGBA;
	}
	else {
		rctx->bytes_per_pixel=3;
		img->imgtype = IW_IMGTYPE_RGB;
	}
	img->bit_depth = 8;
	img->bpr = rctx->bytes_per_pixel * img->width;

	img->pixels = (iw_byte*)iw_malloc_large(rctx->ctx, img->bpr, img->height);
	if(!img->pixels) goto done;

	iw_zeromem(img->pixels,img->bpr*img->height);

	retval=1;
done:
	return retval;
}

Project: imageworsener

static int iwgif_make_row_pointers(struct iwgifrcontext *rctx)
{
	struct iw_image *img;
	int pass;
	int startrow, rowskip;
	int rowcount;
	int row;

	if(rctx->row_pointers) iw_free(rctx->ctx,rctx->row_pointers);
	rctx->row_pointers = (iw_byte**)iw_malloc(rctx->ctx, sizeof(iw_byte*)*rctx->image_height);
	if(!rctx->row_pointers) return 0;

	img = rctx->img;

	if(rctx->interlaced) {
		rowcount=0;
		for(pass=1;pass<=4;pass++) {
			if(pass==1) { startrow=0; rowskip=8; }
			else if(pass==2) { startrow=4; rowskip=8; }
			else if(pass==3) { startrow=2; rowskip=4; }
			else { startrow=1; rowskip=2; }

			for(row=startrow;row<rctx->image_height;row+=rowskip) {
				if(rctx->image_top+row < rctx->screen_height) {
					rctx->row_pointers[rowcount] = &img->pixels[(rctx->image_top+row)*img->bpr + (rctx->image_left)*rctx->bytes_per_pixel];
				}
				else {
					rctx->row_pointers[rowcount] = NULL;
				}
				rowcount++;
			}
		}
	}
	else {
		for(row=0;row<rctx->image_height;row++) {
			if(rctx->image_top+row < rctx->screen_height) {
				rctx->row_pointers[row] = &img->pixels[(rctx->image_top+row)*img->bpr + (rctx->image_left)*rctx->bytes_per_pixel];
			}
			else {
				rctx->row_pointers[row] = NULL;
			}
		}
	}
	return 1;
}

Project: imageworsener

static int iwgif_read(struct iwgifrcontext *rctx,
		iw_byte *buf, size_t buflen)
{
	int ret;
	size_t bytesread = 0;

	ret = (*rctx->iodescr->read_fn)(rctx->ctx,rctx->iodescr,
		buf,buflen,&bytesread);
	if(!ret || bytesread!=buflen) {
		return 0;
	}
	return 1;
}

Project: imageworsener

static int iwgif_read_color_table(struct iwgifrcontext *rctx, struct iw_palette *ct)
{
	int i;
	if(ct->num_entries<1) return 1;

	if(!iwgif_read(rctx,rctx->rbuf,3*ct->num_entries)) return 0;
	for(i=0;i<ct->num_entries;i++) {
		ct->entry[i].r = rctx->rbuf[3*i+0];
		ct->entry[i].g = rctx->rbuf[3*i+1];
		ct->entry[i].b = rctx->rbuf[3*i+2];
	}
	return 1;
}

Project: imageworsener

static int iwgif_read_file_header(struct iwgifrcontext *rctx)
{
	if(!iwgif_read(rctx,rctx->rbuf,6)) return 0;
	if(rctx->rbuf[0]!='G' || rctx->rbuf[1]!='I' || rctx->rbuf[2]!='F') {
		iw_set_error(rctx->ctx,"Not a GIF file");
		return 0;
	}
	return 1;
}

Project: imageworsener

static int iwgif_read_graphic_control_ext(struct iwgifrcontext *rctx)
{
	int retval;

	if(!iwgif_read(rctx,rctx->rbuf,6)) goto done;

	if(rctx->rbuf[0]!=4) goto done;
	if(rctx->rbuf[5]!=0) goto done;
	rctx->has_transparency = (int)((rctx->rbuf[1])&0x01);
	if(rctx->has_transparency) {
		rctx->trans_color_index = (int)rctx->rbuf[4];
	}

	retval=1;
done:
	return retval;
}

Project: imageworsener

static int iwgif_read_main(struct iwgifrcontext *rctx)
{
	int retval=0;
	int i;
	int image_found=0;

	for(i=0;i<256;i++) {
		rctx->colortable.entry[i].a=255;
	}

	if(!iwgif_read_file_header(rctx)) goto done;

	if(!iwgif_read_screen_descriptor(rctx)) goto done;

	if(!iwgif_read_color_table(rctx,&rctx->colortable)) goto done;

	if(rctx->has_bg_color) {
		iw_set_input_bkgd_label(rctx->ctx,
			((double)rctx->colortable.entry[rctx->bg_color_index].r)/255.0,
			((double)rctx->colortable.entry[rctx->bg_color_index].g)/255.0,
			((double)rctx->colortable.entry[rctx->bg_color_index].b)/255.0);
	}


	while(!image_found) {
		if(!iwgif_read(rctx,rctx->rbuf,1)) goto done;

		switch(rctx->rbuf[0]) {
		case 0x21: // extension
			if(!iwgif_read_extension(rctx)) goto done;
			break;
		case 0x2c: // image
			rctx->pages_seen++;
			if(rctx->page == rctx->pages_seen) {
				if(!iwgif_read_image(rctx)) goto done;
				image_found=1;
			}
			else {
				if(!iwgif_skip_image(rctx)) goto done;
			}
			break;
		case 0x3b: // file trailer
			if(rctx->pages_seen==0)
				iw_set_error(rctx->ctx,"No image in file");
			else
				iw_set_error(rctx->ctx,"Image not found");
			goto done;
		default:
			iw_set_error(rctx->ctx,"Invalid or unsupported GIF file");
			goto done;
		}
	}

	retval=1;

done:
	return retval;
}

Project: imageworsener

static void iwgif_record_pixel(struct iwgifrcontext *rctx, unsigned int coloridx,
		int offset)
{
	struct iw_image *img;
	unsigned int r,g,b,a;
	size_t pixnum;
	size_t xi,yi; // position in image coordinates
	size_t xs,ys; // position in screen coordinates
	iw_byte *ptr;

	img = rctx->img;


	pixnum = rctx->pixels_set + offset;
	xi = pixnum%rctx->image_width;
	yi = pixnum/rctx->image_width;
	xs = rctx->image_left + xi;
	ys = rctx->image_top + yi;

	if(yi>=(size_t)rctx->image_height) return;
	if(xs>=(size_t)rctx->screen_width) return;
	if(ys>=(size_t)rctx->screen_height) return;

	if(rctx->row_pointers[yi]==NULL) return;


	if(coloridx<(unsigned int)rctx->colortable.num_entries) {
		r=rctx->colortable.entry[coloridx].r;
		g=rctx->colortable.entry[coloridx].g;
		b=rctx->colortable.entry[coloridx].b;
		a=rctx->colortable.entry[coloridx].a;
	}
	else {
		return; // Illegal palette index
	}


	ptr = &rctx->row_pointers[yi][rctx->bytes_per_pixel*xi];
	ptr[0]=r; ptr[1]=g; ptr[2]=b;
	if(img->imgtype==IW_IMGTYPE_RGBA) {
		ptr[3]=a;
	}
}

Project: imageworsener

static int iwgif_skip_image(struct iwgifrcontext *rctx)
{
	int has_local_ct;
	int local_ct_size;
	int ct_num_entries;
	int retval=0;

	if(!iwgif_read(rctx,rctx->rbuf,9)) goto done;

	has_local_ct = (int)((rctx->rbuf[8]>>7)&0x01);
	if(has_local_ct) {
		local_ct_size = (int)(rctx->rbuf[8]&0x07);
		ct_num_entries = 1<<(1+local_ct_size);
	}

	if(has_local_ct) {
		if(!iwgif_read(rctx,rctx->rbuf,3*ct_num_entries)) goto done;
	}

	if(!iwgif_read(rctx,rctx->rbuf,1)) goto done;

	if(!iwgif_skip_subblocks(rctx)) goto done;

	rctx->has_transparency = 0;

	retval=1;

done:
	return retval;
}

Project: imageworsener

static int lzw_add_to_dict(struct lzwdeccontext *d, unsigned int oldcode, iw_byte val)
{
	static const unsigned int last_code_of_size[] = {
		0,0,0,7,15,31,63,127,255,511,1023,2047,4095
	};
	unsigned int newpos;

	if(d->ct_used>=4096) {
		d->last_code_added = 0;
		return 0;
	}

	newpos = d->ct_used;
	d->ct_used++;

	d->ct[newpos].parent = (iw_uint16)oldcode;
	d->ct[newpos].length = d->ct[oldcode].length + 1;
	d->ct[newpos].firstchar = d->ct[oldcode].firstchar;
	d->ct[newpos].lastchar = val;

	if(newpos == last_code_of_size[d->current_codesize]) {
		if(d->current_codesize<12) {
			d->current_codesize++;
		}
	}

	d->last_code_added = newpos;
	return 1;
}

Project: imageworsener

static void lzw_clear(struct lzwdeccontext *d)
{
	d->ct_used = d->num_root_codes+2;
	d->current_codesize = d->root_codesize+1;
	d->ncodes_since_clear=0;
	d->oldcode=0;
}

Project: imageworsener

static void lzw_emit_code(struct iwgifrcontext *rctx, struct lzwdeccontext *d,
		unsigned int first_code)
{
	unsigned int code;
	code = first_code;


	while(1) {
		iwgif_record_pixel(rctx, (unsigned int)d->ct[code].lastchar, (int)(d->ct[code].length-1));
		if(d->ct[code].length<=1) break;
		code = (unsigned int)d->ct[code].parent;
	}

	rctx->pixels_set += d->ct[first_code].length;
}

Project: imageworsener

static void lzw_init(struct lzwdeccontext *d, unsigned int root_codesize)
{
	unsigned int i;

	iw_zeromem(d,sizeof(struct lzwdeccontext));

	d->root_codesize = root_codesize;
	d->num_root_codes = 1<<d->root_codesize;
	d->clear_code = d->num_root_codes;
	d->eoi_code = d->num_root_codes+1;
	for(i=0;i<d->num_root_codes;i++) {
		d->ct[i].parent = 0;
		d->ct[i].length = 1;
		d->ct[i].lastchar = (iw_byte)i;
		d->ct[i].firstchar = (iw_byte)i;
	}
}

Project: imageworsener

static int lzw_process_bytes(struct iwgifrcontext *rctx, struct lzwdeccontext *d,
	iw_byte *data, size_t data_size)
{
	size_t i;
	int b;
	int retval=0;

	for(i=0;i<data_size;i++) {
		for(b=0;b<8;b++) {
			if(d->eoi_flag) { // Stop if we've seen an EOI (end of image) code.
				retval=1;
				goto done;
			}

			if(data[i]&(1<<b))
				d->pending_code |= 1<<d->bits_in_pending_code;
			d->bits_in_pending_code++;

			if(d->bits_in_pending_code >= d->current_codesize) {
				if(!lzw_process_code(rctx,d,d->pending_code)) goto done;
				d->pending_code=0;
				d->bits_in_pending_code=0;
			}
		}
	}
	retval=1;

done:
	return retval;
}

Project: imageworsener

static int lzw_process_code(struct iwgifrcontext *rctx, struct lzwdeccontext *d,
		unsigned int code)
{
	if(code==d->eoi_code) {
		d->eoi_flag=1;
		return 1;
	}

	if(code==d->clear_code) {
		lzw_clear(d);
		return 1;
	}

	d->ncodes_since_clear++;

	if(d->ncodes_since_clear==1) {
		lzw_emit_code(rctx,d,code);
		d->oldcode = code;
		return 1;
	}

	if(code < d->ct_used) {
		lzw_emit_code(rctx,d,code);

		lzw_add_to_dict(d,d->oldcode,d->ct[code].firstchar);
	}
	else {
		if(d->oldcode>=d->ct_used) {
			iw_set_error(rctx->ctx,"GIF decoding error");
			return 0;
		}

		if(lzw_add_to_dict(d,d->oldcode,d->ct[d->oldcode].firstchar)) {
			lzw_emit_code(rctx,d,d->last_code_added);
		}
	}
	d->oldcode = code;

	return 1;
}