high · 7.5 Patched

vLLM: Public multimodal SHM handle forgery reaches worker-side `pickle.loads()` with insecure serialization disabled

CVE-2026-115912 · GHSA-6757-hggm-hp2w · published 28 Oct 2026

How serious is it

  • Severity: high, CVSS score 7.5 out of 10.
  • No exploit prediction (EPSS) score yet.
  • Not on the CISA list of exploited vulnerabilities.
  • No public exploit in Exploit-DB.

Is your app affected by CVE-2026-115912?

Paste this prompt into Claude Code, Cursor or any AI coding tool: it runs a free Shippable scan of your project and fixes what it finds. Built with Lovable, Bolt or v0? Scan your live app. Or upload your package file.

Who is affected

  • vllm (PyPI) < 0.32.0

How to check

  1. Run pip show vllm in your project to see the installed version, including copies pulled in by other packages.
  2. Or paste your lockfile into the checker: it lists every package with a known vulnerability, this one included.
  3. Or run npx shippable-dev scan localhost:3000 in your project folder. It reads your lockfile and flags this vulnerability along with every other known one.

How to fix

Copies a prompt for Claude Code, Cursor or Codex: it checks your lockfiles for this and upgrades it.
  1. Upgrade vllm to 0.32.0 or later, commit the lockfile and redeploy.
  2. If a vulnerable version was live, check your logs and rotate the secrets that server could read.

The advisory

## Affected - **Ecosystem / package:** pip / `vllm` - **Affected versions:** vLLM < 0.32.0. ## Summary When `--mm-processor-cache-type shm` is enabled, vLLM's multimodal SHM cache represents a cached item with an `address` and `monotonic_id` pair and treats that pair as authority to dereference shared memory in the worker. A public scale-out request can supply its own multimodal `features.kwargs_data`, so an authenticated caller can forge an inner SHM carrier that points at attacker-controlled bytes already staged in the ring buffer. The worker then reaches `pickle.loads()` while deserializing the forged object. This is reachable through ordinary public HTTP routes and does not require `VLLM_ALLOW_INSECURE_SERIALIZATION=1`. The live chain used `/v1/chat/completions/render` to obtain a valid feature envelope, `/v1/chat/completions` to stage attacker-chosen bytes into SHM, and `/inference/v1/generate` to submit the forged inner carrier. ## Affected code Links pinned to the confirmed commit [`752a3a504485`](https://github.com/vllm-project/vllm/tree/752a3a504485790a2e8491cacbb35c137339ad34) (v0.25.1): - [`vllm/distributed/device_communicators/shm_object_storage.py#L375-L400`](https://github.com/vllm-project/vllm/blob/752a3a504485790a2e8491cacbb35c137339ad34/vllm/distributed/device_communicators/shm_object_storage.py#L375-L400) — SHM object deserialization reaches `pickle.loads(serialized_data)` for byte objects. - [`vllm/multimodal/cache.py#L567-L581`](https://github.com/vllm-project/vllm/blob/752a3a504485790a2e8491cacbb35c137339ad34/vllm/multimodal/cache.py#L567-L581) — sender emits an SHM carrier containing only `address` and `monotonic_id`. - [`vllm/multimodal/cache.py#L711-L737`](https://github.com/vllm-project/vllm/blob/752a3a504485790a2e8491cacbb35c137339ad34/vllm/multimodal/cache.py#L711-L737) — worker dereferences the untrusted pair and reads the SHM object. - [`vllm/entrypoints/scale_out/token_in_token_out/serving.py#L146-L173`](https://github.com/vllm-project/vllm/blob/752a3a504485790a2e8491cacbb35c137339ad34/vllm/entrypoints/scale_out/token_in_token_out/serving.py#L146-L173) — public `features.kwargs_data` is decoded into engine input without authenticating SHM handles before handoff. The public scale-out route decodes attacker-controlled `features.kwargs_data` into `MultiModalKwargsItem`s and hands them straight to the engine — no authentication of the SHM handle: ```python # vllm/entrypoints/scale_out/token_in_token_out/serving.py Lines 155-165 # Deserialize tensor data when present; None → cache hit. mm_kwargs: dict[str, list[MultiModalKwargsItem | None]] = {} if features.kwargs_data is not None: for modality, items in features.kwargs_data.items(): mm_kwargs[modality] = [ decode_mm_kwargs_item(item) if item is not None else None for item in items ] else: for modality, hashes in features.mm_hashes.items(): mm_kwargs[modality] = [None] * len(hashes) ``` In the worker, a decoded item that carries an `address` is treated as authority to dereference shared memory: the `(address, monotonic_id)` pair alone drives the read, with no binding to the cache key or to who issued the handle: ```python # vllm/multimodal/cache.py Lines 711-723 @override def get_and_update_item( self, mm_item: MultiModalKwargsItem | None, mm_hash: str, ) -> MultiModalKwargsItem: assert mm_item is not None, f"Expected an address item for {mm_hash=}" if "address" in mm_item: address = cast(int, mm_item["address"].data) monotonic_id = cast(int, mm_item["monotonic_id"].data) return self._shm_cache.get(address, monotonic_id) return mm_item ``` The dereference lands in the SHM deserializer, which `pickle.loads()` any chunk tagged as `bytes` — so attacker bytes staged in the ring buffer are unpickled in the worker process: ```python # vllm/distributed/device_communicators/shm_object_storage.py Lines 375-400 def deserialize(self, data_view: memoryview) -> Any: # pickle.loads do not read past the end of a pickled object # within a large buffer, so we can skip storing the metadata size type_name, nbytes, len_arr = pickle.loads(data_view) serialized_data = data_view[-nbytes:] if type_name == torch.Tensor.__name__: obj = [] start_idx = 0 for length in len_arr: item_bytes = serialized_data[start_idx : start_idx + length] obj.append(item_bytes) start_idx += length obj = self.tensor_decoder.decode(obj) elif type_name == self._mm_kwargs_item_cls.__name__: obj = [] start_idx = 0 for length in len_arr: item_bytes = serialized_data[start_idx : start_idx + length] obj.append(item_bytes) start_idx += length obj = self.mm_decoder.decode(obj) elif type_name == bytes.__name__: obj = pickle.loads(serialized_data) else: raise ValueError(f"Unsupported object type '{type_name}' in metadata") return obj ``` ## Impact An ordinary authenticated inference caller can execute arbitrary Python in the worker process on a deployment that uses SHM multimodal caching. The live proof used a benign marker gadget and also caused the EngineCore health endpoint to fall from 200 to 503 after the forged request. The exploit is reachable with `VLLM_ALLOW_INSECURE_SERIALIZATION=0`; it abuses the built-in SHM object deserializer, not the separate optional msgpack ext-hook flag. ## Suggested Fix Authenticate every SHM carrier with a server-issued capability bound to the cache key, address, and monotonic id; rotate the capability secret when the cache is cleared; and reject ma…

Check your whole app

Dependencies are only half of it. Free and read-only: before you push, run npx shippable-dev scan localhost:3000 in your project folder, no account needed. For your live app, you prove it's yours with a one-line tag first.

References

Merged from GitHub Advisory Database, OSV. Last updated 29 Oct 2026. All vulnerabilities · RSS · JSON