Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions Modules/_xxtestfuzz/dictionaries/fuzz_zlib.dict
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# zlib/lzma/gzip headers and structure tokens
"\x78\x01"
"\x78\x5e"
"\x78\x9c"
"\x78\xda"
"\x78\xf9"
"\xfd7zXZ"
"\xfd7zXZ\x00"
"\x1f\x8b"
"\x1f\x8b\x08"
"\x5d\x00"
"\x5d\x00\x00"
"\x80"
"\x00\x00\xff\xff"
"\x00\x00"
"\xff\xff"
"\x04\x00"
"\x9c"
"\xda"
"\x00"
"\xff"
"\x02"
"\x01"
1 change: 1 addition & 0 deletions Modules/_xxtestfuzz/fuzz_tests.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ fuzz_struct_unpack
fuzz_ast_literal_eval
fuzz_elementtree_parsewhole
fuzz_pycompile
fuzz_zlib
Binary file added Modules/_xxtestfuzz/fuzz_zlib_corpus/gzip_stream
Binary file not shown.
Binary file added Modules/_xxtestfuzz/fuzz_zlib_corpus/lzma_alone
Binary file not shown.
Binary file added Modules/_xxtestfuzz/fuzz_zlib_corpus/lzma_empty
Binary file not shown.
Binary file not shown.
Binary file added Modules/_xxtestfuzz/fuzz_zlib_corpus/xz_stream
Binary file not shown.
Binary file added Modules/_xxtestfuzz/fuzz_zlib_corpus/zlib_empty
Binary file not shown.
Binary file added Modules/_xxtestfuzz/fuzz_zlib_corpus/zlib_level1
Binary file not shown.
Binary file added Modules/_xxtestfuzz/fuzz_zlib_corpus/zlib_level9
Binary file not shown.
Binary file added Modules/_xxtestfuzz/fuzz_zlib_corpus/zlib_small
Binary file not shown.
147 changes: 147 additions & 0 deletions Modules/_xxtestfuzz/fuzzer.c
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,142 @@
return rv;
}

#define MAX_ZLIB_TEST_SIZE 0x100000

/* Fuzz zlib and lzma decompression of attacker-controlled bytes. The
module wrappers live in Modules/zlibmodule.c and Modules/_lzmamodule.c
(the zlib/xz libraries themselves are built from source with sanitizer
instrumentation). Streaming decompression with an explicit max_length
bounds the output so decompression bombs cannot OOM the fuzzer. */
static PyObject* zlib_decompressobj_factory = NULL;
static PyObject* lzma_decompress_factory = NULL;
static PyObject* zlib_error_exc = NULL;
static PyObject* lzma_error_exc = NULL;

/* Called by LLVMFuzzerTestOneInput for initialization */
static int init_zlib(void) {
if (PyErr_Occurred()) {
/* A pending exception left over from interpreter startup would
trip asserts inside attribute lookups; report it visibly and
start the lookups with a clean error indicator. */
PyErr_Print();
}
PyObject* zlib_module = PyImport_ImportModule("zlib");
if (zlib_module == NULL) {
return 0;
}
zlib_decompressobj_factory = PyObject_GetAttrString(zlib_module, "decompressobj");
if (zlib_decompressobj_factory == NULL) {
Py_DECREF(zlib_module);
return 0;
}
zlib_error_exc = PyObject_GetAttrString(zlib_module, "error");
Py_DECREF(zlib_module);
if (zlib_error_exc == NULL) {
return 0;
}
PyObject* lzma_module = PyImport_ImportModule("lzma");
if (lzma_module == NULL) {
return 0;
}
lzma_decompress_factory = PyObject_GetAttrString(lzma_module, "LZMADecompressor");
if (lzma_decompress_factory == NULL) {
Py_DECREF(lzma_module);
return 0;
}
lzma_error_exc = PyObject_GetAttrString(lzma_module, "LZMAError");
Py_DECREF(lzma_module);
if (lzma_error_exc == NULL) {
return 0;
}
return 1;
}

/* Fuzz zlib.decompressobj(wbits).decompress / lzma.LZMADecompressor().decompress */
static int fuzz_zlib(const char* data, size_t size) {
if (size < 2 || size > MAX_ZLIB_TEST_SIZE) {
return 0;
}
unsigned char mode = (unsigned char) data[0];
data += 1;
size -= 1;

PyObject* payload = PyBytes_FromStringAndSize(data, (Py_ssize_t)size);
if (payload == NULL) {
return 0;
}

PyObject* dobj;
if (mode % 4 == 3) {
/* lzma with FORMAT_AUTO (0) */
dobj = PyObject_CallFunction(lzma_decompress_factory, "(i)", 0);
} else {
/* zlib window bits: 15 = zlib stream, -15 = raw deflate,
47 = auto-detect zlib or gzip */
int wbits = 15;
switch (mode % 3) {
case 0: wbits = 15; break;
case 1: wbits = -15; break;
default: wbits = 47; break;
}
dobj = PyObject_CallFunction(zlib_decompressobj_factory, "(i)", wbits);
}
if (dobj == NULL) {
PyErr_Clear();
Py_DECREF(payload);
return 0;
}

/* max_length bounds the output per call: decompression bombs end up
in unconsumed_tail instead of memory. */
PyObject* out = PyObject_CallMethod(dobj, "decompress", "Oi", payload,
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
65536);
if (out == NULL) {
/* Expected for attacker bytes: zlib.error / lzma.LZMAError for
corrupt or unsupported streams, EOFError for truncated ones,
ValueError for bad header combinations. */
if (PyErr_ExceptionMatches(PyExc_EOFError) ||
PyErr_ExceptionMatches(PyExc_ValueError) ||
PyErr_ExceptionMatches(zlib_error_exc) ||
PyErr_ExceptionMatches(lzma_error_exc))
{
PyErr_Clear();
}
} else {
Py_DECREF(out);
if (mode % 4 == 3) {
/* LZMADecompressor has no flush(); reading the end-of-stream
flags exercises its EOF detection paths instead. */
PyObject* eof = PyObject_GetAttrString(dobj, "eof");
if (eof == NULL) {
PyErr_Clear();
} else {
Py_DECREF(eof);
}
PyObject* unused = PyObject_GetAttrString(dobj, "unused_data");
if (unused == NULL) {
PyErr_Clear();
} else {
Py_DECREF(unused);
}
} else {
/* flush() completes the zlib stream handling paths. */
PyObject* flushed = PyObject_CallMethod(dobj, "flush", NULL);
if (flushed == NULL) {
if (PyErr_ExceptionMatches(zlib_error_exc)) {
PyErr_Clear();
}
} else {
Py_DECREF(flushed);
}
}
}
Py_DECREF(dobj);
Py_DECREF(payload);
return 0;
}


/* CPython generates a lot of leak warnings for whatever reason. */
int __lsan_is_turned_off(void) { return 1; }

Expand Down Expand Up @@ -726,6 +862,17 @@
#endif
#if !defined(_Py_FUZZ_ONE) || defined(_Py_FUZZ_fuzz_pycompile)
rv |= _run_fuzz(data, size, fuzz_pycompile);
#endif
#if !defined(_Py_FUZZ_ONE) || defined(_Py_FUZZ_fuzz_zlib)
static int ZLIB_INITIALIZED = 0;
if (!ZLIB_INITIALIZED && !init_zlib()) {
PyErr_Print();
abort();
} else {
ZLIB_INITIALIZED = 1;
}

rv |= _run_fuzz(data, size, fuzz_zlib);
#endif
return rv;
}
Loading