An open API service indexing awesome lists of open source software.

https://github.com/elektrobit/cominit

cominit -- Compact Init. Initramfs tool to verify and mount the real rootfs from storage
https://github.com/elektrobit/cominit

Last synced: about 1 month ago
JSON representation

cominit -- Compact Init. Initramfs tool to verify and mount the real rootfs from storage

Awesome Lists containing this project

README

        

# Compact Init

## Local build using Docker container (including html documentation)

from the root directory run:
```
ci/docker-run.sh
```
and
```
ci/build.sh
```
The docker script will download, build, and install dependencies to the container image.
All further steps (i.e. `clang-tidy` and unit testing) assume the build has been run with `ci/build.sh` and
will fail otherwise.

A `clang-tidy` analysis of the source can be performed using
```
ci/clang-tidy.sh
```
This will also generate a `compile_commands.json`. The output will be saved to `result/clang-tidy`.

To build and run the unit tests, run
```
ci/run-utests.sh
```
The test results will be saved to `result/utest_report.txt`.

## Functional Documentation

### General Description
The resulting binary (`cominit`) expects to be run as the system init process out of an initramfs.
Its purpose is to mount the rootfs partition, switch the root and start the rootfs init. The rootfs filesystem may
be read-only or read-write using any filesystem supported by the Kernel in use.
Continuous integrity checking is supported through dm-verity for read-only and dm-integrity for writable variants.

If successful, `cominit`will clean up after itself and exec into the rootfs init
(`[rootfs]/sbin/init`). On any fatal error, `cominit` will try to exec into a rescue shell. This
will be either `[initramfs]/bin/sh` if the error occured before switching the root or `[rootfs]/bin/sh` if
it occured after.

### Startup
To start `cominit` as the init process, one may copy/symlink `cominit` to `/sbin/init` and/or
specify `rdinit=/path/to/cominit` on the Kernel command line. It is possible to give arguments
to the init process via Kernel command line as well, like so:
```
rdinit=/path/to/cominit [OTHER_KERNEL_PARAMETERS] -- [COMINIT_ARGV1] [COMINIT_ARGV2] [...]
```

`cominit` currently either looks at its last argument (`argv[argc - 1]`) for the location of
the rootfs to mount and switch into. All other settings are read from the partition's metadata.

If the rootfs is not immediately available or accessible, cominit will wait a pre-set interval and try again for a
pre-set number of times. These values are currently set via preprocessor defines but need to made configurable in a
later version.

### Rootfs Partition Metadata
As suggested above, a rootfs partition needs to contain a valid metadata region containing settings
for `cominit` as well as a signature.

The metadata region is defined as the last 4 Kilobytes of a partition. Unused space is padded with zeros. It has a data
and a signature block separated by a single zero-Byte (`\0`). The data field contains three sub-blocks separated by
`\xFF` with ASCII-Strings separated by spaces:

```
>>>The metadata region<<<

================================================= data (ASCII) ======================================++++++++++signature++++++++++++
\xFF\xFF\0<512-Byte RSASSA-PSS signature>

```
#### Settings Fields
* **meta_ver** - The version of the metadata format, currently `1`.
* **fstype** - The filesystem type of the rootfs, same format as for the mount() syscall.
* **mode** - Read-only (`ro`) or read-write (`rw`) mount option.
* **crypt** - The device mapper cryptographic features to set up for the rootfs.
- `plain` - None, `DM_TABLE_VALUES_VERITY_INTEGRITY` and `DM_TABLE_VALUES_CRYPT` can be left empty.
- `verity` - Activate dm-verity. `DM_TABLE_VALUES_VERITY_INTEGRITY` must contain valid dm-verity data (see below)
while `DM_TABLE_VALUES_CRYPT` may be left empty.
- `integrity` - Activate dm-integrity. `DM_TABLE_VALUES_VERITY_INTEGRITY` will need to contain valid dm-integrity
data (see below) while `DM_TABLE_VALUES_CRYPT` may be left empty.
- `crypt` - Activate dm-crypt. `DM_TABLE_VALUES_CRYPT` will need to contain valid dm-crypt data (format TBD) while
`DM_TABLE_VALUES_VERITY_INTEGRITY` may be left empty. Currently unimplemented and will cause boot to fail.
- `crypt-verity` - Activate dm-verity and dm-crypt. `DM_TABLE_VALUES_VERITY_INTEGRITY` will need to contain valid
dm-verity data (format TBD) and `DM_TABLE_VALUES_CRYPT` will need to contain valid dm-crypt data (format TBD).
Currently unimplemented and will cause boot to fail.
- `crypt-integrity` - Activate dm-integrity and dm-crypt. `DM_TABLE_VALUES_VERITY_INTEGRITY` will need to contain
valid dm-integrity data (format TBD) and `DM_TABLE_VALUES_CRYPT` will need to contain valid dm-crypt data (format
TBD). Currently unimplemented and will cause boot to fail.

#### DM\_TABLE data
As shown above, the data block contains two sub-blocks for `DM_TABLE` data if needed. These are settings for
dm-verity/integrity and dm-crypt, respectively. All values are in ASCII text.

Currently dm-verity and (hash-based) dm-integrity are supported. The format for dm-verity (written to
`DM_TABLE_VALUES_VERITY_INTEGRITY`) is
```

```
For an explanation of each option, see the [dm-verity Linux Kernel
documentation](https://www.kernel.org/doc/html/latest/admin-guide/device-mapper/verity.html).

For dm-integrity, use the following format:
```
[ ... ]
```
The additional arguments are documented in the [dm-integrity Linux Kernel
documentation](https://www.kernel.org/doc/html/latest/admin-guide/device-mapper/dm-integrity.html). Note, that the
option `data_blocks:` is already generated by `cominit` and does not need to be specified in the additional
arguments.

An example for a 512MiB partition formatted with sha256-based dm-integrity (i.e. with less than 512 MiB available for
actual data), 512 Bytes block size, and using the newer padding format would look like
```
978936 512 2 internal_hash:sha256 fix_padding
```
If a parameter with an optional key is specified, cominit can fetch the key from the Kernel user keyring. For that, use
the keyring key description string prefixed with a `:`.

An example for the above partition using sha256 with HMAC and using the key with description `dm-integrity-hmac-secret`
from the user keyring looks like
```
978936 512 2 internal_hash:hmac(sha256)::dm-integrity-hmac-secret fix_padding
```

#### Signature
The signature block beginning after the delimiting zero-byte contains an RSASSA-PSS signature over all bytes from the
beginning of the data block up to and including the delimiting zero. The used hash function is SHA-256. The resulting
signature length is 512 Bytes.

Assuming we have generated the data block including the delimiting Bytes as the file `data.meta` and we have our private
RSA key as `rootfs.key`, it is possible to use openssl to generate a compatible signature like so
```
openssl dgst -sha256 -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:-1 -sigopt rsa_mgf1_md:sha256 -sign rootfs.key -out sig.meta data.meta
```
This yields a 512 Byte long file `sig.meta` which can then be appended to `data.meta`. The resulting combined file must
then be padded to 4KiB and written to the end of the partition. Possibly, the partition needs to be enlarged/padded
before doing that to provide enough empty space at the end (4kiB, possibly more for correct alignment if necessary).
Finally, the partition table must reflect the total size of the partition including the metadata region and any
additional padding.

`cominit` will expect the fitting public key for verification to be at `/etc/rootfs_key_pub.pem` in the initramfs. To
generate from the private key, one may use
```
openssl rsa -pubout < rootfs.key > rootfs_key_pub.pem
```

### HSM Emulation
If compiled with the optional `-DFAKE_HSM=On` flag, cominit will enroll private keys in the user keyring during early
bootup. This is meant for development purposes in case a real hardware-security module with key storage is unavailable
or not functional. The source file `keyring.h` shows how to define which keys to enroll. The names of the keys need to
be defined at compile-time and the key files themselves need to be accessible to cominit in the initramfs.

### Design decision regarding static linkage and musl

`cominit` is to be statically linked. A decision was made to use the musl libc over the standard glibc for two reasons:

1. Using musl results in considerably smaller binaries than glibc if linking statically.
2. glibc is released under LGPL which does not permit static linking with closed-source code while
musl is released under the more permissive MIT license without these restrictions.