Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions .github/SECURITY.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,21 @@
# Security policy

## Reporting a vulnerability

To report sensitive vulnerability information, report it [privately on GitHub](https://github.com/python-pillow/Pillow/security/advisories/new).

If you cannot use GitHub, use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure.
If you cannot use GitHub, use the [Tidelift security contact](https://tidelift.com/docs/security). Tidelift will coordinate the fix and disclosure.

**DO NOT report sensitive vulnerability information in public.**

## Threat model

Pillow's primary attack surface is parsing untrusted image data. A full STRIDE threat model covering spoofing, tampering, repudiation, information disclosure, denial of service, and elevation of privilege is maintained in the [Security handbook page](https://pillow.readthedocs.io/en/latest/handbook/security.html).

Key risks to be aware of when using Pillow to process untrusted images:

DO NOT report sensitive vulnerability information in public.
- **Decompression bombs** — do not set `Image.MAX_IMAGE_PIXELS = None` in production.
- **EPS files invoke Ghostscript** — block EPS input at the application layer unless strictly required.
- **`ImageMath.unsafe_eval()`** — never pass user-controlled strings to this function; use `lambda_eval` instead.
- **C extension memory safety** — keep Pillow and its bundled C libraries (libjpeg, libpng, libtiff, libwebp, etc.) up to date.
- **Sandboxing** — for high-risk deployments, run image processing in a sandboxed subprocess.
1 change: 1 addition & 0 deletions docs/handbook/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ Handbook
tutorial
concepts
appendices
security
252 changes: 252 additions & 0 deletions docs/handbook/security.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
Security
========

Pillow's primary attack surface is **parsing untrusted image data**. This page
documents the threat model for developers integrating Pillow into applications
that handle images from untrusted sources, along with recommended mitigations.

To report a vulnerability see :ref:`security-reporting`.

.. _security-threat-model:

Threat model (STRIDE)
---------------------

The analysis below follows the `STRIDE
<https://en.wikipedia.org/wiki/STRIDE_(security)>`_ framework and covers the
boundary between untrusted image input and the Pillow API.

.. code-block:: text

┌──────────────────────────────────────────┐
Untrusted zone │ Pillow API │
───────────── │ │
Image files ────►│ Image.open() ──► Format plugins │
Byte streams │ (40+ parsers) (Python + C FFI) │
User metadata │ │
│ ImageMath.unsafe_eval(expr) ───────────┼──► Python eval()
│ ImageShow.show(image) ─────────────────┼──► os.system / subprocess
│ EpsImagePlugin.open(eps) ──────────────┼──► Ghostscript (gs)
└──────────────┬───────────────────────────┘
│ C extension (_imaging)
┌──────────────────────────────────────────┐
│ C libraries (bundled or system) │
│ libjpeg · libpng · libtiff · libwebp │
│ openjpeg · freetype · littlecms │
└──────────────────────────────────────────┘

Spoofing
^^^^^^^^

**S-1 — Format sniffing bypass**

``Image.open()`` detects format by magic bytes, not file extension or MIME
type. An attacker can name a file ``safe.png`` while its content is TIFF, JPEG
2000, or EPS, causing a different — potentially more dangerous — parser to run.

*Mitigations:* validate MIME type and magic bytes independently before calling
``Image.open()``; pass the ``format`` parameter explicitly; maintain an
allowlist of accepted formats.

**S-2 — Plugin registry spoofing**

Pillow's format registry is a global mutable dictionary. A malicious package
installed in the same environment could register a replacement parser for a
well-known format.

*Mitigations:* use isolated virtual environments with pinned, hash-verified
dependencies; audit ``Image.registered_extensions()`` at startup.

Tampering
^^^^^^^^^

**T-1 — Malicious metadata propagation**

Pillow preserves EXIF, XMP, IPTC, ICC profiles, and comments when
round-tripping images. Applications that store or render metadata without
sanitisation are vulnerable to second-order injection (SQLi, XSS, command
injection).

*Mitigations:* treat all values from ``image.info``, ``image._getexif()``, and
``image.text`` as untrusted; sanitise before storing or rendering; strip
metadata when it is not required.

**T-2 — Covert data channel (steganography)**

Pillow does not remove hidden data (JPEG comments, PNG text chunks, appended
bytes) when re-saving. An attacker can embed data that survives the
encode-decode cycle invisibly.

*Mitigations:* to guarantee a clean output, load pixel data via
``image.tobytes()`` and rebuild the image from raw bytes before saving.

**T-3 — Supply chain tampering**

Pre-compiled wheels bundle libjpeg-turbo, libpng, libtiff, libwebp, openjpeg,
freetype, and littlecms. A compromised PyPI release or build pipeline could
ship malicious binaries.

*Mitigations:* pin with hash verification (``pip install --require-hashes``);
monitor `Pillow security advisories
<https://github.com/python-pillow/Pillow/security/advisories>`_; use
Dependabot or OSV-Scanner for bundled C library CVEs.

Repudiation
^^^^^^^^^^^

**R-1 — No structured audit trail**

Pillow does not emit structured audit logs of files opened, formats detected,
or operations performed, making forensic investigation harder after an
incident.

*Mitigations:* applications should log the filename/hash, detected format, and
dimensions of every image processed; log and alert on
``Image.DecompressionBombWarning`` and ``PIL.UnidentifiedImageError``.

Information disclosure
^^^^^^^^^^^^^^^^^^^^^^

**I-1 — Metadata in saved images**

GPS coordinates, author names, software version strings, and ICC profiles can
be inadvertently included in output images served publicly.

*Mitigations:* explicitly strip EXIF and XMP on save (set ``exif=b""``,
``icc_profile=None``, omit ``pnginfo``); verify output with ``exiftool`` in CI.

**I-2 — Temporary file exposure**

Several code paths write pixel data to temporary files via
``tempfile.mkstemp()``. Exception paths can leave these files behind on shared
filesystems.

*Mitigations:* files are created with mode ``0o600``; mount ``/tmp`` as a
per-container ``tmpfs``; ensure ``try/finally`` cleanup is in place.

Denial of service
^^^^^^^^^^^^^^^^^

**D-1 — Decompression bomb**

A small compressed image can expand to gigabytes in memory.
:py:data:`PIL.Image.MAX_IMAGE_PIXELS` raises
``Image.DecompressionBombError`` at 2× the limit and
``Image.DecompressionBombWarning`` at 1×. PNG text chunks are
separately capped by ``PngImagePlugin.MAX_TEXT_CHUNK`` and
``MAX_TEXT_MEMORY``. Check the values in your installed Pillow version at
runtime or in the reference/source for the current defaults.

*Mitigations:* **never** set ``Image.MAX_IMAGE_PIXELS = None`` in production;
treat ``Image.DecompressionBombWarning`` as an error; set OS/container memory limits
per worker.

**D-2 — CPU exhaustion**

Large-but-legal images (within ``MAX_IMAGE_PIXELS``) can still saturate CPU
through high-quality resampling, convolution filters, or complex draw
operations.

*Mitigations:* apply per-request CPU time limits; set a practical dimension
ceiling below ``MAX_IMAGE_PIXELS``; rate-limit processing requests.

**D-3 — Algorithmic complexity in parsers**

Formats such as TIFF (nested IFD chains), animated GIF/WebP (many frames), and
PNG (many text chunks) can exhaust CPU or memory before pixel data is decoded.

*Mitigations:* restrict accepted formats to the minimum required; enforce a
file-size limit before passing data to Pillow; use per-request timeouts.

Elevation of privilege
^^^^^^^^^^^^^^^^^^^^^^

**E-1 — C extension memory corruption (RCE)**

Pillow's ~87 C source files and its bundled C libraries process
attacker-controlled bytes. Historical CVEs include buffer overflows, integer
overflows, and use-after-free vulnerabilities that allow arbitrary code
execution.

*Mitigations:* keep Pillow and all C libraries up to date; compile with
hardening flags (ASLR, stack canaries, PIE, ``_FORTIFY_SOURCE=2``); run image
processing in a sandboxed subprocess (seccomp-bpf, AppArmor, or a restricted
container).

**E-2 — Ghostscript exploitation via EPS (RCE)**

Opening an EPS file invokes the system Ghostscript binary (``gs``) via
``subprocess``. Ghostscript has a long history of sandbox-escape CVEs
permitting arbitrary code execution from malicious PostScript.

*Mitigations:* **block EPS files** at the application input layer before
passing files to Pillow; if EPS must be supported, run Ghostscript in a fully
isolated sandbox with no network and no sensitive mounts. Pillow does not
provide a stable public API for unregistering individual format plugins, so do
not rely on mutating internal registries such as ``Image.OPEN`` as a security
control.
**E-3 — ``ImageMath.unsafe_eval()`` code injection**

:py:meth:`~PIL.ImageMath.unsafe_eval` calls Python's built-in ``eval()`` with
only a minimal ``__builtins__`` restriction, which can be bypassed via
introspection. Any user-controlled string passed to this function results in
arbitrary code execution.

*Mitigations:* **never** pass user-controlled strings to
``ImageMath.unsafe_eval()``; use :py:meth:`~PIL.ImageMath.lambda_eval` instead,
which accepts a Python callable and never calls ``eval``.

**E-4 — Font path traversal via ``ImageFont``**

``ImageFont.truetype(font, size)`` passes the filename to the FreeType C
library. If font paths are constructed from user input without
canonicalisation, an attacker may supply a path like
``../../../../etc/passwd``.

*Mitigations:* never construct font paths from user input; if font selection
must be user-driven, resolve names against an explicit allowlist of
pre-validated absolute paths.

.. _security-recommendations:

Recommendations
---------------

The following mitigations are listed in priority order.

1. **Sandbox image processing** — run Pillow workers in a seccomp/AppArmor
restricted subprocess, isolated from the main application process.
2. **Block or sandbox EPS** — reject EPS at the application boundary, or run
Ghostscript in an isolated container.
3. **Never use** ``ImageMath.unsafe_eval()`` **with user input** — migrate all
callers to :py:meth:`~PIL.ImageMath.lambda_eval`.
4. **Keep all dependencies current** — Pillow, libjpeg, libpng, libtiff,
libwebp, openjpeg, freetype, Ghostscript. Subscribe to `Pillow security
advisories <https://github.com/python-pillow/Pillow/security/advisories>`_.
5. **Enforce** ``MAX_IMAGE_PIXELS`` — never set it to ``None``; treat
``Image.DecompressionBombWarning`` as an error.
6. **Allowlist image formats** — unregister plugins your application does not
need.
7. **Strip metadata on output** — never pass through EXIF/XMP/ICC from user
uploads to publicly served images.
8. **Sanitise all metadata** returned by Pillow before using it downstream.
9. **Pin dependencies with hash verification** — use
``pip install --require-hashes`` and lockfiles.
10. **Log and alert** on ``Image.DecompressionBombWarning``,
``Image.DecompressionBombError``, ``PIL.UnidentifiedImageError``,
and all exceptions from ``Image.open()``.

.. _security-reporting:

Reporting a vulnerability
-------------------------

To report sensitive vulnerability information, report it `privately on GitHub
<https://github.com/python-pillow/Pillow/security/advisories/new>`_.

If you cannot use GitHub, use the `Tidelift security contact
<https://tidelift.com/docs/security>`_. Tidelift will coordinate the fix and
disclosure.

**Do not report sensitive vulnerability information in public.**
Loading