Safeguard
Vulnerability Analysis

CVE-2020-35654: Buffer over-read in Pillow PCX decoder

A buffer over-read in Pillow's PCX decoder (CVE-2020-35654) could crash image-processing services on crafted files. Here's the fix and detection guidance.

Nayan Dey
Security Researcher
7 min read

Lede

CVE-2020-35654 is a buffer over-read vulnerability in the PCX image decoder shipped with Pillow, the widely used Python imaging library (a fork/successor of the original PIL project). A specially crafted PCX file can cause Pillow's decoder to read memory outside the bounds of the allocated image buffer while processing the file, leading to a crash of the process handling the image. The practical impact is denial of service: any application, service, or pipeline that calls Pillow to open or process untrusted PCX files can be knocked over by a single malformed input.

Because Pillow sits underneath an enormous share of the Python ecosystem's image handling — web frameworks, thumbnail generators, ML data-loading pipelines, document converters, and CMS upload handlers — a decoder-level memory-safety bug like this has a wide blast radius even though the underlying flaw is "just" an out-of-bounds read rather than a memory-corruption-to-RCE primitive.

Affected versions and components

  • Component: Pillow's PCX decoder (the native/C decoding path used for .pcx image files), part of the PIL/Pillow package.
  • Affected releases: Pillow versions prior to 8.1.0. The advisory text associated with this CVE (and the closely related batch of Pillow decoder CVEs disclosed around the same time) also references the historical PIL codebase through 1.1.7, reflecting the shared decoder heritage between the unmaintained original PIL and its Pillow fork.
  • Fixed in: Pillow 8.1.0, which bundled fixes for several image-decoder buffer over-read issues reported around the same disclosure window (PCX, FLI, and other format decoders were addressed together in that release cycle).
  • Trigger condition: The vulnerability is triggered purely by decoding a malicious file — no special configuration is required beyond an application calling Image.open() (or equivalent) on attacker-supplied PCX content. This makes it a classic "untrusted file upload" or "untrusted image ingestion" risk.

If your dependency tree pulls in Pillow anywhere below 8.1.0 — directly or transitively through libraries that render thumbnails, generate previews, or process user-uploaded images — you should treat this as in scope.

CVSS, EPSS, and KEV context

Pillow's late-2020/early-2021 batch of decoder over-read advisories (including this one) were consistently rated as medium-severity issues by NVD, reflecting an availability-only impact: the flaw allows a crash (denial of service) but does not provide confidentiality or integrity impact and does not describe a path to code execution. There is no indication that CVE-2020-35654 has an elevated EPSS (Exploit Prediction Scoring System) probability, and it does not appear on CISA's Known Exploited Vulnerabilities (KEV) catalog. In other words, this is not a vulnerability with evidence of active in-the-wild exploitation — but low observed exploitation is not the same as low relevance. Buffer over-reads in widely embedded parsing libraries are exactly the class of bug that periodically resurfaces as a building block for more severe follow-on findings, and DoS-only image-parsing bugs remain a legitimate risk for any service that can't tolerate crafted-input crashes (image proxies, batch processing pipelines, unattended ingestion jobs).

The practical takeaway for risk scoring: treat this as a "fix in the normal patch cycle" issue rather than an "all-hands emergency patch," but don't let it linger indefinitely in outdated dependency pins — especially in services that process images from untrusted or semi-trusted sources (public uploads, scraped content, third-party integrations).

Timeline

  • Pillow's maintainers addressed a cluster of image-decoder memory-safety issues — including the PCX buffer over-read that became CVE-2020-35654 — culminating in the Pillow 8.1.0 release (early January 2021), which hardened multiple format decoders against malformed input.
  • The CVE identifier itself follows a 2020 numbering sequence, consistent with the broader wave of Pillow decoder CVEs from that disclosure round; as is common with library ecosystems that batch-report several related issues, public NVD/vendor advisory visibility for this specific ID lagged the actual code fix.
  • Downstream Linux distributions and language-ecosystem advisory databases (e.g., distro security trackers, PyPI/GitHub Security Advisories, OSV) subsequently backfilled the CVE mapping so that dependency-scanning tools could match it against installed Pillow versions.

The key operational fact is simpler than the exact disclosure date: any Pillow install older than 8.1.0 has not received this fix, and every release since has carried it forward as part of Pillow's normal cumulative patching.

Remediation steps

  1. Upgrade Pillow. The direct fix is to move to Pillow 8.1.0 or later. In practice, you should upgrade to the latest maintained Pillow release available for your Python version rather than pinning to the minimum fixed version — Pillow has shipped numerous additional security fixes since 8.1.0, and staying current avoids re-litigating this exercise for every subsequent decoder CVE.
  2. Audit your dependency graph, not just your direct requirements. Pillow is frequently pulled in transitively (via Django, matplotlib, reportlab, various OCR/ML tooling, thumbnail/CMS plugins, etc.). Run a full software composition analysis (SCA) pass to confirm every path that resolves to a pre-8.1.0 Pillow build, including vendored or containerized copies.
  3. Pin and lock. Update lockfiles (requirements.txt, poetry.lock, Pipfile.lock, container base images) so the fixed version is actually what gets deployed, not just what's declared as a floor in a loose version specifier.
  4. Rebuild and redeploy affected artifacts. If Pillow is baked into container images, AMIs, or serverless deployment packages, patching the source manifest isn't sufficient — rebuild and redeploy those artifacts so the fixed library is present at runtime.
  5. Add input hardening where feasible. For services that must accept untrusted image uploads, consider defense-in-depth: run image processing in a sandboxed/resource-limited worker, validate file structure before full decode where possible, and ensure decoder crashes fail gracefully (process isolation, restart policies) rather than taking down a shared service.
  6. Verify with a scan, not just a version check. Confirm the fix landed by re-scanning the deployed environment (not just the repository) for the Pillow version actually running in production, including inside containers and third-party service dependencies.

How Safeguard Helps

CVE-2020-35654 is a good illustration of why point-in-time dependency reviews aren't enough: a fix that shipped years ago can still be quietly present in a stale base image, a vendored copy, or a transitive dependency that nobody re-audited after the initial adoption. Safeguard is built to close exactly that gap across the software supply chain.

  • Continuous SBOM-driven visibility. Safeguard maintains a live software bill of materials across your services and containers, so a component like Pillow is tracked by exact version everywhere it appears — direct dependency, transitive dependency, or baked into a build artifact — rather than relying on a one-time audit.
  • Vulnerability correlation with real-world context. Rather than surfacing every CVE as equally urgent, Safeguard correlates findings like CVE-2020-35654 against CVSS impact, EPSS exploitation likelihood, and KEV status, helping teams prioritize the DoS-relevant, actively exploited issues over the long tail of low-risk advisories — and avoid alert fatigue from CVEs with no meaningful exploitation signal.
  • Drift and re-introduction detection. Because vulnerable versions can creep back in through base image updates, forked repos, or unpinned build steps, Safeguard continuously re-checks deployed artifacts against known-fixed versions, catching regressions before they reach production.
  • Provenance and build integrity. Safeguard's supply chain controls verify that the Pillow package (and its build/publish pipeline) resolving into your artifacts matches trusted, attested sources — reducing the risk that a patched dependency declaration is silently undermined by an untrusted or tampered build step.
  • Actionable remediation workflows. When a vulnerable Pillow version is detected, Safeguard surfaces the affected services, the minimum safe version, and the exact artifacts requiring rebuild, turning a sprawling "which of our 40 services still ship an old Pillow" question into a scoped, trackable remediation task.

For any organization processing user-supplied images at scale, CVE-2020-35654 is a low-drama but real reminder: image-parsing libraries are a permanent attack surface, and supply chain visibility is what turns "we should probably check that" into a solved problem.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.