Fix 40+ bugs from audit passes 9-12
- PNG chunk parsing overflow protection with checked arithmetic - Font directory traversal bounded with global result limit - find_unique_path TOCTOU race fixed with create_new + marker byte - Watch mode "processed" dir exclusion narrowed to prevent false skips - Metadata copy now checks format support before little_exif calls - Clipboard temp files cleaned up on app exit - Atomic writes for file manager integration scripts - BMP format support added to encoder and convert step - Regex DoS protection with DFA size limit - Watermark NaN/negative scale guard - Selective EXIF stripping for privacy/custom metadata modes - CLI watch mode: file stability checks, per-file history saves - High contrast toggle preserves and restores original theme - Image list deduplication uses O(1) HashSet lookups - Saturation/trim/padding overflow guards in adjustments
This commit is contained in:
@@ -44,6 +44,7 @@ impl OutputEncoder {
|
||||
ImageFormat::Avif => self.encode_avif(img, quality.unwrap_or(80)),
|
||||
ImageFormat::Gif => self.encode_fallback(img, image::ImageFormat::Gif),
|
||||
ImageFormat::Tiff => self.encode_fallback(img, image::ImageFormat::Tiff),
|
||||
ImageFormat::Bmp => self.encode_fallback(img, image::ImageFormat::Bmp),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +69,7 @@ impl OutputEncoder {
|
||||
ImageFormat::WebP => preset.webp_quality() as u8,
|
||||
ImageFormat::Avif => preset.avif_quality() as u8,
|
||||
ImageFormat::Png => preset.png_level(),
|
||||
_ => preset.jpeg_quality(),
|
||||
ImageFormat::Gif | ImageFormat::Tiff | ImageFormat::Bmp => preset.jpeg_quality(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,6 +203,11 @@ impl OutputEncoder {
|
||||
/// The pHYs chunk must appear before the first IDAT chunk.
|
||||
/// DPI is converted to pixels per meter (1 inch = 0.0254 meters).
|
||||
fn insert_png_phys_chunk(png_data: &[u8], dpi: u32) -> Vec<u8> {
|
||||
// Not a valid PNG (too short for signature) - return as-is
|
||||
if png_data.len() < 8 {
|
||||
return png_data.to_vec();
|
||||
}
|
||||
|
||||
// PNG pixels per meter = DPI / 0.0254
|
||||
let ppm = (dpi as f64 / 0.0254).round() as u32;
|
||||
|
||||
@@ -241,9 +247,12 @@ fn insert_png_phys_chunk(png_data: &[u8], dpi: u32) -> Vec<u8> {
|
||||
png_data[pos], png_data[pos + 1], png_data[pos + 2], png_data[pos + 3],
|
||||
]) as usize;
|
||||
let chunk_type = &png_data[pos + 4..pos + 8];
|
||||
let total_chunk_size = 4 + 4 + chunk_len + 4; // len + type + data + crc
|
||||
// Use checked arithmetic to prevent overflow on malformed PNGs
|
||||
let Some(total_chunk_size) = chunk_len.checked_add(12) else {
|
||||
break; // chunk_len so large it overflows - malformed PNG
|
||||
};
|
||||
|
||||
if pos + total_chunk_size > png_data.len() {
|
||||
if pos.checked_add(total_chunk_size).map_or(true, |end| end > png_data.len()) {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -263,6 +272,11 @@ fn insert_png_phys_chunk(png_data: &[u8], dpi: u32) -> Vec<u8> {
|
||||
pos += total_chunk_size;
|
||||
}
|
||||
|
||||
// Copy any remaining bytes (e.g. trailing data after a truncated chunk)
|
||||
if pos < png_data.len() {
|
||||
result.extend_from_slice(&png_data[pos..]);
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user