Using Linux Network Namespaces to Prevent Tor Leaks
This post is about safely isolating individual processes in such a way that the only kind of network connection they can successfully establish is through Tor.
A network namespace is logically another copy of the network stack, with its own routes, firewall rules, and network devices. Network Namespaces are administrated with the ip(8)
command. Specifically the netns
“OBJECT”. Documentation is available through man ip netns
.
To create a new network namespace called nstor
we use the add
command:
† ip netns add nstor
We can list network namespaces with the list
command:
† ip netns list
nstor
Notice that there is no default namespace listed, we will look into that later.
We can then execute commands in this new network namespace using the exec
command. Let’s spawn a shell just for demonstration purposes:
† ip netns exec nstor bash
† ifconfig
† ifconfig -a
lo: flags=8<LOOPBACK> mtu 65536
loop txqueuelen 1 (Local Loopback)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
According to the documentation, this lo
interface is different from the real lo
interface. We can test this using netcat
. Using 2 terminal windows:
On the server side:
† nc -l 127.0.0.1 -p 8888
On the client side:
† nc 127.0.0.1 8888
And anything you type in one terminal window will appear in the other. This time execute the client nc
inside the new network namespace:
† ip netns exec nstor nc 127.0.0.1 8888
Nothing happens. They really are different.
This in and of itself is useful for cutting off internet connection to individual applications.
As an aside, remember that the list
command only returned our new namespace and not the default one:
† ip netns list
nstor
But what if we are inside a network namespace and want to get back to the default?
From the ip-netns
man page:
By default a process inherits its network namespace from its parent. Ini‐
tially all the processes share the same default network namespace from the
init process.
Unfortunately it does not seem possible to use the ip
tool for this purpose:
ip netns exec automates handling of this configuration, file convention
for network namespace unaware applications, by creating a mount namespace
and bind mounting all of the per network namespace configure files into
their traditional location in /etc.
However there is a different tool called nsenter
with a different interface. It uses the setns
system call with special files under /proc/$PID/ns/net
as an argument, allowing us to join the same network namespace as any other process. Remember that the default network namespace comes from init
which is pid 1:
† nsenter --help
Usage:
nsenter [options] <program> [<argument>...]
Run a program with namespaces of other processes.
Options:
-t, --target <pid> target process to get namespaces from
...
-n, --net[=<file>] enter network namespace
...
For more details see nsenter(1).
† nsenter -n -t1
† ifconfig
wlan0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 192.168.0.3 netmask 255.255.255.0 broadcast 192.168.0.255
...
That’s neat but our goal is to run Tor so some link to the world outside the network namespace is required. For this we can use a Virtual Ethernet pair:
# server
† ip link add vethtor0 type veth peer name vethtor1
† ip address add 10.0.1.1/24 dev vethtor0
† ip link set vethtor0 up
† ip link set vethtor1 netns nstor
† nc -l 10.0.1.2 -p 8888
# client
† ip netns exec nstor bash
† ip address add 10.0.1.2/24 dev vethtor1
† ip link set vethtor1 up
† nc 10.0.1.1 8888
By moving one Virtual Ethernet interface into the network namespace and giving them both an IP address with the same network prefix (10.0.1) we have created a (very limited) connection to the outside world; programs running inside the nework namespace (10.0.1.2) will be able to connect to a program listening on 10.0.1.1 outside of the network namespace.
The final step is to simply run Tor, outside of the network namespace, and have it listen on 10.0.1.1. Any program running inside the network namespace will have no way of contacting the outside world except through Tor, and only if it is configured to connect through 10.0.1.1. This effectively stops any accidental† leaks such as through Flash or Java or some non-browser application you want to torify.
† I say accidental here because as we saw earlier, it is still possible for the root user to switch back to the default namespace. If you are looking for possibly stronger security then check out Firejail.
Full example:
Setup:
# Create a new Network Namespace called nstor
† ip netns add nstor
# Create a new Virtual Ethernet Pair called vethtor0 and vethtor1
† ip link add vethtor0 type veth peer name vethtor1
† ip address add 10.0.1.1/24 dev vethtor0
† ip link set vethtor0 up
# Move vethtor1 interface to the nstor Network Namespace
† ip link set vethtor1 netns nstor
# Now that the vethtor1 interface is in a different Network Namespace, we need
# to use these nested `ip` commands.
† ip netns exec nstor ip address add 10.0.1.2/24 dev vethtor1
† ip netns exec nstor ip link set vethtor1 up
To test:
† cat > torrc << EOF
User tor
PIDFile /var/run/tor/tor.pid
Log notice stderr
DataDirectory /var/lib/tor/data
SOCKSPort 10.0.1.1:9150
RunAsDaemon 1
EOF
† tor -f torrc
† ip netns exec bash
† ifconfig
vethtor1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 10.0.1.2 netmask 255.255.255.0 broadcast 0.0.0.0
ether ae:ea:31:a3:e1:39 txqueuelen 1000 (Ethernet)
RX packets 90 bytes 74537 (72.7 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 89 bytes 7296 (7.1 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
† ping 8.8.8.8
connect: Network is unreachable
† curl example.com
curl: (6) Couldn't resolve host 'example.com'
† curl --socks5-hostname 10.0.1.1:9150 example.com
<!doctype html>
<html>
<head>
<title>Example Domain</title>
...