Safeguard
Application Security

CVE-2021-25287: Buffer overflow in Pillow SGI decoder

A heap buffer overflow in Pillow's SGI image decoder (CVE-2021-25287) let crafted images corrupt memory. Here's the impact, fix, and remediation guidance.

Aman Khan
AppSec Engineer
8 min read

Pillow, the de facto standard imaging library for Python, shipped a heap-based buffer overflow in its SGI (Silicon Graphics Image) decoder that could be triggered simply by asking the library to open a malicious image file. Tracked as CVE-2021-25287, the flaw lives in the run-length encoding (RLE) decompression path Pillow uses to read .sgi/.rgb files. A crafted image with an invalid or oversized RLE run could cause the decoder to write past the bounds of its allocated buffer, corrupting adjacent heap memory. Depending on how an application feeds untrusted images into Pillow — thumbnail generators, document converters, chat attachment previews, CI artifact scanners — the practical impact ranges from a reliable denial-of-service crash to, in the worst case, memory corruption that an attacker could try to leverage for code execution.

This is a textbook example of why "just an image library" bugs matter so much in supply chain security: Pillow is a transitive dependency of an enormous number of Python applications, and very few teams treat image decoding as untrusted input parsing — even though that's exactly what it is.

What CVE-2021-25287 actually is

Pillow's SGI plugin (SgiImagePlugin.py, backed by the native SgiRleDecode.c decoder) is responsible for decoding Silicon Graphics' legacy .sgi/.rgb/.bw image format. SGI images can optionally use run-length encoding to compress scanline data. The decoder reads a sequence of RLE opcodes — a run length followed by either a literal byte sequence or a repeated value — and writes the decompressed pixels into a fixed-size output buffer sized according to the image header.

The vulnerability arises because the decoder did not adequately validate that the RLE run lengths it read from the file actually fit within the destination buffer before writing to it. An attacker who controls the image bytes can specify run lengths that, when decoded, exceed the buffer Pillow allocated based on the (attacker-influenced) image dimensions and channel count. The result is an out-of-bounds heap write: the decoder keeps writing decompressed pixel data past the end of the buffer it was given.

This is the same general bug class — untrusted length/size fields driving buffer writes without bounds checks — that shows up repeatedly in binary image, font, and archive parsers, precisely because these formats were originally designed for trusted, well-formed input and later got exposed to the open internet as file uploads and email attachments.

CVE-2021-25287 was one of a cluster of image-decoder memory-safety issues fixed together in Pillow around the same release, alongside related out-of-bounds read/write bugs in the FLI, PCX, and PSD decoders (tracked under neighboring CVE IDs). That pattern is worth noting on its own: when a fuzzing pass or security review turns up one bounds-checking gap in a set of format decoders sharing similar C-extension code, it's common to find several more nearby.

Affected Versions and Components

  • Component: Pillow (PIL fork), specifically the SGI image decoder (SgiImagePlugin.py / SgiRleDecode.c)
  • Affected versions: Pillow releases prior to 8.1.1
  • Fixed version: Pillow 8.1.1, which bundled fixes for the SGI decoder overflow along with the other image-decoder memory-safety issues fixed in the same release cycle
  • Trigger: Opening/decoding an attacker-supplied SGI-format image (Image.open() on a .sgi/.rgb/.bw file, or any code path — including automatic format sniffing — that routes bytes through Pillow's SGI plugin)
  • Exposure surface: Any Python service that decodes user-supplied images without restricting accepted formats — image upload endpoints, document/thumbnail pipelines, PDF/Office conversion services, ML data-loading pipelines, and CI/CD tooling that inspects build artifacts

Because Pillow auto-detects image format from file content rather than trusting a file extension or declared MIME type by default, an application doesn't need to explicitly support SGI images for this to be reachable — it only needs to call Pillow's generic Image.open() on attacker-controlled bytes.

CVSS, EPSS, and KEV Context

NVD's CVSS v3.1 scoring for this CVE places it in the critical range (9.8), reflecting a network attack vector, low attack complexity, no privileges or user interaction required, and high impact to confidentiality, integrity, and availability under the standard library-vulnerability scoring model. As with most parsing-library CVEs, that score assumes the worst-case deployment — a service that decodes attacker-supplied images directly, with no sandboxing or format allow-listing — so real-world risk should always be adjusted for how a given application actually uses Pillow.

CVE-2021-25287 has not been added to CISA's Known Exploited Vulnerabilities (KEV) catalog, and there is no widely reported evidence of in-the-wild exploitation. That doesn't mean it's unimportant — plenty of critical, unexploited-in-practice bugs still represent real risk in any pipeline that decodes untrusted images — but it does mean prioritization should weigh actual exposure (do you parse untrusted SGI files? at all?) alongside the raw severity score rather than treating every critical CVE as an active-exploitation emergency.

Timeline

  • Discovery: The SGI decoder overflow was identified as part of a broader review/fuzzing effort against Pillow's native image decoders, alongside several related out-of-bounds issues in other legacy format plugins (FLI, PCX, PSD).
  • Fix released: The issue was patched in Pillow 8.1.1, shipped in January 2021, shortly after the Pillow 8.1.0 release. Pillow's maintainers bundled the SGI fix with fixes for the other decoder issues discovered in the same effort.
  • CVE publication: CVE-2021-25287 was published in the NVD in the weeks following the 8.1.1 release, formalizing the advisory for downstream tracking.
  • Downstream response: Linux distributions and language-ecosystem advisory databases (Debian, Ubuntu, Red Hat, GitHub Advisory Database, PyPA Advisory Database) subsequently republished the fix, and dependency scanners began flagging Pillow versions below 8.1.1 against this CVE ID.

Since then, Pillow has continued to harden its image decoders through further releases (8.1.2, 8.2.0, and beyond), so "upgrade past 8.1.1" should be read as a floor, not a target — current guidance is always to run the latest supported Pillow release.

Remediation

  1. Upgrade Pillow to 8.1.1 or later — and ideally to the current supported release. This is the definitive fix; there is no safe workaround that lets you keep an older Pillow version and remain protected against this specific decoder bug.
  2. Inventory where Pillow actually gets called on untrusted input. Search for Image.open(), Image.frombytes(), and any code that hands request bodies, uploaded files, or fetched URLs to Pillow. Confirm those call sites are covered by your dependency-pinning and upgrade process, not just your top-level requirements.txt.
  3. Restrict accepted image formats where feasible. If your application only needs to support JPEG/PNG/WebP uploads, don't leave Pillow's full auto-detection surface open to obscure legacy formats like SGI, which are rarely needed and disproportionately represented in decoder-bug advisories.
  4. Sandbox or isolate image decoding. Running decode operations in a memory-safe, resource-limited worker (separate process, container, or seccomp-restricted sandbox) limits the blast radius of any future decoder bug — this one included — even after you've patched.
  5. Verify the fix reaches production, not just your lockfile. Pillow is frequently pulled in transitively (via Django, ReportLab, python-docx, image-processing ML libraries, etc.). Confirm the resolved dependency graph — not just your direct requirements.txt — actually installs 8.1.1+, since a transitive pin elsewhere can silently reintroduce the vulnerable version.
  6. Re-scan after upgrading. Confirm your SBOM and vulnerability scanner both reflect the updated Pillow version across every service and build artifact, including container base images that may have cached an older wheel.

How Safeguard Helps

CVE-2021-25287 is a good illustration of why supply chain visibility has to go deeper than a top-level requirements.txt diff. Pillow is exactly the kind of dependency that gets pulled in transitively through half a dozen other packages, cached inside container base images, vendored into internal tooling, and quietly left unpatched in services nobody thinks of as "image processing" — CI runners that thumbnail build artifacts, PDF generators, admin dashboards with avatar uploads.

Safeguard is built to close exactly that gap. Continuous SBOM generation across your codebases and build artifacts gives you a real, current inventory of every Pillow instance in your environment — direct and transitive — instead of relying on a manifest file that may not reflect what actually ships. When a CVE like this one is disclosed, Safeguard maps it against that live inventory to tell you precisely which services, containers, and pipelines are affected, rather than leaving your team to grep through dozens of repos under time pressure.

Because Safeguard correlates severity data (CVSS), real-world exploitation signals (KEV status, EPSS trends), and your own deployment context, it helps you separate "critical CVSS score, but no realistic exposure" from "this is reachable in a production service that ingests user-uploaded images today" — so remediation effort goes where it actually reduces risk first. And because Safeguard tracks provenance and build integrity alongside vulnerability data, you get confirmation that the patched Pillow version actually made it into the artifacts you deploy, closing the loop between "we updated the dependency" and "the fix is running in production."

For a bug class this common — untrusted image, font, and archive parsers with insufficient bounds checking — that kind of continuous, artifact-level visibility is what turns the next Pillow-style CVE from a scramble into a routine, well-prioritized patch cycle.

Never miss an update

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