Most of the popular Linux distributions don’t offer any sandboxing or anonymization capabilities and it can be quite difficult to find a good solution. In this post I’m going to describe how I manged to sandbox the messenger app Signal and tunnel all it’s traffic through the anonymization network Tor.

All the tool you need are already in the Archlinux repositories:

pacaur -S firejail tor signal nftables

Firejail is a kind of wrapper around sandboxing capabilities of the Linux kernel. It ships with profiles for various applications, including a profile for Signal.

To launch Signal in a sandboxed environement, just prepend the command firejail like this:

firejail signal-desktop

If you try to share files with someone, you’ll notice that your local files aren’t available anymore to Signal. One of the few “shared” and real directories left is the Signal configuration directory in ~/.config/signal. All files in there will be preserved, even after you close the sandbox. As a lazy workaround I’ll temporarily move files into this directory if I want to share them via Signal.

To isolate the sandbox from your local network and tunnel all traffic through Tor is a bit more difficult. First of all, we have to create a virtual networking bridge with an own subnet:

Kind=bridge

[Match]
Name=tornet

[Network]
Address=10.100.100.1/24
ConfigureWithoutCarrier=true
IgnoreCarrierLoss=yes

Now start and enable the network service to make these changes persistent:

systemctl enable --now systemd-networkd

We also need to enable IP forwarding for the tornet network bridge:

net.ipv4.conf.all.forwarding=1
net.ipv4.conf.tornet.route_localnet=1

In the Tor configuration, we have to enable the a local port to which we can route our internet traffic:

Log notice file /var/log/tor/notices.log
#AutomapHostsSuffixes .onion,.exit
#AutomapHostsOnResolve 1
TransPort 9040
VirtualAddrNetworkIPv4 172.30.0.0/16
DNSPort 5353 IsolateDestAddr
ControlPort 9051
DataDirectory /var/lib/tor

It is than useful to autostart Tor at boot time:

systemctl enable --now tor

Now we need to apply some Nft firewall rules for routing. Create following file and import it with the nft command. Replace any occurrence of “wlan0” with the name of your network interface connected to the internet.

table ip mangle { # handle 13
	chain PREROUTING { # handle 1
		type filter hook prerouting priority mangle; policy accept;
	}

	chain INPUT { # handle 2
		type filter hook input priority mangle; policy accept;
	}

	chain FORWARD { # handle 3
		type filter hook forward priority mangle; policy accept;
	}

	chain OUTPUT { # handle 4
		type route hook output priority mangle; policy accept;
	}

	chain POSTROUTING { # handle 5
		type filter hook postrouting priority mangle; policy accept;
	}
}
table ip nat { # handle 14
	chain PREROUTING { # handle 1
		type nat hook prerouting priority dstnat; policy accept;
		iifname "tornet" udp dport 53 counter packets 0 bytes 0 dnat to 127.0.0.1:5353 # handle 5
		iifname "tornet" ip protocol tcp counter packets 0 bytes 0 dnat to 127.0.0.1:9040 # handle 6
	}

	chain INPUT { # handle 2
		type nat hook input priority 100; policy accept;
	}

	chain OUTPUT { # handle 3
		type nat hook output priority -100; policy accept;
	}

	chain POSTROUTING { # handle 4
		type nat hook postrouting priority srcnat; policy accept;
		oifname "wlan0" ip saddr 10.100.100.0/24 counter packets 0 bytes 0 masquerade # handle 7
	}
}
table ip filter { # handle 15
	chain INPUT { # handle 1
		type filter hook input priority filter; policy accept;
		ct state invalid counter packets 0 bytes 0 drop # handle 4
		ct state established,related counter packets 24 bytes 2247 accept # handle 5
		iifname "tornet" tcp dport 9040 counter packets 0 bytes 0 accept # handle 6
		iifname "tornet" udp dport 5353 counter packets 0 bytes 0 accept # handle 7
	}

	chain FORWARD { # handle 2
		type filter hook forward priority filter; policy drop;
		iifname "tornet" oifname "wlan0" ip protocol tcp counter packets 0 bytes 0 accept # handle 8
		iifname "tornet" oifname "wlan0" udp dport 53 counter packets 0 bytes 0 accept # handle 9
	}

	chain OUTPUT { # handle 3
		type filter hook output priority filter; policy accept;
	}
}

Following commands will import and store the rulset permanently. Be sure to not overwrite existing configurations!

nft flush ruleset
nft -f /tmp/firewall.nft
nft list ruleset > /etc/nftables.conf
systemctl enable --now nftables

You can now run Signal sandboxed and in an isolated network where all traffic is going through Tor:

firejail --net=tornet signal-desktop

Signal won’t have any connection if the Tor daemon isn’t running or when Tor is blocked in your network. You can also use the program arm to check if all traffic is going through Tor.

I’m not entirely sure if DNS queries are also anonymized in this setup but according to the original how-to by kargig this should also be the case.

It is important to note that this setup just adds an extra layer of security and anonymity in using Signal. If you strongly rely on anonymity you should consider using Tails or SubgraphOS as pointed out by the security researcher x0rz. His blog post also explains how to register Signal with a fake mobile number to use it pseudonymously.

Updates:

  • 20.08.2019: Fix systemd-networkd device configuration that it will persist on carrier loss. Switch iptable- to nftable-rules.

💬 Are you interested in our work or have some questions? Join us in our public Signal chat pi crew 👋
🪙 If you like our work or want to supprot us, you can donate MobileCoins to our address.