Bladeren bron

png: Add sane compile-time maximums for dimensions + chunk sizes.

Jeroen van Rijn 3 jaren geleden
bovenliggende
commit
8fcd1794a6

+ 1 - 2
core/compress/common.odin

@@ -1,5 +1,3 @@
-package compress
-
 /*
 /*
 	Copyright 2021 Jeroen van Rijn <[email protected]>.
 	Copyright 2021 Jeroen van Rijn <[email protected]>.
 	Made available under Odin's BSD-3 license.
 	Made available under Odin's BSD-3 license.
@@ -7,6 +5,7 @@ package compress
 	List of contributors:
 	List of contributors:
 		Jeroen van Rijn: Initial implementation, optimization.
 		Jeroen van Rijn: Initial implementation, optimization.
 */
 */
+package compress
 
 
 import "core:io"
 import "core:io"
 import "core:bytes"
 import "core:bytes"

+ 4 - 3
core/image/common.odin

@@ -1,13 +1,12 @@
-package image
-
 /*
 /*
 	Copyright 2021 Jeroen van Rijn <[email protected]>.
 	Copyright 2021 Jeroen van Rijn <[email protected]>.
-	Made available under Odin's BSD-2 license.
+	Made available under Odin's BSD-3 license.
 
 
 	List of contributors:
 	List of contributors:
 		Jeroen van Rijn: Initial implementation, optimization.
 		Jeroen van Rijn: Initial implementation, optimization.
 		Ginger Bill:     Cosmetic changes.
 		Ginger Bill:     Cosmetic changes.
 */
 */
+package image
 
 
 import "core:bytes"
 import "core:bytes"
 import "core:mem"
 import "core:mem"
@@ -128,6 +127,7 @@ Error :: union {
 General_Image_Error :: enum {
 General_Image_Error :: enum {
 	None = 0,
 	None = 0,
 	Invalid_Image_Dimensions,
 	Invalid_Image_Dimensions,
+	Image_Dimensions_Too_Large,
 	Image_Does_Not_Adhere_to_Spec,
 	Image_Does_Not_Adhere_to_Spec,
 }
 }
 
 
@@ -138,6 +138,7 @@ PNG_Error :: enum {
 	IDAT_Missing,
 	IDAT_Missing,
 	IDAT_Must_Be_Contiguous,
 	IDAT_Must_Be_Contiguous,
 	IDAT_Corrupt,
 	IDAT_Corrupt,
+	IDAT_Size_Too_Large,
 	PLTE_Encountered_Unexpectedly,
 	PLTE_Encountered_Unexpectedly,
 	PLTE_Invalid_Length,
 	PLTE_Invalid_Length,
 	TRNS_Encountered_Unexpectedly,
 	TRNS_Encountered_Unexpectedly,

+ 3 - 4
core/image/png/example.odin

@@ -1,9 +1,6 @@
-//+ignore
-package png
-
 /*
 /*
 	Copyright 2021 Jeroen van Rijn <[email protected]>.
 	Copyright 2021 Jeroen van Rijn <[email protected]>.
-	Made available under Odin's BSD-2 license.
+	Made available under Odin's BSD-3 license.
 
 
 	List of contributors:
 	List of contributors:
 		Jeroen van Rijn: Initial implementation.
 		Jeroen van Rijn: Initial implementation.
@@ -11,6 +8,8 @@ package png
 
 
 	An example of how to use `load`.
 	An example of how to use `load`.
 */
 */
+//+ignore
+package png
 
 
 import "core:image"
 import "core:image"
 // import "core:image/png"
 // import "core:image/png"

+ 1 - 2
core/image/png/helpers.odin

@@ -1,5 +1,3 @@
-package png
-
 /*
 /*
 	Copyright 2021 Jeroen van Rijn <[email protected]>.
 	Copyright 2021 Jeroen van Rijn <[email protected]>.
 	Made available under Odin's BSD-2 license.
 	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.
 	These are a few useful utility functions to work with PNG images.
 */
 */
+package png
 
 
 import "core:image"
 import "core:image"
 import "core:compress/zlib"
 import "core:compress/zlib"

+ 54 - 6
core/image/png/png.odin

@@ -1,13 +1,12 @@
-package png
-
 /*
 /*
 	Copyright 2021 Jeroen van Rijn <[email protected]>.
 	Copyright 2021 Jeroen van Rijn <[email protected]>.
-	Made available under Odin's BSD-2 license.
+	Made available under Odin's BSD-3 license.
 
 
 	List of contributors:
 	List of contributors:
 		Jeroen van Rijn: Initial implementation.
 		Jeroen van Rijn: Initial implementation.
 		Ginger Bill:     Cosmetic changes.
 		Ginger Bill:     Cosmetic changes.
 */
 */
+package png
 
 
 import "core:compress"
 import "core:compress"
 import "core:compress/zlib"
 import "core:compress/zlib"
@@ -21,6 +20,28 @@ import "core:io"
 import "core:mem"
 import "core:mem"
 import "core:intrinsics"
 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
 Error     :: image.Error
 Image     :: image.Image
 Image     :: image.Image
 Options   :: image.Options
 Options   :: image.Options
@@ -248,6 +269,20 @@ read_chunk :: proc(ctx: ^$C) -> (chunk: Chunk, err: Error) {
 	}
 	}
 	chunk.header = ch
 	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))
 	chunk.data, e = compress.read_slice(ctx, int(ch.length))
 	if e != .None {
 	if e != .None {
 		return {}, compress.General_Error.Stream_Too_Short
 		return {}, compress.General_Error.Stream_Too_Short
@@ -308,7 +343,7 @@ read_header :: proc(ctx: ^$C) -> (IHDR, Error) {
 	header := (^IHDR)(raw_data(c.data))^
 	header := (^IHDR)(raw_data(c.data))^
 	// Validate IHDR
 	// Validate IHDR
 	using header
 	using header
-	if width == 0 || height == 0 {
+	if width == 0 || height == 0 || u128(width) * u128(height) > MAX_DIMENSIONS {
 		return {}, .Invalid_Image_Dimensions
 		return {}, .Invalid_Image_Dimensions
 	}
 	}
 
 
@@ -438,9 +473,10 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
 
 
 	idat: []u8
 	idat: []u8
 	idat_b: bytes.Buffer
 	idat_b: bytes.Buffer
-	idat_length := u32be(0)
 	defer bytes.buffer_destroy(&idat_b)
 	defer bytes.buffer_destroy(&idat_b)
 
 
+	idat_length := u64(0)
+
 	c:		Chunk
 	c:		Chunk
 	ch:     Chunk_Header
 	ch:     Chunk_Header
 	e:      io.Error
 	e:      io.Error
@@ -521,6 +557,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
 				interlace_method   = interlace_method,
 				interlace_method   = interlace_method,
 			}
 			}
 			info.header = h
 			info.header = h
+
 		case .PLTE:
 		case .PLTE:
 			seen_plte = true
 			seen_plte = true
 			// PLTE must appear before IDAT and can't appear for color types 0, 4.
 			// 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 {
 			if .return_metadata in options {
 				append_chunk(&info.chunks, c) or_return
 				append_chunk(&info.chunks, c) or_return
 			}
 			}
+
 		case .IDAT:
 		case .IDAT:
 			// If we only want image metadata and don't want the pixel data, we can early out.
 			// 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 {
 			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
 				c = read_chunk(ctx) or_return
 
 
 				bytes.buffer_write(&idat_b, c.data)
 				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)
 				ch, e = compress.peek_data(ctx, Chunk_Header)
 				if e != .None {
 				if e != .None {
@@ -571,14 +613,17 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
 				}
 				}
 				next = ch.type
 				next = ch.type
 			}
 			}
+
 			idat = bytes.buffer_to_bytes(&idat_b)
 			idat = bytes.buffer_to_bytes(&idat_b)
 			if int(idat_length) != len(idat) {
 			if int(idat_length) != len(idat) {
 				return {}, .IDAT_Corrupt
 				return {}, .IDAT_Corrupt
 			}
 			}
 			seen_idat = true
 			seen_idat = true
+
 		case .IEND:
 		case .IEND:
 			c = read_chunk(ctx) or_return
 			c = read_chunk(ctx) or_return
 			seen_iend = true
 			seen_iend = true
+
 		case .bKGD:
 		case .bKGD:
 
 
 			// TODO: Make sure that 16-bit bKGD + tRNS chunks return u16 instead of u16be
 			// 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[:])
 					col := mem.slice_data_cast([]u16be, c.data[:])
 					img.background = [3]u16{u16(col[0]), u16(col[1]), u16(col[2])}
 					img.background = [3]u16{u16(col[0]), u16(col[1]), u16(col[2])}
 			}
 			}
+
 		case .tRNS:
 		case .tRNS:
 			c = read_chunk(ctx) or_return
 			c = read_chunk(ctx) or_return
 
 
@@ -645,6 +691,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
 				}
 				}
 			}
 			}
 			trns = c
 			trns = c
+
 		case .iDOT, .CbGI:
 		case .iDOT, .CbGI:
 			/*
 			/*
 				iPhone PNG bastardization that doesn't adhere to spec with broken IDAT chunk.
 				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.
 				across one of these files, use a utility to defry it.
 			*/
 			*/
 			return img, .Image_Does_Not_Adhere_to_Spec
 			return img, .Image_Does_Not_Adhere_to_Spec
+
 		case:
 		case:
 			// Unhandled type
 			// Unhandled type
 			c = read_chunk(ctx) or_return
 			c = read_chunk(ctx) or_return

+ 1 - 2
tests/core/image/test_core_image.odin

@@ -1,5 +1,3 @@
-package test_core_image
-
 /*
 /*
 	Copyright 2021 Jeroen van Rijn <[email protected]>.
 	Copyright 2021 Jeroen van Rijn <[email protected]>.
 	Made available under Odin's BSD-3 license.
 	Made available under Odin's BSD-3 license.
@@ -9,6 +7,7 @@ package test_core_image
 
 
 	A test suite for PNG.
 	A test suite for PNG.
 */
 */
+package test_core_image
 
 
 import "core:testing"
 import "core:testing"