 All right. Hi, everyone. Just introduce yourselves. Next one. Okay. There we go. I'm Jeffrey Thomas. I work for Hedge Fund. This is all personal work. It's unrelated to sort of what I do in my day job. And this is Alex Gaynor. I've got a mic. I do security stuff for a political data company, and this is not related to my day job at all. I'm also the principal and founder at Fish and a Barrel, which is an extremely real security firm that... Extremely real. And you'll see a bit about what Fish and a Barrel does. So we've been working on writing kernel modules in Rust, but I want to talk a little bit about why you would want to do such a thing and sort of what the problems we're facing. Cool. So vulnerabilities due to memory corruption are extremely common and extremely preventable. So a definition of memory unsafety is sort of the property of a programming language to, without programmers going out of their way to allow them, allow programmers to introduce use after free, double free, wild free, overflows, underflows, wild pointers, use of uninitialized memory, and data races often leading to all of the above. So some statistics about the prevalence of memory unsafety. So approximately 49% of the Chrome security vulnerabilities in 2019 had memory unsafety as a root cause. 72% of the Firefox vulnerabilities in 2019. 81% of Zero Days in the Wild since 2014, as tracked by Google's Project Zero, had memory unsafety as a root cause. But these are all silly user space problems. Kernel programmers are very serious and would not introduce the same sorts of bugs into their programs. So surely the numbers are more promising about kernel space. 88% of macOS kernel vulnerabilities in the 10-14 series had memory corruption. Microsoft doesn't release quite as granular statistics, but they've said as a whole since 2006, estimated 70% of vulnerabilities across all of their products had memory unsafety as a root cause, and I would bet almost anything the kernel is higher than other products. 65% of kernel CVEs that made their way into Ubuntu security notifications in the six months before we performed these slides were memory unsafety. 65% of all Android CVEs, this is inclusive of both kernel and user space, had memory unsafety. SizzCaller, at the day we produced this slide, was claimed 225 things that looked like KASAN or KMSAN. A paper released two or three weeks ago that performed use-after-free static analysis on a bunch of kernel data structures particularly focused on concurrency, had hundreds of findings. These vulnerabilities have the same root cause. C and C++ enables programmers to trivially introduce critical vulnerabilities. These vulnerabilities have outcomes like surveilling distance, advocating for human rights in the Middle East, taking down entire hospital systems, and even surveilling nutritionists in Mexico working on soda taxes that is a for-real thing where memory corruption was used to surveil these people. Surely there's a better way. So we're looking at C and C++ in kernel space because we've got needs for high performance, we've got needs to talk to the hardware, etc. We probably don't want to be using something like Python or Java. What could we do? We could stick with C. We could try hardening C. There's a very good talk last year about making C better. People have talked about field approaches so far. One of the things we've picked up from this is that all of these are fundamentally mitigations. They are, we will have bugs, we will not keep up with our bugs. ASLR is a workaround to make the bugs harder but not impossible to exploit. Several of these like Stackleak have overhead. Several of these things like sparse and covarity doing static analysis have been used, but at the end of the day they are not stemming the tide. We are still having hundreds and hundreds of vulnerabilities. So hardening C is an admirable goal but it is not quite enough. What about isolation? What if we say let's run parts of the kernel in, we could run them in WebAssembly, we could use data sandboxing, we could write interesting things in eBPF, we could use hardware support, we could go microkernel, which again has the same sort of overhead issue of how long does it take to execute code that is in isolation and can you even use isolation for things like can my scheduler be isolated? Can we, you know, how would I do that? Can my core TCP stack be isolated and how do I keep the same performance? And also it prevents problems between modules but it does not really prevent a problem within a module. Think of Heartblade for instance. Heartblade was not open SSL attacking some other part of the system. Heartblade was one request to open SSL, from another request to the very same open SSL. And so there was this patch series I was posted earlier this year about system call isolation, which is like page table isolation but even more so for system calls just saying everything is going to go wrong, let us very carefully bound what happens when a system call at a very high overhead cost and one that applies was if the price of Linux using an insecure C runtime is to slow system calls down so much that you might as well be faster in Java why aren't we actually writing Linux in Java which is not an argument for Java but what is going on with C? What should we be doing here? And if you go back to 2004 there was a proposal about should we write Linux in C++ and we can talk about why C++ does not actually solve many of the memory safety problems but without you know quite endorsing the approach here I think that the ideas that Lennox mentioned are very important which is C++ brings things like exception handling it brings things like memory allocations and it brings things like a particular approach to object orientation which is not quite suitable for kernel space. So what do we want out of a potential language here? We want memory safety of course exception handling that doesn't do unwinding we want OO without the overhead without things like diamond dependencies multiple inheritance we don't want hidden memory allocations several promising languages have garbage collectors or runtimes which make them unsuitable and we also want very performant FFI to C if we have any hope of integrating with the existing large body of C code and so there's a handful of very good languages Haskell, Go, D8, etc. which just happened not to be one thing I want to mention briefly is ADA people mention in the context of a safe compiled language and I think it's unfamiliar to many people so the real issue with it is that within ADA itself there is no sort of safe dynamic memory management it's designed for use cases where you can say I statically know exactly what my tasks are and what memory I want and I can do that in compile time there is support for dynamic allocation but as far as I know the deallocate operator is unsafe you were expected to track lifetimes on your own which brings back use after free, etc. the standard solution for things that are not embedded is to use a library that does garbage collection but we probably do not want garbage collection so that brings us to Rust Rust is a compiled language intended for systems programming Mozilla Research or some individuals originally created a better slash more secure implementation language for Firefox which is a very large C++ codebase it now has many production users and relevantly for us it is backed by LVM in user space it uses the system linker and libc it generates .o files it uses the platform calling convention etc basically it is a language that sort of operates the way folks who are working in C would expect it to work and so that makes it suitable for incremental rewrites you can use Rust to generate a .o that you can very directly call from C and vice versa if you were familiar with Rust before 1.0 you might remember that it had a garbage collector that it had a threading runtime built in it had segmented stacks etc all of this was removed during Rust development before 1.0 and so now it is a very sort of tight C compatible language which makes it appealing so let's take a look at what Rust does for memory safety I am going very fast if you are interested in working in Rust I strongly recommend the official book on their website I don't have enough time to go and cover it in huge amounts of detail so this will be fast this is a hello world very simple program syntax should be largely familiar to C programmers a couple things to note here first types and local variables are optional if they can be inferred but in our examples we are going to be adding type annotations for clarity second unusual thing here is that exclamation point after print line means it is a macro why it is a macro is a little bit out of scope for this talk but basically since print line wants to take an arbitrary number of parameters with arbitrary number of types it has to do some stuff at compile time to handle variable arguments in a safe way and so if you see the exclamation point all it means is it is a macro it doesn't quite follow it's not a function, it doesn't quite follow syntax it's pretty rare so variables in Rust are constant by default so this program is an error it will say you tried to modify X which is not mutable so you can just modify it, make it mutable you are allowed to leave a variable uninitialized in Rust but you have to assign something to that variable before you use it so unlike in C you can't have accidental use of uninitialized variables in this case the first line is perfectly legitimate but Rust will notice by the second line this variable is potentially uninitialized that is an error that is not just a warning and also you get this error message which says that Rust wants to or print line rather wants to borrow this variable X so we will talk about Rust Reference System in a tiny bit and how what borrow means in that context a few more bits of syntax Rust has structures just like C syntax is a little bit different but should be pretty familiar Rust lets you put methods on structures which is probably starting to remind you of C++ and you should not take this as it being like C++'s object orientation it does not have inheritance encourages you to use composition but good for us it avoids multiple inheritance, virtual functions downcasting all sorts of weird runtime stuff you don't have the question about is a square a subclass of a rectangle or vice versa you try to avoid that and a couple of syntax notes here that ampersand in self means reference which we will get to in a little bit for now you should just read that as a constar except your guarantee that is valid also note the arrow for return value and finally if you end a function there is no compression and no semicolon it just returns that value one of the things about Rust is it was designed with some good amount of influence from functional programming communities it is an imperative language, it is a systems language but you can see that influence here Rust also does have public and private we don't have time to cover that here but you can definitely say I am making a safe abstraction and I am encapsulating what happens underneath so you get the good parts of OO which is encapsulation, abstraction stay away from the weird parts the way Rust gets you polymorphism is what is called traits a trait is a set of methods that you can implement on a structure it is like an interface or a protocol in some other languages or for kernel folks you would recognize this as similar to an operation structure you can put default implementations in traits you can have traits that depend on other traits like you can only implement shape if you also implement surface or something I don't know but it won't let you have problems like method name conflicts if I had both this area function I am defining here and the one from the previous slide and I had like my circle dot area it would actually say that is ambiguous which one do you want me to call so it is not inheritance it is a collection of methods it is a lot more obvious what is going on you can do generic slash polymorphism in two ways one is you can make it generic over anything that implements a trait and this is expanded at compile time there is no runtime dynamic stuff that is called here if I take this function I call it on a circle and I call it on a rectangle there will be two copies of this code in the final binary the other thing I can do is I can do a dynamic call where I say I actually do want to take a trait object which is basically a fat pointer with both the object itself and the vtable for what methods I want and so this will compile into a single function that does a dispatch at runtime on the type of shape and that is sort of a general pattern in Rust if there is some complexity happening at runtime you have to specifically opt into it usually there are some syntax calling out that you are doing something special and so you can also do a lot of neat things at compile time like we saw in the last slide and they compile out Rust as LTO and often just optimizes out we will also see the borrow checker in a little bit which is also all at compile time Rust and C++ both have a term for this which is zero cost abstractions which means you only pay for things you want to pay for and the language is not imposing things on you and that is one of the things that is absolutely critical if you are thinking what language should I use for something like a kernel Rust also has enums the first part of this should look familiar from C an enum is a unique type it is not just an alias for the integer and it is impossible in safe Rust to construct an enum that is not one of these values the first statement at the bottom is in fact exhaustive even though it doesn't have a default case because you know any object of type over commit policy has to be one of those three values enums can carry data so this probably looks a little bit less familiar from C if you have a functional program background you might recognize this as a sum type the underlying storage format is an integer discriminant saying what variant of the enum it is followed by the data so it's a tagged union it's not quite like a C union and access to the data is safe in that the only way you can get a reference to a member of a particular variant is if you have actually checked that it belongs to this variant so in this match statement I cannot access host unless it's actually of type of subtype address colon colon IP and there are very two common enums in Rust option and result which are defined in the standard library option is what you'd use for optional or nullable values and result is how you do error handling you either have a successful value of type T or an error object of type E an error object can be whatever sort of thing makes sense maybe it's an enum that has various types of errors it's common for libraries to type def a result type with their particular error object and then there's one more syntax construct if let which is the same sort of thing as match combined with if the only way you can get at a actual usable object of type T out of an option is if it is in fact of this variant called sum if it's a none that code won't execute and so that sort of wipes away your null pointer issues right there you can never accidentally take something and forget to check whether it's null the only way you can get into it is by checking and Rust actually does one thing to optimize storage which is that since all pointers in Rust are valid slash not null if you have an option of a reference it just stores the none as a null pointer so at the end of the day the representation memory is just like in C but at the syntax level there's no way to accidentally mess this up and Rust doesn't have exceptions that you throw and catch because they've got pretty good syntax for handling options and results the recommended way to handle things that could potentially fail is to return a result or an option to the caller check it which again is pretty similar to how the kernel does error handling you return a pointer that has one of the magical error values is a negative value or something and you have the caller check it and so there's no exception throwing in the kernel so very should feel very similar with the notable advantage of it is statically checked sort of at compile time that you are not accidentally taking negative enomem and using that as a pointer or whatever and there's one even nicer bit which is the question mark operator so in the left foo question mark expands to one of two possible things if it's okay then if I have an object of variant okay and I put the question mark it just unwraps it if I have an object of variant error and I put the question mark it expands syntactically as basically syntax trigger for a return from this function converting the error object if necessary to the error of this type so I can make code that reads very straightforwardly like code on the right where I say I'm going to open this thing that might fail I'm going to read it that might fail I'm going to parse it that might fail at the end if that all worked I will return another result type with my data and it's straight line code there's no throwing catch behind the scenes it's very clear what this is expanding to the compiler isn't doing anything magical and so if I ever wanted to handle one of these cases myself with a match statement I could just do that so nothing implicit it's just like if I put a bunch of if statements in C but I don't have to write that for genuinely exceptional conditions like division by zero or indexing out of bounds so 1 over 0 or 3, 4, 5 sub 10 we'll do what Rust calls a panic that's usually implemented with the same infrastructure as C++ exception throwing and that it unwinds the stack it can be caught but it is strongly not recommended to use it for error handling it's mostly for things like I have a web server and one of the threads has panicked but I want the rest of the web server to continue running if possible or in kernel land I have one kernel thread or syscall that hit a bug and I would like to kill that user space process but I would like to not panic the entire kernel and so again sort of similar philosophy in how you design things if you have something that might fail Rust basically always as a convention libraries will give you a way to do that without panicking and so if you .get instead of the index operator it will give you an option and so in this case 3, 4, 5, .get 10 will just return you none and then you can always do an explicit panic with the panic macro and so yeah if you haven't noticed the thing I'm trying to sell you on here is the way Rust thinks about things like how do I do OO or how do I do error handling turns out to be very similar to the way the Linux kernel has also decided what is the best way to handle object orientation how do I put a bunch of methods on an iNode or how do I handle something that might seg v on behalf of a user space process without killing the entire kernel okay that's enough syntax hopefully some of that stuck so let's look at how Rust handles safety without a garbage collector so start with references here we are taking a reference to x and we are storing that in a reference variable called y and we dereference that I can also move that to a function to show how writing function that accepts a reference works it's pretty straightforward but Rust is behind the scenes enforcing rules on how you borrow variables and most of what we like about Rust for our purpose and how it solves all the memory problems we've talked about is based on these rules so it's got a borrow checker and when you take a reference to a variable Rust keeps track of how long you are borrowing that variable syntactically lexically and you cannot borrow a variable that's longer than it actually exists so imagine a for loop like this for i and 1 through 5 y equals reference to i and then I try to print line y at the end of that for loop this would be a very easy mistake to make in a language like c in that you could have something and just have it escape the stack from beyond where it lived and in a language with garbage collector ref counting imagine python or java the way I would handle this is by just taking a reference count on i and keeping that alive until the end of the function but Rust does not want garbage collections I don't know when this object is being released you want to know statically what is happening to your memory usage and so the compiler is tracking where each reference comes from and it gives you an error it actually gives you a quite clear error saying at this particular location i does not live long enough because it was borrowed from over here and it was dropped at this curly brace if you want this to work one thing you could do is use the standard library type rc which is a ref counting pointer that is an opt-in I would like to just allocate this on the heap increment the ref every time I make another pointer to it and re-deallocate it once the ref count drops to zero and so in terms of overhead it's like any ref counted or garbage collected language but it has the advantage that you only pay for that cost if you actually want it you actually say this is what I want and like variables references are also constant by default if I try to modify something through a ampersand reference instead of an ampersand mute reference it will say you can't do that and it gives me an error telling me what I should change and rest is something more interesting with mutable references which is that you can't have more than one mutable reference at the same time you can't have a mutable reference and a constant reference at the same time so you can think of this as a compile time reader-writer lock you can have multiple readers but you can only have one writer and that writer, that mutable reference is temporarily borrowing all access to that variable for as long as it exists so really the way to think about it and we'll look at it next slide I think about how you would get around this if you do have ways to safely modify a variable concurrently is that you should think about ampersand mute as a unique reference ampersand is a shared reference if you have a piece of data with ampersand mute you know that nobody is aliasing it nobody else has a reference to it etc if you have a piece of data with ampersand you don't know that no one else is aliasing it but you do know that it is living for as long as you have this reference around so you might be thinking at this point this all seems very restrictive what if I do want to modify something concurrently obviously if I follow that rule just there with the ampersand and ampersand mute I won't have any use after free I won't have any race conditions but like there are plenty of cases where two people have mutable pointers the same data and they successfully avoid corrupting memory how do I do that and so that's great as long as you do it in a controlled way Rust lets you bypass all of these rules as long as you use the keyword unsafe so what you should do is create safe abstractions where you say I've got a wrapper type where the implementation uses the keyword unsafe etc that what I'm doing is safe and then a test I can use unsafe I know what I'm doing and you can also of course just use the keyword unsafe and break all the rules if you want to I won't give you examples of that because it's less interesting but that escape hatch is always there if you really want to write some code like you're writing C and you don't want to think about the rules of correctness you can still always do that in Rust just put a big unsafe block around your code but let's look at how you do this in a controlled way so an atomic 232 in particular this is a type from the Rust standard library is something that you can actually modify through a ampersand which is to say a shared reference because it's atomic that's safe you have no risk of like reading it while it's being written and seeing it torn values seeing a data race unusual undefined behavior problems and so it's safe to have multiple threads access it it's safe to have re-entered code it's safe to have an interrupt handler access it etc and so what the author of this code is saying is the implementation of atomic in a very simplified way as shown on the right side here what the author of the code is saying with this unsafe block is I know that even though this function takes an ampersand self not an ampersand mute self this underlying atomic store in this case that's an LLVM intrinsic that is actually safe to do on a shared reference and on a multiple people have access to it and so as long as they are correct when they're writing this application author on the left can write my code calling these functions they don't have to use the keyword unsafe and there's no deep magic by the way in atomic U32 it is implemented much like this in the standard library the only magical thing is this word unsafe cell which is an indicator to the compiler that multiple people might be referencing this data or might be writing to this data even through an ampersand reference and so you should not optimize that it is what you've always wanted I'll just throw a volatile on this but it's actually defined by here and so this same pattern is true of things like lock guards you can have some data behind a lock you can have a type that says I'm safely managing current access because I've got a spin lock on this and from an ampersand reference to a spin lock you can say I'm going to give you a borrowed temporary ampersand mute to the underlying data until you've released the lock so some more examples I'm going to deal with raw pointers that is star as opposed to the ampersand sort of track reference type all of that is unsafe but you can always just do that and so there's two ways you can do that you can either write a function that has an unsafe block or you can write an unsafe function in this case that first function there on the top left is probably a bad idea because it is just taking a random pointer and writing to it but if you need to say you can use your own unsafe function the contents of that are automatically allowed to use unsafe constructs and in this case you can write a function like this where you say I will allocate a vector vector exclamation is another macro that just allocates a vector on the heap for you I'm going to get a reference to one item from the vector I'm going to zero it out print the contents that will just print zero and so you can call C functions from Rust pretty straightforwardly the declaration for the C function in Rust style or anything that's using the CBI really put it in Rust style syntax inside an extern block and then that's immediately available for you to call as an unsafe function and then what you should probably do which we're doing in the next part of this is write a safe Rust wrapper around here and so glossing over syntax I'm basically saying let me allocate a buffer here I'm going to call this C function which Rust doesn't know sort of what the semantics of those pointers are so Rust always insists that C functions are unsafe put the unsafe block around it you say that's fine what I'm doing in this context is okay and then if I get an error I'm going to return an error object if it works I'm going to convert that to a string I'm going to do a UTF-8 parse on it if the UTF-8 parse doesn't work I'm going to throw a UTF-8 error and then this ends up working and so I can just call this as rs-readlink from any Rust code the other way around on the left side is a function I can compile in Rust and generate a .o or .so or whatever and on the right side is a C program I can link against this function and it'll just call it so very easy calling conventions are compatible they are not you cannot call an arbitrary Rust function because Rust has a rigid type system but there's no sort of overhead there's nothing like cgo or jni or something where you're like Rust just runs on the normal C stacks like a normal C function and you can also access C types in the same way you put a structure you put this attribute representation C on top of it you put whatever it contains so in this case I'm picking on structs-sig-action because it's a little complicated it's got a function pointer it's got flags it's got a few other things and I also am importing this sig-action function from the standard library over in Rust code I can call it note that I'm not doing any sort of marshalling on marshalling I'm creating a sig-action struct just like I was creating any other Rust struct because it is a Rust struct it's just a Rust struct with well-defined layout and then I can call this C function on it and so what you can do from here is say I'm starting with a large C or C++ code base and I'm going to convert bits over I'm going to take something where I have an API boundary and I'm going to take this same API and implement it using Rust underneath and so we aren't the first people to do this by any means Firefox very notably had a research project called Servo where they were writing a browser entirely in Rust they wrote a CSS component called Stylo and now Firefox Quantum is shipping with Stylo as its CSN engine they are writing new components like WebAuthn for two-factor authentication in Rust and the only bar SVG is GNOME's library for SVG rendering and that one has also undergone I think at this point a basically complete Rust conversion while still providing the same ABI, API, C header file, etc to all of its existing consumers Microsoft is considering switching parts of the OS kernel and other things to Rust various other users, Brave, NPM etc are all happily using Rust in parts Cool, so now we're going to show you what we built thus far in terms of interfacing with the Linux kernel from Rust so this is sort of your Hello World kernel module written in Rust using the library we built for abstracting around the Linux kernel and exposing it safely so we represent kernel modules as a trait that you provide the init method on which maps to the module init macro so we have our headers exposed we print Hello World we return an instance of our Hello World module and sort of print line the standard print API in Rust just works compiling this we use something called Cargo XBuild CrossBuild to target the slightly different kernel ABI and then we have a deeply magical make file that will convert the shared object from XBuild into .co in terms of the scope of what we interface with thus far we will interface with printk, error types, the whole allocation system syscuttles very basic file system support character devices, user pointers and file operations so what is involved in actually mapping APIs to safe Rust so let's start with Rust three important data structures to get started with there's box which C++ is roughly a standard unique pointer so that's a pointer to a value on the heap that uniquely owns its memory when the box goes out of scope the memory is deallocated that is a heap based growable linear array string is a heap based linear sequence of UTF-8 encoded and Rust has a global allocator trait that you can provide in your program to use something besides libc malloc and free we provide this built on top of kMalloc or in this case kRealloc since kMalloc is an inline function and kFree and now heap allocations just work so the string syntax.toOwned will allocate a string that lives on the heap and this just works so programs that are using Rust's heap APIs will just automatically use the kernel heap in future versions of Rust there will actually be support for managing multiple allocators which would let you do things like have allocators that use different flags instead of always using GFP kernel but for now everything is just GFP kernel. Okay but what about under-under user pointers this is serious challenge kernels have like kernel pointers aren't just like regular user space pointers and our desired goals are roughly type safety you should never be able to mix up a kernel in a user space pointer you want to be bounds checked really in both directions but you know particularly when copying from user space to kernel space and you want to avoid double fetches leading to time check time of use vulnerabilities so we have an abstraction called user slice pointer which you can pass around and it has methods for reading and writing to it one thing you will notice that we have not demoed so far is all these methods take self without an ampersand so they are not taking self by reference they take self by value which also means self is consumed so this means after you call read all Rust will not allow you to call read all a second time this helps prevent the double fetch problem and so an example of using this you might have a read callback that takes a mutable reference to a user slice pointer writer and you just call write on it it has a length method telling you how long you think it is and you will see write has the question mark operator after it which tells it to handle errors because write can return an default and that will just propagate the error upwards in this case we call write on every method which is like a fresh call to copy to user so like you are going to be dropping and reacquiring snap after everything if you wanted to pass write a longer buffer instead of passing it one character at a time you could also do that so you get sort of control over how you are calling things like copy to user by how you use the user slice pointer API but what about concurrency concurrency is kind of where the fun stuff happens so Rust models concurrency with two traits you can send that different types in Rust implement so sync if a type implement sync that means multiple threads may have references to a value of that type at the same time and so something like a mutex implement sync because you can have multiple threads with access but the mutex will only actually give its inner value to one thread at any given time send is the other type and this means a type may have two different threads so most types are send because they are safe to give to somebody else an example of a type that is not is something like a reference counter pointer that is not using atomic operations for the reference count because if you passed it to another thread then you could have races about the reference count so lots of kernel types need to be safe for concurrent access let's talk about file operations so we've modeled the file operations sort of virtual table as a trait that different things in Rust can implement and the sort of colon syntax after pubtrate file operations says to implement file operations you also must be sync and sized size is not really interesting but a file operations must be sync because you can have multiple threads trying to use a file descriptor concurrently and as a result the read callback takes a lot of time to run and not an unmute self because it has shared semantics you can have multiple people calling read concurrently and that means if your implementation of file operations read needs to say mutate something to get the state in order to work safely with the shared reference you're receiving you will need to bring your own locking over whatever state it is that you're mutating in read we basically force people to correctly handle handle concurrency okay so we are using bindgen which is a very nifty Rust project that already exists to parse C headers so we sort of say point bindgen at the kernel headers and have it generate Rust source code including both a bunch of external things and a bunch of representation C structures mapping to kernel things and that works internally using libclang and so that means it's not its own C implementation uses libclang to read the header files and sort of get a sense of what they mean and so we are making a whole bunch of assumptions here and so this is sort of if we were thinking about putting this into production or potentially what would it look like to have this in mainline in a hypothetical future this is one of the interesting challenges we are assuming that clang and GCC produce binary compatible code this has been mostly working for us in practice one of the things we've noticed it actually fall apart on is GCC plugins like you have ram struct running at the kernel build time there is no ram struct clang plugin it puts the fields in the wrong order for instance and so we would like better guarantees here and sort of a better way to sort of access this I don't know what the solutions are we are open to suggestions maybe we should be parsing dwarf okay this is dwarf sort of debugging metadata for the compiled kernel matched the dwarf data that we would be generating or something this seems relevant to these sort of prockheaders.tar.gz discussion that has been happening recently in terms of how do you communicate what the kernel's ABI is to new modules that you are building for that exact kernel is see the best way to do it it seems like it's probably the best slash only tractable way now but we'd like some better guarantees I'm doing a bunch of weird things in the build script it's on github, I'm not going to go into details about running a kernel build with cc equals clang for a kernel that in most cases has been built with GCC and that works that's not totally guaranteed to work unfortunately and we've seen some issues with that on 50 end up and we're also grabbing the C flags which we need to pass into lib clang so it interprets things correctly and knows what to define we're grabbing those by basically running a dummy kernel module build echoing a bunch of variables that kbuild happens to set for us which doesn't seem great but is also remarkably stable in that I guess we started working on this with like a 4 4-ish kernel maybe and it does still build up to 5.2 I tested 5.2 this weekend so happens to work would like better guarantees one question that is extremely valid to ask about how can I use this for real work if you were an outer tree module developer or we're talking about how would we sort of start to pair back the amount of C in 1x mainline how we get this into mainline is what architectures does it support and so the architectures on the left are the architectures that Rust upstream does support so these are expected to work ARMv5 plus ARM64 force MIPS 2 plus MIPS 1 does not work PowerPC, PowerPC64 RISC5, S390 64-bit Spark UserModeLenix probably works x8632 should work the only thing we're actually running in CI right now is x8664 so we should get that working but one notable thing about this list while it is a huge subset or a bunch of hexagon other random architectures in the architecture this covers all of the distro kernels so rel, fedora, debian etc. are all supporting a subset of these most of the work here is actually in LLVM we don't need much Rust support since we don't have a standard library there are some other approaches that you could take there's a Rust compiler called MRUSTC that emits C as output sort of an experimental thing that doesn't do any checking and then there's an LLVM C back end which the Julia folks have recently revived I have no idea if it works but it might be promising and so before we do future directions I want to attempt to do a live demo wish me luck so over here let's see if that's big enough let me know if I need to see bigger I have got do you want to talk through this while I type we'll talk through this so we've got a relatively simple kernel module here that exposes some syscuttles and when it also exposes a character device when you read it, it gives you a JSON representation of your syscuttles so this is entirely a safe Rust within the kernel module we have written and the JSON module is like a pre-existing one like we do not have to write our own JSON module so we're importing Serdi JSON core which is a standard thing for handling JSON and Rust we make three atomic bulls we make this empty character device struct JSON caredev because we don't need any data to implement some traits on it and we basically say you should read this as the C equivalent of static JSON caredev ops equals file operations etc so we say when we're reading we load these three atomic variables and we put them into this output structure we ask Serdi JSON core to serialize this write it out and then we have our kernel module itself which contains three syscuttles ampersand static means it is borrowed for the entire lifetime of this program or in this case the entire lifetime of this module and we have a registration for a character device that was the output variable I used I put the structs in the wrong order but that's fine and then here's the implementation for JSON syscuttle module which is we register this character device if it works great if not there's a question mark and then we register three syscuttles again if it works great if not there's a question mark you will get an actual kernel you know Ian Val or something out of this we don't need to register anything if any of those fail and we don't need to do anything to unregister it because we have destructors that when these objects go out of scope will automatically clean things up so I'm going to read this alright live demo time pray for us okay great ignore that bit of NSM go to it's fine so this is a normal kbuild makefile except for the part where it picks up this .a that we just generated and we insert it into the kernel you will see our JSON device now shows up we can use it as a .proc devices we can make not it get JSON out when we cat it we can write to proxys to change the syscuttle value and you'll see the JSON updates tada this hypervisor by the way is QMU with mac hypervisor.framework so things are slightly exciting with this if you worked on QMU hypervisor.framework that is very exciting thank you for it but yeah so that's our demo source for that is on github and what do we want to say about future directions yeah so what is sort of next for this first of all we think the future is bright for writing kernel modules in not see particularly in Rust sort of our design goals are to cover more kernel APIs we are sort of relatively limited in what we support at the moment we'd like to find better ways to support particularly out of tree module authors give a moment for booze and we want better kbuild integration so more kernel APIs right now like functionally we have syscuttles and we have character devices we think particularly exciting targets for having memory safety are things like file systems and drivers for various device classes so we would like to expand to cover those real world out of tree modules so like if there are any authors who maintain out of tree modules or in tree modules for that matter like or would like to maintain out of tree modules but don't want to write c yes what would it take for you to actually use this for real we would love to find a way to whether it's functionality or productionization find ways to support real world use of this we want better kbuild integration so right now you have a like two step process for the build there's actually no reason to make file couldn't drive cargo X build and just have that all sort of it look exactly like building a normal kernel module besides needing to have rust installed and finally in what would it take to have a true rust support for writing rust modules in mainline kernel see no longer brings us joy we'd like to thank it we'd like to put it aside thanks I think we have a minute or two for questions our code is on github and feel free to check it out send us pull requests etc try it on your own kernels try it on your own architectures and fish in a barrel the extremely real vulnerability research firm is on twitter at lazy fish barrel you can follow us for more statistics like you saw in the first part of the presentation we'll be having a bof if anyone wants to get started with sort of getting their environment set up and wants to play around with things performance and code size so one thing I should have credited is that we are based in part on some other people's work there's we're not the only people interested in this we think we've gotten a lot farther in terms of sort of safe abstractions but there's one team that started with our repo and made a real driver for the raspberry pies internet controller and they published a paper about it and so they did benchmarking on both performance and code size code size is larger performance is quite comparable in that there is no hidden overhead in any Rust operations like just like when you write C nothing is sort of being allocated on the heap unless you call malloc when you write rust nothing is being allocated on the heap unless you call some function that like you know uses allocations in terms of code size I can so this is with a release build it is about one megabyte and that is including a bunch of things we had not stripped out of sort of the Rust core libraries and that's including this whole JSON parser so it is not untenable basically it seems quite reasonable for at least desktop systems so there's a buff is that today okay so yeah there's a buff from 5pm thanks for the great talk guys thank you