mirror of
https://github.com/openembedded/meta-openembedded.git
synced 2026-10-15 06:17:16 +00:00
tesseract: patch CVE-2026-88048
Details: https://nvd.nist.gov/vuln/detail/cve-2026-88048 Signed-off-by: Ankur Tyagi <ankur.tyagi85@gmail.com> Signed-off-by: Anuj Mittal <anuj.mittal@oss.qualcomm.com>
This commit is contained in:
1 parent
251dd7f80d
commit
9e544d6e50
2 files changed
+229
No files matched your search
@@ -0,0 +1,228 @@
|
||||
From 4dd118c5b87df1a4422ebb3ab3efaa3588b88cfa Mon Sep 17 00:00:00 2001
|
||||
From: Stefan Weil <sw@weilnetz.de>
|
||||
Date: Fri, 21 Aug 2026 15:23:59 +0200
|
||||
Subject: [PATCH] Validate FullyConnected weight matrix dimensions at load
|
||||
|
||||
FullyConnected::DeSerialize read the WeightMatrix without checking
|
||||
that its dimensions match the layer's declared ni/no. MatrixDotVector
|
||||
drives the dot product from the matrix dimensions (writes w.dim1()
|
||||
results, reads w.dim2()-1 inputs) while the scratch buffers are sized
|
||||
from no_ and ni_, so a crafted .traineddata with a mismatched matrix
|
||||
performed a heap out-of-bounds write (up to 65535 rows) and out-of-
|
||||
bounds read on the first recognition step (crash, or heap corruption
|
||||
with attacker-influenced size and content).
|
||||
|
||||
Key changes:
|
||||
- weightmatrix.h: add Dim1()/Dim2() accessors for the active weight
|
||||
matrix (int or float), alongside the existing NumOutputs().
|
||||
- fullyconnected.cpp: reject the layer in DeSerialize unless
|
||||
Dim1() == no_ and Dim2() == ni_ + 1 (the second dimension
|
||||
includes the bias column), the layout that InitWeightsFloat
|
||||
always produces.
|
||||
- unittest: new fullyconnected_test that builds a Softmax network
|
||||
with mismatched matrix dimensions and expects CreateFromFile to
|
||||
return nullptr; on unpatched code the test reaches Forward and
|
||||
ASan catches the out-of-bounds write in MatrixDotVector. A
|
||||
second test verifies that matching dimensions are still accepted.
|
||||
|
||||
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>
|
||||
(cherry picked from commit 103dc134eb36411ddc6833ec20aa2c76795bd0ff)
|
||||
|
||||
CVE: CVE-2026-88048
|
||||
Upstream-Status: Backport [https://github.com/tesseract-ocr/tesseract/commit/103dc134eb36411ddc6833ec20aa2c76795bd0ff]
|
||||
|
||||
Signed-off-by: Ankur Tyagi <ankur.tyagi85@gmail.com>
|
||||
---
|
||||
Makefile.am | 5 ++
|
||||
src/lstm/fullyconnected.cpp | 11 ++-
|
||||
src/lstm/weightmatrix.h | 7 ++
|
||||
unittest/fullyconnected_test.cc | 115 ++++++++++++++++++++++++++++++++
|
||||
4 files changed, 137 insertions(+), 1 deletion(-)
|
||||
create mode 100644 unittest/fullyconnected_test.cc
|
||||
|
||||
diff --git a/Makefile.am b/Makefile.am
|
||||
index 1a0a6771..ea722409 100644
|
||||
--- a/Makefile.am
|
||||
+++ b/Makefile.am
|
||||
@@ -1187,6 +1187,7 @@ if !DISABLED_LEGACY_ENGINE
|
||||
check_PROGRAMS += equationdetect_test
|
||||
endif # !DISABLED_LEGACY_ENGINE
|
||||
check_PROGRAMS += fileio_test
|
||||
+check_PROGRAMS += fullyconnected_test
|
||||
check_PROGRAMS += heap_test
|
||||
check_PROGRAMS += imagedata_test
|
||||
if !DISABLED_LEGACY_ENGINE
|
||||
@@ -1319,6 +1320,10 @@ fileio_test_SOURCES = unittest/fileio_test.cc
|
||||
fileio_test_CPPFLAGS = $(unittest_CPPFLAGS)
|
||||
fileio_test_LDADD = $(TRAINING_LIBS)
|
||||
|
||||
+fullyconnected_test_SOURCES = unittest/fullyconnected_test.cc
|
||||
+fullyconnected_test_CPPFLAGS = $(unittest_CPPFLAGS)
|
||||
+fullyconnected_test_LDADD = $(TESS_LIBS)
|
||||
+
|
||||
heap_test_SOURCES = unittest/heap_test.cc
|
||||
heap_test_CPPFLAGS = $(unittest_CPPFLAGS)
|
||||
heap_test_LDADD = $(TESS_LIBS)
|
||||
diff --git a/src/lstm/fullyconnected.cpp b/src/lstm/fullyconnected.cpp
|
||||
index 85989f40..380707a1 100644
|
||||
--- a/src/lstm/fullyconnected.cpp
|
||||
+++ b/src/lstm/fullyconnected.cpp
|
||||
@@ -121,7 +121,16 @@ bool FullyConnected::Serialize(TFile *fp) const {
|
||||
|
||||
// Reads from the given file. Returns false in case of error.
|
||||
bool FullyConnected::DeSerialize(TFile *fp) {
|
||||
- return weights_.DeSerialize(IsTraining(), fp);
|
||||
+ if (!weights_.DeSerialize(IsTraining(), fp)) {
|
||||
+ return false;
|
||||
+ }
|
||||
+ // The weight matrix must match the declared sizes (the second dimension
|
||||
+ // includes the bias column); otherwise Forward would read or write
|
||||
+ // outside the scratch buffers sized from ni_ and no_.
|
||||
+ if (weights_.Dim1() != no_ || weights_.Dim2() != ni_ + 1) {
|
||||
+ return false;
|
||||
+ }
|
||||
+ return true;
|
||||
}
|
||||
|
||||
// Runs forward propagation of activations on the input line.
|
||||
diff --git a/src/lstm/weightmatrix.h b/src/lstm/weightmatrix.h
|
||||
index a2cdaa52..66b69dfa 100644
|
||||
--- a/src/lstm/weightmatrix.h
|
||||
+++ b/src/lstm/weightmatrix.h
|
||||
@@ -107,6 +107,13 @@ public:
|
||||
int NumOutputs() const {
|
||||
return int_mode_ ? wi_.dim1() : wf_.dim1();
|
||||
}
|
||||
+ // The dimensions of the active weight matrix (wi_ in int mode, else wf_).
|
||||
+ int Dim1() const {
|
||||
+ return int_mode_ ? wi_.dim1() : wf_.dim1();
|
||||
+ }
|
||||
+ int Dim2() const {
|
||||
+ return int_mode_ ? wi_.dim2() : wf_.dim2();
|
||||
+ }
|
||||
// Provides one set of weights. Only used by peep weight maxpool.
|
||||
const TFloat *GetWeights(int index) const {
|
||||
return wf_[index];
|
||||
diff --git a/unittest/fullyconnected_test.cc b/unittest/fullyconnected_test.cc
|
||||
new file mode 100644
|
||||
index 00000000..f9697de4
|
||||
--- /dev/null
|
||||
+++ b/unittest/fullyconnected_test.cc
|
||||
@@ -0,0 +1,115 @@
|
||||
+///////////////////////////////////////////////////////////////////////
|
||||
+// File: fullyconnected_test.cc
|
||||
+// Description: Tests that a FullyConnected (softmax) network layer with
|
||||
+// weight-matrix dimensions that do not match the declared
|
||||
+// ni/no is rejected at load. Without the check,
|
||||
+// MatrixDotVector writes w.dim1() results into a scratch
|
||||
+// buffer sized from no_ and reads w.dim2()-1 inputs from
|
||||
+// a buffer sized from ni_ (heap out-of-bounds write/read)
|
||||
+// on the first recognition step.
|
||||
+//
|
||||
+// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
+// you may not use this file except in compliance with the License.
|
||||
+// You may obtain a copy of the License at
|
||||
+// http://www.apache.org/licenses/LICENSE-2.0
|
||||
+//
|
||||
+///////////////////////////////////////////////////////////////////////
|
||||
+
|
||||
+#include "include_gunit.h"
|
||||
+
|
||||
+#include "network.h" // for Network, NetworkType
|
||||
+#include "networkio.h"
|
||||
+#include "networkscratch.h"
|
||||
+#include "serialis.h" // for TFile
|
||||
+
|
||||
+#include <cstdint>
|
||||
+#include <vector>
|
||||
+
|
||||
+namespace tesseract {
|
||||
+namespace {
|
||||
+
|
||||
+// Appends raw little-endian values to a byte buffer.
|
||||
+class ByteWriter {
|
||||
+public:
|
||||
+ void PutU8(uint32_t v) { data_.push_back(static_cast<char>(v & 0xFF)); }
|
||||
+ void PutU32(uint32_t v) {
|
||||
+ for (int i = 0; i < 4; ++i) {
|
||||
+ data_.push_back(static_cast<char>((v >> (8 * i)) & 0xFF));
|
||||
+ }
|
||||
+ }
|
||||
+ void PutS32(int32_t v) { PutU32(static_cast<uint32_t>(v)); }
|
||||
+ const std::vector<char> &data() const { return data_; }
|
||||
+
|
||||
+private:
|
||||
+ std::vector<char> data_;
|
||||
+};
|
||||
+
|
||||
+void PutDoubleLE(ByteWriter *w, double d) {
|
||||
+ union {
|
||||
+ double d;
|
||||
+ uint64_t u;
|
||||
+ } conv;
|
||||
+ conv.d = d;
|
||||
+ w->PutU32(static_cast<uint32_t>(conv.u & 0xFFFFFFFF));
|
||||
+ w->PutU32(static_cast<uint32_t>(conv.u >> 32));
|
||||
+}
|
||||
+
|
||||
+// Builds a serialized NT_SOFTMAX network: header with the given ni/no,
|
||||
+// then a float-mode WeightMatrix with the given (corrupt) dimensions.
|
||||
+// The matrix is stored as doubles on disk (see WeightMatrix::DeSerialize).
|
||||
+std::vector<char> MakeSoftmaxNetwork(int ni, int no, int32_t dim1, int32_t dim2) {
|
||||
+ ByteWriter w;
|
||||
+ w.PutU8(static_cast<uint32_t>(NT_SOFTMAX));
|
||||
+ w.PutU8(0); // training: TS_DISABLED
|
||||
+ w.PutU8(0); // needs_to_backprop
|
||||
+ w.PutU32(0); // network_flags
|
||||
+ w.PutU32(static_cast<uint32_t>(ni));
|
||||
+ w.PutU32(static_cast<uint32_t>(no));
|
||||
+ w.PutU32(0); // num_weights (not cross-checked, kept consistent anyway)
|
||||
+ w.PutU32(0); // name (empty string)
|
||||
+ // WeightMatrix::DeSerialize:
|
||||
+ w.PutU8(128); // mode: kDoubleFlag, float mode
|
||||
+ w.PutS32(dim1);
|
||||
+ w.PutS32(dim2);
|
||||
+ PutDoubleLE(&w, 0.0); // empty_ cell
|
||||
+ for (int32_t i = 0; i < dim1 * dim2; ++i) {
|
||||
+ PutDoubleLE(&w, 0.0); // weight data
|
||||
+ }
|
||||
+ return w.data();
|
||||
+}
|
||||
+
|
||||
+// A FullyConnected layer whose weight matrix does not match the declared
|
||||
+// sizes must be rejected by CreateFromFile; on unpatched code the test
|
||||
+// reaches Forward, where MatrixDotVector performs the out-of-bounds
|
||||
+// write this regression test guards against.
|
||||
+TEST(FullyconnectedTest, RejectsWeightMatrixDimensionMismatch) {
|
||||
+ // ni_=no_=1 but dim1=3 (OOB write of 3 results into a 1-result buffer)
|
||||
+ // and dim2=5 (OOB read of 4 inputs from a 1-input buffer).
|
||||
+ std::vector<char> bytes = MakeSoftmaxNetwork(1, 1, 3, 5);
|
||||
+ TFile fp;
|
||||
+ ASSERT_TRUE(fp.Open(bytes.data(), bytes.size()));
|
||||
+ Network *net = Network::CreateFromFile(&fp);
|
||||
+ if (net == nullptr) {
|
||||
+ return; // Fixed: the mismatched layer is rejected at load.
|
||||
+ }
|
||||
+ NetworkIO input;
|
||||
+ input.Resize2d(false, /*width=*/1, /*num_features=*/1);
|
||||
+ NetworkScratch scratch;
|
||||
+ NetworkIO output;
|
||||
+ net->Forward(false, input, nullptr, &scratch, &output);
|
||||
+ delete net;
|
||||
+ FAIL() << "crafted FullyConnected layer with mismatched weight matrix was accepted";
|
||||
+}
|
||||
+
|
||||
+// Consistent dimensions must still be accepted.
|
||||
+TEST(FullyconnectedTest, AcceptsMatchingDimensions) {
|
||||
+ std::vector<char> bytes = MakeSoftmaxNetwork(1, 2, 2, 2);
|
||||
+ TFile fp;
|
||||
+ ASSERT_TRUE(fp.Open(bytes.data(), bytes.size()));
|
||||
+ Network *net = Network::CreateFromFile(&fp);
|
||||
+ ASSERT_NE(net, nullptr);
|
||||
+ delete net;
|
||||
+}
|
||||
+
|
||||
+} // namespace
|
||||
+} // namespace tesseract
|
||||
@@ -11,6 +11,7 @@ SRC_URI = "git://github.com/${BPN}-ocr/${BPN}.git;branch=main;protocol=https;tag
|
||||
file://CVE-2026-73067-1.patch \
|
||||
file://CVE-2026-73067-2.patch \
|
||||
file://CVE-2026-88052.patch \
|
||||
file://CVE-2026-88048.patch \
|
||||
"
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user