wuffs: protect against crafted images that cause overflows

Fixes #9579

Protect against panics caused by integer overflows by using functions
that allow integer overflows to be caught instead of causing a panic.

Also protect against DOS from images that might not cause an
overflow but do consume an absurd amount of memory by limiting
images to a maximum size of 4GiB (which is the maximum size of
`image-storage-limit`).
pull/9581/head
Jeffrey C. Ollie 2025-11-13 14:20:19 -06:00
parent 0f64b9a8e8
commit ec55cbc879
No known key found for this signature in database
GPG Key ID: 6F86035A6D97044E
7 changed files with 44 additions and 3 deletions

View File

@ -2,7 +2,7 @@ const std = @import("std");
const c = @import("c.zig").c;
pub const Error = std.mem.Allocator.Error || error{WuffsError};
pub const Error = std.mem.Allocator.Error || error{ WuffsError, Overflow };
pub fn check(log: anytype, status: *const c.struct_wuffs_base__status__struct) error{WuffsError}!void {
if (!c.wuffs_base__status__is_ok(status)) {

View File

@ -4,6 +4,8 @@ const c = @import("c.zig").c;
const Error = @import("error.zig").Error;
const check = @import("error.zig").check;
const ImageData = @import("main.zig").ImageData;
const maximum_image_size = @import("main.zig").maximum_image_size;
const mul = std.math.mul;
const log = std.log.scoped(.wuffs_jpeg);
@ -61,9 +63,20 @@ pub fn decode(alloc: Allocator, data: []const u8) Error!ImageData {
height,
);
const size: usize = try mul(
usize,
try mul(usize, width, height),
@sizeOf(c.wuffs_base__color_u32_argb_premul),
);
if (size > maximum_image_size) {
log.warn("image size {d} is larger than the maximum allowed ({d})", .{ size, maximum_image_size });
return error.Overflow;
}
const destination = try alloc.alloc(
u8,
width * height * @sizeOf(c.wuffs_base__color_u32_argb_premul),
size,
);
errdefer alloc.free(destination);
@ -131,3 +144,8 @@ test "jpeg_decode_FFFFFF" {
try std.testing.expectEqual(1, data.height);
try std.testing.expectEqualSlices(u8, &.{ 255, 255, 255, 255 }, data.data);
}
test "jpeg: too big" {
const data = decode(std.testing.allocator, @embedFile("too_big.jpg"));
try std.testing.expectError(error.Overflow, data);
}

View File

@ -5,6 +5,10 @@ pub const jpeg = @import("jpeg.zig");
pub const swizzle = @import("swizzle.zig");
pub const Error = @import("error.zig").Error;
/// The maximum image size, based on the 4G limit of Ghostty's
/// `image-storage-limit` config.
pub const maximum_image_size = 4 * 1024 * 1024 * 1024;
pub const ImageData = struct {
width: u32,
height: u32,

View File

@ -4,6 +4,8 @@ const c = @import("c.zig").c;
const Error = @import("error.zig").Error;
const check = @import("error.zig").check;
const ImageData = @import("main.zig").ImageData;
const maximum_image_size = @import("main.zig").maximum_image_size;
const mul = std.math.mul;
const log = std.log.scoped(.wuffs_png);
@ -61,9 +63,20 @@ pub fn decode(alloc: Allocator, data: []const u8) Error!ImageData {
height,
);
const size: usize = try mul(
usize,
try mul(usize, width, height),
@sizeOf(c.wuffs_base__color_u32_argb_premul),
);
if (size > maximum_image_size) {
log.warn("image size {d} is larger than the maximum allowed ({d})", .{ size, maximum_image_size });
return error.Overflow;
}
const destination = try alloc.alloc(
u8,
width * height * @sizeOf(c.wuffs_base__color_u32_argb_premul),
size,
);
errdefer alloc.free(destination);
@ -131,3 +144,8 @@ test "png_decode_FFFFFF" {
try std.testing.expectEqual(1, data.height);
try std.testing.expectEqualSlices(u8, &.{ 255, 255, 255, 255 }, data.data);
}
test "png: too big" {
const data = decode(std.testing.allocator, @embedFile("too_big.png"));
try std.testing.expectError(error.Overflow, data);
}

BIN
pkg/wuffs/src/too_big.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 MiB

BIN
pkg/wuffs/src/too_big.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1021 KiB

View File

@ -433,6 +433,7 @@ pub const LoadingImage = struct {
) catch |err| switch (err) {
error.WuffsError => return error.InvalidData,
error.OutOfMemory => return error.OutOfMemory,
error.Overflow => return error.InvalidData,
};
defer alloc.free(result.data);