Right, okay, thank you, everyone, for staying till the end, as I said before, I know it's
tempting to go get some beers and Sunday afternoon, but you stuck out, stuck with us.
All these are going to talk about writing an application kernel in Rust, and let's give
the last of your Sunday energy to welcome him.
Okay, this is work, yeah?
All right, hello, everyone.
This is Sid Reipen and application kernel in Rust.
Again, thank you for staying until this late hour.
I hope I'll make it as entertaining as for you as possible, but it's a bit of a
difficult subject, so bear with me.
So I'm a little bit of an introduction about me, so maybe you trust me.
I'm an external developer for the past 15 years, I used to be an external developer, and
the main author of Sid Box, our topic today.
I'm also an international test trainer, and the co-founder of just without boundaries,
where we try to provide accessible materials to chess players with disabilities.
And you can see my interest in email here if you want to contact me, feel free to.
Here is the basic outline of what we are going to do today.
First, we'll define what's an application kernel.
It's a bit of a vague term, so making a definition versus logical.
Then I'm going to explain you how physical interception actually works, so you'll get a more
wide understanding of how application canals work, and then it's the main meat of the
matter is of course rust, why did I pick rust, what are our memory safe to patterns,
and the trade-offs between safety and performance, and then I'll finish talking a bit about
our testing infrastructure and Q&A.
So what's an application kernel?
Application canals are really vague term, it's both an application and a kernel,
but neither an application nor a kernel, so it's somewhere in between.
And here is a nice description I found from an article.
It's a library operating system variant that interseps emulates and transforms
physical in user space for sandbox processes.
These three are important, I'll explain a bit more later,
interception emulation and transformation, right?
And the way it does it is interseps system calls via set comp unotify, unotify stands for
user notification, it's a new API added to set comp in recent Linux's, and it's main
difference from petrase is you can handle system calls simultaneously from different
threats, unlike petrase, where you have to serialize, and we also use petrase and
unlock optionally, and what it does is it emulates file system network and process
operations, it transforms paths, flags and credentials at runtime, and you can configure it
dynamically via a website virtual path, I'll delve into a bit deeper later, and similar project
are Google's device, which is written and go, very similar device reuse is the second trap API,
and we use the comp unotify, so the idea is in the system calls are handled in different
processes, but in device, it's all in the single process, so depending on your use case,
either one may be more secure, and ramp canals of net BST fame, where you can develop your
canals and applications, so you don't, you're not scared if a crash can crash the whole system,
and other examples are not the containers or spheres, and there are many other examples
what these are the standing zones, and so what's it does actually here, I try to make it as
simple as possible, but not simpler, so on the left you see the path that an open call
takes until it's implemented in sit, and open if you don't know is a very basic unique system call,
which you use to open a file, and you get a file descriptor object from the canal with which
you can use read, write, and so on, and when the sandbox process opens a file, the first thing
that the Linux canal does is send this second-notification to the sit emulator threat that's
handling it, there are many threats, I will tell you see one of them will pick it, and this
notification has the system call number and the arguments, but the arguments are not, we have to first
process them to make them useful, right? We get a pointer, not a string, right? The pointer
tells us where the string is located in sandbox process memory, so in either another step with process
VM read, we actually read this string into our process space, and process VM read, we is a bit more
secure than the old way of reading the prokbitmem, because it respects the other space permissions
of the sandbox process, and now we have a string, this thing is a path name from the perspective
of the sandbox process, it can be a relative path, it can be an absolute path, so we need
another step to actually turn it into real path that we can make a sandbox check on it, right?
And this step is the canonicalization, and canonicalization gives you as a return value to values back,
one is an old path file descriptor, the other one is a canonical path, and the idea is both of
them point to the same thing, old path if you don't know is a type of file descriptor that you can
only use full pet operations, not for reading or writing, so the actual open hasn't happened yet,
right? And then the sandbox check happens, sandbox check can have three outcomes, if you can't say
the pet must be hidden, in which case the sandbox process will get a no such file or directory,
it may be denied, in which case you'll get an operation that permitted, or it may be allowed, right?
When it is allowed, now the final state happens where they actually do the open system call via
this eight item you see, we do a broken the interaction to prevent time of check to time of use
vectors here, and finally we have a file descriptor, and we use second at FTI Octial to add this file
descriptor to the to the process space, and this is the, this is the big picture, and on the right,
you can see many transformations can happen, over this path, you can mask a path to change the path,
it may be encrypted, in which case the encrypted threat will take over, it may be a pandon,
in which case we will force the appant flag, as you can see the transformations
can happen safely, because it all happens in sits process and it's safe, and finally we can
randomize the file descriptor to prevent file descriptor use attacks, so this is the basic idea,
don't worry if you don't get it, but we will get to it, so why does this is the main
meat of the matter and why I'll be at all here, right? I started writing sitbox three,
which became the restoration around three years ago, and my idea was to redesign it from scratch
to make it a security boundary, sitbox one was written and see, and it wasn't meant to be a
security boundary, so instead of doing the conventional rear IP operating in Rust, I actually
took the time to redesign it from scratch in Rust, and this way we could take advantage of many
Rust goodies, right? And this is what I can recommend, you don't just blindly rewrite things
in Rust, redesign it from scratch with the powers of Rust that makes it much better, and here are
a bit a few examples which I will live deeper in a bit, of course memory safety is one of the
prime features of Rust, right? And the many modules, we have this forbidden safe code close,
so unsafe code is outright forbidden, and there are other goodies, when you are working with
untrusted data like the alpha parts and the globe measure, I'm going to delve into it in a bit,
alpha is the executable file format of unixes, and sit parts is out to do some restrictions,
and it's completely untrusted data, right? So things like forbidding arithmetic side effects,
fluctuations or wrapping helps that a malicious alpha can occur as the sandbox, right? And more
over the views, the type system for Rust in our advantage, and the main use cases, we have a safe
interface for Linux's MCL system called to seal a memory region so it's immutable, and I'll
delve into it in a bit later, and we have this generic seal box type that turns into seal,
then it's sealed, and ownership is, of course, another prime feature of Rust, and in sit box,
we use this mostly for file descriptors, and this is fantastic, because file descriptor leaks is a
huge problem in container security, and in Rust you can actually make your compiler work for
you, and prevent this file descriptor leaks, right? And of course, the other two are things we all know,
zero-cost abstractions and fearless concurrency. As I said, sit, sit can be used simultaneously,
so sit is a multi-traded process that can be many emulator processes to handle system calls.
So, let's dive a bit deeper into memory safety patterns. To type state patterns,
this seal box, as I said, is a interface to Linux's MCL system call, and what we do is
we mark the sandbox policy as immutable, when it's locked, such that a compromised sit cannot
edit the sandbox anymore, and as you can see, this enam on the online, there is an enam sealable,
which is generic over the type T, and it has two variants, I'm sealed and sealed, in the default
unsealed state, you can edit it as you wish, and then the one way I don't pull can seal function
is called, and then it turns into sealed, after that you can only read it, you can no longer edit it anymore.
So, as you can see, over the seal function, and this is how we do it safely in Rust.
As a parser is another example, as I said, it works on completely untrusted data,
so we have a handful of forbidden links to forbid, forbidden, that can be a dosa attack,
and I actually run the alpharser over a set of 68,000 malwares from virus share, and it didn't crash.
So, I'm fairly confident it does the right thing. And let's talk a bit about safety and performance
and what are the trade-offs. In my experience, performance is no excuse to use unsafe code,
which is not really common among Rust people, as far as I can say. And here is a very good example,
glopy, if you don't know, means a file name matching, if you ever written a shell and used the
characters star or question mark, this is what you're using, it's a bit similar to regular
expressions, but not quite. And the original glopy method of seed was inherited from arcing,
and it was written exactly 40 years ago in 1986. And as a 40, 30 present to arcing,
I dirote this algorithm in Kirk Cross's festival, compared to algorithm, which is known to be
the fastest out there. And here is a nice example of the benchmarks of two million test cases I generated.
And the wild match code has no unsafe at all. And it performs almost two and a half times faster
than lips is a fan match, which is C, and has to be fast, right? But this is not the case.
And another thing we use to reduce small allocations is custom pad types. Sitting mostly works
on small strings, right? And this tiny vector module allows us to store the small strings in stack.
And only then it overflows, it will be allocated on hips. So this cuts a lot of small allocations
that sit does. And this part is a corresponding dynamically size type, which is pretty much similar
to standard lips pad, but it has comparisons with sims, so on resumcipy uses much faster.
So our testing infrastructure, sit box is a portable sandbox. It only runs on Linux, but it runs on
most architectures that lip-saccomps supports. Some of them are here. And we have a multi-architecture
pipeline that tries to test all of them. And we feed is also a multi-personality sandbox,
which means you can trace a 32-bit process from a 64-bit set just fine. And again, we have
cross-compile tests for that. And another nice benefit is when you're writing a kernel, everyone's
test is your test, right? So next, our Linux, we have package testing on my default, and everything runs
under the sandbox. And if a test fails under the sandbox, but passes without, it's a sandbox bug.
And we also run the Linux testing projects, Cisco test suite, which has over 4,000 tests
and Gnullips, Potsics, Compatibility Test. So we are fairly certain, sit us what Linux would do.
Right? And the idea is to just be a thin layer. And another thing is, of course, security,
this is a security boundary. And for every sandbox escape you found in the past, we have an
integration test that makes sure it doesn't reappear again. Yeah. So this is pretty much all
I have. Here is our GitLab. The code is GPL 3 and forever free. So feel free to do whatever you
want with it. We have extensive documentation in the form of manual pages. And if you have any questions,
you could not ask here, come over, I ask your matrix and ask. And finally, thanks to Fender,
one more data for sponsoring my attendance. That's all I have. I can take questions now.
Thank you very much. Sorry if I missed the point, but I wanted to ask, is there already some
kind of tooling to define rules, to run application under these things like what application
can access, what should be forbidden or hidden from it? Yes, yes, yes, exactly.
Sit box works with text-based policies. And there are over 30 categories of access, right?
So you can say allow read this path or allow right read, write, exact and all that are
are all categories. And you can configure them in a text-based policy. And you can load this
into the runtime. You can also configure it dynamically on the underrun, like both of them are possible.
Thank you for the question. I have a small one. Okay. You are showing two clip-y lines.
I'm sorry. You are showing two clip-y lines that I'm not very familiar with. What are those?
And what do they do prevent? This one, yeah? This one. Yes, that one. Yes, that one. Yes, that one.
Unsafe forbidden, safe code, forbidden, safe code. That's all we know, right?
Arithmetic side effects is for, then you multiply two numbers. For example, and the type of
workflows, what's going to happen, right? Or you are trying to multiply two numbers that won't fit
into a type. All of these arithmetic that can have side effects this way, right? Rapping or
overflowing, you know, all that. And in the Elf parser, like imagine the Elf is completely
untrusted. The size can be wrong. Everything can be wrong. You have to work with this untrusted
data and this helps with it all that, right? To not overcome the boundaries that I can explain
like that. And there are many more forbidden clauses. You can feel free to take a look at the code.
I have comments there. Thank you for the question. Have one there?
Just one small one. Why you had those protection for arithmetical operation?
I'm sorry. Why you had those protection for arithmetical operation? The library did
improve by out of the box or? I don't understand the question. Yes, in a slide before.
The boss protects in your mind, right? Yeah. Yeah, that's fine.
Brad, I'm sorry. Yeah, we need to specify the. This basically means
during parsing the Elf is untrusted, right? And anytime and overflow happens, it will return
an error. It will not overflow or do something undefined with it or things like this.
So you are preventing all the undefined behavior. And instead, you are returning an error.
It's simple as the case. Yeah. And why you should be employed? I'm sorry.
And why you should be employed? Pink Floyd, yeah? I mean, it's said box. It's a better
trite, so pink Floyd. Pink Floyd is a master. Thank you. Thank you for the question.
Any other questions? You're good, yeah.
All right, so thank you very much. Thank you, everyone.