Speed-up chunk list operations.

The chunk list only has two operations: append and set
to one element. The two operations are split and the append
one is sped up by storing the last element.
Corrupted data could make a very long list to search through.

BUG=oss-fuzz:9190

Change-Id: I1aa813ca629df29efaa3b46dbd4c4c42dbeaa34c
This commit is contained in:
Vincent Rabaud 2018-07-03 16:36:34 +02:00
parent 2281bbf6f7
commit a9ceda7ff1
4 changed files with 54 additions and 46 deletions

View File

@ -69,12 +69,12 @@ void WebPMuxDelete(WebPMux* mux) {
if (idx == (INDEX)) { \ if (idx == (INDEX)) { \
err = ChunkAssignData(&chunk, data, copy_data, tag); \ err = ChunkAssignData(&chunk, data, copy_data, tag); \
if (err == WEBP_MUX_OK) { \ if (err == WEBP_MUX_OK) { \
err = ChunkSetNth(&chunk, (LIST), nth); \ err = ChunkSetHead(&chunk, (LIST)); \
} \ } \
return err; \ return err; \
} }
static WebPMuxError MuxSet(WebPMux* const mux, uint32_t tag, uint32_t nth, static WebPMuxError MuxSet(WebPMux* const mux, uint32_t tag,
const WebPData* const data, int copy_data) { const WebPData* const data, int copy_data) {
WebPChunk chunk; WebPChunk chunk;
WebPMuxError err = WEBP_MUX_NOT_FOUND; WebPMuxError err = WEBP_MUX_NOT_FOUND;
@ -190,7 +190,7 @@ WebPMuxError WebPMuxSetChunk(WebPMux* mux, const char fourcc[4],
if (err != WEBP_MUX_OK && err != WEBP_MUX_NOT_FOUND) return err; if (err != WEBP_MUX_OK && err != WEBP_MUX_NOT_FOUND) return err;
// Add the given chunk. // Add the given chunk.
return MuxSet(mux, tag, 1, chunk_data, copy_data); return MuxSet(mux, tag, chunk_data, copy_data);
} }
// Creates a chunk from given 'data' and sets it as 1st chunk in 'chunk_list'. // Creates a chunk from given 'data' and sets it as 1st chunk in 'chunk_list'.
@ -202,7 +202,7 @@ static WebPMuxError AddDataToChunkList(
ChunkInit(&chunk); ChunkInit(&chunk);
err = ChunkAssignData(&chunk, data, copy_data, tag); err = ChunkAssignData(&chunk, data, copy_data, tag);
if (err != WEBP_MUX_OK) goto Err; if (err != WEBP_MUX_OK) goto Err;
err = ChunkSetNth(&chunk, chunk_list, 1); err = ChunkSetHead(&chunk, chunk_list);
if (err != WEBP_MUX_OK) goto Err; if (err != WEBP_MUX_OK) goto Err;
return WEBP_MUX_OK; return WEBP_MUX_OK;
Err: Err:
@ -342,7 +342,7 @@ WebPMuxError WebPMuxSetAnimationParams(WebPMux* mux,
// Set the animation parameters. // Set the animation parameters.
PutLE32(data, params->bgcolor); PutLE32(data, params->bgcolor);
PutLE16(data + 4, params->loop_count); PutLE16(data + 4, params->loop_count);
return MuxSet(mux, kChunks[IDX_ANIM].tag, 1, &anim, 1); return MuxSet(mux, kChunks[IDX_ANIM].tag, &anim, 1);
} }
WebPMuxError WebPMuxSetCanvasSize(WebPMux* mux, WebPMuxError WebPMuxSetCanvasSize(WebPMux* mux,
@ -540,7 +540,7 @@ static WebPMuxError CreateVP8XChunk(WebPMux* const mux) {
PutLE24(data + 4, width - 1); // canvas width. PutLE24(data + 4, width - 1); // canvas width.
PutLE24(data + 7, height - 1); // canvas height. PutLE24(data + 7, height - 1); // canvas height.
return MuxSet(mux, kChunks[IDX_VP8X].tag, 1, &vp8x, 1); return MuxSet(mux, kChunks[IDX_VP8X].tag, &vp8x, 1);
} }
// Cleans up 'mux' by removing any unnecessary chunks. // Cleans up 'mux' by removing any unnecessary chunks.

View File

@ -127,11 +127,14 @@ WebPChunk* ChunkSearchList(WebPChunk* first, uint32_t nth, uint32_t tag);
WebPMuxError ChunkAssignData(WebPChunk* chunk, const WebPData* const data, WebPMuxError ChunkAssignData(WebPChunk* chunk, const WebPData* const data,
int copy_data, uint32_t tag); int copy_data, uint32_t tag);
// Sets 'chunk' at nth position in the 'chunk_list'. // Sets 'chunk' as the only element in 'chunk_list' if it is empty.
// nth = 0 has the special meaning "last of the list".
// On success ownership is transferred from 'chunk' to the 'chunk_list'. // On success ownership is transferred from 'chunk' to the 'chunk_list'.
WebPMuxError ChunkSetNth(WebPChunk* chunk, WebPChunk** chunk_list, WebPMuxError ChunkSetHead(WebPChunk* chunk, WebPChunk** chunk_list);
uint32_t nth); // Sets 'chunk' at last position in the 'chunk_list'.
// On success ownership is transferred from 'chunk' to the 'chunk_list'.
// *chunk_list also points towards the last valid element of the initial
// *chunk_list.
WebPMuxError ChunkAppend(WebPChunk* chunk, WebPChunk*** chunk_list);
// Releases chunk and returns chunk->next_. // Releases chunk and returns chunk->next_.
WebPChunk* ChunkRelease(WebPChunk* const chunk); WebPChunk* ChunkRelease(WebPChunk* const chunk);

View File

@ -111,27 +111,6 @@ WebPChunk* ChunkSearchList(WebPChunk* first, uint32_t nth, uint32_t tag) {
return ((nth > 0) && (iter > 0)) ? NULL : first; return ((nth > 0) && (iter > 0)) ? NULL : first;
} }
// Outputs a pointer to 'prev_chunk->next_',
// where 'prev_chunk' is the pointer to the chunk at position (nth - 1).
// Returns true if nth chunk was found.
static int ChunkSearchListToSet(WebPChunk** chunk_list, uint32_t nth,
WebPChunk*** const location) {
uint32_t count = 0;
assert(chunk_list != NULL);
*location = chunk_list;
while (*chunk_list != NULL) {
WebPChunk* const cur_chunk = *chunk_list;
++count;
if (count == nth) return 1; // Found.
chunk_list = &cur_chunk->next_;
*location = chunk_list;
}
// *chunk_list is ok to be NULL if adding at last location.
return (nth == 0 || (count == nth - 1)) ? 1 : 0;
}
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Chunk writer methods. // Chunk writer methods.
@ -156,11 +135,12 @@ WebPMuxError ChunkAssignData(WebPChunk* chunk, const WebPData* const data,
return WEBP_MUX_OK; return WEBP_MUX_OK;
} }
WebPMuxError ChunkSetNth(WebPChunk* chunk, WebPChunk** chunk_list, WebPMuxError ChunkSetHead(WebPChunk* const chunk,
uint32_t nth) { WebPChunk** const chunk_list) {
WebPChunk* new_chunk; WebPChunk* new_chunk;
if (!ChunkSearchListToSet(chunk_list, nth, &chunk_list)) { assert(chunk_list != NULL);
if (*chunk_list != NULL) {
return WEBP_MUX_NOT_FOUND; return WEBP_MUX_NOT_FOUND;
} }
@ -168,11 +148,26 @@ WebPMuxError ChunkSetNth(WebPChunk* chunk, WebPChunk** chunk_list,
if (new_chunk == NULL) return WEBP_MUX_MEMORY_ERROR; if (new_chunk == NULL) return WEBP_MUX_MEMORY_ERROR;
*new_chunk = *chunk; *new_chunk = *chunk;
chunk->owner_ = 0; chunk->owner_ = 0;
new_chunk->next_ = *chunk_list; new_chunk->next_ = NULL;
*chunk_list = new_chunk; *chunk_list = new_chunk;
return WEBP_MUX_OK; return WEBP_MUX_OK;
} }
WebPMuxError ChunkAppend(WebPChunk* const chunk,
WebPChunk*** const chunk_list) {
assert(chunk_list != NULL && *chunk_list != NULL);
if (**chunk_list == NULL) {
ChunkSetHead(chunk, *chunk_list);
} else {
WebPChunk* last_chunk = **chunk_list;
while (last_chunk->next_ != NULL) last_chunk = last_chunk->next_;
ChunkSetHead(chunk, &last_chunk->next_);
*chunk_list = &last_chunk->next_;
}
return WEBP_MUX_OK;
}
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Chunk deletion method(s). // Chunk deletion method(s).

View File

@ -103,6 +103,7 @@ static int MuxImageParse(const WebPChunk* const chunk, int copy_data,
const uint8_t* const last = bytes + size; const uint8_t* const last = bytes + size;
WebPChunk subchunk; WebPChunk subchunk;
size_t subchunk_size; size_t subchunk_size;
WebPChunk** unknown_chunk_list = &wpi->unknown_;
ChunkInit(&subchunk); ChunkInit(&subchunk);
assert(chunk->tag_ == kChunks[IDX_ANMF].tag); assert(chunk->tag_ == kChunks[IDX_ANMF].tag);
@ -117,7 +118,7 @@ static int MuxImageParse(const WebPChunk* const chunk, int copy_data,
if (size < hdr_size) goto Fail; if (size < hdr_size) goto Fail;
ChunkAssignData(&subchunk, &temp, copy_data, chunk->tag_); ChunkAssignData(&subchunk, &temp, copy_data, chunk->tag_);
} }
ChunkSetNth(&subchunk, &wpi->header_, 1); ChunkSetHead(&subchunk, &wpi->header_);
wpi->is_partial_ = 1; // Waiting for ALPH and/or VP8/VP8L chunks. wpi->is_partial_ = 1; // Waiting for ALPH and/or VP8/VP8L chunks.
// Rest of the chunks. // Rest of the chunks.
@ -134,19 +135,23 @@ static int MuxImageParse(const WebPChunk* const chunk, int copy_data,
switch (ChunkGetIdFromTag(subchunk.tag_)) { switch (ChunkGetIdFromTag(subchunk.tag_)) {
case WEBP_CHUNK_ALPHA: case WEBP_CHUNK_ALPHA:
if (wpi->alpha_ != NULL) goto Fail; // Consecutive ALPH chunks. if (wpi->alpha_ != NULL) goto Fail; // Consecutive ALPH chunks.
if (ChunkSetNth(&subchunk, &wpi->alpha_, 1) != WEBP_MUX_OK) goto Fail; if (ChunkSetHead(&subchunk, &wpi->alpha_) != WEBP_MUX_OK) goto Fail;
wpi->is_partial_ = 1; // Waiting for a VP8 chunk. wpi->is_partial_ = 1; // Waiting for a VP8 chunk.
break; break;
case WEBP_CHUNK_IMAGE: case WEBP_CHUNK_IMAGE:
if (wpi->img_ != NULL) goto Fail; // Only 1 image chunk allowed. if (wpi->img_ != NULL) goto Fail; // Only 1 image chunk allowed.
if (ChunkSetNth(&subchunk, &wpi->img_, 1) != WEBP_MUX_OK) goto Fail; if (ChunkSetHead(&subchunk, &wpi->img_) != WEBP_MUX_OK) goto Fail;
if (!MuxImageFinalize(wpi)) goto Fail; if (!MuxImageFinalize(wpi)) goto Fail;
wpi->is_partial_ = 0; // wpi is completely filled. wpi->is_partial_ = 0; // wpi is completely filled.
break; break;
case WEBP_CHUNK_UNKNOWN: case WEBP_CHUNK_UNKNOWN:
if (wpi->is_partial_) goto Fail; // Encountered an unknown chunk if (wpi->is_partial_) {
goto Fail; // Encountered an unknown chunk
// before some image chunks. // before some image chunks.
if (ChunkSetNth(&subchunk, &wpi->unknown_, 0) != WEBP_MUX_OK) goto Fail; }
if (ChunkAppend(&subchunk, &unknown_chunk_list) != WEBP_MUX_OK) {
goto Fail;
}
break; break;
default: default:
goto Fail; goto Fail;
@ -177,6 +182,9 @@ WebPMux* WebPMuxCreateInternal(const WebPData* bitstream, int copy_data,
const uint8_t* data; const uint8_t* data;
size_t size; size_t size;
WebPChunk chunk; WebPChunk chunk;
// Stores the end of the chunk lists so that it is faster to append data to
// their ends.
WebPChunk** chunk_list_ends[WEBP_CHUNK_NIL + 1] = { NULL };
ChunkInit(&chunk); ChunkInit(&chunk);
// Sanity checks. // Sanity checks.
@ -230,7 +238,6 @@ WebPMux* WebPMuxCreateInternal(const WebPData* bitstream, int copy_data,
while (data != end) { while (data != end) {
size_t data_size; size_t data_size;
WebPChunkId id; WebPChunkId id;
WebPChunk** chunk_list;
if (ChunkVerifyAndAssign(&chunk, data, size, riff_size, if (ChunkVerifyAndAssign(&chunk, data, size, riff_size,
copy_data) != WEBP_MUX_OK) { copy_data) != WEBP_MUX_OK) {
goto Err; goto Err;
@ -240,11 +247,11 @@ WebPMux* WebPMuxCreateInternal(const WebPData* bitstream, int copy_data,
switch (id) { switch (id) {
case WEBP_CHUNK_ALPHA: case WEBP_CHUNK_ALPHA:
if (wpi->alpha_ != NULL) goto Err; // Consecutive ALPH chunks. if (wpi->alpha_ != NULL) goto Err; // Consecutive ALPH chunks.
if (ChunkSetNth(&chunk, &wpi->alpha_, 1) != WEBP_MUX_OK) goto Err; if (ChunkSetHead(&chunk, &wpi->alpha_) != WEBP_MUX_OK) goto Err;
wpi->is_partial_ = 1; // Waiting for a VP8 chunk. wpi->is_partial_ = 1; // Waiting for a VP8 chunk.
break; break;
case WEBP_CHUNK_IMAGE: case WEBP_CHUNK_IMAGE:
if (ChunkSetNth(&chunk, &wpi->img_, 1) != WEBP_MUX_OK) goto Err; if (ChunkSetHead(&chunk, &wpi->img_) != WEBP_MUX_OK) goto Err;
if (!MuxImageFinalize(wpi)) goto Err; if (!MuxImageFinalize(wpi)) goto Err;
wpi->is_partial_ = 0; // wpi is completely filled. wpi->is_partial_ = 0; // wpi is completely filled.
PushImage: PushImage:
@ -261,8 +268,11 @@ WebPMux* WebPMuxCreateInternal(const WebPData* bitstream, int copy_data,
default: // A non-image chunk. default: // A non-image chunk.
if (wpi->is_partial_) goto Err; // Encountered a non-image chunk before if (wpi->is_partial_) goto Err; // Encountered a non-image chunk before
// getting all chunks of an image. // getting all chunks of an image.
chunk_list = MuxGetChunkListFromId(mux, id); // List to add this chunk. if (chunk_list_ends[id] == NULL) {
if (ChunkSetNth(&chunk, chunk_list, 0) != WEBP_MUX_OK) goto Err; chunk_list_ends[id] =
MuxGetChunkListFromId(mux, id); // List to add this chunk.
}
if (ChunkAppend(&chunk, &chunk_list_ends[id]) != WEBP_MUX_OK) goto Err;
if (id == WEBP_CHUNK_VP8X) { // grab global specs if (id == WEBP_CHUNK_VP8X) { // grab global specs
if (data_size < CHUNK_HEADER_SIZE + VP8X_CHUNK_SIZE) goto Err; if (data_size < CHUNK_HEADER_SIZE + VP8X_CHUNK_SIZE) goto Err;
mux->canvas_width_ = GetLE24(data + 12) + 1; mux->canvas_width_ = GetLE24(data + 12) + 1;