,

Fuchsia: Rethinking OS security design after 50 years

Nikolai Thees

Ever since the inception of Unix in the 1960s, the core design of most general purpose operating systems we use today has remained largely unchanged. However, over time, many of the security principles established during that era have since been deemed outdated. In this article, we will look into Google’s new operating system called Fuchsia, exploring how it differs from other conventional operating systems with a focus on security design patterns.

Fuchsia (pronounced: /ˈfjuːʃə/) mainly improves on OS security by limiting the components which have access to kernel privileges, by employing the principle of least privilege, and by isolating components from each other and from the rest of the system.

While Fuchsia cannot be described as a traditional Unix-like OS, it nevertheless draws inspiration from Unix and Unix-like systems, particularly Linux. To better highlight the differences and similarities to those systems, I will be comparing Fuchsia to Linux throughout this article.

Microkernel

Microkernels provide a minimal set of functions to an operating system, which are required for the OS to run on and interact with the computer hardware. Microkernels are much smaller and easier to overview than monolithic kernels, which provide further auxiliary functionality to an OS in order to reduce the complexity of interaction between various OS subsystems and hardware components. The minimalism of the microkernel not only reduces the attack surface of the kernel but also makes it more difficult for malicious contributions to go unnoticed by code reviewers.

Another benefit of the microkernel approach is that the separation of the modules makes updating these modules independently from each other much easier, allowing for security fixes to be applied quicker.

Wooptoo, Public domain, via Wikimedia Commons

Fuchsia’s microkernel is called Zircon. While Zircon technically refers to an entire subsystem that also contains some services, drivers, and libraries required to boot and run the system, these components are not part of the kernel itself and run in the user space instead. The kernel itself only contains the scheduler, memory management tools, an inter-process-communication (IPC) system and synchronization mechanisms like locks.

This design differs greatly from the monolithic kernel design of the Linux-Kernel, which, in addition to the previously mentioned modules, also contains storage and network controllers, filesystems, device drivers and more. In fact, drivers make up around two-thirds of the entire Linux-Kernel source code.

A consequence of this design is that drivers run with the same privileges as the very core of the OS, which becomes a problem when drivers aren’t developed to the same standard as the rest of the critical operating system modules. The sometimes ridiculous implications this has for security can be read in the article “Security Knockout: How Capcom’s Street Fighter 5 punched a hole in Intel’s security system” from a fellow student of mine, Frederik Omlor.

In Fuchsia, device drivers run in user space and since they are isolated from the kernel and the rest of the system, they can do far less damage to the OS, should they contain malware.

However, this also poses a problem because drivers usually need access to hardware and physical memory addresses, which are privileges reserved for kernel mode operations. So drivers require an efficient IPC solution to communicate with the kernel. While Zircon does provide IPC using a purpose-built interface definition language, this is nevertheless not as performant as giving the driver direct hardware access from the kernel space. With the microkernel approach each hardware access from a driver requires expensive context switches from user mode to kernel mode and back. The performance implications this has, is the reason why the OSs we use today opted for a monolithic kernel when they were designed. Due to limited performance of systems back in the late 1900s OS designers chose the more performant monolithic approach to kernels over the more secure microkernel approach.

Sandboxing & Namespaces

One of the most notable security mechanisms of Fuchsia is the isolation of components from each other and from the rest of the system. These components run in their own so-called namespace which only they have access to and which they cannot escape by default. This practice of separating processes, resources, etc. from each other is called sandboxing.

In the context of Fuchsia, “components” is the term used to describe any form of (executable) software running on a Fuchsia system. In order to not overcomplicate things in this article you can assume that a component is essentially the same thing as a process in Unix/Linux. An application or program can consist of multiple components (e.g. a frontend and a backend) that run in separate namespaces which may share resources via IPC mechanisms. This separation into different namespaces, by design, restricts access to resources not owned by the program.

Since components run in their own namespace, they may be the owner of all content within their namespace without risking that the component tampers with files that it shouldn’t do. The component process essentially becomes the root user in its own namespace. In theory at least, this makes privilege escalation attacks effectively impossible as there are no further privileges to gain within that namespace. It also makes path traversal attacks a thing of the past because the process cannot simply “../” into another directory, owned by another component (except when they are explicitly shared). The sharing of resources between components needs to be wanted by both parties. The mechanisms used to manage all this is discussed in the next chapter.

There are some interesting quirks with this design. Since the filesystem is a user space process, the kernel has no knowledge or concept of what a file or a directory is. Because of this, the kernel provides no system calls to open or write files. Instead, programs directly interact with the filesystem component, which itself interacts with either the memory management of the kernel or with the block device drivers, which also run in user space. With the filesystem being a user space process, it also lives inside its own sandbox and namespace. As such, there is no central “root-filesystem”, so each component, by design, needs to be provided its own personal filesystem.

The reason why sandboxing improves security is best explained with a concrete example: 

Let’s say I installed a music player application on my system to listen to some local audio files. On a standard Linux OS, which doesn’t have additional privacy & security systems like there are on Android or iOS, this music player would have access to all of the resources that my user account has access to. This includes all my personal documents, photos, videos but also access to devices like my camera.

Fuchsia employs the principle of least privilege, this means that components need to explicitly request access to resources before they can access them. This means that by default, the music player doesn’t even have access to my audio files. Obviously this defeats the purpose of a music player, so in order to be able to play audio files, the app would need to request the capability to open these files from the system.

Capabilities

Capabilities are Fuchsia’s solution to managing access to resources. They provide a flexible and secure way to control which component may access which resource, and what they can do with it. It achieves this by combining the means of access to a resource with a set of rights, such as read and write. For example, a capability might grant read access to a specific file or directory, or it might grant the ability to start a particular service.

Initially this may sound similar to what access control lists (ACLs) do on traditional OSs. ACLs on Linux specify access permissions per file on a user/group/world basis. For example: if I have a file called important.txt, I would be able to display its ACL using the  getfacl command like so:

This command lists the owner (user) of the file along with the group the user belongs to, as well as the file access permissions, represented as “rwx” triplets, each for the user, the group and the rest of the world. In this case only my user “nikolai” is allowed to read and write the file, everyone else may only read its contents. The file may not be executed by anyone.

While ACLs manage file access on a per-user level, capabilities manage access on a per-component level. What this means is that on my Linux system, every process which is started under my user account has access to this file, while on Fuchsia every process would individually need to request the capability to access the file before being granted a handle to it. As a result, capabilities offer far more fine-grained control over resources than ACLs. 

Components do not get any capabilities without requesting them, even if other components offer them access, again employing the principle of least privilege. Components may provide capabilities to its own resources to other components, be they child components or the parent.

All of the capabilities a component requires and supplies are declared in the component manifest file that is distributed in the same package as the component program files and dependencies. Below is the content of a component manifest file of a hello world application.

While there is only one capability explicitly declared inside this manifest in the use section, this manifest in fact provides three different capabilities to the component:

  1. is imported from the “syslog/client.shard.cml” manifest file which uses the “/svc/fuchsia.logger.LogSink” capability in order to write log files. 
  2. is the capability to run an ELF (Executable and Linkable Format, the standard binary file format for Unix and Unix-like systems) encoded binary using the Fuchsia “elf” runner. Fuchsia also offers other runner types, for example the “web” runner, which uses the chromium engine to execute and display web content.
  3. is read and write access to a “data” directory mounted under /data inside the component’s namespace

Evidently, there needs to be a central instance which parses all of these manifest files and tracks all components and their capabilities. This is the job of the component manager, which is one of the first components that is started during the boot process, as it manages all other components running on a Fuchsia system. It ensures that components are executed and managed in a safe and secure manner and provides them with the necessary capabilities and checks whether an attempt to access a resource is allowed by a corresponding capability.

The caveats of this design

There is no free lunch, and these security mechanisms generally come at a cost. Some of them are performance related others more convenience/workflow related. 

As discussed earlier, the microkernel is not as performant as the monolithic kernel design. This is due to the many context switches between user and kernel space by the components that run in user space but need to perform kernel related tasks, e.g. device drivers.

Because of the high reliance of components on IPC, not just between kernel and user space but also between each other due to strict sandboxing, the IPC mechanism needs to be extremely efficient as it’s so performance critical. This could result in highly complex interface definitions, in order to reduce communication overhead.

Speaking of sandboxing: The principle of least privilege is extremely restrictive and there are lots of quirks with this design that break many of the workflows we are used to from other OSs.

Remember how we discussed that there is no central “root-filesystem”? That’s a huge problem! There are plenty of good reasons why files need to be shared between programs. When I’m working on my computer, I need to be able to store and modify files. Where do I store my personal documents so that I can open them with one sandboxed app and edit them in another sandboxed app? There would need to be a component which simulates a global filesystem, where I can store all my documents. But how would other applications know to look for this component in order to access my files? Would that file-manager component automatically offer access to my files to all other components? That would mean my files are not protected from malicious programs. And if there isn’t a central filesystem, how else would I be able to transfer my files between components? Would I need to manually set the required capabilities for each component? 

Unfortunately, I could not find any satisfying answers to these questions in the official documentation.

Capabilities are also unpredictable at times due to their ability to change during the runtime of components. There is no guarantee that a requested capability can actually be accessed if the provider component fails to start properly, fails to provide the capability with the same parameters as requested or simply fails to offer the capability because of misconfigurations or bugs. This means every access to a resource through a capability would need to be surrounded by a try-catch block to ensure program stability.

Consider this example where component A offers access to its data directory with only read access to component B. Since component B requests the capability to the data directory with read & write access the capability routing fails and the data directory won’t exist in B’s namespace.

Since managing capabilities is a non-trivial task, it is going to be tempting to developers to play it safe and route capabilities between components even if they might not need them, defeating the point of the principle of least privilege.

So the capability concept only works correctly if application developers play along with the rules of the system. That’s an optimistic assumption given that hackers and malware authors rarely care about the rules.

Speaking of hackers, they will probably set their sights on the critical components of the OS, like the component manager, which is responsible for managing all other components of the system and their capabilities. Should bad actors gain access to this component then they essentially have control over the entire system, even though it’s not running in the kernel. 
Another thing I personally find disappointing is that the Zircon kernel itself is going to be susceptible to the same buffer overflow attacks as for example the Linux-Kernel, as it is written in C++, not in a memory safer language like Rust. In fact, it was decided that the usage of Rust is actually not even allowed inside the kernel, only in the user space [source].

In conclusion…

Fuchsia is still very much a work in progress and the system design is still subject to change. So it is still too early to definitively say whether or not Fuchsia’s design is superior to that of our current OSs.

As of right now, it’s very difficult to say how Fuchsia will be able to compete with today’s OSs. It will need developer support in order to thrive and for that to happen, developers will need to be able to understand the concepts of Fuchsia. With the current vague state of the official documentation, that’s unlikely to happen.

Fuchsia is still surrounded by mystery, only very rarely is it publicly mentioned by Google at all. It remains to be seen what exactly Google’s plan for this OS is. Maybe it is like they initially said simply a project to test out new design concepts that could be applied to existing OSs like Android [source]. However that seems implausible since, with the Google Nest Hubs, there are now devices running Fuchsia in production. Sadly the Google Nest Hub as a smart home device that has a minimal user interface makes little use of the security features provided by Fuchsia. And recent news saying Fuchsia isn’t coming to the other Google Nest smart speaker devices isn’t good news for the operating system [source].

And concerning security, it would be unfair to say that other OSs haven’t evolved at all. A lot of improvements have been made, especially on the user space side. For example on Android or iOS there are privacy and security systems in place that prevent apps from accessing hardware components like the camera or GPS location without the user’s permission. On Linux OSs Flatpaks and Snaps run applications in sandboxed environments, which allow for fine-grained control over resource access. There are improved mandatory access control frameworks like SELinux or AppArmor which offer much greater control over resource access than the default ACLs. It would be interesting to compare the security of these to the capability system of Fuchsia.

And even the kernel architecture of OSs has evolved over the years. The macOS XNU kernel as well as the Windows NT kernel are both considered hybrid kernels that make use of the improved security and stability of moving certain kernel components to the user space. This is despite the fact that changing the architecture of a running system without breaking things is extremely difficult. Also Unix and Unix-like systems all adhere to the POSIX standard, which cannot be modified too drastically without breaking compatibility for a large portion of OSs. 

Writing a new OS basically from scratch, like Google is with Fuchsia, presents the opportunity to revolutionize the design of operating systems. We will need to wait and see whether Google will follow through with Fuchsia or if it’s going to end up being abandoned like so many other Google products and services.

Written by: Nikolai Thees

Sources

Fuchsia – official developer documentation
https://fuchsia.dev

Francesco Pagano, Luca Verderame, Alessio Merlo – Understanding Fuchsia Security
https://arxiv.org/pdf/2108.04183

Anna-Lena Marx, Inovex – A deep-dive into Fuchsia
https://www.inovex.de/de/blog/a-deep-dive-into-fuchsia/


Posted

in

,

by

Nikolai Thees

Comments

Leave a Reply