mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-11-15 18:49:51 +01:00
Images: Limit sizes, and handle ZIM and JPEG load failures
- JPEGs over 8192 pixels in either dimension are rejected (in jpgd, so it covers sceJpeg too), and its allocation size is computed in size_t, since the int product overflowed and gave a heap overflow. Image loading also checks the caller's maximum before decoding. - Image loading premultiplied a ZIM that failed to load (null pointer), and treated 16-bit ZIMs as 32-bit (twice the buffer). Reject those, and ZIMs over 8192, and a zlib stream that ends early. - PNG: pngLoadPtr's error path could free a caller's uninitialized pointer, and pngLoad didn't check its allocation or decode. - Texture replacements: reject header sizes of 0 or over 8192 for every format (DDS sizes overflowed an int), transcode KTX2 mips with their own height, and don't copy more ZIM rows than were decoded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
56cf35ac95
commit
382a8eecea
5 files changed
+81
-16
No files matched your search
@@ -23,13 +23,28 @@ int pngLoad(const char *file, int *pwidth, int *pheight, unsigned char **image_d
|
||||
*image_data_ptr = nullptr;
|
||||
return 0;
|
||||
}
|
||||
if (png.width == 0 || png.height == 0 || png.width > 8192 || png.height > 8192) {
|
||||
WARN_LOG(Log::IO, "pngLoad: Bad size %dx%d (%s)", (int)png.width, (int)png.height, file);
|
||||
png_image_free(&png);
|
||||
*image_data_ptr = nullptr;
|
||||
return 0;
|
||||
}
|
||||
*pwidth = png.width;
|
||||
*pheight = png.height;
|
||||
png.format = PNG_FORMAT_RGBA;
|
||||
|
||||
int stride = PNG_IMAGE_ROW_STRIDE(png);
|
||||
*image_data_ptr = (unsigned char *)malloc(PNG_IMAGE_SIZE(png));
|
||||
png_image_finish_read(&png, NULL, *image_data_ptr, stride, NULL);
|
||||
if (!*image_data_ptr) {
|
||||
png_image_free(&png);
|
||||
return 0;
|
||||
}
|
||||
if (!png_image_finish_read(&png, NULL, *image_data_ptr, stride, NULL)) {
|
||||
WARN_LOG(Log::IO, "pngLoad: %s (%s)", png.message, file);
|
||||
free(*image_data_ptr);
|
||||
*image_data_ptr = nullptr;
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -50,11 +65,13 @@ struct PngReadContext {
|
||||
|
||||
|
||||
int pngLoadPtr(const unsigned char *input_ptr, size_t input_len, int *pwidth, int *pheight, unsigned char **image_data_ptr, int maxWidth, int maxHeight) {
|
||||
png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, pngErrorHandler, pngWarningHandler);
|
||||
if (!png) {
|
||||
// The error handler below frees whatever this points to.
|
||||
*image_data_ptr = nullptr;
|
||||
if (input_len == 0) {
|
||||
return 0;
|
||||
}
|
||||
if (input_len == 0) {
|
||||
png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, pngErrorHandler, pngWarningHandler);
|
||||
if (!png) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -63,7 +63,9 @@ int LoadZIMPtr(const uint8_t *zim, size_t datasize, int *width, int *height, int
|
||||
memcpy(height, zim + 8, 4);
|
||||
memcpy(flags, zim + 12, 4);
|
||||
|
||||
if (*width <= 0 || *height <= 0) {
|
||||
// Nothing we use is anywhere near this, and the data is often compressed, so the
|
||||
// size is otherwise only bounded by how much memory a decompression bomb can claim.
|
||||
if (*width <= 0 || *height <= 0 || *width > 8192 || *height > 8192) {
|
||||
ERROR_LOG(Log::IO, "Invalid ZIM dimensions %i x %i", *width, *height);
|
||||
return 0;
|
||||
}
|
||||
@@ -147,9 +149,12 @@ int LoadZIMPtr(const uint8_t *zim, size_t datasize, int *width, int *height, int
|
||||
*image = 0;
|
||||
return 0;
|
||||
}
|
||||
if (outlen != total_data_size) {
|
||||
// Shouldn't happen if return value was Z_OK.
|
||||
if (outlen != (long)total_data_size) {
|
||||
// The stream ended early, leaving the rest of the image uninitialized.
|
||||
ERROR_LOG(Log::IO, "Wrong size data in ZIM: %i vs %i", (int)outlen, (int)total_data_size);
|
||||
free(*image);
|
||||
*image = 0;
|
||||
return 0;
|
||||
}
|
||||
} else if (*flags & ZIM_ZSTD_COMPRESSED) {
|
||||
size_t outlen = ZSTD_decompress(*image, total_data_size, zim + 16, payload_size);
|
||||
|
||||
@@ -94,6 +94,18 @@ bool TempImage::LoadTextureLevelsFromFileData(const uint8_t *data, size_t size,
|
||||
switch (typeSuggestion) {
|
||||
case ImageFileType::ZIM:
|
||||
numLevels = LoadZIMPtr((const uint8_t *)data, size, width, height, &zimFlags, levels);
|
||||
if (numLevels <= 0) {
|
||||
numLevels = 0;
|
||||
return false;
|
||||
}
|
||||
// Everything below treats the data as four bytes per pixel.
|
||||
if ((zimFlags & ZIM_FORMAT_MASK) != ZIM_RGBA8888 || width[0] > maxWidth || height[0] > maxHeight) {
|
||||
ERROR_LOG(Log::IO, "Unsupported ZIM: format %d, %dx%d", zimFlags & ZIM_FORMAT_MASK, width[0], height[0]);
|
||||
free(levels[0]);
|
||||
levels[0] = nullptr;
|
||||
numLevels = 0;
|
||||
return false;
|
||||
}
|
||||
fmt = ZimToT3DFormat(zimFlags & ZIM_FORMAT_MASK);
|
||||
ConvertRGBA8888ToPremulAlpha((u32 *)levels[0], (const u32 *)levels[0], width[0] * height[0]);
|
||||
break;
|
||||
@@ -117,6 +129,22 @@ bool TempImage::LoadTextureLevelsFromFileData(const uint8_t *data, size_t size,
|
||||
case ImageFileType::JPEG:
|
||||
{
|
||||
int actual_components = 0;
|
||||
if (size > INT_MAX) {
|
||||
return false;
|
||||
}
|
||||
// Check the size before decoding.
|
||||
{
|
||||
jpgd::jpeg_decoder_mem_stream stream(data, (jpgd::uint)size);
|
||||
jpgd::jpeg_decoder decoder(&stream);
|
||||
if (decoder.get_error_code() != jpgd::JPGD_SUCCESS) {
|
||||
ERROR_LOG(Log::IO, "Bad JPEG header");
|
||||
return false;
|
||||
}
|
||||
if (decoder.get_width() <= 0 || decoder.get_height() <= 0 || decoder.get_width() > maxWidth || decoder.get_height() > maxHeight) {
|
||||
ERROR_LOG(Log::IO, "JPEG too large: %dx%d (max %dx%d)", decoder.get_width(), decoder.get_height(), maxWidth, maxHeight);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
unsigned char *jpegBuf = jpgd::decompress_jpeg_image_from_memory(data, (int)size, &width[0], &height[0], &actual_components, 4);
|
||||
if (jpegBuf) {
|
||||
numLevels = 1;
|
||||
|
||||
@@ -553,6 +553,12 @@ ReplacedTexture::LoadLevelResult ReplacedTexture::LoadLevelData(VFSFileReference
|
||||
ERROR_LOG(Log::TexReplacement, "Could not load texture replacement info: %s - unsupported format %s", filename.c_str(), magic.c_str());
|
||||
}
|
||||
|
||||
// The sizes come straight from the file headers, and everything below sizes buffers from them.
|
||||
if (good && (level.w <= 0 || level.h <= 0 || level.w > 8192 || level.h > 8192)) {
|
||||
ERROR_LOG(Log::TexReplacement, "Replacement texture %s has a bad size: %dx%d", filename.c_str(), level.w, level.h);
|
||||
good = false;
|
||||
}
|
||||
|
||||
// TODO: We no longer really need to have a split in this function, the upper and lower parts can be merged now.
|
||||
|
||||
if (good && mipLevel != 0) {
|
||||
@@ -723,19 +729,22 @@ ReplacedTexture::LoadLevelResult ReplacedTexture::LoadLevelData(VFSFileReference
|
||||
bool result = transcoder.get_image_level_info(levelInfo, i, 0, 0);
|
||||
_dbg_assert_(result);
|
||||
|
||||
size_t dataSizeBytes = levelInfo.m_total_blocks * blockSize;
|
||||
size_t dataSizeBytes = (size_t)levelInfo.m_total_blocks * blockSize;
|
||||
size_t outputSize = levelInfo.m_total_blocks;
|
||||
size_t outputPitch = levelInfo.m_num_blocks_x;
|
||||
// Support transcoded-to-RGBA8888 images too.
|
||||
if (!bc) {
|
||||
dataSizeBytes = levelInfo.m_orig_width * levelInfo.m_orig_height * 4;
|
||||
outputSize = levelInfo.m_orig_width * levelInfo.m_orig_height;
|
||||
dataSizeBytes = (size_t)levelInfo.m_orig_width * levelInfo.m_orig_height * 4;
|
||||
outputSize = (size_t)levelInfo.m_orig_width * levelInfo.m_orig_height;
|
||||
outputPitch = levelInfo.m_orig_width;
|
||||
}
|
||||
out.resize(dataSizeBytes);
|
||||
|
||||
transcodeState.clear();
|
||||
transcoder.transcode_image_level(i, 0, 0, &out[0], (uint32_t)outputSize, transcoderFormat, 0, (uint32_t)outputPitch, level.h, -1, -1, &transcodeState);
|
||||
if (!result || dataSizeBytes == 0 || !transcoder.transcode_image_level(i, 0, 0, &out[0], (uint32_t)outputSize, transcoderFormat, 0, (uint32_t)outputPitch, levelInfo.m_orig_height, -1, -1, &transcodeState)) {
|
||||
WARN_LOG(Log::TexReplacement, "KTX2: Failed to transcode level %d of %s", i, filename.c_str());
|
||||
std::fill(out.begin(), out.end(), 0);
|
||||
}
|
||||
level.w = levelInfo.m_orig_width;
|
||||
level.h = levelInfo.m_orig_height;
|
||||
if (i != 0)
|
||||
@@ -822,15 +831,15 @@ ReplacedTexture::LoadLevelResult ReplacedTexture::LoadLevelData(VFSFileReference
|
||||
// We only use the first level for now.
|
||||
int numLevels = LoadZIMPtr(&zim[0], fileSize, w, h, &f, image);
|
||||
if (numLevels > 0) {
|
||||
if (w[0] > level.w || h[0] > level.h) {
|
||||
if (w[0] > level.w || h[0] > level.h || (f & ZIM_FORMAT_MASK) != ZIM_RGBA8888) {
|
||||
ERROR_LOG(Log::TexReplacement, "Texture replacement changed since header read: %s", filename.c_str());
|
||||
free(image[0]);
|
||||
return LoadLevelResult::LOAD_ERROR;
|
||||
}
|
||||
|
||||
out.resize(level.w * level.h * 4);
|
||||
out.resize((size_t)level.w * level.h * 4);
|
||||
if (w[0] == level.w) {
|
||||
memcpy(&out[0], image[0], level.w * 4 * level.h);
|
||||
memcpy(&out[0], image[0], (size_t)level.w * 4 * h[0]);
|
||||
} else {
|
||||
for (int y = 0; y < h[0]; ++y) {
|
||||
memcpy(&out[level.w * 4 * y], image[0] + w[0] * 4 * y, w[0] * 4);
|
||||
|
||||
+8
-2
@@ -3188,12 +3188,18 @@ namespace jpgd {
|
||||
*height = image_height;
|
||||
*actual_comps = decoder.get_num_components();
|
||||
|
||||
// PPSSPP: Nothing we decode is anywhere near this, and the header alone (from a game, or
|
||||
// a file) could otherwise make us allocate gigabytes.
|
||||
if (image_width <= 0 || image_height <= 0 || image_width > 8192 || image_height > 8192)
|
||||
return nullptr;
|
||||
|
||||
if (decoder.begin_decoding() != JPGD_SUCCESS)
|
||||
return nullptr;
|
||||
|
||||
const int dst_bpl = image_width * req_comps;
|
||||
|
||||
uint8* pImage_data = (uint8*)jpgd_malloc(dst_bpl * image_height);
|
||||
// PPSSPP: In size_t, the int product overflows for large (up to 32768x32768) images.
|
||||
uint8* pImage_data = (uint8*)jpgd_malloc((size_t)dst_bpl * image_height);
|
||||
if (!pImage_data)
|
||||
return nullptr;
|
||||
|
||||
@@ -3207,7 +3213,7 @@ namespace jpgd {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
uint8* pDst = pImage_data + y * dst_bpl;
|
||||
uint8* pDst = pImage_data + (size_t)y * dst_bpl;
|
||||
|
||||
if (((req_comps == 1) && (decoder.get_num_components() == 1)) || ((req_comps == 4) && (decoder.get_num_components() == 3)))
|
||||
memcpy(pDst, pScan_line, dst_bpl);
|
||||
|
||||
Reference in new issue
Block a user