Make decompress API functions return a result code rather than a 'bool'

This commit is contained in:
Eric Biggers 2016-01-23 11:21:30 -06:00
parent be419e24fa
commit ee1535ecc1
6 changed files with 146 additions and 72 deletions

View File

@ -9,7 +9,6 @@
extern "C" { extern "C" {
#endif #endif
#include <stdbool.h>
#include <stddef.h> #include <stddef.h>
/* ========================================================================== */ /* ========================================================================== */
@ -124,26 +123,56 @@ struct deflate_decompressor;
extern struct deflate_decompressor * extern struct deflate_decompressor *
deflate_alloc_decompressor(void); 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 * deflate_decompress() decompresses 'in_nbytes' bytes of DEFLATE-compressed
* data at 'in' and writes the uncompressed data to 'out', which is a buffer of * 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 * at least 'out_nbytes_avail' bytes. If decompression was successful, then 0
* true is returned; otherwise, false is returned. * (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 * deflate_decompress() can be used in cases where the actual uncompressed size
* is known (recommended) or unknown (not recommended). If the actual * is known (recommended) or unknown (not recommended):
* uncompressed size is known, then pass the actual uncompressed size as *
* 'out_nbytes_avail' and pass NULL for 'actual_out_nbytes_ret'. This makes * - If the actual uncompressed size is known, then pass the actual
* deflate_decompress() return false if the data did not decompress to exactly * uncompressed size as 'out_nbytes_avail' and pass NULL for
* the specified number of bytes. Alternatively, if the actual uncompressed * 'actual_out_nbytes_ret'. This makes deflate_decompress() fail with
* size is unknown, then provide a non-NULL 'actual_out_nbytes_ret' and provide * DECOMPRESS_SHORT_OUTPUT if the data decompressed to fewer than the
* a buffer with some size 'out_nbytes_avail' that you think is large enough to * specified number of bytes.
* 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() * - If the actual uncompressed size is unknown, then provide a non-NULL
* will write the actual uncompressed size to *actual_out_nbytes_ret and return * 'actual_out_nbytes_ret' and provide a buffer with some size
* true. Otherwise, it will return false. * '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, deflate_decompress(struct deflate_decompressor *decompressor,
const void *in, size_t in_nbytes, const void *in, size_t in_nbytes,
void *out, size_t out_nbytes_avail, 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 * Like deflate_decompress(), but assumes the zlib wrapper format instead of raw
* DEFLATE. * DEFLATE.
*/ */
extern bool extern enum decompress_result
zlib_decompress(struct deflate_decompressor *decompressor, zlib_decompress(struct deflate_decompressor *decompressor,
const void *in, size_t in_nbytes, const void *in, size_t in_nbytes,
void *out, size_t out_nbytes_avail, 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 * Like deflate_decompress(), but assumes the gzip wrapper format instead of raw
* DEFLATE. * DEFLATE.
*/ */
extern bool extern enum decompress_result
gzip_decompress(struct deflate_decompressor *decompressor, gzip_decompress(struct deflate_decompressor *decompressor,
const void *in, size_t in_nbytes, const void *in, size_t in_nbytes,
void *out, size_t out_nbytes_avail, void *out, size_t out_nbytes_avail,

View File

@ -6,7 +6,7 @@
* sets. * sets.
*/ */
static bool ATTRIBUTES static enum decompress_result ATTRIBUTES
FUNCNAME(struct deflate_decompressor * restrict d, FUNCNAME(struct deflate_decompressor * restrict d,
const void * restrict in, size_t in_nbytes, const void * restrict in, size_t in_nbytes,
void * restrict out, size_t out_nbytes_avail, void * restrict out, size_t out_nbytes_avail,
@ -176,7 +176,8 @@ next_block:
nlen = READ_U16(); nlen = READ_U16();
SAFETY_CHECK(len == (u16)~nlen); 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); SAFETY_CHECK(len <= in_end - in_next);
memcpy(out_next, in_next, len); memcpy(out_next, in_next, len);
@ -236,7 +237,8 @@ next_block:
REMOVE_BITS(entry & HUFFDEC_LENGTH_MASK); REMOVE_BITS(entry & HUFFDEC_LENGTH_MASK);
if (entry & HUFFDEC_LITERAL) { if (entry & HUFFDEC_LITERAL) {
/* 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); *out_next++ = (u8)(entry >> HUFFDEC_RESULT_SHIFT);
continue; continue;
} }
@ -258,7 +260,8 @@ next_block:
* SIZE_MAX. */ * SIZE_MAX. */
STATIC_ASSERT(HUFFDEC_END_OF_BLOCK_LENGTH == 0); STATIC_ASSERT(HUFFDEC_END_OF_BLOCK_LENGTH == 0);
if (unlikely((size_t)length - 1 > out_end - out_next)) { 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; goto block_done;
} }
@ -361,9 +364,11 @@ block_done:
/* That was the last block. */ /* That was the last block. */
if (!actual_out_nbytes_ret) if (actual_out_nbytes_ret) {
return out_next == out_end;
*actual_out_nbytes_ret = out_next - (u8 *)out; *actual_out_nbytes_ret = out_next - (u8 *)out;
return true; } else {
if (out_next != out_end)
return DECOMPRESS_SHORT_OUTPUT;
}
return DECOMPRESS_SUCCESS;
} }

View File

@ -37,14 +37,15 @@
#include "x86_cpu_features.h" #include "x86_cpu_features.h"
/* By default, if the expression passed to SAFETY_CHECK() evaluates to false, /* By default, if the expression passed to SAFETY_CHECK() evaluates to false,
* then deflate_decompress() immediately returns false as the compressed data is * then deflate_decompress() immediately returns DECOMPRESS_BAD_DATA as the
* invalid. But if unsafe decompression is enabled, then the value of the * compressed data is invalid. But if unsafe decompression is enabled, then the
* expression is ignored, allowing the compiler to optimize out some code. */ * value of the expression is ignored, allowing the compiler to optimize out
* some code. */
#if UNSAFE_DECOMPRESSION #if UNSAFE_DECOMPRESSION
# warning "UNSAFE DECOMPRESSION IS ENABLED. THIS MUST ONLY BE USED IF THE DECOMPRESSOR INPUT WILL ALWAYS BE TRUSTED!" # warning "UNSAFE DECOMPRESSION IS ENABLED. THIS MUST ONLY BE USED IF THE DECOMPRESSOR INPUT WILL ALWAYS BE TRUSTED!"
# define SAFETY_CHECK(expr) (void)(expr) # define SAFETY_CHECK(expr) (void)(expr)
#else #else
# define SAFETY_CHECK(expr) if (unlikely(!(expr))) return false # define SAFETY_CHECK(expr) if (unlikely(!(expr))) return DECOMPRESS_BAD_DATA
#endif #endif
/* /*
@ -814,20 +815,21 @@ copy_word_unaligned(const void *src, void *dst)
#if DISPATCH_ENABLED #if DISPATCH_ENABLED
static bool static enum decompress_result
dispatch(struct deflate_decompressor * restrict d, dispatch(struct deflate_decompressor * restrict d,
const void * restrict in, size_t in_nbytes, const void * restrict in, size_t in_nbytes,
void * restrict out, size_t out_nbytes_avail, void * restrict out, size_t out_nbytes_avail,
size_t *actual_out_nbytes_ret); size_t *actual_out_nbytes_ret);
typedef bool (*decompress_func_t)(struct deflate_decompressor * restrict d, typedef enum decompress_result (*decompress_func_t)
(struct deflate_decompressor * restrict d,
const void * restrict in, size_t in_nbytes, const void * restrict in, size_t in_nbytes,
void * restrict out, size_t out_nbytes_avail, void * restrict out, size_t out_nbytes_avail,
size_t *actual_out_nbytes_ret); size_t *actual_out_nbytes_ret);
static decompress_func_t decompress_impl = dispatch; static decompress_func_t decompress_impl = dispatch;
static bool static enum decompress_result
dispatch(struct deflate_decompressor * restrict d, dispatch(struct deflate_decompressor * restrict d,
const void * restrict in, size_t in_nbytes, const void * restrict in, size_t in_nbytes,
void * restrict out, size_t out_nbytes_avail, 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 * calling the appropriate implementation depending on the CPU features at
* runtime. * runtime.
*/ */
LIBEXPORT bool LIBEXPORT enum decompress_result
deflate_decompress(struct deflate_decompressor * restrict d, deflate_decompress(struct deflate_decompressor * restrict d,
const void * restrict in, size_t in_nbytes, const void * restrict in, size_t in_nbytes,
void * restrict out, size_t out_nbytes_avail, void * restrict out, size_t out_nbytes_avail,

View File

@ -14,7 +14,7 @@
#include "gzip_constants.h" #include "gzip_constants.h"
#include "unaligned.h" #include "unaligned.h"
LIBEXPORT bool LIBEXPORT enum decompress_result
gzip_decompress(struct deflate_decompressor *d, gzip_decompress(struct deflate_decompressor *d,
const void *in, size_t in_nbytes, const void *in, size_t in_nbytes,
void *out, size_t out_nbytes_avail, 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; const u8 * const in_end = in_next + in_nbytes;
u8 flg; u8 flg;
size_t actual_out_nbytes; size_t actual_out_nbytes;
enum decompress_result result;
if (in_nbytes < GZIP_MIN_OVERHEAD) if (in_nbytes < GZIP_MIN_OVERHEAD)
return false; return DECOMPRESS_BAD_DATA;
/* ID1 */ /* ID1 */
if (*in_next++ != GZIP_ID1) if (*in_next++ != GZIP_ID1)
return false; return DECOMPRESS_BAD_DATA;
/* ID2 */ /* ID2 */
if (*in_next++ != GZIP_ID2) if (*in_next++ != GZIP_ID2)
return false; return DECOMPRESS_BAD_DATA;
/* CM */ /* CM */
if (*in_next++ != GZIP_CM_DEFLATE) if (*in_next++ != GZIP_CM_DEFLATE)
return false; return DECOMPRESS_BAD_DATA;
flg = *in_next++; flg = *in_next++;
/* MTIME */ /* MTIME */
in_next += 4; in_next += 4;
@ -46,7 +47,7 @@ gzip_decompress(struct deflate_decompressor *d,
in_next += 1; in_next += 1;
if (flg & GZIP_FRESERVED) if (flg & GZIP_FRESERVED)
return false; return DECOMPRESS_BAD_DATA;
/* Extra field */ /* Extra field */
if (flg & GZIP_FEXTRA) { if (flg & GZIP_FEXTRA) {
@ -54,7 +55,7 @@ gzip_decompress(struct deflate_decompressor *d,
in_next += 2; in_next += 2;
if (in_end - in_next < (u32)xlen + GZIP_FOOTER_SIZE) if (in_end - in_next < (u32)xlen + GZIP_FOOTER_SIZE)
return false; return DECOMPRESS_BAD_DATA;
in_next += xlen; in_next += xlen;
} }
@ -64,7 +65,7 @@ gzip_decompress(struct deflate_decompressor *d,
while (*in_next++ != 0 && in_next != in_end) while (*in_next++ != 0 && in_next != in_end)
; ;
if (in_end - in_next < GZIP_FOOTER_SIZE) if (in_end - in_next < GZIP_FOOTER_SIZE)
return false; return DECOMPRESS_BAD_DATA;
} }
/* File comment (zero terminated) */ /* File comment (zero terminated) */
@ -72,20 +73,23 @@ gzip_decompress(struct deflate_decompressor *d,
while (*in_next++ != 0 && in_next != in_end) while (*in_next++ != 0 && in_next != in_end)
; ;
if (in_end - in_next < GZIP_FOOTER_SIZE) if (in_end - in_next < GZIP_FOOTER_SIZE)
return false; return DECOMPRESS_BAD_DATA;
} }
/* CRC16 for gzip header */ /* CRC16 for gzip header */
if (flg & GZIP_FHCRC) { if (flg & GZIP_FHCRC) {
in_next += 2; in_next += 2;
if (in_end - in_next < GZIP_FOOTER_SIZE) if (in_end - in_next < GZIP_FOOTER_SIZE)
return false; return DECOMPRESS_BAD_DATA;
} }
/* Compressed data */ /* Compressed data */
if (!deflate_decompress(d, in_next, in_end - GZIP_FOOTER_SIZE - in_next, result = deflate_decompress(d, in_next,
out, out_nbytes_avail, actual_out_nbytes_ret)) in_end - GZIP_FOOTER_SIZE - in_next,
return false; out, out_nbytes_avail,
actual_out_nbytes_ret);
if (result != DECOMPRESS_SUCCESS)
return result;
if (actual_out_nbytes_ret) if (actual_out_nbytes_ret)
actual_out_nbytes = *actual_out_nbytes_ret; actual_out_nbytes = *actual_out_nbytes_ret;
@ -96,12 +100,12 @@ gzip_decompress(struct deflate_decompressor *d,
/* CRC32 */ /* CRC32 */
if (crc32_gzip(out, actual_out_nbytes) != get_unaligned_le32(in_next)) if (crc32_gzip(out, actual_out_nbytes) != get_unaligned_le32(in_next))
return false; return DECOMPRESS_BAD_DATA;
in_next += 4; in_next += 4;
/* ISIZE */ /* ISIZE */
if ((u32)actual_out_nbytes != get_unaligned_le32(in_next)) if ((u32)actual_out_nbytes != get_unaligned_le32(in_next))
return false; return DECOMPRESS_BAD_DATA;
return true; return DECOMPRESS_SUCCESS;
} }

View File

@ -14,7 +14,7 @@
#include "unaligned.h" #include "unaligned.h"
#include "zlib_constants.h" #include "zlib_constants.h"
LIBEXPORT bool LIBEXPORT enum decompress_result
zlib_decompress(struct deflate_decompressor *d, zlib_decompress(struct deflate_decompressor *d,
const void *in, size_t in_nbytes, const void *in, size_t in_nbytes,
void *out, size_t out_nbytes_avail, 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; const u8 * const in_end = in_next + in_nbytes;
u16 hdr; u16 hdr;
size_t actual_out_nbytes; size_t actual_out_nbytes;
enum decompress_result result;
if (in_nbytes < ZLIB_MIN_OVERHEAD) if (in_nbytes < ZLIB_MIN_OVERHEAD)
return false; return DECOMPRESS_BAD_DATA;
/* 2 byte header: CMF and FLG */ /* 2 byte header: CMF and FLG */
hdr = get_unaligned_be16(in_next); hdr = get_unaligned_be16(in_next);
@ -34,24 +35,27 @@ zlib_decompress(struct deflate_decompressor *d,
/* FCHECK */ /* FCHECK */
if ((hdr % 31) != 0) if ((hdr % 31) != 0)
return false; return DECOMPRESS_BAD_DATA;
/* CM */ /* CM */
if (((hdr >> 8) & 0xF) != ZLIB_CM_DEFLATE) if (((hdr >> 8) & 0xF) != ZLIB_CM_DEFLATE)
return false; return DECOMPRESS_BAD_DATA;
/* CINFO */ /* CINFO */
if ((hdr >> 12) > ZLIB_CINFO_32K_WINDOW) if ((hdr >> 12) > ZLIB_CINFO_32K_WINDOW)
return false; return DECOMPRESS_BAD_DATA;
/* FDICT */ /* FDICT */
if ((hdr >> 5) & 1) if ((hdr >> 5) & 1)
return false; return DECOMPRESS_BAD_DATA;
/* Compressed data */ /* Compressed data */
if (!deflate_decompress(d, in_next, in_end - ZLIB_FOOTER_SIZE - in_next, result = deflate_decompress(d, in_next,
out, out_nbytes_avail, actual_out_nbytes_ret)) in_end - ZLIB_FOOTER_SIZE - in_next,
return false; out, out_nbytes_avail,
actual_out_nbytes_ret);
if (result != DECOMPRESS_SUCCESS)
return result;
if (actual_out_nbytes_ret) if (actual_out_nbytes_ret)
actual_out_nbytes = *actual_out_nbytes_ret; actual_out_nbytes = *actual_out_nbytes_ret;
@ -62,7 +66,7 @@ zlib_decompress(struct deflate_decompressor *d,
/* ADLER32 */ /* ADLER32 */
if (adler32(out, actual_out_nbytes) != get_unaligned_be32(in_next)) if (adler32(out, actual_out_nbytes) != get_unaligned_be32(in_next))
return false; return DECOMPRESS_BAD_DATA;
return true; return DECOMPRESS_SUCCESS;
} }

View File

@ -12,6 +12,7 @@
#undef _ANSI_SOURCE #undef _ANSI_SOURCE
#define _POSIX_C_SOURCE 199309L #define _POSIX_C_SOURCE 199309L
#include <stdbool.h>
#include <errno.h> #include <errno.h>
#include <fcntl.h> #include <fcntl.h>
#include <getopt.h> #include <getopt.h>
@ -181,14 +182,13 @@ compressor_destroy(struct compressor *c)
struct decompressor { struct decompressor {
void *private; 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 *); void (*free_private)(void *);
}; };
static bool static bool
libz_decompress(void *private, const void *in, size_t in_nbytes, libz_decompress(void *private, const void *in, size_t in_nbytes,
void *out, size_t out_nbytes_avail, void *out, size_t out_nbytes_avail)
size_t *actual_out_nbytes_ret)
{ {
z_stream *z = private; z_stream *z = private;
@ -209,6 +209,36 @@ libz_free_decompressor_private(void *private)
free(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 static void
decompressor_init(struct decompressor *d, enum wrapper wrapper, bool use_libz) 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"); ASSERT(d->private != NULL, "out of memory");
switch (wrapper) { switch (wrapper) {
case NO_WRAPPER: case NO_WRAPPER:
d->decompress = (void *)deflate_decompress; d->decompress = libdeflate_deflate_decompress;
break; break;
case ZLIB_WRAPPER: case ZLIB_WRAPPER:
d->decompress = (void *)zlib_decompress; d->decompress = libdeflate_zlib_decompress;
break; break;
case GZIP_WRAPPER: case GZIP_WRAPPER:
d->decompress = (void *)gzip_decompress; d->decompress = libdeflate_gzip_decompress;
break; 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) void *out, size_t out_nbytes_avail)
{ {
return (*d->decompress)(d->private, in, in_nbytes, return (*d->decompress)(d->private, in, in_nbytes,
out, out_nbytes_avail, NULL); out, out_nbytes_avail);
} }
static void static void