 All right, thank you. So yeah, I'm Brooks. I'm here to talk to you today about a hardware-enforced memory safety scheme that we've implemented on FreeBSD. So since it would be reasonable to express a certain amount of disbelief that this could work, we'll skip straight to the punchline. It really does work. We have a full FreeBSD operating system with spatial and referential memory safety. This covers programs, libraries, linkers, and kernel access to user memory. Performance is pretty good. And we can run real-world software, both C and C++ software. So let me give you an introduction to Cherry, the technology we've built this on. Cherry introduces a new register type, the capability. You can think of this as a bit like there are integer registers and there are floating point registers. Now we have capability registers. Cherry capabilities grant access to a bounded region of address space. They're protected in memory by tags, and I'll explain that a little more later. Architecturally speaking, Cherry capabilities have a virtual address, just like an ordinary pointer. There's a tag bit off to the side, which ensures that if you perform an invalid manipulation, that tag is stripped, and you can no longer load through a capability. And also that if you overwrite the memory with random stuff, that is the tag is clear. Additionally, we have a length and a base, notionally, which is the range of memory that can be accessible by manipulating that virtual address. If you push that address out of bounds, you get a fault rather than access to memory. Additionally, there are permissions. These control things like load, store, or instruction fetch from capabilities. However, as you might notice here, this is 257 bits, which is a bit large for a pointer. So we've cut it down a bit. Internally, we have a compressed mode where we make the bounds floating point, and we compress them relative to the virtual address. There's a bunch of clever tricks. There's a long, 20-odd page journal paper that explains it in great detail and with lots of math. But the short version is that we compress the bounds, so you have to pad your allocations a bit, which your allocator probably does anyway. We maintain all the security properties of the architectural view. And we have strong support for C language. So we see language features that are commonly used even if they're not technically correct. For instance, allowing pointers to drift out of bounds and then be brought back in before they're used. This is used both for dubious optimizations and simply as a matter of the way pointer arithmetic is performed on pointers. Sometimes you make multiple operations. You're moving that address around. And then in the process, you go out and then back in because you apply the arithmetic in the particular order. This increases the tag density. But otherwise, it shrinks the size of the pointers. That's them in half, which means we're only twice as big now, which is hopefully more palatable. And we've got to, so all of this work, we've got a full prototype with a software stack on an FPGA and in QEMO. So how does Cherry work? Well, Cherry impinges on all memory accesses. So either that's explicitly by new instructions which load and store via capabilities or jump via capabilities, or implicitly. So if you're using a legacy MIPS load or store instruction, it's by a default data capability. Or if your setting address is the program counter, it's via a program, it's relative to a program counter capability. Obviously, all this wouldn't be of much use if you could just make up your own capabilities and have your own pointers to anything, because that would just be what we have now where virtual addresses, you just make up a number and you try to access it. And sometimes it works. Sometimes it explodes. Sometimes you own the system. So capabilities are used and manipulated in capability registers. The manipulations are monotonic, which is to say that they can reduce bounds and permissions, but they can't increase them. So every capability has to be derived from a more privileged capability. When stored in memory, they're protected by tags. So as I mentioned before, if you have a classic stack buffer overflow and you run over your return address, well, you've just written text over the top of your pointer, you lose the tag and you crash on return, which is much more friendly than before. Additionally, though, we put bounds on that stack address, so you're not gonna get there anyway. So you'd only get there if you had a compiler bug in the first place. Now, cherry capabilities were designed from the beginning to be usable as C pointers. There have been a number of capability systems including some memory capability systems that we take inspiration from, but they weren't designed to work with C. So we, as I mentioned before, we allow out of bounds pointers between dereferences. We can store full 64-bit integers untagged in memory in the pointer because we do that. Either most often we store sentinel values, so things near zero, minus one, when we're returning from Mmap in an error or that sort of thing. But there are coding models where you need to store full integers inside a pointer. And importantly, we don't have any tables, so we don't have a protection look-aside buffer. We don't have MPXs screwing your bounds all across memory and taking with, I think, normal virtualization up to 192 page faults to read a byte. So we don't do that. We don't increase the number of page faults that you take, except in so far as your structure's got a bit bigger, so cash effects and the like. We support two compilation modes, a hybrid mode where we annotate select pointers as being capabilities, so they become, they are capabilities and the rest of the pointers remain flat virtual addresses. Additionally, we support a pure capability mode where all pointers are capabilities. That's the focus of the work today. So Cherry ABI takes that compilation mode and we've built a pure capability process environment within FreeBSD, so we've added a new compatibility layer, much like FreeBSD 32. Yes, you have a question? So the question is, what are the implications of context switches? Are they still required? In the current system, the context switch, there's more context switch overhead because there are more registers. So we take that cost and we retain the classic virtualization model. We have work in progress where we are looking at a single address space unit. There would still be a context switch, which is, say, you swap your register set, but you wouldn't swap your address space potentially, but that's research, you know. Let's see, so all programs here are, it's in the pure capability mode, so all pointers are capabilities that include system call arguments and return values. Our goal is to minimize bounds on objects, both C language objects, which is the compiler's problem and the runtime's problem, and pointers returned by the kernel, which is to say, when you M-map some memory, you get some memory by calling M-map and you map a region of address space with some backing pages. You get a capability that references that address space and nothing more. And our overall goal is a compatibility goal, which is to run pure capability programs with simple recompilation. We'd like you to get all the benefits of memory safety with no work. We don't quite get there, but I think we do pretty well. So the implementation in the kernel, it's, as I said, implemented as a compatibility layer, much like 32-bit compatible and free BSD. The kernel is a hybrid C program, that's because we weren't ready to do pure capability and because the kernel does a lot of evil things with addresses. So it was easier not to solve all the problems at once. And we've modified both those, all the pointers from user space, so the system call arguments and as they percolate down, and also select data structures. So struct IOVAC, which takes a vector of, which is used to construct a vector of pointers and links, inside the kernel, those are now always capabilities. So all user space accesses, via capabilities and it's direct access. So we have a set of new copy in and copy out functions for the ordinary path and when you're in a Cherry ABI program, we have broken the old ones completely. We haven't removed them because it's a bunch of work and I don't need to fix I3D6 only drivers and things like that. And by default, capabilities aren't copied from user space to the kernel or VAC. The reason for this is most of the time, most structures that you're copying from the kernel don't contain capabilities or shouldn't and you don't want to be leaking them back and forth not because I think the kernel has bugs in this area, but because I'm paranoid. I don't want to introduce the possibility of bugs. I would like this small number of copy in cap and copy out cap calls to be something I could audit it a couple of days. And right now I could audit them all in a day. It's fairly straightforward. If you were to audit all the way down the call stack from those points, it would be more work, but it's something you could do. So that was an important design goal. So let me step aside and talk a little bit about a theoretical concept that we came up with in the process of this work, which is the abstract capability. So one of the issues we have when you're thinking about this theoretically is how should the programmer think about bounds? If you have a mental model of how C should work or a theoretical model of how C should work, the kernel user space interaction is really kind of weird. So we came up with this idea of the abstract capability. It is a set of permissions of the process, which is to say the pages that can be accessed and what it means when you load from a particular virtual address, for instance. This includes a certain amount of ghost dates, such as the fact that when you swap a page out, it may store permissions and that's not available. It also includes the address mapping in the program. So at any given moment, you might have to take some TLB faults in order to access particular pages of physical memory. This is all constructed and maintained by the kernel and the language runtime. So let's talk about how it is at power up. When the system powers on, we have two capabilities in the system. The default data capability and the program counter capability are both omniscient. You could take a stronger view and do a WX or X thing here, but we have not, because that would be a lot of work. And then all the other capability registers are null. Additionally, all the tags in memory are clear. So in early in boot, we start doing some capability manipulation. We take the DDC and in the kernel, we strip the execute bit off of it. So we're WX or X from the very beginning of low-car. Similarly, we take the right bit off PCC. So you have to at least be principled in your access even if they are fully overlapping. We then create a user root capability, which only contains the portion of address base that the user can access. A swap root capability, which is unfortunately omniscient because we need to restore anything that might be swapped out and that currently does include some kernel things. It's pretend it's in the working set. The order of operations differs. No. Well, I mean, the swap capability is an obnoxious backdoor. We'd like to do something about that. Yes. The swap root is, this is all Fs and that's from zero. So it's the entire range of address space. That's to be fixed. So now, so that's sort of the basics of setting up the kernel. The kernel doesn't do much more manipulation beyond setting up user address spaces. So let's talk about user address spaces. So when we're exacting a program, we have an initial register file, which is zeroed. We have a, we map in a thread stack, a program binary, the runtime linker, some process arguments base. That includes the argument and environment strings, the elf auxiliary arguments vector, the environment vector, the argument vector, those point into the strings in various places. This is all basically normal. We have pointers back to the program and the runtime linker. The only difference here is that the pointers are all capability pointers and they're bounded more or less appropriately. In Cherry ABI, I said, you know, DDC is where you use MIPS instructions. Well, we make it null here, so you can't use legacy MIPS instructions. You must use capabilities. We have a program counter capability, points to the runtime linker. We have the stack. We point it in the right place. And then we have changed the process ABI. Normally you pass either through your stack argument convention on x86 or through your register convention. You pass argc, argv, and nv as the arguments to the start function. In our case, we pass only the pointer to the elf auxiliary arguments vector and we've extended it to have pointers to the other bits. So we do all our rendezvous through the elf auxiliary arguments vector. It's much easier. It's extensible. Sorry? You were saying that as probably? Yeah, I think I probably took some inspiration incidentally from Cloud API. Ed was starting to do much of that work about the time I was working on this. So now virtual memory, the virtual memory system has both programmer visible aspects, which is to say that it allocates new regions of address space to a process and arranges their map, their backings. That's via mmap in friends and shemmat. It also alters in freeze mappings. And then abstract capability maintenance, which is responsible for ensuring that your virtual to physical mapping is correct. We haven't done anything here. We are assuming that the free BSD curl is correct in this regard. I think generally we believe that's true or boxes would be getting owned left and right. So we're just counting on that. It also preserves stored capabilities. So we'll talk about that in a little bit. First, mmap. So mmap has an annoyingly conflated set of jobs. It allocates virtual address space and it puts pages behind those, behind that. And it can do one that it's one and then it can poke things in and out. It's a terrible API. So in Cherry API, mmap returns a bounded pointer. So if you make a really weirdly sized mmap request, right now we just reject that. If we can't represent that due to the floating point use. In our current model, we're not seeing problems with it in practice, but there is work to be done to make it easier to map really strange sized things. So users have to round up unrepresentable requests. And then permissions are based on the page permissions requested in the mmap request. So they're pointer permissions. To allow a semi-common pattern of doing a prop none, just to say no permissions, and then poking holes in it to map things. This is, for instance, used in the runtime linker. So slightly important. We've added to CherryBSD and also to FreeBSD now, a max prop macro, which allows you to specify the maximum permissions of the page that both enforces that in in the virtual memory system and tells us what permissions we should actually use for that pointer. Slightly annoyingly, I did it about the same time, NetBSD did something else, and we have slightly different semantics in there incompatible, but it's mmap, every extension is an incompatible mess. And then, so swap, I'm gonna give you a quick picture of how we do swap. So let's consider a user page. Got a little page of user memory here. And then, we have a file store. We haven't touched device drivers and storage and whatnot. Question? So the prop max is you? No, so it has two functions. One is it controls the maximum permissions of the pages you're currently mapping, rather than doing it sort of semi-implicitly. It also, so if you say prop max of read write on a page, you cannot later and protect it to be executable. We enforce that. The second thing is for the capabilities, we derive the permissions from the maxProt if you give one, rather than from the permissions, because you need, if you're mapping, if you need a single pointer that can be used for the runtime linker, it needs to be an RWX pointer. So we've got unmodified tag storage here. We've got unmodified storage here. It's just disk, as usual. We want to swap out to it. So we create a tag bitmap that we store in the swap structure currently, and we store that alongside, and then we store the contents of the page in memory, or in spinning roster, whatever. And then on restore, we combine that information with the swap route capability to redrive these capabilities. So that's how we preserve, from the user's perspective, that this page never left. We preserve those capabilities. So the runtime linker is involved in various bits of setup. It links and loads runtime dynamic libraries as usual. It resolves symbols and synthesizes all the capabilities within the process. One interesting thing with the capabilities is that even a statically linked program has a pointer synthesis portion of the startup, where it has to create all the globals that are pointers and drive them at runtime, because we can't just store them linked on disk. And it jumps to the program entry point, and then the program runs as usual. There's a little bit of additional runtime linker interaction, both demand loading of libraries and handling it doing exception handling. The C runtime is intimately involved in all sorts of bits of capability preservation and setting bounds appropriately. So malloc, when you malloc memory, the bounds are set to the requested size, or at least a slightly rounded version of the requested size, depending on the requirements in order to make that bound only cover the allocation, the underlying allocation. Realloc either adjusts bounds in the case that there's an in place expansion available or allocates new storage. And then thread local storage is allocated throughout yet another malloc implementation, and it is currently bounded per thread. Question? Okay, so the question is basically about rounding in malloc, and if malloc rounds up for various reasons, is the capability, the requested size, or the rounded size? The answer is both. Yes, yeah, so the slightly more detailed answer is if what you requested is representable, which is to say, so let's say anything under a page is representable at practice. If you ask for 4K minus one, you will get a capability to 4K minus one address space. Malloc will have allocated a page probably. If you ask for some weirdly shaped thing that's very large, we will have to round it up and round up its alignment in order for the capability to be fully representable. We will give you then the smallest capability we can. But we will have rounded up both your allocation and the bounds. So in addition to these bits in the runtime library, the compiler generates bounds. If you, for instance, take a reference to something to a stack and pass it to a function, we bound that reference. And as well as we bound references to global objects. Okay, let's consider system calls. Since a lot of the point of this whole work was that I wanted pure capability programs to have their system call pointers be respected by the kernel so that we don't have a potential confused deputies hack where for instance, we call read with a buffer of length, let's say 10 and n bytes of 20. You could, for instance, if the kernel didn't respect your capability pointers, you could use the kernel to implement, for instance, a stack buffer overflow. Interestingly, I haven't found a single case of these. I'm sure they exist and people have exploited this sort of thing, but I suspect that no one's bothered in practice because unmodified C is so terrible. Why would you even have to involve the kernel most of the time to break it? So we've got a thread stack. We put a buffer on it. We're gonna go call a system call. So we load an argument that says, hey, we're gonna call the read system call. Another one that says here's our file descriptor. Here's a capability to the buffer and here's the length. We then call into the kernel that in our case calls into the cherry ABI read call. It calls in turn calls kern read V, which is the implementation of read in the general case. Bit more work. Eventually we get a copy out. This copy out uses, is in fact a copy out C, uses this buffer and if that, if your length is wrong, you will get a fault in the copy out and you'll get a EPROT error. So let's consider some kernel changes we had to make to the kernel to make this all work. So here's the user read thing, which is sits in the middle of that stack where the dots were, where some of the dots were. It is abstracted out. So it's basically, it takes the normal arguments to read. The thread pointer, file descriptor, capability buffer now and end byte. It does the usual bits of checking. We have a new macro that we've, what was the other one? Now we have a new set of macros from refilating IOVex. That's because IOVex now have capability annotations. I didn't want to have to write capability everywhere. I can't type it even after working on this project for eight years. At least I can't type it fast. So we have a new set of macros that manipulate IOVex. And then that's really most of the changes. This function is also new because I've added extra compatibility layers and I'm now allergic to duplicate code. So this would typically be in the Sys implementation or in the Cherry IBI implementation. And now I have a FreeBSD64 implementation. So life is better when I just add more functions. So I'm doing that. Like any proper computer scientist. So another example of changes. I talked before about, very briefly, about capability manipulation. Well, the old bit of code here is an example of a semi-common pattern which violates the rules. So you're taking, you've done, up here you've done a reallocation of memory. You're replacing this, let's see, where are we going here? You're replacing this words with a new string. Or you're replacing pointers into a, into a, sorry, pointers into a copy of this strings very member. And you're doing an update in a way that works because integer virtual addresses are pointers here in the old world. So you've got this old pointer. We're just gonna increment it by the difference between the new and old allocation. The provenance is wrong here. So you've now, you now derive the pointer from the old value, not the new one. So we have to make this change here. So we derive the value from the new pointer rather than the old one. It's a simple change. It's worth noting that this is still undefined behavior. And that basically no use of realloc that isn't realloc extending a string or an array is anything but UB. Just stop, don't use realloc, it's bad. So a quick summary of the changes we had to make. So in user space, we changed about 200 files in the FreeBSD source tree. That's maybe a one percentage. Those changes are mostly in libraries. And most programs required no changes. So to give an example here, OpenSSL required a number of changes because OpenSSL does some interesting things in an attempt to do constant time conditional assignment. It XORs pointers. Needless to say that it's not a capability pointer operation that we support. It's trying to hide the fact from the compiler to ensure that both branches are taken. Ironically, if the code was open coded, which is to say, do one thing or the other thing, the compiler probably would have turned it into a C move, which is more likely to be constant than the mess that they made. But here we are. So, but interesting OpenSSH, for instance, only required one change. And that change was to stop sending pointers to members of an array between the child and parent. That's been fixed. I think we have one. Yeah, I think we do. There is one thing to be aware of with using C move for constant time operation. No ISA actually guarantees that it's constant. Or in fact that both sides are evaluated. Because super scalar. But anyway, in the kernel we had more changes. So we had about 6% of lines of code. This is because we're doing a hybrid program. So everywhere a user space pointer comes down from user space and ends up somewhere, we have to change the file. Additionally, every network driver that takes a struct ifrack in one of our prototype in our 256 bit implementation, which is the initial prototype, the size of ifrack changes. Because it's a structure with a 16 byte name. And then a pointer. So there's an alignment gap. And now I had to edit all those files. So in case you were wondering why I was deleting old network drivers, that's why. So the pervasive changes, a peer capability kernel would definitely reduce the number of changes although it would introduce new changes in the VM system. For instance, detangling the concept of virtual address, which sometimes you're definitely talking about virtual address. And pointer. Because sometimes you're definitely getting a pointer. And the kernel mushes those together of it. So we have a grad student who's working on this. And there's a bunch of science yet to do. So one thing that's useful, one thing that's good to note here, many of these changes improve code quality. We're just writing better C because we're not allowing you to be quite so clever. How many bugs in existing code? Fewer than I might have expected when we started out. We were mostly finding quite small subtle things. I mean, we're finding things like that realoc mess that I showed you. That has like three instances of UB and six lines of code. But mostly we're finding very subtle off by ones. So I think my favorite example is in the T-shell implementation that's the default on FreeBSD. If you hit tab on a blank line, prior to the change that we made, you read one byte before the beginning of a buffer. However, it's in BSS. Most people are on little Indian systems. That byte's always zero. I mean, you know, modulo compiler layout issues and linkers and everything. So probably every once in a while, somebody got a bad compiler or hitting tab on the command line caused a crash. But be extremely aware, it was definitely always mapped memory. So it's that sort of thing where you stick bounds on things, you start finding strange things. Similarly, we've recently added support to the compiler for sub-object bounds, which is to say, if you take a reference to an array in a struct, you can put bounds on it with some exceptions. And we're finding more of those. We've found quite a few, we think all hardmos so far in Lib Archive, for instance, where there was always a read which was then dead because it was unused, just as a result of the way a loop was structured. And so that's the sort of thing we're finding. We have not found any astonishing massive security bugs. Which makes me a little sad, but. Yes, we support all, well, we support most of those things. We also have escape hatches. So you can say, yes, I'm a bad person. Yeah, we support container of, we can do it both in a fine-grained and a coarse-grained way. That's also grad student work that's still in progress. He's busy writing up. So yeah, so most changes I would say improve code quality. I've also brought in a bunch of FreeBSD32 changes where I found I needed compatibility support for something that no one had bothered to implement. So we got the FreeBSD32 version. I might have tested it. So here we are. So one thing, an example of one of our goals was we'd like to reduce bounds on pointers. So I've got here a graph of the size distribution of pointers in a simple open SSL server run. This is created through instruction traces, so it's why it's not a big complicated program. Because the instruction traces take forever, and then it takes eight hours to generate this graph. After quite a few rounds of optimization. So how to read this graph is that this way, upper left is better, which is to say things are smaller. So if your curve is way over on the upper left-hand side, that means most of your pointers are small. If you consider a classic hybrid program, most of the pointers are off the screen. There's a vertical line off the screen to the right, which is say all pointers can access all memory. So we've shrunk pointers quite a bit. Most capabilities are under a page. So that's something. There's a few references to the whole stack as just part of setup and stack manipulation. Likewise, there's a small number of pointers to whole object, because obviously somebody had to have a pointer to the object so you could link it. So yeah, so I think it shows that we are reducing pointers. It's hard to come up with an objective measure of what ideal would be here. So we're working a bit on that, but it's hard to say. We have two variants. We have two variants. The compiler has two modes. The compiler has two modes. And they're not compatible. No, they're not part of the same instruction set. They are variants of the instruction set, which are mutually exclusive. So you could have either 128 bit pointers or 256 bit pointers. We hope to kill 256 pointers. Thanks bit pointers. They are thorn in my side. And combinatorics are not your friend when you add more pointer sizes to your CI system. And you'll see why I care about that in a few slides. So a quick bit of performance. Here's a graph showing we have sort of reasonable performance on a bunch of micro benchmarks. Many of these are designed to make it awful to use pointers. So we have some things. I think Patricia mostly makes trees with no contents. So it's rather pointer intensive. We've also, in here, I've excluded a bunch of crypto and bit manipulation benchmarks that due to an artifact of our system design, that we wanted for a week one decision back in 2010 was that we would do the MIPSI thing here, which is to say we're adding a new feature. So we're gonna use a co-processor extension, which means a separate set of registers. It turns out if your code is register bound, which crypto and bit manipulation code tends to be, and you add more 32 more registers for pointers, your code gets faster. So we don't include those there because it's fair, even though we're using more memory and taking more page faults. So, okay. So a few reflections on using FreeBSD for this process. Since it's always, I think, good to talk about what's good and what's bad about FreeBSD as a research operating system. So the good news, there's a well-obstracted model for adding new program ABIs to FreeBSD. So we already have FreeBSD 32, we have several Linux implementations, we have FreeBSD eight on out support for I386. So that's all there. The current abstraction somewhat bakes in the system five, the x86 system five ABI, which is to say argv and argc are passed on the stack. And they're expected to be, you're expected to have argc, which is confusingly along instead of an int. And then you have all of the argument array, all of the environment array, and then all of the oxargs array. And in fact, in a bit of cuteness, you'll find a bit of libc, where you find the oxargs array by finding the environment array, walking to the end, jumping off the cliff, and then there it is. So we wanted to get rid, we got rid of that, but the abstraction problem here is not bad. The fact that we have central generated system call tables and stub generation code is enormously helpful, that this system calls all, all the bits around hooking them up to the various vectors isn't hand coded, it's enormously helpful. I did add over a thousand lines of code to the Oc that we used for that, but it is there. And having a single relatively hackable with some pain and suffering build system, where I can build seven or 800 programs, and I was able to change things around so I could decide which ABI I was building each program for was really helpful. So I could get a whole bunch of code in place and only have to hack one set of make files, and not hack this program's CMake and this thing's broken autoconf and blah, blah, blah. So having a lot of stuff in one place is actually really helpful. So not slimming the base system down too much is in fact extraordinarily useful for this sort of purpose. The bad, the IOctl implementation, a decision made in FreeBSD, I don't actually remember if the other BSDs do this, I don't remember when the decision was taken. In FreeBSD, the copy in and copy out for structure arguments in IOctl occurs in the general layer before things are passed down. This means you can't forget and whatnot, but it also means you don't actually know anything about the type you're copying, you only know the length. That means I always have to copy in capabilities and copy out capabilities for every IOctl, which as I said way back in the beginning, I don't like doing, it offends me. It also makes it hard to do compatibility in that you now copy this thing in and then you get finally down to where you are, where you wanna manipulate something and you actually know what type it is and you're like, oh, now it's the wrong type, so I need to convert it to the right type so I can actually do some work on it. It'd be nice if we could, so that's an annoyance. And our test framework requires things that aren't in the base system and that means I don't get the benefit of the hackable build system, instead I get the really broken autocont build system. So I've spent hours trying to build this stupid thing static and had no luck. So that's been quite frustrating. But in general, it's been a pretty good platform for us. I think we were able to get enough code up and running so that when we got to the point where we wanted to use Postgres, we'd shaken most of the bugs out. So we were able to then modify Postgres's build system, then modify WebKit's build system, et cetera. So some work in progress. We are currently porting the ISA from MIPS to risk five under a DARPA program and I can announce here that ARM has a port of the ISA to ARM V8 and they are doing a prototype hardware implementation with UK government funding. The implementation is a version of the architecture which is definitely a prototype and definitely a dead end which is to say it has a number of features in it which will not ship, they just don't know which ones. Because there are some science questions we haven't answered and are best answered at scale. So for instance, there's more than one way to implement tags. And so they are implementing both of them in this hardware. There will be boards in about two years. They will be performing and there will be a funding hall at the UK for people who could use UK government money. Let's see, we have a new format for compressed capabilities which I actually adopted that now. The bounds are smaller or we do a bit more rounding now. We're working on temporal memory safety, which is say we've solved the spatial memory safety problem more or less. So now we would like to get rid of all your use after freeze. That's also a work in progress. I hope you submit a paper on that soon. We have some pretty promising results there. I'm gonna swap things around and stop using a compat layer for Cherry ABI and make the default that should reduce the total number of diffs in the system. And also pure capability kernel. So let's see, I'm gonna go ahead and skip this slide. So we have, again, we have a full UNIX-like operating system with spatial and referential memory safety. We have programs, linkers, libraries, everything's covered. We didn't just cheat and do a bunch of hyper-formates, a bunch of little micro-benchworks and call it good. All the kernel accesses are protected. So the kernel can't be used as a confused deputy. And while there were some fundamental changes required, we feel that the overall set of changes is generally pretty non-disruptive. And it's not just things in the free BSD-based system. We have Postgres, which is a big C program, and we have WebKit, which is an enormous C++ program. And they are both running and running pretty well. So I would be happy to take any further questions. There's further reading on our website. And, sorry. So the question is, have we benchmarked the overhead for JIT compilers? We have not done much with JIT compilation yet. That's in fact one of the things that's explicitly mentioned in this funding call for the new ARM prototypes is people to work on language runtimes. I will say that we have done, we had a student who worked on changes to the Rust Runtime, which is not a JIT, but working on the Rust Runtime to make the unsafe parts safe by using capabilities. And we've actually done that with Java, J and I. So we've made the million lines of C code that makes your Java, J and I run. Or JVM run rather. We've pushed capabilities into that and we enforce the Java model on them. Any other questions? Yes. Are the slides available? The slides will be, I think the slides are on the, these slides are identical to the BSD can ones. They're on this website. They will be, if the organizers tell me where to put them, I will send them. Yes, we can run unmodified programs. We can also run hybrid programs. In fact, what we envision is the likely model is that you'll get a hybrid libc whether you want one or not. That mostly means you get faster mem copy. Huh? On this. Well, yeah. On architectures where you don't already have 128 bit registers. Yes, that's a key part of the transition strategy is in fact that we keep everything working. So you can start with one, move to the next. Did I have a question back there? What's walking us from doing a full capability kernel time? I mean, it's, we have one booting. It does work remarkably well. We've started putting bounds on things like mbuffs. So it is a matter of merging that, well, of getting that branch polished, getting all the other things that need to be merged, merged and then getting it merged. It's a big thing actually is the transition of cherry ABI to being the default ABI because it really makes sense when everything's already a capability and more. So that's the big thing. Yes, that's true. He did port go. Thank you. Well, we're merging the register file. So we'll only have one register file. Registers just get wider. Arm is taking the same approach there. I think we are going to support both models transiently because we want to do the science and make sure it really makes sense. There's other, that's probably the biggest one. We've been, we've had a lot of flag days. So we've been willing to polish works off as we go. Yeah, I mean, the kernel code is mostly written by, mostly written by software developers, not grad students. So we have a lot of free VSE experience. So we've tried to do the right thing. That is not to say we've always succeeded. So John is running into that with the risk five port. Thank you all for coming.