Showing posts with label yocto. Show all posts
Showing posts with label yocto. Show all posts

Monday, April 10, 2023

Yocto - building a dmverity/squashfs ro-rootfs

Configure the kernel with dm-verity support

$ bitbake linux-yocto -c kernel_configme -f
$ bitbake linux-yocto -c menuconfig

Using menuconfig select -

Device Drivers / Multiple device drivers support / Device Mapper Support / DM "dm-mod.create=" parameter support = [*]
Verity target support = [*]
Verity data device root hash signature verification support = [*]
Cryptographic API / Certificates for signature checking / Additional X.509 keys for default system keyring = ("${HOME}/keys/verity_cert.pem")

Generate a config fragment and check the content

$ bitbake linux-yocto -c diffconfig

$ cat tmp/work/qemux86_64-poky-linux/linux-yocto/*/fragment.cfg
CONFIG_DM_INIT=y
CONFIG_DM_VERITY=y
CONFIG_DM_VERITY_VERIFY_ROOTHASH_SIG=y
# CONFIG_DM_VERITY_FEC is not set
CONFIG_SYSTEM_TRUSTED_KEYS="${HOME}/keys/verity_cert.pem"

$ mv 
tmp/work/qemux86_64-poky-linux/linux-yocto/*/fragment.cfg ../meta-mylayer/recipes-kernel/linux/linux-yocto/dmverity.cfg

Add a reference to the fragment in your kernel recipe - meta-mylayer/recipes-kernel/linux/linux-yocto_%.bbappend

SRC_URI += "file://dmverity.cfg"

Create the certificate

Parameter explanation
-nodes
No DES (i.e. don't encrypt the private key with Data Encryption Standard) 
-days
Specifies the number of days to make a certificate valid for. 
-set_serial
Specifies the serial number to use. 
In a certificate, the serial number is chosen by the CA which issued the certificate. It is just written in the certificate. The CA can choose the serial number in any way as it sees fit, not necessarily randomly (and it has to fit in 20 bytes). A CA is supposed to choose unique serial numbers, that is, unique for the CA.  
-subject
https://en.wikipedia.org/wiki/X.509#Sample_X.509_certificates

Command -
$ openssl req -x509 -newkey rsa:1024 -keyout verity_key.pem \
    -out verity_cert.pem -nodes -days 7300 -set_serial 01 -subject /CN=bugwhine.blogspot.com

Move them somewhere safe
$ mv verity_cert.pem ${HOME}/keys
$ mv verity_key.pem ${HOME}/keys

Build the kernel
$ bitbake linux-yocto -c clean
$ bitbake linux-yocto 

Confirm that it was configured correctly -
$ grep -i verity tmp/work/qemux86_64-poky-linux/linux-yocto/*/*/.config
CONFIG_DM_VERITY=y
CONFIG_DM_VERITY_VERIFY_ROOTHASH_SIG=y
# CONFIG_DM_VERITY_FEC is not set
# CONFIG_FS_VERITY is not set
CONFIG_SYSTEM_TRUSTED_KEYS="${HOME}/keys/verity_cert.pem"

Setup yocto to generate verity metadata for the rootfs image

$ git clone git://git.yoctoproject.org/meta-security
$ cd meta-security
$ git checkout --track remotes/origin/dunfell
$ cd ..
$ git clone https://github.com/openembedded/meta-openembedded.git
$ git checkout --track remotes/origin/dunfell

Add these layers to conf/bblayers.conf -

  meta-security \
  meta-openembedded/meta-oe \
  meta-openembedded/meta-python \
  meta-openembedded/meta-networking \
  meta-openembedded/meta-perl \

Create a wic kickstart file (recipes-core/images/my-image-verity-wic.wks) containing -

part / --source rootfs --ondisk sda --fstype=squashfs
part /media/rfs/rw --ondisk sda --fstype=ext4 --label rwoverlay --size 100M
part / --source rawcopy --ondisk sda --sourceparams="file=${IMGDEPLOYDIR}/${DM_VERITY_IMAGE}-${MACHINE}.${DM_VERITY_IMAGE_TYPE}.verity"

Create a new image recipe (recipes-core/images/my-image-verity-wic.bb) containing -

DESCRIPTION = "A console-only image with more full-featured Linux system \
functionality installed."

SRC_URI = "file://${FILE_DIRNAME}/${BPN}.wks"

IMAGE_FEATURES += "splash ssh-server-openssh"

IMAGE_INSTALL = "\
    packagegroup-core-boot \
    packagegroup-core-full-cmdline \
    ${CORE_IMAGE_EXTRA_INSTALL} \
    initscripts-readonly-rootfs-overlay \
    cryptsetup \
    "

DM_VERITY_IMAGE = "my-image-verity-wic"
DM_VERITY_IMAGE_TYPE = "squashfs"
IMAGE_CLASSES += "dm-verity-img"
IMAGE_FSTYPES = "squashfs squashfs.verity wic"

WICVARS_append = " DM_VERITY_IMAGE DM_VERITY_IMAGE_TYPE"

inherit core-image

WKS_FILE = "my-image.wks"

WKS_FILE_DEPENDS = "dosfstools-native mtools-native gptfdisk-native squashfs-tools-native"
WKS_FILE_DEPENDS_append_x86 = " syslinux-native syslinux"
WKS_FILE_DEPENDS_append_x86-64 = " syslinux-native syslinux"
WKS_FILE_DEPENDS_append_x86-x32 = " syslinux-native syslinux"

QB_KERNEL_CMDLINE_APPEND += "root=/dev/vda1 rootrw=/dev/vda2 rootrwoptions=rw,noatime init=/init"
QB_DEFAULT_FSTYPE = "wic"
QB_FSINFO = "wic:no-kernel-in-fs"
#QB_KERNEL_ROOT = "/dev/vda1"

Patch meta-security/classes/dm-verity-img.bbclass adding squashfs support -

VERITY_TYPES = "ext2.verity ext3.verity ext4.verity btrfs.verity squashfs.verity"

Build and note the verity parameters needed to map it

$ bitbake my-image-verity-wic

$ cat tmp/work-shared/qemux86-64/dm-verity/my-image-verity-wic.squashfs.verity.env
UUID=abcab4c0-3187-4b91-bcb5-d804229456e2
HASH_TYPE=1
DATA_BLOCKS=26760
DATA_BLOCK_SIZE=1024
HASH_BLOCK_SIZE=4096
HASH_ALGORITHM=sha256
SALT=5ac714319ab03dfad686c5563dc78b3be03c802c0f62477a423a56148a32c417
ROOT_HASH=6b4478922104f14b6ca6f463dab26b9a4c653e6b2507e493cf2c95f687b19fc8
DATA_SIZE=27402240

Boot in qemu

At this stage we have both the original squashfs image, and squashfs image with verity metadata appended in partitions 1 and 3 of the wic image.

We boot the squashfs image first -

$ runqemu my-image-verity-wic nographic kvm

Poky (Yocto Project Reference Distro) 3.1.24 qemux86-64 ttyS0

qemux86-64 login: root
root@qemux86-64:~#
root@qemux86-64:~# lsblk
NAME   MAJ:MIN RM   SIZE RO TYPE MOUNTPOINT
hdc     22:0    1     4G  0 disk
vda    253:0    0 153.1M  0 disk
|-vda1 253:1    0  26.1M  0 part /media/rfs/ro
|-vda2 253:2    0   100M  0 part /media/rfs/rw
`-vda3 253:3    0    27M  0 part

Then map and mount it -

root@qemux86-64:~# veritysetup --data-block-size=1024 --hash-offset=27402240 open /dev/vda3 root /dev/vda3 \
>  6b4478922104f14b6ca6f463dab26b9a4c653e6b2507e493cf2c95f687b19fc8
root@qemux86-64:~# ls /dev/mapper/root
/dev/mapper/root
root@qemux86-64:~# mkdir /tmp/dmroot
root@qemux86-64:~# mount /dev/mapper/root /tmp/dmroot/
mount: /var/volatile/tmp/dmroot: WARNING: device write-protected, mounted read-only.
root@qemux86-64:~#

and confirm that it is readable

root@qemux86-64:~# file /tmp/dmroot/usr/sbin/veritysetup
/tmp/dmroot/usr/sbin/veritysetup: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-x86-64.so.2, BuildID[sha1]=8035280d420b22091cf8319d4ceda8e6d

Now we'll tamper with the image, reboot -

root@qemux86-64:~# dd if=/dev/zero of=/dev/vda3 bs=1024 skip=10 count=1
1+0 records in
1+0 records out
1024 bytes (1.0 kB, 1.0 KiB) copied, 0.00516249 s, 198 kB/s
root@qemux86-64:~# reboot

And check if we can remap/mount it -

root@qemux86-64:~#
root@qemux86-64:~# veritysetup --data-block-size=1024 --hash-offset=27402240 open /dev/vda3 root /dev/vda3 \
>  6b4478922104f14b6ca6f463dab26b9a4c653e6b2507e493cf2c95f687b19fc8
Verity device detected corruption after activation.
root@qemux86-64:~#
root@qemux86-64:~# dmesg |tail
[   46.457801] Buffer I/O error on dev dm-0, logical block 0, async page read
[   46.459131] device-mapper: verity: 253:3: data block 0 is corrupted
[   46.459468] device-mapper: verity: 253:3: data block 0 is corrupted
[   46.459475] Buffer I/O error on dev dm-0, logical block 0, async page read
[   46.461389] Buffer I/O error on dev dm-0, logical block 0, async page read
[   46.462957] Buffer I/O error on dev dm-0, logical block 0, async page read
[   46.465372] Buffer I/O error on dev dm-0, logical block 0, async page read
[   46.466989] Buffer I/O error on dev dm-0, logical block 0, async page read
[   46.468663] Buffer I/O error on dev dm-0, logical block 0, async page read
[   46.544350] device-mapper: verity: 253:3: reached maximum errors
root@qemux86-64:~#
root@qemux86-64:~# mkdir /tmp/dmroot
root@qemux86-64:~# mount /dev/mapper/root /tmp/dmroot/
mount: /var/volatile/tmp/dmroot: can't read superblock on /dev/mapper/root.
root@qemux86-64:~#

Note it complains about logical block 0 as I used 'skip=10' rather than 'seek=10' in my dd command.

As previously, the files are available at -


Next steps will be 
- signing and authenticating the root hash
- boot with authentication

Sunday, April 9, 2023

Yocto - read-only rootfs (squashfs) + ext4 overlay (qemu)

Experimenting with a qemu yocto build that models an Embedded system with a read-only rootfs, and writeable overlay.

Basic yocto setup

Clone git://git.yoctoproject.org/poky

Checkout dunfell branch

Clone https://github.com/marcusfolkesson/meta-readonly-rootfs-overlay.git

Update the compatibility in meta-readonly-rootfs-overlay/conf/layer.conf to match your poky branch

LAYERSERIES_COMPAT_readonly-rootfs-overlay = "honister kirkstone dunfell"

source oe-init-build-env

Add meta-readonly-rootfs-overlay to build/conf/bblayers.conf

Create own layer meta-mylayer (git@github.com:bugwhine/meta-mylayer.git)

and add to build/conf/bblayers.conf

Setup meta-mylayer/conf/layer.conf containing the boilerplate -

# We have a conf and classes directory, add to BBPATH
BBPATH .= ":${LAYERDIR}"

# We have recipes-* directories, add to BBFILES
BBFILES += "${LAYERDIR}/recipes-*/*/*.bb \
            ${LAYERDIR}/recipes-*/*/*.bbappend"

BBFILE_COLLECTIONS += "mylayer"
BBFILE_PATTERN_mylayer = "^${LAYERDIR}/"
BBFILE_PRIORITY_mylayer = "5"
LAYERVERSION_mylayer = "4"
LAYERSERIES_COMPAT_mylayer = "dunfell"

Add squashfs support to the yocto kernel

$ bitbake linux-yocto -c kernel_configme -f
$ bitbake linux-yocto -c menuconfig

Use menuconfig to add squashfs support, and customize options as desired

$ bitbake linux-yocto -c diffconfig
$ mkdir -p meta-mylayer/recipes-kernel/linux
$ mv fragment.cfg ../meta-mylayer/recipes-kernel/linux/linux-yocto/squashfs.cfg

Create a bbappend file meta-mylayer/recipes-kernel/linux/linux-yocto_%.bbappend referencing the fragment

FILESEXTRAPATHS:prepend := "${THISDIR}/${PN}:"
SRC_URI += "file://squashfs.cfg"

Build the kernel 

$ bitbake linux-yocto

And confirm that the configuration is present

$ grep SQUASHFS `find tmp/work/qemux86_64-poky-linux/linux-yocto/ -name '.config'`
CONFIG_SQUASHFS=y
...
CONFIG_SQUASHFS_FRAGMENT_CACHE_SIZE=3

Setup an image (wic)

Create an image recipe meta-mylayer/recipes-core/images/my-image-wic.bb with content (based on core-image-full-cmdline)

DESCRIPTION = "A console-only image with more full-featured Linux system \
functionality installed."

SRC_URI = "file://${FILE_DIRNAME}/${BPN}.wks"

IMAGE_FEATURES += "splash ssh-server-openssh"

IMAGE_INSTALL = "\
    packagegroup-core-boot \
    packagegroup-core-full-cmdline \
    ${CORE_IMAGE_EXTRA_INSTALL} \
    initscripts-readonly-rootfs-overlay \
    "

IMAGE_FSTYPES = "wic"

inherit core-image

WKS_FILE = "my-image-wic.wks"

WKS_FILE_DEPENDS = "dosfstools-native mtools-native gptfdisk-native squashfs-tools-native"
WKS_FILE_DEPENDS_append_x86 = " syslinux-native syslinux"
WKS_FILE_DEPENDS_append_x86-64 = " syslinux-native syslinux"
WKS_FILE_DEPENDS_append_x86-x32 = " syslinux-native syslinux"

QB_KERNEL_CMDLINE_APPEND += "root=/dev/vda1 rootrw=/dev/vda2 rootrwoptions=rw,noatime init=/init"
QB_DEFAULT_FSTYPE = "wic"
QB_FSINFO = "wic:no-kernel-in-fs"
#QB_KERNEL_ROOT = "/dev/vda1"

Create the wks (wick kickstart script) meta-mylayer/recipes-core/images/my-image-wic.wks with content - 

part / --source rootfs --ondisk sda --fstype=squashfs --align 1024
part /media/rfs/rw --ondisk sda --fstype=ext4 --label rwoverlay --size 100M

Build and run

$ bitbake my-image-wic
...
$ runqemu my-image-wic nographic kvm
...
Booting from ROM...
[    0.000000] Linux version 5.4.230-yocto-standard (oe-user@oe-host) (gcc version 9.5.0 (GCC)) #1 SMP PREEMPT Wed Feb 1 15:40:31 UTC 2023
[    0.000000] Command line: root=/dev/vda rw  console=ttyS0 mem=256M ip=192.168.7.2::192.168.7.1:255.255.255.0 oprofile.timer=1 root=/dev/vda1 rootrw=/dev/vda2 rootrwoptions=rw,noatime init=/ini
...
Poky (Yocto Project Reference Distro) 3.1.24 qemux86-64 ttyS0

qemux86-64 login: root

Check the rootfs mounts  

root@qemux86-64:~# lsblk
NAME   MAJ:MIN RM   SIZE RO TYPE MOUNTPOINT
hdc     22:0    1     4G  0 disk
vda    253:0    0 126.3M  0 disk
|-vda1 253:1    0  25.3M  0 part /media/rfs/ro
`-vda2 253:2    0   100M  0 part /media/rfs/rw

root@qemux86-64:~# cat /proc/mounts
...
/dev/root /media/rfs/ro squashfs ro,relatime 0 0
/dev/vda2 /media/rfs/rw ext4 rw,noatime 0 0
overlay / overlay rw,relatime,lowerdir=/media/rfs/ro,upperdir=/media/rfs/rw/upperdir,workdir=/media/rfs/rw/work 0 0
...

Confirm that the overlay is working

root@qemux86-64:~# touch /etc/aaa
root@qemux86-64:~# ls /media/rfs/rw/upperdir/etc/aaa
/media/rfs/rw/upperdir/etc/aaa
root@qemux86-64:~# ls /media/rfs/ro/etc/aaa
ls: cannot access '/media/rfs/ro/etc/aaa': No such file or directory


Friday, May 4, 2018

Yocto kernel in-tree development work-flows

The Yocto Project Linux Kernel Development Manual, covers at least two flows for kernel development - 'Traditional Kernel Development' and 'devtool'. Here, I'll present some experiences with both these methods, along with a third method that I found convenient.

Traditional Kernel Development
Preparation
- create a layer for holding kernel patches, and configuration fragments
- create an append recipe (eg. meta-mylayer/recipes-kernel/linux/linux-yocto_4.12.bbappend) that identifies the patches and configuration fragments that you wish to apply
- create a local clone of the Yocto Linux Kernel

Development Loop
- Make code and/or configuration changes in local clone of Yocto Linux Kernel
- Stage and commit the changes (note this is necessary to have them included in the build)
- Adjust conf/local.conf to point to your local kernel clone
- Build and test the changes
- Generate a patch/config fragment
- Move the patch file to your layer, and update the .bbappend file to use it

Reflecting on the flow
Despite the efficiency of git's 'amend commit' function, the loop is quite heavy/time consuming.
For example, if you find the need to add a few printk debug statements, for troubleshooting, committing them each time before building seems an unnecessary burden.

Devtool Kernel Development
Preparation
- create a layer for holding kernel patches
- create an append recipe (eg. meta-mylayer/recipes-kernel/linux/linux-yocto_4.12.bbappend) that identifies the patches that you wish to apply
- (build and install an extensible SDK)
- build a clean image
- checkout the kernel source using 'devtool modify linux-yocto'. This step creates a local copy of the kernel source, in the (SDK) workspace, as well a recipe to include it in the build

Development Loop
- Make code changes in the SDK workspace clone of the kernel source tree
- Build and test the changes
- Stage and commit the changes
- Use 'devtool finish' to generate patches, and include them in your layer

Reflecting on the flow
This flow address the burdens of the traditional flow, as the stage/commit/patch steps are moved outside the change-build-test loop. I found this works well for source code changes.

A limitation of the flow, is that configuration fragments and patches are 'locked' during the preparation step of the flow. Ie. if, whilst in the development loop, you wish to add a configuration fragment ('from the shelf'), you should exit the flow, add the fragment, and re-enter the flow by again completing the preparation steps.

Alternatively, one can manually apply the fragments/patches in the workspace - but doing so conflates these changes with those being made in the development loop.

External Source Kernel Development
Preparation
- create a local clone of the Yocto Linux Kernel
- create a layer for holding kernel patches, and configuration fragments
- create an append recipe (eg. meta-mylayer/recipes-kernel/linux/linux-yocto_4.12.bbappend) that identifies the patches and configuration fragments that you wish to apply
- in the .bbappend recipe add the following -
inherit externalsrc
EXTERNALSRC = "/repo/linux-custom"
SRCTREECOVEREDTASKS := "do_validate_branches do_kernel_checkout do_fetch"

Development Loop
- Make code and/or configuration changes in the kernel source tree
- Build and test the changes
- Stage and commit the changes
- Generate a patch/config fragment
- Move the patch file to your layer, and update the .bbappend file to use it
- Remove the references to the local clone of the Yocto Linux Kernel when done

Reflecting on the flow
This flow attempts to combine the best parts of the other two flows.

It 'gives back' the capability to add/remove patches/configuration fragments via the meta layer (aka. recipe), by overriding this definition in the kernel-yocto.bbclass -
SRCTREECOVEREDTASKS += "do_kernel_configme do_validate_branches do_kernel_configcheck do_kernel_checkout do_fetch do_unpack do_patch"

Thus allowing patches and config fragements to be applied by running the 'unpack' task when building. ie.
bitbake -C unpack linux-yocto

It retains the simple change-build-test loop of the devtool flow.
It sacrifices the automated update of the meta data, provided by 'devtool finish'.

Friday, April 27, 2018

Yocto - update kernel command line in image

The kernel command line is part of the machine configuration.

If you wish to add to it 'manually', you can use the APPEND variable, in for example your conf/local.conf

APPEND += "oops=panic panic=5"

Then, depending on which boot mechanism you're using, you will need to force building of the boot configuration, to include the change.

eg.
$ bitbake -C populate_sysroot grub-efi

$ grep panic build/tmp/work/genericx86_64-poky-linux/core-image-minimal/1.0-r0/core-image-minimal-1.0/hddimg/EFI/BOOT/grub.cfg
linux /vmlinuz LABEL=boot root=/dev/ram0 oops=panic panic=5 

Unfortunately, as is sometimes the case with Yocto, the left hand doesn't know what the right is doing.

$ grep cmdline_append  build/tmp/deploy/images/genericx86-64/core-image-minimal-genericx86-64.qemuboot.conf

qb_kernel_cmdline_append = vga=0 uvesafb.mode_option=640x480-32 oprofile.timer=1 uvesafb.task_timeout=-1

This likely needs tweaking below -

$ grep QB_KERNEL_CMDLINE_APPEND meta/conf/machine/include/qemuboot-x86.inc
QB_KERNEL_CMDLINE_APPEND = "vga=0 uvesafb.mode_option=${UVESA_MODE} oprofile.timer=1 uvesafb.task_timeout=-1"

Similarly, the append is not present, in the wic image -

$ bitbake -C image_wic core-image-minimal 

$ sudo losetup -o $((2048*512)) --show -f core-image-minimal-genericx86-64.wic
/dev/loop1
$ mkdir /mnt/loop1
$ sudo mount /dev/loop1 /mnt/loop1

$ cat /mnt/loop1/EFI/BOOT/grub.cfg 
serial --unit=0 --speed=115200 --word=8 --parity=no --stop=1
default=boot
timeout=5
menuentry 'boot'{
linux /bzImage root=PARTUUID=c496bfa5-a796-473c-bb6f-50d7b0161295 rootwait rootfstype=ext4 console=ttyS0,115200 console=tty0
}

This can be tweaked in the 'kickstart' file below -

$ grep append meta-yocto-bsp/wic/genericx86.wks
bootloader --ptable gpt --timeout=5 --append="rootfstype=ext4 console=ttyS0,115200 console=tty0"