class: center, middle ### Secure Computer Architecture and Systems *** # Software Compartmentalisation Abstractions ??? - Hi everyone - Let us talk a bit about compartmentalisation abstract --- name: abstraction # Abstractions - In computer science an **abstraction** is a layer **simplifying** the use of a software/hardware component - By **hiding unnecessary details** and exposing a **convenient interface** ??? - What is an abstraction exactly - It's a term commonly used in computer science - It refers to a simplification layer which goal is to ease the use of a software or hardware component - The abstraction will hide all the unnecessary internal details of the component in question, and will expose a convenient and clear interface for it to be invoked --- template: abstraction
??? - Let's illustrate an example here - We have a complex component that have a lot of different functionalities --- template: abstraction
??? - We create an abstraction that somehow handles all of these different functionalities, and exposes them under a higher-level, simpler interface for clients to use --- template: abstraction
??? - Here the client can be the programmer, another layer, etc. - More concretely, let's take the Linux kernel as an example - The kernel itself implements tons of functionalities, remember it is made of more than 20 million lines of code - The system call interface abstracts all these functionalities for user space applications under a relatively small interface made of a series of system calls that can be invoked to request any service from the OS - In other words the system call interface is an abstraction of the operating system for client applications --- template: abstraction - Examples of abstractions: - A **programming language** abstracts away assembly instructions with high-level statement - The **system call interface** abstracts a plethora of OS features - E.g. UNIX `fork` abstracts many details of process creation/duplication - A **file** abstracts disk blocks - High-level operations on it (`read`/`write`) abstract many mechanisms e.g. caching ??? - Abstraction are absolutely everywhere in computer science - I give you a few more examples here - A programming language is an abstraction of assembly instructions, simplifying the development of programs using high level statements, functions, loops, etc. - Each of the system calls is an abstraction for relatively complex features implemented by the operating system, for example the fork UNIX primitive, implemented through the Linux clone system call, abstract many details of process creation and duplication - A file is an abstraction of data stored on disk and indexed with a particular path on the filesystem - The operations one can apply on a file, such as reading from or writing to it, abstracts away complex mechanisms such as device drivers, block allocation, request scheduling, caching, etc. --- # Compartmentalisation Abstractions - A compartmentalisation abstraction **defines and implement primitives to express compartmentalisation policies in a program** ??? - Now what is a compartmentalisation abstraction - A compartmentalisation abstraction is a simplification layer that exposes primitives for the programmer to express compartmentalisation policies within a program -- - Abstractions helps the programmer or other layers express: - What part of the application goes into what compartment - Where are compartment boundaries - What data should be shared between what compartment - What data should be private to the containing compartment - When and how compartments should be created/destroyed - When and how to set up/update compartments' permissions - Etc. ??? - Using abstractions, the programmer can express many things: - What part of the application goes into what compartment - What and where are the compartment boundaries - What data should be shared between compartments, or private to the containing compartment - When and how compartments should be created and destroyed - How to manage compartments' permissions - Etc. --- # Main Abstraction Categories .leftcol[ - `CREATE` and `DESTROY` a compartment ] .rightcol[
] ??? - The compartmentalisation abstraction one can use look very different depending on what compartmentalisation approach one decides upon - However, most abstraction will fall within one of the main categories that we will present here - The first category is CREATE and DESTROY: the programmer needs a way to express the creation and destruction of compartment in the code --- # Main Abstraction Categories .leftcol[ - `CREATE` and `DESTROY` a compartment ] .rightcol[
] ??? - This is illustrated here --- # Main Abstraction Categories .leftcol[ - `CREATE` and `DESTROY` a compartment ] .rightcol[
] ??? - We create a few compartments --- # Main Abstraction Categories .leftcol[ - `CREATE` and `DESTROY` a compartment ] .rightcol[
] ??? - And destroy one of them - Example of abstractions belonging to the CREATE and DESTROY categories are calling fork() and exit() if one chose to use process-based compartmentalisation --- # Main Abstraction Categories .leftcol[ - `CREATE` and `DESTROY` a compartment - `ASSIGN` permissions to compartments ] .rightcol[
] ??? - Another important category of abstraction is ASSIGN - It corresponds to abstractions that lets the programmer assign permissions to compartments - For example here we could have part of the memory be accessible only by the green compartment, and another part of the memory accessible only by the orange one - We'll see in the next video that there are several mechanisms that allow us to enforce these permissions - An example would simply be again to run each compartment within its own process, this way they would have separate address space --- # Main Abstraction Categories .leftcol[ - `CREATE` and `DESTROY` a compartment - `ASSIGN` permissions to compartments - Transition execution between compartments with `CALL` and `RETURN` primitives ] .rightcol[
] ??? - Because they belong to the same application, at some point compartments need to communicate with each others and transition execution from one to another - These cross compartment transitions are security domain switches, realised with abstractions belonging to the CALL and RETURN categories --- # `CALL`/`RETURN` Abstractions - Achieve privileged domain switches, with **security requirements:** 1. Enforce cross-compartment control flow integrity 2. Switch stack and clear registers to avoid leaks ??? - if we zoom in a bit on CALL and RETURN abstractions, they will generally enforce the following security properties - First, we need some form of control flow integrity between compartments - We cannot let an untrusted caller compartment jump at arbitrary code addresses within another callee compartment, that would be more or less equivalent to letting the caller execute arbitrary code within the context of the callee - So CALL and RETURN abstractions must make sure that cross compartment transition only target legitimate locations in the code - For example a compartment should only be called through the API it exposes - Another thing CALL and RETURN abstraction must do is switch stacks and clear register as part of the cross compartment transitions - Without this, the old stack and register content may contain important information that would leak across compartments, we don't want that to happen -- - **Synchronicity** of `CALL`/`RETURN` - **Synchronous**: caller blocks, akin to traditional function calls/returns - **Asynchronous**: compartments run concurrently and exchange messages, closer to a distributed protocol
??? - Another important aspect of CALLs and RETURNs is the synchronous or asynchronous nature of the transitions - With synchronous calls and returns, compartment switches look similar to function calls and returns - The caller compartment blocks waiting for the callee to return - If the compartments run concurrently, CALLs and RETURNs can also be asynchronous: a caller calls - In that case the application is closer to a distributed system, and transitions between compartments become remote procedure calls involving message passing communications --- # Implicit/Explicit Abstractions - **Explicit abstractions**: exposed to the developer that explicitly uses it - E.g. place an annotation to denote a variable shared between compartments - Implies a certain amount of engineering effort ??? - Abstractions can be explicit, meaning they are exposed to the programmer that must explicitly use them for example by placing annotations - This involves a certain amount of engineering effort, depending on how easy it is to use the abstractions -- - **Implicit abstractions**: handled automatically under the hood - E.g. a framework placing every library within its own compartment - No effort needed from the programmer ??? - Abstractions can also be implicit, requiring no intervention from the programmer because they are applied automatically somehow - For example you could have a compartmentalisation framework placing each library within its own compartment and handling automatically compartment creation, destruction, and transitions -- - Because most approaches are code-centric `CREATE`/`DESTROY` are often implicit abstractions - Each compartment is created when the application is launched - And destroyed when the application exits - With automated approaches `ASSIGN` and `CALL`/`RETURN` may also be implicit ??? - Because most approaches are code centric, CREATE and DESTROY abstractions are often implicit and managed automatically - For example if you are placing a library within its own compartment, that compartment will be created when t he application starts, and destroyed when the application exits - The more automated an approach is, the more implicit abstraction it will support, including ASSIGN, CALL and RETURN --- # Properties Enforced - The vast majority of existing abstractions will enforce: - **Integrity** (prerequisite for enforcing confidentiality/availability) - **Confidentiality** ??? - Software compartmentalisation abstractions will mostly enforce integrity and confidentiality between compartments -- - **Very few target availability** - In our context availability means cross-compartment fault tolerance - Very difficult to achieve ??? - The reason why there is almost no compartmentalisation work targeting availability is rather simple: it is extremely difficult to achieve, and in most cases it requires entirely redesigning the application one wishes to compartmentalise from scratch --- # Enforcing Availability - **Availability**: can a compartment continue to perform its duties in the presence of other adversarial compartments actively trying to crash/starve it from resources? - Require specific abstractions: - Concurrently running compartments, asynchronous `CALL`/`RETURN` - Cross-compartment performance isolation and bounded resource consumption - Careful TCB and interface design to store state outside of compartments - Recursive restart of crashed compartments and their dependencies to maintain state consistency - etc. - Long story short the target software becomes a **distributed application** ??? - Indeed, to be able to preserve availability in the presence of one or more malicious components the application needs to be reworked into a fault-tolerant distributed system - That requires specific abstractions - Compartments need to run concurrently, and CALL/RETURN abstraction need to be asynchronous - We need to enforce performance isolation and bounded resource consumption across compartments, because we can't let a malicious compartment starve the rest of the application of resources - The TCB as well as interfaces need to be redesign to place as much state as possible outside of compartment that may be prone to crashing, because we want to be able to restart them in the case they fail - All of this is obviously extremely complicated, and that complexity needs to be added on top of the difficulty to maintain confidentiality and integrity which is already pretty hard --- # Composing with Other Abstractions - **Processes/threads:** multiprocessing/threading model can either be: - **Orthogonal** to compartments: - A thread/process can execute multiple compartments - Enters/exists them with `CALL`/`RETURN` - **Coupled** with compartments: - A thread/process runs a single compartment only - `CALL`/`RETURN` spawn/transition to the corresponding thread/process
??? - One last important consideration about compartmentalisation abstraction is how they compose with other system abstractions - For example regarding threads and processes - They can either be orthogonal to compartment, with one thread or one process able to execute multiple compartments, with compartments transitions realised in the context of the thread or process - This is illustrated on the left here, with two threads running several compartments, transitioning upon CALLs and RETURNs - Processes or threads can also be coupled with the compartment - In that case each process or each thread executes a single compartment only, and upon CALLs and RETURNs the thread/process running needs to be switched - This is illustrated on the right here --- # Composing with Other Abstractions **CPU privilege levels** - Application-level compartmentalisation abstractions are influenced by the **user/kernel interface** - Kernel may play the role of a monitor performing security domain transitions - Or this can also be done in user space ??? - When compartmentalising an application, the abstractions used are influenced by the user kernel interface - In particular, as many compartmentalised systems require a privileged monitor to perform security domain transitions, the kernel can play that role - It is the case implicitly when using process-based compartmentalisation - The monitor can also be placed in user space, which may have some performance benefits or drawback based on how the monitor is implemented and isolated -- - Kernel/hypervisor-level compartmentalisation harder as these generally assume ambient authority - Need to establish a TCB and isolate it ??? - When compartmentalising the kernel or another privileged entity like a virtual machine monitor, things are more complicated because these entities are used to run with full privileges, but we need to reduce some of these privileges for certain compartments - The goal here is to establish a trusted computing base that will play the role of the aforementioned monitor, and isolated it somehow from the rest of the kernel or hypervisor --- # Composing with Other Abstractions .leftcol[ - Compartmentalisation prevents direct access from a compartment A to a compartment B - Given proper interface security between A and B - **Other system interfaces should also be resilient vs. attempts to bypass the compartmentalisation** - System call interface (`mmap`) - Pseudo filesystems (`/dev/mem`) ] .rightcol[
] ??? - The isolation mechanisms used for compartmentalisation, that we will cover in the next video, are quite robust and in most cases, combined with some good interface security, they will prevent a compartment A being able to directly access the private memory of another compartment B - Still another attack vector is the secure monitor, which in many cases is the kernel - if not properly secured for compartmentalisation, some kernel interfaces may allow a malicious compartment to access memory supposed to be private for other compartments - Kernel interfaces are well secure when considering process-based compartmentalisation, because the kernel is already designed to provide good isolation between processes - But when using some of the more modern isolation mechanisms these interfaces may become concerning, because the kernel was not designed with these isolation mechanisms in mind --- # Summary - **Compartmentalisation abstractions** let the programmer define how to split an application into lesser-privileged compartments - Denote compartment boundaries, assign permissions, etc. - **Main categories**: `CREATE`, `DESTROY`, `ASSIGN` permissions, `CALL`, `RETURN` - Can be **implicit** or **explicit** - Most existing approaches target enforcing **confidentiality** and **integrity** - **Availability** is hard to enforce - Need to **compose** with other abstractions: execution flows, privilege levels, kernel interfaces ??? - To sum up, compartmentalisation abstractions represent layers of simplification that lets the programmer compartmentalise an application more or less easily - The main categories of abstractions are CREATE and DESTROY a compartment, ASSIGN permissions to it, CALL and RETURN to transition back and forth between compartments - Abstractions can be implicit or explicit - Most will target the enforcement of confidentiality and integrity, while availability is generally out of scope because it is too hard to enforce - Compartmentalisation also need to compose with other system abstractions such as processes and threads, privilege levels, and kernel interfaces