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
.
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.
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.
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++++++++++++
<meta_ver> <fstype> <mode> <crypt>\xFF<DM_TABLE_VALUES_VERITY_INTEGRITY>\xFF<DM_TABLE_VALUES_CRYPT>\0<512-Byte RSASSA-PSS signature>
- 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
andDM_TABLE_VALUES_CRYPT
can be left empty.verity
- Activate dm-verity.DM_TABLE_VALUES_VERITY_INTEGRITY
must contain valid dm-verity data (see below) whileDM_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) whileDM_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) whileDM_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) andDM_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) andDM_TABLE_VALUES_CRYPT
will need to contain valid dm-crypt data (format TBD). Currently unimplemented and will cause boot to fail.
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
<version> <data_block_size> <hash_block_size> <num_data_blocks> <hash_start_block> <algorithm> <digest> <salt>
For an explanation of each option, see the dm-verity Linux Kernel documentation.
For dm-integrity, use the following format:
<num_data_blocks> <data_block_size> <num_additional_args> [<additional> <arguments> ... ]
The additional arguments are documented in the dm-integrity Linux Kernel
documentation. Note, that the
option data_blocks:<number>
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
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
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.
cominit
is to be statically linked. A decision was made to use the musl libc over the standard glibc for two reasons:
- Using musl results in considerably smaller binaries than glibc if linking statically.
- 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.