The issue is that on line #3 txn_sidecar_mem was allocated to store single transaction of max size (FD_BANK_ABI_TXN_FOOTPRINT_SIDECAR_MAX).
To simplify things we will focus on legacy txns.
The sizes of txn_abi_mem and txn_sidecar_mem buffers are 7688 and 18096 bytes.
The maximum number of txns in microblock is 31.
So, when fd_bank_abi_txn_init() will try to process several transactions and store them in these buffers, heap overflow will occur.
To trigger the issue I modified fuzz_txn_parse fuzzer, basically after parsing txn and making sure it is valid the fuzzer tries to store it in pre-allocated buffers.
The logic is exactly the same as in fd_bank_abi_txn_init() function.
Impact Details
Buffer overflow in bank tile during txn processing. Possibility of inter-tile RCE.
Proof of concept
Proof of Concept
How to reproduce:
get archive by using provided gist link
decode and unpack it
$ base64 -d poc.txt > arch.tgz
copy provided fuzzer over fuzz_txn_parse.c fuzzer and build firedancer with 'make -j fuzz-test'
run fuzz_txn_parse fuzzer with included test case:
$./fuzz_txn_parse bug2.bin
Max size 18096
Processing tx 0, footprint_bytes=0
Processing tx 1, footprint_bytes=6304
Processing tx 2, footprint_bytes=12608
Processing tx 3, footprint_bytes=18912
=================================================================
==265260==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x629000004be0 at pc 0x64c9aed8ca98 bp 0x7ffe20d73230 sp 0x7ffe20d73228
WRITE of size 1 at 0x629000004be0 thread T0
#0 0x64c9aed8ca97 in LLVMFuzzerTestOneInput src/ballet/txn/fuzz_txn_parse.c:292:48
#1 0x64c9aecb2543 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) (build/linux/clang/x86_64/fuzz-test/fuzz_txn_parse+0x5a543) (BuildId: 6f80b66d1c683ddf319ed6b58a068270d87210e6)
#2 0x64c9aec9c2bf in fuzzer::RunOneTest(fuzzer::Fuzzer*, char const*, unsigned long) (build/linux/clang/x86_64/fuzz-test/fuzz_txn_parse+0x442bf) (BuildId: 6f80b66d1c683ddf319ed6b58a068270d87210e6)
#3 0x64c9aeca2016 in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) (build/linux/clang/x86_64/fuzz-test/fuzz_txn_parse+0x4a016) (BuildId: 6f80b66d1c683ddf319ed6b58a068270d87210e6)
#4 0x64c9aeccbe32 in main (build/linux/clang/x86_64/fuzz-test/fuzz_txn_parse+0x73e32) (BuildId: 6f80b66d1c683ddf319ed6b58a068270d87210e6)
#5 0x762ce5e29d8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
#6 0x762ce5e29e3f in __libc_start_main csu/../csu/libc-start.c:392:3
#7 0x64c9aec96b84 in _start (build/linux/clang/x86_64/fuzz-test/fuzz_txn_parse+0x3eb84) (BuildId: 6f80b66d1c683ddf319ed6b58a068270d87210e6)
0x629000004be0 is located 816 bytes to the right of 18096-byte region [0x629000000200,0x6290000048b0)
allocated by thread T0 here:
#0 0x64c9aed4ebbe in malloc (build/linux/clang/x86_64/fuzz-test/fuzz_txn_parse+0xf6bbe) (BuildId: 6f80b66d1c683ddf319ed6b58a068270d87210e6)
#1 0x64c9aed8c260 in LLVMFuzzerTestOneInput src/ballet/txn/fuzz_txn_parse.c:264:26
#2 0x64c9aecb2543 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) (build/linux/clang/x86_64/fuzz-test/fuzz_txn_parse+0x5a543) (BuildId: 6f80b66d1c683ddf319ed6b58a068270d87210e6)
#3 0x64c9aec9c2bf in fuzzer::RunOneTest(fuzzer::Fuzzer*, char const*, unsigned long) (build/linux/clang/x86_64/fuzz-test/fuzz_txn_parse+0x442bf) (BuildId: 6f80b66d1c683ddf319ed6b58a068270d87210e6)
#4 0x64c9aeca2016 in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) (build/linux/clang/x86_64/fuzz-test/fuzz_txn_parse+0x4a016) (BuildId: 6f80b66d1c683ddf319ed6b58a068270d87210e6)
#5 0x64c9aeccbe32 in main (build/linux/clang/x86_64/fuzz-test/fuzz_txn_parse+0x73e32) (BuildId: 6f80b66d1c683ddf319ed6b58a068270d87210e6)
#6 0x762ce5e29d8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
SUMMARY: AddressSanitizer: heap-buffer-overflow src/ballet/txn/fuzz_txn_parse.c:292:48 in LLVMFuzzerTestOneInput
Shadow bytes around the buggy address:
0x0c527fff8920: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c527fff8930: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c527fff8940: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c527fff8950: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c527fff8960: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
=>0x0c527fff8970: fa fa fa fa fa fa fa fa fa fa fa fa[fa]fa fa fa
0x0c527fff8980: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c527fff8990: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c527fff89a0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c527fff89b0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c527fff89c0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==265260==ABORTING