Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions Source/astcenccli_image_load_store.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<uint64_t>(end - start) >= static_cast<uint64_t>(bytes);
}

/**
* @brief Load an uncompressed KTX image using the local custom loader.
*
Expand Down Expand Up @@ -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<uint8_t[]> buf;
try
{
Expand Down Expand Up @@ -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<char*>(img.data.data()), data_len);
Expand Down Expand Up @@ -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<uint8_t[]> buf;
try
{
Expand Down Expand Up @@ -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
{
Expand Down
10 changes: 4 additions & 6 deletions Test/astc_test_functional.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down