From 2febfe0d105822575328759dd950c8a24b0ad6b3 Mon Sep 17 00:00:00 2001 From: Lode Date: Sun, 12 Jan 2020 10:24:07 +0100 Subject: [PATCH] handle integer overflow in lodepng_chunk_next and lodepng_chunk_find Fixes issue https://github.com/lvandeve/lodepng/issues/123 --- examples/example_png_info.cpp | 18 ++++----- lodepng.cpp | 69 +++++++++++++++++++++-------------- lodepng.h | 43 ++++++++++++---------- lodepng_unittest.cpp | 9 +++-- lodepng_util.cpp | 42 ++++++++------------- lodepng_util.h | 2 +- pngdetail.cpp | 2 +- 7 files changed, 95 insertions(+), 90 deletions(-) diff --git a/examples/example_png_info.cpp b/examples/example_png_info.cpp index 43427ccc..e321afee 100644 --- a/examples/example_png_info.cpp +++ b/examples/example_png_info.cpp @@ -93,14 +93,14 @@ Display the names and sizes of all chunks in the PNG file. */ void displayChunkNames(const std::vector& buffer) { // Listing chunks is based on the original file, not the decoded png info. - const unsigned char *chunk, *begin, *end, *next; + const unsigned char *chunk, *end; end = &buffer.back() + 1; - begin = chunk = &buffer.front() + 8; + chunk = &buffer.front() + 8; std::cout << std::endl << "Chunks:" << std::endl; std::cout << " type: length(s)"; std::string last_type; - while(chunk + 8 < end && chunk >= begin) { + while(chunk < end && end - chunk >= 8) { char type[5]; lodepng_chunk_type(type, chunk); if(std::string(type).size() != 4) { @@ -116,9 +116,7 @@ void displayChunkNames(const std::vector& buffer) { std::cout << lodepng_chunk_length(chunk) << ", "; - next = lodepng_chunk_next_const(chunk); - if (next <= chunk) break; // integer overflow - chunk = next; + chunk = lodepng_chunk_next_const(chunk, end); } std::cout << std::endl; } @@ -200,13 +198,13 @@ void displayFilterTypes(const std::vector& buffer, bool ignore_ch } //Read literal data from all IDAT chunks - const unsigned char *chunk, *begin, *end, *next; + const unsigned char *chunk, *begin, *end; end = &buffer.back() + 1; begin = chunk = &buffer.front() + 8; std::vector zdata; - while(chunk + 8 < end && chunk >= begin) { + while(chunk < end && end - chunk >= 8) { char type[5]; lodepng_chunk_type(type, chunk); if(std::string(type).size() != 4) { @@ -227,9 +225,7 @@ void displayFilterTypes(const std::vector& buffer, bool ignore_ch } } - next = lodepng_chunk_next_const(chunk); - if (next <= chunk) break; // integer overflow - chunk = next; + chunk = lodepng_chunk_next_const(chunk, end); } //Decompress all IDAT data diff --git a/lodepng.cpp b/lodepng.cpp index e5723b56..788c95e3 100644 --- a/lodepng.cpp +++ b/lodepng.cpp @@ -1,7 +1,7 @@ /* -LodePNG version 20191219 +LodePNG version 20200112 -Copyright (c) 2005-2019 Lode Vandevenne +Copyright (c) 2005-2020 Lode Vandevenne This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -44,7 +44,7 @@ Rename this file to lodepng.cpp to use it for C++, or to lodepng.c to use it for #pragma warning( disable : 4996 ) /*VS does not like fopen, but fopen_s is not standard C so unusable here*/ #endif /*_MSC_VER */ -const char* LODEPNG_VERSION_STRING = "20191219"; +const char* LODEPNG_VERSION_STRING = "20200112"; /* This source file is built up in the following large parts. The code sections @@ -135,6 +135,14 @@ static size_t lodepng_strlen(const char* a) { #define LODEPNG_MIN(a, b) (((a) < (b)) ? (a) : (b)) #define LODEPNG_ABS(x) ((x) < 0 ? -(x) : (x)) +#if defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_DECODER) +/* Safely check if adding two integers will overflow (no undefined +behavior, compiler removing the code, etc...) and output result. */ +static int lodepng_addofl(size_t a, size_t b, size_t* result) { + *result = a + b; /* Unsigned addition is well defined and safe in C90 */ + return *result < a; +} +#endif /*defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_DECODER)*/ #ifdef LODEPNG_COMPILE_DECODER /* Safely check if multiplying two integers will overflow (no undefined @@ -144,13 +152,6 @@ static int lodepng_mulofl(size_t a, size_t b, size_t* result) { return (a != 0 && *result / a != b); } -/* Safely check if adding two integers will overflow (no undefined -behavior, compiler removing the code, etc...) and output result. */ -static int lodepng_addofl(size_t a, size_t b, size_t* result) { - *result = a + b; /* Unsigned addition is well defined and safe in C90 */ - return *result < a; -} - #ifdef LODEPNG_COMPILE_ZLIB /* Safely check if a + b > c, even if overflow could happen. */ static int lodepng_gtofl(size_t a, size_t b, size_t c) { @@ -2515,50 +2516,61 @@ void lodepng_chunk_generate_crc(unsigned char* chunk) { lodepng_set32bitInt(chunk + 8 + length, CRC); } -unsigned char* lodepng_chunk_next(unsigned char* chunk) { +unsigned char* lodepng_chunk_next(unsigned char* chunk, unsigned char* end) { + if(chunk >= end || end - chunk < 12) return end; /*too small to contain a chunk*/ if(chunk[0] == 0x89 && chunk[1] == 0x50 && chunk[2] == 0x4e && chunk[3] == 0x47 && chunk[4] == 0x0d && chunk[5] == 0x0a && chunk[6] == 0x1a && chunk[7] == 0x0a) { /* Is PNG magic header at start of PNG file. Jump to first actual chunk. */ return chunk + 8; } else { - unsigned total_chunk_length = lodepng_chunk_length(chunk) + 12; - return chunk + total_chunk_length; + size_t total_chunk_length; + unsigned char* result; + if(lodepng_addofl(lodepng_chunk_length(chunk), 12, &total_chunk_length)) return end; + result = chunk + total_chunk_length; + if(result < chunk) return end; /*pointer overflow*/ + return result; } } -const unsigned char* lodepng_chunk_next_const(const unsigned char* chunk) { +const unsigned char* lodepng_chunk_next_const(const unsigned char* chunk, const unsigned char* end) { + if(chunk >= end || end - chunk < 12) return end; /*too small to contain a chunk*/ if(chunk[0] == 0x89 && chunk[1] == 0x50 && chunk[2] == 0x4e && chunk[3] == 0x47 && chunk[4] == 0x0d && chunk[5] == 0x0a && chunk[6] == 0x1a && chunk[7] == 0x0a) { /* Is PNG magic header at start of PNG file. Jump to first actual chunk. */ return chunk + 8; } else { - unsigned total_chunk_length = lodepng_chunk_length(chunk) + 12; - return chunk + total_chunk_length; + size_t total_chunk_length; + const unsigned char* result; + if(lodepng_addofl(lodepng_chunk_length(chunk), 12, &total_chunk_length)) return end; + result = chunk + total_chunk_length; + if(result < chunk) return end; /*pointer overflow*/ + return result; } } -unsigned char* lodepng_chunk_find(unsigned char* chunk, const unsigned char* end, const char type[5]) { +unsigned char* lodepng_chunk_find(unsigned char* chunk, unsigned char* end, const char type[5]) { for(;;) { - if(chunk + 12 >= end) return 0; + if(chunk >= end || end - chunk < 12) return 0; /* past file end: chunk + 12 > end */ if(lodepng_chunk_type_equals(chunk, type)) return chunk; - chunk = lodepng_chunk_next(chunk); + chunk = lodepng_chunk_next(chunk, end); } } const unsigned char* lodepng_chunk_find_const(const unsigned char* chunk, const unsigned char* end, const char type[5]) { for(;;) { - if(chunk + 12 >= end) return 0; + if(chunk >= end || end - chunk < 12) return 0; /* past file end: chunk + 12 > end */ if(lodepng_chunk_type_equals(chunk, type)) return chunk; - chunk = lodepng_chunk_next_const(chunk); + chunk = lodepng_chunk_next_const(chunk, end); } } unsigned lodepng_chunk_append(unsigned char** out, size_t* outlength, const unsigned char* chunk) { unsigned i; - unsigned total_chunk_length = lodepng_chunk_length(chunk) + 12; + size_t total_chunk_length, new_length; unsigned char *chunk_start, *new_buffer; - size_t new_length = (*outlength) + total_chunk_length; - if(new_length < total_chunk_length || new_length < (*outlength)) return 77; /*integer overflow happened*/ + + if(lodepng_addofl(lodepng_chunk_length(chunk), 12, &total_chunk_length)) return 77; + if(lodepng_addofl(*outlength, total_chunk_length, &new_length)) return 77; new_buffer = (unsigned char*)lodepng_realloc(*out, new_length); if(!new_buffer) return 83; /*alloc fail*/ @@ -2575,8 +2587,9 @@ unsigned lodepng_chunk_create(unsigned char** out, size_t* outlength, unsigned l const char* type, const unsigned char* data) { unsigned i; unsigned char *chunk, *new_buffer; - size_t new_length = (*outlength) + length + 12; - if(new_length < length + 12 || new_length < (*outlength)) return 77; /*integer overflow happened*/ + size_t new_length = *outlength; + if(lodepng_addofl(new_length, length, &new_length)) return 77; + if(lodepng_addofl(new_length, 12, &new_length)) return 77; new_buffer = (unsigned char*)lodepng_realloc(*out, new_length); if(!new_buffer) return 83; /*alloc fail*/ (*out) = new_buffer; @@ -4868,7 +4881,7 @@ static void decodeGeneric(unsigned char** out, unsigned* w, unsigned* h, if(lodepng_chunk_check_crc(chunk)) CERROR_BREAK(state->error, 57); /*invalid CRC*/ } - if(!IEND) chunk = lodepng_chunk_next_const(chunk); + if(!IEND) chunk = lodepng_chunk_next_const(chunk, in + insize); } if (state->info_png.color.colortype == LCT_PALETTE @@ -5750,7 +5763,7 @@ static unsigned addUnknownChunks(ucvector* out, unsigned char* data, size_t data while((size_t)(inchunk - data) < datasize) { CERROR_TRY_RETURN(lodepng_chunk_append(&out->data, &out->size, inchunk)); out->allocsize = out->size; /*fix the allocsize again*/ - inchunk = lodepng_chunk_next(inchunk); + inchunk = lodepng_chunk_next(inchunk, data + datasize); } return 0; } diff --git a/lodepng.h b/lodepng.h index 770812ff..0ab14de9 100644 --- a/lodepng.h +++ b/lodepng.h @@ -1,7 +1,7 @@ /* -LodePNG version 20191219 +LodePNG version 20200112 -Copyright (c) 2005-2019 Lode Vandevenne +Copyright (c) 2005-2020 Lode Vandevenne This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -856,16 +856,16 @@ Input must be at the beginning of a chunk (result of a previous lodepng_chunk_ne or the 8th byte of a PNG file which always has the first chunk), or alternatively may point to the first byte of the PNG file (which is not a chunk but the magic header, the function will then skip over it and return the first real chunk). -Expects at least 8 readable bytes of memory in the input pointer. -Will output pointer to the start of the next chunk or the end of the file if there -is no more chunk after this. Start this process at the 8th byte of the PNG file. +Will output pointer to the start of the next chunk, or at or beyond end of the file if there +is no more chunk after this or possibly if the chunk is corrupt. +Start this process at the 8th byte of the PNG file. In a non-corrupt PNG file, the last chunk should have name "IEND". */ -unsigned char* lodepng_chunk_next(unsigned char* chunk); -const unsigned char* lodepng_chunk_next_const(const unsigned char* chunk); +unsigned char* lodepng_chunk_next(unsigned char* chunk, unsigned char* end); +const unsigned char* lodepng_chunk_next_const(const unsigned char* chunk, const unsigned char* end); /*Finds the first chunk with the given type in the range [chunk, end), or returns NULL if not found.*/ -unsigned char* lodepng_chunk_find(unsigned char* chunk, const unsigned char* end, const char type[5]); +unsigned char* lodepng_chunk_find(unsigned char* chunk, unsigned char* end, const char type[5]); const unsigned char* lodepng_chunk_find_const(const unsigned char* chunk, const unsigned char* end, const char type[5]); /* @@ -1775,14 +1775,17 @@ symbol. Not all changes are listed here, the commit history in github lists more: https://github.com/lvandeve/lodepng +*) 12 jan 2020: (!) added 'end' argument to lodepng_chunk_next to allow correct + overflow checks. *) 14 aug 2019: around 25% faster decoding thanks to huffman lookup tables. -*) 15 jun 2019 (!): auto_choose_color API changed (for bugfix: don't use palette - if gray ICC profile) and non-ICC LodePNGColorProfile renamed to LodePNGColorStats. +*) 15 jun 2019: (!) auto_choose_color API changed (for bugfix: don't use palette + if gray ICC profile) and non-ICC LodePNGColorProfile renamed to + LodePNGColorStats. *) 30 dec 2018: code style changes only: removed newlines before opening braces. *) 10 sep 2018: added way to inspect metadata chunks without full decoding. -*) 19 aug 2018 (!): fixed color mode bKGD is encoded with and made it use +*) 19 aug 2018: (!) fixed color mode bKGD is encoded with and made it use palette index in case of palette. -*) 10 aug 2018 (!): added support for gAMA, cHRM, sRGB and iCCP chunks. This +*) 10 aug 2018: (!) added support for gAMA, cHRM, sRGB and iCCP chunks. This change is backwards compatible unless you relied on unknown_chunks for those. *) 11 jun 2018: less restrictive check for pixel size integer overflow *) 14 jan 2018: allow optionally ignoring a few more recoverable errors @@ -1802,25 +1805,25 @@ Not all changes are listed here, the commit history in github lists more: *) 22 dec 2013: Power of two windowsize required for optimization. *) 15 apr 2013: Fixed bug with LAC_ALPHA and color key. *) 25 mar 2013: Added an optional feature to ignore some PNG errors (fix_png). -*) 11 mar 2013 (!): Bugfix with custom free. Changed from "my" to "lodepng_" +*) 11 mar 2013: (!) Bugfix with custom free. Changed from "my" to "lodepng_" prefix for the custom allocators and made it possible with a new #define to use custom ones in your project without needing to change lodepng's code. *) 28 jan 2013: Bugfix with color key. *) 27 okt 2012: Tweaks in text chunk keyword length error handling. -*) 8 okt 2012 (!): Added new filter strategy (entropy) and new auto color mode. +*) 8 okt 2012: (!) Added new filter strategy (entropy) and new auto color mode. (no palette). Better deflate tree encoding. New compression tweak settings. Faster color conversions while decoding. Some internal cleanups. *) 23 sep 2012: Reduced warnings in Visual Studio a little bit. -*) 1 sep 2012 (!): Removed #define's for giving custom (de)compression functions +*) 1 sep 2012: (!) Removed #define's for giving custom (de)compression functions and made it work with function pointers instead. *) 23 jun 2012: Added more filter strategies. Made it easier to use custom alloc and free functions and toggle #defines from compiler flags. Small fixes. -*) 6 may 2012 (!): Made plugging in custom zlib/deflate functions more flexible. -*) 22 apr 2012 (!): Made interface more consistent, renaming a lot. Removed +*) 6 may 2012: (!) Made plugging in custom zlib/deflate functions more flexible. +*) 22 apr 2012: (!) Made interface more consistent, renaming a lot. Removed redundant C++ codec classes. Reduced amount of structs. Everything changed, but it is cleaner now imho and functionality remains the same. Also fixed several bugs and shrunk the implementation code. Made new samples. -*) 6 nov 2011 (!): By default, the encoder now automatically chooses the best +*) 6 nov 2011: (!) By default, the encoder now automatically chooses the best PNG color model and bit depth, based on the amount and type of colors of the raw image. For this, autoLeaveOutAlphaChannel replaced by auto_choose_color. *) 9 okt 2011: simpler hash chain implementation for the encoder. @@ -1829,7 +1832,7 @@ Not all changes are listed here, the commit history in github lists more: A bug with the PNG filtertype heuristic was fixed, so that it chooses much better ones (it's quite significant). A setting to do an experimental, slow, brute force search for PNG filter types is added. -*) 17 aug 2011 (!): changed some C zlib related function names. +*) 17 aug 2011: (!) changed some C zlib related function names. *) 16 aug 2011: made the code less wide (max 120 characters per line). *) 17 apr 2011: code cleanup. Bugfixes. Convert low to 16-bit per sample colors. *) 21 feb 2011: fixed compiling for C90. Fixed compiling with sections disabled. @@ -1937,5 +1940,5 @@ Domain: gmail dot com. Account: lode dot vandevenne. -Copyright (c) 2005-2019 Lode Vandevenne +Copyright (c) 2005-2020 Lode Vandevenne */ diff --git a/lodepng_unittest.cpp b/lodepng_unittest.cpp index 9b4386c5..90c8323d 100644 --- a/lodepng_unittest.cpp +++ b/lodepng_unittest.cpp @@ -1,7 +1,7 @@ /* LodePNG Unit Test -Copyright (c) 2005-2019 Lode Vandevenne +Copyright (c) 2005-2020 Lode Vandevenne This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -1290,13 +1290,14 @@ void createComplexPNG(std::vector& png) { std::string extractChunkNames(const std::vector& png) { const unsigned char* chunk = &png[8]; + const unsigned char* end = &png.back() + 1; char name[5]; std::string result = ""; for(;;) { lodepng_chunk_type(name, chunk); result += (std::string(" ") + name); if(std::string(name) == "IEND") break; - chunk = lodepng_chunk_next_const(chunk); + chunk = lodepng_chunk_next_const(chunk, end); assertTrue(chunk < &png.back(), "jumped out of chunks"); } return result; @@ -3671,7 +3672,9 @@ int main() { doMain(); } catch(...) { - std::cout << "error!" << std::endl; + std::cout << std::endl; + std::cout << "caught error!" << std::endl; + std::cout << "*** TEST FAILED ***" << std::endl; } return 0; diff --git a/lodepng_util.cpp b/lodepng_util.cpp index d42fcf05..5c5d876f 100644 --- a/lodepng_util.cpp +++ b/lodepng_util.cpp @@ -1,7 +1,7 @@ /* LodePNG Utils -Copyright (c) 2005-2019 Lode Vandevenne +Copyright (c) 2005-2020 Lode Vandevenne This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -39,11 +39,11 @@ LodePNGInfo getPNGHeaderInfo(const std::vector& png) { unsigned getChunkInfo(std::vector& names, std::vector& sizes, const std::vector& png) { // Listing chunks is based on the original file, not the decoded png info. - const unsigned char *chunk, *begin, *end, *next; + const unsigned char *chunk, *end; end = &png.back() + 1; - begin = chunk = &png.front() + 8; + chunk = &png.front() + 8; - while(chunk + 8 < end && chunk >= begin) { + while(chunk < end && end - chunk >= 8) { char type[5]; lodepng_chunk_type(type, chunk); if(std::string(type).size() != 4) return 1; @@ -51,11 +51,7 @@ unsigned getChunkInfo(std::vector& names, std::vector& size unsigned length = lodepng_chunk_length(chunk); names.push_back(type); sizes.push_back(length); - if(chunk + length + 12 > end) return 1; - - next = lodepng_chunk_next_const(chunk); - if (next <= chunk) return 1; // integer overflow - chunk = next; + chunk = lodepng_chunk_next_const(chunk, end); } return 0; } @@ -63,20 +59,19 @@ unsigned getChunkInfo(std::vector& names, std::vector& size unsigned getChunks(std::vector names[3], std::vector > chunks[3], const std::vector& png) { - const unsigned char *chunk, *next, *begin, *end; + const unsigned char *chunk, *next, *end; end = &png.back() + 1; - begin = chunk = &png.front() + 8; + chunk = &png.front() + 8; int location = 0; - while(chunk + 8 < end && chunk >= begin) { + while(chunk < end && end - chunk >= 8) { char type[5]; lodepng_chunk_type(type, chunk); std::string name(type); if(name.size() != 4) return 1; - next = lodepng_chunk_next_const(chunk); - if (next <= chunk) return 1; // integer overflow + next = lodepng_chunk_next_const(chunk, end); if(name == "IHDR") { location = 0; @@ -87,7 +82,7 @@ unsigned getChunks(std::vector names[3], } else if(name == "IEND") { break; // anything after IEND is not part of the PNG or the 3 groups here. } else { - if(next > end) return 1; // invalid chunk, content too far + if(next >= end) return 1; // invalid chunk, content too far names[location].push_back(name); chunks[location].push_back(std::vector(chunk, next)); } @@ -100,7 +95,7 @@ unsigned getChunks(std::vector names[3], unsigned insertChunks(std::vector& png, const std::vector > chunks[3]) { - const unsigned char *chunk, *next, *begin, *end; + const unsigned char *chunk, *begin, *end; end = &png.back() + 1; begin = chunk = &png.front() + 8; @@ -108,15 +103,12 @@ unsigned insertChunks(std::vector& png, long l1 = 0; //location 1: PLTE-l1-IDAT (or IHDR-l0-l1-IDAT) long l2 = 0; //location 2: IDAT-l2-IEND - while(chunk + 8 < end && chunk >= begin) { + while(chunk < end && end - chunk >= 8) { char type[5]; lodepng_chunk_type(type, chunk); std::string name(type); if(name.size() != 4) return 1; - next = lodepng_chunk_next_const(chunk); - if (next <= chunk) return 1; // integer overflow - if(name == "PLTE") { if(l0 == 0) l0 = chunk - begin + 8; } else if(name == "IDAT") { @@ -126,7 +118,7 @@ unsigned insertChunks(std::vector& png, if(l2 == 0) l2 = chunk - begin + 8; } - chunk = next; + chunk = lodepng_chunk_next_const(chunk, end); } std::vector result; @@ -153,13 +145,13 @@ unsigned getFilterTypesInterlaced(std::vector >& filt if(error) return 1; //Read literal data from all IDAT chunks - const unsigned char *chunk, *begin, *end, *next; + const unsigned char *chunk, *begin, *end; end = &png.back() + 1; begin = chunk = &png.front() + 8; std::vector zdata; - while(chunk + 8 < end && chunk >= begin) { + while(chunk < end && end - chunk >= 8) { char type[5]; lodepng_chunk_type(type, chunk); if(std::string(type).size() != 4) break; //Probably not a PNG file @@ -177,9 +169,7 @@ unsigned getFilterTypesInterlaced(std::vector >& filt } } - next = lodepng_chunk_next_const(chunk); - if (next <= chunk) break; // integer overflow - chunk = next; + chunk = lodepng_chunk_next_const(chunk, end); } //Decompress all IDAT data (if the while loop ended early, this might fail) diff --git a/lodepng_util.h b/lodepng_util.h index 514b51c8..97fd804c 100644 --- a/lodepng_util.h +++ b/lodepng_util.h @@ -1,7 +1,7 @@ /* LodePNG Utils -Copyright (c) 2005-2019 Lode Vandevenne +Copyright (c) 2005-2020 Lode Vandevenne This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/pngdetail.cpp b/pngdetail.cpp index 3b8aa381..e369c324 100644 --- a/pngdetail.cpp +++ b/pngdetail.cpp @@ -1,7 +1,7 @@ /* LodePNG pngdetail -Copyright (c) 2005-2018 Lode Vandevenne +Copyright (c) 2005-2020 Lode Vandevenne This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages