Vulnerability

Ghostscript and the PDF preview that overruns memory

Wordfence's test PDF makes Ghostscript write 1,024 bytes into a 256-byte row buffer. The researchers developed the corruption into code execution on a purpose-built WordPress upload site. The vulnerable converter arrives through system packages or container images, beyond an ordinary plugin update.

A hand-drawn document passes through an image press and becomes preview sheets. Red ink spills beyond the last paper compartment onto adjacent pages on pale parchment.
In this article

A website receives a PDF and makes a thumbnail. The visitor sees an attachment cover. The server may already have handed the file to another program with access to files and network connections. A small convenience feature has connected a web upload to a system process's memory.

Wordfence's research published on October 12 followed this route to CVE-2026-39919 in Ghostscript's JPEG 2000 handling. On a purpose-built WordPress test site, the researchers added an anonymous upload route and demonstrated server-side code execution through a PDF. That route belonged to their test application. Stock WordPress does not provide anonymous media uploads.

The repair is easy to look for in the wrong place. Ghostscript usually comes from an operating-system package or container image. Updating WordPress and its plugins can leave that program untouched. The useful questions are whether incoming files reach the preview process and whether the executable it runs contains the fix. The published work explains the memory error clearly. It also shows how much depends on the build. A document that crashes a program still needs build-specific exploitation work to take over a particular installation.

1 Processing begins after the upload

Ghostscript interprets PostScript and PDF and can turn pages into images. To generate a PDF attachment preview, WordPress may call ImageMagick through PHP's Imagick extension, after which ImageMagick launches Ghostscript. Application code can depend on it without naming gs directly.

Several conditions must line up. The file enters attachment processing, the PDF branch needs a preview, the chosen image editor is Imagick, ImageMagick's policy permits PDF reads, and the Ghostscript build contains the vulnerable OpenJPEG decoding path. An endpoint that only stores attachments has a different exposure from one that automatically converts them. Site Health reporting that Imagick recognizes PDF answers a format-recognition question. It still leaves the conversion policy and installed program to check.

WordPress 7.1's wp_generate_attachment_metadata() in image.php lines 669–725 identifies the requested image sizes first. It calls wp_get_image_editor() only when merged_sizes is nonempty, then saves a JPEG preview and generates other sizes. The researchers supplied the anonymous upload form that brought an untrusted visitor's document into that core feature. A plugin, form, business API or existing account determines who can deliver a file to the converter. Installing WordPress alone does not establish an anonymous route.

The converter runs under the process identity assigned by the server. Its readable resources may include site configuration, database credentials and mounted directories. It may also reach internal services. Code execution inherits that scope. A separate conversion service with no site secrets mounted and only necessary network access reduces the reachable consequences. Isolation limits impact. A permanent repair still replaces the faulty image-handling code.

2 A 256-byte row receives 1,024 bytes

JPEG 2000 calls separate sets of image information components. A grayscale component describes brightness, while an alpha component describes transparency. Components can have different resolutions, and each sample can occupy fewer than eight bits. With two-bit values, four samples fit into one byte. These are valid features of the format.

In pinned sjpx_openjpeg.c lines 425–444, decode_image() compares the components' horizontal and vertical sampling intervals, dx and dy. A difference sets state->samescale to false, requiring output processing to locate samples using their individual intervals. The one-byte difference in Wordfence's control changes the alpha component's resolution and the selected code path. The equally scaled sub-byte branch already accumulates bits through bit_pos and emits a byte when enough bits are ready. The faulty branch is the later one that needs resampling.

process_one_trunk() lines 570–581 calculate row_size as (width × out_numcomps × bpp + 7) >> 3 and allocate row_data with gs_alloc_byte_array(). Wordfence's minimal test image contains 1,024 two-bit samples per row, requiring 1024 × 2 ÷ 8 = 256 bytes when packed. The capacity calculation is correct. The filling loop brings in a different unit.

The mistake sits in two nested loops in process_one_trunk(). In the old lines 688–703 shown in Artifex's patch, the outer i runs from zero to the image width and emits a byte each time. The inner loop gathers ppbyte1 = 8 / state->bpp samples, four at two-bit precision, without advancing i. It therefore takes the same position four times and packs those values. The outer loop still runs 1,024 times and writes 1,024 bytes, exceeding the row buffer by 768. Its counter confuses sample position with emitted-byte count.

Fixed lines 691–707 retain image width as the sample endpoint and move i++ into the inner loop. Each consumed sample advances the position. Four samples produce one byte, so 1,024 samples yield 256 bytes. A partial final group still completes its shifts, while i < state->width prevents an extra sample read.

// The two old counter positions, excerpted from the pinned patch
for (i = 0; i < state->width; i++)
    for (b = 0; b < ppbyte1; b++)

// The fixed counter positions
for (i = 0; i < state->width; )
    for (b = 0; b < ppbyte1; b++, i++)
// Reading also checks i < state->width

The repair also handles sample precision. Lines 509–514 in decode_image() preserve the existing expansion from 12 to 16 bits, then accept only internal precisions of 1, 2, 4, 8 and 16. The patch description identifies a ten-bit regression file, where the old loop read no data but the corrected counting would read too much. The new precision check rejects unsupported inputs before the output loop. It and the counter change have distinct jobs.

Four two-bit samples fit in one byte. Only the first blue segment represents the row's 256-byte allocation. Three reference segments show the old loop's 768 excess bytes.
Swipe sideways, or open the original.
Figure 1. Only the first blue segment is the row's 256-byte allocation. The three segments beyond the dotted boundary provide a quantity comparison and are not allocated to this row. The old loop writes the corresponding 768 excess bytes. The grid shows proportions, not an actual heap layout. Open original.

Writing beyond the buffer damages adjacent memory. Wordfence followed that corruption into Ghostscript's own allocation bookkeeping, including addresses consulted later while cleaning up objects. The image author can influence the overflowing data, creating an opportunity for code execution. Turning it into a working takeover still depends on the structure layout, usable addresses and executable build. An overflow of 768 bytes alone cannot establish an exploitation success rate for a particular server.

The dangerous public test PDF is only 1,029 bytes long. It differs from a harmless control by one byte describing the alpha component's resolution. Both can be valid PDFs containing a valid JPEG 2000 image. Extension, MIME-type and ordinary structural checks can identify a PDF. They cannot replace the converter's correct handling of this combination of image parameters.

3 A crash and a takeover are separate results

All ten Linux package environments Wordfence tested on September 15 contained the error. That is a dated historical sample, not the present patch status of ten systems. The subsequent study explored a different question. Although the root cause existed in several packages, the two developed exploit documents did not automatically accommodate another executable's layout.

The researchers applied the two documents to ten other package environments. Across 10,240 cross-build attempts, none achieved code execution. This bounds those documents' direct portability. Another study kept the Ghostscript executable unchanged while varying fonts, language settings, font configuration and one core system library. The matching exploit succeeded in all four setups, though some attempts still failed. The surroundings changed while the document-processing binary stayed the same. That is why fleets built from a common base image deserve particular attention.

Update converters reachable by untrusted documents first, and preserve the actual executable and package identity during investigation. After a conversion failure, check whether the corresponding Ghostscript process crashed, then look for unexpected child processes and outbound connections. A crash records a processing failure. The other records help establish whether additional execution or connections followed it. An upload-success response can conceal a failed background conversion. Suspicious historical conversions or subsequent execution warrant preserving files, timestamps, process ancestry and network records, then examining and handling credentials according to what the converter could access.

Wordfence's public study also explains how a failure can lead to another processing attempt. The WordPress Imagick PDF loading flow it shows first tries one crop setting and retries with another after an exception. The researchers measured one Ghostscript process for a harmless PDF, two for a crashing PDF and six for three attachments. Those counts belong to their test environment. Limiting attachments per submission reduces opportunities without changing the unsafe memory write.

-dSAFER restricts what documents can do through the interpreter's supported operations. This error occurs while C code manipulates memory. Wordfence reached the flaw with the option enabled. Retaining the sandbox remains useful. Repairing this overflow still requires updating the program. In its October 12 publication, Wordfence said it had found no public evidence of active exploitation of this vulnerability.

4 Update the program that actually handles documents

Wordfence identifies the affected upstream range as 9.21–10.07.1 when the relevant OpenJPEG decoder is present. VulnCheck, which published the vulnerability record, describes before 10.08.0. These scopes have different granularity. Artifex's final 10.08.0 release on September 8 contains the fix and remains the latest final release on its official page on October 14. The unpatched 10.08.0rc1 in the study displayed the same base version, so a short version string cannot complete the installation check.

Distributions backport repairs into older versions. Check the complete package identifier against the advisory for the installed distribution. The upstream number need not reach 10.08.0. On October 14, the Debian tracker lists trixie security's 10.05.1~dfsg-1+deb13u3 as fixed, with u2 the historical first-fixed package. Bookworm's 10.0.0~dfsg-11+deb12u8 remains marked vulnerable. The distribution branch tells more than a bare version number. These read-only queries retrieve the full package identity for comparison with the relevant branch's advisory.

# Debian / Ubuntu
dpkg-query -W -f='${Package} ${Version}\n' ghostscript

# Fedora / Red Hat family
rpm -q ghostscript

Ubuntu, Red Hat and SUSE likewise maintain their own package tables. Use the update route appropriate to the actual system. Direct upstream installations need a final fixed artifact and confirmation that the caller uses its path. Rebuild and redeploy containers from an updated base. Pulling that base alone cannot replace the old layers and processes in a running container. Check every replica using a shared image, including those outside the test environment. Finally, send a harmless document through the normal upload and preview workflow and confirm that it reaches the new program. This does not require sending a dangerous PDF to production.

If updating has to wait, keep untrusted PDFs away from the converter. Suspend external PDF submissions or disable PDF reads in ImageMagick's security policy. Suspending submissions interrupts document delivery. Disabling PDF reads stops the affected previews. Choose a measure according to the workflow and verify it there. Hiding an upload button leaves API and queue routes available. Rate limits only reduce attempts. Keep conversion restricted after an upgrade failure, and do not restore thumbnails by reverting to the vulnerable program.