Fix OOB reads in tokenizer decoders, image transforms, and speech features - #1098
Merged
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
This PR hardens several tokenizer, image pre-processing, and speech feature kernels against malformed inputs by adding bounds checks and validating assumptions that previously could lead to out-of-bounds reads.
Changes:
- Fix batched BPE decoder loop bound to use per-row
seq_leninstead of total element count. - Add/adjust bounds checks for tokenizer decoders (WordPiece row guard, BERT positions slicing, Trie detokenizer rank handling).
- Add input validation in image transforms (Resize channel count, Normalize channel-vs-mean/std alignment, Permute dims range) and speech features (window length vs frame length), plus a regression test for batched detokenization.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| operators/tokenizer/bpe_decoder.hpp | Fix per-row iteration bound for batched decoding to prevent OOB reads. |
| operators/tokenizer/wordpiece_tokenizer.cc | Move bounds check ahead of existing_rows[row_index] access. |
| operators/tokenizer/bert_tokenizer_decoder.cc | Validate attacker-controlled positions slices before slicing ids. |
| operators/tokenizer/trie_tokenizer.hpp | Handle rank-1 inputs without indexing ids_dim[1]; adjust indexing by seq_len. |
| shared/api/image_transforms.hpp | Add validation for Resize/Normalize/Permute to prevent channel/dim-driven OOB reads. |
| shared/api/speech_features.hpp | Validate FFT window length is at least frame_length_ to prevent OOB reads. |
| test/pp_api_test/test_tokenizer_impl.cc | Add regression test for batched BPE detokenization (batch=2). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Sayan Shaw (sayanshaw24)
enabled auto-merge (squash)
August 4, 2026 17:15
added 2 commits
August 6, 2026 17:19
…sions into sayanshaw/kernel-oob
Akshay Sonawane (apsonawane)
approved these changes
Aug 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fix OOB reads in tokenizer decoders, image transforms, and speech features
Summary
This PR adds missing bounds checks and input validation across multiple custom operators to prevent heap out-of-bounds reads from malformed inputs or crafted models.
Changes
operators/tokenizer/bpe_decoder.hppKernelBpeDecoder::Compute: the inner loop iterated over the entire tensor element count instead of the per-row sequence length, causing OOB reads when processing batched inputs (batch >= 2).operators/tokenizer/wordpiece_tokenizer.ccKernelWordpieceTokenizer_Tokenizer: therow_index >= n_existing_rowsguard ran one line after the dereference ofexisting_rows[row_index]. Moved the check into the condition before the access.operators/tokenizer/bert_tokenizer_decoder.ccKernelBertTokenizerDecoder::Compute: start/end indices from the attacker-controlledpositionsinput were used to slice theidsbuffer without any bounds validation. Added0 <= start <= end <= ids.NumberOfElement()check.operators/tokenizer/trie_tokenizer.hppTrieDetokenizer::Compute: the code assumed rank-2 input and accessedids_dim[1]without verifyingids_dim.size() >= 2. Rank-1 inputs now treated as[1, N].shared/api/image_transforms.hppResize::Compute: addedc != 3channel validation. The copy loop uses a hardcoded stride of 3, so non-3-channel inputs read past the buffer.Normalize::Compute: addedC == mean_.size()validation. The channel loop indexed into fixed 3-elementmean_/std_vectors using the input's channel dimension.Permute3D::Init: added validation that eachdims_[i]is in[0, 2]. Out-of-range values would index past the 3-element shape vector.shared/api/speech_features.hppSpeechLibSTFTNorm: added validation thatfft_win_.size() >= frame_length_after initialization. A caller-suppliedhann_winshorter thanframe_length_caused the STFT loop to read past the window buffer.test/pp_api_test/test_tokenizer_impl.ccBpeDecoderBatchedDetokenizeregression test: verifies batched detokenization (batch=2) produces correct output without OOB access.Testing
The
BpeDecoderBatchedDetokenizetest exercises the BPE decoder fix through theTokenizerImpl::DetokenizeAPI. The remaining fixes are in custom op kernels or config-driven pipelines that require either a crafted ONNX model with an ORT session or attacker-controlled preprocessor config bundles, not worth adding into the repository.