diff options
| -rw-r--r-- | core/compress/common.odin | 3 | ||||
| -rw-r--r-- | core/image/common.odin | 7 | ||||
| -rw-r--r-- | core/image/png/example.odin | 7 | ||||
| -rw-r--r-- | core/image/png/helpers.odin | 3 | ||||
| -rw-r--r-- | core/image/png/png.odin | 60 | ||||
| -rw-r--r-- | tests/core/image/test_core_image.odin | 3 |
6 files changed, 64 insertions, 19 deletions
diff --git a/core/compress/common.odin b/core/compress/common.odin index 819cfb481..41f292b6f 100644 --- a/core/compress/common.odin +++ b/core/compress/common.odin @@ -1,5 +1,3 @@ -package compress - /* Copyright 2021 Jeroen van Rijn <nom@duclavier.com>. Made available under Odin's BSD-3 license. @@ -7,6 +5,7 @@ package compress List of contributors: Jeroen van Rijn: Initial implementation, optimization. */ +package compress import "core:io" import "core:bytes" diff --git a/core/image/common.odin b/core/image/common.odin index 9bb99a5d4..2826a65ca 100644 --- a/core/image/common.odin +++ b/core/image/common.odin @@ -1,13 +1,12 @@ -package image - /* Copyright 2021 Jeroen van Rijn <nom@duclavier.com>. - Made available under Odin's BSD-2 license. + Made available under Odin's BSD-3 license. List of contributors: Jeroen van Rijn: Initial implementation, optimization. Ginger Bill: Cosmetic changes. */ +package image import "core:bytes" import "core:mem" @@ -128,6 +127,7 @@ Error :: union { General_Image_Error :: enum { None = 0, Invalid_Image_Dimensions, + Image_Dimensions_Too_Large, Image_Does_Not_Adhere_to_Spec, } @@ -138,6 +138,7 @@ PNG_Error :: enum { IDAT_Missing, IDAT_Must_Be_Contiguous, IDAT_Corrupt, + IDAT_Size_Too_Large, PLTE_Encountered_Unexpectedly, PLTE_Invalid_Length, TRNS_Encountered_Unexpectedly, diff --git a/core/image/png/example.odin b/core/image/png/example.odin index ad0423d67..5370b0bcf 100644 --- a/core/image/png/example.odin +++ b/core/image/png/example.odin @@ -1,9 +1,6 @@ -//+ignore -package png - /* Copyright 2021 Jeroen van Rijn <nom@duclavier.com>. - Made available under Odin's BSD-2 license. + Made available under Odin's BSD-3 license. List of contributors: Jeroen van Rijn: Initial implementation. @@ -11,6 +8,8 @@ package png An example of how to use `load`. */ +//+ignore +package png import "core:image" // import "core:image/png" diff --git a/core/image/png/helpers.odin b/core/image/png/helpers.odin index 1b3749f76..59d8fb70b 100644 --- a/core/image/png/helpers.odin +++ b/core/image/png/helpers.odin @@ -1,5 +1,3 @@ -package png - /* Copyright 2021 Jeroen van Rijn <nom@duclavier.com>. Made available under Odin's BSD-2 license. @@ -10,6 +8,7 @@ package png These are a few useful utility functions to work with PNG images. */ +package png import "core:image" import "core:compress/zlib" diff --git a/core/image/png/png.odin b/core/image/png/png.odin index 21b27fc82..883e51d83 100644 --- a/core/image/png/png.odin +++ b/core/image/png/png.odin @@ -1,13 +1,12 @@ -package png - /* Copyright 2021 Jeroen van Rijn <nom@duclavier.com>. - Made available under Odin's BSD-2 license. + Made available under Odin's BSD-3 license. List of contributors: Jeroen van Rijn: Initial implementation. Ginger Bill: Cosmetic changes. */ +package png import "core:compress" import "core:compress/zlib" @@ -21,6 +20,28 @@ import "core:io" import "core:mem" import "core:intrinsics" +/* + 67_108_864 pixels max by default. + Maximum allowed dimensions are capped at 65535 * 65535. +*/ +MAX_DIMENSIONS :: min(#config(PNG_MAX_DIMENSIONS, 8192 * 8192), 65535 * 65535) + +/* + Limit chunk sizes. + By default: IDAT = 8k x 8k x 16-bits + 8k filter bytes. +*/ +_MAX_IDAT_DEFAULT :: ( 8192 /* Width */ * 8192 /* Height */ * 2 /* 16-bit */) + 8192 /* Filter bytes */ +_MAX_IDAT :: (65535 /* Width */ * 65535 /* Height */ * 2 /* 16-bit */) + 65535 /* Filter bytes */ + +MAX_IDAT_SIZE :: min(#config(PNG_MAX_IDAT_SIZE, _MAX_IDAT_DEFAULT), _MAX_IDAT) + +/* + For chunks other than IDAT with a variable size like `zTXT` and `eXIf`, + limit their size to 16 MiB each by default. Max of 256 MiB each. +*/ +MAX_CHUNK_SIZE :: min(#config(PNG_MAX_CHUNK_SIZE, 16_777_216), 268_435_456) + + Error :: image.Error Image :: image.Image Options :: image.Options @@ -248,6 +269,20 @@ read_chunk :: proc(ctx: ^$C) -> (chunk: Chunk, err: Error) { } chunk.header = ch + /* + Sanity check chunk size + */ + #partial switch ch.type { + case .IDAT: + if ch.length > MAX_IDAT_SIZE { + return {}, image.PNG_Error.IDAT_Size_Too_Large + } + case: + if ch.length > MAX_CHUNK_SIZE { + return {}, image.PNG_Error.Invalid_Chunk_Length + } + } + chunk.data, e = compress.read_slice(ctx, int(ch.length)) if e != .None { return {}, compress.General_Error.Stream_Too_Short @@ -308,7 +343,7 @@ read_header :: proc(ctx: ^$C) -> (IHDR, Error) { header := (^IHDR)(raw_data(c.data))^ // Validate IHDR using header - if width == 0 || height == 0 { + if width == 0 || height == 0 || u128(width) * u128(height) > MAX_DIMENSIONS { return {}, .Invalid_Image_Dimensions } @@ -438,9 +473,10 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a idat: []u8 idat_b: bytes.Buffer - idat_length := u32be(0) defer bytes.buffer_destroy(&idat_b) + idat_length := u64(0) + c: Chunk ch: Chunk_Header e: io.Error @@ -521,6 +557,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a interlace_method = interlace_method, } info.header = h + case .PLTE: seen_plte = true // PLTE must appear before IDAT and can't appear for color types 0, 4. @@ -543,6 +580,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a if .return_metadata in options { append_chunk(&info.chunks, c) or_return } + case .IDAT: // If we only want image metadata and don't want the pixel data, we can early out. if .return_metadata not_in options && .do_not_decompress_image in options { @@ -563,7 +601,11 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a c = read_chunk(ctx) or_return bytes.buffer_write(&idat_b, c.data) - idat_length += c.header.length + idat_length += u64(c.header.length) + + if idat_length > MAX_IDAT_SIZE { + return {}, image.PNG_Error.IDAT_Size_Too_Large + } ch, e = compress.peek_data(ctx, Chunk_Header) if e != .None { @@ -571,14 +613,17 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a } next = ch.type } + idat = bytes.buffer_to_bytes(&idat_b) if int(idat_length) != len(idat) { return {}, .IDAT_Corrupt } seen_idat = true + case .IEND: c = read_chunk(ctx) or_return seen_iend = true + case .bKGD: // TODO: Make sure that 16-bit bKGD + tRNS chunks return u16 instead of u16be @@ -614,6 +659,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a col := mem.slice_data_cast([]u16be, c.data[:]) img.background = [3]u16{u16(col[0]), u16(col[1]), u16(col[2])} } + case .tRNS: c = read_chunk(ctx) or_return @@ -645,6 +691,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a } } trns = c + case .iDOT, .CbGI: /* iPhone PNG bastardization that doesn't adhere to spec with broken IDAT chunk. @@ -652,6 +699,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a across one of these files, use a utility to defry it. */ return img, .Image_Does_Not_Adhere_to_Spec + case: // Unhandled type c = read_chunk(ctx) or_return diff --git a/tests/core/image/test_core_image.odin b/tests/core/image/test_core_image.odin index d2db9d11d..7752cf7dc 100644 --- a/tests/core/image/test_core_image.odin +++ b/tests/core/image/test_core_image.odin @@ -1,5 +1,3 @@ -package test_core_image - /* Copyright 2021 Jeroen van Rijn <nom@duclavier.com>. Made available under Odin's BSD-3 license. @@ -9,6 +7,7 @@ package test_core_image A test suite for PNG. */ +package test_core_image import "core:testing" |