demostf / parser

Parsing of tf2 demo files
55 stars 17 forks source link

Malformed string compression table causes panic/abort in parser #2

Closed 5225225 closed 3 years ago

5225225 commented 3 years ago

The demo file is rather large and I couldn't minify it, but I've attached it anyways.

crash.zip

The crash is

https://github.com/demostf/parser/blob/e2a631cef3462dcb299822c2671abc8e708646c6/src/demo/message/stringtable.rs#L57 , if compressed_size is less than 4, it will either panic on wraparound with debug assertions enabled, or tries to allocate a huge amount of memory in release mode, and aborts. (This could probably be used cause a denial of service by trying to allocate large buffers).

The fix here IMO is to do a checked subtraction there, but also not to trust the value we got from the file for the compressed size. That might be a change in bitbuffer too, which should have tests that trying to read huge amounts of data doesn't crash, and instead returns an error, which it doesn't seem to be doing.

icewind1991 commented 3 years ago

I don't think a very large compressed_size is an issue since the bitbuffer will check that there are compressed_size - 4 bytes in the table data before allocating the buffer.

5225225 commented 3 years ago

Looks like it's not bitbuffer, it's the compressed data. https://docs.rs/snap/1.0.5/snap/raw/struct.Decoder.html#method.decompress_vec is being used. Maybe instead we should use https://docs.rs/snap/1.0.5/snap/read/struct.FrameDecoder.html and limit the amount of bytes we're willing to read to something reasonable?

In any case, this will OOM it.

oom-6cba3d29efefe05228de10e9e770d6544a8eb1e9.zip

My copy of the fuzzer looks like

#![no_main]
use libfuzzer_sys::fuzz_target;

pub use tf_demo_parser::{Demo, DemoParser, Parse, ParseError, ParserState, Stream};

fuzz_target!(|data: &[u8]| { 
    let demo = Demo::new(&data);
    let parser = DemoParser::new_all(demo.get_stream());
    let _ = parser.parse();
});

and the stack trace for the OOM is

Running: oom-6cba3d29efefe05228de10e9e770d6544a8eb1e9
==3992646== ERROR: libFuzzer: out-of-memory (malloc(2164285209))
   To change the out-of-memory limit use -rss_limit_mb=<N>

    #0 0x5645b86b6241 in __sanitizer_print_stack_trace /rustc/llvm/src/llvm-project/compiler-rt/lib/asan/asan_stack.cpp:86:3
    #1 0x5645b9101cf8 in fuzzer::PrintStackTrace() (/home/jess/.cache/cargo/target/x86_64-unknown-linux-gnu/release/fuzz_target_1+0xe4ecf8)
    #2 0x5645b90dccb7 in fuzzer::Fuzzer::HandleMalloc(unsigned long) (/home/jess/.cache/cargo/target/x86_64-unknown-linux-gnu/release/fuzz_target_1+0xe29cb7)
    #3 0x5645b90dcd21 in fuzzer::MallocHook(void const volatile*, unsigned long) (/home/jess/.cache/cargo/target/x86_64-unknown-linux-gnu/release/fuzz_target_1+0xe29d21)
    #4 0x5645b86bc547 in __sanitizer::RunMallocHooks(void const*, unsigned long) /rustc/llvm/src/llvm-project/compiler-rt/lib/sanitizer_common/sanitizer_common.cpp:301:5
    #5 0x5645b8633720 in __asan::Allocator::Allocate(unsigned long, unsigned long, __sanitizer::BufferedStackTrace*, __asan::AllocType, bool) /rustc/llvm/src/llvm-project/compiler-rt/lib/asan/asan_allocator.cpp:611:5
    #6 0x5645b86338d9 in Calloc /rustc/llvm/src/llvm-project/compiler-rt/lib/asan/asan_allocator.cpp:748:17
    #7 0x5645b86338d9 in __asan::asan_calloc(unsigned long, unsigned long, __sanitizer::BufferedStackTrace*) /rustc/llvm/src/llvm-project/compiler-rt/lib/asan/asan_allocator.cpp:984:34
    #8 0x5645b86adb23 in calloc /rustc/llvm/src/llvm-project/compiler-rt/lib/asan/asan_malloc_linux.cpp:155:10
    #9 0x5645b8b46c76 in snap::decompress::Decoder::decompress_vec::h810688ab8033e1b2 (/home/jess/.cache/cargo/target/x86_64-unknown-linux-gnu/release/fuzz_target_1+0x893c76)
    #10 0x5645b8a2894c in _$LT$tf_demo_parser..demo..message..stringtable..CreateStringTableMessage$u20$as$u20$tf_demo_parser..demo..parser..Parse$GT$::parse::hb29ee8e106992416 (/home/jess/.cache/cargo/target/x86_64-unknown-linux-gnu/release/fuzz_target_1+0x77594c)
    #11 0x5645b8a3e0d6 in tf_demo_parser::demo::message::Message::from_type::ha1f9ff6e4102b563 (/home/jess/.cache/cargo/target/x86_64-unknown-linux-gnu/release/fuzz_target_1+0x78b0d6)
    #12 0x5645b8a5525c in tf_demo_parser::demo::packet::message::MessageIterator::next::h15af4754be65372f (/home/jess/.cache/cargo/target/x86_64-unknown-linux-gnu/release/fuzz_target_1+0x7a225c)
    #13 0x5645b86e1d79 in tf_demo_parser::demo::parser::handler::DemoHandler$LT$T$GT$::handle_packet::ha2db951a9c5c4263 (/home/jess/.cache/cargo/target/x86_64-unknown-linux-gnu/release/fuzz_target_1+0x42ed79)
    #14 0x5645b86df2f4 in tf_demo_parser::demo::parser::DemoParser$LT$A$GT$::parse::h8ee8545cd567cbf6 (/home/jess/.cache/cargo/target/x86_64-unknown-linux-gnu/release/fuzz_target_1+0x42c2f4)
    #15 0x5645b8721c0f in rust_fuzzer_test_input (/home/jess/.cache/cargo/target/x86_64-unknown-linux-gnu/release/fuzz_target_1+0x46ec0f)
    #16 0x5645b90e6520 in __rust_try (/home/jess/.cache/cargo/target/x86_64-unknown-linux-gnu/release/fuzz_target_1+0xe33520)
    #17 0x5645b90e617f in LLVMFuzzerTestOneInput (/home/jess/.cache/cargo/target/x86_64-unknown-linux-gnu/release/fuzz_target_1+0xe3317f)
    #18 0x5645b90dd3c4 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) (/home/jess/.cache/cargo/target/x86_64-unknown-linux-gnu/release/fuzz_target_1+0xe2a3c4)
    #19 0x5645b90d180a in fuzzer::RunOneTest(fuzzer::Fuzzer*, char const*, unsigned long) (/home/jess/.cache/cargo/target/x86_64-unknown-linux-gnu/release/fuzz_target_1+0xe1e80a)
    #20 0x5645b90d5462 in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) (/home/jess/.cache/cargo/target/x86_64-unknown-linux-gnu/release/fuzz_target_1+0xe22462)
    #21 0x5645b8631cf2 in main (/home/jess/.cache/cargo/target/x86_64-unknown-linux-gnu/release/fuzz_target_1+0x37ecf2)
    #22 0x7f6570b81b24 in __libc_start_main (/usr/lib/libc.so.6+0x27b24)
    #23 0x5645b8631e9d in _start (/home/jess/.cache/cargo/target/x86_64-unknown-linux-gnu/release/fuzz_target_1+0x37ee9d)

SUMMARY: libFuzzer: out-of-memory
────────────────────────────────────────────────────────────────────────────────

Error: Fuzz target exited with exit code: 71

edit:

I did try fuzzing snap directly with decompress_to_vec, and you can easily OOM with that. So you can only use that method if you trust the input data.

icewind1991 commented 3 years ago

Ah yes, I didn't think about the decompression step itself, bb79d26 adds some hard limits for compressed and decompressed size (10mb and 100mb)