# vLLM has Remote DoS via Invalid Recovered Token Reinjection

Speculative decoding in vLLM can recover token equal to vocab_size, convert it to -1, feed it back as live input id, crashing GPU worker via public gRPC.

- Payout: Research
- Severity: high
- Program: vLLM
- Found by: NeilAlfred
- Disclosed: 2026-07-17
- CVE: CVE-2026-54234
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-8wr5-jm2h-8r4f
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-8wr5-jm2h-8r4f
- Tags: area:ai domain:ai impact:dos kind:research platform:ghsa sev:high tech:grpc tech:python technique:race-window technique:source-review vuln:dos vuln:memory-corruption

**Target:** vLLM EngineCore speculative decoding / gRPC Generate+Abort surface
**Root cause:** Rejection sampling can emit a recovered token equal to vocab_size (151936 for Qwen3), which prepare_next_token_ids_padded treats as the live next token and converts to -1; that -1 is written back into live drafter input_ids and consumed by embed/attention, triggering a CUDA device-side assert.
**Steps:**
1. Deploy vLLM 0.17.1 with Qwen/Qwen3-0.6B-GPTQ-Int8 and speculative decoding enabled.
2. Send a frontend-legal multi-request workload using structured outputs, stop, bad_words, min_tokens and streaming overlap so rejection sampling, overlap and cancellation are active.
3. Trigger rejection sampling so `sample_recovered_tokens` returns `recovered_token_ids[0] = 151936` (== vocab_size) in position 0 of the sampled speculative row.
4. `prepare_next_token_ids_padded` converts that live token to `next_token_ids[0] = -1`.
5. `set_inputs_first_pass` writes it back: `input_ids_after = [-1, 0, 0, 0, ...]`.
6. `embed_input_ids` consumes `-1`; worker dies with `CUDA error: device-side assert triggered` under `flash_attn_varlen_func`.
7. Via public gRPC: send overlapping `Generate`/`Abort` sequence, then a probe `Generate` to confirm `EngineDeadError`.
**How found:** Reporter traced the recovered-token chain during speculative decoding with structured outputs and overlap, noticing a recovered token equal to vocab_size appearing in the live position 0 of a sampled speculative row and following it through next-token preparation into the drafter input ids.
**Exploitation:** Remote client with access to the public gRPC generation surface sends a fixed overlapping Generate/Abort sequence that drives speculative decoding into the out-of-vocabulary recovered-token state; the worker crashes with a CUDA device-side assert, aborting concurrent requests and failing subsequent requests until restart. Repeatable to sustain outage.
**Insight:** In speculative decoding, distinguish padding -1 from a live token converted to -1: an out-of-vocabulary recovered token equal to vocab_size in position 0 of the sampled row is the real fault, so instrument the recovered-token -> next_token_ids -> input_ids chain rather than blaming padding.

