Commit message: Fixed bug #75571: Potential infinite loop in gdImageCreateFromGifCtx

Due to a signedness confusion in `GetCode_` a corrupt GIF file can
trigger an infinite loop.  Furthermore we make sure that a GIF without
any palette entries is treated as invalid *after* open palette entries
have been removed.
CVE: CVE-2018-5711
CWE: CWE-681
Vulnerability description: gd_gif_in.c in the GD Graphics Library (aka libgd), as used in PHP before 5.6.33, 7.0.x before 7.0.27, 7.1.x before 7.1.13, and 7.2.x before 7.2.1, has an integer signedness error that leads to an infinite loop via a crafted GIF file, as demonstrated by a call to the imagecreatefromgif or imagecreatefromstring PHP function. This is related to GetCode_ and gdImageCreateFromGifCtx.

Project: php-src
File: gd_gif_in.c

DoExtension(gdIOCtx *fd, int label, int *Transparent, int *ZeroDataBlockP)
{
	unsigned char buf[256];

	switch (label) {
		case 0xf9:              /* Graphic Control Extension */
			memset(buf, 0, 4); /* initialize a few bytes in the case the next function fails */
               (void) GetDataBlock(fd, (unsigned char*) buf, ZeroDataBlockP);
#if 0
			Gif89.disposal    = (buf[0] >> 2) & 0x7;
			Gif89.inputFlag   = (buf[0] >> 1) & 0x1;
			Gif89.delayTime   = LM_to_uint(buf[1],buf[2]);
#endif
			if ((buf[0] & 0x1) != 0)
				*Transparent = buf[3];

			while (GetDataBlock(fd, (unsigned char*) buf, ZeroDataBlockP) > 0);
			return FALSE;
		default:
			break;
	}
       while (GetDataBlock(fd, (unsigned char*) buf, ZeroDataBlockP) > 0)
		;

	return FALSE;
}

Project: php-src
File: gd_gif_in.c

GetDataBlock(gdIOCtx *fd, unsigned char *buf, int *ZeroDataBlockP)
{
	int rv;
	int i;

	rv = GetDataBlock_(fd,buf, ZeroDataBlockP);
	if (VERBOSE) {
		char *tmp = NULL;
		if (rv > 0) {
			tmp = safe_emalloc(3 * rv, sizeof(char), 1);
			for (i=0;i<rv;i++) {
				sprintf(&tmp[3*sizeof(char)*i], " %02x", buf[i]);
			}
		} else {
			tmp = estrdup("");
		}
		php_gd_error_ex(E_NOTICE, "[GetDataBlock returning %d: %s]", rv, tmp);
		efree(tmp);
	}
	return(rv);
}

Project: php-src
File: gd_gif_in.c

GetCode_(gdIOCtx *fd, CODE_STATIC_DATA *scd, int code_size, int flag, int *ZeroDataBlockP)
{
	int           i, j, ret;
	int           count;

	if (flag) {
		scd->curbit = 0;
		scd->lastbit = 0;
		scd->last_byte = 0;
		scd->done = FALSE;
		return 0;
	}

	if ( (scd->curbit + code_size) >= scd->lastbit) {
		if (scd->done) {
			if (scd->curbit >= scd->lastbit) {
				/* Oh well */
			}
			return -1;
		}
		scd->buf[0] = scd->buf[scd->last_byte-2];
		scd->buf[1] = scd->buf[scd->last_byte-1];

               if ((count = GetDataBlock(fd, &scd->buf[2], ZeroDataBlockP)) <= 0)
			scd->done = TRUE;

		scd->last_byte = 2 + count;
		scd->curbit = (scd->curbit - scd->lastbit) + 16;
		scd->lastbit = (2+count)*8 ;
	}

	if ((scd->curbit + code_size - 1) >= (CSD_BUF_SIZE * 8)) {
		ret = -1;
	} else {
		ret = 0;
		for (i = scd->curbit, j = 0; j < code_size; ++i, ++j) {
			ret |= ((scd->buf[i / 8] & (1 << (i % 8))) != 0) << j;
		}
	}

	scd->curbit += code_size;
	return ret;
}

Project: php-src
File: gd_gif_in.c

static int set_verbose(void)
{
	verbose = !!getenv("GIF_VERBOSE");
	verbose_set = 1;
	return(verbose);
}

Project: php-src
File: gd_gif_in.c

LWZReadByte(gdIOCtx *fd, LZW_STATIC_DATA *sd, char flag, int input_code_size, int *ZeroDataBlockP)
{
	int rv;

 rv = LWZReadByte_(fd, sd, flag, input_code_size, ZeroDataBlockP);
 if (VERBOSE) printf("[LWZReadByte(,%d,%d) returning %d]\n",flag,input_code_size,rv);
	return(rv);
}

Project: php-src
File: gd_gif_in.c

GetDataBlock_(gdIOCtx *fd, unsigned char *buf, int *ZeroDataBlockP)
{
	unsigned char   count;

	if (! ReadOK(fd,&count,1)) {
		return -1;
	}

	*ZeroDataBlockP = count == 0;

	if ((count != 0) && (! ReadOK(fd, buf, count))) {
		return -1;
	}

	return count;
}

Project: php-src
File: gd_gif_in.c

LWZReadByte_(gdIOCtx *fd, LZW_STATIC_DATA *sd, char flag, int input_code_size, int *ZeroDataBlockP)
{
	int code, incode, i;

	if (flag) {
		sd->set_code_size = input_code_size;
		sd->code_size = sd->set_code_size+1;
		sd->clear_code = 1 << sd->set_code_size ;
		sd->end_code = sd->clear_code + 1;
		sd->max_code_size = 2*sd->clear_code;
		sd->max_code = sd->clear_code+2;

		GetCode(fd, &sd->scd, 0, TRUE, ZeroDataBlockP);

		sd->fresh = TRUE;

		for (i = 0; i < sd->clear_code; ++i) {
			sd->table[0][i] = 0;
			sd->table[1][i] = i;
		}
		for (; i < (1<<MAX_LWZ_BITS); ++i)
			sd->table[0][i] = sd->table[1][0] = 0;

		sd->sp = sd->stack;

		return 0;
	} else if (sd->fresh) {
		sd->fresh = FALSE;
		do {
			sd->firstcode = sd->oldcode =
			GetCode(fd, &sd->scd, sd->code_size, FALSE, ZeroDataBlockP);
		} while (sd->firstcode == sd->clear_code);
		return sd->firstcode;
	}

	if (sd->sp > sd->stack)
		return *--sd->sp;

		while ((code = GetCode(fd, &sd->scd, sd->code_size, FALSE, ZeroDataBlockP)) >= 0) {
		if (code == sd->clear_code) {
			for (i = 0; i < sd->clear_code; ++i) {
				sd->table[0][i] = 0;
				sd->table[1][i] = i;
			}
			for (; i < (1<<MAX_LWZ_BITS); ++i)
				sd->table[0][i] = sd->table[1][i] = 0;
			sd->code_size = sd->set_code_size+1;
			sd->max_code_size = 2*sd->clear_code;
			sd->max_code = sd->clear_code+2;
			sd->sp = sd->stack;
			sd->firstcode = sd->oldcode =
								GetCode(fd, &sd->scd, sd->code_size, FALSE, ZeroDataBlockP);
			return sd->firstcode;
		} else if (code == sd->end_code) {
			int             count;
			unsigned char   buf[260];

			if (*ZeroDataBlockP)
				return -2;

			while ((count = GetDataBlock(fd, buf, ZeroDataBlockP)) > 0)
				;

			if (count != 0)
				return -2;
		}

		incode = code;

		if (sd->sp == (sd->stack + STACK_SIZE)) {
			/* Bad compressed data stream */
			return -1;
		}

		if (code >= sd->max_code) {
			*sd->sp++ = sd->firstcode;
			code = sd->oldcode;
		}

		while (code >= sd->clear_code) {
			if (sd->sp == (sd->stack + STACK_SIZE)) {
				/* Bad compressed data stream */
				return -1;
			}
			*sd->sp++ = sd->table[1][code];
			if (code == sd->table[0][code]) {
				/* Oh well */
			}
			code = sd->table[0][code];
		}

		*sd->sp++ = sd->firstcode = sd->table[1][code];

		if ((code = sd->max_code) <(1<<MAX_LWZ_BITS)) {
			sd->table[0][code] = sd->oldcode;
			sd->table[1][code] = sd->firstcode;
			++sd->max_code;
			if ((sd->max_code >= sd->max_code_size) &&
					(sd->max_code_size < (1<<MAX_LWZ_BITS))) {
				sd->max_code_size *= 2;
				++sd->code_size;
			}
		}

		sd->oldcode = incode;

		if (sd->sp > sd->stack)
			return *--sd->sp;
	}
	return code;
}

Project: php-src
File: gd_gif_in.c

gdImagePtr gdImageCreateFromGifSource(gdSourcePtr inSource) /* {{{ */
{
	gdIOCtx         *in = gdNewSSCtx(inSource, NULL);
	gdImagePtr      im;

	im = gdImageCreateFromGifCtx(in);

	in->gd_free(in);

	return im;
}

Project: php-src
File: gd_gif_in.c

GetCode(gdIOCtx *fd, CODE_STATIC_DATA *scd, int code_size, int flag, int *ZeroDataBlockP)
{
	int rv;

 rv = GetCode_(fd, scd, code_size,flag, ZeroDataBlockP);
 if (VERBOSE) printf("[GetCode(,%d,%d) returning %d]\n",code_size,flag,rv);
	return(rv);
}

Project: php-src
File: gd_gif_in.c

static int ReadColorMap(gdIOCtx *fd, int number, unsigned char (*buffer)[256]) /* {{{ */
{
	int             i;
	unsigned char   rgb[3];


	for (i = 0; i < number; ++i) {
		if (! ReadOK(fd, rgb, sizeof(rgb))) {
			return TRUE;
		}
		buffer[CM_RED][i] = rgb[0] ;
		buffer[CM_GREEN][i] = rgb[1] ;
		buffer[CM_BLUE][i] = rgb[2] ;
	}


	return FALSE;
}

Project: php-src
File: gd_gif_in.c

ReadImage(gdImagePtr im, gdIOCtx *fd, int len, int height, unsigned char (*cmap)[256], int interlace, int *ZeroDataBlockP) /*1.4//, int ignore) */
{
	unsigned char   c;
	int             v;
	int             xpos = 0, ypos = 0, pass = 0;
	int i;
	LZW_STATIC_DATA sd;


	/*
	 **  Initialize the Compression routines
	 */
	if (! ReadOK(fd,&c,1)) {
		return;
	}

	if (c > MAX_LWZ_BITS) {
		return;	
	}

	/* Stash the color map into the image */
	for (i=0; (i<gdMaxColors); i++) {
		im->red[i] = cmap[CM_RED][i];
		im->green[i] = cmap[CM_GREEN][i];
		im->blue[i] = cmap[CM_BLUE][i];
		im->open[i] = 1;
	}
	/* Many (perhaps most) of these colors will remain marked open. */
	im->colorsTotal = gdMaxColors;
	if (LWZReadByte(fd, &sd, TRUE, c, ZeroDataBlockP) < 0) {
		return;
	}

	/*
	 **  If this is an "uninteresting picture" ignore it.
	 **  REMOVED For 1.4
	 */
	/*if (ignore) { */
	/*        while (LWZReadByte(fd, &sd, FALSE, c) >= 0) */
	/*                ; */
	/*        return; */
	/*} */

	while ((v = LWZReadByte(fd, &sd, FALSE, c, ZeroDataBlockP)) >= 0) {
		if (v >= gdMaxColors) {
			v = 0;
		}
		/* This how we recognize which colors are actually used. */
		if (im->open[v]) {
			im->open[v] = 0;
		}
		gdImageSetPixel(im, xpos, ypos, v);
		++xpos;
		if (xpos == len) {
			xpos = 0;
			if (interlace) {
				switch (pass) {
					case 0:
					case 1:
						ypos += 8; break;
					case 2:
						ypos += 4; break;
					case 3:
						ypos += 2; break;
				}

				if (ypos >= height) {
					++pass;
					switch (pass) {
						case 1:
							ypos = 4; break;
						case 2:
							ypos = 2; break;
						case 3:
							ypos = 1; break;
						default:
							goto fini;
					}
				}
			} else {
				++ypos;
			}
		}
		if (ypos >= height)
			break;
	}

fini:
	if (LWZReadByte(fd, &sd, FALSE, c, ZeroDataBlockP) >=0) {
		/* Ignore extra */
	}
}

Project: php-src
File: gd_gif_in.c

gdImagePtr gdImageCreateFromGif(FILE *fdFile) /* {{{ */
{
	gdIOCtx		*fd = gdNewFileCtx(fdFile);
	gdImagePtr    	im = 0;

	im = gdImageCreateFromGifCtx(fd);

	fd->gd_free(fd);

	return im;
}

Project: php-src
File: gd_gif_in.c

gdImagePtr gdImageCreateFromGifCtx(gdIOCtxPtr fd) /* {{{ */
{
	int BitPixel;
#if 0
	int ColorResolution;
	int Background;
	int AspectRatio;
#endif
	int Transparent = (-1);
	unsigned char   buf[16];
	unsigned char   c;
	unsigned char   ColorMap[3][MAXCOLORMAPSIZE];
	unsigned char   localColorMap[3][MAXCOLORMAPSIZE];
	int             imw, imh, screen_width, screen_height;
	int             gif87a, useGlobalColormap;
	int             bitPixel;
	int	       i;
	/*1.4//int             imageCount = 0; */

	int ZeroDataBlock = FALSE;
	int haveGlobalColormap;
	gdImagePtr im = 0;

	memset(ColorMap, 0, 3 * MAXCOLORMAPSIZE);
	memset(localColorMap, 0, 3 * MAXCOLORMAPSIZE);

	/*1.4//imageNumber = 1; */
	if (! ReadOK(fd,buf,6)) {
		return 0;
	}
	if (strncmp((char *)buf,"GIF",3) != 0) {
		return 0;
	}

	if (memcmp((char *)buf+3, "87a", 3) == 0) {
		gif87a = 1;
	} else if (memcmp((char *)buf+3, "89a", 3) == 0) {
		gif87a = 0;
	} else {
		return 0;
	}

	if (! ReadOK(fd,buf,7)) {
		return 0;
	}

	BitPixel        = 2<<(buf[4]&0x07);
#if 0
	ColorResolution = (int) (((buf[4]&0x70)>>3)+1);
	Background      = buf[5];
	AspectRatio     = buf[6];
#endif
	screen_width = imw = LM_to_uint(buf[0],buf[1]);
	screen_height = imh = LM_to_uint(buf[2],buf[3]);

	haveGlobalColormap = BitSet(buf[4], LOCALCOLORMAP);    /* Global Colormap */
	if (haveGlobalColormap) {
		if (ReadColorMap(fd, BitPixel, ColorMap)) {
			return 0;
		}
	}

	for (;;) {
		int top, left;
		int width, height;

		if (! ReadOK(fd,&c,1)) {
			return 0;
		}
		if (c == ';') {         /* GIF terminator */
			goto terminated;
		}

		if (c == '!') {         /* Extension */
			if (! ReadOK(fd,&c,1)) {
				return 0;
			}
			DoExtension(fd, c, &Transparent, &ZeroDataBlock);
			continue;
		}

		if (c != ',') {         /* Not a valid start character */
			continue;
		}

		/*1.4//++imageCount; */

		if (! ReadOK(fd,buf,9)) {
			return 0;
		}

		useGlobalColormap = ! BitSet(buf[8], LOCALCOLORMAP);

		bitPixel = 1<<((buf[8]&0x07)+1);
		left = LM_to_uint(buf[0], buf[1]);
		top = LM_to_uint(buf[2], buf[3]);
		width = LM_to_uint(buf[4], buf[5]);
		height = LM_to_uint(buf[6], buf[7]);

		if (left + width > screen_width || top + height > screen_height) {
			if (VERBOSE) {
				printf("Frame is not confined to screen dimension.\n");
			}
			return 0;
		}

		if (!(im = gdImageCreate(width, height))) {
			return 0;
		}
		im->interlace = BitSet(buf[8], INTERLACE);
		if (!useGlobalColormap) {
			if (ReadColorMap(fd, bitPixel, localColorMap)) { 
				gdImageDestroy(im);
				return 0;
			}
			ReadImage(im, fd, width, height, localColorMap, 
					BitSet(buf[8], INTERLACE), &ZeroDataBlock);
		} else {
			if (!haveGlobalColormap) {
				gdImageDestroy(im);
				return 0;
			}
			ReadImage(im, fd, width, height,
						ColorMap, 
						BitSet(buf[8], INTERLACE), &ZeroDataBlock);
		}
		if (Transparent != (-1)) {
			gdImageColorTransparent(im, Transparent);
		}
		goto terminated;
	}

terminated:
	/* Terminator before any image was declared! */
	if (!im) {
		return 0;
	}
	/* Check for open colors at the end, so
	   we can reduce colorsTotal and ultimately
	   BitsPerPixel */
	for (i=((im->colorsTotal-1)); (i>=0); i--) {
		if (im->open[i]) {
			im->colorsTotal--;
		} else {
			break;
		}
	}
	if (!im->colorsTotal) {
		gdImageDestroy(im);
		return 0;
	}
	return im;
}