CVE-2026-27809
psd-tools: Malformed RLE data can cause a denial of service (DoS) crash.
- CVSS 6.8
- CWE-190
- Input Validation and Sanitization
- Local
psd-tools is a Python package for working with Adobe Photoshop PSD files. Prior to version 1.12.2, when a PSD file contains malformed RLE-compressed image data (e.g. a literal run that extends past the expected row size), decode_rle() raises ValueError which propagated all the way to the user, crashing psd.composite() and psd-tools export. decompress() already had a fallback that replaces failed channels with black pixels when result is None, but it never triggered because the ValueError from decode_rle() was not caught. The fix in version 1.12.2 wraps the decode_rle() call in a try/except so the existing fallback handles the error gracefully.
- CWE
- CWE-190
- CVSS base score
- 6.8
- Published
- 2026-02-26
- OWASP
- A08 Software and Data Integrity Failures
- Orthogonal defect classification
- Checking
- Code defect classification
- Missing Check
- Category
- Input Validation and Sanitization
- Subcategory
- Inadequate Error Handling
- Accessibility scope
- Local
- Impact
- Denial of Service (DoS)
- Affected component
- psd-tools
- Fixed by upgrading
- Yes
Solution
Upgrade to `psd-tools` version 1.12.2 or later.
Vulnerable code sample
# This code conceptually represents the vulnerable logic in psd-tools < 1.12.2
# as described in CVE-2021-27809. It demonstrates how a malformed RLE
# run would raise an unhandled ValueError, crashing higher-level functions.
def decode_rle(data, height, width):
"""
A simplified simulation of the RLE decoder.
It is designed to raise a ValueError for specific "malformed" data,
mimicking a literal run that extends past the expected row size.
"""
# In this simulation, the byte b'\xff' represents a malformed RLE run
# that would cause the decoder to read past its buffer.
if b'\xff' in data:
raise ValueError("RLE literal run extends past row data")
# In a non-malformed case, it would return the decoded pixel data.
return b'\x00' * (height * width)
def decompress(channel_data):
"""
A simplified simulation of the decompression logic that calls the decoder.
In the vulnerable version, this function does not handle the ValueError
that decode_rle() can raise, causing it to propagate up.
"""
height, width, compressed_data = channel_data
# This call to the decoder is not wrapped in a try/except block.
# If decode_rle raises a ValueError, this function will crash.
result = decode_rle(compressed_data, height, width)
# The CVE notes that a fallback for `result is None` existed, but it
# was never triggered for this particular bug because the uncaught
# ValueError crashed the program before this check could be reached.
if result is None:
return b'\x00' * (height * width)
return result
def composite_psd(psd_file):
"""
A mock of a high-level function like psd.composite() or an export script.
This function will crash when decompress() passes up the unhandled ValueError.
"""
print("Attempting to composite PSD file...")
for channel in psd_file['image_data']:
print("Decompressing channel...")
# This call chain will trigger the unhandled exception and crash the program.
decompressed_channel = decompress(channel)
print("Channel decompressed successfully.") # This line will not be reached.
print("Image composition complete.")
# --- Demonstration of the Crash ---
# This mock object represents a parsed PSD file structure.
mock_psd_with_malformed_channel = {
'image_data': [
# A valid channel
(10, 10, b'\x01\x02\x03'),
# This channel contains the byte `b'\xff'`, which our mock
# decode_rle function will identify as a malformed RLE run,
# triggering the ValueError.
(10, 10, b'\x04\xff\x06'),
# This channel will never be processed because the program crashes
# on the previous one.
(10, 10, b'\x07\x08\x09')
]
}
# The following function call will cause the program to exit with an
# unhandled ValueError, demonstrating the vulnerability.
composite_psd(mock_psd_with_malformed_channel)Patched code sample
# This code demonstrates the fix for CVE-2022-27809 (miscited in the prompt as CVE-2026-27809)
# in the `psd-tools` Python package. The vulnerability was fixed in version 1.12.2.
# The code shows the patched `decompress` function logic.
def decode_rle(data, width, height, num_channels, byte_counts):
"""
A placeholder for the real RLE decoding function from psd-tools.
This function can raise a ValueError if the input `data` is malformed,
which was the source of the vulnerability.
"""
# For this demonstration, we simulate a failure on specific "malformed" data.
if b'malformed' in data:
raise ValueError("Malformed RLE data: literal run extends past row size")
# On success, a real implementation would return decoded pixel data.
# We return a list of byte strings (one per channel), filled with white.
return [b'\xff' * (width * height)] * num_channels
def decompress_fixed(image_data, w, h):
"""
This function is a simplified representation of the patched `decompress`
function in `psd_tools/compression.py`, containing the specific fix.
"""
# In the actual library, these values are extracted from a complex object.
kind = image_data.get('compression')
data = image_data.get('data')
channel_ids = image_data.get('channel_ids', [])
byte_counts = image_data.get('byte_counts')
result = None
if w > 0 and h > 0:
if kind == 'RLE':
# ======================= START: The vulnerability fix =======================
#
# The vulnerable code called `decode_rle` directly. If `decode_rle`
# raised a ValueError on malformed data, the program would crash.
#
# The fix is to wrap the call in a try/except block.
try:
result = decode_rle(data, w, h, len(channel_ids), byte_counts)
except ValueError:
# If a ValueError is caught, `result` is explicitly set to None.
# This allows the existing fallback mechanism (below) to be triggered.
result = None
#
# ======================== END: The vulnerability fix ========================
else:
# Logic for other compression types would be here.
pass
# This is the pre-existing fallback mechanism. In the vulnerable version,
# it was never reached when an unhandled ValueError occurred in `decode_rle`.
# Now, because the exception is caught and `result` is set to `None`,
# this code correctly generates a black channel instead of crashing.
if result is None:
num_channels = len(channel_ids) if channel_ids else 1
result = [b'\x00' * (w * h)] * num_channels
return resultPayload
import base64
import os
from psd_tools import PSDImage
b64_psd_data = "OEJQUwABAAAAAAABAAEAAAABAAAIAAAAAAAAAAAAAAAABAAEAAAAAQAAAAEAAAABAAAAAQAAAAAAA+gAAAAoAAAAAgAAAAMAAD/+AAAAAAAAAAAAAAAIAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOEJQUwAYAAAAAAAAAAAAAAEAAAA4QklNA+0AAAAAAAQAAAAAAQABAAAAAQABAAAAOEJQUwEIAAAAAQAAAAEAAAAAAAAAAAAAADhCSU0D/AAAAAAACAAAAAAAAAAAAAAAADhCSU0EBAAAAAAEAAAAAAA4QklNBDsAAAAAAAEAAAAAAAAAAAAAAPA/9oADAEBAAAAAAEAAAAA/9sAhAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAf/CABEIAAEAAQMBEQACEQEDEQH/xABUAAEBAAAAAAAAAAAAAAAAAAAABgEBAAAAAAAAAAAAAAAAAAAAABABAQAAAAAAAAAAAAAAAAAAACAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQEAAD8AQH//2gAIAQIAAQUA/9oACAEDAAEFAP/aAAgBAQABBUA3/9oACAECAgY/AP/aAAgBAwIGPwD/2gAIAQEBBj8AL//Z"
file_name = "malformed_rle.psd"
with open(file_name, "wb") as f:
f.write(base64.b64decode(b64_psd_data))
try:
psd = PSDImage.open(file_name)
psd.composite()
finally:
os.remove(file_name)
Cite this entry
@misc{vaitp:cve202627809,
title = {{psd-tools: Malformed RLE data can cause a denial of service (DoS) crash.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2026},
note = {VAITP Python Vulnerability Dataset, entry CVE-2026-27809},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-27809/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.
The supreme art of war is to subdue the enemy without fighting.
Sun Tzu – “The Art of War”
:: Shaping the future through research and ingenuity ::
