Commit d20daaf2 for tesseract
commit d20daaf23ae4224643e76516868ff3b70da7ae6e
Author: Stefan Weil <sw@weilnetz.de>
Date: Fri Nov 13 17:31:00 2026 +0100
training: fix use after free in DeleteDeadSamples
TrainingSampleSet::DeleteDeadSamples() erased the entry of a dead
sample from the vector and then deleted *it. As erase() moves the
following entries down, that deleted the next (live) sample and left
a dangling pointer in the vector, while the dead sample was leaked.
shapeclustering and mftraining crashed.
Delete the sample first, and continue with the iterator which erase()
returns.
This was a regression from commit af131241a.
Fix proposed by the Anthropic OSS Scanner (finding ANT-2026-6OZE8ZWP).
Assisted-by: OpenCode / Claude Fable 5.2 (Anthropic)
Signed-off-by: Stefan Weil <sw@weilnetz.de>
diff --git a/src/training/common/trainingsampleset.cpp b/src/training/common/trainingsampleset.cpp
index d05459b0..d9f7adae 100644
--- a/src/training/common/trainingsampleset.cpp
+++ b/src/training/common/trainingsampleset.cpp
@@ -539,10 +539,12 @@ void TrainingSampleSet::KillSample(TrainingSample *sample) {
// Deletes all samples with zero features marked by KillSample.
void TrainingSampleSet::DeleteDeadSamples() {
using namespace std::placeholders; // for _1
- for (auto &&it = samples_.begin(); it < samples_.end();) {
+ for (auto it = samples_.begin(); it != samples_.end();) {
if (*it == nullptr || (*it)->class_id() < 0) {
- samples_.erase(it);
+ // Delete before erasing: erase shifts the following samples down, so
+ // *it would otherwise refer to the next (live) sample.
delete *it;
+ it = samples_.erase(it);
} else {
++it;
}