diff --git a/Source/astcenccli_image_load_store.cpp b/Source/astcenccli_image_load_store.cpp index 8708d30a..10a53f58 100644 --- a/Source/astcenccli_image_load_store.cpp +++ b/Source/astcenccli_image_load_store.cpp @@ -910,6 +910,39 @@ static void ktx_header_switch_endianness(ktx_header * kt) #undef REV } +/** + * @brief Determine whether a stream still holds a given number of bytes. + * + * Loaders size allocations from header fields, so a header declaring more + * payload than the file holds must be rejected before the allocation. + * + * @param[in,out] file The stream, positioned at the start of the payload. + * @param bytes The payload size the header declared. + * + * @return @c true if the stream holds at least @c bytes more data. + */ +static bool stream_holds_bytes( + std::ifstream& file, + size_t bytes +) { + std::streampos start = file.tellg(); + if (start < 0) + { + return false; + } + + file.seekg(0, std::ios::end); + std::streampos end = file.tellg(); + file.seekg(start); + + if (file.fail() || (end < start)) + { + return false; + } + + return static_cast(end - start) >= static_cast(bytes); +} + /** * @brief Load an uncompressed KTX image using the local custom loader. * @@ -1201,6 +1234,12 @@ static astcenc_image_ptr load_ktx_uncompressed_image( return nullptr; } + if (!stream_holds_bytes(file, bytes_per_image)) + { + print_error("ERROR: Image header corrupt '%s'\n", filename); + return nullptr; + } + std::unique_ptr buf; try { @@ -1383,6 +1422,12 @@ bool load_ktx_compressed_image( return true; } + if (!stream_holds_bytes(file, data_len)) + { + print_error("ERROR: Image header corrupt '%s'\n", filename); + return true; + } + // Read the data img.data.resize(data_len); file.read(reinterpret_cast(img.data.data()), data_len); @@ -2078,6 +2123,12 @@ static astcenc_image_ptr load_dds_uncompressed_image( return nullptr; } + if (!stream_holds_bytes(file, bytes_per_image)) + { + print_error("ERROR: Image header corrupt '%s'\n", filename); + return nullptr; + } + std::unique_ptr buf; try { @@ -2677,6 +2728,12 @@ int load_cimage( return 1; } + if (!stream_holds_bytes(file, data_size)) + { + print_error("ERROR: Image header corrupt '%s'\n", filename); + return 1; + } + // Allocation may fail if image is suspiciously large try { diff --git a/Test/astc_test_functional.py b/Test/astc_test_functional.py index 063e0160..dbc7da59 100644 --- a/Test/astc_test_functional.py +++ b/Test/astc_test_functional.py @@ -2207,13 +2207,11 @@ def test_dl_corrupt_astc_magic(self) -> None: def test_dl_corrupt_astc_huge_size(self) -> None: ''' - Test -dl with an astc file with a data size that will fail alloc. + Test -dl with an astc file declaring more data than the file holds. - Note this test will cause ASAN to error because ASAN itself cannot cope - with OOM issues. ASAN documentation says you can run with environment - ASAN_OPTIONS=allocator_may_return_null=1, but this doesn't seem to work - for allocations made with C++ new[]. This test will need to be disabled - when ASAN is used. + The loader compares the declared payload size against the bytes left in + the file before it allocates, so this is rejected as a corrupt header + and never reaches an allocation large enough to trouble ASAN. ''' # Build a valid cmd with a bad image file cmd = [