Commit message: fix "pixel overrun"

fixes #3
CVE: CVE-2018-10717
CWE: CWE-119
Vulnerability description: The DecodeGifImg function in ngiflib.c in MiniUPnP ngiflib 0.4 does not consider the bounds of the pixels data structure, which allows remote attackers to cause a denial of service (WritePixels heap-based buffer overflow and application crash) or possibly have unspecified other impact via a crafted GIF file, a different vulnerability than CVE-2018-10677.

Project: ngiflib

int CheckGif(u8 * b) {
	return (b[0]=='G')&&(b[1]=='I')&&(b[2]=='F')&&(b[3]=='8');
}

Project: ngiflib

static void FillGifBackGround(struct ngiflib_gif * g) {
	long n = (long)g->width*g->height;
#ifndef NGIFLIB_INDEXED_ONLY
	u32 bg_truecolor;
#endif /* NGIFLIB_INDEXED_ONLY */

	if((g->frbuff.p8==NULL)||(g->palette==NULL)) return;
#ifndef NGIFLIB_INDEXED_ONLY
	if(g->mode & NGIFLIB_MODE_INDEXED) {
#endif /* NGIFLIB_INDEXED_ONLY */
		ngiflib_memset(g->frbuff.p8, g->backgroundindex, n);
#ifndef NGIFLIB_INDEXED_ONLY
	} else {
		u32 * p = g->frbuff.p32;
		bg_truecolor = GifIndexToTrueColor(g->palette, g->backgroundindex);
		while(n-->0) *p++ = bg_truecolor;
	}
#endif /* NGIFLIB_INDEXED_ONLY */
}

Project: ngiflib

static u8 GetByte(struct ngiflib_gif * g) {
#ifndef NGIFLIB_NO_FILE
	if(g->mode & NGIFLIB_MODE_FROM_MEM) {
#endif /* NGIFLIB_NO_FILE */
		return *(g->input.bytes++);
#ifndef NGIFLIB_NO_FILE
	} else {
		return (u8)(getc(g->input.file));
	}
#endif /* NGIFLIB_NO_FILE */
}

Project: ngiflib

static int GetByteStr(struct ngiflib_gif * g, u8 * p, int n) {
	if(!p) return -1;
#ifndef NGIFLIB_NO_FILE
	if(g->mode & NGIFLIB_MODE_FROM_MEM) {
#endif /* NGIFLIB_NO_FILE */
		ngiflib_memcpy(p, g->input.bytes, n);
		g->input.bytes += n;
		return 0;
#ifndef NGIFLIB_NO_FILE
	} else {
		size_t read;
		read = fread(p, 1, n, g->input.file);
		return ((int)read == n) ? 0 : -1;
	}
#endif /* NGIFLIB_NO_FILE */
}

Project: ngiflib

static u16 GetGifWord(struct ngiflib_img * i, struct ngiflib_decode_context * context) {
	u16 r;
	int bits_todo;
	u16 newbyte;

	bits_todo = (int)context->nbbit - (int)context->restbits;
	if( bits_todo <= 0) {	/* nbbit <= restbits */
		r = context->lbyte;
		context->restbits -= context->nbbit;
		context->lbyte >>= context->nbbit;
	} else if( bits_todo > 8 ) {	/* nbbit > restbits + 8 */
		if(context->restbyte >= 2) {
			context->restbyte -= 2;
			r = *context->srcbyte++;
		} else {
			if(context->restbyte == 0) {
				context->restbyte = GetByte(i->parent);
#if defined(DEBUG) && !defined(NGIFLIB_NO_FILE)
				if(i->parent->log) fprintf(i->parent->log, "restbyte = %02X\n", context->restbyte);
#endif /* defined(DEBUG) && !defined(NGIFLIB_NO_FILE) */
				GetByteStr(i->parent, context->byte_buffer, context->restbyte);
				context->srcbyte = context->byte_buffer;
			}
			r = *context->srcbyte++;
			if(--context->restbyte == 0) {
				context->restbyte = GetByte(i->parent);
#if defined(DEBUG) && !defined(NGIFLIB_NO_FILE)
				if(i->parent->log) fprintf(i->parent->log, "restbyte = %02X\n", context->restbyte);
#endif /* defined(DEBUG) && !defined(NGIFLIB_NO_FILE) */
				GetByteStr(i->parent, context->byte_buffer, context->restbyte);
				context->srcbyte = context->byte_buffer;
			}
			context->restbyte--;
		}
		newbyte = *context->srcbyte++;
		r |= newbyte << 8;
		r = (r << context->restbits) | context->lbyte;
		context->restbits = 16 - bits_todo;
		context->lbyte = newbyte >> (bits_todo - 8);
	} else /*if( bits_todo > 0 )*/ { /* nbbit > restbits */
		if(context->restbyte == 0) {
			context->restbyte = GetByte(i->parent);
#if defined(DEBUG) && !defined(NGIFLIB_NO_FILE)
			if(i->parent->log) fprintf(i->parent->log, "restbyte = %02X\n", context->restbyte);
#endif /* defined(DEBUG) && !defined(NGIFLIB_NO_FILE) */
			GetByteStr(i->parent, context->byte_buffer, context->restbyte);
			context->srcbyte = context->byte_buffer;
		}
		newbyte = *context->srcbyte++;
		context->restbyte--;
		r = (newbyte << context->restbits) | context->lbyte;
		context->restbits = 8 - bits_todo;
		context->lbyte = newbyte >> bits_todo;
	}
	return (r & context->max);	/* applique le bon masque pour eliminer les bits en trop */
}

Project: ngiflib

static u16 GetWord(struct ngiflib_gif * g) {
	u16 r = (u16)GetByte(g);
	r |= ((u16)GetByte(g) << 8);
	return r;
}

Project: ngiflib

void GifDestroy(struct ngiflib_gif * g) {
	if(g==NULL) return;
	if(g->palette) ngiflib_free(g->palette);
	if(g->frbuff.p8) ngiflib_free(g->frbuff.p8);
	GifImgDestroy(g->first_img);
	ngiflib_free(g);
}

Project: ngiflib

void GifImgDestroy(struct ngiflib_img * i) {
	if(i==NULL) return;
	if(i->next) GifImgDestroy(i->next);
	if(i->palette && (i->palette != i->parent->palette))
	  ngiflib_free(i->palette);
	ngiflib_free(i);
}

Project: ngiflib

u32 GifIndexToTrueColor(struct ngiflib_rgb * palette, u8 v) {
	return palette[v].b | (palette[v].g << 8) | (palette[v].r << 16);
}

Project: ngiflib

int LoadGif(struct ngiflib_gif * g) {
	struct ngiflib_gce gce;
	u8 sign;
	u8 tmp;
	int i;

	if(!g) return -1;
	gce.gce_present = 0;
	
	if(g->nimg==0) {
		GetByteStr(g, g->signature, 6);
		g->signature[6] = '\0';
		if(   g->signature[0] != 'G'
		   || g->signature[1] != 'I'
		   || g->signature[2] != 'F'
		   || g->signature[3] != '8') {
			return -1;
		}
#if !defined(NGIFLIB_NO_FILE)
		if(g->log) fprintf(g->log, "%s\n", g->signature);
#endif /* !defined(NGIFLIB_NO_FILE) */

		g->width = GetWord(g);
		g->height = GetWord(g);
		/* allocate frame buffer */
#ifndef NGIFLIB_INDEXED_ONLY
		if((g->mode & NGIFLIB_MODE_INDEXED)==0)
			g->frbuff.p32 = ngiflib_malloc(4*(long)g->height*(long)g->width);
		else
#endif /* NGIFLIB_INDEXED_ONLY */
			g->frbuff.p8 = ngiflib_malloc((long)g->height*(long)g->width);

		tmp = GetByte(g);/* <Packed Fields> = Global Color Table Flag       1 Bit
		                                      Color Resolution              3 Bits
		                                      Sort Flag                     1 Bit
		                                      Size of Global Color Table    3 Bits */
		g->colorresolution = ((tmp & 0x70) >> 4) + 1;
		g->sort_flag = (tmp & 8) >> 3;
		g->imgbits = (tmp & 7) + 1;	/* Global Palette color resolution */
		g->ncolors = 1 << g->imgbits;

		g->backgroundindex = GetByte(g);

#if !defined(NGIFLIB_NO_FILE)
		if(g->log) fprintf(g->log, "%hux%hu %hhubits %hu couleurs  bg=%hhu\n",
		                   g->width, g->height, g->imgbits, g->ncolors, g->backgroundindex);
#endif /* NGIFLIB_INDEXED_ONLY */

		g->pixaspectratio = GetByte(g);	/* pixel aspect ratio (0 : unspecified) */

		if(tmp&0x80) {
			/* la palette globale suit. */
			g->palette = (struct ngiflib_rgb *)ngiflib_malloc(sizeof(struct ngiflib_rgb)*g->ncolors);
			for(i=0; i<g->ncolors; i++) {
				g->palette[i].r = GetByte(g);
				g->palette[i].g = GetByte(g);
				g->palette[i].b = GetByte(g);
#if defined(DEBUG) && !defined(NGIFLIB_NO_FILE)
				if(g->log) fprintf(g->log, "%3d %02X %02X %02X\n", i, g->palette[i].r,g->palette[i].g,g->palette[i].b);
#endif /* defined(DEBUG) && !defined(NGIFLIB_NO_FILE) */
			}
#ifdef NGIFLIB_ENABLE_CALLBACKS
			if(g->palette_cb) g->palette_cb(g, g->palette, g->ncolors);
#endif /* NGIFLIB_ENABLE_CALLBACKS */
		} else {
			g->palette = NULL;
		}
		g->netscape_loop_count = -1;
	}

	for(;;) {
		char appid_auth[11];
		u8 id,size;
		int blockindex;

		sign = GetByte(g);	/* signature du prochain bloc */
#if !defined(NGIFLIB_NO_FILE)
		if(g->log) fprintf(g->log, "BLOCK SIGNATURE 0x%02X '%c'\n", sign, (sign >= 32) ? sign : '.');
#endif /* NGIFLIB_INDEXED_ONLY */
		switch(sign) {
		case 0x3B:	/* END OF GIF */
			return 0;
		case '!':	/* Extension introducer 0x21 */
			id = GetByte(g);
			blockindex = 0;
#if !defined(NGIFLIB_NO_FILE)
			if(g->log) fprintf(g->log, "extension (id=0x%02hhx)\n", id);
#endif /* NGIFLIB_NO_FILE */
			while( (size = GetByte(g)) ) {
				u8 ext[256];

				GetByteStr(g, ext, size);

				switch(id) {
				case 0xF9:	/* Graphic Control Extension */
					/* The scope of this extension is the first graphic
					 * rendering block to follow. */
					gce.gce_present = 1;
					gce.disposal_method = (ext[0] >> 2) & 7;
					gce.transparent_flag = ext[0] & 1;
					gce.user_input_flag = (ext[0] >> 1) & 1;
					gce.delay_time = ext[1] | (ext[2]<<8);
					gce.transparent_color = ext[3];
#if !defined(NGIFLIB_NO_FILE)
					if(g->log) fprintf(g->log, "disposal_method=%hhu delay_time=%hu (transp=%hhu)transparent_color=0x%02hhX\n",
					       gce.disposal_method, gce.delay_time, gce.transparent_flag, gce.transparent_color);
#endif /* NGIFLIB_INDEXED_ONLY */
					/* this propably should be adjusted depending on the disposal_method
					 * of the _previous_ image. */
					if(gce.transparent_flag && ((g->nimg == 0) || gce.disposal_method == 2)) {
						FillGifBackGround(g);
					}
					break;
				case 0xFE:	/* Comment Extension. */
#if !defined(NGIFLIB_NO_FILE)
					if(g->log) {
						if(blockindex==0) fprintf(g->log, "-------------------- Comment extension --------------------\n");
						ext[size] = '\0';
						fputs((char *)ext, g->log);
					}
#endif /* NGIFLIB_NO_FILE */
					break;
				case 0xFF:	/* application extension */
					/* NETSCAPE2.0 extension :
					 * http://www.vurdalakov.net/misc/gif/netscape-looping-application-extension */
					if(blockindex==0) {
						ngiflib_memcpy(appid_auth, ext, 11);
#if !defined(NGIFLIB_NO_FILE)
						if(g->log) {
							fprintf(g->log, "---------------- Application extension ---------------\n");
							fprintf(g->log, "Application identifier : '%.8s', auth code : %02X %02X %02X (",
							        appid_auth, ext[8], ext[9], ext[10]);
							fputc((ext[8]<32)?' ':ext[8], g->log);
							fputc((ext[9]<32)?' ':ext[9], g->log);
							fputc((ext[10]<32)?' ':ext[10], g->log);
							fprintf(g->log, ")\n");
						}
#endif /* NGIFLIB_INDEXED_ONLY */
					} else {
#if !defined(NGIFLIB_NO_FILE)
						if(g->log) {
							fprintf(g->log, "Datas (as hex) : ");
							for(i=0; i<size; i++) {
								fprintf(g->log, "%02x ", ext[i]);
							}
							fprintf(g->log, "\nDatas (as text) : '");
							for(i=0; i<size; i++) {
								putc((ext[i]<32)?' ':ext[i], g->log);
							}
							fprintf(g->log, "'\n");
						}
#endif /* NGIFLIB_INDEXED_ONLY */
						if(0 == ngiflib_memcmp(appid_auth, "NETSCAPE2.0", 11)) {
							/* ext[0] : Sub-block ID */
							if(ext[0] == 1) {
								/* 1 : Netscape Looping Extension. */
								g->netscape_loop_count = (int)ext[1] | ((int)ext[2] << 8);
#if !defined(NGIFLIB_NO_FILE)
								if(g->log) {
									fprintf(g->log, "NETSCAPE loop_count = %d\n", g->netscape_loop_count);
								}
#endif /* NGIFLIB_NO_FILE */
							}
						}
					}
					break;
				case 0x01:	/* plain text extension */
#if !defined(NGIFLIB_NO_FILE)
					if(g->log) {
						fprintf(g->log, "Plain text extension   blockindex=%d\n", blockindex);
						for(i=0; i<size; i++) {
							putc((ext[i]<32)?' ':ext[i], g->log);
						}
						putc('\n', g->log);
					}
#endif /* NGIFLIB_INDEXED_ONLY */
					break;
				}
				blockindex++;
			}
			switch(id) {
			case 0x01:	/* plain text extension */
			case 0xFE:	/* Comment Extension. */
			case 0xFF:	/* application extension */
#if !defined(NGIFLIB_NO_FILE)
				if(g->log) {
					fprintf(g->log, "-----------------------------------------------------------\n");
				}
#endif /* NGIFLIB_NO_FILE */
				break;
			}
			break;
		case 0x2C:	/* Image separator */
			if(g->nimg==0) {
				g->cur_img = ngiflib_malloc(sizeof(struct ngiflib_img));
				g->first_img = g->cur_img;
			} else {
				g->cur_img->next = ngiflib_malloc(sizeof(struct ngiflib_img));
				g->cur_img = g->cur_img->next;
			}
			g->cur_img->next = NULL;
			g->cur_img->parent = g;
			if(gce.gce_present) {
				ngiflib_memcpy(&g->cur_img->gce, &gce, sizeof(struct ngiflib_gce));
			} else {
				ngiflib_memset(&g->cur_img->gce, 0,  sizeof(struct ngiflib_gce));
			}
			DecodeGifImg(g->cur_img);
			g->nimg++;

			tmp = GetByte(g);/* 0 final */
#if !defined(NGIFLIB_NO_FILE)
			if(g->log) fprintf(g->log, "ZERO TERMINATOR 0x%02X\n", tmp);
#endif /* NGIFLIB_INDEXED_ONLY */
			return 1;	/* image decode */
		default:
			/* unexpected byte */
#if !defined(NGIFLIB_NO_FILE)
			if(g->log) fprintf(g->log, "unexpected signature 0x%02X\n", sign);
#endif /* NGIFLIB_INDEXED_ONLY */
			return -1;
		}
	}
}

Project: ngiflib

void fprintf_ngiflib_gif(FILE * f, struct ngiflib_gif * g) {
	struct ngiflib_img * i;
	fprintf(f, "* ngiflib_gif @ %p %s\n", g, g->signature);
	fprintf(f, "  %dx%d, %d bits, %d couleurs\n", g->width, g->height, g->imgbits, g->ncolors);
	fprintf(f, "  palette = %p, backgroundcolorindex %d\n", g->palette, g->backgroundindex);
	fprintf(f, "  pixelaspectratio = %d\n", g->pixaspectratio);
	fprintf(f, "  frbuff = %p\n", g->frbuff.p8);
	
	fprintf(f, "  cur_img = %p\n", g->cur_img);
	fprintf(f, "  %d images :\n", g->nimg);
	i = g->first_img;
	while(i) {
		fprintf_ngiflib_img(f, i);
		i = i->next;
	}
}

Project: ngiflib

void fprintf_ngiflib_img(FILE * f, struct ngiflib_img * i) {
	fprintf(f, "  * ngiflib_img @ %p\n", i);
	fprintf(f, "    next = %p\n", i->next);
	fprintf(f, "    parent = %p\n", i->parent);
	fprintf(f, "    palette = %p\n", i->palette);
	fprintf(f, "    %3d couleurs", i->ncolors);
	if(i->interlaced) fprintf(f, " interlaced");
	fprintf(f, "\n    taille : %dx%d, pos (%d,%d)\n", i->width, i->height, i->posX, i->posY);
	fprintf(f, "    sort_flag=%x localpalbits=%d\n", i->sort_flag, i->localpalbits);
}