mirror of
https://github.com/storytold/photocraft.git
synced 2026-10-24 01:47:08 -06:00
security(codecs): stop quadratic rescans in XMP value scanning
xmp_values advanced its cursor only past the matched key, so the value-closing search (find(close)) rescanned the remaining packet for every property-like substring. Repeating text such as ' tiff:ResolutionUnit>' inside the packet produced quadratic CPU work even when no real resolution property existed. Advance the cursor past each located value; a missing close delimiter means the value is unterminated, so no later value can parse and the scan stops. Valid multi-value extraction is unchanged. Finding: 6ce55e33-27f0-4fb9-a22f-737382e1d328 (CWE-407)
This commit is contained in:
@@ -429,8 +429,14 @@ fn xmp_values(xmp: &str, key: &str) -> Vec<(usize, usize)> {
|
||||
continue;
|
||||
};
|
||||
let start = xmp.len() - value.len();
|
||||
let Some(len) = value.find(close) else { continue };
|
||||
// Advance past this value before searching for the next key. Without this, `from`
|
||||
// only moved past the key text, so property-like text repeated inside an unrelated
|
||||
// attribute re-ran this full-suffix `find(close)` for every occurrence, which is
|
||||
// quadratic in the packet size. A missing close means the value is unterminated, so
|
||||
// no later value can parse either; stop scanning in that case.
|
||||
let Some(len) = value.find(close) else { break };
|
||||
out.push((start, start + len));
|
||||
from = start + len + close.len_utf8();
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
@@ -284,6 +284,17 @@ fn truncated_metadata_never_panics_and_falls_back() {
|
||||
assert_eq!(xmp_resolution(x), None, "{x}");
|
||||
let _ = xmp_with_resolution(x, (1.0, 1.0));
|
||||
}
|
||||
|
||||
// Property-like text repeated inside the packet must not trigger a full-suffix rescan
|
||||
// per occurrence (it used to be quadratic). With the cursor advancing past each located
|
||||
// value, this completes well under a second even with many decoys; a timeout here would
|
||||
// indicate the quadratic scan returned. A clean packet still extracts correctly.
|
||||
let start = std::time::Instant::now();
|
||||
let decoys = " tiff:ResolutionUnit>".repeat(50_000);
|
||||
let _ = xmp_resolution(&decoys);
|
||||
assert!(start.elapsed() < std::time::Duration::from_secs(2), "xmp scan took too long: {:?}", start.elapsed());
|
||||
let clean = "<tiff:XResolution>300/1</tiff:XResolution><tiff:YResolution>300/1</tiff:YResolution><tiff:ResolutionUnit>2</tiff:ResolutionUnit>";
|
||||
near(xmp_resolution(clean), 300.0);
|
||||
}
|
||||
|
||||
proptest! {
|
||||
|
||||
Reference in New Issue
Block a user