Flattened Image Tree Specification v0.8-30-g795fd5f

2. Acknowledgements

FIT (Flattened Image Tree) was developed in 2008 by Marian Balakowicz and Bartlomiej Sieka of Semihalf, under the guidance of Wolfgang Denk, founder of Denx Software Engineering and creator of U-Boot.

Since then, FIT has been maintained and extended by the U-Boot community to deal with the developing needs of Open Source firmware.

This specification builds on this previous work.

FIT has stood the test of time due to its simplicity and extensibility. This specification aims to build on this work and provide a means for further improvement with a wider group of collaborators.

3. Revision History

Table 3.1 Revision History

Revision

Date

Description

0.8

2023-AUG-9

Initial prerelease version. Imported text from U-Boot source tree.

4. Introduction

4.1. Purpose and Scope

The number of elements playing a role in the kernel booting process has increased over time and now typically includes the devicetree, kernel image and possibly a ramdisk image. Generally, all must be placed in the system memory and booted together.

For firmware images a similar process has taken place, with various binaries loaded at different addresses, such as ARM’s ATF, OpenSBI, FPGA and U-Boot itself.

FIT provides a flexible and extensible format to deal with this complexity. It provides support for multiple components. It also supports multiple configurations, so that the same FIT can be used to boot multiple boards, with some components in common (e.g. kernel) and some specific to that board (e.g. devicetree).

This specification, the Flattened Image Tree Specification (FITSpec), provides a suitable format which can be used to describe any set of files along with grouping and selection mechanisms.

  • Chapter 4 introduces the purpose and background of FITSpec.

  • Chapter 5 introduces the FIT concept and describes its logical structure and standard properties. certain classes of devices and specific device types.

  • Chapter 6 describes how FIT is used in bootloaders to handle booting Operating Systems as well as firmware.

Conventions Used in this Document

The word shall is used to indicate mandatory requirements strictly to be followed in order to conform to the standard and from which no deviation is permitted (shall equals is required to).

The word should is used to indicate that among several possibilities one is recommended as particularly suitable, without mentioning or excluding others; or that a certain course of action is preferred but not necessarily required; or that (in the negative form) a certain course of action is deprecated but not prohibited (should equals is recommended that).

The word may is used to indicate a course of action permissible within the limits of the standard (may equals is permitted).

Examples of devicetree constructs are frequently shown in Devicetree Syntax form. See [dtspec] for a description of this.

4.2. Relationship to Devicetree Specification

FITSpec is based on the Devicetree Specification, in that it uses the same structure and shares some concepts.

4.3. 32-bit and 64-bit Support

The FITSpec supports CPUs with both 32-bit and 64-bit addressing capabilities. Where applicable, sections of the FITSpec describe any requirements or considerations for 32-bit and 64-bit addressing.

4.4. Definition of Terms

DTB

Devicetree blob. Compact binary representation of the devicetree.

DTC

Devicetree compiler. An open source tool used to create DTB files from DTS files.

DTS

Devicetree syntax. A textual representation of a devicetree consumed by the DTC. See Appendix A Devicetree Source Format (version 1).

5. Flattened Image Tree (FIT) Format

5.1. Introduction

FIT consists of a devicetree blob with nodes and properties following a certain schema. Therefore this document defines FIT by providing FDT (Flat Device Tree) bindings. These describe the final form of the FIT at the moment when it is used. The user perspective may be simpler, as some of the properties (like timestamps and hashes) are filled in automatically by available tooling, such as mkimage.

To avoid confusion with the kernel FDT the following naming convention is used:

FIT

Flattened Image Tree

FIT is formally a flattened devicetree (in the libfdt meaning), which conforms to bindings defined in this document.

.its

image tree source

.fit

flattened image tree blob

This was previously known as .itb but has been renamed to .fit.

5.1.1. Image-building procedure

The following picture shows how the FIT is prepared. Input consists of image source file (.its) and a set of data files. Image is created with the help of standard U-Boot mkimage tool which in turn uses dtc (device tree compiler) to produce image tree blob (.fit). The resulting .fit file is the actual binary of a new FIT:

tqm5200.its
+
vmlinux.bin.gz     mkimage + dtc               xfer to target
eldk-4.2-ramdisk  --------------> tqm5200.fit --------------> boot
tqm5200.dtb                          /|\
                                      |
                                 'new FIT'

Steps:

  1. Create .its file, automatically filled-in properties are omitted

  2. Call mkimage tool on .its file

  3. mkimage calls dtc to create .fit image and assures that missing properties are added

  4. .fit (new FIT) is uploaded onto the target and used therein

5.1.2. Unique identifiers

To identify FIT sub-nodes representing images, hashes, configurations (which are defined in the following sections), the “unit name” of the given sub-node is used as its identifier as it assures uniqueness without additional checking required.

5.1.3. External data

FIT is normally built initially with image data in the ‘data’ property of each image node. It is also possible for this data to reside outside the FIT itself. This allows the ‘FDT’ part of the FIT to be quite small, so that it can be loaded and scanned without loading a large amount of data. Then when an image is needed it can be loaded from an external source.

External FITs use ‘data-offset’ or ‘data-position’ instead of ‘data’.

The mkimage tool can convert a FIT to use external data using the -E argument, optionally using -p to specific a fixed position.

It is often desirable to align each image to a block size or cache-line size (e.g. 512 bytes), so that there is no need to copy it to an aligned address when reading the image data. The mkimage tool provides a -B argument to support this.

5.2. Root-node properties

The root node of the FIT should have the following layout:

/ o image-tree
    |- description = "image description"
    |- timestamp = <12399321>
    |- #address-cells = <1>
    |
    o images
    | |
    | o image-1 {...}
    | o image-2 {...}
    | ...
    |
    o configurations
      |- default = "conf-1"
      |
      o conf-1 {...}
      o conf-2 {...}
      ...

5.2.1. Optional property

description

Textual description of the FIT

5.2.2. Mandatory property

timestamp

Last image modification time being counted in seconds since 1970-01-01 00:00:00 - to be automatically calculated by mkimage tool.

5.2.3. Conditionally mandatory property

#address-cells

Number of 32bit cells required to represent entry and load addresses supplied within sub-image nodes. May be omitted when no entry or load addresses are used.

5.2.4. Mandatory nodes

images

This node contains a set of sub-nodes, each of them representing single component sub-image (like kernel, ramdisk, etc.). At least one sub-image is required.

configurations

Contains a set of available configuration nodes and defines a default configuration.

5.3. ‘/images’ node

This node is a container node for component sub-image nodes. Each sub-node of the ‘/images’ node should have the following layout:

o image-1
    |- description = "component sub-image description"
    |- data = /incbin/("path/to/data/file.bin")
    |- type = "sub-image type name"
    |- arch = "ARCH name"
    |- os = "OS name"
    |- compression = "compression name"
    |- load = <00000000>
    |- entry = <00000000>
    |
    o hash-1 {...}
    o hash-2 {...}
    o dm-verity {...}
    ...

5.3.1. Mandatory properties

description

Textual description of the component sub-image

type

Name of component sub-image type. Supported types are:

Sub-image type

Meaning

invalid

Invalid Image

aisimage

Davinci AIS image

atmelimage

ATMEL ROM-Boot Image

copro

Coprocessor Image

fdt_legacy

legacy Image with Flat Device Tree

filesystem

Filesystem Image

firmware

Firmware

firmware_ivt

Firmware with HABv4 IVT

flat_dt

Flat Device Tree

fpga

FPGA Device Image (bitstream file, vendor specific)

gpimage

TI Keystone SPL Image

imx8image

NXP i.MX8 Boot Image

imx8mimage

NXP i.MX8M Boot Image

imximage

Freescale i.MX Boot Image

kernel

Kernel Image

kernel_noload

Kernel Image (no loading done)

kwbimage

Kirkwood Boot Image

lpc32xximage

LPC32XX Boot Image

mtk_image

MediaTek BootROM loadable Image

multi

Multi-File Image

mxsimage

Freescale MXS Boot Image

omapimage

TI OMAP SPL With GP CH

pblimage

Freescale PBL Boot Image

pmmc

TI Power Management Micro-Controller Firmware

ramdisk

RAMDisk Image

rkimage

Rockchip Boot Image

rksd

Rockchip SD Boot Image

rkspi

Rockchip SPI Boot Image

script

Script

socfpgaimage

Altera SoCFPGA CV/AV preloader

socfpgaimage_v1

Altera SoCFPGA A10 preloader

spkgimage

Renesas SPKG Image

standalone

Standalone Program

stm32image

STMicroelectronics STM32 Image

sunxi_egon

Allwinner eGON Boot Image

sunxi_toc0

Allwinner TOC0 Boot Image

tee

Trusted Execution Environment Image

tfa-bl31

Trusted Firmware-A BL31 Image

ublimage

Davinci UBL image

vybridimage

Vybrid Boot Image

x86_setup

x86 setup.bin

zynqimage

Xilinx Zynq Boot Image

zynqmpbif

Xilinx ZynqMP Boot Image (bif)

zynqmpimage

Xilinx ZynqMP Boot Image

compression

Compression used by included data. If no compression is used, the compression property should be set to “none”. If the data is compressed but it should not be uncompressed by the loader (e.g. compressed ramdisk <pair: ramdisk; compressed), this should also be set to “none”.

Supported compression types are:

Compression type

Meaning

none

uncompressed

bzip2

bzip2 compressed

gzip

gzip compressed

lz4

lz4 compressed

lzma

lzma compressed

lzo

lzo compressed

zstd

zstd compressed

5.3.2. Conditionally mandatory properties

data

Path to the external file which contains this node’s binary data. Within the FIT this is the contents of the file. This is mandatory unless external data is used.

data-size

Size of the data in bytes. This is mandatory if external data is used.

data-offset

Offset of the data in a separate image store. The image store is placed immediately after the last byte of the device tree binary, aligned to a 4-byte boundary. This is mandatory if external data is used, with an offset.

data-position

Machine address at which the data is to be found. This is a fixed address not relative to the loading of the FIT. This is mandatory if external data is used with a fixed address.

os

OS name, mandatory for types “kernel”. Valid OS names are:

OS name

Meaning

invalid

Invalid OS

4_4bsd

4_4BSD

arm-trusted-firmware

ARM Trusted Firmware

dell

Dell

efi

EFI Firmware

esix

Esix

freebsd

FreeBSD

integrity

INTEGRITY

irix

Irix

linux

Linux

ncr

NCR

netbsd

NetBSD

openbsd

OpenBSD

openrtos

OpenRTOS

opensbi

RISC-V OpenSBI

ose

Enea OSE

plan9

Plan 9

psos

pSOS

qnx

QNX

rtems

RTEMS

sco

SCO

solaris

Solaris

svr4

SVR4

tee

Trusted Execution Environment

u-boot

U-Boot

vxworks

VxWorks

arch

Architecture name, mandatory for types: “standalone”, “kernel”, “firmware”, “ramdisk” and “fdt”. Valid architecture names are:

Architecture type

Meaning

invalid

Invalid ARCH

alpha

Alpha

arc

ARC

arm64

AArch64

arm

ARM

avr32

AVR32

blackfin

Blackfin

ia64

IA64

m68k

M68K

microblaze

MicroBlaze

mips64

MIPS 64 Bit

mips

MIPS

nds32

NDS32

nios2

NIOS II

or1k

OpenRISC 1000

powerpc

PowerPC

ppc

PowerPC

riscv

RISC-V

s390

IBM S390

sandbox

Sandbox

sh

SuperH

sparc64

SPARC 64 Bit

sparc

SPARC

x86_64

AMD x86_64

x86

Intel x86

xtensa

Xtensa

entry

Entry point address, address size is determined by ‘#address-cells’ property of the root node. Mandatory for types: “firmware”, and “kernel”.

load

Load address, address size is determined by ‘#address-cells’ property of the root node. Mandatory for types: “firmware”, and “kernel”.

compatible

Compatible method for loading image. Mandatory for types: “fpga”, and images that do not specify a load address. Supported compatible methods:

Compatible string

Meaning

u-boot,fpga-legacy

Generic fpga loading routine.

u-boot,zynqmp-fpga-ddrauth

Signed non-encrypted FPGA bitstream for Xilinx Zynq UltraScale+ (ZymqMP) device.

u-boot,zynqmp-fpga-enc

Encrypted FPGA bitstream for Xilinx Zynq UltraScale+ (ZynqMP) device.

Note

For fdt images, the node should not have a compatible for the model. The compatible here is not derived from the fdt, nor is it used to identify the fdt. Such usage belongs in the configuration node.

phase

U-Boot phase for which the image is intended.

“spl”

image is an SPL image

“u-boot”

image is a U-Boot image

5.3.3. Optional nodes

hash-1

Each hash sub-node represents a separate hash or checksum calculated for node’s data according to specified algorithm.

signature-1

Each signature sub-node represents a separate signature calculated for node’s data according to specified algorithm.

dm-verity

For images of type filesystem, this sub-node carries dm-verity Merkle-tree metadata so that the bootloader can construct kernel command-line parameters for integrity-verified boot. See dm-verity nodes.

5.4. Hash nodes

o hash-1
    |- algo = "hash or checksum algorithm name"
    |- value = [hash or checksum value]

5.4.1. Mandatory properties

algo

Algorithm name. Supported algorithms and their value sizes are:

Sub-image type

Size (bytes)

Meaning

crc16-ccitt

2

Cyclic Redundancy Check 16-bit (Consultative Committee for International Telegraphy and Telephony)

crc32

4

Cyclic Redundancy Check 32-bit

md5

16

Message Digest 5 (MD5)

sha1

20

Secure Hash Algorithm 1 (SHA1)

sha256

32

Secure Hash Algorithm 2 (SHA256)

sha384

48

Secure Hash Algorithm 2 (SHA384)

sha512

64

Secure Hash Algorithm 2 (SHA512)

value

Actual checksum or hash value.

5.5. Image-signature nodes

o signature-1
    |- algo = "algorithm name"
    |- key-name-hint = "key name"
    |- value = [hash or checksum value]

5.5.1. Mandatory properties

FIT Algorithm:

algo

Algorithm name. Supported algorithms and their value sizes are shown below. Note that the hash is specified separately from the signing algorithm, so it is possible to mix and match any SHA algorithm with any signing algorithm. The size of the signature relates to the signing algorithm, not the hash, since it is the hash that is signed.

Sub-image type

Size (bytes)

Meaning

sha1,rsa2048

256

SHA1 hash signed with 2048-bit Rivest–Shamir–Adleman algorithm

sha1,rsa3072

384

SHA1 hash signed with 2048-bit RSA

sha1,rsa4096

512

SHA1 hash signed with 2048-bit RSA

sha1,ecdsa256

32

SHA1 hash signed with 256-bit Elliptic Curve Digital Signature Algorithm

sha256,…

sha384,…

sha512,…

key-name-hint

Name of key to use for signing. The keys will normally be in a single directory (parameter -k to mkimage). For a given key <name>, its private key is stored in <name>.key and the certificate is stored in <name>.crt.

sign-images

An unsorted list of images to sign, each being a property of the conf node that contains them. The default is “kernel,fdt” which means that these two images will be looked up in the config and signed if present. This is used by mkimage to determine which images to sign.

The following properties are added as part of signing, and are mandatory:

value

Actual signature value. This is added by mkimage.

hashed-nodes

An unsorted list of nodes which were hashed by the signer. Each is a string - the full path to node. Since this property is not itself protected by a hash, it serves only as a hint for the signer and must not be relied upon by the loader for validation purposes. A typical value might be:

hashed-nodes = "/", "/configurations/conf-1", "/images/kernel",
    "/images/kernel/hash-1", "/images/fdt-1",
    "/images/fdt-1/hash-1";
hashed-strings

The start and size of the string region of the FIT that was hashed. The start is normally 0, indicating the first byte of the string table. The size indicates the number of bytes hashed as part of signing.

The following properties are added as part of signing, and are optional:

timestamp

Time when image was signed (standard Unix time_t format)

signer-name

Name of the signer (e.g. “mkimage”)

signer-version

Version string of the signer (e.g. “2013.01”)

comment

Additional information about the signer or image

padding

The padding algorithm, it may be pkcs-1.5 or pss, if no value is provided we assume pkcs-1.5

5.6. dm-verity nodes

Image nodes whose type is filesystem may contain an optional dm-verity child node. The bootloader uses this metadata to construct dm-mod.create and dm-mod.waitfor kernel command-line parameters so that the kernel can set up a dm-verity integrity target over the corresponding block device at boot.

See dm-verity for filesystem images for details on how a bootloader should translate these properties into kernel command-line parameters.

o dm-verity
    |- data-block-size = <data block size in bytes>
    |- hash-block-size = <hash block size in bytes>
    |- num-data-blocks = <number of data blocks>
    |- hash-start-block = <hash tree start block>
    |- algo = "hash algorithm name"
    |- digest = [root hash bytes]
    |- salt = [salt bytes]

The property names are intentionally aligned with the dm-verity construction parameters defined by the Linux kernel. The <version> parameter is always 1, and <dev> / <hash_dev> both implicitly refer to the /dev/fitN block device that the Linux uImage.FIT block driver creates for this sub-image.

5.6.1. Mandatory properties

data-block-size

The block size on the data device in bytes. Each block corresponds to one digest in the hash tree. Must be a power of two and at least 512; typically 4096.

hash-block-size

The size of a hash block in bytes. Must be a power of two and at least 512; typically 4096.

num-data-blocks

The number of data blocks on the data device. This corresponds to the <num_data_blocks> dm-verity parameter and the value reported by veritysetup format.

hash-start-block

Offset in hash-block-size-sized blocks from the start of the sub-image to the root block of the hash tree. Corresponds to the <hash_start_block> dm-verity parameter.

algo

The cryptographic hash algorithm used for dm-verity, e.g. "sha256".

digest

The root hash of the dm-verity Merkle tree, stored as a raw byte array. The length must match the output size of algo.

salt

Salt value, stored as a raw byte array.

5.6.2. Optional properties

restart-on-corruption

Boolean. Restart the system when a corrupted block is discovered. Corresponds to restart_on_corruption.

panic-on-corruption

Boolean. Panic the system when a corrupted block is discovered. Not compatible with restart-on-corruption. Corresponds to panic_on_corruption.

restart-on-error

Boolean. Restart the system when an I/O error is detected. Corresponds to restart_on_error.

panic-on-error

Boolean. Panic the system when an I/O error is detected. Not compatible with restart-on-error. Corresponds to panic_on_error.

check-at-most-once

Boolean. Verify data blocks only the first time they are read, reducing overhead on constrained systems at the cost of not detecting online tampering. Corresponds to check_at_most_once.

When none of the error-handling properties are present, the kernel default behaviour (return I/O error) applies.

Additional optional parameters defined by the kernel’s dm-verity target may be added as properties in future revisions of this specification.

These values correspond to the parameters passed to dm-mod.create on the kernel command line (see dm-verity for filesystem images).

Both the filesystem payload data and the dm-verity Merkle-tree hash data are expected to reside inside the same sub-image.

5.7. ‘/configurations’ node

The ‘configurations’ node creates convenient, labeled boot configurations, which combine together kernel images with their ramdisks and fdt blobs.

The ‘configurations’ node has the following structure:

o configurations
    |- default = "default configuration sub-node unit name"
    |
    o config-1 {...}
    o config-2 {...}
    ...

5.7.1. Optional property

default

Selects one of the configuration sub-nodes as a default configuration.

5.7.2. Mandatory nodes

configuration-sub-node-unit-name

At least one configuration sub-node is required.

5.7.3. Optional nodes

signature-1

Each signature sub-node represents a separate signature calculated for the configuration according to specified algorithm.

5.8. Configuration nodes

Each configuration has the following structure:

o config-1
    |- description = "configuration description";
    |- kernel = "kernel sub-node unit name";
    |- cmdline = "command line for next boot stage";
    |- fdt = "fdt sub-node unit-name" [, "fdt overlay sub-node unit-name", ...];
    |- ramdisk = "ramdisk sub-node unit-name";
    |- loadables = "loadables sub-node unit-name" [, ...];
    |- script = "script sub-node unit-name";
    |- compatible = "vendor,board-style device tree compatible string";
    o signature-1 {...}

5.8.1. Mandatory properties

description

Textual configuration description.

5.8.2. Conditionally mandatory property

kernel or firmware

Unit name of the corresponding kernel or firmware (u-boot, op-tee, etc) image. If both “kernel” and “firmware” are specified, control is passed to the firmware image. For load_only images, these two properties are optional.

5.8.3. Optional properties

cmdline

Command line passed to the next boot stage, e.g. the operating system kernel. The value is an UTF-8 encoded string.

fdt

Unit name of the corresponding fdt blob (component image node of a “fdt type”). Additional fdt overlay nodes can be supplied which signify that the resulting device tree blob is generated by the first base fdt blob with all subsequent overlays applied.

fpga

Unit name of the corresponding fpga bitstream blob (component image node of a “fpga type”).

loadables

Unit name containing a list of additional binaries to be loaded at their given locations. “loadables” is a comma-separated list of strings. U-Boot will load each binary at its given load address (see load) and may optionally invoke additional post-processing steps on this binary based on its component image node type.

ramdisk

Unit name of the corresponding ramdisk to be loaded at the given location.

script

The image to use when loading a U-Boot script (for use with the source command).

compatible

The root compatible string of the bootloader device tree that this configuration shall automatically match. If this property is not provided, the compatible string will be extracted from the fdt blob instead. This is only possible if the fdt is not compressed, so images with compressed fdts that want to use compatible string matching must always provide this property.

Note that U-Boot requires the CONFIG_FIT_BEST_MATCH option to be enabled for this matching to work.

load-only

Indicates that this configuration does not necessarily contain an executable image, i.e. kernel or firmware. The configuration’s images may be loaded into memory for use by the executable image, which comes from another configuration or FIT. See see Multi-step loading.

The FDT blob is required to properly boot FDT-based kernel, so the minimal configuration for 2.6 FDT kernel is (kernel, fdt) pair.

Older, 2.4 kernel and 2.6 non-FDT kernel do not use FDT blob, in such cases ‘struct bd_info’ must be passed instead of FDT blob, thus fdt property must not be specified in a configuration node.

5.9. Configuration-signature nodes

o signature-1
    |- algo = "algorithm name"
    |- key-name-hint = "key name"
    |- sign-images = "path1", "path2";
    |- value = [hash or checksum value]
    |- hashed-strings = <0 len>

5.9.1. Mandatory properties

algo

See FIT Algorithm.

key-name-hint

Name of key to use for signing. The keys will normally be in a single directory (parameter -k to mkimage). For a given key <name>, its private key is stored in <name>.key and the certificate is stored in <name>.crt.

The following properties are added as part of signing, and are mandatory:

value

Actual signature value. This is added by mkimage.

The following properties are added as part of signing, and are optional:

timestamp

Time when image was signed (standard Unix time_t format)

signer-name

Name of the signer (e.g. “mkimage”)

signer-version

Version string of the signer (e.g. “2013.01”)

comment

Additional information about the signer or image

padding

The padding algorithm, it may be pkcs-1.5 or pss, if no value is provided we assume pkcs-1.5

6. Flattened Image Tree (FIT) Usage

6.1. Introduction

This section describes how FIT is typically used. This is not necessarily proscriptive but may be useful for those implementing this specification.

6.2. Boot process

At some point in the boot process, the bootloader select and boot an Operating System. To do this, it follows these steps:

  1. Load a FIT into memory

  2. Select a configuration to boot

  3. Load the images from the selected configuration

  4. Fix up the devicetree

  5. Jump to the OS

Each of these is now dealt with in turn.

6.2.1. Load a FIT into memory

The bootloader provides a way to select a FIT to load into memory. This is typically on boot media available to the bootloader, such as eMMC or UFS.

There may be multiple FITs available. The mechanism for locating and selecting a FIT is not defined by this specification. See for example [VBE].

The bootloader may load the entire FIT into memory at once, before processing it. For simple applications where there are just a few images, this is the easiest approach.

Where there are many configuration and several images, such that only a subset of the available images will actually be used on any one boot, it is inefficient to load the entire FIT, since most of the loaded data will not be used. In this case, an external-data FIT can be used. See External data.

In this case, the bootloader reads the FDT header (say 64 bytes), checks that it is valid, then reads enough more bytes to bring in totalsize bytes (totalsize is the second 32-bit word in the header). Typically this will be a few KB of data, consisting just of the FIT metadata. Later, the bootloader can read more data from the FIT as it needs to load each image.

Another case that sometimes comes up is loading images from a FIT into internal SRAM, which may be very limited. In that case it may be useful to align images on a storage-device’s block boundary (see -B flag in External data). The bootloader can then avoid needing bounce buffers and other complications.

6.2.2. Select a configuration to boot

The FIT typically contains more than one configuration. It is common to use a separate configuration for each supported model. The configuration contains a compatible stringlist which indicates which models the configuration is compatible with.

The bootloader itself typically has a compatible stringlist, indicating the model that it is running on. For U-Boot this is in the root node of the devicetree used by U-Boot, typically exactly the same devicetree as is used by Linux for that model. For other bootloaders, the stringlist may be hard-coded, or obtained by some other means.

The bootloader should loop through each configuration to find the best match to its own compatible string. The best match is the configuration which matches earliest string in the bootloader’s compatible stringlist.

For example, imagine the bootloader has compatible = "foo,bar", "bim,bam" and the FIT has two configurations:

config-1 {
    compatible = "foo,bar";
    fdt = "fdt-1";
    ...
};
config-2 {
    compatible = "bim,bam", "baz,biz";
    fdt = "fdt-2";
    ...
};

Here, the bootloader chooses config-1 since it is a better match. The first string in the bootloader’s compatible list, "foo,bar", matches a compatible string in the root of fdt1. Although "bim,bam" in fdt2 matches the second string, this isn’t as good a match as fdt1.

In U-Boot this algorithm is handled by fit_conf_find_compat() and enabled by the CONFIG_FIT_BEST_MATCH option.

Sometime models have multiple PCB revisions or different minor variants, often referred to as SKUs. For this reason, bootloaders may want to select configurations in a finer-grained way. In this case, rather than using the compatible stringlist in its devicetree, if any, it constructs a single string using the base name along with any available suffixes, each beginning with a hyphen. The best match algorithm is then run using that string.

The following compatible-string suffixes may be used to this end. They must be provided in this order (<n> is an integer >= 0):

-rev<n>

Board revision number, typically referring to a revision of the PCB to fix a problem or adjust component selection. The intention is that the board is the same design, just with some minor fixes or improvements. The first revision is typically rev0.

-sku<n>

Board variant, called a SKU (Stock-Keeping Unit) which is a unique code that identifies a model variant. This may encode differences in the display, WiFi and the like, but where the same PCB design (and revision) is used. The base SKU is typically sku0.

Examples:

compatible = "google,kevin-rev15";
compatible = "google,kevin-rev15-sku2";

When matching, the bootloader should build the most specific string it can using any available revision / SKU information, then try to match that. If the most specific string fails (e.g. "google,kevin-rev15-sku2"), it should fall back to just "google,kevin-rev15" and then "google,kevin-sku2". If nothing matches, then it should try without any additions, i.e. "google,kevin".

This multi-stage process uses the same ‘best match’ approach as above. Each attempt finds the best match given the compatible string being searched. Where a stage does not find any match, the next stage begins. As soon as a match is found, searching stops, using the best match found in the stage.

Other suffixes may be added in future.

6.2.3. Load the images from the selected configuration

The configuration contains a number of images. One of these is the OS itself. Another is typically a devicetree blob, which provides information about available devices, useful for the OS as it boots and runs. Another image may be a ramdisk (or initrd) which provides an initial root disk for the OS to use, before it is able to access the real root disk.

The bootloader reads each image from the FIT and ‘loads’ it to the correct address. This address may be provided by the image’s load property (see load), but if not provided, the bootloader can load it to any suitable address. In some cases it may be possible to avoid loading the image and just refer to the image data within the FIT itself.

6.2.4. Fix up the devicetree

Many Operating Systems use devicetree blobs for configuration. As a result, most bootloaders provide a way to update the devicetree in the FIT before passing it to the OS. This may be used to pass command-line parameters to Linux, to select the console device to use, or to pass the ramdisk to the OS. It is also common to enable or disable certain devicetree nodes based on the hardware in use.

The fixups required depend on the OS and its expectations. The result is a devicetree slightly modified from the FIT version.

6.2.5. Jump to the OS

Once everything is ready, the bootloader jumps to the OS. At this point the FIT is no longer in use. The OS typically does not see the FIT itself and only cares about the images that were loaded. At this point, the FIT has served its purpose.

6.3. Firmware usage

As firmware has become more complex, with multiple binaries loaded at each phase of the boot, it has become common to use FIT to load firmware.

In this case, there is the concept of a boot phase (see phase), indicating which phase each image is for.

In this case the bootloader itself is likely split into multiple phases. For U-Boot, a common approach is for SPL (Secondary Program Loader) to load U-Boot proper, along with ATF and any other images required by U-Boot proper.

FIT processing for firmware images is no different from the approach described above, except that any image with a phase property is only loaded if the phase matches the phase being loaded. So, for example, SPL loads U-Boot proper so will only load images with a phase of “u-boot”. If TPL is in use (the phase before SPL), then TPL will only load images with a phase of “spl”. This allows all images to be provided in a single FIT, with each phase pulling out what is needed as the boot proceeds.

6.4. Multi-step loading

The most common use of a FIT is where each configuration contains everything needed to boot. For example, on ARM systems a configuration contains a kernel, devicetree(s) and a ramdisk if needed. This approach is widely used on embedded systems.

This approach is not always desirable, however, particularly when the firmware and the OS are supplied by different parties. In that case, the devicetree may be provided by the firmware with the other pieces coming from the OS. This means that FIT may omit the devicetree images.

With devicetree in particular, it is common for the OS to provide its own version, or perhaps a devicetree overlay to add some new nodes and properties.

Obviously if the OS has to provide a devicetree for every device, the OS files would become very large. A middle path could be that the hardware vendor provides a FIT on a boot partition, containing devicetrees for hardware supported by that vendor. Then the bootloader can load that FIT to get just the devicetree, followed by the main FIT to load the OS.

To enable this last case, add a load-only property to the configuration. This signals to the bootloader that it should not require an executable (i.e. kernel or firmware), nor should it try to boot with this configuration. Booting then becomes a two-step process: load one FIT to obtain the devicetree, then another to obtain the OS. Only the second FIT is booted.

Specifically, the ‘load-only’ property adjusts the meaning of loading a FIT, so that implementors should follow the following behaviour:

‘load-only’ present

Executable present

Behaviour

no

no

Raise an error

yes

no

Only load the images

no

yes

Execute binary

yes

yes

Execute binary

6.5. dm-verity for filesystem images

A FIT may contain filesystem-type sub-images that carry a dm-verity child node (see dm-verity nodes). Such images bundle both the filesystem payload and the dm-verity Merkle-tree hash data inside the same sub-image. A Linux block driver exposes each loadable sub-image as /dev/fit0, /dev/fit1, etc.

When a dm-verity node is present, the bootloader should translate its properties into kernel command-line parameters so that the kernel can activate a dm-verity integrity target at boot, before mounting the root filesystem. Two parameters are needed:

dm-mod.waitfor

Tells dm-init to wait for the block device to appear before creating mapped devices. The bootloader should list the /dev/fitN device that corresponds to the sub-image:

dm-mod.waitfor=/dev/fit0
dm-mod.create

Defines a device-mapper table using the --concise format accepted by dmsetup. The general form for a verity target is:

<name>,<uuid>,<minor>,ro,
  0 <num_sectors> verity <version>
  <dev> <hash_dev>
  <data_block_size> <hash_block_size>
  <num_data_blocks> <hash_start_block>
  <algorithm> <digest> <salt>
  [<#opt_params> <opt_params>]

Because both the filesystem data and the hash tree reside inside the same /dev/fitN device, <dev> and <hash_dev> are identical and shall be set by the bootloader. The <version> is always 1.

6.5.1. Field mapping

The following table shows how each dm-verity construction parameter is derived from the dm-verity node properties.

dm-verity parameter

Source

<name>

The unit name of the /images sub-node that contains the dm-verity child node.

<uuid>

May be left empty ("").

<minor>

May be left empty ("").

<num_sectors>

num-data-blocks * (data-block-size / 512)

<version>

Always 1.

<dev>, <hash_dev>

Both set to the /dev/fitN block device that the uImage.FIT block driver creates for this sub-image.

<data_block_size>

data-block-size from the dm-verity node.

<hash_block_size>

hash-block-size from the dm-verity node.

<num_data_blocks>

num-data-blocks from the dm-verity node.

<hash_start_block>

hash-start-block from the dm-verity node.

<algorithm>

algo from the dm-verity node.

<digest>

digest from the dm-verity node, hex-encoded.

<salt>

salt from the dm-verity node, hex-encoded.

<opt_params>

Constructed from the boolean option properties present in the dm-verity node (e.g. restart-on-corruption, panic-on-error). The bootloader collects every option property that is present, converts the property name from hyphenated to underscored form (e.g. restart-on-corruption becomes restart_on_corruption), counts them, and appends <count> <option> [<option> ...] to the target line.

6.5.2. Example

Given a filesystem sub-image node named rootfs-1, exposed as /dev/fit0, whose dm-verity node contains:

dm-verity {
    data-block-size = <4096>;
    hash-block-size = <4096>;
    num-data-blocks = <204800>;
    hash-start-block = <204800>;
    algo = "sha256";
    digest = [ac 87 db 56 30 3c 9c 1d a4 33 d7 20 9b 5a 6e f3
              e4 77 9d f1 41 20 0c bd 7c 15 7d cb 8d d8 9c 42];
    salt = [5e bf e8 7f 7d f3 23 5b 80 a1 17 eb c4 07 8e 44
            f5 50 45 48 7a d4 a9 65 81 d1 ad b5 64 61 5b 51];
    panic-on-corruption;
    panic-on-error;
};

The bootloader constructs:

dm-mod.waitfor=/dev/fit0

dm-mod.create="rootfs-1,,, ro,
  0 1638400 verity 1
  /dev/fit0 /dev/fit0
  4096 4096 204800 204800 sha256
  ac87db56303c9c1da433d7209b5a6ef3e4779df141200cbd7c157dcb8dd89c42
  5ebfe87f7df3235b80a117ebc4078e44f55045487ad4a96581d1adb564615b51
  2 panic_on_corruption panic_on_error"

Note

The newlines inside the dm-mod.create value above are for readability only. The actual kernel command-line parameter must be a single line.

Here num_sectors = 204800 × (4096 / 512) = 1638400. <name> is the image unit name rootfs-1; <uuid> and <minor> are left empty; ro indicates a read-only target. <dev> and <hash_dev> are both /dev/fit0 because the filesystem data and the Merkle tree reside in the same sub-image. The two boolean properties panic-on-corruption and panic-on-error become the optional-parameter suffix 2 panic_on_corruption panic_on_error (count followed by underscore-separated names). The remaining fields map directly from the dm-verity node properties.

Note

When preparing the sub-image with veritysetup format, pass --no-superblock so that the hash tree starts directly after the data blocks. By default veritysetup writes a one-block on-disk superblock between data and hash tree, which would shift hash-start-block to num-data-blocks + 1. The kernel dm-verity target never reads this superblock — all parameters are supplied via dm-mod.create — so it is unnecessary when the metadata is already stored in the FIT dm-verity node.

7. Security

7.1. Introduction

FIT has robust security features. When enabled, each FIT configuration has one or more signatures. These protect the configuration and the images it refers to. The bootloader must check the signatures against a public key which it has stored elsewhere.

If any configuration fails its signature check, then it must be ignored. Images must each include a suitable hash node so that they are protected against modification. Once each image is loaded, its hash must be computed and checked against the hash in the FIT. The exception is filesystem-type images that carry a dm-verity node: their integrity is delegated to the kernel’s dm-verity target rather than verified by the bootloader.

For more information on FIT security, see U-Boot’s documentation. The mechanism is also widely covered in conference talks, some of which are listed at elinux.org.

7.2. Architecture

FIT security uses a two-level scheme: image hashing and configuration signing.

7.2.1. Image hashing

Each image node contains one or more hash sub-nodes. Each hash sub-node holds the algorithm name (e.g. sha256) and the resulting digest of the image data. The hash covers the image content only, so the loader can verify that the image data has not been modified after the hash was computed.

Hashing alone does not provide authentication, since an attacker who can modify the image data can also replace the hash. Authentication comes from the configuration signature, described next.

7.2.2. Configuration signing

Each configuration node may contain one or more signature sub-nodes. A configuration signature covers:

  • the configuration node itself (including its references to images),

  • each image node referenced by the configuration,

  • the hash sub-nodes of those images,

  • the dm-verity sub-nodes of those images (where present), and

  • the root (/) node of the FIT.

Because the signature covers the hash sub-nodes, the image data is transitively protected: any change to the image data invalidates the hash, and any change to the hash invalidates the configuration signature.

This design means that image data is protected without being directly included in the configuration signature. The data property (and related properties data-size, data-position and data-offset) of image nodes are explicitly excluded from the signed region, since image-data integrity is already guaranteed by the image hash.

This two-level design has an important consequence: the same image can appear in multiple configurations, each with its own signature, without duplicating the image data or requiring it to be signed multiple times.

7.2.3. Configuration signing compared to image signing

Signing each image independently is vulnerable to a mix-and-match attack, where an attacker combines legitimately signed images into a configuration that was never intended. For example, an attacker could pair a signed kernel with a different signed devicetree to change the system’s behaviour, even though both images carry valid signatures.

Configuration signing prevents this, because the signature binds a specific set of images together. A loader that verifies the configuration signature knows that this exact combination of images was approved by the signer.

7.2.4. Verification procedure

The bootloader verifies a configuration as follows:

  1. Locate the configuration’s signature node and verify the signature against a trusted public key.

  2. The signature covers certain FDT nodes and a region of the string table (see Hash contents below). Rebuild the list of nodes that should have been signed (the root node, the configuration node, each referenced image node, its hash sub-nodes, and any dm-verity sub-node) and verify that the hash of those nodes matches the signature.

  3. For each image referenced by the configuration, compute the hash of the image data and compare it against the value in the image’s hash sub-node. This step may be deferred until the image is actually loaded, which can be some time after the configuration is selected.

    For filesystem-type images that carry a dm-verity child node and are being used to launch Linux, the bootloader may omit loading the image into RAM and skip this hash check entirely. Instead, the bootloader shall derive kernel command-line parameters from the dm-verity node as described in dm-verity for filesystem images, delegating integrity verification to the kernel’s dm-verity target at mount time.

If any step fails, the configuration must be rejected.

7.3. Hash contents

This section defines exactly which bytes are included when computing the hash for a signature. A FIT is a flattened devicetree (FDT), so the hash operates on the FDT binary structure as defined in the Devicetree Specification [dtspec].

The input to the hash is the concatenation of two regions: a set of nodes from the FDT structure block, followed by a region of the FDT strings block.

7.3.1. Structure block

The signer and verifier each construct a node list: the set of FDT nodes whose content is included in the hash. For a configuration signature this list contains:

  • the root (/) node,

  • the configuration node (e.g. /configurations/conf-1),

  • each image node referenced by the configuration (e.g. /images/kernel, /images/fdt-1),

  • the hash sub-nodes of those image nodes (e.g. /images/kernel/hash-1, /images/fdt-1/hash-1),

  • any cipher sub-nodes of those image nodes (e.g. /images/kernel/cipher-1), and

  • the dm-verity sub-node of any filesystem-type image node that carries one (e.g. /images/rootfs-1/dm-verity).

The signer walks the FDT structure block sequentially and includes or excludes each token according to the following rules:

FDT_BEGIN_NODE

The token and the node’s unit name are included if the node or its parent is in the node list.

FDT_END_NODE

Included under the same condition as FDT_BEGIN_NODE.

FDT_PROP

The token, the property length word, the string-table offset word, and the property data are all included if the containing node is in the node list and the property name is not one of the excluded properties: data, data-size, data-position and data-offset. These are excluded because image-data integrity is covered by image hashes instead.

FDT_NOP

Included if the containing node is in the node list.

FDT_END

Always included.

Note that the “or its parent” condition in the FDT_BEGIN_NODE and FDT_END_NODE rules means that sub-nodes of listed nodes contribute their structural tokens to the hash, even though they are not themselves in the node list. For example, the signature sub-nodes of the configuration node have their FDT_BEGIN_NODE and FDT_END_NODE tokens included, but their properties are excluded (since FDT_PROP requires the node itself to be in the list).

All included bytes are fed into the hash in the order they appear in the structure block. Padding bytes that are part of the FDT token alignment are included as they appear.

7.3.2. Strings block

The hashed-strings property in the signature node records the start offset and size of the region of the FDT strings block that is hashed. The start is normally 0 (the beginning of the strings block). Only property names that are referenced by the signed nodes need to appear in this region; the signer must ensure that the region is large enough to cover them.

After hashing the structure-block regions, the hash algorithm continues with the strings-block region to produce the final digest.

7.3.3. Image hashing

For image hash nodes (/images/image-name/hash-1), the hash is computed over the image’s data property value only (i.e. the raw image content, not any FDT metadata). The algorithm is given by the hash node’s algo property and the resulting digest is stored in its value property.

7.3.4. Worked example

This section walks through a concrete FIT to show exactly which bytes are included in a configuration signature hash.

7.3.4.1. Source

Consider the following minimal FIT source:

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

    images {
        kernel {
            data = /incbin/("vmlinuz");
            type = "kernel";
            arch = "arm64";
            os = "linux";
            compression = "none";
            load = <0x40000000>;
            entry = <0x40000000>;
            hash-1 {
                algo = "sha256";
            };
        };
        fdt-1 {
            data = /incbin/("board.dtb");
            type = "flat_dt";
            arch = "arm64";
            compression = "none";
            hash-1 {
                algo = "sha256";
            };
        };
    };
    configurations {
        default = "conf-1";
        conf-1 {
            description = "Boot Linux";
            compatible = "vendor,board";
            kernel = "kernel";
            fdt = "fdt-1";
            signature-1 {
                algo = "sha256,rsa2048";
                key-name-hint = "dev";
                sign-images = "kernel", "fdt";
            };
        };
    };
};
7.3.4.2. After signing

During signing, the signer adds a value property to each hash node containing the image digest, and adds value, hashed-nodes, hashed-strings and other properties to the signature node. The resulting FIT looks like this:

/ {
    description = "Example FIT";
    timestamp = <0x67d96bac>;
    #address-cells = <1>;

    images {
        kernel {
            data = <...kernel data...>;
            type = "kernel";
            arch = "arm64";
            os = "linux";
            compression = "none";
            load = <0x40000000>;
            entry = <0x40000000>;
            hash-1 {
                algo = "sha256";
                value = <...32-byte SHA-256 digest of kernel data...>;
            };
        };
        fdt-1 {
            data = <...devicetree data...>;
            type = "flat_dt";
            arch = "arm64";
            compression = "none";
            hash-1 {
                algo = "sha256";
                value = <...32-byte SHA-256 digest of devicetree data...>;
            };
        };
    };
    configurations {
        default = "conf-1";
        conf-1 {
            description = "Boot Linux";
            compatible = "vendor,board";
            kernel = "kernel";
            fdt = "fdt-1";
            signature-1 {
                algo = "sha256,rsa2048";
                key-name-hint = "dev";
                sign-images = "kernel", "fdt";
                value = <...256-byte RSA-2048 signature...>;
                hashed-nodes = "/", "/configurations/conf-1",
                    "/images/kernel", "/images/kernel/hash-1",
                    "/images/fdt-1", "/images/fdt-1/hash-1";
                hashed-strings = <0x00000000 0x000000d4>;
                timestamp = <0x67d96bac>;
                signer-name = "mkimage";
                signer-version = "2025.04-rc3";
            };
        };
    };
};
7.3.4.3. Node list

For the configuration signature /configurations/conf-1/signature-1, the node list is:

  • /

  • /configurations/conf-1

  • /images/kernel

  • /images/kernel/hash-1

  • /images/fdt-1

  • /images/fdt-1/hash-1

7.3.4.4. What is hashed

The following shows the signed FIT with bold indicating the parts that are included in the configuration signature hash. Lines in normal weight are not hashed. Note that node braces ({ and }) represent FDT_BEGIN_NODE and FDT_END_NODE tokens respectively; these are included whenever the node or its parent is in the node list.

/ {
    description = "Example FIT";
    timestamp = <0x67d96bac>;
    #address-cells = <1>;

    images {
        kernel {
            data = <...kernel data...>;
            type = "kernel";
            arch = "arm64";
            os = "linux";
            compression = "none";
            load = <0x40000000>;
            entry = <0x40000000>;
            hash-1 {
                algo = "sha256";
                value = <...32-byte SHA-256 digest...>;
            };
        };
        fdt-1 {
            data = <...devicetree data...>;
            type = "flat_dt";
            arch = "arm64";
            compression = "none";
            hash-1 {
                algo = "sha256";
                value = <...32-byte SHA-256 digest...>;
            };
        };
    };
    configurations {
        default = "conf-1";
        conf-1 {
            description = "Boot Linux";
            compatible = "vendor,board";
            kernel = "kernel";
            fdt = "fdt-1";
            signature-1 {
                algo = "sha256,rsa2048";
                key-name-hint = "dev";
                sign-images = "kernel", "fdt";
                value = <...256-byte RSA-2048 signature...>;
                hashed-nodes = "/", "/configurations/conf-1", ...;
                hashed-strings = <0x00000000 0x000000d4>;
                timestamp = <0x67d96bac>;
                signer-name = "mkimage";
                signer-version = "2025.04-rc3";
            };
        };
    };
};

Strings block:
description\0
timestamp\0
#address-cells\0
type\0
arch\0
os\0
compression\0
load\0
entry\0
algo\0
value\0
compatible\0
kernel\0
fdt\0
default\0
padding\0

Key points to note:

  • The data properties of both image nodes are excluded since image-data integrity is verified separately through the hash nodes.

  • The default property of the configurations node is not hashed because that node is not in the node list (only its parent / is). This is safe because the bootloader selects a configuration by its own logic, not by trusting the default.

  • All properties of signature-1 are excluded because that node is not in the node list. Its braces are included because its parent conf-1 is. This is safe because the signature itself is verified against a trusted public key, not by hashing.

  • The images and configurations nodes have no properties of their own, but their braces are included because their parent / is in the node list. This serves as a structural sanity check, ensuring that an attacker cannot inject unexpected nodes into the tree without detection.

  • The strings-block region contains the property name strings referenced by the hashed nodes. Although the string-table offset in each FDT_PROP token is hashed, the string at that offset must also be protected; otherwise an attacker could rename a property (e.g. changing algo to something unrecognised) to trick the bootloader into skipping verification. The hashed region should therefore always start at offset 0.

The complete byte sequence (structure-block regions plus strings-block region) is hashed with SHA-256. The resulting digest is then signed with the RSA-2048 private key to produce the signature value.

8. References

[dtspec]

Devicetree Specification https://www.devicetree.org/specifications

[VBE]

Verified Boot for Embedded (VBE) https://docs.u-boot.org/en/latest/develop/vbe.html