GitHub Advisoryadvisory · PyPI

GHSA-2823-qmq8-rwvj vLLM: Loose `cache_salt` validation lets a single request kill EngineCore on LMCache-MP deployments — uncaught downstream `ValueError` denial of service

MEDIUM Published 2026-10-05 Modified 2026-10-05 Official page ↗
Aliases
CVE-2026-105756
Ecosystem
PyPI
Packages
vllm
Severity
Moderate
CVSS vector
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H
Weaknesses
CWE-20, CWE-248
Reviewed
2026-10-05

Affected packages

• PyPI vllm: < 0.30.0 → fixed in 0.30.0

Description

Affected • Ecosystem / package: pip / `vllm` • Affected versions: vLLM ≤ 0.25.1 (confirmed on 0.25.1, commit `752a3a504485`). The lower bound predates 0.25.1; maintainers can confirm how far back the loose `cache_salt` validator and unguarded scheduling-path lookup reach. Summary vLLM's OpenAI-compatible request models (Completions, Chat Completions, Responses) accept a client-supplied `cache_salt` field and validate it only as "must be a non-empty string" — no character or length restrictions. On a deployment with the built-in LMCache-MP KV connector enabled, that value is stored verbatim on the request tracker and forwarded unguarded as a keyword argument into the scheduler's per-step cache lookup. The downstream LMCache library applies a *stricter* check in `IPCCacheServerKey.__post_init__`, which raises `ValueError` for any `cache_salt` containing `@`, `/`, `\`, or NUL (or longer than 128 characters). Neither the LMCache-MP connector lookup call site nor `Scheduler.schedule()` wraps that call in a request-scoped `try`/`except`, so the `ValueError` propagates uncaught into `EngineCore`'s top-level handler, which treats any uncaught exception as fatal and kills the whole engine process. A single publicly reachable request with, for example, `cache_salt="/"` therefore takes down the engine for all concurrent users. vLLM's boundary validator is looser than the downstream consumer's, and the gap is never converted into a request-scoped failure on the scheduling path. Affected code Links pinned to the confirmed commit `752a3a504485` (v0.25.1): • The three `check_cache_salt_support` validators require only a non-empty string — no character or length bound: `vllm/entrypoints/openai/completion/protocol.py#L502-L508` (field at `#L172`), `vllm/entrypoints/openai/chat_completion/protocol.py#L913-L919` (field at `#L425`), and `vllm/entrypoints/openai/responses/protocol.py#L459-L465` (field at `#L235`). The loose test itself is at `completion #L503-L505`, `chat_completion #L914-L916`, `responses #L460-L462`. • Two further request models accept `cache_salt` with the same or weaker checking, and should be hardened at the same time: `vllm/entrypoints/pooling/base/protocol.py#L74-L85` carries the identical non-empty-string-only validator (field at `#L59`), and the token-in-token-out scale-out `GenerateRequest` exposes the field with no `cache_salt` validator at all: `vllm/entrypoints/scale_out/token_in_token_out/protocol.py#L110`. • The LMCache-MP request tracker stores the salt verbatim: `vllm/distributed/kv_transfer/kv_connector/v1/lmcache_mp_connector.py#L189` (`LMCacheMPRequestTracker`), assignment at `#L223`. • The lookup call forwards `cache_salt` with no surrounding `try`: `vllm/distributed/kv_transfer/kv_connector/v1/lmcache_mp_connector.py#L733-L774` (`get_num_new_matched_tokens` → `maybe_submit_lookup_request(...)` at `#L770`). • The scheduler invokes the connector unguarded: `vllm/v1/core/sched/scheduler.py#L739` (`self.connector.get_num_new_matched_tokens(...)`, inside `schedule()` at #L396). • The generic top-level handler treats the exception as fatal: `vllm/v1/engine/core.py#L1229-L1235` — the `except Exception` in `run_engine_core` (`#L1154`) logs `EngineCore encountered a fatal error.`, calls `_send_engine_dead()`, and re-raises. • Downstream strict validator (external LMCache library, not vLLM): `IPCCacheServerKey.__post_init__` in `lmcache/v1/multiprocess/custom_types.py` rejects `@ / \` NUL and >128-char salts (`_SALT_FORBIDDEN_CHARS = frozenset("@/\\\x00")`) by raising `ValueError`. The three OpenAI `check_cache_salt_support` validators are identical; the completion one is representative — a non-empty-string test with no character or length bound: ```python vllm/entrypoints/openai/completion/protocol.py Lines 503-509 if data.get("cache_salt") is not None and ( not isinstance(data["cache_salt"], str) or not data["cache_salt"] ): raise VLLMValidationError( "Parameter 'cache_salt' must be a non-empty string if provided.", parameter="cache_salt", ) ``` On the scheduling path the connector is invoked with no surrounding `try` — a `ValueError` from the downstream salt check propagates straight out of `schedule()`: ```python vllm/v1/core/sched/scheduler.py Lines 736-742 # Get externally-cached tokens if using a KVConnector. if self.connector is not None: ext_tokens, load_kv_async = ( self.connector.get_num_new_matched_tokens( request, num_new_local_computed_tokens ) ) ``` `run_engine_core`'s generic handler — the next `except` up the stack — treats that as fatal, marks the engine dead, and re-raises: ```python vllm/v1/engine/core.py Lines 1229-1235 except Exception as e: if engine_core is None: …

Related

Alias · CVE (1)

  • CVE-2026-105756 vLLM is an inference and serving engine for large language models. vulnerability

Weakness · CWE (2)

  • CWE-20 Improper Input Validation weakness
  • CWE-248 Uncaught Exception weakness

References