Skip to content

Validate eos_token_id against vocab_size to prevent OOB write in ApplyMinLength - #2266

Merged
jiafatom merged 4 commits into
mainfrom
fix/eos-token-id-vocab-bounds
Jul 12, 2026
Merged

Validate eos_token_id against vocab_size to prevent OOB write in ApplyMinLength#2266
jiafatom merged 4 commits into
mainfrom
fix/eos-token-id-vocab-bounds

Conversation

@jiafatom

@jiafatom jiafatom commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes an out-of-bounds write (CWE-787 / CWE-129) reachable from a malicious genai_config.json.

Search_Cpu::ApplyMinLength suppresses EOS tokens while the sequence is shorter than min_length by writing std::numeric_limits<float>::lowest() into the per-beam score row at each configured eos_token_id:

std::span<float> const beam_token_scores = GetScores(i);       // vocab_size elements
for (auto token_id : params_->config.model.eos_token_id)
  beam_token_scores[token_id] = std::numeric_limits<float>::lowest();  // unchecked index

eos_token_id comes straight from config (src/config.cpp) with only a static_cast<int> and is never bounded against vocab_size. A model whose eos_token_id >= vocab_size (or < 0) drives the store past the end of the vocab_size-sized GetScores() subspan — a heap-buffer-overflow write on the first decode step (reached via Generator::GenerateNextToken -> ApplyMinLength). The same pattern exists in the CUDA backend (Search_Cuda::ApplyMinLength).

Changes

  • src/generators.cpp: reject any eos_token_id outside [0, vocab_size) in the Generator::Generator validator, mirroring the existing top_k <= vocab_size check (added in Fix security vulnerabilities #2224).
  • src/search.cpp and src/cuda/search_cuda.cpp: defense-in-depth — skip out-of-range ids at the sink in both backends.
  • test/sampling_tests.cpp: add EosTokenIdExceedsVocabSizeThrowsCpu, which overlays an out-of-range eos_token_id and asserts OgaGenerator::Create throws instead of proceeding to the OOB write.

Testing

New regression test follows the existing SamplingTests overlay pattern (tiny-random-gpt2-fp32 + vocab_size/eos_token_id overlay). Local build isn't available in my environment; relying on CI to build and run the C++ unit tests.

@jiafatom
jiafatom requested a review from a team as a code owner July 6, 2026 17:33
Copilot AI review requested due to automatic review settings July 6, 2026 17:33

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses a security issue where untrusted eos_token_id values from genai_config.json could be used as unchecked indices into per-token score buffers, leading to out-of-bounds writes during ApplyMinLength (CPU and CUDA).

Changes:

  • Add generator-construction validation to reject any eos_token_id outside [0, vocab_size).
  • Add defense-in-depth bounds checks at the ApplyMinLength sinks for CPU and CUDA implementations.
  • Add a regression test ensuring generator creation fails when eos_token_id >= vocab_size.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
src/generators.cpp Validates eos_token_id against vocab_size during generator construction to prevent OOB writes.
src/search.cpp Adds bounds check before indexing per-beam score rows with eos_token_id (CPU defense-in-depth).
src/cuda/search_cuda.cpp Adds bounds check before launching CUDA score-masking kernel with eos_token_id (CUDA defense-in-depth).
test/sampling_tests.cpp Adds regression test covering out-of-range eos_token_id rejection at generator creation.

Comment thread test/sampling_tests.cpp Outdated
Comment thread src/generators.cpp Outdated
Comment thread src/search.cpp Outdated
Comment thread src/cuda/search_cuda.cpp Outdated
@jiafatom
jiafatom force-pushed the fix/eos-token-id-vocab-bounds branch from d3d4773 to 473ef2f Compare July 7, 2026 23:06
kunal-vaishnavi
kunal-vaishnavi previously approved these changes Jul 8, 2026
jiafatom and others added 4 commits July 10, 2026 16:21
eos_token_id from genai_config.json is used directly as an index into the
per-beam, vocab_size-sized score row in Search::ApplyMinLength. A config
value >= vocab_size (or < 0) caused an out-of-bounds write of
std::numeric_limits<float>::lowest() past the end of the row
(heap-buffer-overflow, CWE-787 / CWE-129).

Fix:
- Reject out-of-range eos_token_id at generator creation, mirroring the
  existing top_k <= vocab_size validation.
- Defensively skip out-of-range ids at the sink in both the CPU
  (Search_Cpu::ApplyMinLength) and CUDA (Search_Cuda::ApplyMinLength)
  backends.
- Add a CPU regression test overlaying an out-of-range eos_token_id.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- eos_token_id test now catches std::runtime_error specifically and asserts
  the message mentions eos_token_id, so unrelated exceptions can't satisfy it.
- Reword the validation error to a clear half-open interval:
  'must be in range [0, <vocab_size>) (vocab_size)'.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… at creation)

Per reviewer feedback, remove the defense-in-depth bounds checks in
Search_Cpu::ApplyMinLength and Search_Cuda::ApplyMinLength since an
out-of-range eos_token_id is already rejected at generator creation and
these checks are on the per-token inference hot path.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The new creation-time check (eos_token_id must be in [0, vocab_size)) rejected
the sampling tests, which overlay a tiny vocab_size (5/13/17/21) onto the
tiny-random-gpt2-fp32 base config while leaving its eos_token_id=98 in place:

  C++ exception "eos_token_id (98) must be in range [0, 5) (vocab_size)"
  -> 6 CPU tests failed (Batched/Randomized TopP/TopK), plus the CUDA and
     NvTensorRtRtx variants on their runners.

Overlay a valid eos_token_id (0) alongside every small vocab_size overlay
(fixed vocab-5 CPU/CUDA tests, the RunSamplingTest helper, and the
NvTensorRtRtx setup helper). This is behavior-neutral for these tests: none
set min_length, so eos_token_id is never used (it only indexes the score row
in Search::ApplyMinLength). The intentional out-of-range regression test
(EosTokenIdExceedsVocabSizeThrowsCpu, eos_token_id=5 vs vocab_size=5) is left
unchanged and still asserts the validation throws.

Verified: all 10 CPU SamplingTests pass, including the regression test.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jiafatom
jiafatom force-pushed the fix/eos-token-id-vocab-bounds branch from cb1eb80 to d4ef5ea Compare July 10, 2026 16:21
@jiafatom
jiafatom enabled auto-merge (squash) July 10, 2026 21:41
@jiafatom
jiafatom merged commit a21e718 into main Jul 12, 2026
63 of 69 checks passed
@jiafatom
jiafatom deleted the fix/eos-token-id-vocab-bounds branch July 12, 2026 23:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants