P1.6: reject oversize export dimensions instead of overflowing

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Andrew Frahn
2026-10-10 12:20:23 +11:00
co-authored by Claude Opus 5.5
parent 82ddd95167
commit e0b279ad8e
4 changed files with 30 additions and 1 deletions
+1
View File
@@ -1256,6 +1256,7 @@ impl crate::Session {
space: dac_pipeline::OutputSpace,
depth: dac_pipeline::OutputDepth,
) -> Result<RenderJob, String> {
dac_pipeline::check_output_size(max_w, max_h)?;
let mut job = self.render_job(id, max_w, max_h, false, true).ok_or("no such photo")?;
// An export is made from the denoised picture its Denoise amount asks for, made when the job runs if the
// background queue has not got to it. (A source kept in memory may lack the picture, so the job reads the file.)
+10
View File
@@ -183,3 +183,13 @@ fn pan_at_one_to_one_on_45_megapixels() {
);
}
}
/// An absurd export size is refused, not a multiply overflow (it panicked before the fix).
#[test]
fn huge_export_size_is_an_error() {
let mut s = Session::with_demo();
let id = s.catalog.photos().next().unwrap().id;
let huge = usize::MAX >> 8;
assert!(s.render_export(id, huge, huge, OutputSpace::Srgb, OutputDepth::U8).is_err());
assert!(s.render_export(id, 70_000, 10, OutputSpace::Srgb, OutputDepth::U8).is_err());
}
+4 -1
View File
@@ -564,7 +564,10 @@ pub fn render(
};
let plan = dac_pipeline::plan(src, info, s, req);
let (w, h) = (plan.w, plan.h);
let n = w * h;
let Some(n) = w.checked_mul(h).filter(|n| n.checked_mul(12).is_some()) else {
fail(FailKind::Limit, format!("{w}×{h} output is too large"));
return None;
};
let _limit = match fault {
Some(crate::Fault::Limit(bytes)) => Some(crate::ctx::LimitOverride::new(bytes)),
_ => None,
+15
View File
@@ -156,6 +156,21 @@ impl PixelWindow {
}
}
/// The longest output side a render may be asked for (TIFF/PNG/JPEG all fit within it).
pub const MAX_OUTPUT_SIDE: usize = 65_535;
/// The most output pixels a render may be asked for (the box it fits into, ≈ 1.07 Gpx).
pub const MAX_OUTPUT_PIXELS: usize = 1 << 30;
/// Rejects an output box past [`MAX_OUTPUT_SIDE`] or [`MAX_OUTPUT_PIXELS`] before anything is
/// sized from it.
pub fn check_output_size(max_w: usize, max_h: usize) -> Result<(), String> {
let pixels = max_w.checked_mul(max_h);
if max_w > MAX_OUTPUT_SIDE || max_h > MAX_OUTPUT_SIDE || pixels.is_none_or(|n| n > MAX_OUTPUT_PIXELS) {
return Err(format!("output size {max_w}×{max_h} is too large (at most {MAX_OUTPUT_SIDE} px per side and {MAX_OUTPUT_PIXELS} pixels)"));
}
Ok(())
}
impl RenderRequest {
pub fn fit(max_w: usize, max_h: usize) -> Self {
Self {