mirror of
https://github.com/openembedded/meta-openembedded.git
synced 2026-10-15 18:27:12 +00:00
tesseract: patch CVE-2026-88047
Details:
https://nvd.nist.gov/vuln/detail/cve-2026-88047
(cherry picked from commit b8fa7aac91)
Signed-off-by: Ankur Tyagi <ankur.tyagi85@gmail.com>
Signed-off-by: Anuj Mittal <anuj.mittal@oss.qualcomm.com>
Signed-off-by: Ankur Tyagi <ankur.tyagi85@gmail.com>
Signed-off-by: Khem Raj <khem.raj@oss.qualcomm.com>
This commit is contained in:
1 parent
cc2b58f135
commit
2ba3ed5dc8
2 files changed
+226
No files matched your search
@@ -0,0 +1,225 @@
|
||||
From e974684acbe3aeb6bc6192b06a048537c345b713 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 63ca6686..cc748455 100644
|
||||
--- a/Makefile.am
|
||||
+++ b/Makefile.am
|
||||
@@ -1184,6 +1184,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
|
||||
@@ -1388,6 +1389,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 a660c9b1..4c677a60 100644
|
||||
--- a/unittest/CMakeLists.txt
|
||||
+++ b/unittest/CMakeLists.txt
|
||||
@@ -59,6 +59,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
|
||||
@@ -11,6 +11,7 @@ SRC_URI = "git://github.com/${BPN}-ocr/${BPN}.git;branch=main;protocol=https;tag
|
||||
file://CVE-2026-88048.patch \
|
||||
file://CVE-2026-88049.patch \
|
||||
file://CVE-2026-88050.patch \
|
||||
file://CVE-2026-88047.patch \
|
||||
"
|
||||
|
||||
DEPENDS = "leptonica"
|
||||
|
||||
Reference in new issue
Block a user