GHSA-Q97C-P368-75C4

Vulnerability from github – Published: 2026-10-28 21:15 – Updated: 2026-10-28 21:15
VLAI
Summary
vLLM: Out-of-vocabulary prompt token id passes input validation and triggers a CUDA device-side assert (engine crash / DoS)
Details

For a model whose vocabulary size is an exact multiple of the embedding padding size (64) — e.g. any Llama-2-family model, vocab 32000 — a single request carrying a pre-tokenized prompt with token id equal to the vocab size (32000) passes vLLM's input-id validation but is one past the last embedding row. At tensor-parallel size 1 the embedding lookup does not mask indices, so the id reaches F.embedding out of range and raises a CUDA device-side assert, which poisons the engine's CUDA context and takes the engine down. Any client that can send pre-tokenized input (/v1/completions with prompt as token ids, /tokenize-fed flows, or the kv_transfer_params["prompt_token_ids"] reuse path) can trigger it.

Root cause

vllm/v1/engine/input_processor.py accepts a token id when id <= max(tokenizer.max_token_id, model_config.get_vocab_size() - 1). vllm/tokenizers/hf.py computes max_token_id = max(max(vocab.values()), tokenizer.vocab_size). For Llama-2 tokenizers max(vocab.values()) = 31999 but tokenizer.vocab_size = 32000, so max_token_id = 32000 — one greater than the largest valid id. The engine bound therefore becomes max(32000, 31999) = 32000, and id 32000 is accepted. The model's embedding has pad_vocab_size(32000, 64) = 32000 rows (no padding), valid indices 0..31999. At VocabParallelEmbedding.forward with tp_size == 1 there is no masking, so index 32000 hits F.embedding out of range.

Affected

  • tp=1 (single GPU per model — common per-copy layout). tp>1 masks via get_masked_input_and_mask, so the id is clamped, not an OOB — likely wrong output rather than a crash.
  • Models whose vocab is a multiple of 64 with tokenizer.vocab_size one past the max real id (Llama-2 family, vocab 32000, is the clear case). Qwen2.5 is NOT affected (padded headroom).

Suggested fix

max_token_id in vllm/tokenizers/hf.py now uses tokenizer.vocab_size - 1, so an id equal to the vocabulary size fails input validation instead of reaching the embedding. Fixed in MR59491, released in v0.32.0.

Reporter

Shijie Lyu (GitHub shijie-lyu lshjhf@gmail.com). Reported privately per SECURITY.

This advisory was fixed in MR59491.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "PyPI",
        "name": "vllm"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "0.32.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-115916"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-129",
      "CWE-193"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-10-28T21:15:08Z",
    "nvd_published_at": "2026-10-26T18:17:39Z",
    "severity": "MODERATE"
  },
  "details": "For a model whose vocabulary size is an exact multiple of the embedding padding size (64) \u2014 e.g. any Llama-2-family model, vocab 32000 \u2014 a single request carrying a pre-tokenized prompt with token id equal to the vocab size (32000) passes vLLM\u0027s input-id validation but is one past the last embedding row. At tensor-parallel size 1 the embedding lookup does not mask indices, so the id reaches `F.embedding` out of range and raises a CUDA device-side assert, which poisons the engine\u0027s CUDA context and takes the engine down. Any client that can send pre-tokenized input (`/v1/completions` with `prompt` as token ids, `/tokenize`-fed flows, or the `kv_transfer_params[\"prompt_token_ids\"]` reuse path) can trigger it.\n\n## Root cause\n`vllm/v1/engine/input_processor.py` accepts a token id when `id \u003c= max(tokenizer.max_token_id, model_config.get_vocab_size() - 1)`.\n`vllm/tokenizers/hf.py` computes `max_token_id = max(max(vocab.values()), tokenizer.vocab_size)`. For Llama-2 tokenizers `max(vocab.values()) = 31999` but `tokenizer.vocab_size = 32000`, so `max_token_id = 32000` \u2014 one greater than the largest valid id.\nThe engine bound therefore becomes `max(32000, 31999) = 32000`, and id 32000 is accepted.\nThe model\u0027s embedding has `pad_vocab_size(32000, 64) = 32000` rows (no padding), valid indices 0..31999. At `VocabParallelEmbedding.forward` with `tp_size == 1` there is no masking, so index 32000 hits `F.embedding` out of range.\n\n## Affected\n- tp=1 (single GPU per model \u2014 common per-copy layout). tp\u003e1 masks via `get_masked_input_and_mask`, so the id is clamped, not an OOB \u2014 likely wrong output rather than a crash.\n- Models whose vocab is a multiple of 64 with `tokenizer.vocab_size` one past the max real id (Llama-2 family, vocab 32000, is the clear case). Qwen2.5 is NOT affected (padded headroom).\n\n## Suggested fix \nmax_token_id in vllm/tokenizers/hf.py now uses tokenizer.vocab_size - 1, so an id equal to the vocabulary size fails input validation instead of reaching the embedding. Fixed in [MR59491](https://github.com/vllm-project/vllm/pull/59491), released in v0.32.0.\n\n## Reporter\nShijie Lyu (GitHub shijie-lyu lshjhf@gmail.com). Reported privately per SECURITY.\n\nThis advisory was fixed in [MR59491](https://github.com/vllm-project/vllm/pull/59491).",
  "id": "GHSA-q97c-p368-75c4",
  "modified": "2026-10-28T21:15:08Z",
  "published": "2026-10-28T21:15:08Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/vllm-project/vllm/security/advisories/GHSA-q97c-p368-75c4"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-115916"
    },
    {
      "type": "WEB",
      "url": "https://github.com/vllm-project/vllm/pull/59491"
    },
    {
      "type": "WEB",
      "url": "https://github.com/vllm-project/vllm/commit/37cecc8141e2dcba9dd534897a164d4baaf27229"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/vllm-project/vllm"
    },
    {
      "type": "WEB",
      "url": "https://github.com/vllm-project/vllm/releases/tag/v0.32.0"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "vLLM: Out-of-vocabulary prompt token id passes input validation and triggers a CUDA device-side assert (engine crash / DoS)"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Drag nodes to arrange them. Double-click a related vulnerability to open it, or a source to visit its link. The Physics button on the left tunes the layout and the simulation; the legend at the bottom left hides or shows nodes by kind and by sighting type.

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
Vulnerability timeline

Sightings and recorded dates, grouped by day · UTC

JSON
Events included in the timeline

Loading timeline…


    Loading…

    Loading…

    Loading…

    Related by attack behaviour

    Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.