Segfault and buffer overrun with aarch64 hashx compiler
Summary
I just found this overrun while fuzzing the hashx program generator. I'll explain below, but while it's worrying I don't think it's exploitable for arbitrary code execution and even a denial-of-service level of exploitation would require a huge amount of luck.
I think we can safely mark this as non-confidential, but let's think about the impact first.
This is applicable to tor daemons (either client or service) running on aarch64, which have the proof of work module available (configured with --enable-gpl
and not --disable-module-pow
, and which use the compiled hashx implementation (CompiledProofOfWorkHash
torrc option not set to 0).
The impact is different for clients and for services. Clients choose their own nonce value just before generating a program, so even if a method were discovered to trigger this bug by choosing a particular HashX seed, this would be negated by the client's choice of a random 16-byte nonce to include in that seed.
Services could be exploited if a method were discovered to trigger this bug by choosing a particular 16-byte nonce value, given the server's existing seed, during the window when that seed is still valid. I think this implies finding a cryptographic vulnerability in blake2b.
The actual issue occurs due to the hashx compiler being cavalier with buffer limits while assembling code. It relies on a constant buffer size without checking at runtime, and that buffer was sized for x86_64 without regard for the larger code size hashx generates on aarch64. The inline immediate encoding on aarch64 is quite large, so programs with a lot of constant operands can overflow what's effectively a hardcoded 1-page buffer. This will almost immediately cause a segfault when we execute code that's larger than one page from a buffer that's only had its first page made executable.
Steps to reproduce:
- Current repro case is on aarch64 linux, using the Arti fuzzers
$ base64 -d | zcat > 6c7b0e5b1fb8965d11227270e892f3d8cd56514d
H4sIAAAAAAAAA4uLA4OwuJmMcQwQNgODAZg+ZXfK9hQGQFH7HwI44k5hUfry1KlYCOscVGccmGZi
gAI5dIUQ+xetXBjD+It0xyDUYig15QbaoYnsGCIA2DhboHFxmHJA43zQjRsyDkV2ErF2IMA0bgYG
VBGwo2FRjcencG0nYE5DSR9YATw8D0IFGMFJh0kdn6eoZjsMRAPxKyLCOetUBJJD4DKkWYQaoRqQ
7HKKiGhWBtqPxRoiI5qBwRRiPG4HIyweEu5EzV+DsdiRt3vgg+k/6uRmBlh8DMYEP5iTzamhkLxH
iyF0dw4256GVPpB6i0FgEJU+2P1HtbbEaDgNyjYXCmfw1AgoFg21smcQuxPEAACQRCtf5Q0AAA==
~/src/arti/crates/hashx/fuzz$ cargo +nightly fuzz run rng ./6c7b0e5b1fb8965d11227270e892f3d8cd56514d
- Observe a segfault at page boundary,
==6024==ERROR: AddressSanitizer: SEGV on unknown address 0xffff94326000 (pc 0xffff94326000 bp 0xffffeaac9910 sp 0xffffeaac9910 T0)
==6024==The signal is caused by a READ memory access.
==6024==Hint: PC is at a non-executable region. Maybe a wild jump?
#0 0xffff94326000 (<unknown module>)
#1 0xaaaae1538f84 in tor_c_equix::HashX::exec::h7063e9b25db75774 /home/mobian/src/tor/src/ext/equix/src/lib.rs:89:22
#2 0xaaaae1538f84 in rng::test_instance_c::_$u7b$$u7b$closure$u7d$$u7d$::h196a66e93420385a /home/mobian/src/arti/crates/hashx/fuzz/fuzz_targets/rng.rs:174:30
- Retry in gdb for more information, observe that the crash is near the end of the hash function (which is only slightly larger than one page)
Thread 1 "rng" received signal SIGSEGV, Segmentation fault.
0x0000fffff7d56000 in ?? ()
...
(gdb) x/30i $pc
=> 0xfffff7d56000: str x6, [x8, #48]
0xfffff7d56004: str x7, [x8, #56]
0xfffff7d56008: ret
0xfffff7d5600c: udf #65535
...
What is the current bug behavior?
Brief overwrite of some memory after the HashX program buffer, almost immediately followed by a segfault.
What is the expected behavior?
We should be using a larger buffer clearly, but we also really shouldn't let hashx be quite so cavalier with the buffer write operations. Even if we aren't checking bounds for every single-byte write, I think we should define an upper limit on bytes-per-instruction and then use that value both to size the buffer and to check bounds just before each instruction is emitted.
Environment
Running tor from main
, on mainline aarch64 linux.