Skip to content

Bound tokenizer merges count to INT32_MAX like the tokens table#548

Open
gigioneggiando wants to merge 1 commit into
antirez:mainfrom
gigioneggiando:fix/tokenizer-merges-bound
Open

Bound tokenizer merges count to INT32_MAX like the tokens table#548
gigioneggiando wants to merge 1 commit into
antirez:mainfrom
gigioneggiando:fix/tokenizer-merges-bound

Conversation

@gigioneggiando

Copy link
Copy Markdown

Summary

vocab_load() validates the token table with tokens.len > INT32_MAX, but the merges table validation omits the same bound. The merge loop then stores each rank with:

table_put(&vocab->merge_rank, merge, (int)i);   // i is uint64_t

A GGUF declaring more than INT32_MAX merges makes (int)i wrap negative and stores corrupt merge ranks (affecting tokenization correctness).

Fix

Mirror the tokens sibling's bound: reject merges.len > INT32_MAX in the merges validation.

Verification

Source-confirmed and compile-checked (the patched translation unit builds cleanly under the ASan/UBSan flags). I did not build a runtime PoC — it would require a crafted GGUF declaring >2³¹ merge entries, which isn't practical to materialize as a file. The (int)i narrowing and the missing bound (vs the present one on tokens.len) are visible directly in vocab_load.

Found during a coordinated security review of ds4.

vocab_load() validates the token table with tokens.len > INT32_MAX but
omits the same bound on the merges table. The merge loop then stores each
rank with table_put(&vocab->merge_rank, merge, (int)i), narrowing the
uint64 index to int. A GGUF declaring more than INT32_MAX merges makes
(int)i wrap negative and stores corrupt ranks. Mirror the tokens sibling's
bound on merges.len.
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.

1 participant