Okay, now that Pierre is here, we can get started.
So it's my pleasure to invite Martin to talk about Rust-based Unikernel.
So combining the two cool words here, Unikernels and Rust and security.
Go ahead, Martin.
Yeah, there were two words.
Okay.
Hi, everyone.
Thanks for coming to our talk.
I'm going to be talking about Rusty Hermit, which is our Rust-based modular Unikernel
for micro-VMs.
Who are we?
This is us.
So there's Stefan, who initiated the project a few years ago.
There's Jonathan, and there's Martin, that's me.
We are from the Institute for Automation of Complex Power Systems at RWTH Aachen University
. Stefan is the academic director, Jonathan is a PhD student, and I'm a master student.
I'm currently writing my master thesis with both Stefan and Jonathan as my supervisors,
and yeah, I'm happy to be able to present our project to you now.
Yeah, just a remark, this project has been funded through EU projects.
Okay, Rusty Hermit.
Rusty Hermit is a library operating system for creating Unikernel images, similar to
what you've seen before with Unicraft, if you were here.
It started as a Hermit Core research project around eight years ago, started by Stefan.
That project was written in C and had a focus on HPC, high-performance computing.
And in 2018, it was completely rewritten in Rust, every component of it, well, and assembly,
but that doesn't count.
Quick recap, Unikernels, very similar to a slide you've seen before presented by Simon.
On the left, we have the classical Linux VM, running on a hypervisor type 2 here.
And we have a fully-fledged operating system inside of the VM image, which is quite large,
and has its own distinction between kernel and user space inside the virtual machine.
Docker containers run on a container runtime, which has their own user space, but share
the kernel with the host system, which makes it faster and more flexible.
Unikernels on the right are very small.
They are created by linking your application against a library operating system to create
a tightly integrated Unikernel image, which can then run on machines, real or virtual
machines in this case.
It has the same isolation from the host or other guests as classical Linux VMs.
And since it's just one application and one process, we have a single address-based operating
system and no distinction between user space and kernel space.
This is really good for performance, because we don't need to do any privileged context
switches, which are costly otherwise.
And we don't have preemptions and don't do interruptions in that case either.
Also, it's very small in this case, because we can just throw away everything we don't
need from the binary and have a runnable hello world image at around half a megabyte.
We also focus on micro VMs. Micro VMs are a special type of virtual machine platform,
which are more bare bones, because we don't need to emulate things like PCI or ACPI.
This of course requires para virtualization, so the guest image needs to be specialized
and know that we don't want to talk about PCI in this case. That can make the unicolonial
image even smaller in some cases.
And let's talk about Rust.
Our unicolonial is written in Rust for a number of reasons.
It's productive, it's fun, and it's safe.
Rust has many modern language features that are really nice to work with compared to C
or other older languages. It has a strong type system, helpful compiler errors, which
are really a bliss if you're coming from C++ template errors.
It's a growing ecosystem. It's being adopted by several big projects. I'm sure you've heard
of Linux adopting Rust at least in some part already upstream.
Rust has also great tooling. There's a very nice package manager that virtually everyone
uses to put their projects into so-called crates in Rust.
And there's great tooling for formatting and linting, for example.
For our case in OS programming, it's also very cool that you can use very much of the
Rust standard library without an operating system, like, for example, a vector for a
growable dynamically allocated array, for example.
The biggest point which really put Rust on the landscape is the last point, which is
that Rust is a safe language. It's the first major systems programming language that guarantees
memory safety. And that's pretty cool because memory safety is hard if you do it manually.
I think if you've programmed C or C++ before, you might have dereferenced a null pointer
and resulted in some sec void or something. And it's very cool if you don't do that.
Just don't.
In big projects like Chromium or other cases, it's been shown that around more than 60%
of vulnerabilities are caused by memory and safety.
And moving those projects to Rust is in the spirit of hoping that that alleviates this
problem.
I have an example, proof of coolness of the Rust language. Just one example that I picked
to demonstrate the modernity and elegance. It's sometimes aka tagged unions.
You can see on the bottom here that there is a generic enum type option, which is either
a none or some and then has some data in it. And in Rust, these types are coupled. So the
some variant of the enumeration contains the data. And it's really nice working with that.
If we have an option as shown at the bottom, we can match this option and then either unpack
the none or the some variant and then reuse it directly.
I've kind of lied to you before because Rust is really two languages. First, there's safe
Rust and unsafe Rust. What does that mean?
Safe Rust is awesome because safe Rust gives us all the guarantees that we want.
Things like accessing invalid pointers, which would result in use after free, double free
or out of bound problems, as well as data races, are classified as undefined behavior
in Rust. And using only safe Rust, these problems can't happen to you.
These problems don't guarantee correctness, though. So things like race conditions, which
are different from data races or logic errors can occur, which is natural, I think.
When doing OS development and other low level stuff, we have a few additional requirements,
though. We might want to do raw memory access for MMIO. We have to sometimes write assembly
code for invoking special CPU instructions. These, unfortunately, cannot be checked by
the compiler for safety invariance. That means this is not possible to do in safe Rust.
This is why unsafe Rust exists. Unsafe Rust is a strict superset of safe Rust. So it means
you can do everything that you can do in safe Rust, but a few things more. But you have
to tell the compiler that you promise to be extra careful and don't do any bad stuff.
You have special superpowers, then. You can access raw pointers and call unsafe functions,
which is required for inline assembly, for example. At the bottom, you can see how we
can access raw pointers or write inline assembly, which, if we are not careful, might really
do bad stuff. And this is why we have to put this in unsafe blocks. That means, if something
goes wrong, we can just grab for any unsafe things and rethink if we did everything correctly
there. When writing this unsafe code, we have to be sure not to violate Rust's fundamental
soundness property, which says that no matter what, safe Rust cannot cause undefined behavior.
And if we encapsulate some unsafe code in some safe function, we have to make sure that
this API cannot be misused in any way. Okay. Enough about Rust. Let's talk about Rusty
Hermit again. Rusty Hermit is tightly integrated with the Rust language. It's our first language
of choice for applications and very specialized. Now I'm going to show you how you would port
a Rust application that runs on Linux to Rusty Hermit, which is really easy, I think. But
let's see. We have a few requirements. Rust up. The first one is the Rust toolchain manager
that virtually every Rust developer has already installed. We then need, of course, a hypervisor
of our choice. We can either use the ubiquitous QEMU or U-Hive. U-Hive is a specialized hypervisor
created by us in Rust, of course, that is specialized for the Rusty Hermit operating
system to have really fast API between those two. If we are compiling with simultaneous
multiprocessing for Intel processors, we also need nothing, but that's not important
if you don't need that. Okay. This is a bare-bones Rust project. We have a cargo tumble, which
is a manifest file for the cargo package manager, which describes the package metadata, and
it just says hello world, version, addition, something. Not very important. We have then
our main source file, the main RS, which is just a main function and prints hello world.
Everything that we need to do to get Rusty Hermit support is first add a Rusty Hermit
dependency. It's written a bit complicated to just include this dependency if we actually
compile for the Hermit operating system. Then we just need to add two more lines to
the main RS to import this dependency. What this does then is that Hermit sys in the background
transparently builds the Hermit kernel, the library operating system, and then by importing
it like this, we make sure we actually link against this. What we then get is a runnable
unicarnal image that can be run in Quemo or U-Hive. To then build this, we have to pin
a Rust compiler version because we have some internal things that require that, but we're
working on getting rid of that and then just build it. We say cargo build, then specify
the Hermit target, which is our target, and then we tell it to build the standard library
on the fly because we are small yet and only tier three target, which is why Rust does
not support us natively yet, but we support Rust. There was easy. To make sure that all
of you can believe me, I have prepared a small demo. I have to get on this screen. Right
here you can see exactly the project I talked about. It's just a hello world with the Hermit
CSS dependency. It's a main RS, which does hello world. Then we can go ahead and open
a terminal, then do cargo build, which is really fast right now because I pre-built
it. Normally it takes around one minute on this machine I'm logged into. Then we can
run it on you have hello world. To make sure that we didn't cheat, I can also show you
the verbose messages, which tells you I have to please print the kernel messages along
with it. We can see that there's Rust, the Hermit booting and initializing all the hardware
and preparing the memory and everything and then in the end jumping into our application
and printing hello world. After that, there's just shut down. Okay, back to the presentation.
Yes. Okay, now a bit about our modularity story in Rusty Hermit. There are several modularity
stories. The first one is user facing. This is the same similar dependency declaration
in our cargo manifest as before, but a little bit expanded. We added features. Features are
a thing in the cargo package manager that allows us to select and configure conditional
compilation in our dependency. In this case, Hermit's is. We use this to be able to specify
in this manner which features we want to be present in the unicolonel image. In this case,
I enabled SMP, TCP and DHCP4 and disabled PCI and ACPI. This means that this should
be runnable in a micro VM, for example, with no PCI support present. Internally, we also
quite modular and we're working on further modularizing our kernel. At the top, you
can see the lib Hermit kernel, which has a few dependencies. The first one is a internal
Hermit entry dependency, which is shared between the kernel and anything that loads and jumps
into the kernel. We then have Hermit sync for internal collection of synchronization
primitives like mutexes. The other crates are really provided by the Rust ecosystem, which
is really rich. The linked list allocator is our allocation algorithm that we just import
and then use. We can also just import and use some device drivers or architecture-specific
abstractions so that we don't even have to write assembly code ourselves. Also, small
TCP is our TCP stack. Just import it and configure it. We also contribute back upstream, which
is cool, but this shows the strength of the Rust ecosystem and community for Rust OS development,
I think. In the end, this is a broad overview of the Hermit ecosystem as it is today.
On the left, you can see a unicorn image that has been built. At the top, we have the application.
It's either a Rust application or a C application, although Rust application is our primary focus,
which then either uses the Rust standard library or a new C library. Those are then customized
by us to invoke the special syscalls into the kernel to do the required functionality,
and this altogether then makes up the unicorn image. This can then be run on either our
specialized virtual machine monitor, U-Hive, or a generic VM like Kimu. For Kimu, we have
a Rusty loader, which then chain loads our unicorn image, and Rusty loader supports
some boot protocols, as you can see here. That's been the main part. What are we working
on right now? I'm working on the first three things. Further code-based oxidization, which
means making it more Rusty. That means applying more Rust idioms more thoroughly, because
there have been a few C-isms that we've been stuck with from the original part. I'm personally
also working on Miri support, also as part of my master thesis. Miri is an interpreter
for Rust, which initially sounds strange, but using Miri, we can spot a few cases of undefined
behavior if we do something wrong in unsafe code. If something runs in Miri, though, that
doesn't mean that this is guaranteed to be correct, but it can help us in some cases.
Third point is more modularization, and I already talked about that. It's about spinning
out internal drivers, for example, in separate projects and crates. Then TCPI stack overhaul
is something that Stefan is currently working on, and U-Hive network overhaul is something
that Jonathan oversees. We are also generally working on firecracker support and arm support,
both of which have working prototypes, but have not really been mainline that much.
Please find us at GitHub. We are always happy to have conversations and contributions. Yeah,
that's been it. Thanks for listening.
Okay, any questions for Martin? Unikernels, raw security. All righty.
There's one. Yeah, I just want to know what the subprime
focus of this project. So do you have some industry which is already picking up on Hermit,
or is it pure science so far? What are the plans?
As far as I understand, it started as a research project, and it's much there now, I think,
Stefan? Yeah, it's still in research project, but we use it in two U-projects, and they
are mostly partners from the cloud area and edge computing, and we want to use it here.
Thanks. Hey, thank you for your talk. I have a question.
As far as I know, the original C implementation supported quite a few more targets than only
Rust and C. As far as I remember, you could run Go code as well, and Fortran, and some
other stuff that linked against G-Lib C, if I'm remembering correctly.
New Lib, I think. And New Lib, is there any plan to open up your
targets as well for the new Rust implementation to support some more stuff, not only Rust
and C? So as far as I've been there, it's been only
Rust. I'm not that old into the project. I'm not sure what the plans are on further
supporting that. We currently have bare-bound support for C, and I don't think the Go implementation
is currently working, and it's possible to get it working, but we are not really working
on that actively, I think. So, any plans for RISC-5 support?
We have a quick press from RISC-5 support. This is also done by two students, but didn't
need time to analyze this. So it's there, but a lack of time.
Okay, so proof of concept is working, but not upstream yet.
This question obviously has to be asked, is there async support?
Is there what? Async support.
Async.
Rust async. We have a runtime, or like async runtime.
I think not mainline yet, right?
So the kernel uses it internally for networking, and I think the exposure to user space via
Mio or something is not merged upstream, but it's something that we are actively interested
in. Anything else? If not, thank you again, Martin.
Thank you all for coming.