Fully unprivileged VMs with User Mode Linux (UML) and SLIRP User Networking

A few months ago I wanted to test something that involved OpenVPN on an old, small VPS I rented.

The VPS runs on OpenVZ, which shares a kernel with the host and comes with one important constraint:
TUN/TAP support needs to be manually enabled, which on this VPS it was not.

Maybe run a VM instead? Nope, KVM is not available.

At this point it would've been easier to give up or temporarily rent another VPS, but I really wanted to run the test on this particular one.

Enter: User Mode Linux

User Mode Linux (UML) is a way to run the Linux kernel as an user-mode process on another Linux system, no root or special setup required.

At its time it was considered to be useful for virtualization, sandboxing and more. These days it's well past its prime but it still exists in the Linux source and more importantly works.

You'd build a kernel binary like this:

git clone --depth=100 https://github.com/torvalds/linux
cd linux
make ARCH=um defconfig
nice make ARCH=um -j4
strip vmlinux

The virtual machine will require a root filesystem image, you can obtain one via the usual ways such as debootstrap (Debian/Ubuntu) or pacstrap (Arch) which I won't cover here.


Now onto the next issue: How is networking supported in User Mode Linux?

UML provides a number of options for network connectivity [1]: attaching to TUN/TAP, purely virtual networks and SLIRP
TUN/TAP is out of question, a virtual network doesn't help us so that leaves only SLIRP.

SLIP is a very old protocol [2] designed to carry IP packets over a serial line. SLIRP describes the use of this protocol to share the host's Internet connection over serial.
The SLIRP application exposes a virtual network to the client and performs NAT internally.

The standard slirp implementation is found on sourceforge: https://sourceforge.net/projects/slirp/
Its last release was in 2006 and the tarball even includes a file named security.patch that swaps out a few sprintf for snprintf and adds /* TODO: length check */ in other places.
At this point it was obvious that this wasn't going to work.

Rewriting slirp

The only logical thing to do now is to rewrite slirp so that it works.

Although slirp itself is dead the concept lives on as libslirp, which is notably used by QEMU [3] and VirtualBox.
libslirp's API is still a bit too low-level so I chose to use libvdeslirp.
SLIP is a simple protocol and not too complicated to implement, the rest is just passing packets around with Ethernet (un-)wrapping and a tiny bit of ARP.

Here's the code: https://gist.github.com/sfan5/696ad2f03f05a3e13952c44f7b767e81


You'll need:

  • vmlinux: the User Mode Linux kernel

  • root_fs: a root filesystem image

  • /path/to/slirp: the compiled slirp binary (build it using the Makefile that comes with it)

At this point your virtualized Linux system is just one command away:

./vmlinux mem=256M rw eth0=slirp,,/path/to/slirp
Once logged in you need to manually configure the network like this:
ip a add dev eth0 && ip l set eth0 up && ip r add default dev eth0
echo nameserver >/etc/resolv.conf
While you enter these you should see --- slirp ready --- on the console you ran vmlinux on.

You can forward port(s) from outside into the VM by editing the commented out code in slirp.c.