GitHub Advisoryadvisory · PyPI

GHSA-ph72-cqr5-qpp7 vLLM: Scale-out disaggregated multimodal transport trusts caller-supplied features

MEDIUM Published 2026-10-05 Modified 2026-10-05 Official page ↗
Aliases
CVE-2026-105754
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-1284, CWE-20, CWE-617, CWE-639, CWE-668, CWE-704
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 scale-out transport path reaches. Summary vLLM's disaggregated scale-out transport splits a multimodal request into a trusted render step (`POST /v1/chat/completions/render`) and a separate generate step (`POST /inference/v1/generate`). The generate route decodes a caller-supplied `features` object — serialized encoder tensors (`kwargs_data`), multimodal hashes (`mm_hashes`), placeholder ranges (`mm_placeholders`), and the internal field-processor selection — and forwards it into the engine as if it had come from the trusted renderer, with no rebinding to (or validation against) the active model's renderer contract. Because the two routes are ordinary auth-guarded HTTP endpoints (the `/inference` prefix is registered by default on generate-capable servers), any authenticated caller can submit an otherwise-valid render body with a single forged field. Depending on which field is forged, this produces: • an engine-fatal crash of the shared EngineCore process (denial of service), reproduced as a CUDA illegal-memory-access, a post-admission rank-mismatch `ValueError`, and a hard `assert` — three independent forged fields (sites 1, 2, 3); • silent cross-request encoder-cache poisoning / disclosure of shared encoder state when the cache-key hash is not bound to the payload (site 4); • transport-level integrity loss when sparse placeholder masks are dropped during render-to-generate replay (site 5). All five share one root cause and one fix shape: the reconstructed multimodal state on the scale-out path is trusted without being rebound to, and validated against, the active model's renderer output before it reaches the engine. These sites are distinct from prior multimodal hardening. Site 1 survives GHSA-wv77-2vpf-vmmg (that fix validates full tensor shape in `MultiModalDataParser`/`get_input_embeddings` on the prompt-embeds path), because our request forges `image_grid_thw` metadata with the pixel bytes intact and reaches the Qwen2 vision RoPE/`cu_seqlens` and `image_embeds.split` sink, which the shape-check fix does not rebind. Site 4 is distinct from GHSA-c65p-x677-fgj6 (which folds metadata into `MultiModalHasher.serialize_item` to stop hash collisions), because the scale-out generate path trusts a caller-supplied `mm_hash` as the cache key with no origin binding, so that fix does not stop a caller from submitting a victim's hash or a `kwargs_data=None` cache read. Affected code Links pinned to the confirmed commit `752a3a504485` (v0.25.1). Shared entry point and control surface for all five sites: • `POST /inference/v1/generate` route: `vllm/entrypoints/scale_out/token_in_token_out/api_router.py#L46-L75`. • Public `features` schema (`kwargs_data`, `mm_hashes`, `mm_placeholders`): `vllm/entrypoints/scale_out/token_in_token_out/protocol.py#L42-L63`. • `ServingTokens.serve_tokens()` copies the decoded geometry and hashes into engine structures without rebinding to the renderer schema: `vllm/entrypoints/scale_out/token_in_token_out/serving.py#L145-L172`. • The scale-out routers are registered by default on generate-capable servers and `/inference` is treated as an ordinary auth-guarded prefix: `vllm/entrypoints/openai/api_server.py#L217-L219`. Site 1 — forged Qwen grid geometry (engine-fatal DoS). The decoded `image_grid_thw` is never rebound to the rendered pixel-tensor element count. • Reconstruction with no model-specific geometry invariant: `serving.py#L147-L172`. • `Qwen2VisionTransformer.prepare_encoder_metadata()` derives RoPE tables, `cu_seqlens`, and the FlashAttention `max_seqlen` from the caller-supplied grid: `qwen2_vl.py#L658-L712`, consumed in `forward()` (`#L713-L751`). • `_process_image_input()` computes `image_embeds.split(sizes)` from the same untrusted metadata: `qwen2_vl.py#L1348-L1369`; M-RoPE positions at `qwen2_vl.py#L1223`. The only shape check is `assert grid_thw.ndim == 2`; the split sizes and the vision-encoder call are then derived directly from the caller-supplied grid, with no cross-check against the pixel-tensor row count: ```python vllm/model_executor/models/qwen2_vl.py Lines 1348-1369 def _process_image_input( self, image_input: Qwen2VLImageInputs ) -> tuple[torch.Tensor, ...]: grid_thw = image_input["image_grid_thw"] assert grid_thw.ndim == 2 if image_input["type"] == "image_embeds": image_embeds = image_input["image_embeds"] else: pixel_values = image_input["pixel_values"] if self.use_data_parallel: return run_dp_sharded_mrope_vision_model( self.visual, pixel_values, grid_thw.tolist(), rope_type="rope_3d" ) else: image_embeds = self.visual(pixel_values, grid_thw=grid_thw) # Split concatenated embeddings for each image item. merge_size = self.visual.spatial_merge_size sizes = (grid_thw.prod(-1) // merge_size // merge_size).tolist() return image_embeds.split(sizes) ``` Site 2 — wire-selected field-processor type confusion (engine-fatal DoS). `MsgpackDecoder._decode_mm_field_elem()` trusts a wire-selected field-factory name and constructs the internal field processor directly from caller data. • `vllm/v1/serial_utils.py#L440-L454` reads `factory_meth_name, factory_kw = obj["field"]` and calls `getattr(MultiModalFieldConfig, factory_meth_name)`. • Qwen2-VL field/schema contract: `qwen2_vl.py#L763-L791` and `Qwen2VLImagePixelInputs` (`#L119-L144`); parse/validate at `qwen2_vl.py#L1300`. • Rank mismatch raised post-admission: [`vllm/utils/tensor_schema.py#L155-L171`](https://github.com/vl…

Related

Alias · CVE (1)

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

Weakness · CWE (6)

  • CWE-1284 Improper Validation of Specified Quantity in Input weakness
  • CWE-20 Improper Input Validation weakness
  • CWE-617 Reachable Assertion weakness
  • CWE-639 Authorization Bypass Through User-Controlled Key weakness
  • CWE-668 Exposure of Resource to Wrong Sphere weakness
  • CWE-704 Incorrect Type Conversion or Cast weakness

References