fix(plot-style): bound custom lineweight table keys to u8 range

Bug:
Custom lineweight table entries in a CTB are keyed by a number parsed
from the file and stored without an upper bound, but the only reader
(resolve_lineweight) indexes through entry.lineweight: u8. A key beyond
u8::MAX (e.g. 18446744073709551615 in a crafted file) caused an
out-of-bounds-style failure or a multi-GB table allocation when the
style was applied.

Reproduce:
1. Craft a CTB whose custom lineweight table uses a huge key
   (usize::MAX).
2. Apply the style or open plot settings.
3. Before: overflow panic (or a giant allocation).
4. After: the key is out of range, the line is ignored like any other
   malformed CTB line, and the style loads.

Fix:
Keys are bounded to u8::MAX when parsed - exactly the range the
u8-typed reader can ever use. In-range keys behave as before; keys 256
and above are dropped. Tests: a usize::MAX key does not overflow, an
in-range key is kept and applied, 256 is ignored.
This commit is contained in:
Karim Jerbi committed 2026-09-30 19:15:49 +01:00
1 parent ac6aa750f6
commit 790da218b5
1 file changed
+42 -3
+42 -3
View File
@@ -785,11 +785,19 @@ fn parse_plot_style_text(text: &str, name: String, is_stb: bool) -> Result<PlotS
_ => {}
},
Section::Lineweights => {
// The key is file-controlled: `18446744073709551615` made
// `index + 1` overflow (debug panic, release wrap to
// `resize(0)` then OOB), and `500000000` resized a 2 GB
// table. Entries past 255 are unreachable — the consumer
// indexes with `PlotStyleEntry.lineweight: u8` — so
// oversized keys are ignored like other malformed lines.
if let (Ok(index), Ok(weight)) = (key.parse::<usize>(), value.parse::<f32>()) {
if lineweights.len() <= index {
lineweights.resize(index + 1, 0.0);
if index <= u8::MAX as usize {
if lineweights.len() <= index {
lineweights.resize(index + 1, 0.0);
}
lineweights[index] = weight;
}
lineweights[index] = weight;
}
}
_ => {}
@@ -1088,3 +1096,34 @@ mod inflate_limit_tests {
assert!(error.contains("limit"), "{error}");
}
}
#[cfg(test)]
mod lineweight_table_tests {
use super::{parse_plot_style_text, LW_TABLE};
fn parse(text: &str) -> super::PlotStyleTable {
parse_plot_style_text(text, "crafted.ctb".into(), false).expect("parses")
}
/// The table key is file-controlled: `18446744073709551615` made
/// `index + 1` overflow — debug builds panic, release wraps to
/// `resize(0)` and then panics OOB at `lineweights[usize::MAX]` — and
/// `500000000` resized a 2 GB table (allocation abort). Entries past
/// 255 are unreachable anyway: `PlotStyleEntry.lineweight` is a `u8`.
#[test]
fn oversized_lineweight_table_keys_are_ignored() {
let table = parse("custom_lineweight_table{\n18446744073709551615=0.5\n}\n");
assert_eq!(table.lineweights, LW_TABLE.to_vec());
}
/// In-range keys still extend the table; 256 (beyond `u8`) is ignored.
#[test]
fn in_range_lineweight_table_keys_are_kept() {
let table = parse("custom_lineweight_table{\n30=0.7\n}\n");
assert_eq!(table.lineweights.len(), 31);
assert_eq!(table.lineweights[30], 0.7);
let table = parse("custom_lineweight_table{\n256=0.7\n}\n");
assert_eq!(table.lineweights, LW_TABLE.to_vec());
}
}