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" {
#endif
#include <stdbool.h>
#include <stddef.h>
/* ========================================================================== */
@ -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,

View File

@ -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;
}

View File

@ -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,

View File

@ -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;
}

View File

@ -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;
}

View File

@ -12,6 +12,7 @@
#undef _ANSI_SOURCE
#define _POSIX_C_SOURCE 199309L
#include <stdbool.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
@ -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