Skip to content

Commit

Permalink
docs for rpi4 and fit-image support
Browse files Browse the repository at this point in the history
  • Loading branch information
nihalpasham committed Jun 4, 2022
1 parent 7e4a9f9 commit 86d4d01
Show file tree
Hide file tree
Showing 8 changed files with 555 additions and 23 deletions.
14 changes: 7 additions & 7 deletions book.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,19 @@ multilingual = false
src = "src"
title = "rustBoot Documentation"

[preprocessor]
# [preprocessor]

[preprocessor.mermaid]
command = "mdbook-mermaid"
# [preprocessor.mermaid]
# command = "mdbook-mermaid"

[preprocessor.svgbob2]
font_family = "Lucida Console"
# [preprocessor.svgbob2]
# font_family = "Lucida Console"

[output]

[output.html]
additional-js = ["mermaid.min.js", "mermaid-init.js"]
git-repository-url = "https://github.com/nihalpasham/rustBoot-book"
git-repository-icon = "fa-github"
default-theme = "rust"
preferred-dark-theme = "navy"
default-theme = "light"
preferred-dark-theme = "ayu"
Empty file modified ci/spellcheck.sh
100644 → 100755
Empty file.
254 changes: 246 additions & 8 deletions src/arch/images.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
# rustBoot Images

rustBoot images comprise of a `256-byte header` pre-pended to a firmware binary and are deliberately designed to be as simple as possible.
rustBoot supports 2 types of firmware image formats, depending on the underlying device. It could either be an
- [`mcu-image:`](./images.md#mcu-image-format) a simple 256-byte firmware image format for microcontrollers or a
- [`fit-image:`](./images.md#fit-image-format) the flattened-image-tree format for systems capable of booting linux.

- it does not rely on the use of complex digital certificate formats to [`reduce its TCB`](../index.md#trusted-computing-base) and avoids unnecessary [code-complexity](../index.md#complexity--boot-time)
## MCU image format
rustBoot mcu-images comprise of a `256-byte header` pre-pended to a firmware binary and are deliberately designed to be as simple as possible.

- it does not rely on the use of complex digital certificate formats which keeps the [`TCB`](../index.md#trusted-computing-base) small and avoids unnecessary [code-complexity](../index.md#complexity--boot-time)

[![rustBoot_header](https://github.com/imrank03/rustBoot-book-diagrams/blob/main/rustBoot_header.svg?raw=true "Simplified Block Diagram, 256 byte rustBoot header")](https://github.com/imrank03/rustBoot-book-diagrams/blob/main/rustBoot_header.svg?raw=true)

### rustBoot Image header layout:
### rustBoot image header layout:

The header always starts with a 4-byte magic number, followed by a 4-byte field indicating the size of the firmware image (excluding the header). All header contents are stored in little-endian format.

Expand All @@ -16,7 +21,7 @@ The 2 (`magic and size`) fixed fields are followed by one or more `TLV(s) or Typ
- **Length:** 2 bytes to indicate the `length in bytes` of the tag (excluding the type and size bytes).
- **Value:** N bytes of tag content

### Padding and End of Header bytes:
### Padding and End of header bytes:

- An `0xFF` byte in the `Type` field indicates a padding byte. A 'padding' byte does *NOT* have a size field, and the next byte is interpreted as `Type` again.
- A 2 byte value of `0x0000` signals the end of the rustBoot header.
Expand All @@ -42,16 +47,249 @@ Each tag represents some information about the firmware. `rustBoot` requires the
- Length: 64 bytes


### Optional Tags:
### Optional tags:

- **Pubkey Hint:** A `pubkey hint digest` tag can be included in the header.
- Type: `0x1000`
- Length: 32 bytes
- This tag contains the SHA256 digest of the public key of the corresponding private-key used by the signing tool. The bootloader may use this field to locate the correct public key in case multiple keys are available.

> **rustBoot Defaults:**
> **MCU defaults:**
> - By default, a valid rustBoot image is always signed.
> - It relies on the 256-byte header for firmware validation.
> - It will fail to boot an image
- if it does not possess a [`valid rustBoot header`](images.md#rustboot-images) or
- if it isn't signed or if it cannot be verified using the included the [`authentication-type`](images.md#tags).
> - if it does not possess a [`valid rustBoot header`](images.md#rustboot-images) or
> - if it isn't signed or if it cannot be verified using the included the [`authentication-type`](images.md#tags).
## FIT-image format
rustBoot leverages Uboot's [`flattened-uImage-tree`](https://raw.githubusercontent.com/u-boot/u-boot/master/doc/uImage.FIT/howto.txt) format when booting the linux kernel.

The FIT format is essentially an extension of the [`device-tree`](https://github.com/devicetree-org/devicetree-specification/releases/tag/v0.4-rc1) format. FIT allows us to combine multiple binaries such as the kernel, ramdisk, device-tree-blob etc. into a single image.

A typical rustBoot fit-image contains 4 items in the following order
-   kernel
-   fdt
-   initrd
-   rbconfig

**Here's an example fit-image source file :** It is also referred to as an image-tree source file or `.its` file.

```json
/dts-v1/;

/ {
description = "rustBoot FIT Image";
#address-cells = <1>;

images {
kernel {
description = "Kernel";
data = /incbin/("vmlinuz");
type = "kernel";
arch = "arm64";
os = "linux";
compression = "none";
load = <0x40480000>;
entry = <0x40480000>;
hash {
algo = "sha256";
};
};
fdt {
description = "DTB";
data = /incbin/("unpatched-bcm2711-rpi-4-b.dtb");
type = "flat_dt";
arch = "arm64";
compression = "none";
load = <0x43000000>;
entry = <0x43000000>;
hash {
algo = "sha256";
};
};
initrd {
description = "Initrd";
data = /incbin/("initramfs");
type = "ramdisk";
arch = "arm64";
os = "linux";
compression = "none";
hash {
algo = "sha256";
};
};
rbconfig {
description = "rustBoot Config";
data = /incbin/("rbconfig.txt");
type = "rustBoot cmdline config";
arch = "none";
os = "linux";
compression = "none";
hash {
algo = "sha256";
};
};
};

configurations {
default = "bootconfig";
bootconfig {
description = "Boot Config";
kernel = "kernel";
fdt = "fdt";
ramdisk = "initrd";
rbconfig = "rbconfig";
signature@1 {
algo = "sha256,ecdsa256,nistp256";
key-name-hint = "dev";
signed-images = "fdt", "kernel", "ramdisk", "rbconfig";
value = "";
};
};
};

};
```
The default configuration of an `.its` file determines which kernel, initrd, fdt and rbconfig is to be used for booting. In the above example, `bootconfig` is our default configuration.

> rustBoot's FIT parser will select the corresponding kernel, fdt, initrd and rbconfig associated with `bootconfig` for booting
### Building a rustBoot compliant fit-image:
As shown in the example above, a rustBoot compliant fit-image contains 4 items -

- `kernel` - the linux kernel
- `fdt` - the flattened device tree or device tree blob
- `ramdisk`- a root filesystem that is embedded into the kernel and loaded at an early stage of the boot process. It is the successor of initrd. It can do things the kernel can't easily do by itself during the boot process. For example: customize the boot process (e.g., print a welcome message)
- `rbconfig` - this is rustBoot's kernel configuraton. A simple `txt` file to add kernel command-line arguments.

You can retrieve the first 3 (i.e. kernel, fdt, ramdisk) from a pre-built OS image:
- Maintainers of a particular linux distribution provide pre-built OS images. These images usually contain several partitions such as -
- `boot:` contains the bootloader, kernel, dtb, ramdisk and other stuff
- `system:` contains the root file system
- `others:` may contain other partitions for things such as storage etc.
- simply download an OS image or a pre-built linux distribution from the maintainers website.
- In this example, I'll be using the `apertis` distribution.
- it’s usually a compressed (zImage) format. Decompress it using a tool like unarchiver to get a disk image.
- use `partx --show` to list all partitions
```powershell
$ partx --show __linux_image_filepath__
NR START END SECTORS SIZE NAME UUID
1 8192 532479 524288 256M 9730496b-01
2 532480 3661823 3129344 1.5G 9730496b-02
```
In the above case, the first partition with a size of 256MB contains our boot-files. It's usually named `boot`. We can calculate the offset to `boot` volume/partition with the following command
```powershell
$ bc
bc 1.07.1
Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006, 2008, 2012-2017 Free Software Foundation, Inc.
This is free software with ABSOLUTELY NO WARRANTY.
For details type `warranty'.
> 8192 * 512
> 4194304
> quit
```
> `512` is the sector-size. We multiply sector-size with the sector offset to get the actual starting (byte) location of `boot`.
mount the partition as an `ext4` file-system (or `fat` file-system, whichever)
```
$ sudo mkdir /mnt/other
$ sudo mount -v -o offset=272629760 -t ext4 /_path_to_file_image/__filename__.img /mnt/other
mount: /dev/loop0 mounted on /mnt/other.
Check mounted image
$ ls /mnt/other
```
Finally copy the `dtb`, `ramdisk` and `vmlinuz` image (i.e. kernel) from the mounted partition to a new folder. You can give it any name you want. I'll use `pkg` in this example.
> vmlinuz is a PE (portable executable) i.e. we can jump to it and it will in-turn jump to the kernel's entry point.
After storing all 4 items in the `pkg` folder, you can build a fit-image by running the following commands.
- the `.its` file is the input to `mkimage`
- and the `.itb` filename is the generated fit-image, stored in the same `pkg` folder.

**On a mac:**
```powershell
brew install u-boot-tools
```
**On a linux machine:**
```powershell
sudo apt install u-boot-tools
```
and then run

```powershell
mkimage -f rpi4-apertis.its rpi4-test-apertis.itb
Output:
rpi4-apertis.its:65.37-70.6: Warning (unit_address_vs_reg): /configurations/bootconfig/signature@1: node has a unit name, but no reg or ranges property
Image contains unit addresses @, this will break signing
FIT description: rustBoot FIT Image
Created: Sat Jun 4 13:18:45 2022
Image 0 (kernel)
Description: Kernel
Created: Sat Jun 4 13:18:45 2022
Type: Kernel Image
Compression: uncompressed
Data Size: 29272576 Bytes = 28586.50 KiB = 27.92 MiB
Architecture: AArch64
OS: Linux
Load Address: 0x40480000
Entry Point: 0x40480000
Hash algo: sha256
Hash value: 97dcbff24ad0a60514e31a7a6b34a765681fea81f8dd11e4644f3ec81e1044fb
Image 1 (fdt)
Description: DTB
Created: Sat Jun 4 13:18:45 2022
Type: Flat Device Tree
Compression: uncompressed
Data Size: 25713 Bytes = 25.11 KiB = 0.02 MiB
Architecture: AArch64
Load Address: 0x43000000
Hash algo: sha256
Hash value: 3572783be74511b710ed7fca9b3131e97fd8073c620a94269a4e4ce79d331540
Image 2 (initrd)
Description: Initrd
Created: Sat Jun 4 13:18:45 2022
Type: RAMDisk Image
Compression: uncompressed
Data Size: 32901194 Bytes = 32130.07 KiB = 31.38 MiB
Architecture: AArch64
OS: Linux
Load Address: unavailable
Entry Point: unavailable
Hash algo: sha256
Hash value: f1290587e2155e3a5c2c870fa1d6e3e2252fb0dddf74992113d2ed86bc67f37c
Image 3 (rbconfig)
Description: rustBoot Config
Created: Sat Jun 4 13:18:45 2022
Type: Unknown Image
Compression: uncompressed
Data Size: 141 Bytes = 0.14 KiB = 0.00 MiB
Hash algo: sha256
Hash value: b16d058c4f09abdb8da98561f3a15d06ff271c38a4655c2be11dec23567fd519
Default Configuration: 'bootconfig'
Configuration 0 (bootconfig)
Description: Boot Config
Kernel: kernel
Init Ramdisk: initrd
FDT: fdt
Sign algo: sha256,ecdsa256,nistp256:dev
Sign value: 00
Timestamp: unavailable
```
The `.itb` file is our fit-image. It does not contain a signature yet i.e. it is not signed. You'll notice `sign-value` field is empty.
### Signing fit-images
rustBoot fit-images are signed with `ecdsa256`. The signature includes the kernel, fdt, initrd and rbconfig.

Signing a rustBoot fit-image involves 2 steps:
- **Building a fit-image:** As explained in [preceding section](./images.md#building-a-rustboot-compliant-fit-image), FIT images can be built using `mkimage` - a command-line utility from the `uboot-tools` package i.e. you can pass an `.its` file to the mkimage tool and mkimage will produce an `.itb` blob or a image-tree blob.
- **signing the fit-image:** once you've built your fit-image, you can pass the it along with a signing key to rustBoot's `rbsigner` utility to [generate a signed fit-image](./signing_utilities.md#signed-fit-image).

> **FIT-image defaults:**
> - By default, valid rustBoot images are always signed.
> - It will fail to boot an image
> - if the image fails fit-validation i.e. if its not a properly formatted fit-image or if the fit-parser cant find the specified default config or its components.
> - if it isn't signed or if it cannot be verified using the included algo.
> - rustBoot's fit parser currently supports the following architectures
> - `Aarch64`
21 changes: 15 additions & 6 deletions src/arch/partitions.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# rustBoot Partitions

`rustBoot` offers 2 `update-types`, depending on the type of the underlying system.
- **micro-controller updates:** uses the concept of [`swappable flash partitions`](https://github.com/nihalpasham/rustBoot/issues/2) to update micro-controller firmware.
`rustBoot` has 2 distinct partioning schemes, depending on the type of the underlying system.
- [**micro-controller partitions:**](./partitions.md#micro-controller-partitions) uses the concept of [`swappable flash partitions`](https://github.com/nihalpasham/rustBoot/issues/2) to update micro-controller firmware.
> This usually means bare-metal firmware but it is also applicable to `RTOS(s)`.
- **linux system updates:** uses the traditional `ram based swap` method to update linux distributions running atop micro-processors.
## Micro-controller Updates:
- [**linux system partitions:**](./partitions.md#linux-system-partitions) uses a single fat32 partition to host the `rustBoot-bootloader` and (boot/update) fit-images. This method uses a `rustBoot-state` file to determine which image is to be booted.
## Micro-controller Partitions:

[![partition](https://github.com/imrank03/rustBoot-book-diagrams/blob/main/partition.svg?raw=true "Simplified Block Diagram, 256 byte rustBoot header")](https://github.com/imrank03/rustBoot-book-diagrams/blob/main/partition.svg?raw=true)

Expand Down Expand Up @@ -34,9 +34,18 @@ MCU flash memory is partitioned as follows:

BOOT, UPDATE, SWAP addresses and SECTOR_SIZE, PARTITION_SIZE values can be set via command line options or developers `constants.rs`.

> **rustBoot Defaults:**
> **MCU defaults:**
> - By default, public keys used for firmware validation are embedded in `rustBoot-firmware` during a factory-image-burn. However, rustBoot also offers the option to retrieve them from secure-hardware (ex: crypto-elements).
> - The `BOOT` partiton is the only partition from which we can boot a firmware image. The firmware image must be linked so that its entry-point is at address `256 + BOOT_PARTITION_ADDRESS`.
> - `BOOT` firmware is responsible for downloading a new firmware image via a secure channel and installing it in the `UPDATE` partition. To trigger an update, the `BOOT` firmware updates the `status byte` of the `UPDATE` partition and performs a reboot. This will allow the bootloader to `swap the contents` of `BOOT` partition with that of the `UPDATE` partition.
## Linux System Updates:
## Linux System Updates:

To boot into linux systems, rustBoot includes support for the fat32 file-system.

Boot-storage media must contain a fat32 partition
- of at least 150 MiB to accomodate the bootloader, boot + update fit-images and other vendor-specific boot files.
- in order to integrate rustBoot, you can either implement the `BlockDevice` trait for your board's boot-storage media `controller` or simply use an existing implementation included in the repo.

> **note:** rustBoot comes with batteries-included. It provides `rusty` implementations for basic peripherals such as flash, uart, crypto, gpio (out of the box) along with the necessary arch-specific initialization routines.
> - for example: the rustBoot implementation for `rpi4` includes bare-metal drivers for the on-board emmc controller, gpio and uart peripherals.
31 changes: 31 additions & 0 deletions src/arch/signing_utilities.md
Original file line number Diff line number Diff line change
@@ -1 +1,32 @@
# Signing Utilities

As rustBoot supports 2 types of firmware image formats, depending on the underlying device i.e. either an
- [`mcu-image:`](./images.md#mcu-image-format) a simple 256-byte firmware image format for microcontrollers or a
- [`fit-image:`](./images.md#fit-image-format) the flattened-image-tree format for systems capable of booting linux.

rustBoot `rbsigner` utility can produce 2 different types signed images.
### Signed mcu-image:
[TODO ..]
### Signed fit-image:
To sign a fit-image, rustBoot's [image signing utility](https://github.com/nihalpasham/rustBoot/tree/main/rbsigner) takes 3 inputs
- an unsigned fit-image in the above format
- a raw signing-key or ecdsa private key
- the ecdsa curve-type - (nistp256 only for now).

Here's an example for how to sign a fit-image:
- `../boards/bootloaders/rpi4/apertis/rpi4-test-apertis.itb` is the path to my fit-image
- `../boards/rbSigner/keygen/ecc256.der` is the path to my `test` signing-key
- `nistp256` is the type ecdsa curve I'd like to use. Its the only one supported for now.

Simply run the following command from root directory of the rustBoot project.
```
cargo run ../boards/bootloaders/rpi4/apertis/rpi4-test-apertis.itb ../boards/rbSigner/keygen/ecc256.der nistp256
Output:
Finished dev [unoptimized + debuginfo] target(s) in 0.04s
Running `/Users/nihal.pasham/devspace/rust/projects/prod/rustBoot/target/debug/rbsigner ../boards/bootloaders/rpi4/apertis/rpi4-test-apertis.itb ../boards/rbSigner/keygen/ecc256.der nistp256`
signature: ecdsa::Signature<NistP256>([64, 147, 93, 99, 241, 5, 118, 167, 156, 150, 203, 234, 74, 207, 182, 243, 129, 143, 38, 2, 107, 85, 114, 145, 178, 163, 33, 153, 2, 100, 0, 114, 135, 18, 174, 183, 194, 110, 24, 186, 33, 36, 39, 105, 116, 74, 8, 118, 171, 237, 30, 108, 64, 205, 206, 14, 110, 226, 43, 143, 180, 193, 19, 33])
bytes_written: 62202019
```
In this case, the `signed fit-image` will be stored at the following path - `../boards/bootloaders/rpi4/apertis/signed-rpi4-apertis.itb`
2 changes: 1 addition & 1 deletion src/architecture.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# High Level Overview

rustBoot's architecture reflects its focus on `simplicity and security above all else`.
rustBoot's architecture reflects its focus on `simplicity and security`, above everything else.

For a high-level overview, you can think of rustBoot as operating in 2 independent stages.

Expand Down
Empty file added src/objectives.md.new
Empty file.
Loading

0 comments on commit 86d4d01

Please sign in to comment.