[tor-talk] Linux kernel transproxy packet leak (w/ repro case + workaround)
mikeperry at torproject.org
Fri Mar 28 21:34:58 UTC 2014
Velope on IRC suggested a better workaround. It turns out these
connections actually end up in state INVALID when the transproxy side
dies. I tested this with my repro case and confirmed that the --ctstate
rule is working by itself.
Additional iptables rules inline below. Preserving full original
text for historical record.
> Hello all,
> I've discovered that the Linux kernel appears to have a leak in how it
> applies transproxy rules to the TCP CLOSE_WAIT shutdown condition under
> certain circumstances. This applies to both the kernels in use by common
> Android devices (Cyanogenmod 10.x and 11-M4), as well as the Linux
> kernel in Ubuntu 13.04 (3.8.0-35-generic).
> The bug can be triggered either by a remote server closing a connection,
> or by restarting the local tor client.
> Basically, the bug happens when a transproxy connection shuts down
> completely before a client application properly closes the socket. This
> seems to cause the kernel to lose track of the fact that the client
> application connection was being transproxied, and when the client
> application finally does close its socket (or exits), the Linux kernel
> generates a FIN ACK that completely bypasses any transproxy rules you
> have installed. It sends this packet first as the UID of the app in
> question, and if that fails, it resends it as a blank UID (the kernel
> Here's how to reproduce it and see for yourself:
> First run the attached iptables script, and launch a tor daemon with the
> attached torrc (edit the iptables script's TOR_UID=`id -u debian-tor`
> and NETWORK_USER_ID=1000 vars if your setup is different).
> Then, fire up tcpdump, like so:
> sudo tcpdump -n -i wlan0 host 220.127.116.11 and tcp port 80
> Replace '-i wlan0' with your network interface. If you use '-i any', you
> will also see transproxied packets (which are not normally leaked).
> Then, as your transproxied user, paste this python snippet into a python
> import socket
> s = socket.create_connection(("18.104.22.168", 80))
> (That IP handles www.google.com).
> After the connection is made, you should see something like the
> following in 'netstat -natp':
> tcp 0 0 127.0.0.1:9040 192.168.1.23:42235 ESTABLISHED 1121/tor
> tcp 0 0 192.168.1.23:42235 22.214.171.124:80 ESTABLISHED 977/python
> At this point, either wait a couple minutes for Google to close that
> connection on you, or shut down your Tor daemon. In either case, you
> should see the first connection transition to TIME_WAIT, or FIN_WAIT2,
> or similar, lose track of its PID+UID, and then finally disappear
> entirely, but the python program will remain in CLOSE_WAIT indefinitely.
> Once the first connection is fully gone (this takes 60s from TIME_WAIT
> state with default TCP settings), issue this in your python shell:
> At this point, you will see a FIN ACK or RST ACK packet appear in your
> tcpdump window. That packet has leaked past the iptables firewall rules,
> and past the transproxy rules. It went straight to Google.
> I have noticed several Android apps (including Firefox, F-Droid, and
> many Google apps and Android services) that allow their sockets to sit
> in CLOSE_WAIT upon remote close while transproxied, and they all leak
> packets in this case, which happens frequently in normal usage. I am not
> sure if this is just a common programming error, an issue with how the
> Android networking APIs are designed, something specifically exacerbated
> by the transproxy, or some combination of these.
> For a workaround, I was able to prevent this issue with the addition
> of the following rules:
> iptables -I OUTPUT ! -o lo ! -d 127.0.0.1 ! -s 127.0.0.1 -p tcp -m tcp --tcp-flags ACK,FIN ACK,FIN -j DROP
> iptables -I OUTPUT ! -o lo ! -d 127.0.0.1 ! -s 127.0.0.1 -p tcp -m tcp --tcp-flags ACK,RST ACK,RST -j DROP
Here's a set of rules to try both --ctstate and --state invalid, as well
as log which ones get hit, for testing purposes. Note the use of -A in
this case, for readability wrt ordering. These rules should come before
any other rule in the OUTPUT chain section of the firewall script you
#iptables -A OUTPUT -m conntrack --ctstate INVALID -j LOG --log-prefix "Transproxy ctstate leak blocked: " --log-uid
iptables -A OUTPUT -m conntrack --ctstate INVALID -j DROP
iptables -A OUTPUT -m state --state INVALID -j LOG --log-prefix "Transproxy state leak blocked: " --log-uid
iptables -A OUTPUT -m state --state INVALID -j DROP
iptables -A OUTPUT ! -o lo ! -d 127.0.0.1 ! -s 127.0.0.1 -p tcp -m tcp --tcp-flags ACK,FIN ACK,FIN -j LOG --log-prefix "Transproxy leak blocked: " --log-uid
iptables -A OUTPUT ! -o lo ! -d 127.0.0.1 ! -s 127.0.0.1 -p tcp -m tcp --tcp-flags ACK,RST ACK,RST -j LOG --log-prefix "Transproxy leak blocked: " --log-uid
iptables -A OUTPUT ! -o lo ! -d 127.0.0.1 ! -s 127.0.0.1 -p tcp -m tcp --tcp-flags ACK,FIN ACK,FIN -j DROP
iptables -A OUTPUT ! -o lo ! -d 127.0.0.1 ! -s 127.0.0.1 -p tcp -m tcp --tcp-flags ACK,RST ACK,RST -j DROP
It's likely only the first pair is needed, and you may want to comment
out the --ctstate LOG line as I did to limit noise for successfully
handled --ctstate INVALID DROP blocks.
I did test this with the above repro method, and --ctstate INVALID did
appear sufficient by itself, but reports of any --ctstate DROP rule
bypass happening will be tremendously useful (which will result in the
later LOG lines being hit, and sending output to 'dmesg').
> None of the transproxy documentation I could find mentions this issue,
> nor suggests any additional safety rules. This means every transproxied
> Tor user is unwittingly leaking packets, at least some of the time.
> Sorry to be the bearer of bad news.
> Please send workaround discussion to tor-talk, and kernel/TCP state
> machine discussion to tor-dev. I Cc'd both like a jerk, because I figure
> each group might have different sets of commentary, and both groups
> should be aware of this issue. Don't be a jerk like me, please. Use your
> best judgment to Cc one list or the other.
> Mike Perry
> TOR_UID=`id -u debian-tor`
> # Clear existing rules
> $IPTABLES -F INPUT
> $IPTABLES -F OUTPUT
> $IPTABLES -t nat -F
> ## Transproxy rules for Tor
> $IPTABLES -t nat -A OUTPUT ! -d 127.0.0.1 -m owner ! --uid-owner $TOR_UID -p tcp -j REDIRECT --to-ports 9040 || exit
> $IPTABLES -t nat -A OUTPUT -p udp -m owner ! --uid-owner $TOR_UID -m udp --dport 53 -j REDIRECT --to-ports 5300 || exit
> # Allow Tor and the network user
> $IPTABLES -A OUTPUT -m owner --uid-owner $TOR_UID -j ACCEPT || exit
> $IPTABLES -A OUTPUT -m owner --uid-owner $NETWORK_USER_ID -j ACCEPT
> $IPTABLES -A INPUT -j LOG --log-prefix "OUTPUT DROPPED: " --log-uid || exit
> $IPTABLES -A OUTPUT -j DROP || exit
> # Create INPUT firewall. Allow established connections and transproxy
> $IPTABLES -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT || exit
> $IPTABLES -A INPUT -i lo -j ACCEPT # Transproxy output comes from lo
> $IPTABLES -A INPUT -d 127.0.0.1 -m udp -p udp --dport 5300 -j ACCEPT || exit
> $IPTABLES -A INPUT -j LOG --log-prefix "INPUT DROPPED: " --log-uid || exit
> $IPTABLES -A INPUT -j DROP || exit
> RunAsDaemon 1
> DataDirectory /var/lib/tor
> Log info file /var/lib/tor/log
> User debian-tor
> DNSPort 5300
> TransPort 9040
-------------- next part --------------
A non-text attachment was scrubbed...
Size: 801 bytes
Desc: Digital signature
More information about the tor-talk