 Hello, my name is Alexandru and welcome to this talk together with my colleague Alexandru here. We've been contributing to DocOS for the last year and throughout this talk we will talk to you about the latest project that we have been working on about building a low latency secure embedded system using EPPF. By the way, even though this is a pre-recorded session, it is recorded live, so if something goes wrong, it will go wrong. Now I will ask Alex to make an introduction about how DocOS works. Thank you Alex, everybody. I'm really excited here that we can talk about Doc, an operating system written in Rust. A few words about Doc. It's a modern operating system designed for microcontrollers, so drone controllers or industrial devices. That is basically modeled after a normal operating system like Linux. It runs several applications, has a separated kernel, so on and so forth. Alex, if you can move the slide please. A few words about, details about Doc. It's a preemptive operating system, meaning that it can stop applications and swap them on the CPU. It currently runs on Cortex-M processors and RISC-5 processors. An important feature of Doc is that it uses the memory protection unit or something similar in the RISC-5 processor, and every application runs in its own memory sandbox. All of its applications were fault, similar to a sac fault in Linux. An important feature of Doc, which makes it unique in the MCU operating system space, is that it has a separated kernel. So just like Linux, which has the Linux kernel and on top of it applications, Doc has the doc kernel. It is written fully in Rust. It is compiled separately and loaded separately on devices. On top of the kernel we have the user space or the applications. Applications can be written in any language as long as it compiles for the platform and will be loaded separately to the board. Alex, this is the general architecture of the Doc operating system. At the bottom we have the hardware or the microcontroller. On top of the microcontroller or the board, we have the kernel, which is composed out of three parts. First, we have the low-level drivers, which are represented in orange here. These are drivers that directly interact with the hardware. In our case, and this is really, really important, these drivers are considered to be trusted code. This means developers can use any means to write them, including unsafe code in Rust. On top of the kernel we have the capsules. Capsules are high-upper-level drivers that usually either export system calls interfaces to the applications, either export services to other capsules. From the kernel point of view, these are untrusted, meaning they only contain safe Rust code. It means we cannot direct access memory. We have to obey all the rules of the Rust compiler. The glue code between these two levels is the actual kernel. The actual kernel, again, is a trusted piece of code, meaning it does make use of unsafe. It handles processes, the scheduler, and all the interfaces that the capsules use to communicate with each other. This kernel is a completely separate part and is uploaded as a firmware to devices. On top of the kernel we have the processes. Processes, as I said before, can be written in any programming language as long as they compile. This is completely untrusted code, and applications are sandboxed to their own memory space. And yes, this is a new feature that TOC brings to the table, they fault. So what's the problem? Due to the design of TOC, the operating system is not an actual real-time operating system. In the sense that it doesn't have low latency. All the interrupt handlers in TOC are bottom-half handlers. This means that when an interrupt arrives, certain peripherals need attention. It signals the MCU. The MCU signals the operating system, meaning TOC. TOC registers that it needs to process the interrupt, but then continues in the kernel and will process the interrupt a little bit later. This is due to the design of TOC. Another important aspect of TOC is that the kernel is not preemptive. So in TOC we have only one single thread that runs the kernel, meaning that if a driver or an interrupt handler takes longer to execute, all the others will wait. It cannot be preempted. So everything happens in bottom-half handlers. On the other hand, TOC provides applications which are sandboxed. So whenever we usually want to add functionality to a system, we will update an application. Now this design is not great if we have a low latency system, like a drone, which has a flight controller, or like a high-speed industrial line, where the response needs to be really, really fast. So our main problem is, how can we run arbitrary code really fast, but still obey TOC security sandbox? And I'll let Alex continue from here. Okay, so what we want to achieve, as Alex said, we want to run arbitrary code in the kernel. This managed without actually recombining the kernel. So this is where the EBBF part comes into play. EBBF was created with the main purpose of extending the kernel capability at runtime, which means that we have a sandboxed environment inside the kernel, which is used to run EBBF code that is passed from the user space. This being done without having the need to modify the kernel source code, because this is happening at kernel runtime, operating system runtime, and also without the need to load additional kernel modules. Initially, the EBBF standard was created in order to abuse for networking purposes, such as the name says, extended Berkeley packet filter. So as for use cases, the main tree that I will present here are the main original use cases. The first one being, having managing high performance networking, so that at runtime in the kernel, we can add additional protocol parsers or maybe even forwarding logic when the packet comes from the network. The second use case would be having security enforcement, so the EBBF framework coming as a system called filtering framework, which can also act for process context tracing. And also the last use case, which can be used for enabling EBBF programs that are run as breakpoints throughout the process. The last use case that we come with for our framework is to use EBBF programs in order to execute fast interrupt handlers in doc. Therefore, the new architecture that Alex presented earlier would look like this. Inside the capsules, we would have a new capsule that is able to interpret and create a VM virtual machine that runs EBBF code. And inside the application, we can upload through to the capsule. We can send to the capsule or the EBBF code that should be run in case of an interrupt. So it would look like this. We have an external interrupt that comes on a pin, for example. We upload the EBBF code that needs to be run when that interrupt appears. The capsule has set the EBBF code. And when the interrupt appears, the VM is created and the EBBF code that we uploaded earlier will run. What we used in order to implement EBBF inside the capsule was the RBBF module, which is a user space virtual machine for Rust, to which we had to do several modifications because this is a user space module. It has a lot of standard library dependencies, which cannot appear inside operating system, which has no such dependencies. So first we had to remove all dependencies such as usage of VEC, which is the vector for Rust. Then we had to remove the unsafe code blocks, because as Alex explained earlier, inside the capsules, we cannot have unsafe code blocks. Then the last thing, this module came with a lot of just-in-time compiler implementations, which are not a needed feature inside of talk. That would be all. Thank you. If I may add another comment, Alex. One thing that is usually important at EBBF is that it is guaranteed to finish. So why did we choose EBBF to run this and not some other arbitrary binary code? First, EBBF can be compiled by LLVM. So whenever talk users build their applications, they can automatically build the EBBF part in their application. Secondly, compilers like LLVM try to unwind the code and restrict the code from making infinite loops. And third, we use the virtual machine and knowledge it, because that way we can count the number of cycles that that machine performs. And if the surpasses a certain limit, we simply stop the machine, the EBBF machine. This will guarantee us that any capsule that runs EBBF code will actually finish in a certain amount of time. This being said, thank you so much. Thank you, Alex. And we kindly encourage you to take a look at our project on GitHub and we expect contributions. And if you have any questions.