vLLM: incomplete CVE-2026-22778 fix leaks PIL repr addresses via Anthropic router
Medium5.3CVE-2026-54236 · Published Jun 17, 2026 · updated Sep 10, 2026
# vLLM: incomplete CVE-2026-22778 fix leaks PIL repr addresses via the Anthropic API router **Researcher:** Kai Aizen , SnailSploit (@SnailSploit), Adversarial & Offensive Security Research **Severity:** CVSS 3.1 5.3 (Medium) `AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N` **Target:** https://github.com/vllm-project/vllm --- ## Summary The fix for CVE-2026-22778 / GHSA-4r2x-xpjr-7cvv (PRs #31987 and #32319) introduced `sanitize_message` and applied it at four FastAPI exception-handling sites in the OpenAI router. The sanitizer strips object-repr memory addresses (`<_io.BytesIO object at 0x7a95e299e750>` → `<_io.BytesIO object>`) before error messages reach the client, defeating the ASLR-bypass primitive that CVE-2026-22778 chained with a libopenjp2 heap overflow for RCE. The fix is incomplete: response paths added to vLLM at or after the same time as the fix continue to echo `str(exc)` directly to clients without `sanitize_message`. The original Stage 1 primitive , sending malformed image bytes so PIL raises `UnidentifiedImageError` whose message contains the BytesIO object repr , reaches all of them unmodified and leaks the heap address verbatim in the response body. All five line...
Affected versions
| Package | Affected | Fixed in |
|---|---|---|
| vllm PyPI | < 0.24.0 | 0.24.0 |
Details and references
# vLLM: incomplete CVE-2026-22778 fix leaks PIL repr addresses via the Anthropic API router **Researcher:** Kai Aizen , SnailSploit (@SnailSploit), Adversarial & Offensive Security Research **Severity:** CVSS 3.1 5.3 (Medium) `AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N` **Target:** https://github.com/vllm-project/vllm --- ## Summary The fix for CVE-2026-22778 / GHSA-4r2x-xpjr-7cvv (PRs #31987 and #32319) introduced `sanitize_message` and applied it at four FastAPI exception-handling sites in the OpenAI router. The sanitizer strips object-repr memory addresses (`<_io.BytesIO object at 0x7a95e299e750>` → `<_io.BytesIO object>`) before error messages reach the client, defeating the ASLR-bypass primitive that CVE-2026-22778 chained with a libopenjp2 heap overflow for RCE. The fix is incomplete: response paths added to vLLM at or after the same time as the fix continue to echo `str(exc)` directly to clients without `sanitize_message`. The original Stage 1 primitive , sending malformed image bytes so PIL raises `UnidentifiedImageError` whose message contains the BytesIO object repr , reaches all of them unmodified and leaks the heap address verbatim in the response body. All five lines below are present in `main` HEAD (`771e1e48b`, 2026-05-26). ## Affected sites Current `main` HEAD (`771e1e48b`, 2026-05-26): | # | File | Line | Code | |---|---|---|---| | 1 | `vllm/entrypoints/anthropic/api_router.py` | 78 | `message=str(e),` (inside `POST /v1/messages` exception handler) | | 2 | `vllm/entrypoints/anthropic/api_router.py` | 124 | `message=str(e),` (inside `POST /v1/messages/count_tokens`) | | 3 | `vllm/entrypoints/anthropic/serving.py` | 808 | `error=AnthropicError(type="internal_error", message=str(e)),` (SSE streaming converter) | | 4 | `vllm/entrypoints/speech_to_text/realtime/connection.py` | 75 | `await self.send_error(str(e), "processing_error")` (WebSocket event loop) | | 5 | `vllm/entrypoints/speech_to_text/realtime/connection.py` | 265 | `await self.send_error(str(e), "processing_error")` (WebSocket generation loop) | ## Why the global exception handler does not save these paths `api_server.py` registers a catch-all `app.exception_handler(Exception)(exception_handler)` at line 262, and that handler calls `create_error_response(exc)` which DOES apply `sanitize_message`. However, FastAPI exception handlers fire only on **unhandled** exceptions that propagate out of a route function. All affected HTTP paths catch `Exception` *inside* the route coroutine and construct the response themselves: ```python # vllm/entrypoints/anthropic/api_router.py:71-81 (POST /v1/messages) try: generator = await handler.create_messages(request, raw_request) except Exception as e: logger.exception("Error in create_messages: %s", e) return JSONResponse( status_code=HTTPStatus.INTERNAL_SERVER_ERROR.value, content=AnthropicErrorResponse( error=AnthropicError( type="internal_error", message=str(e), # <-- unsanitized ) ).model_dump(), ) ``` Because the exception is caught and a `JSONResponse` is returned in-route, every registered FastAPI exception handler , including the sanitizing global one , is bypassed. The WebSocket path bypasses it for a different reason: WebSocket frames don't traverse FastAPI's HTTP exception handler chain at all. ## Reachability , the same primitive as the parent CVE The Anthropic Messages API accepts image content parts in the request body (`type: "image"` with base64 `source.data` or `type: "image_url"`). Image bytes are passed to the same multimodal loader used by the OpenAI router. Malformed bytes cause `PIL.Image.open` to raise: ``` UnidentifiedImageError: cannot identify image file <_io.BytesIO object at 0x7a95e299e750> ``` The exception propagates up through `handler.create_messages` into the `except Exception as e:` at `api_router.py:75`. `str(e)` returns the exception message verbatim, including the a
- CVSS 3.1
- CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N
- Severity from
- GitHub (reviewed advisory)
- Weakness
- CWE-532
- Also known as
- CVE-2026-54236, PYSEC-2026-3408
- github.com/vllm-project/vllm/security/advisories/GHSA-hgg8-fqqc-vfmw
- nvd.nist.gov/vuln/detail/CVE-2026-54236
- github.com/vllm-project/vllm/pull/45119
- github.com/vllm-project/vllm/commit/94923629729381d7f7c9efde72071a2441f7fd82
- github.com/advisories/GHSA-hgg8-fqqc-vfmw
- github.com/pypa/advisory-database/tree/main/vulns/vllm/PYSEC-2026-3408.yaml
- github.com/vllm-project/vllm
- pypi.org/project/vllm
More vLLM advisories
All vLLM| Date | Advisory | Severity | Fixed in |
|---|---|---|---|
| Jun 17 | vLLM: OOM Denial of Service via Audio Decompression Bomb | Medium6.5 | 0.24.0 |
| Jun 17 | vLLM: GGUF dequantize kernel int truncation exposes uninitialized GPU memory in multi-tenant serving | Medium7.5 | 0.24.0 |
| Jun 17 | ## Summary Issue 1: EXIF orientation not normalized → The image orientation... | Medium4.8 | 0.24.0 |
| Jun 17 | vLLM: temperature=NaN and temperature=Infinity bypass validation and propagate to GPU kernels | Medium6.5 | 0.24.0 |
| Jun 16 | vLLM: OpenAI auth bypass | Critical9.1 | 0.22.0 |
| Jun 16 | vLLM: remote code execution | High7.5 | 0.22.0 |