mirror of
https://github.com/openembedded/meta-openembedded.git
synced 2026-10-22 20:38:28 +00:00
tesseract: patch CVE-2026-88047
Details: https://nvd.nist.gov/vuln/detail/cve-2026-88047 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
4aab0847c2
commit
b8fa7aac91
2 files changed
+227
No files matched your search
@@ -0,0 +1,226 @@
|
|||||||
|
From a6e329ff576ad4a90952f08359dbffab7e480157 Mon Sep 17 00:00:00 2001
|
||||||
|
From: Stefan Weil <sw@weilnetz.de>
|
||||||
|
Date: Mon, 24 Aug 2026 13:39:22 +0200
|
||||||
|
Subject: [PATCH] Limit unichar extraction in ReadNormProtos to the buffer size
|
||||||
|
|
||||||
|
Classify::ReadNormProtos parsed each normproto line with
|
||||||
|
`stream >> unichar >> NumProtos` into a char unichar[2 * UNICHAR_LEN + 1]
|
||||||
|
stack buffer, but char* extraction from an istream has no length limit
|
||||||
|
(the stream width was never set). A crafted TESSDATA_NORMPROTO component
|
||||||
|
in a .traineddata file whose first proto-line token exceeds 60
|
||||||
|
characters (the 100-byte line buffer allows up to 99) overflows the
|
||||||
|
stack buffer during legacy engine initialization (CWE-121).
|
||||||
|
|
||||||
|
Toolchain note: Apple's libc++ provides a C++20 array overload of
|
||||||
|
operator>>(basic_istream&, char(&)[N]) that implicitly bounds the
|
||||||
|
extraction to the array size, so builds against that standard library
|
||||||
|
are incidentally protected. Standard libraries without that overload
|
||||||
|
(e.g. libstdc++) still take the unbounded char* overload, so the
|
||||||
|
explicit width limit below makes the behavior defined on all
|
||||||
|
toolchains.
|
||||||
|
|
||||||
|
Key changes:
|
||||||
|
- normmatch.cpp: read the unichar token with
|
||||||
|
std::setw(2 * UNICHAR_LEN + 1); char* extraction takes at most
|
||||||
|
width - 1 characters, which fits the buffer exactly. Overlong
|
||||||
|
tokens are truncated and the line is rejected like any other
|
||||||
|
unparseable line.
|
||||||
|
- unittest: add normproto_test covering a 99-character token (the
|
||||||
|
maximum a 100-byte line can hold), a token of exactly 2 *
|
||||||
|
UNICHAR_LEN characters, and a well-formed component. A minimal
|
||||||
|
reproduction of the unbounded extraction crashes an ASan build
|
||||||
|
with a stack-buffer-overflow.
|
||||||
|
|
||||||
|
Reported-by: Tristan Madani <tristan@talencesecurity.com>
|
||||||
|
Assisted-by: OpenCode / qwen3.8-27b-thinking (Alibaba Cloud)
|
||||||
|
Signed-off-by: Stefan Weil <sw@weilnetz.de>
|
||||||
|
(cherry picked from commit 1bda5079b1c8a7e25f523486837426903d29ce84)
|
||||||
|
|
||||||
|
CVE: CVE-2026-88047
|
||||||
|
Upstream-Status: Backport [https://github.com/tesseract-ocr/tesseract/commit/1bda5079b1c8a7e25f523486837426903d29ce84]
|
||||||
|
|
||||||
|
Signed-off-by: Ankur Tyagi <ankur.tyagi85@gmail.com>
|
||||||
|
---
|
||||||
|
Makefile.am | 5 ++
|
||||||
|
src/classify/normmatch.cpp | 6 +-
|
||||||
|
unittest/CMakeLists.txt | 1 +
|
||||||
|
unittest/normproto_test.cc | 111 +++++++++++++++++++++++++++++++++++++
|
||||||
|
4 files changed, 122 insertions(+), 1 deletion(-)
|
||||||
|
create mode 100644 unittest/normproto_test.cc
|
||||||
|
|
||||||
|
diff --git a/Makefile.am b/Makefile.am
|
||||||
|
index c1491263..48e7dcbc 100644
|
||||||
|
--- a/Makefile.am
|
||||||
|
+++ b/Makefile.am
|
||||||
|
@@ -1213,6 +1213,7 @@ check_PROGRAMS += mastertrainer_test
|
||||||
|
endif # !DISABLED_LEGACY_ENGINE
|
||||||
|
check_PROGRAMS += matrix_test
|
||||||
|
check_PROGRAMS += networkio_test
|
||||||
|
+check_PROGRAMS += normproto_test
|
||||||
|
if ENABLE_TRAINING
|
||||||
|
check_PROGRAMS += normstrngs_test
|
||||||
|
endif # ENABLE_TRAINING
|
||||||
|
@@ -1417,6 +1418,10 @@ networkio_test_SOURCES = unittest/networkio_test.cc
|
||||||
|
networkio_test_CPPFLAGS = $(unittest_CPPFLAGS)
|
||||||
|
networkio_test_LDADD = $(TESS_LIBS)
|
||||||
|
|
||||||
|
+normproto_test_SOURCES = unittest/normproto_test.cc
|
||||||
|
+normproto_test_CPPFLAGS = $(unittest_CPPFLAGS)
|
||||||
|
+normproto_test_LDADD = $(TESS_LIBS)
|
||||||
|
+
|
||||||
|
normstrngs_test_SOURCES = unittest/normstrngs_test.cc
|
||||||
|
normstrngs_test_CPPFLAGS = $(unittest_CPPFLAGS)
|
||||||
|
normstrngs_test_LDADD = $(TRAINING_LIBS) $(ICU_I18N_LIBS) $(ICU_UC_LIBS)
|
||||||
|
diff --git a/src/classify/normmatch.cpp b/src/classify/normmatch.cpp
|
||||||
|
index d5bd7e6a..1ce7c928 100644
|
||||||
|
--- a/src/classify/normmatch.cpp
|
||||||
|
+++ b/src/classify/normmatch.cpp
|
||||||
|
@@ -28,6 +28,7 @@
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdio>
|
||||||
|
+#include <iomanip> // for std::setw
|
||||||
|
#include <sstream> // for std::istringstream
|
||||||
|
|
||||||
|
namespace tesseract {
|
||||||
|
@@ -190,7 +191,10 @@ NORM_PROTOS *Classify::ReadNormProtos(TFile *fp) {
|
||||||
|
while (fp->FGets(line, kMaxLineSize) != nullptr) {
|
||||||
|
std::istringstream stream(line);
|
||||||
|
stream.imbue(std::locale::classic());
|
||||||
|
- stream >> unichar >> NumProtos;
|
||||||
|
+ // unichar holds at most 2 * UNICHAR_LEN characters; the width limit
|
||||||
|
+ // (width - 1 characters for char* extraction) keeps the extraction
|
||||||
|
+ // from overflowing the buffer on overlong lines.
|
||||||
|
+ stream >> std::setw(2 * UNICHAR_LEN + 1) >> unichar >> NumProtos;
|
||||||
|
if (stream.fail()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
diff --git a/unittest/CMakeLists.txt b/unittest/CMakeLists.txt
|
||||||
|
index 6a91c62f..b65cf922 100644
|
||||||
|
--- a/unittest/CMakeLists.txt
|
||||||
|
+++ b/unittest/CMakeLists.txt
|
||||||
|
@@ -61,6 +61,7 @@ set(LEGACY_TESTS
|
||||||
|
indexmapbidi_test.cc
|
||||||
|
intfeaturemap_test.cc
|
||||||
|
mastertrainer_test.cc
|
||||||
|
+ normproto_test.cc
|
||||||
|
osd_test.cc
|
||||||
|
params_model_test.cc
|
||||||
|
shapetable_test.cc)
|
||||||
|
diff --git a/unittest/normproto_test.cc b/unittest/normproto_test.cc
|
||||||
|
new file mode 100644
|
||||||
|
index 00000000..574b2f3d
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/unittest/normproto_test.cc
|
||||||
|
@@ -0,0 +1,111 @@
|
||||||
|
+///////////////////////////////////////////////////////////////////////
|
||||||
|
+// File: normproto_test.cc
|
||||||
|
+// Description: Tests that Classify::ReadNormProtos handles a normproto
|
||||||
|
+// line whose first (unichar) token exceeds the
|
||||||
|
+// unichar[2 * UNICHAR_LEN + 1] stack buffer. The
|
||||||
|
+// istream extraction has no intrinsic length limit, so a
|
||||||
|
+// crafted NORMPROTO component in a .traineddata file
|
||||||
|
+// could overflow the stack buffer during legacy engine
|
||||||
|
+// initialization.
|
||||||
|
+//
|
||||||
|
+// 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 "classify.h"
|
||||||
|
+#include "serialis.h" // for TFile
|
||||||
|
+
|
||||||
|
+#include <cstdio>
|
||||||
|
+#include <cstdlib>
|
||||||
|
+#include <string>
|
||||||
|
+#include <vector>
|
||||||
|
+
|
||||||
|
+namespace tesseract {
|
||||||
|
+namespace {
|
||||||
|
+
|
||||||
|
+// Minimal unicharset (space and 'a').
|
||||||
|
+const char kMinUnicharset[] =
|
||||||
|
+ "2\n"
|
||||||
|
+ "NULL 1 0,255,0,255,0,0,0,0,0,0 Latin 2 0 2\n"
|
||||||
|
+ "a 1 0,255,0,255,0,0,0,0,0,0 Latin 2 0 2\n";
|
||||||
|
+
|
||||||
|
+// Builds a normproto component: a sample-size line (5), five parameter
|
||||||
|
+// description lines, then the given raw proto lines.
|
||||||
|
+std::vector<char> MakeNormproto(const std::string &lines) {
|
||||||
|
+ std::string data = "5\n";
|
||||||
|
+ for (int i = 0; i < 5; ++i) {
|
||||||
|
+ data += "e e 0 1\n";
|
||||||
|
+ }
|
||||||
|
+ data += lines;
|
||||||
|
+ return std::vector<char>(data.begin(), data.end());
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+class NormprotoTest : public testing::Test {
|
||||||
|
+protected:
|
||||||
|
+ void SetUp() override {
|
||||||
|
+ tmpl_ = "/tmp/tess_normproto_test_XXXXXX";
|
||||||
|
+ char *dir = mkdtemp(tmpl_.data());
|
||||||
|
+ ASSERT_NE(dir, nullptr);
|
||||||
|
+ dir_ = dir;
|
||||||
|
+ std::string uc_path = dir_ + "/eng.unicharset";
|
||||||
|
+ FILE *f = fopen(uc_path.c_str(), "w");
|
||||||
|
+ ASSERT_NE(f, nullptr);
|
||||||
|
+ ASSERT_EQ(fwrite(kMinUnicharset, 1, sizeof(kMinUnicharset) - 1, f),
|
||||||
|
+ sizeof(kMinUnicharset) - 1);
|
||||||
|
+ fclose(f);
|
||||||
|
+ // Load the minimal unicharset into the classifier's inherited
|
||||||
|
+ // unicharset member.
|
||||||
|
+ ASSERT_TRUE(classifier_.unicharset.load_from_file(uc_path.c_str()));
|
||||||
|
+ }
|
||||||
|
+ void TearDown() override {
|
||||||
|
+ std::remove((dir_ + "/eng.unicharset").c_str());
|
||||||
|
+ rmdir(dir_.c_str());
|
||||||
|
+ }
|
||||||
|
+ std::string dir_;
|
||||||
|
+ std::string tmpl_;
|
||||||
|
+ Classify classifier_;
|
||||||
|
+};
|
||||||
|
+
|
||||||
|
+// A 99-character first token (the maximum FGets can return) overflows
|
||||||
|
+// unichar[2 * UNICHAR_LEN + 1] on unpatched code; the width-limited
|
||||||
|
+// extraction must reject the line instead.
|
||||||
|
+TEST_F(NormprotoTest, ToleratesOverlongUnicharToken) {
|
||||||
|
+ std::vector<char> bytes = MakeNormproto(std::string(99, 'A') + "\n");
|
||||||
|
+ TFile fp;
|
||||||
|
+ ASSERT_TRUE(fp.Open(bytes.data(), bytes.size()));
|
||||||
|
+ classifier_.NormProtos = classifier_.ReadNormProtos(&fp);
|
||||||
|
+ ASSERT_NE(classifier_.NormProtos, nullptr);
|
||||||
|
+ classifier_.FreeNormProtos();
|
||||||
|
+ EXPECT_EQ(classifier_.NormProtos, nullptr);
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+// A token of exactly 2 * UNICHAR_LEN characters is the maximum legitimate
|
||||||
|
+// size; it must not be truncated or rejected by the width limit.
|
||||||
|
+TEST_F(NormprotoTest, ToleratesMaxLenUnicharToken) {
|
||||||
|
+ std::vector<char> bytes = MakeNormproto(std::string(2 * UNICHAR_LEN, 'A') + " 0\n");
|
||||||
|
+ TFile fp;
|
||||||
|
+ ASSERT_TRUE(fp.Open(bytes.data(), bytes.size()));
|
||||||
|
+ classifier_.NormProtos = classifier_.ReadNormProtos(&fp);
|
||||||
|
+ ASSERT_NE(classifier_.NormProtos, nullptr);
|
||||||
|
+ classifier_.FreeNormProtos();
|
||||||
|
+ EXPECT_EQ(classifier_.NormProtos, nullptr);
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+// A well-formed normproto component must still parse.
|
||||||
|
+TEST_F(NormprotoTest, ReadsValidNormprotos) {
|
||||||
|
+ std::vector<char> bytes = MakeNormproto("a 0\n");
|
||||||
|
+ TFile fp;
|
||||||
|
+ ASSERT_TRUE(fp.Open(bytes.data(), bytes.size()));
|
||||||
|
+ classifier_.NormProtos = classifier_.ReadNormProtos(&fp);
|
||||||
|
+ ASSERT_NE(classifier_.NormProtos, nullptr);
|
||||||
|
+ classifier_.FreeNormProtos();
|
||||||
|
+ EXPECT_EQ(classifier_.NormProtos, nullptr);
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+} // namespace
|
||||||
|
+} // namespace tesseract
|
||||||
@@ -14,6 +14,7 @@ SRC_URI = "git://github.com/${BPN}-ocr/${BPN}.git;branch=main;protocol=https;tag
|
|||||||
file://CVE-2026-88048.patch \
|
file://CVE-2026-88048.patch \
|
||||||
file://CVE-2026-88049.patch \
|
file://CVE-2026-88049.patch \
|
||||||
file://CVE-2026-88050.patch \
|
file://CVE-2026-88050.patch \
|
||||||
|
file://CVE-2026-88047.patch \
|
||||||
"
|
"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user