diff --git a/libdeflate.h b/libdeflate.h index 6c47f30..be57f7b 100644 --- a/libdeflate.h +++ b/libdeflate.h @@ -9,7 +9,6 @@ extern "C" { #endif -#include #include /* ========================================================================== */ @@ -124,26 +123,56 @@ struct deflate_decompressor; extern struct deflate_decompressor * deflate_alloc_decompressor(void); +/* + * Result of a call to deflate_decompress(), zlib_decompress(), or + * gzip_decompress(). + */ +enum decompress_result { + /* Decompression was successful. */ + DECOMPRESS_SUCCESS = 0, + + /* Decompressed failed because the compressed data was invalid, corrupt, + * or otherwise unsupported. */ + DECOMPRESS_BAD_DATA = 1, + + /* A NULL 'actual_out_nbytes_ret' was provided, but the data would have + * decompressed to fewer than 'out_nbytes_avail' bytes. */ + DECOMPRESS_SHORT_OUTPUT = 2, + + /* The data would have decompressed to more than 'out_nbytes_avail' + * bytes. */ + DECOMPRESS_INSUFFICIENT_SPACE = 3, +}; + /* * deflate_decompress() decompresses 'in_nbytes' bytes of DEFLATE-compressed * data at 'in' and writes the uncompressed data to 'out', which is a buffer of - * at least 'out_nbytes_avail' bytes. If decompression was successful, then - * true is returned; otherwise, false is returned. + * at least 'out_nbytes_avail' bytes. If decompression was successful, then 0 + * (DECOMPRESS_SUCCESS) is returned; otherwise, a nonzero result code such as + * DECOMPRESS_BAD_DATA is returned. If a nonzero result code is returned, then + * the contents of the output buffer are undefined. * * deflate_decompress() can be used in cases where the actual uncompressed size - * is known (recommended) or unknown (not recommended). If the actual - * uncompressed size is known, then pass the actual uncompressed size as - * 'out_nbytes_avail' and pass NULL for 'actual_out_nbytes_ret'. This makes - * deflate_decompress() return false if the data did not decompress to exactly - * the specified number of bytes. Alternatively, if the actual uncompressed - * size is unknown, then provide a non-NULL 'actual_out_nbytes_ret' and provide - * a buffer with some size 'out_nbytes_avail' that you think is large enough to - * hold all the uncompressed data. In this case, if the data decompresses to - * less than or equal to 'out_nbytes_avail' bytes, then deflate_decompress() - * will write the actual uncompressed size to *actual_out_nbytes_ret and return - * true. Otherwise, it will return false. + * is known (recommended) or unknown (not recommended): + * + * - If the actual uncompressed size is known, then pass the actual + * uncompressed size as 'out_nbytes_avail' and pass NULL for + * 'actual_out_nbytes_ret'. This makes deflate_decompress() fail with + * DECOMPRESS_SHORT_OUTPUT if the data decompressed to fewer than the + * specified number of bytes. + * + * - If the actual uncompressed size is unknown, then provide a non-NULL + * 'actual_out_nbytes_ret' and provide a buffer with some size + * 'out_nbytes_avail' that you think is large enough to hold all the + * uncompressed data. In this case, if the data decompresses to less than + * or equal to 'out_nbytes_avail' bytes, then deflate_decompress() will + * write the actual uncompressed size to *actual_out_nbytes_ret and return 0 + * (DECOMPRESS_SUCCESS). Otherwise, it will return + * DECOMPRESS_INSUFFICIENT_SPACE if the provided buffer was not large enough + * but no other problems were encountered, or another nonzero result code if + * decompression failed for another reason. */ -extern bool +extern enum decompress_result deflate_decompress(struct deflate_decompressor *decompressor, const void *in, size_t in_nbytes, void *out, size_t out_nbytes_avail, @@ -153,7 +182,7 @@ deflate_decompress(struct deflate_decompressor *decompressor, * Like deflate_decompress(), but assumes the zlib wrapper format instead of raw * DEFLATE. */ -extern bool +extern enum decompress_result zlib_decompress(struct deflate_decompressor *decompressor, const void *in, size_t in_nbytes, void *out, size_t out_nbytes_avail, @@ -163,7 +192,7 @@ zlib_decompress(struct deflate_decompressor *decompressor, * Like deflate_decompress(), but assumes the gzip wrapper format instead of raw * DEFLATE. */ -extern bool +extern enum decompress_result gzip_decompress(struct deflate_decompressor *decompressor, const void *in, size_t in_nbytes, void *out, size_t out_nbytes_avail, diff --git a/src/decompress_impl.h b/src/decompress_impl.h index d32c668..08368d7 100644 --- a/src/decompress_impl.h +++ b/src/decompress_impl.h @@ -6,7 +6,7 @@ * sets. */ -static bool ATTRIBUTES +static enum decompress_result ATTRIBUTES FUNCNAME(struct deflate_decompressor * restrict d, const void * restrict in, size_t in_nbytes, void * restrict out, size_t out_nbytes_avail, @@ -176,7 +176,8 @@ next_block: nlen = READ_U16(); SAFETY_CHECK(len == (u16)~nlen); - SAFETY_CHECK(len <= out_end - out_next); + if (unlikely(len > out_end - out_next)) + return DECOMPRESS_INSUFFICIENT_SPACE; SAFETY_CHECK(len <= in_end - in_next); memcpy(out_next, in_next, len); @@ -236,7 +237,8 @@ next_block: REMOVE_BITS(entry & HUFFDEC_LENGTH_MASK); if (entry & HUFFDEC_LITERAL) { /* Literal */ - SAFETY_CHECK(out_next < out_end); + if (unlikely(out_next == out_end)) + return DECOMPRESS_INSUFFICIENT_SPACE; *out_next++ = (u8)(entry >> HUFFDEC_RESULT_SHIFT); continue; } @@ -258,7 +260,8 @@ next_block: * SIZE_MAX. */ STATIC_ASSERT(HUFFDEC_END_OF_BLOCK_LENGTH == 0); if (unlikely((size_t)length - 1 > out_end - out_next)) { - SAFETY_CHECK(length == HUFFDEC_END_OF_BLOCK_LENGTH); + if (unlikely(length != HUFFDEC_END_OF_BLOCK_LENGTH)) + return DECOMPRESS_INSUFFICIENT_SPACE; goto block_done; } @@ -361,9 +364,11 @@ block_done: /* That was the last block. */ - if (!actual_out_nbytes_ret) - return out_next == out_end; - - *actual_out_nbytes_ret = out_next - (u8 *)out; - return true; + if (actual_out_nbytes_ret) { + *actual_out_nbytes_ret = out_next - (u8 *)out; + } else { + if (out_next != out_end) + return DECOMPRESS_SHORT_OUTPUT; + } + return DECOMPRESS_SUCCESS; } diff --git a/src/deflate_decompress.c b/src/deflate_decompress.c index 610d5a1..330405a 100644 --- a/src/deflate_decompress.c +++ b/src/deflate_decompress.c @@ -37,14 +37,15 @@ #include "x86_cpu_features.h" /* By default, if the expression passed to SAFETY_CHECK() evaluates to false, - * then deflate_decompress() immediately returns false as the compressed data is - * invalid. But if unsafe decompression is enabled, then the value of the - * expression is ignored, allowing the compiler to optimize out some code. */ + * then deflate_decompress() immediately returns DECOMPRESS_BAD_DATA as the + * compressed data is invalid. But if unsafe decompression is enabled, then the + * value of the expression is ignored, allowing the compiler to optimize out + * some code. */ #if UNSAFE_DECOMPRESSION # warning "UNSAFE DECOMPRESSION IS ENABLED. THIS MUST ONLY BE USED IF THE DECOMPRESSOR INPUT WILL ALWAYS BE TRUSTED!" # define SAFETY_CHECK(expr) (void)(expr) #else -# define SAFETY_CHECK(expr) if (unlikely(!(expr))) return false +# define SAFETY_CHECK(expr) if (unlikely(!(expr))) return DECOMPRESS_BAD_DATA #endif /* @@ -814,20 +815,21 @@ copy_word_unaligned(const void *src, void *dst) #if DISPATCH_ENABLED -static bool +static enum decompress_result dispatch(struct deflate_decompressor * restrict d, const void * restrict in, size_t in_nbytes, void * restrict out, size_t out_nbytes_avail, size_t *actual_out_nbytes_ret); -typedef bool (*decompress_func_t)(struct deflate_decompressor * restrict d, - const void * restrict in, size_t in_nbytes, - void * restrict out, size_t out_nbytes_avail, - size_t *actual_out_nbytes_ret); +typedef enum decompress_result (*decompress_func_t) + (struct deflate_decompressor * restrict d, + const void * restrict in, size_t in_nbytes, + void * restrict out, size_t out_nbytes_avail, + size_t *actual_out_nbytes_ret); static decompress_func_t decompress_impl = dispatch; -static bool +static enum decompress_result dispatch(struct deflate_decompressor * restrict d, const void * restrict in, size_t in_nbytes, void * restrict out, size_t out_nbytes_avail, @@ -853,7 +855,7 @@ dispatch(struct deflate_decompressor * restrict d, * calling the appropriate implementation depending on the CPU features at * runtime. */ -LIBEXPORT bool +LIBEXPORT enum decompress_result deflate_decompress(struct deflate_decompressor * restrict d, const void * restrict in, size_t in_nbytes, void * restrict out, size_t out_nbytes_avail, diff --git a/src/gzip_decompress.c b/src/gzip_decompress.c index 5d63350..a2748a5 100644 --- a/src/gzip_decompress.c +++ b/src/gzip_decompress.c @@ -14,7 +14,7 @@ #include "gzip_constants.h" #include "unaligned.h" -LIBEXPORT bool +LIBEXPORT enum decompress_result gzip_decompress(struct deflate_decompressor *d, const void *in, size_t in_nbytes, void *out, size_t out_nbytes_avail, @@ -24,19 +24,20 @@ gzip_decompress(struct deflate_decompressor *d, const u8 * const in_end = in_next + in_nbytes; u8 flg; size_t actual_out_nbytes; + enum decompress_result result; if (in_nbytes < GZIP_MIN_OVERHEAD) - return false; + return DECOMPRESS_BAD_DATA; /* ID1 */ if (*in_next++ != GZIP_ID1) - return false; + return DECOMPRESS_BAD_DATA; /* ID2 */ if (*in_next++ != GZIP_ID2) - return false; + return DECOMPRESS_BAD_DATA; /* CM */ if (*in_next++ != GZIP_CM_DEFLATE) - return false; + return DECOMPRESS_BAD_DATA; flg = *in_next++; /* MTIME */ in_next += 4; @@ -46,7 +47,7 @@ gzip_decompress(struct deflate_decompressor *d, in_next += 1; if (flg & GZIP_FRESERVED) - return false; + return DECOMPRESS_BAD_DATA; /* Extra field */ if (flg & GZIP_FEXTRA) { @@ -54,7 +55,7 @@ gzip_decompress(struct deflate_decompressor *d, in_next += 2; if (in_end - in_next < (u32)xlen + GZIP_FOOTER_SIZE) - return false; + return DECOMPRESS_BAD_DATA; in_next += xlen; } @@ -64,7 +65,7 @@ gzip_decompress(struct deflate_decompressor *d, while (*in_next++ != 0 && in_next != in_end) ; if (in_end - in_next < GZIP_FOOTER_SIZE) - return false; + return DECOMPRESS_BAD_DATA; } /* File comment (zero terminated) */ @@ -72,20 +73,23 @@ gzip_decompress(struct deflate_decompressor *d, while (*in_next++ != 0 && in_next != in_end) ; if (in_end - in_next < GZIP_FOOTER_SIZE) - return false; + return DECOMPRESS_BAD_DATA; } /* CRC16 for gzip header */ if (flg & GZIP_FHCRC) { in_next += 2; if (in_end - in_next < GZIP_FOOTER_SIZE) - return false; + return DECOMPRESS_BAD_DATA; } /* Compressed data */ - if (!deflate_decompress(d, in_next, in_end - GZIP_FOOTER_SIZE - in_next, - out, out_nbytes_avail, actual_out_nbytes_ret)) - return false; + result = deflate_decompress(d, in_next, + in_end - GZIP_FOOTER_SIZE - in_next, + out, out_nbytes_avail, + actual_out_nbytes_ret); + if (result != DECOMPRESS_SUCCESS) + return result; if (actual_out_nbytes_ret) actual_out_nbytes = *actual_out_nbytes_ret; @@ -96,12 +100,12 @@ gzip_decompress(struct deflate_decompressor *d, /* CRC32 */ if (crc32_gzip(out, actual_out_nbytes) != get_unaligned_le32(in_next)) - return false; + return DECOMPRESS_BAD_DATA; in_next += 4; /* ISIZE */ if ((u32)actual_out_nbytes != get_unaligned_le32(in_next)) - return false; + return DECOMPRESS_BAD_DATA; - return true; + return DECOMPRESS_SUCCESS; } diff --git a/src/zlib_decompress.c b/src/zlib_decompress.c index accc1a2..eca05dd 100644 --- a/src/zlib_decompress.c +++ b/src/zlib_decompress.c @@ -14,7 +14,7 @@ #include "unaligned.h" #include "zlib_constants.h" -LIBEXPORT bool +LIBEXPORT enum decompress_result zlib_decompress(struct deflate_decompressor *d, const void *in, size_t in_nbytes, void *out, size_t out_nbytes_avail, @@ -24,9 +24,10 @@ zlib_decompress(struct deflate_decompressor *d, const u8 * const in_end = in_next + in_nbytes; u16 hdr; size_t actual_out_nbytes; + enum decompress_result result; if (in_nbytes < ZLIB_MIN_OVERHEAD) - return false; + return DECOMPRESS_BAD_DATA; /* 2 byte header: CMF and FLG */ hdr = get_unaligned_be16(in_next); @@ -34,24 +35,27 @@ zlib_decompress(struct deflate_decompressor *d, /* FCHECK */ if ((hdr % 31) != 0) - return false; + return DECOMPRESS_BAD_DATA; /* CM */ if (((hdr >> 8) & 0xF) != ZLIB_CM_DEFLATE) - return false; + return DECOMPRESS_BAD_DATA; /* CINFO */ if ((hdr >> 12) > ZLIB_CINFO_32K_WINDOW) - return false; + return DECOMPRESS_BAD_DATA; /* FDICT */ if ((hdr >> 5) & 1) - return false; + return DECOMPRESS_BAD_DATA; /* Compressed data */ - if (!deflate_decompress(d, in_next, in_end - ZLIB_FOOTER_SIZE - in_next, - out, out_nbytes_avail, actual_out_nbytes_ret)) - return false; + result = deflate_decompress(d, in_next, + in_end - ZLIB_FOOTER_SIZE - in_next, + out, out_nbytes_avail, + actual_out_nbytes_ret); + if (result != DECOMPRESS_SUCCESS) + return result; if (actual_out_nbytes_ret) actual_out_nbytes = *actual_out_nbytes_ret; @@ -62,7 +66,7 @@ zlib_decompress(struct deflate_decompressor *d, /* ADLER32 */ if (adler32(out, actual_out_nbytes) != get_unaligned_be32(in_next)) - return false; + return DECOMPRESS_BAD_DATA; - return true; + return DECOMPRESS_SUCCESS; } diff --git a/tools/benchmark.c b/tools/benchmark.c index fba6843..bc88fa0 100644 --- a/tools/benchmark.c +++ b/tools/benchmark.c @@ -12,6 +12,7 @@ #undef _ANSI_SOURCE #define _POSIX_C_SOURCE 199309L +#include #include #include #include @@ -181,14 +182,13 @@ compressor_destroy(struct compressor *c) struct decompressor { void *private; - bool (*decompress)(void *, const void *, size_t, void *, size_t, size_t *); + bool (*decompress)(void *, const void *, size_t, void *, size_t); void (*free_private)(void *); }; static bool libz_decompress(void *private, const void *in, size_t in_nbytes, - void *out, size_t out_nbytes_avail, - size_t *actual_out_nbytes_ret) + void *out, size_t out_nbytes_avail) { z_stream *z = private; @@ -209,6 +209,36 @@ libz_free_decompressor_private(void *private) free(private); } +static bool +libdeflate_deflate_decompress(void *private, const void *in, size_t in_nbytes, + void *out, size_t out_nbytes_avail) +{ + return 0 == deflate_decompress(private, in, in_nbytes, + out, out_nbytes_avail, NULL); +} + +static bool +libdeflate_zlib_decompress(void *private, const void *in, size_t in_nbytes, + void *out, size_t out_nbytes_avail) +{ + return 0 == zlib_decompress(private, in, in_nbytes, + out, out_nbytes_avail, NULL); +} + +static bool +libdeflate_gzip_decompress(void *private, const void *in, size_t in_nbytes, + void *out, size_t out_nbytes_avail) +{ + return 0 == gzip_decompress(private, in, in_nbytes, + out, out_nbytes_avail, NULL); +} + +static void +libdeflate_free_decompressor_private(void *private) +{ + deflate_free_decompressor(private); +} + static void decompressor_init(struct decompressor *d, enum wrapper wrapper, bool use_libz) { @@ -236,16 +266,16 @@ decompressor_init(struct decompressor *d, enum wrapper wrapper, bool use_libz) ASSERT(d->private != NULL, "out of memory"); switch (wrapper) { case NO_WRAPPER: - d->decompress = (void *)deflate_decompress; + d->decompress = libdeflate_deflate_decompress; break; case ZLIB_WRAPPER: - d->decompress = (void *)zlib_decompress; + d->decompress = libdeflate_zlib_decompress; break; case GZIP_WRAPPER: - d->decompress = (void *)gzip_decompress; + d->decompress = libdeflate_gzip_decompress; break; } - d->free_private = (void *)deflate_free_decompressor; + d->free_private = libdeflate_free_decompressor_private; } } @@ -254,7 +284,7 @@ do_decompress(struct decompressor *d, const void *in, size_t in_nbytes, void *out, size_t out_nbytes_avail) { return (*d->decompress)(d->private, in, in_nbytes, - out, out_nbytes_avail, NULL); + out, out_nbytes_avail); } static void