onekey-sec/unblob

infinite loop in zlib handler makes unblob hang

qkaiser opened this issue · 0 comments

So during fuzzing we identified that unblob hanged on some malformed data. We dumped each subprocess with py-spy and found one subprocess hanging in unblob/handlers/compression/zlib.py:

py-spy dump -p 517531 
Process 517531: /home/quentin/.cache/pypoetry/virtualenvs/unblob-U71Ryf-L-py3.8/bin/python -c import sys; from importlib import import_module; sys.argv = ['unblob', '-v', '-f', '-e', '/tmp/out', 'REDACTED']; import_module('unblob.cli').main()
Python v3.8.10 (/usr/bin/python3.8)

Thread 517531 (active+gil): "MainThread"
    calculate_chunk (unblob/handlers/compression/zlib.py:49)
    _calculate_chunk (unblob/finder.py:42)
    _hyperscan_match (unblob/finder.py:96)
    search_chunks (unblob/finder.py:142)
    process (unblob/processing.py:309)
    _process_task (unblob/processing.py:287)
    process_task (unblob/processing.py:222)
    _worker_process (unblob/pool.py:57)
    run (multiprocessing/process.py:108)
    _bootstrap (multiprocessing/process.py:315)
    _launch (multiprocessing/popen_fork.py:75)
    __init__ (multiprocessing/popen_fork.py:19)
    _Popen (multiprocessing/context.py:277)
    _Popen (multiprocessing/context.py:224)
    start (multiprocessing/process.py:121)
    start (unblob/pool.py:87)
    __enter__ (unblob/pool.py:29)
    _process_task (unblob/processing.py:132)
    process_file (unblob/processing.py:109)
    decorator (unblob/signals.py:37)
    cli (unblob/cli.py:204)
    decorator (unblob/cli_options.py:15)
    invoke (click/core.py:760)
    invoke (click/core.py:1404)
    main (unblob/cli.py:241)
    <module> (<string>:1)

The bad code is here:

decompressor = zlib.decompressobj()

try:
    while not decompressor.eof:
        decompressor.decompress(file.read(DEFAULT_BUFSIZE))
except zlib.error:
    raise InvalidInputFormat("invalid zlib stream")

end_offset = file.tell() - len(decompressor.unused_data)

If and only if a file containing a valid ZLIB stream at its end ends (EOF) before the end marker of the stream, unblob will go into an infinite loop.

This is due to the combination of two things:

  • file.read(DEFAULT_BUFSIZE) will continuously return an empty string once we reach the file's EOF
  • decompressor.decompress(b'') returns an empty string

The bad assumption I probably made at the time was that decompressing an empty string would raise an Exception.