Commit message: Do not realloc array if new raster size is 0.

if realloc() is called with 0 size it may return NULL and this will be incorrectly handled
as not enough memory and (also) rasterBits will be freed by realloc but we will not update
it.
CVE: CVE-2019-11932
CWE: CWE-415
Vulnerability description: A double free vulnerability in the DDGifSlurp function in decoding.c in the android-gif-drawable library before version 1.2.18, as used in WhatsApp for Android before version 2.19.244 and many other Android applications, allows remote attackers to execute arbitrary code or cause a denial of service when the library is used to parse a specially crafted GIF image.

Project: android-gif-drawable
File: decoding.c

void DDGifSlurp(GifInfo *info, bool decode, bool exitAfterFrame) {
	GifRecordType RecordType;
	GifByteType *ExtData;
	int ExtFunction;
	GifFileType *gifFilePtr;
	gifFilePtr = info->gifFilePtr;
	uint_fast32_t lastAllocatedGCBIndex = 0;
	do {
		if (DGifGetRecordType(gifFilePtr, &RecordType) == GIF_ERROR) {
			break;
		}
		bool isInitialPass = !decode && !exitAfterFrame;
		switch (RecordType) {
			case IMAGE_DESC_RECORD_TYPE:

				if (DGifGetImageDesc(gifFilePtr, isInitialPass) == GIF_ERROR) {
					break;
				}

				if (isInitialPass) {
					int_fast32_t widthOverflow = gifFilePtr->Image.Width - gifFilePtr->SWidth;
					int_fast32_t heightOverflow = gifFilePtr->Image.Height - gifFilePtr->SHeight;
					if (widthOverflow > 0 || heightOverflow > 0) {
						gifFilePtr->SWidth += widthOverflow;
						gifFilePtr->SHeight += heightOverflow;
					}
					SavedImage *sp = &gifFilePtr->SavedImages[gifFilePtr->ImageCount - 1];
					int_fast32_t topOverflow = gifFilePtr->Image.Top + gifFilePtr->Image.Height - gifFilePtr->SHeight;
					if (topOverflow > 0) {
						sp->ImageDesc.Top -= topOverflow;
					}

					int_fast32_t leftOverflow = gifFilePtr->Image.Left + gifFilePtr->Image.Width - gifFilePtr->SWidth;
					if (leftOverflow > 0) {
						sp->ImageDesc.Left -= leftOverflow;
					}
					if (!updateGCB(info, &lastAllocatedGCBIndex)) {
						break;
					}
				}

				if (decode) {
					const uint_fast32_t newRasterSize = gifFilePtr->Image.Width * gifFilePtr->Image.Height;
					if (newRasterSize == 0) {
						free(info->rasterBits);
						info->rasterBits = NULL;
						info->rasterSize = newRasterSize;
						return;
					}
					const int_fast32_t widthOverflow = gifFilePtr->Image.Width - info->originalWidth;
					const int_fast32_t heightOverflow = gifFilePtr->Image.Height - info->originalHeight;
					if (newRasterSize > info->rasterSize || widthOverflow > 0 || heightOverflow > 0) {
						void *tmpRasterBits = reallocarray(info->rasterBits, newRasterSize, sizeof(GifPixelType));
						if (tmpRasterBits == NULL) {
							gifFilePtr->Error = D_GIF_ERR_NOT_ENOUGH_MEM;
							break;
						}
						info->rasterBits = tmpRasterBits;
						info->rasterSize = newRasterSize;
					}
					if (gifFilePtr->Image.Interlace) {
						uint_fast16_t i, j;
						/*
						 * The way an interlaced image should be read -
						 * offsets and jumps...
						 */
						uint_fast8_t InterlacedOffset[] = {0, 4, 2, 1};
						uint_fast8_t InterlacedJumps[] = {8, 8, 4, 2};
						/* Need to perform 4 passes on the image */
						for (i = 0; i < 4; i++)
							for (j = InterlacedOffset[i]; j < gifFilePtr->Image.Height; j += InterlacedJumps[i]) {
								if (DGifGetLine(gifFilePtr, info->rasterBits + j * gifFilePtr->Image.Width, gifFilePtr->Image.Width) == GIF_ERROR)
									break;
							}
					} else {
						if (DGifGetLine(gifFilePtr, info->rasterBits, gifFilePtr->Image.Width * gifFilePtr->Image.Height) == GIF_ERROR) {
							break;
						}
					}

					if (info->sampleSize > 1) {
						unsigned char *dst = info->rasterBits;
						unsigned char *src = info->rasterBits;
						unsigned char *const srcEndImage = info->rasterBits + gifFilePtr->Image.Width * gifFilePtr->Image.Height;
						do {
							unsigned char *srcNextLineStart = src + gifFilePtr->Image.Width * info->sampleSize;
							unsigned char *const srcEndLine = src + gifFilePtr->Image.Width;
							unsigned char *dstEndLine = dst + gifFilePtr->Image.Width / info->sampleSize;
							do {
								*dst = *src;
								dst++;
								src += info->sampleSize;
							} while (src < srcEndLine);
							dst = dstEndLine;
							src = srcNextLineStart;
						} while (src < srcEndImage);
					}
					return;
				} else {
					do {
						if (DGifGetCodeNext(gifFilePtr, &ExtData) == GIF_ERROR) {
							break;
						}
					} while (ExtData != NULL);
					if (exitAfterFrame) {
						return;
					}
				}
				break;

			case EXTENSION_RECORD_TYPE:
				if (DGifGetExtension(gifFilePtr, &ExtFunction, &ExtData) == GIF_ERROR) {
					break;
				}
				if (isInitialPass) {
					updateGCB(info, &lastAllocatedGCBIndex);
					if (readExtensions(ExtFunction, ExtData, info) == GIF_ERROR) {
						break;
					}
				}
				while (ExtData != NULL) {
					if (DGifGetExtensionNext(gifFilePtr, &ExtData) == GIF_ERROR) {
						break;
					}
					if (isInitialPass && readExtensions(ExtFunction, ExtData, info) == GIF_ERROR) {
						break;
					}
				}
				break;

			case TERMINATE_RECORD_TYPE:
				break;

			default: /* Should be trapped by DGifGetRecordType */
				break;
		}
	} while (RecordType != TERMINATE_RECORD_TYPE);

	info->rewindFunction(info);
}

Project: android-gif-drawable
File: decoding.c

static int getComment(GifByteType *Bytes, GifInfo *info) {
	unsigned int len = (unsigned int) Bytes[0];
	size_t offset = info->comment != NULL ? strlen(info->comment) : 0;
	char *ret = reallocarray(info->comment, len + offset + 1, sizeof(char));
	if (ret != NULL) {
		memcpy(ret + offset, &Bytes[1], len);
		ret[len + offset] = 0;
		info->comment = ret;
		return GIF_OK;
	}
	info->gifFilePtr->Error = D_GIF_ERR_NOT_ENOUGH_MEM;
	return GIF_ERROR;
}

Project: android-gif-drawable
File: decoding.c

static bool updateGCB(GifInfo *info, uint_fast32_t *lastAllocatedGCBIndex) {
	if (*lastAllocatedGCBIndex < info->gifFilePtr->ImageCount) {
		GraphicsControlBlock *tmpInfos = reallocarray(info->controlBlock, info->gifFilePtr->ImageCount + 1, sizeof(GraphicsControlBlock));
		if (tmpInfos == NULL) {
			info->gifFilePtr->Error = D_GIF_ERR_NOT_ENOUGH_MEM;
			return false;
		}
		*lastAllocatedGCBIndex = info->gifFilePtr->ImageCount;
		info->controlBlock = tmpInfos;
		setGCBDefaults(&info->controlBlock[info->gifFilePtr->ImageCount]);
	}
	return true;
}

Project: android-gif-drawable
File: decoding.c

static int readExtensions(int ExtFunction, GifByteType *ExtData, GifInfo *info) {
	if (ExtData == NULL) {
		return GIF_OK;
	}
	if (ExtFunction == GRAPHICS_EXT_FUNC_CODE) {
		GraphicsControlBlock *GCB = &info->controlBlock[info->gifFilePtr->ImageCount];
		if (DGifExtensionToGCB(ExtData[0], ExtData + 1, GCB) == GIF_ERROR) {
			return GIF_ERROR;
		}

		GCB->DelayTime = GCB->DelayTime > 1 ? GCB->DelayTime * 10 : DEFAULT_FRAME_DURATION_MS;
	}
	else if (ExtFunction == COMMENT_EXT_FUNC_CODE) {
		if (getComment(ExtData, info) == GIF_ERROR) {
			info->gifFilePtr->Error = D_GIF_ERR_NOT_ENOUGH_MEM;
			return GIF_ERROR;
		}
	}
	else if (ExtFunction == APPLICATION_EXT_FUNC_CODE) {
		char const *string = (char const *) (ExtData + 1);
		if (strncmp("NETSCAPE2.0", string, ExtData[0]) == 0
		    || strncmp("ANIMEXTS1.0", string, ExtData[0]) == 0) {
			if (DGifGetExtensionNext(info->gifFilePtr, &ExtData) == GIF_ERROR) {
				return GIF_ERROR;
			}
			if (ExtData && ExtData[0] == 3 && ExtData[1] == 1) {
				uint_fast16_t loopCount = (uint_fast16_t) (ExtData[2] + (ExtData[3] << 8));
				if (loopCount) {
					loopCount++;
				}
				info->loopCount = loopCount;
			}
		}
	}
	return GIF_OK;
}