Threat database
455 threats
Most recently published first
| Severity | CVSS | Signals | Threat | Source | Published | Score and star |
|---|---|---|---|---|---|---|
| Medium | 5.3 | EPSS 0.2% | CVE-2026-105814Tapo C210 v3.6 · Improper Authentication Medium5.3EPSS 0.2%CVE Database V5CVE-2026-105814 TP-Link Tapo C210 V3.60 contains an authentication bypass vulnerability in the DS JSON API. A crafted JSON request containing both a protected operation and public onboarding-related fields may cause module-level authorization checks to be applied inconsistently, allowing protected camera operations, including pan-tilt-zoom control, to be executed without a valid authenticated session. Successful exploitation may allow an unauthenticated attacker on the adjacent network to perform protected camera operations, including pan-tilt-zoom (PTZ) control, without a valid authenticated session. This could result in unauthorized manipulation of camera functionality. | CVE Database V5 | ||
| –Not rated | – | Bleeping Computer Some downloads of the Rank Math PRO WordPress plugin from the official vendor website were compromised to include malware that planted hidden backdoors in a supply-chain attack. This incident was reported recently and involves the distribution of malicious plugin versions that could allow unauthorized access to affected websites. | Bleeping Computer | |||
| Critical | 9.2 | EPSS 0.3% | CVE-2026-112403Pillow · Heap-based Buffer Overflow Critical9.2EPSS 0.3%CVE Database V5CVE-2026-112403 Pillow is a Python imaging library. Prior to 13.0.0, Pillow's JPEG decoder can write a full-width scanline into a buffer sized for a requested draft scale. ImagingJpegDecode does not verify libjpeg's actual output width and component count after scaling is requested, and an 8-bit lossless SOF3 JPEG decoded by a build linked to libjpeg-turbo 3.x can ignore the requested downscaling geometry. An attacker can supply such a JPEG to an application that uses draft() or thumbnail() to request downscaling. This can corrupt heap memory, crash the process, or potentially allow code execution. Older libjpeg-turbo 2.x and IJG builds reject the SOF3 input. This issue is fixed in version 13.0.0. | CVE Database V5 | ||
| High | 8.7 | EPSS 0.4% | CVE-2026-112402Pillow · Use After Free High8.7EPSS 0.4%CVE Database V5CVE-2026-112402 ``` ============================================================================ Double free in ImagingScaleAffine on calloc failure via Image.transform Location: src/libImaging/Geometry.c:866 in ImagingScaleAffine() Commit: a707c6b21c313880d0771e54965d142dd42358b6 ============================================================================ ``` There is a double free in Pillow's `Image.transform()` that crashes the process when an oversized transform runs out of memory. Instead of only raising `MemoryError`, Pillow frees the output image's memory once while handling the error and then again when the Python object is cleaned up. Anyone who can choose the output size of `Image.transform()` can trigger it, but only on a worker with a memory limit (a common hardening for image workers) or a 32-bit process. The result is a crash of the worker, plus corruption of its heap that has not been shown to be controllable. The bug is in `ImagingScaleAffine()` in `src/libImaging/Geometry.c` (line 866). When its `calloc()` for the per-column lookup table of `4 × width` bytes fails, it calls `ImagingDelete(imOut)`. When the call comes from `Image.transform()`, `imOut` is the `Imaging` struct owned by the live Python `ImagingCore` output object. `_dealloc()` in `src/_imaging.c` later runs `ImagingDelete()` on the freed struct again, writing its mutex and refcount in freed memory and freeing the pixel blocks a second time. The path is reached with `Transform.EXTENT`, or `Transform.AFFINE` with a pure scale/translate matrix, under the default NEAREST resampling. A very wide, 1-row output size makes the lookup table the allocation that fails while the image itself still fits. On 64-bit Linux with default overcommit, that requires an address-space limit such as `ulimit -v`, `uwsgi --limit-as` or `systemd LimitAS=`. `resize()`, `thumbnail()` and `rotate()` are not affected. The attached reproducer runs on a stock build with a 1 GB limit: `transform((300000000, 1), EXTENT)` raises and catches `MemoryError`, then the process dies with SIGSEGV in `free()` called from `_dealloc()`. A memory-checking build reports the same path as a heap use-after-free. The faulty line was inherited from PIL 1.1.7 in the original fork commit, so every Pillow release since 1.0 is affected. A patch is attached. ## Details `ImagingScaleAffine()` frees an image it does not own. On the one error path where its `calloc()` for the per-column lookup table fails, it calls `ImagingDelete(imOut)`. When reached through `Image.transform()`, `imOut` is the `Imaging` struct held by a live Python `ImagingCore` object; that object's `_dealloc()` later calls `ImagingDelete()` on the already-freed struct and frees its pixel blocks a second time. Shortest demonstration on the stock build (the address-space limit stands in for `ulimit -v` / `uwsgi --limit-as` / `systemd LimitAS=` on a worker): ```python import resource, gc from PIL import Image resource.setrlimit(resource.RLIMIT_AS, (1_000_000_000,) * 2) src = Image.new("L", (8, 8)) try: src.transform((300_000_000, 1), Image.Transform.EXTENT, (0, 0, 8, 8)) except MemoryError: pass # expected, and the process should carry on gc.collect() # instead: SIGSEGV in _dealloc -> ImagingDelete -> free() ``` The 300 MB output image allocates; `calloc(300000000, 4)` (1.2 GB) does not. `MemoryError` is raised as intended, but the interpreter then dies with SIGSEGV, gdb showing `free()` ← `memory_return_block`/`ImagingDestroyArray` (`src/libImaging/Storage.c:396,408`) ← `ImagingDelete` (`Storage.c:271`) ← `_dealloc` (`src/_imaging.c:188`). An AddressSanitizer build of the same sources (with `max_allocation_size_mb` used to make the `calloc` fail instead of an rlimit) reports `heap-use-after-free` in `ImagingDelete src/libImaging/Storage.c:257` called from `_dealloc src/_imaging.c:188`, on a 128-byte region "freed by" `ImagingDelete` ← `ImagingScaleAffine src/libImaging/Geometry.c:866` ← `ImagingTransformAffine Geometry.c:1043` ← `ImagingTransform Geometry.c:1135` ← `_transform src/_imaging.c:2153`, "previously allocated by" `ImagingNewPrologueSubtype Storage.c:53` ← … ← `_new src/_imaging.c:756`. ### Code path `Image.transform()` creates the output image as an ordinary Python-owned image and then transforms *into* it: ```python # src/PIL/Image.py:3008…3021 im = new(self.mode, size, fillcolor) … im.__transformer( (0, 0, *size), self, method, data, resample, fillcolor is None ) ``` `__transformer()` rewrites `Transform.EXTENT` into an axis-aligned affine matrix (`src/PIL/Image.py:3040-3046`: `data = (xs, 0, x0, 0, ys, y0)`, `method = Transform.AFFINE`) and calls `self.im.transform(box, image.im, method, data, resample, fill)` (`Image.py:3117`). `resample` defaults to `Resampling.NEAREST` (`Image.py:2936`). In C, `_transform()` passes its *own* image as the destination: ```c /* src/_imaging.c:2153 */ imOut = ImagingTra | CVE Database V5 | ||
| High | 8.7 | EPSS 0.4% | CVE-2026-112397Pillow · Out-of-bounds Write High8.7EPSS 0.4%CVE Database V5CVE-2026-112397 ### Summary A PPM or PGM file can declare a width of 0, which Pillow accepts, producing an image of size `(0, N)`. Passing that image to `ImageMorph.MorphOp.apply()` makes the native morphology code index a row buffer at `outrow[width - 1]`, which is `outrow[-1]` when the width is 0. The process crashes. ### Details The PPM parser reads width and height with no lower-bound check: - `src/PIL/PpmImagePlugin.py:119` The native `apply()` writes the row edges with no guard that the width is positive: - `src/_imagingmorph.c:81` sets `outrow[0] = outrow[width - 1] = 0;` `match()` and `get_on_pixels()` use the same edge-indexing pattern. ### PoC ```python from PIL import Image, ImageMorph # A width-0 image (also producible from a crafted PPM header). apply() indexes # outrow[width - 1] == outrow[-1] in native code. im = Image.new("L", (0, 5)) ImageMorph.MorphOp(op_name="dilation4").apply(im) ``` Run it: ``` $ python 03_poc.py Segmentation fault (core dumped) ``` Observed on a source build of commit `3e2ab96`. Under AddressSanitizer: ``` ==ERROR: AddressSanitizer: SEGV on unknown address (pc ... in apply) #0 apply src/_imagingmorph.c:81 SUMMARY: AddressSanitizer: SEGV src/_imagingmorph.c:81 in apply ``` With width 0 the `outrow[width - 1]` write lands on an unmapped page, so it surfaces as a SEGV pinned to the edge-write line rather than a redzone overflow. ### Impact Out-of-bounds write in a C extension. Affects any application that runs a morphology operation on an image opened from untrusted input. Result is a crash and possible memory corruption. | CVE Database V5 | ||
| High | 8.2 | EPSS 0.3% | CVE-2026-112392Pillow · NULL Pointer Dereference High8.2EPSS 0.3%CVE Database V5CVE-2026-112392 ``` ============================================================================ NULL dereference in ImagingExpand via rank filters on zero-size images Location: src/libImaging/Filter.c:79 in ImagingExpand() Commit: a707c6b21c313880d0771e54965d142dd42358b6 ============================================================================ ``` Applying Pillow's median, minimum, maximum or rank filter to an image with zero width or zero height crashes the whole Python process instead of raising an error. Empty images like this are easy to produce with ordinary calls, for example a crop box whose left and right edges (or top and bottom edges) are equal, or the second page of a crafted multi-page image file. An attacker who can supply crop coordinates or an uploaded file to an application that runs one of these filters can kill its worker process. The effect is a crash only, with no memory disclosure or corruption. The fault is in `ImagingExpand()` in `src/libImaging/Filter.c`. This is the edge-padding step that `RankFilter.filter()` always runs through the core `expand()` method, so `MedianFilter`, `MinFilter` and `MaxFilter` go through it too. It fills the margin by copying the input's edge pixels, reading `imIn->image[y][0]` and rows `0` / `ysize-1`, and never checks that the input has any pixels. For an empty image, `ImagingAllocateArray` leaves the line pointers NULL, so the first read dereferences NULL and the process dies with SIGSEGV, in every mode. Zero-size images reach this point through public API alone: - `Image.crop()`, `ImageOps.crop()` and `Image.new()`; - a multi-page TIFF opened by path whose second IFD declares `ImageLength = 0`, on which `seek(1)` and `load()` succeed. Every other `ImageFilter` returns an empty image for the same inputs. The zero-height case has crashed since the original PIL import, and the zero-width case since Pillow 4.3.0. This is separate from the `margin == 0` fix in #9755: the path is the same, but here the degenerate value is the image size rather than the kernel size. It has the same shape as the zero-dimension `ImagingOffset` crash fixed under GHSA-f87v-9578-p47w. The attached reproducer shows a one-pixel-wide control crop filtering normally, while four empty crops passed through the rank filters are each killed by SIGSEGV. A patch that returns early in `ImagingExpand` for empty input is included. ## Details `ImagingExpand()` (`src/libImaging/Filter.c`), the edge-padding helper that `ImageFilter.RankFilter` runs before the actual rank filter, reads the first pixel of each input line without checking that the input has any pixels. For an image with width 0 or height 0 the line pointers are NULL, so `Image.filter()` with `MedianFilter`, `MinFilter`, `MaxFilter` or `RankFilter` kills the interpreter with SIGSEGV instead of returning or raising: ```python from PIL import Image, ImageFilter im = Image.new("L", (10, 10)).crop((3, 0, 3, 10)) # size (0, 10); crop() accepts left == right im.filter(ImageFilter.MedianFilter(3)) # SIGSEGV ``` Under gdb on the stock build the fault is at `ImagingExpand (imIn=…, margin=1) at src/libImaging/Filter.c:105` (the `EXPAND(UINT8, image8)` expansion), called from `_expand_image` at `src/_imaging.c:1151`, with `imIn->xsize = 0` and `imIn->image[0] = 0x0`. The same happens for every mode the rank filters accept (`L`, `RGB`, `RGBA`, `I`, `F`), and the core `expand(m)` method with `m >= 1` crashes the same way in any mode, including `P`. The same input through `BoxBlur`, `GaussianBlur`, `Kernel`, `ModeFilter` and `UnsharpMask` returns a `(0, 10)` image; a `(1, 10)` crop through `MedianFilter` also returns normally. The unchecked edge reads date from the PIL import (`9a640e315`, 2010, so every Pillow release): the zero-height case has crashed since then, and the zero-width case since `fd907fbdc` ("Fix 0-width and 0-height images other way", Pillow 4.3.0), which stopped backing zero-width images with a dummy block and left their row pointers NULL. It is adjacent to the `margin == 0` divide-by-zero fix in #9755 — same `expand()` → `ImagingExpand()` path, but the unhandled degenerate value here is the image size rather than the kernel size. The path: ```python # src/PIL/ImageFilter.py:111-116 RankFilter.filter (MedianFilter/MinFilter/MaxFilter subclass it) image = image.expand(self.size // 2) return image.rankfilter(self.size, self.rank) ``` `expand(m)` with `m > 0` is `ImagingExpand(self->image, m)` (`src/_imaging.c:1142-1151`). It validates only `margin`, allocates an output of `(xsize+2*margin) × (ysize+2*margin)` — which is non-empty, so the allocation succeeds — and then fills the margin by replicating edge pixels of the input: ```c // src/libImaging/Filter.c:70-101 imOut = ImagingNewDirty(imIn->mode, imIn->xsize + 2 * margin, imIn->ysize + 2 * margin); … #define EXPAND_LINE(type, image, yin, yout) \ { | CVE Database V5 | ||
| Medium | 6.9 | EPSS 0.4% | CVE-2026-112401Pillow · Inefficient Algorithmic Complexity Medium6.9EPSS 0.4%CVE Database V5CVE-2026-112401 ``` ============================================================================ Quadratic GIF comment concatenation allows DoS in Image.open() Location: src/PIL/GifImagePlugin.py:266 in GifImageFile._seek() Commit: a707c6b21c313880d0771e54965d142dd42358b6 ============================================================================ ``` Opening a crafted GIF file with `Image.open()` takes time that grows with the square of the file's size, so anyone who can submit an image to a service that uses Pillow can keep one worker busy at full processor load for a long time with a small file. A 2 MB file takes about 5 seconds to open, a 4 MB file about 35 seconds, and an 8 MB file about 3 minutes. An ordinary GIF of the same size opens in well under a second. The cost is paid inside `Image.open()` itself, with default settings, before the application has seen the image's size or format. The bug is in `GifImageFile._seek()` in `src/PIL/GifImagePlugin.py`, which builds `info["comment"]` by appending to an immutable `bytes` object with no size limit. It appends once per 255-byte sub-block (`comment += block`, line 261) and once per comment extension (`info["comment"] += b"\n" + comment`, line 266). Each append copies everything collected so far. A file made of many tiny comment extensions forces a full re-copy for each one. Because `_open()` ends with `self._seek(0)`, any code that only opens an uploaded file to identify it, read its dimensions, or make a thumbnail pays the full cost. The decompression-bomb check does not help, since the image is 1×1. Memory use stays linear, so the effect is processor exhaustion and latency, bounded by whatever upload size the service allows (about 1 second at 1 MB). This is the same kind of issue as the EPS open-phase slowdown fixed as CVE-2021-28677, here in the GIF comment handling. The quadratic pattern was introduced in commit b2b737b68b06 ("Allow arbitrary number of comment extension subblocks", November 2018) and has shipped since Pillow 5.4.0. The attached script builds 2 MB and 4 MB test files and compares each against a byte-identical control whose extensions are labelled as plain text instead of comment. The 4 MB comment file took about 34 seconds to open, against 0.26 seconds for its control. A patch that collects the pieces in a list and joins them once is also attached. ## Details `GifImageFile._seek()` collects GIF comment extensions into `info["comment"]` using `bytes` concatenation, once per 255-byte sub-block and once per comment extension, with no limit on the total. `bytes` is immutable, so every `+=` allocates a new object and copies everything accumulated so far; the work done by `Image.open()` is therefore quadratic in the amount of comment data in frame 0. A 1×1 GIF89a whose body is nothing but the 5-byte comment extension `21 FE 01 41 00` repeated makes this directly observable. `Image.open(BytesIO(data))` on such a file, stock build at this commit, 2-CPU machine: | file size | comment extensions (`21 FE …`) | same bytes labelled plain-text extension (`21 01 …`) | |---|---|---| | 1 MB | 1.2 s | 0.09 s | | 2 MB | 4.9 s | 0.13 s | | 4 MB | 35 s | 0.26 s | | 8 MB | 183 s | — | The control column is the identical byte stream with the extension label changed from `0xFE` to `0x01`; it goes through the same loop and the same `self.data()` reads but is discarded instead of concatenated, and scales linearly. No `load()` is needed and no warning or exception is raised; `im.info["comment"]` simply ends up holding the multi-megabyte blob. The code, `src/PIL/GifImagePlugin.py:229-281` (trimmed): ```python elif s == b"!": s = self.fp.read(1) block = self.data() … elif s[0] == 254: # # comment extension # comment = b"" # Read this comment block while block: comment += block # :261 one copy per sub-block block = self.data() if "comment" in info: # If multiple comment blocks in frame, separate with \n info["comment"] += b"\n" + comment # :266 one copy per extension else: info["comment"] = comment s = b"" continue … while self.data(): # every other extension: read and drop pass ``` `self.data()` (`:97-102`) reads one length byte and then that many bytes, so each call is cheap and bounded; the cost is entirely in the two `+=` lines. For a single comment extension of N bytes delivered as N/255 sub-blocks, line 261 copies about N²/510 bytes in total (4 MB → 2.3 s, 8 MB → 9.5 s, 16 MB → 38 s measured). Splitting the same data into many small comment extensions is worse, becaus | CVE Database V5 | ||
| Low | 2.1 | EPSS 0.3% | CVE-2026-112358Lespass · Server-Side Request Forgery Low2.1EPSS 0.3%CVE Database V5CVE-2026-112358 A weakness has been identified in TiBillet Lespass up to 10e87909c5d526dffa6d424c104fc12860740537. This vulnerability affects the function get_img_from_url of the file ApiBillet/serializers.py of the component APIv1. Executing a manipulation of the argument img_url/image_url can lead to server-side request forgery. The attack can be launched remotely. A high complexity level is associated with this attack. It is stated that the exploitability is difficult. The vendor confirms: "[T]he fix (destination IP validation, redirect re-checks, timeout and size limit on remote image download) is already planned in our v2 API, which replaces the affected v1 endpoints. The v1 API, including the three affected serializers, will be removed in our next major release." | CVE Database V5 | ||
| High | 8.7 | EPSS 0.3% | CVE-2026-112400Pillow · Uncontrolled Resource Consumption High8.7EPSS 0.3%CVE Database V5CVE-2026-112400 ``` ============================================================================ GIF seek keeps rejected size after DecompressionBombError, allowing DoS Location: src/PIL/GifImagePlugin.py:293 in GifImageFile._seek() Commit: a707c6b21c313880d0771e54965d142dd42358b6 ============================================================================ ``` A GIF of about 60 bytes can make Pillow allocate gigabytes of memory. This affects applications that catch the "image too large" error raised while stepping to a later animation frame and then keep using the same image object. Pillow correctly refuses the oversized frame, but it stores the refused dimensions on the image object anyway, and later operations size their memory from them. An attacker who can upload a GIF to such a service can exhaust its memory: the process either fails with an out-of-memory error after committing gigabytes, or is killed by the operating system. There is no memory corruption. The bug is in `GifImageFile._seek()` (`src/PIL/GifImagePlugin.py:292-294`). When a frame's extent enlarges the image, the code assigns the new size to `self._size` and only then calls `Image._decompression_bomb_check()`. Nothing restores the old size when `DecompressionBombError` is raised, because `seek()` only rolls back on `EOFError`. After the caught error, `im.size` reports the rejected size while the pixel buffer `im.im` is still the 1×1 first frame. Because the extent is a 16-bit offset plus a 16-bit size, that rejected size can be as large as 131070×131070. In the attached reproducer, the rejected size is 40000×40000. A retried `seek(1)` followed by `load()` then reaches `load_prepare()`, which calls `Image.core.fill()` with that size to decode a single pixel. Peak memory rises from 17 MiB to 1700 MiB before a 3 GiB address-space cap stops the next allocation. `Image.new("RGB", im.size, ...)` and `rotate(..., expand=True)` also allocate from the stale size. The default paths are unaffected: plain `Image.open()` plus `load()`, `ImageSequence.Iterator`, and `n_frames`/`is_animated`. The ordering was introduced in c9f1b35e9810 ("Added GIF decompression bomb check", 2022-06-30, first released in 9.2.0), the check added for CVE-2022-45198. The two dispose-extent checks later in the same function already validate before using the size, and the attached patch makes this check follow the same order. ## Details `GifImageFile._seek()` lets a later frame enlarge the image, and performs the decompression-bomb check for the new size only *after* storing it on the object: ```python # src/PIL/GifImagePlugin.py:289-294 (GifImageFile._seek) x0, y0 = i16(s, 0), i16(s, 2) x1, y1 = x0 + i16(s, 4), y0 + i16(s, 6) if (x1 > self.size[0] or y1 > self.size[1]) and update_image: self._size = max(x1, self.size[0]), max(y1, self.size[1]) # state mutated Image._decompression_bomb_check(self._size) # ...then validated ``` `_decompression_bomb_check()` (`src/PIL/Image.py:3586`) raises `DecompressionBombError` for anything above `2 * MAX_IMAGE_PIXELS` (178 956 970 px by default), but by then `self._size` already holds the rejected value and nothing restores it: `seek()` (`GifImagePlugin.py:166-179`) only rolls back on `EOFError`, and `_seek(0)` only ever grows `_size`. The object that the application still holds therefore reports `im.size == (40000, 40000)` while `im.im` is the 1×1 buffer of frame 0. Compare the two dispose-extent checks a few lines further down in the same function (`GifImagePlugin.py:383` and `:407`), which call `_decompression_bomb_check(dispose_size)` *before* the corresponding `Image.core.fill()`, and `TiffImagePlugin.load_prepare()` (`src/PIL/TiffImagePlugin.py:1319-1321`), which checks `_tile_size` before allocating from it. With this 60-byte GIF (hex) ``` 47494638396101000100800000000000ffffff GIF89a, 1x1 screen, 2-entry global palette 2c000000000100010000 02 024401 00 frame 0: 1x1 @ (0,0), LZW data 2c00000000409c409c00 frame 1 descriptor: 40000x40000 @ (0,0) 2c000000000100010000 02 024401 00 another 1x1 frame 3b trailer ``` `Image.open()` succeeds (`im.size == (1, 1)`), `im.seek(1)` raises `DecompressionBombError: Image size (1600000000 pixels) exceeds limit of 178956970 pixels`, and afterwards `im.size == (40000, 40000)`, `im.im.size == (1, 1)`, `im.tell() == 0`. What happens next depends on what the application does with the object, but several ordinary operations take their dimensions from `im.size` rather than from the C buffer: - Retrying `im.seek(1)`. The failed `_seek` left `self.__offset == 0` and the file pointer just past the 9-byte descriptor, so the retry parses whatever follows — in the file above, a valid 1×1 frame — and succeeds. `im.load()` then runs `GifImageFile.load_prepare()`: ```python # src/PIL/GifImagePlugin.py:452-456 elif self.mode in ("RGB", "RGBA"): self._prev_im = self. | CVE Database V5 | ||
| High | 8.7 | EPSS 0.3% | CVE-2026-112399Pillow · Memory Allocation with Excessive Size Value High8.7EPSS 0.3%CVE Database V5CVE-2026-112399 ``` ============================================================================ APNG background disposal allocates full canvas before decompression bomb check Location: src/PIL/PngImagePlugin.py:1000 in PngImageFile._seek() Commit: a707c6b21c313880d0771e54965d142dd42358b6 ============================================================================ ``` A 126-byte animated PNG file makes Pillow's `Image.open()` allocate and fill memory sized by the image dimensions the file claims, before Pillow's built-in check for oversized images runs. A file claiming 22000×22000 pixels costs about 1.9 GB. Anyone who can get an application to open an image they supply, with default settings and no further calls such as `load()`, can cause a multi-gigabyte memory spike per file. If the claimed size exceeds the machine's free memory, the operating system kills the process outright. The bug is in `PngImageFile._seek()` in `src/PIL/PngImagePlugin.py` (around line 1000). For any APNG, `_open()` calls `self._seek(0)`. When the first `fcTL` chunk requests `OP_BACKGROUND` disposal, `_seek()` calls `Image.core.fill(self.mode, self.size)` with the full IHDR size, which allocates and zero-fills width×height×4 bytes. `OP_PREVIOUS` takes the same path, because frame 0 rewrites it to `OP_BACKGROUND`. This all happens inside the plugin factory, so `Image.open()`'s `_decompression_bomb_check(im.size)` only runs after the memory has been touched. A 1×1 frame rectangle passes the only checks on the way. Below available memory, the allocation is freed when `DecompressionBombError` propagates, so sustained pressure needs repeated or concurrent uploads. A 45000×45000 header gets the process killed by the out-of-memory killer on an 8 GB host. The attached reproducer opens two such files that differ in one byte (`dispose_op` 0 vs 1). Both end in `DecompressionBombError`, but peak resident memory is 17 MB for the first and 1864 MB for the second. The eager fill was introduced in 5e4e0fa6ee67 ("Use disposal settings from previous frame", December 2020) and first shipped in 8.1.0. Two candidate patches with a regression test are attached. ## Details For an APNG whose first `fcTL` chunk requests `OP_BACKGROUND` disposal, `PngImageFile._open()` creates and zero-fills a core image of the full IHDR-declared size. This happens inside the plugin factory, i.e. before `Image.open()` gets to `_decompression_bomb_check(im.size)`, so the check that is meant to reject oversized headers without allocating anything runs only after the allocation has been made and touched. The following 126-byte file (all chunk lengths and CRCs valid) makes `Image.open(io.BytesIO(data))`, with default `MAX_IMAGE_PIXELS` and no `load()`, reach 1.86 GB resident (`ru_maxrss`) and then raise `DecompressionBombError`. The byte-identical file with `dispose_op=0` raises the same error at 17 MB. ``` IHDR width=22000 height=22000 bit_depth=8 colour_type=6 (RGBA) acTL num_frames=2 num_plays=0 fcTL seq=0 w=1 h=1 x=0 y=0 delay=1/10 dispose_op=1 (OP_BACKGROUND) blend_op=0 IDAT zlib(00 00 00 00 00) # one 1x1 RGBA scanline IEND ``` With `width=height=45000` (8.1 GB canvas) the same call attempts the full allocation: under a 4 GiB `RLIMIT_AS` it fails with `MemoryError` from the allocator instead of `DecompressionBombError`; without a cap, on an 8 GB host with no swap, the process is OOM-killed. ### Code path `Image.open()` runs the bomb check only after the plugin's constructor (and therefore `_open()`) has returned: ```python # src/PIL/Image.py:3699-3701 im = factory(fp, filename) _decompression_bomb_check(im.size) return im ``` For a plain PNG that ordering is fine: `PngImageFile._open()` only parses chunks up to the first `IDAT` and records `self._size` from `IHDR`; nothing is allocated from it. For an APNG (`acTL` seen, so `im_n_frames` is set) `_open()` additionally positions on frame 0: ```python # src/PIL/PngImagePlugin.py:845-853 if self.png.im_n_frames is not None: … self._seek(0) ``` `_seek(0)` copies `disposal` and `bbox` from the `fcTL` chunk into `self.dispose_op` / `self.dispose_extent` and then prepares the disposal image for the *next* frame eagerly, using the full image size: ```python # src/PIL/PngImagePlugin.py:991-1001 if self._prev_im is None and self.dispose_op == Disposal.OP_PREVIOUS: self.dispose_op = Disposal.OP_BACKGROUND self.dispose = None if self.dispose_op == Disposal.OP_PREVIOUS: … elif self.dispose_op == Disposal.OP_BACKGROUND: self.dispose = Image.core.fill(self.mode, self.size) self.dispose = self._crop(self.dispose, self.dispose_extent) ``` `Image.core.fill` is `_fill()` in `src/_imaging.c:711`, which does `ImagingNewDirty(mode, xsize, ysize)` followed by `ImagingFill()`; `ImagingFill` (`src/libImaging/Fill.c:23`) writes every row, so the whole ` | CVE Database V5 | ||
| Critical | 9.2 | EPSS 0.3% | CVE-2026-112398Pillow · Heap-based Buffer Overflow Critical9.2EPSS 0.3%CVE Database V5CVE-2026-112398 Pillow is a Python imaging library. Prior to 13.0.0, Pillow's FlashPix JPEG-tile decoder can write decoded scanlines beyond the allocated tile buffer. FlashPix (FPX) JPEG decoding selects raw mode LA with two bytes per pixel, while an unrecognized JPEG mode can be decoded as JCS_UNKNOWN with four components, causing ImagingJpegDecode to write four-component scanlines into the two-byte tile buffer. An attacker can supply a crafted OLE2 FlashPix file to an application with the optional olefile dependency installed that opens or thumbnails untrusted input. This can corrupt heap memory and may allow arbitrary code execution. This issue is fixed in version 13.0.0. | CVE Database V5 | ||
| Medium | 5.9 | EPSS 0.5% | CVE-2026-112396Pillow · Use After Free Medium5.9EPSS 0.5%CVE Database V5CVE-2026-112396 ### Summary Saving an image as TIFF to an in-memory buffer can trigger a heap use-after-free if libtiff rejects a tag at encode time. When `TIFFWriteScanline` fails, the encoder frees its output buffer but leaves the libtiff handle open. Later, when the encoder object is torn down, `TIFFClose` flushes buffered bytes through the write callback and writes into the freed buffer. An application that re-encodes attacker-influenced images to TIFF in memory can be crashed and is a candidate for remote code execution. ### Details The failure path in `ImagingLibTiffEncode` frees `clientstate->data` for the in-memory case but does not close the TIFF or null `clientstate->tiff` (the file-descriptor branch just above it does set `tiff = NULL`): - `src/libImaging/TiffDecode.c:1041-1046` Teardown then calls `ImagingLibTiffEncodeCleanup`, which runs `TIFFClose(tiff)` because `tiff` is still set: - `src/encode.c:99-101` - `src/libImaging/TiffDecode.c:955` `TIFFClose` flushes through `_tiffWriteProc`, which reallocs and memcpys into the freed `state->data`: - `src/libImaging/TiffDecode.c:103-115` The Predictor tag is a convenient trigger: the floating-point predictor (value 3) is invalid for integer sample data, so libtiff aborts the write. ### PoC ```python import io from PIL import Image # In-memory TIFF re-encode. libtiff rejects Predictor=3 (floating point) on integer # data, so TIFFWriteScanline fails. The encoder frees clientstate->data but leaves the # TIFF open; cleanup's TIFFClose then writes through the callback into the freed buffer. im = Image.new("L", (64, 64)) buf = io.BytesIO() im.save(buf, format="TIFF", compression="tiff_lzw", tiffinfo={317: 3}) # 317 = Predictor ``` Run it: ``` $ python 01_poc.py PredictorSetup: Floating point "Predictor" not supported with 1 data format. OSError: encoder error -2 when writing image file Segmentation fault (core dumped) ``` Observed on a source build of commit `3e2ab96` with libtiff. The process aborts during cleanup after the encoder error. Depending on heap state the same run sometimes reports `corrupted double-linked list` instead of a segfault; both are the same write into freed memory. Under AddressSanitizer (build with `-fsanitize=address`), relevant frames: ``` ==ERROR: AddressSanitizer: heap-use-after-free on address 0x7ef737b48804 WRITE of size 4 at 0x7ef737b48804 thread T0 #1 _tiffWriteProc src/libImaging/TiffDecode.c:115 #3 TIFFFlush libtiff.so.6 #5 TIFFClose libtiff.so.6 #6 ImagingLibTiffEncodeCleanup src/libImaging/TiffDecode.c:955 #7 _dealloc src/encode.c:100 freed by thread T0 here: #1 ImagingLibTiffEncode src/libImaging/TiffDecode.c:1045 #2 _encode src/encode.c:139 previously allocated by thread T0 here: #1 ImagingLibTiffEncodeInit src/libImaging/TiffDecode.c:863 #2 PyImaging_LibTiffEncoderNew src/encode.c:757 SUMMARY: AddressSanitizer: heap-use-after-free src/libImaging/TiffDecode.c:115 in _tiffWriteProc ``` The buffer allocated in `ImagingLibTiffEncodeInit` is freed in `ImagingLibTiffEncode`, then written by `_tiffWriteProc` during the `TIFFClose` flush that runs from the encoder's `_dealloc`. ### Impact Use-after-free in a C extension. Affects any service that converts or thumbnails untrusted images to TIFF in memory (a `BytesIO` or other non-seekable target). Result is a crash and possible memory corruption leading to code execution. | CVE Database V5 | ||
| Medium | 5.3 | EPSS 0.4% | CVE-2026-112302php-archive · Path Traversal Medium5.3EPSS 0.4%CVE Database V5CVE-2026-112302 A weakness has been identified in kiwilan php-archive up to 2.3.02. This affects the function ArchiveZip::extract of the file src/Readers/ArchiveZip.php of the component Extraction. This manipulation causes path traversal. The attack is possible to be carried out remotely. The exploit has been made available to the public and could be used for attacks. The vendor was contacted early about this disclosure but did not respond in any way. | CVE Database V5 | ||
| Medium | 6.5 | EPSS 0.3% | CVE-2026-112395Pillow · Heap-based Buffer Overflow Medium6.5EPSS 0.3%CVE Database V5CVE-2026-112395 ### Summary Pillow's libImaging `_decodeTile` decoder, used to decode tiled TIFF images, allocates a per-tile buffer using `realloc(state->buffer, TIFFTileSize(tiff))`. After each `TIFFReadTile` it then iterates `tile_y` from 0 up to `current_tile_length` and calls the configured row unpacker (here `copy4`) with the source pointer `state->buffer + tile_y * row_byte_size`. When libtiff returns a `tile_bytes_size` smaller than the worst-case `row_byte_size * tile_length`, the inner stride-and-copy loop walks past the realloc'd region and `copy4` performs a 1024-byte `memcpy` that reads up to `(tile_length-1) * row_byte_size + row_byte_size` bytes from a buffer sized only to `tile_bytes_size`. The supplied PoC TIFF triggers a 1024-byte over-read exactly 0 bytes past a 196608-byte allocation. An attacker who can convince a Pillow-using application to decode a crafted TIFF can trigger a process crash (DoS) and may cause adjacent heap memory to be copied into the decoded pixel buffer. ### Details Pillow's TIFF decoder, when libtiff reports the file is tiled, takes the `_decodeTile` path. The function: 1. Asks libtiff for `TIFFTileSize(tiff)` (the bytes-per-tile) and `TIFFTileRowSize(tiff)` (the bytes per row *as stored by libtiff*). 2. Reallocates `state->buffer` to exactly `tile_bytes_size`. 3. For each tile origin `(x, y)` and each plane, calls `TIFFReadTile` into `state->buffer`, then walks the tile row by row, calling the configured `ImagingShuffler` (here `copy4`) on each row's worth of source bytes. The validation done before the loop is: ```c if (tile_bytes_size > ((tile_length * state->bits / planes + 7) / 8) * tile_width) { state->errcode = IMAGING_CODEC_BROKEN; return -1; } ``` This rejects oversized libtiff tiles, but it does **not** reject *undersized* ones — i.e. the case where libtiff returns a `tile_bytes_size` strictly **less** than `row_byte_size * tile_length`. The loop body, however, indexes into the buffer assuming the buffer is at least `row_byte_size * tile_length` bytes long. ### Vulnerable Code ```c // src/libImaging/TiffDecode.c:446-490 — _decodeTile new_data = realloc(state->buffer, state->bytes); // bytes = tile_bytes_size if (!new_data) { ... } state->buffer = new_data; for (y = state->yoff; y < state->ysize; y += tile_length) { for (plane = 0; plane < planes; plane++) { ImagingShuffler shuffler = unpackers[plane]; for (x = state->xoff; x < state->xsize; x += tile_width) { if (TIFFReadTile(tiff, (tdata_t)state->buffer, x, y, 0, plane) == -1) { ... } current_tile_width = min((INT32)tile_width, state->xsize - x); current_tile_length = min((INT32)tile_length, state->ysize - y); for (tile_y = 0; tile_y < current_tile_length; tile_y++) { shuffler( (UINT8 *)im->image[tile_y + y] + x * im->pixelsize, state->buffer + tile_y * row_byte_size, // <-- OOB stride current_tile_width ); } } } } ``` ```c // src/libImaging/Unpack.c:1288-1291 — copy4 (the crash site) static void copy4(UINT8 *out, const UINT8 *in, int pixels) { /* RGBA, CMYK quadruples */ memcpy(out, in, 4 * pixels); // <-- ASAN-detected READ here } ``` The PoC's tile metadata yields `tile_width = tile_length = 256`, `row_byte_size = 1024` (4 bytes/pixel × 256 pixels), and `tile_bytes_size = 196608` (which is 256·256·3 = three-channel-byte storage). The realloc'd buffer is 196608 bytes. The unpacker iterates `tile_y` from 0 to 255 and for the highest rows the source pointer `state->buffer + tile_y * 1024` already lies at or past offset 196608 — the very last row's `memcpy(in=buffer+255·1024, 1024)` reads 1024 bytes starting `260096 - 196608 = 63488` bytes past the allocation; ASAN flags the very first byte past the heap region (`READ of size 1024 at … 0 bytes after 196608-byte region`). The mismatch is real: libtiff's `TIFFTileSize` returns the *packed* per-tile storage (which depends on the file's photometric/separation layout — it can legitimately be smaller than `row_byte_size × tile_length`), while the inner loop strides as if the buffer always equals `row_byte_size × tile_length`. ## Attribution This issue was found by Anthropic from using Claude to study security of open source projects, and I am from Ada Logics helping validate the found issues and report to maintainers. The data in this report has been reviewed manually. | CVE Database V5 | ||
| Medium | – | EPSS 0.2% | MediumEPSS 0.2%CVE Database V5CVE-2026-111946 Plack::Middleware::ETag versions from 0.04 up to but not including 0.06 for Perl disclose file inode numbers in the default ETag header when the response body is a filehandle and the file_etag option is not set. This exposure allows clients to obtain inode numbers, which may aid in backend fingerprinting and correlation of files served through different URLs. | CVE Database V5 | ||
| Medium | 6.3 | EPSS 0.3% | CVE-2026-112394Pillow · Buffer Over-read Medium6.3EPSS 0.3%CVE Database V5CVE-2026-112394 Pillow is a Python imaging library. Prior to 13.0.0, Pillow's P;2L and P;4L raw decoders can read past the end of an input buffer. ImagingRawDecode calculates a packed-pixel row size, while the unpackers read separately padded bit planes. A caller can supply a buffer and image dimensions whose accepted size is smaller than the unpacker's actual footprint. This can disclose up to three adjacent heap bytes in output pixels or crash the process. This issue is fixed in version 13.0.0. | CVE Database V5 | ||
| Critical | 9.2 | EPSS 0.5% | CVE-2026-112393Pillow · Heap-based Buffer Overflow Critical9.2EPSS 0.5%CVE Database V5CVE-2026-112393 ``` ============================================================================ Heap buffer overflow in _tiffWriteProc when saving TIFF to BytesIO Location: src/libImaging/TiffDecode.c:94 in _tiffWriteProc() Commit: a707c6b21c313880d0771e54965d142dd42358b6 ============================================================================ ``` Pillow can write attacker-chosen data past the end of a heap buffer when it saves a TIFF image to an in-memory destination such as `io.BytesIO` rather than to a file. The same code also copies leftover heap memory from the process into the saved file. Anyone who can supply an image to a service that opens it and re-saves it as TIFF this way can trigger this with default settings. The attached reproducer crashes the process, and the overflow could potentially be developed further, but code execution is not demonstrated here. The bug is in `_tiffWriteProc` in `src/libImaging/TiffDecode.c`. This is the write callback for the in-memory sink that Pillow gives libtiff whenever the save target has no `fileno()`. While writing the directory, libtiff seeks past the current end of data and writes tag values there. `_tiffWriteProc` sizes its `realloc()` from `state->size + size` instead of from the write position `state->loc`. As a result, the first out-of-line tag value written after that seek can run past the allocation by up to the size of the directory: 12 bytes per entry, 126 bytes in the minimal case, and 2384 bytes observed with a 331-entry directory. The overflowing bytes are the tail of a tag value taken from the uploaded file. No save options are needed, because compression and tags are inherited from the source image. The overflow does require an attacker-sized out-of-line tag to reach libtiff. That happens when the opened TIFF is saved directly (ImageDescription, DocumentName or XMP), or through an embedded ICC profile, which survives `copy()` in `im.info`. The bytes libtiff skips for alignment are never initialised, so any compressed TIFF over 64 KiB written this way also leaks one stale heap byte per padding hole. That leak needs no crafted tag. The attached reproducer builds a 131 KB PackBits TIFF and runs `Image.open(...).save(io.BytesIO(), "TIFF")`. Saving the same image to a file path succeeds, but the `BytesIO` save aborts with glibc's `malloc(): corrupted top size`. A second part of the script shows uninitialised heap bytes at the padding offsets of a `BytesIO` save that are zero in the file save. Saves to real files are unaffected. The defect dates to e782fe721e01 (2013) and has been present since Pillow 2.0.0. ## Details When `im.save()` writes a TIFF through libtiff to a target that has no usable `fileno()` (`io.BytesIO`, an HTTP response object, a storage backend stream), libtiff is opened with `TIFFClientOpen()` on Pillow's own in-memory sink, `_tiffWriteProc` / `_tiffSeekProc` in `src/libImaging/TiffDecode.c`. libtiff treats that sink like a file: when it writes the directory it seeks *beyond* the current end of data (to `eof + IFD size`, where the out-of-line tag values go, and one byte further after every odd-length tag value to keep offsets even) and writes there. On a real file the kernel grows the file and zero-fills the gap. The memory sink does neither correctly: 1. `_tiffWriteProc` sizes its `realloc()` from `state->size + size` instead of `state->loc + size`, so the first write after such a seek can land past the end of the new allocation — a heap buffer overflow whose content is tag data taken from the image being saved. 2. The bytes that libtiff skips are never written, and the grown region is a bare `realloc()`, so they reach the output TIFF with whatever the heap held before. Both are reachable with default settings by the ordinary "re-encode the upload" sequence `Image.open(upload); im.save(io.BytesIO(), "TIFF")` with no save options: `compression` defaults to the source file's (`src/PIL/TiffImagePlugin.py:1734`), any compression other than raw selects the libtiff writer (`:1746`), the source's `tag_v2` entries are passed through to libtiff (`:1959`), and a target without `fileno()` leaves `_fp = 0` (`:1928-1935`), i.e. the memory sink. The overflow half additionally needs an attacker-controlled out-of-line tag value of the right length to reach libtiff. With the opened `TiffImageFile` saved directly (`tag_v2` passthrough) that can be a ~64 KiB ASCII tag (269/270/271/272) or the XMP packet (tag 700, type BYTE — arbitrary bytes), and the number of tags the upload carries sets the IFD size, i.e. the overrun length (12 bytes per entry; 2384 bytes with a 331-entry IFD observed). After `convert()`/`resize()`/`thumbnail()`/`copy()` the `tag_v2` entries are gone, but `im.info["compression"]` (still selecting libtiff) and `im.info["icc_profile"]` survive and the ICC profile is written as tag 34675 (`:1822-1824`), an equally attacker-controlled out-of-line value: `Image.open(x).copy().save(BytesIO(), "TIFF")` with the payload in the ICC profile | CVE Database V5 | ||
| Medium | 6.9 | EPSS 2.0% | CVE-2026-112301imagemagick-php · OS Command Injection Medium6.9EPSS 2.0%CVE Database V5CVE-2026-112301 A security flaw has been discovered in orbitale imagemagick-php up to 3.3.5. Affected by this issue is the function Command::font of the file src/Command.php. The manipulation of the argument fontFile results in os command injection. The attack can be executed remotely. The exploit has been released to the public and may be used for attacks. The vendor was contacted early about this disclosure but did not respond in any way. | CVE Database V5 | ||
| Medium | 6.3 | EPSS 0.4% | CVE-2026-112391Pillow · Infinite Loop Medium6.3EPSS 0.4%CVE Database V5CVE-2026-112391 ### Summary `PdfParser`'s `get_value()` can recurse without any limit and crash with an uncaught `RecursionError`. It is reachable through `Image.save(im, fp=<pdf>, format="PDF", append=True)` when the existing PDF file is attacker-controlled. This is the same class of bug as https://github.com/advisories/GHSA-r73j-pqj5-w3x7 (the /Prev trailer chain loop, fixed earlier this year), in the same file, but in two different functions that fix did not touch. ### Details **`get_value()` in `src/PIL/PdfParser.py`** This is a classmethod that takes `max_nesting: int = -1` and has a guard: ```python if max_nesting == 0: return None, None ``` Every recursive call (nested arrays, dictionaries, indirect object definitions) passes `max_nesting=max_nesting - 1`. Since the default and every call site start at -1, this only ever counts down further negative: -1, -2, -3, and so on. It never equals exactly 0, so the guard never runs. A PDF object with a value nested a few thousand levels deep is enough to hit Python's recursion limit. It does not even need a valid `/Pages` tree, since `read_pdf_info()` parses the `/Root` object's own dictionary first, before checking anything else. This advisory reports a single vulnerability: an ineffective recursion-depth guard in `PdfParser.get_value()` (the sentinel `-1` default never reaches the `== 0` check). The mention of GHSA-r73j-pqj5-w3x7 is comparative context for an already-fixed, already-CVE'd bug in a different function — it is not a second vulnerability being reported. The three trigger paths (nested arrays/dicts/indirect objects) are not independently fixable; they share one root cause and one fix location in get_value() . ### Steps to reproduce Both PDFs below are plain text, built directly in Python, no PDF library needed. ```python from PIL.PdfParser import PdfParser def build_pdf(root): out = bytearray(b"%PDF-1.4\n") offset = len(out) out += f"1 0 obj\n".encode() out += root out += b"\nendobj\n" out += b"xref\n" out += f"0 2\n".encode() out += b"0000000000 65535 f \n" out += f"{offset:010d} 00000 ".encode() + b"n \n" xref_offset = len(out) out += b"xref\n" out += f"0 2\n".encode() out += b"0000000000 65535 f \n" out += f"{offset:010d} 00000 ".encode() + b"n \n" out += b"trailer\n" out += f"<< /Size 2 /Root 1 0 R >>\n".encode() out += b"startxref\n" out += f"{xref_offset}\n".encode() out += b"%%EOF" return bytes(out) bomb = b"[" * 3000 + b"]" * 3000 root = b"<< /Type /Catalog /Pages 2 0 R /Bomb " + bomb + b" >>" with open("nesting_bomb.pdf", "wb") as f: f.write(build_pdf(root)) PdfParser("nesting_bomb.pdf") # raises RecursionError ``` Expected: a clean `PdfFormatError`. Actual: raises a raw `RecursionError: maximum recursion depth exceeded`. ### Impact - Availability: an attacker who controls the content of a PDF file that an application later appends an image page to (`Image.save(..., format="PDF", append=True)`) can crash that request with a RecursionError, using a file of a few hundred bytes. - Confidentiality: none. - Integrity: none. - Pillow cannot open PDFs for reading through `Image.open()`. `PdfParser` is only reached through the append-mode save path, so the app has to be doing that specific thing (for example, stamping or watermarking an existing PDF) for this to matter. | CVE Database V5 | ||
| High | 8.8 | EPSS 0.2% | CVE-2026-76788zephyr · bounds High8.8EPSS 0.2%CVE Database V5CVE-2026-76788 The OSDP Control Panel reply parser cp_decode_response() in subsys/mgmt/osdp/src/osdp_cp.c decodes replies from a Peripheral Device into a stack-allocated struct osdp_event. For the REPLY_KEYPPAD and REPLY_RAW reply types, the payload length is taken from the received frame and the only validation performed is that the declared length is self-consistent with the number of payload bytes present in the frame. Because the attacker controls both the length field and the frame length, that check imposes no bound, and the subsequent memcpy() writes into the fixed 64-byte data arrays of struct osdp_event_keypress / struct osdp_event_cardread (OSDP_EVENT_MAX_DATALEN). The sibling REPLY_FMT handler in the same switch already carried the missing > OSDP_EVENT_MAX_DATALEN bound. The defect is reached from cp_process_reply(), which reads bytes from the OSDP RS-485 channel and passes them through osdp_phy_check_packet() and osdp_phy_decode_packet() before dispatching on the reply ID. Triggering it requires CONFIG_OSDP with CONFIG_OSDP_MODE_CP and an application event callback registered (the normal Control Panel integration), plus a frame carrying a correct SOM, a valid CRC-16, and a matching 2-bit sequence number — all of which are computable or observable by anyone on the bus. No check ties the reply ID to the outstanding command, so an oversized REPLY_KEYPPAD or REPLY_RAW is accepted as the answer to a routine CMD_POLL. A malicious or compromised card reader, or anyone with access to the RS-485 wiring, can supply such a frame; CONFIG_OSDP_SC_ENABLED only helps while a secure channel is active, and a single unauthenticated frame carrying a bad MAC causes sc_deactivate(), after which plaintext replies are again accepted. With the default CONFIG_OSDP_UART_BUFFER_LENGTH of 256, roughly 180 bytes of fully attacker-chosen data can be written past the end of the stack-allocated event object, over saved registers and the return address on typical Cortex-M and RISC-V frame layouts (Zephyr does not enable stack canaries by default). The impact ranges from a hard crash of the access-control panel to potential control-flow hijack in the context of the OSDP receive thread. The fix adds an OSDP_EVENT_MAX_DATALEN bound to both handlers, capping each memcpy() at the size of its destination. | CVE Database V5 |
Showing 41–60 of 455 results