Commit c789b5a5 for tesseract
commit c789b5a5d4f8652a32dc12bd47d822d7a949e1ed
Author: Stefan Weil <sw@weilnetz.de>
Date: Fri Nov 13 17:30:08 2026 +0100
textord: clip the ratio limits in compute_row_descdrop
compute_row_descdrop() converts row->xheight multiplied by
textord_ascx_ratio_min/max and textord_descx_ratio_min/max to int.
The conversion is undefined if the product is out of the int range,
which happens for a huge ratio from a config file. The loop which
counts the potential ascenders then ran over up to 2^31 indices for
each row.
Clip the ascender limits to the range of the histogram (they can only
narrow it) and the descender limits to +/-INT16_MAX before the
conversion. Nothing changes for values which were converted correctly
before.
Fix proposed by the Anthropic OSS Scanner (finding ANT-2026-LSPRDZR1).
Assisted-by: OpenCode / Claude Fable 5.2 (Anthropic)
Signed-off-by: Stefan Weil <sw@weilnetz.de>
diff --git a/src/textord/makerow.cpp b/src/textord/makerow.cpp
index d5d98022..414d884b 100644
--- a/src/textord/makerow.cpp
+++ b/src/textord/makerow.cpp
@@ -1571,20 +1571,33 @@ int compute_xheight_from_modes(STATS *heights, STATS *floating_heights, bool cap
int32_t compute_row_descdrop(TO_ROW *row, float gradient, int xheight_blob_count,
STATS *asc_heights) {
// Count how many potential ascenders are in this row.
+ // The ratio limits can only narrow the range of the histogram, so clip them
+ // to it before the conversion to int: the conversion is undefined for
+ // values out of range (e.g. a huge ratio from a config), and would
+ // otherwise leave the loop below running over billions of indices.
int i_min = asc_heights->min_bucket();
+ int i_max = asc_heights->max_bucket();
+ const double lowest = i_min - 1.0;
+ const double highest = i_max + 1.0;
if ((i_min / row->xheight) < textord_ascx_ratio_min) {
- i_min = static_cast<int>(floor(row->xheight * textord_ascx_ratio_min + 0.5));
+ i_min = static_cast<int>(
+ ClipToRange(floor(row->xheight * textord_ascx_ratio_min + 0.5), lowest, highest));
}
- int i_max = asc_heights->max_bucket();
if ((i_max / row->xheight) > textord_ascx_ratio_max) {
- i_max = static_cast<int>(floor(row->xheight * textord_ascx_ratio_max));
+ i_max = static_cast<int>(
+ ClipToRange(floor(row->xheight * textord_ascx_ratio_max), lowest, highest));
}
int num_potential_asc = 0;
for (int i = i_min; i <= i_max; ++i) {
num_potential_asc += asc_heights->pile_count(i);
}
- auto min_height = static_cast<int32_t>(floor(row->xheight * textord_descx_ratio_min + 0.5));
- auto max_height = static_cast<int32_t>(floor(row->xheight * textord_descx_ratio_max));
+ // Likewise clip the descender limits before the conversion. Blob heights
+ // are bounded by the page coordinates, so that is all the histogram needs.
+ const double max_coord = INT16_MAX;
+ auto min_height = static_cast<int32_t>(
+ ClipToRange(floor(row->xheight * textord_descx_ratio_min + 0.5), -max_coord, max_coord));
+ auto max_height = static_cast<int32_t>(
+ ClipToRange(floor(row->xheight * textord_descx_ratio_max), -max_coord, max_coord));
float xcentre; // centre of blob
float height; // height of blob
BLOBNBOX_IT blob_it = row->blob_list();