Paranoid Penguin - Running Network Services under User-Mode Linux, Part II
Here in the Paranoid Penguin column, we're in the midst of building a virtual network server using User-Mode Linux. Last month, I explained why this is a good idea, how it works, how to prepare your host for optimized User-Mode Linux operation and how to build a kernel for your guest (virtual) system(s).
This month, we turn our attention to the guest system: how to obtain a prebuilt root filesystem image, how to configure networking on both your host and guest systems, and how to begin customizing the root filesystem image for your own purposes.
First, here's a quick review of what we're trying to do, in case you missed last month's column. Our objective is to use User-Mode Linux to create one or more virtual guest machines, each running a different network service. That way, if one application (for example, BIND) on one guest machine gets compromised somehow, Sendmail, Apache and whatever else you've got running on other guest systems (or on the underlying host system itself) won't be affected.
(Per User-Mode Linux convention, we're using the word host to denote a system on top of which virtual machines run and the word guest to denote a virtual system instance.)
Debian is our somewhat arbitrary choice here for both host and guest systems, due to the ease with which you can create bare-bones Debian installations, though User-Mode Linux itself is decidedly distribution-agnostic. We'll create a single guest system, running BIND software for DNS services.
On the strength of last month's procedures, hopefully you've got a skas-enabled host kernel and a guest kernel compiled for the um architecture. Now, it's time to acquire or build a root filesystem image.
When your Linux host starts up, it learns where / is via the root command-line switch; somewhere in lilo.conf or menu.lst is a kernel-invocation line containing something like root=/dev/hda1. That's how it works with User-Mode Linux too, except that rather than a physical hard disk, such as /dev/hda, we generally use a virtual disk in the form of a single flat file, called a root filesystem image.
The root filesystem image contains a complete Linux distribution. You've already created similar image files yourself if you've ever copied a CD-ROM to an ISO file (or vice versa). Using a filesystem that takes the form of a single file has two important ramifications for User-Mode Linux: first, it helps keep your guest system relatively compact and portable; second, it makes change control as simple as tracking changes to a single file, via the COW file method.
Suppose I start a User-Mode Linux guest with this command:
umluser@host:~> ./guestkernel ubd0=mycow,my_root_fs root=/dev/ubda
Note the umluser@host prompt. I'm executing this command from a shell session to which I'm logged in as a regular user, not root. guestkernel is my executable User-Mode Linux guest kernel; ubd0 is a virtual disk device I'm declaring to consist of the image file my_root_fs plus a change-on-write (COW) file called mycow. The root switch defines our root partition to be the virtual disk ubda (identified by its full path, /dev/ubda).
Somewhat confusingly, by convention, virtual disk declarations use numbered device names (ubd0, ubd1 and so on), but root filesystem definitions use the corresponding letters instead (ubda, ubdb and so on), which are synonymous. The command ./guestkernel ubda=mycow,my_root_fs root=/dev/ubda actually works just as well on my SUSE system as the above command, but your distribution of choice may behave differently.
Strictly speaking, the COW file is optional. If you specify one, changes you make to the image file during your User-Mode Linux session will be written to the COW file rather than to the disk image itself. If you omit the COW filename, the image file will be written to directly by the guest kernel—that is, any changes you make to your guest system will be “permanent”.
As far as I'm concerned, when using UML in security scenarios, COW files are mandatory. A key assumption in using User-Mode Linux for hosting a network service is that this service may be compromised in some way, and if it is, you'll want to be able to recover as quickly as possible. If you use a COW file, all you'll need to do to restore a guest system to its baseline state is delete the old COW file and create a new (empty) one.
Another key advantage of using COW files is that they allow you to use the same root filesystem image on more than one guest system simultaneously. All you need to do is specify a different COW file each time you bring up a guest kernel. In fact, you can use both the same image file and the same kernel for multiple guests. As you can guess, we're going to use a COW file in our example scenarios.