blob: a400a941e2b5dae505944c792add8f7bb072998d [file] [log] [blame] [view] [edit]
# Verified Execution
## Background
Fuchsia is designed from the ground up to support and extend strong verified
boot models. In most systems with verified boot, verification only covers code
up to a certain privilege level, and at some point unverified code must execute.
Fuchsia has taken verification into the runtime of the system, hence the name
verified execution (abbreviated as VX or FVX). The goal of FVX is simply to
ensure that all executable code is trustworthy.
Unless specified, this document uses *executable code* to refer to machine code
that executes directly on the hardware. Executable code does *not* refer code
that is sandboxed, interpreted, or otherwise subject to a confinement mechanism.
### Scope
This document describes the general principles and phases of verified execution
for an end-user buildtype (unless otherwise specified). Implementation details
and policy decisions that are hardware- or product-specific are out of scope.
This document focuses on the core verification logic and leaves out several
layers of defense-in-depth.
### Security Models
VX considers two security models: the running software model and the verified
boot model.
```
+------------------------------------------------------------------------------+
| |
| +---------------------------------------------+--------------------------+ |
| | | | |
| | +------------------+ +--------------------+ | +----------------------+ | |
| | | Recovery from | | Recovery from full | | | Mitigation against | | |
| | | physical attacks | | attacker control | | | vulnerabilities | | |
| | +------------------+ +--------------------+ | +----------------------+ | |
| | Verified boot model | Running software model | | |
| +---------------------------------------------+--------------------------+ |
| |
| Verified execution |
+------------------------------------------------------------------------------+
```
#### The Verified Boot Security Model
There are two aspects to the verified boot security model: recovery from
physical attacks, and recovery from full attacker control. Physical attacks are
largely out of scope for this overview, but suffice it to say that an attacker
with physical access can accomplish strictly less than one with full control.
*Full attacker control (FAC)* is the ability for an attacker to run code of
their choosing as supervisor or kernel mode (the existence of more powerful
modes of execution is not considered here). The goal of a defender in this
security model is to recover by eliminating untrustworthy states (code and data)
in which attackers could persist control across reboots.
Fuchsias response to the verified boot security model is to allow a computer to
recover from any compromise by simply rebooting. At boot, Fuchsia verified
execution recursively guarantees that
1. all executed code is trustworthy according to an anchor (for example, code
in ROM), and
1. all data that serves as input to code is trustworthy according to the same
anchor.
This guarantee allows the system to fully remove untrustworthy states, even in
the presence of arbitrarily bad vulnerabilities.
##### Anti-Rollback
The verified boot model assumes that an attacker can replace mass storage
content with the content of their choosing. Under this model, any security
vulnerability that can be exploited after a reboot can never be patched without
a hardware-based anti-rollback mechanism. Without such a mechanism, an attacker
could flash an older, vulnerable version of the operating system that will pass
verified boot checks and then replay the exploit needed to regain FAC.
Hardware-based anti-rollback prevents this scenario entirely by refusing to boot
an out-of-date version of Fuchsia once the vulnerability has been patched.
#### The Running Software Model
In this model, software runs and attackers try to exploit vulnerabilities in the
software to gain control over the running software, usually by crafting
malicious inputs to the software. In this security model, the aim of a defender
is to solve or mitigate possible vulnerabilities by hardening code against
malicious input.
Fuchsias response to the running software model is to restrict execution to
trustworthy code. (What is trustworthy will be defined in more detail later.)
### Principles
The following is a non-exhaustive list of general principles guiding the
implementation of Fuchsia verified execution:
- Code and associated read-only data should be verified against an intentional
policy. Policies may not be the same for every kind of code, product, or
build type, but all code should be bound to a policy. A simple example of a
policy is code cannot execute unless it is digitally signed by a trusted
party.”
- There is no default or fallback policy.
- Policies do not allow one-off, side-loaded code (no snowflakes”).
- Policies rarely allow just-in-time compilation (JITs).
- Inputs to policy verification and cryptographic schemes should be as simple
as possible.
- Policy verification should not require parsing untrusted complex data.
- Verified execution can be implemented on a variety of hardware, but strongly
prefers to have hardware immutable trust anchors and tamper-resistant
storage (for example, to store a rollback index that can only be modified by
firmware, not the kernel).
## Phases of Verification
Code and data are deemed trustworthy after verification using a hash-and-sign
paradigm: data is loaded in message+signature pairs and verified using a trusted
public key. Messages contain cryptographic hashes of larger code or data blobs,
which are hashed and verified before treating them as trustworthy.
### Phase Zero: Hardware to First Bootloader
Phase zero comprises the hardware to software transition. There is nothing
verified execution can do against hardware attacks, so for the purpose of this
document, the hardware is assumed to be trusted. In this phase, immutable code
verifies the first bootloader against an immutable trust anchor (for example, a
public key in One-Time Programmable (OTP) memory). The details are
hardware-specific.
### Phase One: First Bootloader to Main Bootloader
Once the first bootloader has been verified, it is trusted to verify and execute
additional software needed to boot the system. This could be a chain of software
images where each one verifies and executes the next, or it could be a tree-like
flow. This phase is also hardware-specific.
### Phase Two: Main Bootloader to Preauthorized Code
The main bootloader is responsible for the verification of preauthorized code,
which refers to all code explicitly approved by a product authority to run as a
core part of that product. Preauthorized code may run on the hardware directly
(i.e. without a sandbox) and includes all the required elements of a Fuchsia
system (for example, Zircon kernel, package management system, drivers). The
bootloader verification may be product-specific, but usually a version of
Android Verified Boot
([AVB](https://android.googlesource.com/platform/external/avb/+/master/README.md)))
is used to verify the Zircon Boot Image
([ZBI](/docs/glossary/README.md#zircon-boot-image)).
#### Delegation from the Main Bootloader
After the bootloader verifies the ZBI, there are two crucial points where
verification responsibility is delegated.
1. The verified ZBI verifies the package management system. Specifically, the
ZBI receives from the bootloader a precise description of the package
management system and enforces all policies which apply to that system.
1. Once verified, the package management system becomes responsible for the
verification of all remaining preauthorized code that is not part of the ZBI
or the package management system.
#### Direct vs Indirect Verification
Direct verification specifies exactly *what* to trust by precisely describing
the software that is permitted to run (for example, using a cryptographic hash
of a package). Indirect verification specifies who to trust and leaves the
*what* up to a delegated authority. Once the package management system takes
over as the VX policy enforcer, it may use indirect verification to allow for
on-the-fly delivery of software without a full system update and reboot. To
maintain the chain of trust, components and configuration involved in the
indirect verification process (ZBI, package management system) must always be
verified directly.
Note that descriptions for direct verification can be arbitrarily complex. If a
signature is attached to a hash of a file that contains more hashes, all of the
hashed content is still directly verified.
### Phase Three: Non-Preauthorized Code
If neither the code nor its signing authority was authorized by the product
owners, that code is not verified so much as it is subject to a static policy.
Some purpose-built devices may not run non-preauthorized code at all. The
enforcement of the static policy is the role of the package management system
and component framework.
#### Strong vs Weak Intention
Non-preauthorized code with strong intention is code from a package that the
user, acting in an administrative role, has explicitly authorized to run in a
particular environment. This type of non-preauthorized code can be run directly
on the machine in an environment that is isolated from preauthorized code.
Non-preauthorized code with weak intention is code that is loaded incidentally
or authorized by users not acting in an administrative role (for example,
JavaScript loaded by a web page). Non-preauthorized code with weak intention
must be sandboxed, interpreted, or otherwise subject to a strict confinement
mechanism.
## Implementation
The following Fuchsia systems are integral to enforcing verified execution
policies.
### Main Bootloader
The main bootloader implementation relies on
[Android Verified Boot](https://android.googlesource.com/platform/external/avb/+/master/README.md)
for verification and kernel rollback protection.
### BlobFS
[BlobFS](/docs/concepts/filesystems/blobfs.md) is a cryptographic,
content-addressed filesystem purpose-built to support verified execution. BlobFS
is the sole storage system for executable code and associated read-only data
(with exceptions for pre-kernel code, the kernel, and its
[bootfs](/docs/concepts/process/userboot.md#bootfs)) ramdisk, all of which are
stored in the ZBI). Each blob in BlobFS is uniquely represented and accessed by
a hash ([Merkle root](/docs/concepts/packages/merkleroot.md)), and a Merkle tree
structure allows for random blob access. It is computationally infeasible for an
attacker to change a blob without changing the hash.
Once BlobFS has been loaded in Phase Two, the method of verifying preauthorized
code is simply to check a signature for each blob hash that is loaded, and to
ensure that the blob content matches the hash. BlobFS performs the content
verification, and the signature checks are delegated to the package management
system.
### Package Management System
The package management system, also known as the software delivery (SWD) stack,
adds a layer over BlobFS to make handling blobs friendlier to programmers. The
SWD stack translates Merkle roots describing packages (specifically, the
[Merkle root of the packages meta.far](/docs/concepts/packages/package.md#structure-of-a-package))
into human-readable package names. If any package content changes, the Merkle
root of the meta.far will change. For brevity, this document will use package
hash to refer to the packages meta.far Merkle root.
The SWD stack manages a special package called the system image, which includes
a list of hashes for all
[base packages](/docs/concepts/packages/package.md#base-packages). The system
image package is verified by the ZBI when the package management system is
loaded in Phase Two, so all base packages are directly verified; that is, no
signature checks are required after boot. For some purpose-built devices, the
SWD stack can be configured such that only base packages can be loaded into
memory with execute rights, which means the device will only execute directly
verified code.
For indirectly verified code
(“[universe packages](/docs/concepts/packages/package.md#universe-packages)”),
trusted public keys and other metadata about the delegated authority that
provided them are included in a location covered by the verified boot signature
(for example, the ZBI or a base package). Any packages downloaded from such an
authority must be verified, by signature and content hash, every time the
package is loaded (rather than checking the signature once at download time).
Backstop version metadata may be included in the directly verified data to
ensure that both directly and indirectly verified packages are covered by
hardware anti-rollback protection.
The SWD stack is also responsible for downloading
[system updates](/docs/concepts/packages/ota.md). It has a number of controls
that prevent any blobs from an in-progress update from being available to the
running system before the device has rebooted into (and thus verified) the
update.
### Component Framework
Packages are the unit of software *distribution*, whereas components are
Fuchsias standard unit of software *execution*. One consequence of this
distinction is that most Fuchsia components do not interact directly with the
SWD stack. Instead, the main customer of the SWD package resolver is the
component framework, which contains a number of
[component resolvers](/docs/concepts/components/v2/capabilities/resolvers.md)
that delegate to the SWD stack to load code and data. Some component resolvers
are limited to base packages, and others may allow access to universe packages.
Like other Fuchsia capabilities, these resolvers are routed in component
manifests, which provides additional control over and auditability of their use.
For example, indirectly verified code can be limited to only specific
[environments](/docs/concepts/components/v2/environments.md).