Allium

Allium (Arti library linked in unikernel mode) is a project in the earliest stages of getting Arti—the Rust rewrite of Tor—working with Hermit—the unkiernel project aimed at Rust. So far, nothing works, and most all the associated code is in a hacky state, so either private or unlisted. Once it's in a state that it makes sense for other people to help out, I'll add a link to the project's git repos here.

What is a unikernel?

Expand this if you'd like a high-level overview of unikernels. A modern operating system is a complicated beast. Your kernel needs implementations of every filesystem a typical user is decently likely to run into, support for every major platform on your given CPU architecture, network drivers for all major network cards, as well as implementations for all the major link, network, and transport layer protocols (as well as some application-layer protocols these days), support for peripherals like graphics, keyboards, and printers, and, for the most common OSs, infrastructure for hot-swapping less common drivers in when the user plugs in one of the unfathomably diverse devices that are expected to just work. Meanwhile, significant time, memory, and code is spent managing multiple processes. Each process needs its own virtual memory, with pages allocated and managed by the kernel in real time to provide the illusion of a single address space that is not shared by other applications (except for when you want part of it to be shared, of course). Resources like files need to be checked for security permissions to ensure only the correct processes with the correct properties can do the given operation, and because managing all these processes gets complicated, we add more processes that manage those processes, and processes that manage those, all being run concurrently with CPU time managed by some algorithm determining what gets priority. All the while, we perform a series of instructions to switch in and out of the kernel's operation, adding overhead every time we need to do any of the above.

Now suppose we want to run some process in a virtual machine. Maybe we need improved security, maybe there's a performance consideration, or maybe that's the cheapest hosting possible, but we have one process, and it needs its own VM. The default way to do this is to run all this complicated machinery, even though we clearly don't need most of it. There's only one process we care about, so we don't need virtual memory, or scheduling between processes, or permissions, or daemons. We might need support for one or two file systems, and a couple network protocols, but we don't need graphics drivers, or printer drivers, or support for all the possible platforms one might encounter in the real world.

This is what unikernels solve. Instead of running your application as a process in an OS, you link it against a library OS (i.e., a library designed to provide support for virtual "hardware", network protocols, etc., that would typically be supported by kernel drivers), use some configuration options to enable and disable features you do or don't need, and let link-time optimization perform dead code analysis to remove any features your code won't use.

Your application is the kernel now.

Why a Tor unikernel?

Tor is an application well-suited for a unikernel. The only things a Tor process really needs is TCP network support, and basic file storage for logs, configuration files, and a bit of state (guard relay selection, consensus data, etc.).

It's also commonly recommended to run Tor processes in their own VM. On the desktop side, this is how Qubes OS, SecureDrop Workstation, and Whonix work. On the server side, this is typically a more bespoke operation, set up manually. A major advantage of this for both client applications and onion services is the improved security of Tor running on its own network interface, simplifying the process of ensuring any other network requests don't leak, as well as isolating vulnerabilities in each VM. Onion services also sometimes need the performance benefit of tools like Onionbalance that involve running multiple onion services for one logical service. Running an entire operating system to run the one process is a lot of overhead, and can be a problem for desktop systems with limited resources, or be expensive for onion services.

To summarize, the benefits of a Tor unikernel include:

  • Reduced attack surface. Unnecessary processes and kernel components are removed entirely from the VM, so cannot be attacked.
  • Reduced resource requirements/overhead to run Qubes OS and similar desktop OSs.
  • Reduced leakage. With secondary Tor VMs becoming more viable, more services can rely on them for all their advantages.
  • Lighter relays. No OS means cheaper or faster relays (on the low end, at least—the largest relays might enjoy the reduced attack surface, but unless context switches are causing measurable overhead, the OS is probably not the performance constraint).
  • Agile relays. Fast boot times and low resource requirements means relays can be spun up, taken down, and migrated quickly, which can be useful for bridges.

Why Hermit?

Hermit is specifically designed to run Rust applications. Since Tor is going to be switching to Arti as its recommended client (presumably sometime in the near future), the two are an obvious match. However, there is a lot of work that needs to be done in Hermit and the Rust ecosystem before it's viable for using with Arti. Using a fully featured program like Arti as a motivating case can help reveal a lot of the pain points in Hermit as it exists now, where fixes in functionality and libraries that Arti relies on translate directly into expanding the practical uses of Hermit across the entire Rust ecosystem.

If this interests you, feel free to reach out.