From: ankur.tyagi85@gmail.com
To: openembedded-devel@lists.openembedded.org
Cc: Ankur Tyagi <ankur.tyagi85@gmail.com>
Subject: [oe][meta-oe][wrynose][PATCH 20/24] tesseract: patch CVE-2026-88047
Date: Thu, 24 Sep 2026 16:33:10 +1200 [thread overview]
Message-ID: <20260924043315.1663186-20-ankur.tyagi85@gmail.com> (raw)
In-Reply-To: <20260924043315.1663186-1-ankur.tyagi85@gmail.com>
From: Ankur Tyagi <ankur.tyagi85@gmail.com>
Details:
https://nvd.nist.gov/vuln/detail/cve-2026-88047
Signed-off-by: Ankur Tyagi <ankur.tyagi85@gmail.com>
---
.../tesseract/tesseract/CVE-2026-88047.patch | 226 ++++++++++++++++++
.../tesseract/tesseract_5.5.2.bb | 1 +
2 files changed, 227 insertions(+)
create mode 100644 meta-oe/recipes-graphics/tesseract/tesseract/CVE-2026-88047.patch
diff --git a/meta-oe/recipes-graphics/tesseract/tesseract/CVE-2026-88047.patch b/meta-oe/recipes-graphics/tesseract/tesseract/CVE-2026-88047.patch
new file mode 100644
index 0000000000..04e9953d2a
--- /dev/null
+++ b/meta-oe/recipes-graphics/tesseract/tesseract/CVE-2026-88047.patch
@@ -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
diff --git a/meta-oe/recipes-graphics/tesseract/tesseract_5.5.2.bb b/meta-oe/recipes-graphics/tesseract/tesseract_5.5.2.bb
index 756e780659..61cb1f7cad 100644
--- a/meta-oe/recipes-graphics/tesseract/tesseract_5.5.2.bb
+++ b/meta-oe/recipes-graphics/tesseract/tesseract_5.5.2.bb
@@ -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-88049.patch \
file://CVE-2026-88050.patch \
+ file://CVE-2026-88047.patch \
"
next prev parent reply other threads:[~2026-09-24 4:34 UTC|newest]
Thread overview: 28+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-24 4:32 [oe][meta-oe][wrynose][PATCH 1/24] rabbitmq-c: patch CVE-2026-44235 ankur.tyagi85
2026-09-24 4:32 ` [oe][meta-oe][wrynose][PATCH 2/24] rabbitmq-c: patch CVE-2026-44236 ankur.tyagi85
2026-09-24 4:32 ` [oe][meta-oe][wrynose][PATCH 3/24] redis: patch CVE-2026-81934 ankur.tyagi85
2026-09-24 4:32 ` [oe][meta-networking][wrynose][PATCH 4/24] sngrep: patch CVE-2026-90558 ankur.tyagi85
2026-09-24 4:32 ` [oe][meta-networking][wrynose][PATCH 5/24] strongswan: patch CVE-2026-78123 ankur.tyagi85
2026-09-24 4:32 ` [oe][meta-networking][wrynose][PATCH 6/24] strongswan: patch CVE-2026-78124 ankur.tyagi85
2026-09-24 4:32 ` [oe][meta-networking][wrynose][PATCH 7/24] strongswan: patch CVE-2026-78126 ankur.tyagi85
2026-09-24 4:32 ` [oe][meta-networking][wrynose][PATCH 8/24] strongswan: patch CVE-2026-78127 ankur.tyagi85
2026-09-24 4:32 ` [oe][meta-networking][wrynose][PATCH 9/24] strongswan: patch CVE-2026-78129 ankur.tyagi85
2026-09-24 4:33 ` [oe][meta-networking][wrynose][PATCH 10/24] strongswan: patch CVE-2026-78133 ankur.tyagi85
2026-09-24 4:33 ` [oe][meta-networking][wrynose][PATCH 11/24] strongswan: patch CVE-2026-78131 ankur.tyagi85
2026-09-24 4:33 ` [oe][meta-networking][wrynose][PATCH 12/24] strongswan: patch CVE-2026-78130 ankur.tyagi85
2026-10-05 9:49 ` Anuj Mittal
2026-10-06 20:25 ` Ankur Tyagi
2026-09-24 4:33 ` [oe][meta-networking][wrynose][PATCH 13/24] strongswan: patch CVE-2026-78132 ankur.tyagi85
2026-09-24 4:33 ` [oe][meta-networking][wrynose][PATCH 14/24] strongswan: patch CVE-2026-78135 ankur.tyagi85
2026-09-24 4:33 ` [oe][meta-networking][wrynose][PATCH 15/24] strongswan: patch CVE-2026-78134 ankur.tyagi85
2026-09-24 4:33 ` [oe][meta-oe][wrynose][PATCH 16/24] tesseract: patch CVE-2026-88052 ankur.tyagi85
2026-09-24 4:33 ` [oe][meta-oe][wrynose][PATCH 17/24] tesseract: patch CVE-2026-88048 ankur.tyagi85
2026-09-24 4:33 ` [oe][meta-oe][wrynose][PATCH 18/24] tesseract: patch CVE-2026-88049 ankur.tyagi85
2026-09-24 4:33 ` [oe][meta-oe][wrynose][PATCH 19/24] tesseract: patch CVE-2026-88050 ankur.tyagi85
2026-09-24 4:33 ` ankur.tyagi85 [this message]
2026-09-24 4:33 ` [oe][meta-oe][wrynose][PATCH 21/24] tesseract: patch CVE-2026-88051 ankur.tyagi85
2026-09-24 4:33 ` [oe][meta-oe][wrynose][PATCH 22/24] tesseract: patch CVE-2026-88054 ankur.tyagi85
2026-09-24 4:33 ` [oe][meta-oe][wrynose][PATCH 23/24] valkey: upgrade 9.0.5 -> 9.0.6 ankur.tyagi85
2026-09-24 4:33 ` [oe][meta-oe][wrynose][PATCH 24/24] valkey: mark CVE-2026-86227 fixed ankur.tyagi85
2026-10-06 4:19 ` [oe][meta-oe][wrynose][PATCH 1/24] rabbitmq-c: patch CVE-2026-44235 Anuj Mittal
2026-10-06 20:27 ` Ankur Tyagi
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260924043315.1663186-20-ankur.tyagi85@gmail.com \
--to=ankur.tyagi85@gmail.com \
--cc=openembedded-devel@lists.openembedded.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.