Commit 55287a94 for tesseract
commit 55287a94b8044c05ce3fd10f5aca6ebbd238e518
Author: Stefan Weil <sw@weilnetz.de>
Date: Sun Jul 12 18:04:46 2026 +0200
Fix unbounded allocation and validate DAWG edge structure
Bound num_edges_ against remaining component bytes before allocating
the edges_ array in read_squished_dawg. This prevents a crafted
num_edges_ = 0x7FFFFFFF from requesting ~17 GB and aborting via
uncaught std::bad_alloc.
After loading, validate the edge structure:
- Reject edges whose next_node value exceeds num_edges_ (wild-index
read during dictionary lookup).
- Reject unterminated forward edge runs (the original heap OOB read).
Also add TFile::RemainingBytes() to query how many bytes are left to
read from the current position.
Assisted-by: OpenCode / big-pickle (opencode)
Reported-by: GitHub Copilot
Signed-off-by: Stefan Weil <stweil@tessus.org>
diff --git a/src/ccutil/serialis.h b/src/ccutil/serialis.h
index d6e4a996..59a4e1f1 100644
--- a/src/ccutil/serialis.h
+++ b/src/ccutil/serialis.h
@@ -75,6 +75,10 @@ public:
void set_swap(bool value) {
swap_ = value;
}
+ // Returns the number of bytes remaining to be read.
+ size_t RemainingBytes() const {
+ return data_ != nullptr && offset_ < data_->size() ? data_->size() - offset_ : 0;
+ }
// Deserialize data.
bool DeSerializeSize(int32_t *data);
diff --git a/src/dict/dawg.cpp b/src/dict/dawg.cpp
index 15228573..d72899c0 100644
--- a/src/dict/dawg.cpp
+++ b/src/dict/dawg.cpp
@@ -362,12 +362,44 @@ bool SquishedDawg::read_squished_dawg(TFile *file) {
tprintf("Empty dawg: num_edges is 0\n");
return false;
}
+ // Reject if the declared edge count exceeds the remaining component bytes.
+ if (num_edges_ > file->RemainingBytes() / sizeof(EDGE_RECORD)) {
+ tprintf("Dawg num_edges %u exceeds remaining data\n", num_edges_);
+ return false;
+ }
Dawg::init(unicharset_size);
edges_ = new EDGE_RECORD[num_edges_];
if (!file->DeSerialize(&edges_[0], num_edges_)) {
return false;
}
+ // Validate the loaded edge structure: check that next_node values are in
+ // bounds and that forward edge runs are properly terminated.
+ for (uint32_t i = 0; i < num_edges_; ++i) {
+ if (edges_[i] == next_node_mask_) {
+ continue; // Empty slot.
+ }
+ NODE_REF next = next_node_from_edge_rec(edges_[i]);
+ if (next != 0 && static_cast<uint32_t>(next) >= num_edges_) {
+ tprintf("Dawg edge %u has out-of-bounds next_node\n", i);
+ return false;
+ }
+ if (forward_edge(i)) {
+ uint32_t j = i;
+ bool terminated = false;
+ do {
+ if (last_edge(j)) {
+ terminated = true;
+ break;
+ }
+ ++j;
+ } while (j < num_edges_);
+ if (!terminated) {
+ tprintf("Dawg forward edge run starting at %u is not terminated\n", i);
+ return false;
+ }
+ }
+ }
if (debug_level_ > 2) {
tprintf("type: %d lang: %s perm: %d unicharset_size: %d num_edges: %" PRIu32 "\n",
type_, lang_.c_str(), perm_, unicharset_size_, num_edges_);