lockdown and the di-semi-default route

i’ve been working on a vpn client lately. i’ve invented (i think) two pretty simple tricks that are worth sharing.

lockdown

the first thing is a method for locking down a process to not have any filesystem access. the idea is a pretty simple twist on chroot() to an empty directory.

  • chdir() to /tmp
  • make a directory in /tmp with mkdtemp()
  • chroot() to this new directory
  • rmdir() the temporary directory (the current directory, unaffected by the chroot, is is still /tmp)
  • chdir(“/”) to drop any reference to the old filesystem
  • setuid() to a non-privileged account

effectively you now have your process’s root directory as a non-existent directory.

this seems pretty secure. even access(“/”) fails. it also has the added advantage of not requiring a static empty chroot directory (ala /var/run/sshd).

di-semi-default route

one problem faced by vpn clients that want to set the default route is how to manage to ensure packets still get delivered over the normal network to the vpn server (ie: no infinite loop). another problem is how to restore the normal default route when the vpn client exits (or crashes).

the first problem is usually solved by adding an explicit route to the vpn server using the default gateway. for example, if the default gateway on the network was 192.168.0.1 and the vpn server had an address of 209.132.176.176 then one would add a route for 209.132.176.176 gateway 192.168.0.1. no changes here.

the normal method of setting the default route is to delete the current default route (perhaps remembering what it was) and then setting a new route to the network interface created by the vpn program. when the program exits it may restore the old default route. if the program crashes or is kill()ed then you lose.

my approach is to setup something that i’m humourously calling the “di-semi-default route”. essentially, instead of deleting the old default route and replacing it, you add two new half-default routes. say the vpn interface is called vpn0:

  • route 0.0.0.0 netmask 128.0.0.0 to vpn0
  • route 128.0.0.0 netmask 128.0.0.0 to vpn0

these routes do not conflict with the default route and because the kernel matches routes with tighter netmasks first, they will get matched before the default route. together, they cover the entire ip address space (the first covers all addresses starting with 0-127 and the second covers all address from 128-255). the really nice thing is that when the ‘vpn0’ interface disappears then so do the routes, re-exposing the normal default route.

update: an attentive commenter, craig box, noted that the “usual” method that i use (and is used by software that he packages for ubuntu) is flawed. it fails to take into account the case where the vpn server is on the same local network as the laptop. in this case, it is an error to send the packets to the default gateway.

the method i now use to deal with this is open /proc/net/route and walk through it until i hit a match for the ip of the vpn server (it is sorted by netmask). once i hit a match i only add the new route if the line i hit was the default route.

thanks, craig :)