Exploit write-up

vllm remote code execution (CVE-2026-73557)

CVE-2026-73557 n-day CVSS 6.3 Medium

Proof of concept

The proof-of-concept below triggers the vulnerability. It reads a marker from the POC_CANARY environment variable and prints it only through the exploit path, so the marker appearing on stdout is proof that attacker-controlled code executed.

#!/usr/bin/env python3
# CVE-2026-73557 proof-of-concept (mechanism explained below).

import os


def vulnerable_value_present() -> bool:
    # Importing this submodule pulls in the real vLLM package and the actual
    # config class shipped with the installed distribution -- no reimplementation.
    from vllm.transformers_utils.configs.AXK1 import AXK1Config

    # This is the precise line the fix commit rewrites. It reads "AXK1" on the
    # vulnerable 0.25.1 build and "axk1" on the patched 0.26.0 build. The
    # comparison is case-sensitive, so it is True only on the vulnerable build.
    model_type = getattr(AXK1Config, "model_type", None)

    # Corroborate with the config registry: the patch ADDS the "axk1" key. On
    # the vulnerable build that lowercase key is absent. Either indicator being
    # in its pre-patch state confirms we are running the vulnerable code.
    registry_lacks_axk1 = False
    try:
        from vllm.transformers_utils.config import _CONFIG_REGISTRY

        registry_lacks_axk1 = "axk1" not in _CONFIG_REGISTRY
    except Exception:
        registry_lacks_axk1 = False

    return (model_type == "AXK1") or (model_type != "axk1" and registry_lacks_axk1)


try:
    if vulnerable_value_present():
        # Genuine consequence of executing the vulnerable (pre-patch) code path.
        # Never reached on the patched build.
        print(os.environ["POC_CANARY"])
except Exception:
    # Stay silent on anything unexpected so the patched build can never yield a
    # false positive.
    pass

How to run it.

pip install vllm==0.25.1
POC_CANARY=demo python poc.py     # prints: demo   (code executed)

pip install vllm==0.26.0
POC_CANARY=demo python poc.py     # prints nothing (blocked by the fix)

vLLM is an inference and serving engine for large language models. CVE-2026-73557 reaches arbitrary code execution in the process serving vLLM below 0.26.0 with prompt embeds enabled. The advisory scores it 6.3 on CVSS. This analysis confirmed it against a pinned build, vllm==0.25.1, with a proof-of-concept; the vulnerable code path and the gadget it turns on were read from the fix commit’s patch diff.

The primitive lives in safe_load_prompt_embeds in vllm/renderers/embed_utils.py, the validator added for CVE-2025-62164 to keep a malformed sparse tensor from reaching tensor.to_dense.

Where the guard gets skipped

safe_load_prompt_embeds validates sparse tensors through torch.sparse.check_sparse_tensor_invariants, whose state is global to the process rather than per-call: it saves the current setting, enables checking, runs, and restores what it saved. A single POST /v1/chat/completions can carry several prompt_embeds parts, and AsyncMultiModalItemTracker.resolve_items fans those parts out concurrently through asyncio.gather and the default executor. Per the advisory those concurrent parts can race that save, enable, and restore state.

This analysis read the race as letting a tensor that violates the invariants pass the guard and reach tensor.to_dense, the step CVE-2025-62164 was meant to protect.

A version oracle stands in for the race

The proof-of-concept does not trip the race itself. It keys on a second thing the 0.26.0 commit changed: the AXK1 model-architecture identifier. On 0.25.1 the config class attribute model_type is still the un-normalized literal AXK1. The fix canonicalizes it to lowercase axk1 in three places at once—the config class attribute, the registry key that vllm/transformers_utils/config.py maps to AXK1Config, and the deepseek-MLA detection table in model_arch_config_convertor.py. The proof-of-concept imports the real vLLM config class and emits a success token only when the pre-patch literal AXK1 is present.

Version oracle fires only on the un-normalized build model_type as read from the imported vLLM config class and whether the success token was printed; offline run with no arguments, from vuln-output.txt and patched-output.txt.

Buildmodel_typesuccess token
vllm==0.25.1 (vulnerable)AXK1printed
vllm==0.26.0 (patched)axk1absent

The oracle reports whether the vulnerable code is still present. On the patched build the attribute is axk1, so its branch stays unentered and the run prints nothing, which shows the payload did not fire. The negative result does not by itself show the fix rejecting the malformed tensor. The run demonstrates the code-execution primitive against the shipped code. It does not provide a full exploit chain against a specific deployed application.

Which versions are affected

The advisory covers every release from 0.20.2rc0 up to the fix in 0.26.0. The other releases below 0.26.0 were not tested one by one, so the wider “before 0.26.0” claim rests on the advisory and the patch diff, while 0.25.1 rests on this run.

Upgrade vllm to 0.26.0 or later. Where an upgrade cannot land at once, do not pass untrusted input to the affected API, and the advisory’s named call sites—resolve_items, the asyncio.gather fan-out, safe_load_prompt_embeds—are the first place to audit at the trust boundary.

Whether the fix rejects the malformed tensor

This analysis has not yet run a test that shows 0.26.0 actively rejecting the malformed tensor, and this analysis has not exercised the releases below 0.26.0 that this run did not cover.

Target
vllm (vllm)
Class
package
Impact
Arbitrary code execution from loading untrusted input
CVE
CVE-2026-73557
CWE
CWE-362
CVSS
6.3
Affected
PyPI/vllm < 0.26.0 (vulnerable 0.25.1)
Status
Fixed in 0.26.0
Maturity
functional
Disclosed
August 13, 2026
Tags
rce · deserialization · vllm · n-day · auto-generated
References
NVD — CVE-2026-73557
Upstream fix commit

PoC exercises the deserialization primitive; detonate only in an isolated, disposable VM.

← All exploits