Skip to content

Reject out-of-range null_char in LSTM model load - #4642

Open
stweil wants to merge 3 commits into
tesseract-ocr:mainfrom
stweil:ghsa-m232-nullchar
Open

stweil wants to merge 3 commits into
tesseract-ocr:mainfrom
stweil:ghsa-m232-nullchar

Conversation

@stweil

@stweil stweil commented Oct 8, 2026

Copy link
Copy Markdown
Member

LSTMRecognizer::DeSerialize read the null character index null_char_ from the TESSDATA_LSTM component with only a read-success check. The value is later used as an index into top_n_flags_ and outputs in RecodeBeamSearch, both sized to the network softmax width (NumOutputs), so a negative or oversized null_char caused an out-of-bounds write and read on the first recognition step.

Validate null_char_ against network_->NumOutputs() at load time and fail the load on an out-of-range value.

Add lstm_nullchar_test, which patches the null_char field of a copy of eng.traineddata (locating it via Network::CreateFromFile) and checks that Init rejects an oversized and a negative value. Both tests pass with the fix and fail without it.

Fixes GHSA-m232-6r7p-cwq7

Reported-by: Zhixi "Jace" Sun g.mygenie@gmail.com
Assisted-by: OpenCode / qwen3.8-27b-thinking (Alibaba Cloud)

LSTMRecognizer::DeSerialize read the null character index null_char_
from the TESSDATA_LSTM component with only a read-success check. The
value is later used as an index into top_n_flags_ and outputs in
RecodeBeamSearch, both sized to the network softmax width (NumOutputs),
so a negative or oversized null_char caused an out-of-bounds write and
read on the first recognition step.

Validate null_char_ against network_->NumOutputs() at load time and fail
the load on an out-of-range value.

Add lstm_nullchar_test, which patches the null_char field of a copy of
eng.traineddata (locating it via Network::CreateFromFile) and checks that
Init rejects an oversized and a negative value. Both tests pass with the
fix and fail without it.

Fixes GHSA-m232-6r7p-cwq7

Reported-by: Zhixi "Jace" Sun <g.mygenie@gmail.com>
Assisted-by: OpenCode / qwen3.8-27b-thinking (Alibaba Cloud)
Signed-off-by: Stefan Weil <sw@weilnetz.de>
@codacy-production

codacy-production Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 21 complexity · 2 duplication

Metric Results
Complexity 21
Duplication 2

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@stweil

stweil commented Oct 8, 2026

Copy link
Copy Markdown
Member Author

@manus-use, please review.

The CMake unit tests are discovered by globbing unittest/*.cc, so
lstm_nullchar_test is picked up automatically there, but the autotools
build lists every check program and source explicitly. Add the test to
check_PROGRAMS and give it a _SOURCES block. It exercises only
libtesseract plus Leptonica (it drives TessBaseAPI/Network loading) and
needs neither the legacy engine nor the training tools, so it is not
guarded.

Assisted-by: OpenCode / qwen3.8-27b-thinking (Alibaba Cloud)
Signed-off-by: Stefan Weil <sw@weilnetz.de>
@stweil
stweil force-pushed the ghsa-m232-nullchar branch from 363f153 to 7625d20 Compare October 8, 2026 08:33
The new null_char range check in LSTMRecognizer::DeSerialize rejects a
model when null_char_ >= network_->NumOutputs(). plumbing_test's
ToleratesUnicharsetWithoutSpecialCodes built a fixture whose NT_INPUT
network declared no outputs, so the (in-range) null_char_ of 0 was now
rejected and the model failed to load.

The check is correct: a network with zero outputs has no valid output
index, so null_char_ can never be in range and the model is unusable.
Relaxing the check would leave top_n_flags_[null_char_] indexing past an
empty vector, which is exactly the OOB the advisory describes. Instead,
declare one output on the fixture so the check passes and the test
continues to verify that a unicharset without the special codes loads
cleanly. The Rejects* fixtures are unaffected: they are dropped on the
empty-stack check before null_char_ is read.

Assisted-by: OpenCode / qwen3.8-27b-thinking (Alibaba Cloud)
Signed-off-by: Stefan Weil <sw@weilnetz.de>

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.

🟡 Changes recommended

Fixture-construction failures can silently skip both security regression tests.

1 open finding
What changed in this PR

Validates LSTM null_char_ during model loading to prevent out-of-bounds softmax access.

Changes:

  • Rejects negative or oversized null-character indices.
  • Adds malformed-model regression tests.
  • Updates existing fixtures and Autotools registration.
File Description
src/​lstm/​lstmrecognizer.cpp Adds range validation.
unittest/​lstm_nullchar_test.cc Adds regression tests.
unittest/​plumbing_test.cc Updates fixture output width.
Makefile.am Registers the new test.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +55 to +56
bool MakePatchedTraineddata(const std::string &dir, int32_t patched_null_char,
int *num_outputs) {
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.

2 participants