diff --git a/src/psd.imageio/psdinput.cpp b/src/psd.imageio/psdinput.cpp index e22617db63..eb9cb3a0ff 100644 --- a/src/psd.imageio/psdinput.cpp +++ b/src/psd.imageio/psdinput.cpp @@ -227,6 +227,7 @@ class PSDInput final : public ImageInput { GlobalMaskInfo m_global_mask_info; ImageDataSection m_image_data; ImageBuf m_thumbnail; + uint32_t m_maxres = 30000; // Maximum resolution based on header version //Reset to initial state void init(); @@ -236,6 +237,7 @@ class PSDInput final : public ImageInput { bool read_header(); bool validate_header(); static bool validate_signature(const char signature[4]); + bool validate_resolution(string_view name, uint32_t width, uint32_t height); //Color Mode Data bool load_color_data(); @@ -436,7 +438,8 @@ class PSDInput final : public ImageInput { return true; } - int read_pascal_string(std::string& s, uint16_t mod_padding); + bool read_pascal_string(std::string& s, uint16_t mod_padding, + int* bytes_read = nullptr); // Swap a planar bytespan representing the bytes of a float vector to its // interleaved byte order. This is per scanline @@ -966,6 +969,22 @@ PSDInput::validate_signature(const char signature[4]) +bool +PSDInput::validate_resolution(string_view name, uint32_t width, uint32_t height) +{ + if (width < 1 || width > m_maxres) { + errorfmt("[{}] invalid image width {}", name, width); + return false; + } + if (height < 1 || height > m_maxres) { + errorfmt("[{}] invalid image height {}", name, height); + return false; + } + return true; +} + + + bool PSDInput::validate_header() { @@ -981,32 +1000,13 @@ PSDInput::validate_header() errorfmt("[Header] invalid channel count"); return false; } - switch (m_header.version) { - case 1: - // PSD - // width/height range: [1,30000] - if (m_header.height < 1 || m_header.height > 30000) { - errorfmt("[Header] invalid image height"); - return false; - } - if (m_header.width < 1 || m_header.width > 30000) { - errorfmt("[Header] invalid image width"); - return false; - } - break; - case 2: - // PSB (Large Document Format) - // width/height range: [1,300000] - if (m_header.height < 1 || m_header.height > 300000) { - errorfmt("[Header] invalid image height {}", m_header.height); - return false; - } - if (m_header.width < 1 || m_header.width > 300000) { - errorfmt("[Header] invalid image width {}", m_header.width); - return false; - } - break; - } + if (m_header.version == 2 /* PSB - Large Document Format */) + m_maxres = 300000; + else /* PSD */ + m_maxres = 30000; + if (!validate_resolution("Header", m_header.height, m_header.width)) + return false; + // Valid depths are 1,8,16,32 if (m_header.depth != 1 && m_header.depth != 8 && m_header.depth != 16 && m_header.depth != 32) { @@ -1205,7 +1205,10 @@ PSDInput::load_resource_1006(uint32_t length) int32_t bytes_remaining = length; std::string name; while (bytes_remaining >= 2) { - bytes_remaining -= read_pascal_string(name, 1); + int b = 0; + if (!read_pascal_string(name, 1, &b)) + return false; + bytes_remaining -= b; m_alpha_names.push_back(name); } return true; @@ -1524,6 +1527,8 @@ PSDInput::load_layer(Layer& layer) layer.width = std::abs((int)layer.right - (int)layer.left); layer.height = std::abs((int)layer.bottom - (int)layer.top); + if (!validate_resolution("Layer Record", layer.width, layer.height)) + return false; layer.channel_info.resize(layer.channel_count); for (uint16_t channel = 0; channel < layer.channel_count; channel++) { ChannelInfo& channel_info = layer.channel_info[channel]; @@ -1589,7 +1594,10 @@ PSDInput::load_layer(Layer& layer) if (!ok) return false; - extra_remaining -= read_pascal_string(layer.name, 4); + int b = 0; + if (!read_pascal_string(layer.name, 4, &b)) + return false; + extra_remaining -= b; while (ok && extra_remaining >= 12) { layer.additional_info.emplace_back(); Layer::AdditionalInfo& info = layer.additional_info.back(); @@ -1654,6 +1662,8 @@ PSDInput::load_layer_channel(Layer& layer, ChannelInfo& channel_info) width = layer.width; height = layer.height; } + if (!validate_resolution("layer_channel", width, height)) + return false; channel_info.width = width; channel_info.height = height; @@ -2226,33 +2236,40 @@ PSDInput::set_type_desc() -int -PSDInput::read_pascal_string(std::string& s, uint16_t mod_padding) +bool +PSDInput::read_pascal_string(std::string& s, uint16_t mod_padding, + int* bytes_read) { + if (bytes_read) + *bytes_read = 0; + int bytes = 0; s.clear(); uint8_t length; - int bytes = 0; - if (ioread((char*)&length, 1)) { - bytes = 1; - if (length == 0) { - if (ioseek(mod_padding - 1, SEEK_CUR)) - bytes += mod_padding - 1; - } else { - s.resize(length); - if (ioread(&s[0], length)) { - bytes += length; - if (mod_padding > 0) { - for (int padded_length = length + 1; - padded_length % mod_padding != 0; padded_length++) { - if (!ioseek(1, SEEK_CUR)) - break; - bytes++; - } - } + if (!ioread((char*)&length, 1)) + return false; + bytes = 1; + if (length == 0) { + if (ioseek(mod_padding - 1, SEEK_CUR)) + bytes += mod_padding - 1; + else + return false; + } else { + s.resize(length); + if (!ioread(&s[0], length)) + return false; + bytes += length; + if (mod_padding > 0) { + for (int padded_length = length + 1; + padded_length % mod_padding != 0; padded_length++) { + if (!ioseek(1, SEEK_CUR)) + return false; + bytes++; } } } - return bytes; + if (bytes_read) + *bytes_read = bytes; + return true; } diff --git a/testsuite/psd/ref/out-linuxarm.txt b/testsuite/psd/ref/out-linuxarm.txt index 799e1d0e29..50a050ea38 100644 --- a/testsuite/psd/ref/out-linuxarm.txt +++ b/testsuite/psd/ref/out-linuxarm.txt @@ -2105,8 +2105,15 @@ oiiotool ERROR: read : [Image Data Section] channel count 3 is too few for color failed to open "src/crash-8a15.psd": failed load_image_data Full command line was: > oiiotool --info -v -a --hash src/crash-8a15.psd -oiiotool ERROR: read : unable to decode zip compressed data: src_size=39, dst_size=1296 -Error during layer decompression. Possible corrupt file? +oiiotool ERROR: read : [Layer Record] invalid image width 16777264 failed to open "../oiio-images/psd/corrupt_20260312a.psd": failed load_global_additional Full command line was: > oiiotool --info -v -a --hash ../oiio-images/psd/corrupt_20260312a.psd +oiiotool ERROR: read : [Layer Record] invalid image height 1493172215 +failed to open "src/crash-layerres.psd": failed load_layers +Full command line was: +> oiiotool --info -v -a --hash src/crash-layerres.psd +oiiotool ERROR: read : Read error: hit end of file in psd reader +failed to open "src/crash-eofstring.psd": failed load_resources +Full command line was: +> oiiotool --info -v -a --hash src/crash-eofstring.psd diff --git a/testsuite/psd/ref/out.txt b/testsuite/psd/ref/out.txt index 8d43514ac3..fb8bc906e5 100644 --- a/testsuite/psd/ref/out.txt +++ b/testsuite/psd/ref/out.txt @@ -2105,8 +2105,15 @@ oiiotool ERROR: read : [Image Data Section] channel count 3 is too few for color failed to open "src/crash-8a15.psd": failed load_image_data Full command line was: > oiiotool --info -v -a --hash src/crash-8a15.psd -oiiotool ERROR: read : unable to decode zip compressed data: src_size=39, dst_size=1296 -Error during layer decompression. Possible corrupt file? +oiiotool ERROR: read : [Layer Record] invalid image width 16777264 failed to open "../oiio-images/psd/corrupt_20260312a.psd": failed load_global_additional Full command line was: > oiiotool --info -v -a --hash ../oiio-images/psd/corrupt_20260312a.psd +oiiotool ERROR: read : [Layer Record] invalid image height 1493172215 +failed to open "src/crash-layerres.psd": failed load_layers +Full command line was: +> oiiotool --info -v -a --hash src/crash-layerres.psd +oiiotool ERROR: read : Read error: hit end of file in psd reader +failed to open "src/crash-eofstring.psd": failed load_resources +Full command line was: +> oiiotool --info -v -a --hash src/crash-eofstring.psd diff --git a/testsuite/psd/run.py b/testsuite/psd/run.py index 031b3ec215..33700e5ee5 100755 --- a/testsuite/psd/run.py +++ b/testsuite/psd/run.py @@ -34,4 +34,7 @@ command += info_command ("src/crash-8a15.psd", failureok=True) # Corruption where bad zip compression data caused a buffer overrun command += info_command (OIIO_TESTSUITE_IMAGEDIR + "/corrupt_20260312a.psd", failureok=True) - +# Corruption where invalid layer resolution caused a huge allocation +command += info_command ("src/crash-layerres.psd", failureok=True) +# Corruption where eof was hit in the end of a string read +command += info_command ("src/crash-eofstring.psd", failureok=True) diff --git a/testsuite/psd/src/crash-eofstring.psd b/testsuite/psd/src/crash-eofstring.psd new file mode 100644 index 0000000000..a816e64ec5 Binary files /dev/null and b/testsuite/psd/src/crash-eofstring.psd differ diff --git a/testsuite/psd/src/crash-layerres.psd b/testsuite/psd/src/crash-layerres.psd new file mode 100644 index 0000000000..1514c5898e Binary files /dev/null and b/testsuite/psd/src/crash-layerres.psd differ