From 0eabdb39edbf0493f9529725fd40c1981b9070dc Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Wed, 8 Jul 2026 11:44:15 -0700 Subject: [PATCH] fix(psd): avoid signed overflow computing layer/mask extents read_layer_record computed a layer's width/height as std::abs((int)right - (int)left). The rectangle coordinates are signed 32-bit values (layers can have negative offsets), but were stored in unsigned (uint32_t) struct fields, and subtracting two ints near the extremes overflows -- undefined behavior that UBSan flagged on a fuzzed file (1615681375 - -1605526113 cannot be represented in type 'int'). Retype Layer's and MaskData's top/left/bottom/right fields as int32_t, matching what they actually hold, instead of reinterpreting bits at the point of use. Widen to int64_t before subtracting so the computation can't overflow; the absolute difference of two int32 values always fits in uint32, and validate_resolution still rejects implausibly large extents. The mask data rect (used for layer masks) had the identical std::abs((int)right - (int)left) overflow hazard in load_layer_channel and gets the same fix. Assisted-by: Claude Code / Sonnet 5 Signed-off-by: Larry Gritz --- src/psd.imageio/psdinput.cpp | 44 +++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/src/psd.imageio/psdinput.cpp b/src/psd.imageio/psdinput.cpp index 28395bebab..1d8dff8470 100644 --- a/src/psd.imageio/psdinput.cpp +++ b/src/psd.imageio/psdinput.cpp @@ -143,7 +143,7 @@ class PSDInput final : public ImageInput { }; struct Layer { - uint32_t top, left, bottom, right; + int32_t top, left, bottom, right; uint32_t width, height; uint16_t channel_count; @@ -157,7 +157,7 @@ class PSDInput final : public ImageInput { uint32_t extra_length; struct MaskData { - uint32_t top, left, bottom, right; + int32_t top, left, bottom, right; uint8_t default_color; uint8_t flags; }; @@ -1557,16 +1557,22 @@ bool PSDInput::load_layer(Layer& layer) { bool ok = true; - ok &= read_bige(layer.top); - ok &= read_bige(layer.left); - ok &= read_bige(layer.bottom); - ok &= read_bige(layer.right); + ok &= read_bige(layer.top); + ok &= read_bige(layer.left); + ok &= read_bige(layer.bottom); + ok &= read_bige(layer.right); ok &= read_bige(layer.channel_count); if (!ok) return false; - layer.width = std::abs((int)layer.right - (int)layer.left); - layer.height = std::abs((int)layer.bottom - (int)layer.top); + // The rectangle coordinates are signed 32-bit (a layer may have negative + // offsets). Widen to int64 before subtracting: a plain int32 subtraction + // can overflow (undefined behavior) on corrupt files with extreme values. + // The absolute difference of two int32 always fits in uint32. + int64_t w = int64_t(layer.right) - int64_t(layer.left); + int64_t h = int64_t(layer.bottom) - int64_t(layer.top); + layer.width = uint32_t(w < 0 ? -w : w); + layer.height = uint32_t(h < 0 ? -h : h); if (!validate_resolution("Layer Record", layer.width, layer.height)) return false; layer.channel_info.resize(layer.channel_count); @@ -1608,10 +1614,10 @@ PSDInput::load_layer(Layer& layer) auto lmd_end = lmd_start + lmd_length; if (lmd_length >= 4 * 4 + 1 * 2) { - ok &= read_bige(layer.mask_data.top); - ok &= read_bige(layer.mask_data.left); - ok &= read_bige(layer.mask_data.bottom); - ok &= read_bige(layer.mask_data.right); + ok &= read_bige(layer.mask_data.top); + ok &= read_bige(layer.mask_data.left); + ok &= read_bige(layer.mask_data.bottom); + ok &= read_bige(layer.mask_data.right); ok &= read_bige(layer.mask_data.default_color); ok &= read_bige(layer.mask_data.flags); } @@ -1694,10 +1700,16 @@ PSDInput::load_layer_channel(Layer& layer, ChannelInfo& channel_info) // Use mask_data size when channel_id is -2 uint32_t width, height; if (channel_info.channel_id == ChannelID_LayerMask) { - width = (uint32_t)std::abs((int)layer.mask_data.right - - (int)layer.mask_data.left); - height = (uint32_t)std::abs((int)layer.mask_data.bottom - - (int)layer.mask_data.top); + // Widen to int64 before subtracting: a plain int32 subtraction can + // overflow (undefined behavior) on corrupt files with extreme + // values. The absolute difference of two int32 always fits in + // uint32. + int64_t w = int64_t(layer.mask_data.right) + - int64_t(layer.mask_data.left); + int64_t h = int64_t(layer.mask_data.bottom) + - int64_t(layer.mask_data.top); + width = uint32_t(w < 0 ? -w : w); + height = uint32_t(h < 0 ? -h : h); } else { width = layer.width; height = layer.height;