From 2f255c7aff1b8689b1db9bb619364af187d1f919 Mon Sep 17 00:00:00 2001 From: Dominic Szablewski Date: Thu, 16 Dec 2021 20:12:27 +0100 Subject: [PATCH] Enforce a limit of 400 million pixels, 2GB file size --- README.md | 18 ++++++++++++++++-- qoi.h | 22 +++++++++++++++++----- 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index a3165940..3c2e134a 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,6 @@ the documentation. More info at https://phoboslab.org/log/2021/11/qoi-fast-lossless-image-compression -⚠️ Please note that this library is not yet ready to deal with untrusted input. - ⚠️ 2021.11.30 – the file format is not yet finalized. We're still working to fix some smaller issues. The final specification will be announced on 2021.12.20. Thanks for your patience! The WIP file format specification can be found in @@ -30,6 +28,22 @@ converts between png <> qoi a simple wrapper to benchmark stbi, libpng and qoi +## Limitations + +The QOI file format allows for huge images with up to 18 exa-pixels. A streaming +en-/decoder can handle these with minimal RAM requirements, assuming there is +enough storage space. + +This particular implementation of QOI however is limited to images with a +maximum size of 400 million pixels. It will safely refuse to en-/decode anything +larger than that. This is not a streaming en-/decoder. It loads the whole image +file into RAM before doing any work and is not extensively optimized for +performance (but it's still very fast). + +If this is a limitation for your use case, please look into any of the other +implementations listed below. + + ## Tools - https://github.com/floooh/qoiview diff --git a/qoi.h b/qoi.h index d473df24..1962164c 100644 --- a/qoi.h +++ b/qoi.h @@ -168,8 +168,8 @@ Values are stored as unsigned integers with a bias of 2. E.g. -2 is stored as The green channel is used to indicate the general direction of change and is encoded in 6 bits. The red and green channels (dr and db) base their diffs off of the green channel difference and are encoded in 4 bits. I.e.: - dr_dg = (last_px.r - cur_px.r) - (last_px.g - cur_px.g) - db_dg = (last_px.b - cur_px.b) - (last_px.g - cur_px.g) + dr_dg = (last_px.r - cur_px.r) - (last_px.g - cur_px.g) + db_dg = (last_px.b - cur_px.b) - (last_px.g - cur_px.g) The difference to the current channel values are using a wraparound operation, so "10 - 13" will result in 253, while "250 + 7" will result in 1. @@ -344,6 +344,12 @@ Implementation */ #define QOI_HEADER_SIZE 14 #define QOI_PADDING 8 +/* 2GB is the max file size that this implementation can safely handle. We guard +against anything larger than that, assuming the worst case with 5 bytes per +pixel, rounded down to a nice clean value. 400 million pixels ought to be +enough for anybody. */ +#define QOI_PIXELS_MAX ((unsigned int)400000000) + typedef union { struct { unsigned char r, g, b, a; } rgba; unsigned int v; @@ -376,7 +382,8 @@ void *qoi_encode(const void *data, const qoi_desc *desc, int *out_len) { data == NULL || out_len == NULL || desc == NULL || desc->width == 0 || desc->height == 0 || desc->channels < 3 || desc->channels > 4 || - desc->colorspace > 2 + desc->colorspace > 2 || + desc->height >= QOI_PIXELS_MAX / desc->width ) { return NULL; } @@ -502,7 +509,7 @@ void *qoi_decode(const void *data, int size, qoi_desc *desc, int channels) { unsigned char *pixels; qoi_rgba_t index[64]; qoi_rgba_t px; - int px_len, chunks_len, px_pos; + int px_len, chunks_len, px_pos; int p = 0, run = 0; if ( @@ -525,7 +532,8 @@ void *qoi_decode(const void *data, int size, qoi_desc *desc, int channels) { desc->width == 0 || desc->height == 0 || desc->channels < 3 || desc->channels > 4 || desc->colorspace > 2 || - header_magic != QOI_MAGIC + header_magic != QOI_MAGIC || + desc->height >= QOI_PIXELS_MAX / desc->width ) { return NULL; } @@ -636,6 +644,10 @@ void *qoi_read(const char *filename, qoi_desc *desc, int channels) { fseek(f, 0, SEEK_END); size = ftell(f); + if (size <= 0) { + fclose(f); + return NULL; + } fseek(f, 0, SEEK_SET); data = QOI_MALLOC(size);