Repository: OSInside/kiwi

Issue:
Add EROFS support for OEM image types
## Overview

Hello, I am Caleb from Amazon Linux and we are interested in adding support for EROFS to Kiwi NG for OEM image types. This would enable creating bootable images with EROFS as the root filesystem, providing the benefits of EROFS to the base image, particularly for security-critical and attestable workloads.

Benefits would include: 

* Immutable root filesystems for enhanced security
* Robust attestation of root filesystems with DM-Verity
* Efficient compression of the root filesystem
* Enhanced security posture for sensitive workloads

## Background

EROFS is already available as a filesystem and image type in kiwi but is not a provided as filesystem for the OEM image type. EROFS is helpful creating attestable images that can run secure workloads with integrity. Adding EROFS support to the root filesystem will allow users to secure images and attest the root filesystems contents.

## What we would like to implement

Based on my initial work, the implementation would require:

1. Adding EROFS to the list of supported filesystems in Kiwi's schema for oem types
1. Implementing the `create_on_device` function to the `erofs.py` module to handle EROFS filesystem creation for OEM images
1. Implement targeted overlays for the EROFS root filesystem to minimize what is modifiable and still allow the image to function (i.e. keep /var,/etc and possible /home writable)
1. Updating the OEM image builder to support EROFS as a root filesystem as an XML attribute 
1. Implementing optional DM-Verity integration for attestation scenarios
1. Adding support for generating filesystem measurement manifests

## Why EROFS support in OEM is needed

It's important to note that simply supporting EROFS as a filesystem is not sufficient for attesting images. For this feature to be truly useful for attestation, we need to ensure that:

1. **OEM image type compatibility**: EROFS needs to be fully supported in the OEM image type, allowing for bootable images with EROFS as the root filesystem. 
    1. Likely this will imply that overlays will be required, fortunately kiwi already provides this feature.
    2. We may also want to add the ability to target specific directories for overlays, thus minimizing what is modifiable in the root filesystem
1. **Initrd/initramfs support**: The initial ramdisk must be able to recognize and mount EROFS root filesystems.
1. **Measurement and attestation hooks**: Support for filesystem measurements that can be used in attestation flows.

### Security benefits for attestable workloads

1. **Tamper-evident filesystem**: EROFS combined with DM-Verity creates a tamper-evident root filesystem where any modification can be cryptographically detected.
1. **Supply chain security**: Helps maintain the integrity of the software supply chain by ensuring that deployed images match exactly what was built and signed.
1. **Runtime integrity**: Prevents runtime modifications to system binaries and libraries, protecting against certain classes of persistent threats.

## Example configuration

Here's how EROFS could be configured in a Kiwi XML file:

```
<!-- OEM image with EROFS root filesystem -->
<preferences>
    <type image="oem" filesystem="erofs" erofscompression="zstd,level=12">
        <bootloader name="systemd_boot"/>
    </type>
</preferences>

<!-- Secure OEM image with EROFS and DM-Verity -->
<preferences>
    <type image="oem" filesystem="erofs" erofscompression="zstd,level=9" verity="true" verity_hash_algorithm="sha256">
        <bootloader name="systemd_boot" secure_boot="true"/>
    </type>
</preferences>

<!-- Live ISO with EROFS root -->
<preferences>
    <type image="iso" filesystem="erofs" erofscompression="zstd,level=12">
        <bootloader name="grub2" console="serial" timeout="10"/>
    </type>
</preferences>
```

## Next steps

I'm willing to contribute this implementation with a PR and we've already done some preliminary work and testing that demonstrates the feasibility and benefits of this approach.

Would the maintainers be interested in this feature? Any specific requirements or considerations I should be aware of before proceeding with a pull request?

Discussion:
@Conan-Kudo: I am certainly interested in this capability. I think the only open question I have for myself and @schaefi is whether we want to add significantly more code to our dracut modules or if we want to push this functionality into upstream dracut, since dracut is now a living upstream and also co-maintained by a member of the kiwi team (me specifically).

But I am interested regardless and I'd love to see a pull request to review this and discuss it further.@hsiangkao: In short, I'm glad to see this too, and hopefully more developpers can help and try.@schaefi: @hsiangkao Thanks for bringing up this topic and the very good initial description. It's a coincidence that I'm currently working on the dm-verity setup using the systemd-verity-generator. As part of the work there I also extended the schema to allow selecting the read-only filesystem in an overlay disk. So far this was hard coded to squashfs but we recently added support for erofs. So extending that part is relatively easy. I will come up with a PR in that regard and I believe from there we can add the missing pieces

Thanks@xcawolfe-amzn: > It's a coincidence that I'm currently working on the dm-verity setup using the systemd-verity-generator. As part of the work there I also extended the schema to allow selecting the read-only filesystem in an overlay disk. So far this was hard coded to squashfs but we recently added support for erofs. So extending that part is relatively easy. I will come up with a PR in that regard and I believe from there we can add the missing pieces

@schaefi O nice! good timing with your dm-verity work, just to make sure I am not misunderstanding, are you working on a PR for both erofs and dm-verity? We on the Amazon Linux side are here to help where ever we can 😄 


@schaefi: It will cover both ends. I started testing systemd-veritysetup-generator and the read-only part was a squashfs, but mksquashfs was soo slow that I decided to add the few lines of code to allow erofs here. So erofs is working fine for me but I struggle with systemd-veritysetup-generator which I could not get to work even though there is a correct etc/veritytab in the initrd and I can manually map via `veritysetup open ...` 

I'll create the PR in a minute so you can also see what has been done so far. Really funny that you came up with this topic right on time :) 

> We on the Amazon Linux side are here to help where ever we can

That's awesome :) I'm happy if you can help on the verity side of things@xcawolfe-amzn: Awesome sounds good, we'll dig into the verity side. We will create a separate issue for the verity side of the things!@schaefi: > Awesome sounds good, we'll dig into the verity side. We will create a separate issue for the verity side of the things!

Sounds great. See the open PR. Once our buildsystem is ready you will also find image builds to test. My integration test still uses grub and I see you prefer sd_boot. We can change that for this integration test image too but imho has low prio at the moment. From the PR you also see a few differences in the way how the image type gets described. To be noticed:

* verity_hash_algorithm is something we currently don't offer to be configured and stick with the default sha256. If this is needed we can add that in a followup PR
*  secure_boot="true" That is handled differently in kiwi via the firmware attribute. If set to uefi we have code to enable secure boot. However this is a hot topic because there is no standard for the secure boot setup across distributions and especially on read-only root devices most of the tools I saw fail to work. Actually all we need is a shim signed EFI binary but many distros adds vendor stuff there which makes this really hard. Not sure how you handle things in this regard at AmazonLinux. So I suggest to handle secure boot as the last topic once we sorted out all the rest. secure boot is also currently turned off in my integration test image which you can see as we only set `firmware="efi"` not `uefi`

Hope this makes sense@xcawolfe-amzn: Yep makes sense!

>  My integration test still uses grub and I see you prefer sd_boot. We can change that for this integration test image too but imho has low prio at the moment

No problem. For context on sd_boot, for our needs we want a UKI ultimately so sd_boot seems like the best way to go for us, Ill have more details on this specific topic in the verity ticket. 

> secure_boot="true

Gotcha, I forgot you have the uefi and efi respectively for toggling secure boot we'll investigate the best way the flags you all already have for our needs.

Thank you!


@xcawolfe-amzn: @schaefi @Conan-Kudo, fyi I created this issue for the verity side of what we are thinking we might want to add https://github.com/OSInside/kiwi/issues/2801@schaefi: @xcawolfe-amzn An update about the current state:

With the changes proposed in PR #2800 the following issues are covered:

* Ability to select read-only filesystem for oem image types with overlayroot. A new attribute `overlayroot_readonly_filesystem="erofs|squashfs"` has been added
* Update of the `test-image-overlayroot` integration test to meet the needs of the oem disk image as described here
    - overlayroot setup with erofs
    - systemd_boot bootloader
    - secure boot enabled
    - verity baked
    - builds available here: https://build.opensuse.org/package/show/Virtualization:Appliances:Images:Testing_x86:tumbleweed/test-image-overlayroot

**NOTE: Images are currently rebuilding, give it an hour or two to settle. If you see still grub based variants, it's because of an old stage**

I believe PR #2800 is in a good state for review and merge. Still open topics are

- [ ] The features are available together with the two kiwi provided dracut modules `kiwi-overlay` and `kiwi-verity` For kiwi-overlay I don't see a need and also not much chance to get that into upstream dracut because the overlay layout for which the module works is coupled with the implementation and ideas of kiwi in this regard. The kiwi-verity module however should not be needed if systemd-verity-generator would work in the expected way. Even though kiwi writes a correct /etc/veritytab the generator did not work properly for me. To handle this defect #2801 was opened and I'm happy for the helping hands
- [x] We eventually want to provide an additional integration test which uses grub and btrfs(as write filesystem) as just another technology setup

For details on the images built so far please read the comments in the open PR and/or test yourself via

```
wget https://download.opensuse.org/repositories/Virtualization:/Appliances:/Images:/Testing_x86:/tumbleweed/images/kiwi-test-image-overlayroot.x86_64-sdboot_verity_erofs.vmdk
qemu-kvm -cpu Broadwell-v2 -m 4096 -netdev user,id=user0 -device virtio-net-pci,netdev=user0 -serial stdio -bios /usr/share/qemu/ovmf-x86_64.bin -hda kiwi-test-image-overlayroot.x86_64-sdboot_verity_erofs.vmdk
```

Thanks@Conan-Kudo: > We eventually want to provide an additional integration test which uses grub and btrfs(as write filesystem) as just another technology setup

I don't think we should land the PR without that, because I'm afraid of this being subtly broken.@Conan-Kudo: > For kiwi-overlay I don't see a need and also not much chance to get that into upstream dracut because the overlay layout for which the module works is coupled with the implementation and ideas of kiwi in this regard.

Yes, but there's a dmsquash module in upstream dracut that we should be able to make it work with.@schaefi: > > For kiwi-overlay I don't see a need and also not much chance to get that into upstream dracut because the overlay layout for which the module works is coupled with the implementation and ideas of kiwi in this regard.
> 
> Yes, but there's a dmsquash module in upstream dracut that we should be able to make it work with.

I don't think so because 90dmsquash-live is for live systems and it's code has historically grown. The kiwi-overlay dracut module is specifically for disk images using overlay technologies and I don't want to mix that with live iso support in dracut. Don't mix kiwi-overlay with kiwi-live. The kiwi-live module is already scheduled for deletion because dmsquash supports all it can do@schaefi: > > We eventually want to provide an additional integration test which uses grub and btrfs(as write filesystem) as just another technology setup
> 
> I don't think we should land the PR without that, because I'm afraid of this being subtly broken.

I can add that, but don't see what should break because I had grub working before I switched to systemd_boot :) I'll add the tests@schaefi: @Conan-Kudo test build with grub and btrfs added and building
* https://build.opensuse.org/package/show/Virtualization:Appliances:Images:Testing_x86:tumbleweed/test-image-overlayroot

I changed the profile names to be a bit more descriptive@Conan-Kudo: > I don't think so because 90dmsquash-live is for live systems and it's code has historically grown.

Upstream dracut has split the module up quite a bit more. There are now generic `overlayfs` module and `squash-erofs`/`squash-squashfs` modules. It may be possible to reuse that for an upstream module.@schaefi: > > I don't think so because 90dmsquash-live is for live systems and it's code has historically grown.
> 
> Upstream dracut has split the module up quite a bit more. There are now generic `overlayfs` module and `squash-erofs`/`squash-squashfs` modules. It may be possible to reuse that for an upstream module.

This makes sense. Still leaves room for issues on distros which doesn't provide them, but I agree its worth looking at it. However, I suggest to do this also as an extra effort not tightly coupled to this issue report here as it already covers some sub-topics that doesn't have to be addressed all at the same time. Thoughts@Conan-Kudo: We should definitely break it out into another ticket to more broadly look at our internal dracut modules and what we can do to upstream their functionality. We can then benefit from everyone reusing them, and we can figure out a backport strategy for when we're shipping to older distros.

Solution patch:
diff --git a/doc/source/image_description/elements.rst b/doc/source/image_description/elements.rst
index 24887b80..2e9eed24 100644
--- a/doc/source/image_description/elements.rst
+++ b/doc/source/image_description/elements.rst
@@ -825,6 +825,11 @@ overlayroot="true|false":
   the squashfs root filesystem. In fact this mode is the same
   as not installing the `kiwi-overlay` dracut module.
 
+overlayroot_readonly_filesystem="squashfs|erofs":
+  For the `oem` type only, specifies the filesystem type to use
+  as read-only filesystem in an `overlayroot` setup. By default
+  `squashfs` is used
+
 overlayroot_write_partition="true|false":
   For the `oem` type only, allows to specify if the extra read-write
   partition in an `overlayroot` setup should be created or not.
diff --git a/dracut/modules.d/80kiwi-verity/kiwi-veritytab-setup.sh b/dracut/modules.d/80kiwi-verity/kiwi-veritytab-setup.sh
new file mode 100755
index 00000000..8935a8ed
--- /dev/null
+++ b/dracut/modules.d/80kiwi-verity/kiwi-veritytab-setup.sh
@@ -0,0 +1,40 @@
+#!/bin/sh
+# This file is part of kiwi.
+#
+# kiwi is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# kiwi is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with kiwi.  If not, see <http://www.gnu.org/licenses/>
+#
+# shellcheck disable=SC1091
+type getarg >/dev/null 2>&1 || . /lib/dracut-lib.sh
+
+if [ ! -e "/etc/veritytab" ];then
+    return
+fi
+
+read -r name data_device hash_device root_hash options < /etc/veritytab
+
+if [ "$(echo "${data_device}" | cut -f1 -d=)" = "UUID" ];then
+    data_device=/dev/disk/by-uuid/$(echo "${data_device}" | cut -f2 -d=)
+fi
+if [ "$(echo "${hash_device}" | cut -f1 -d=)" = "UUID" ];then
+    hash_device=/dev/disk/by-uuid/$(echo "${hash_device}" | cut -f2 -d=)
+fi
+
+veritysetup="veritysetup open "
+veritysetup="${veritysetup} ${data_device} ${name} ${hash_device} ${root_hash}"
+
+for option in $(echo "${options}" | tr , " ");do
+    veritysetup="${veritysetup} --${option}"
+done
+
+eval "${veritysetup}"
diff --git a/dracut/modules.d/80kiwi-verity/module-setup.sh b/dracut/modules.d/80kiwi-verity/module-setup.sh
index 18ed1777..1a9b6903 100755
--- a/dracut/modules.d/80kiwi-verity/module-setup.sh
+++ b/dracut/modules.d/80kiwi-verity/module-setup.sh
@@ -30,6 +30,11 @@ depends() {
 }
 
 install() {
-    inst_multiple /usr/bin/kiwi-parse-verity /usr/sbin/veritysetup
+    inst_multiple \
+        /usr/bin/kiwi-parse-verity \
+        /usr/sbin/veritysetup \
+        cut \
+        tr
     inst_hook initqueue/settled 70 "$moddir/kiwi-verity-setup.sh"
+    inst_hook initqueue/settled 71 "$moddir/kiwi-veritytab-setup.sh"
 }
diff --git a/kiwi.yml b/kiwi.yml
index 06910ea4..ac7c5cfe 100644
--- a/kiwi.yml
+++ b/kiwi.yml
@@ -101,9 +101,6 @@
 #      # verify that the URL for imageinclude repos is accessable
 #      - check_image_include_repos_publicly_resolvable
 
-#      # verify secure boot setup disabled for overlay configured disk images
-#      - check_efi_mode_for_disk_overlay_correctly_setup
-
 #      # verify for legacy kiwi boot images that they exist on the host
 #      - check_boot_description_exists
 
diff --git a/kiwi/bootloader/config/base.py b/kiwi/bootloader/config/base.py
index 3765bf68..11a1f148 100644
--- a/kiwi/bootloader/config/base.py
+++ b/kiwi/bootloader/config/base.py
@@ -60,6 +60,7 @@ class BootLoaderConfigBase(ABC):
         self.proc_mount = None
         self.sys_mount = None
         self.tmp_mount = None
+        self.etc_kernel_mount = None
 
         self.root_filesystem_is_overlay = xml_state.build_type.get_overlayroot()
         self.post_init(custom_args)
@@ -555,16 +556,24 @@ class BootLoaderConfigBase(ABC):
 
         if self.root_filesystem_is_overlay:
             # In case of an overlay root system all parts of the rootfs
-            # are read-only by squashfs except for the extra boot partition.
-            # However tools like grub's mkconfig creates temporary files
-            # at call time and therefore /tmp needs to be writable during
-            # the call time of the tools
+            # are read-only. However tools like grub's mkconfig creates
+            # temporary files at call time and therefore /tmp needs to
+            # be writable during the call time of the tools
             self.tmp_mount = MountManager(
                 device='/tmp',
                 mountpoint=self.root_mount.mountpoint + '/tmp'
             )
             self.tmp_mount.bind_mount()
 
+            # There are also tools that writes to /etc/kernel, e.g
+            # systemd-boot. If it exists we map it to the ESP
+            etc_kernel = f'{self.root_mount.mountpoint}/etc/kernel'
+            if os.path.exists(etc_kernel):
+                self.etc_kernel_mount = MountManager(
+                    device=efi_device, mountpoint=etc_kernel
+                )
+                self.etc_kernel_mount.mount()
+
         self.device_mount = MountManager(
             device='/dev',
             mountpoint=self.root_mount.mountpoint + '/dev'
@@ -600,6 +609,8 @@ class BootLoaderConfigBase(ABC):
                 self.efi_mount.umount()
             if self.tmp_mount:
                 self.tmp_mount.umount()
+            if self.etc_kernel_mount:
+                self.etc_kernel_mount.umount()
             if self.boot_mount:
                 self.boot_mount.umount()
             if self.root_mount:
@@ -626,13 +637,17 @@ class BootLoaderConfigBase(ABC):
                 return root_search.group(1)
         if boot_device:
             if self.xml_state.build_type.get_overlayroot():
-                # In case of an overlay setup the root partition is a squashfs
-                # In this case the root location can only be specified by the
-                # partition uuid because squashfs itself doesn't have one.
+                # In case of an overlay setup the root partition is read-only
+                # In this case the root location will be specified by the
+                # partition uuid because not all read-only filesystems have one.
                 # Exception to this is if the overlay is also encrypted
+                # Exception to this is if the overlay is on verity
+                verity = self.xml_state.build_type.get_verity_blocks()
                 luks = self.xml_state.get_luks_credentials()
                 if luks is not None:
                     return 'root=overlay:MAPPER=luks'
+                elif verity:
+                    return 'root=overlay:MAPPER=verityroot'
                 else:
                     root_location = self._get_location(
                         boot_device, 'by-partuuid'
diff --git a/kiwi/builder/disk.py b/kiwi/builder/disk.py
index 25d1fa5c..cbc1d011 100644
--- a/kiwi/builder/disk.py
+++ b/kiwi/builder/disk.py
@@ -56,7 +56,6 @@ from kiwi.storage.integrity_device import (
 )
 from kiwi.storage.device_provider import DeviceProvider
 from kiwi.filesystem import FileSystem
-from kiwi.filesystem.squashfs import FileSystemSquashFs
 from kiwi.volume_manager import VolumeManager
 from kiwi.volume_manager.base import VolumeManagerBase
 from kiwi.command import Command
@@ -126,10 +125,13 @@ class DiskBuilder:
             xml_state.build_type.get_spare_part_mountpoint()
         self.persistency_type = xml_state.build_type.get_devicepersistency()
         self.root_filesystem_is_overlay = xml_state.build_type.get_overlayroot()
+        self.root_filesystem_read_only_type = \
+            xml_state.build_type.get_overlayroot_readonly_filesystem()
         self.root_filesystem_has_write_partition = \
             xml_state.build_type.get_overlayroot_write_partition()
         self.root_filesystem_read_only_partsize = \
             xml_state.build_type.get_overlayroot_readonly_partsize()
+        self.veritysetup = None
         self.root_filesystem_verity_blocks = \
             xml_state.build_type.get_verity_blocks()
         self.root_filesystem_embed_verity_metadata = \
@@ -853,6 +855,11 @@ class DiskBuilder:
             )
 
             # run post sync actions...
+            if self.veritysetup:
+                self._write_veritytab_to_boot_image(
+                    device_map, self.veritysetup
+                )
+
             if system:
                 with ImageSystem(
                     device_map, self.root_dir,
@@ -860,6 +867,21 @@ class DiskBuilder:
                     self.custom_partitions if self.custom_partitions else {}
                 ) as image_system:
                     image_system.mount()
+                    root = image_system.mountpoint()
+
+                    # rebuild initrd with veritytab included
+                    if self.veritysetup and \
+                       self.boot_image.has_initrd_support():
+                        self.boot_image.create_initrd(self.mbrid)
+                        boot_names = self.boot_image.get_boot_names()
+                        if os.access(f'{root}/boot/', os.W_OK):
+                            Command.run(
+                                [
+                                    'mv', self.boot_image.initrd_filename,
+                                    f'{root}/boot/{boot_names.initrd_name}'
+                                ]
+                            )
+
                     disk_system = SystemSetup(
                         self.xml_state, image_system.mountpoint()
                     )
@@ -868,7 +890,7 @@ class DiskBuilder:
                     ):
                         # run post sync script hook and security context
                         disk_system.call_disk_script()
-                    else:
+                    elif not self.root_filesystem_is_overlay:
                         # setup security context
                         disk_system.setup_selinux_file_contexts()
 
@@ -878,6 +900,18 @@ class DiskBuilder:
                             device_map, disk, system, bootloader_config
                         )
 
+                    # initrd update on the ESP for systemd for read-only boot/
+                    if self.veritysetup and \
+                       self.boot_image.has_initrd_support() and not \
+                       os.access(f'{root}/boot/', os.W_OK) and \
+                       self.bootloader == 'systemd_boot':
+                        Command.run(
+                            [
+                                'mv', self.boot_image.initrd_filename,
+                                f'{root}/boot/efi/os/{boot_names.initrd_name}'
+                            ]
+                        )
+
                     # set root filesystem properties
                     if system:
                         self._setup_property_root_is_readonly_snapshot(system)
@@ -1115,7 +1149,8 @@ class DiskBuilder:
             squashed_rootfs_mbsize = self.root_filesystem_read_only_partsize
             if not self.root_filesystem_read_only_partsize:
                 squashed_root_file = Temporary().new_file()
-                with FileSystemSquashFs(
+                with FileSystem.new(
+                    self.root_filesystem_read_only_type or 'squashfs',
                     device_provider=DeviceProvider(), root_dir=self.root_dir,
                     custom_args={
                         'compression':
@@ -1309,6 +1344,31 @@ class DiskBuilder:
         log.info('Creating generic system etc/fstab')
         self._write_generic_fstab(device_map, self.system_setup, system)
 
+    def _write_veritytab_to_boot_image(
+        self, device_map: Dict, veritysetup: VeritySetup
+    ) -> None:
+        log.info('Creating generic boot image etc/veritytab')
+        veritytab_filename = ''.join([self.root_dir, '/etc/veritytab'])
+        uuid = device_map['readonly'].get_uuid(
+            device_map['readonly'].get_device()
+        ) if device_map.get('readonly') else device_map['root'].get_uuid(
+            device_map['root'].get_device()
+        )
+        with open(veritytab_filename, 'w') as veritytab:
+            veritytab.write(
+                'verityroot UUID={0} UUID={0} {1} {2},{3}{4}'.format(
+                    uuid, veritysetup.verity_dict.get('Roothash'),
+                    f'hash-offset={veritysetup.verity_hash_offset}',
+                    f'hash-block-size={defaults.VERITY_HASH_BLOCKSIZE}',
+                    os.linesep
+                )
+            )
+            self.boot_image.include_file(
+                filename=os.sep + os.sep.join(
+                    ['etc', os.path.basename(veritytab_filename)]
+                ), delete_after_include=True
+            )
+
     def _write_generic_fstab_to_boot_image(
         self, device_map: Dict,
         system: Optional[Union[FileSystemBase, VolumeManagerBase]]
@@ -1572,7 +1632,8 @@ class DiskBuilder:
         log.info('--> Syncing root filesystem data')
         if self.root_filesystem_is_overlay:
             squashed_root_file = Temporary().new_file()
-            with FileSystemSquashFs(
+            with FileSystem.new(
+                self.root_filesystem_read_only_type or 'squashfs',
                 device_provider=DeviceProvider(), root_dir=self.root_dir,
                 custom_args={
                     'compression':
@@ -1615,6 +1676,7 @@ class DiskBuilder:
                         self.root_filesystem_verity_blocks if
                         self.root_filesystem_verity_blocks != 'all' else None
                     )
+                    self.veritysetup = squashed_root.veritysetup
 
                 readonly_target = device_map['readonly'].get_device()
                 readonly_target_bytesize = device_map['readonly'].get_byte_size(
@@ -1682,6 +1744,7 @@ class DiskBuilder:
                     self.root_filesystem_verity_blocks != 'all' else None,
                     verity_root_file.name
                 )
+                self.veritysetup = filesystem.veritysetup
 
             log.info(
                 '--> Dumping rootfs file({0} bytes) -> {1}({2} bytes)'.format(
@@ -1745,11 +1808,12 @@ class DiskBuilder:
         root_device = device_map['root']
         boot_device = root_device
         readonly_device = None
-        if 'boot' in device_map:
-            boot_device = device_map['boot']
-
         if 'readonly' in device_map:
             readonly_device = device_map['readonly']
+            boot_device = readonly_device
+
+        if 'boot' in device_map:
+            boot_device = device_map['boot']
 
         custom_install_arguments = {
             'boot_device': boot_device.get_device(),
diff --git a/kiwi/runtime_checker.py b/kiwi/runtime_checker.py
index 4eabe0f7..4461c99a 100644
--- a/kiwi/runtime_checker.py
+++ b/kiwi/runtime_checker.py
@@ -838,35 +838,6 @@ class RuntimeChecker:
                     message.format(required_dracut_packages)
                 )
 
-    def check_efi_mode_for_disk_overlay_correctly_setup(self) -> None:
-        """
-        Disk images configured to use a root filesystem overlay
-        only supports the standard EFI mode and not secure boot.
-        That's because the shim setup performs changes to the
-        root filesystem which can not be applied during the
-        bootloader setup at build time because at that point
-        the root filesystem is a read-only squashfs source.
-        """
-        message = dedent('''\n
-            Secure Boot not supported with overlay disk image
-
-            Disk images configured to use a root filesystem overlay
-            only supports the standard EFI mode and not secure boot.
-            That's because the shim setup performs changes to the
-            root filesystem which can not be applied during the
-            bootloader setup at build time because at that point
-            the root filesystem is a read-only squashfs source
-
-            Thus please change the firmware attribute in the <type>
-            section of the system XML description as follows:
-
-            <type ... firmware="efi"/>
-        ''')
-        overlayroot = self.xml_state.build_type.get_overlayroot()
-        firmware = self.xml_state.build_type.get_firmware()
-        if overlayroot and firmware == 'uefi':
-            raise KiwiRuntimeError(message)
-
     def check_xen_uniquely_setup_as_server_or_guest(self) -> None:
         """
         If the image is classified to be used as Xen image, it can
diff --git a/kiwi/system/mount.py b/kiwi/system/mount.py
index 8c600af3..ac4119d4 100644
--- a/kiwi/system/mount.py
+++ b/kiwi/system/mount.py
@@ -103,7 +103,7 @@ class ImageSystem:
                     self.root_mount_mountpoint, 'boot', 'efi'
                 )
             )
-        if not f'{root_device}' == f'{boot_device}':
+        if root_device != boot_device:
             self.mount_list.append(boot_mount)
             boot_mount.mount()
 
@@ -175,11 +175,12 @@ class ImageSystem:
     def _setup_device_names(self) -> tuple:
         root_device = self.device_map['root'].get_device()
         boot_device = root_device
-        efi_device = None
-        if 'boot' in self.device_map:
-            boot_device = self.device_map['boot'].get_device()
+        efi_device = ''
         if 'readonly' in self.device_map:
             root_device = self.device_map['readonly'].get_device()
+            boot_device = root_device
+        if 'boot' in self.device_map:
+            boot_device = self.device_map['boot'].get_device()
         if 'efi' in self.device_map:
             efi_device = self.device_map['efi'].get_device()
         return (root_device, boot_device, efi_device)
diff --git a/kiwi/tasks/base.py b/kiwi/tasks/base.py
index 5c196a24..13fe84b4 100644
--- a/kiwi/tasks/base.py
+++ b/kiwi/tasks/base.py
@@ -71,7 +71,6 @@ class CliTask:
         # initialize generic runtime check dicts
         self.checks_before_command_args: Dict[str, List[str]] = {
             'check_image_version_provided': [],
-            'check_efi_mode_for_disk_overlay_correctly_setup': [],
             'check_initrd_selection_required': [],
             'check_boot_description_exists': [],
             'check_consistent_kernel_in_boot_and_system_image': [],
diff --git a/kiwi/utils/veritysetup.py b/kiwi/utils/veritysetup.py
index 740b5a65..4d375f2c 100644
--- a/kiwi/utils/veritysetup.py
+++ b/kiwi/utils/veritysetup.py
@@ -73,7 +73,6 @@ class VeritySetup:
             [
                 'veritysetup', 'format',
                 self.image_filepath, self.image_filepath,
-                '--no-superblock',
                 f'--hash-offset={self.verity_hash_offset}',
                 f'--hash-block-size={defaults.VERITY_HASH_BLOCKSIZE}'
             ] + (
@@ -109,7 +108,6 @@ class VeritySetup:
             [
                 'veritysetup', 'format',
                 self.image_filepath, temp_file.name,
-                '--no-superblock',
                 f'--hash-block-size={defaults.VERITY_HASH_BLOCKSIZE}'
             ] + (
                 [
@@ -240,5 +238,5 @@ class VeritySetup:
                 verity.write(
                     f'Root hashoffset: {self.verity_hash_offset}')
                 verity.write(os.linesep)
-                verity.write('Superblock: --no-superblock')
+                verity.write('Superblock:')
                 verity.write(os.linesep)
diff --git a/kiwi/xml_parse.py b/kiwi/xml_parse.py
index 8c664bd7..e588d9c2 100644
--- a/kiwi/xml_parse.py
+++ b/kiwi/xml_parse.py
@@ -3363,7 +3363,7 @@ class type_(GeneratedsSuper):
     """The Image Type of the Logical Extend"""
     subclass = None
     superclass = None
-    def __init__(self, boot=None, bootfilesystem=None, firmware=None, bootkernel=None, bootpartition=None, bootpartsize=None, efipartsize=None, efifatimagesize=None, eficsm=None, efiparttable=None, dosparttable_extended_layout=None, bootprofile=None, btrfs_quota_groups=None, btrfs_root_is_snapper_snapshot=None, btrfs_root_is_subvolume=None, btrfs_set_default_volume=None, btrfs_root_is_readonly_snapshot=None, compressed=None, devicepersistency=None, editbootconfig=None, editbootinstall=None, filesystem=None, flags=None, enclave_format=None, format=None, formatoptions=None, fsmountoptions=None, fscreateoptions=None, squashfscompression=None, erofscompression=None, gcelicense=None, hybridpersistent=None, hybridpersistent_filesystem=None, gpt_hybrid_mbr=None, force_mbr=None, initrd_system=None, image=None, metadata_path=None, installboot=None, install_continue_on_timeout=None, installprovidefailsafe=None, installiso=None, installstick=None, installpxe=None, mediacheck=None, kernelcmdline=None, luks=None, luks_version=None, luksOS=None, luks_randomize=None, luks_pbkdf=None, mdraid=None, overlayroot=None, overlayroot_write_partition=None, overlayroot_readonly_partsize=None, verity_blocks=None, embed_verity_metadata=None, standalone_integrity=None, embed_integrity_metadata=None, integrity_legacy_hmac=None, integrity_metadata_key_description=None, integrity_keyfile=None, primary=None, ramonly=None, rootfs_label=None, spare_part=None, spare_part_mountpoint=None, spare_part_fs=None, spare_part_fs_attributes=None, spare_part_is_last=None, target_blocksize=None, target_removable=None, selinux_policy=None, vga=None, vhdfixedtag=None, volid=None, application_id=None, wwid_wait_timeout=None, derived_from=None, delta_root=None, provide_system_files=None, require_system_files=None, ensure_empty_tmpdirs=None, xen_server=None, publisher=None, disk_start_sector=None, root_clone=None, boot_clone=None, bundle_format=None, bootloader=None, containerconfig=None, machine=None, oemconfig=None, size=None, systemdisk=None, partitions=None, vagrantconfig=None, installmedia=None, luksformat=None):
+    def __init__(self, boot=None, bootfilesystem=None, firmware=None, bootkernel=None, bootpartition=None, bootpartsize=None, efipartsize=None, efifatimagesize=None, eficsm=None, efiparttable=None, dosparttable_extended_layout=None, bootprofile=None, btrfs_quota_groups=None, btrfs_root_is_snapper_snapshot=None, btrfs_root_is_subvolume=None, btrfs_set_default_volume=None, btrfs_root_is_readonly_snapshot=None, compressed=None, devicepersistency=None, editbootconfig=None, editbootinstall=None, filesystem=None, flags=None, enclave_format=None, format=None, formatoptions=None, fsmountoptions=None, fscreateoptions=None, squashfscompression=None, erofscompression=None, gcelicense=None, hybridpersistent=None, hybridpersistent_filesystem=None, gpt_hybrid_mbr=None, force_mbr=None, initrd_system=None, image=None, metadata_path=None, installboot=None, install_continue_on_timeout=None, installprovidefailsafe=None, installiso=None, installstick=None, installpxe=None, mediacheck=None, kernelcmdline=None, luks=None, luks_version=None, luksOS=None, luks_randomize=None, luks_pbkdf=None, mdraid=None, overlayroot=None, overlayroot_write_partition=None, overlayroot_readonly_filesystem=None, overlayroot_readonly_partsize=None, verity_blocks=None, embed_verity_metadata=None, standalone_integrity=None, embed_integrity_metadata=None, integrity_legacy_hmac=None, integrity_metadata_key_description=None, integrity_keyfile=None, primary=None, ramonly=None, rootfs_label=None, spare_part=None, spare_part_mountpoint=None, spare_part_fs=None, spare_part_fs_attributes=None, spare_part_is_last=None, target_blocksize=None, target_removable=None, selinux_policy=None, vga=None, vhdfixedtag=None, volid=None, application_id=None, wwid_wait_timeout=None, derived_from=None, delta_root=None, provide_system_files=None, require_system_files=None, ensure_empty_tmpdirs=None, xen_server=None, publisher=None, disk_start_sector=None, root_clone=None, boot_clone=None, bundle_format=None, bootloader=None, containerconfig=None, machine=None, oemconfig=None, size=None, systemdisk=None, partitions=None, vagrantconfig=None, installmedia=None, luksformat=None):
         self.original_tagname_ = None
         self.boot = _cast(None, boot)
         self.bootfilesystem = _cast(None, bootfilesystem)
@@ -3419,6 +3419,7 @@ class type_(GeneratedsSuper):
         self.mdraid = _cast(None, mdraid)
         self.overlayroot = _cast(bool, overlayroot)
         self.overlayroot_write_partition = _cast(bool, overlayroot_write_partition)
+        self.overlayroot_readonly_filesystem = _cast(None, overlayroot_readonly_filesystem)
         self.overlayroot_readonly_partsize = _cast(int, overlayroot_readonly_partsize)
         self.verity_blocks = _cast(None, verity_blocks)
         self.embed_verity_metadata = _cast(bool, embed_verity_metadata)
@@ -3663,6 +3664,8 @@ class type_(GeneratedsSuper):
     def set_overlayroot(self, overlayroot): self.overlayroot = overlayroot
     def get_overlayroot_write_partition(self): return self.overlayroot_write_partition
     def set_overlayroot_write_partition(self, overlayroot_write_partition): self.overlayroot_write_partition = overlayroot_write_partition
+    def get_overlayroot_readonly_filesystem(self): return self.overlayroot_readonly_filesystem
+    def set_overlayroot_readonly_filesystem(self, overlayroot_readonly_filesystem): self.overlayroot_readonly_filesystem = overlayroot_readonly_filesystem
     def get_overlayroot_readonly_partsize(self): return self.overlayroot_readonly_partsize
     def set_overlayroot_readonly_partsize(self, overlayroot_readonly_partsize): self.overlayroot_readonly_partsize = overlayroot_readonly_partsize
     def get_verity_blocks(self): return self.verity_blocks
@@ -3982,6 +3985,9 @@ class type_(GeneratedsSuper):
         if self.overlayroot_write_partition is not None and 'overlayroot_write_partition' not in already_processed:
             already_processed.add('overlayroot_write_partition')
             outfile.write(' overlayroot_write_partition="%s"' % self.gds_format_boolean(self.overlayroot_write_partition, input_name='overlayroot_write_partition'))
+        if self.overlayroot_readonly_filesystem is not None and 'overlayroot_readonly_filesystem' not in already_processed:
+            already_processed.add('overlayroot_readonly_filesystem')
+            outfile.write(' overlayroot_readonly_filesystem=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.overlayroot_readonly_filesystem), input_name='overlayroot_readonly_filesystem')), ))
         if self.overlayroot_readonly_partsize is not None and 'overlayroot_readonly_partsize' not in already_processed:
             already_processed.add('overlayroot_readonly_partsize')
             outfile.write(' overlayroot_readonly_partsize="%s"' % self.gds_format_integer(self.overlayroot_readonly_partsize, input_name='overlayroot_readonly_partsize'))
@@ -4473,6 +4479,11 @@ class type_(GeneratedsSuper):
                 self.overlayroot_write_partition = False
             else:
                 raise_parse_error(node, 'Bad boolean attribute')
+        value = find_attr_value_('overlayroot_readonly_filesystem', node)
+        if value is not None and 'overlayroot_readonly_filesystem' not in already_processed:
+            already_processed.add('overlayroot_readonly_filesystem')
+            self.overlayroot_readonly_filesystem = value
+            self.overlayroot_readonly_filesystem = ' '.join(self.overlayroot_readonly_filesystem.split())
         value = find_attr_value_('overlayroot_readonly_partsize', node)
         if value is not None and 'overlayroot_readonly_partsize' not in already_processed:
             already_processed.add('overlayroot_readonly_partsize')
diff --git a/kiwi/xml_state.py b/kiwi/xml_state.py
index 7653be28..88996aed 100644
--- a/kiwi/xml_state.py
+++ b/kiwi/xml_state.py
@@ -2110,7 +2110,7 @@ class XMLState:
         if selected_system_disk and selected_system_disk.get_preferlvm():
             # LVM volume management is preferred, use it
             volume_management = 'lvm'
-        elif selected_filesystem in volume_filesystems:
+        elif selected_filesystem in volume_filesystems and selected_system_disk:
             # specified filesystem has its own volume management system
             volume_management = selected_filesystem
         elif selected_system_disk:


Test patch:
diff --git a/build-tests/x86/tumbleweed/test-image-overlayroot/config.sh b/build-tests/x86/tumbleweed/test-image-overlayroot/config.sh
index 48be438e..a6256671 100644
--- a/build-tests/x86/tumbleweed/test-image-overlayroot/config.sh
+++ b/build-tests/x86/tumbleweed/test-image-overlayroot/config.sh
@@ -1,66 +1,52 @@
 #!/bin/bash
-#================
-# FILE          : config.sh
-#----------------
-# PROJECT       : OpenSuSE KIWI Image System
-# COPYRIGHT     : (c) 2006 SUSE LINUX Products GmbH. All rights reserved
-#               :
-# AUTHOR        : Marcus Schaefer <ms@suse.de>
-#               :
-# BELONGS TO    : Operating System images
-#               :
-# DESCRIPTION   : configuration script for SUSE based
-#               : operating systems
-#               :
-#               :
-# STATUS        : BETA
-#----------------
-#======================================
-# Functions...
-#--------------------------------------
-test -f /.kconfig && . /.kconfig
-test -f /.profile && . /.profile
+set -ex
+
+declare kiwi_iname=${kiwi_iname}
+declare kiwi_profiles=${kiwi_profiles}
 
 #======================================
 # Greeting...
 #--------------------------------------
 echo "Configure image: [$kiwi_iname]..."
 
-#======================================
-# Setup baseproduct link
-#--------------------------------------
-suseSetupProduct
-
 #======================================
 # Activate services
 #--------------------------------------
-suseInsertService sshd
+systemctl enable sshd
 
 #======================================
-# Setup default target, multi-user
+# kernel links
 #--------------------------------------
-baseSetRunlevel 3
-
-# For image tests with an extra boot partition the
-# kernel must not be a symlink to another area of
-# the filesystem. Latest changes on SUSE changed the
-# layout of the kernel which breaks every image with
-# an extra boot partition
-#
-# All of the following is more than a hack and I
-# don't like it all
-#
-# Complains and discussions about this please with
-# the SUSE kernel team as we in kiwi can just live
-# with the consequences of this change
-#
-pushd /
+for profile in ${kiwi_profiles//,/ }; do
+    if [ "${profile}" = "grub_verity_erofs" ]; then
+        # For image tests with an extra boot partition the
+        # kernel must not be a symlink to another area of
+        # the filesystem. Latest changes on SUSE changed the
+        # layout of the kernel which breaks every image with
+        # an extra boot partition
+        #
+        # All of the following is more than a hack and I
+        # don't like it all
+        #
+        # Complains and discussions about this please with
+        # the SUSE kernel team as we in kiwi can just live
+        # with the consequences of this change
+        #
+        pushd /
 
-for file in /boot/* /boot/.*; do
-    if [ -L ${file} ];then
-        link_target=$(readlink ${file})
-        if [[ ${link_target} =~ usr/lib/modules ]];then
-            mv ${link_target} ${file}
-        fi
+        for file in /boot/* /boot/.*; do
+            if [ -L ${file} ];then
+                link_target=$(readlink ${file})
+                if [[ ${link_target} =~ usr/lib/modules ]];then
+                    mv ${link_target} ${file}
+                fi
+            fi
+        done
     fi
 done
+
+#======================================
+# Include erofs module
+#--------------------------------------
+# remove from blacklist
+rm -f /usr/lib/modprobe.d/60-blacklist_fs-erofs.conf
diff --git a/test/unit/bootloader/config/base_test.py b/test/unit/bootloader/config/base_test.py
index a5395e52..4f21136c 100644
--- a/test/unit/bootloader/config/base_test.py
+++ b/test/unit/bootloader/config/base_test.py
@@ -145,6 +145,22 @@ class TestBootLoaderConfigBase:
             '/dev/myroot'
         ) == 'root=overlay:MAPPER=luks'
 
+    @patch('kiwi.xml_parse.type_.get_kernelcmdline')
+    def test_get_boot_cmdline_root_overlay_verity(self, mock_cmdline):
+        mock_cmdline.return_value = ''
+        self.state.build_type.get_overlayroot = Mock(
+            return_value=True
+        )
+        self.state.build_type.get_verity_blocks = Mock(
+            return_value=42
+        )
+        self.state.get_luks_credentials = Mock(
+            return_value=None
+        )
+        assert self.bootloader.get_boot_cmdline(
+            '/dev/myroot'
+        ) == 'root=overlay:MAPPER=verityroot'
+
     @patch('kiwi.xml_parse.type_.get_initrd_system')
     @patch('kiwi.bootloader.config.base.BlockID')
     def test_get_boot_cmdline_initrd_system_is_dracut(
@@ -339,6 +355,7 @@ class TestBootLoaderConfigBase:
     @patch('kiwi.bootloader.config.base.MountManager')
     def test_mount_system_s390(self, mock_MountManager):
         tmp_mount = MagicMock()
+        etc_kernel_mount = MagicMock()
         proc_mount = MagicMock()
         sys_mount = MagicMock()
         dev_mount = MagicMock()
@@ -349,7 +366,8 @@ class TestBootLoaderConfigBase:
         boot_mount.device = 'bootdev'
 
         mount_managers = [
-            proc_mount, sys_mount, dev_mount, tmp_mount, boot_mount, root_mount
+            proc_mount, sys_mount, dev_mount, tmp_mount,
+            etc_kernel_mount, boot_mount, root_mount
         ]
 
         def mount_managers_effect(**args):
@@ -372,10 +390,15 @@ class TestBootLoaderConfigBase:
 
     @patch('kiwi.bootloader.config.base.MountManager')
     @patch('kiwi.bootloader.config.base.SystemSetup')
-    def test_mount_system(self, mock_SystemSetup, mock_MountManager):
+    @patch('os.path.exists')
+    def test_mount_system(
+        self, mock_os_path_exists, mock_SystemSetup, mock_MountManager
+    ):
+        mock_os_path_exists.return_value = True
         setup = Mock()
         mock_SystemSetup.return_value = setup
         tmp_mount = MagicMock()
+        etc_kernel_mount = MagicMock()
         proc_mount = MagicMock()
         sys_mount = MagicMock()
         dev_mount = MagicMock()
@@ -389,8 +412,8 @@ class TestBootLoaderConfigBase:
         volume_mount = MagicMock()
 
         mount_managers = [
-            proc_mount, sys_mount, dev_mount, tmp_mount, volume_mount,
-            efi_mount, boot_mount, root_mount
+            proc_mount, sys_mount, dev_mount, tmp_mount, etc_kernel_mount,
+            volume_mount, efi_mount, boot_mount, root_mount
         ]
 
         def mount_managers_effect(**args):
@@ -414,6 +437,7 @@ class TestBootLoaderConfigBase:
                 call(device='efidev', mountpoint='root_mount_point/boot/efi'),
                 call(device='device', mountpoint='root_mount_point/boot/grub2'),
                 call(device='/tmp', mountpoint='root_mount_point/tmp'),
+                call(device='efidev', mountpoint='root_mount_point/etc/kernel'),
                 call(device='/dev', mountpoint='root_mount_point/dev'),
                 call(device='/proc', mountpoint='root_mount_point/proc'),
                 call(device='/sys', mountpoint='root_mount_point/sys')
@@ -434,6 +458,7 @@ class TestBootLoaderConfigBase:
 
         volume_mount.umount.assert_called_once_with()
         tmp_mount.umount.assert_called_once_with()
+        etc_kernel_mount.umount.assert_called_once_with()
         dev_mount.umount.assert_called_once_with()
         proc_mount.umount.assert_called_once_with()
         sys_mount.umount.assert_called_once_with()
diff --git a/test/unit/builder/disk_test.py b/test/unit/builder/disk_test.py
index 8716e6ec..683a93d1 100644
--- a/test/unit/builder/disk_test.py
+++ b/test/unit/builder/disk_test.py
@@ -686,13 +686,94 @@ class TestDiskBuilder:
     @patch('kiwi.builder.disk.ImageSystem')
     @patch('kiwi.builder.disk.VeritySetup')
     @patch('kiwi.builder.disk.Temporary.new_file')
+    @patch('os.access')
+    def test_create_disk_standard_root_with_dracut_initrd_readonly_root(
+        self, mock_os_access, mock_Temporary_new_file, mock_VeritySetup,
+        mock_ImageSystem, mock_SystemSetup, mock_os_path_getsize,
+        mock_path, mock_grub_dir, mock_command, mock_rand, mock_fs,
+        mock_LoopDevice, mock_create_boot_loader_config,
+        mock_Disk
+    ):
+        mock_os_access.return_value = False
+        system = Mock()
+        system.mountpoint.return_value = 'diskroot'
+        mock_ImageSystem.return_value.__enter__.return_value = system
+        disk = self._get_disk_instance()
+        mock_Disk.return_value.__enter__.return_value = disk
+        bootloader_config = Mock()
+        bootloader_config.get_boot_cmdline = Mock(
+            return_value='boot_cmdline'
+        )
+        mock_create_boot_loader_config.return_value.__enter__.return_value = \
+            bootloader_config
+        loop_provider = Mock()
+        mock_LoopDevice.return_value.__enter__.return_value = loop_provider
+        tempfile = Mock()
+        tempfile.name = 'tempfile'
+        mock_Temporary_new_file.return_value = tempfile
+        mock_os_path_getsize.return_value = 42
+        self.boot_image_task.get_boot_names.return_value = self.boot_names_type(
+            kernel_name='vmlinuz-1.2.3-default',
+            initrd_name='initramfs-1.2.3.img'
+        )
+        mock_path.return_value = True
+        mock_rand.return_value = 15
+        filesystem = MagicMock()
+        mock_fs.return_value = filesystem
+        self.firmware.get_legacy_bios_partition_size.return_value = 2
+        self.disk_builder.root_filesystem_verity_blocks = 10
+        self.disk_builder.root_filesystem_embed_verity_metadata = True
+        self.disk_builder.root_filesystem_is_overlay = False
+        self.disk_builder.volume_manager_name = None
+        self.disk_builder.initrd_system = 'dracut'
+        self.disk_builder.bootloader = 'systemd_boot'
+        self.setup.script_exists.return_value = True
+        disk_system = Mock()
+        mock_SystemSetup.return_value = disk_system
+
+        m_open = mock_open()
+        with patch('builtins.open', m_open, create=True):
+            self.disk_builder.create_disk()
+
+        assert mock_command.call_args_list == [
+            call(['cp', 'root_dir/recovery.partition.size', 'boot_dir']),
+            call(['mv', 'initrd', 'root_dir/boot/initramfs-1.2.3.img']),
+            call(['blockdev', '--getsize64', '/dev/root-device']),
+            call(['dd', 'if=tempfile', 'of=/dev/root-device']),
+            call(
+                [
+                    'blkid', '/dev/readonly-root-device',
+                    '-s', 'UUID', '-o', 'value'
+                ]
+            ),
+            call(['mv', 'initrd', 'diskroot/boot/efi/os/initramfs-1.2.3.img'])
+        ]
+
+    @patch('kiwi.builder.disk.Disk')
+    @patch('kiwi.builder.disk.create_boot_loader_config')
+    @patch('kiwi.builder.disk.LoopDevice')
+    @patch('kiwi.builder.disk.FileSystem.new')
+    @patch('random.randrange')
+    @patch('kiwi.builder.disk.Command.run')
+    @patch('kiwi.builder.disk.Defaults.get_grub_boot_directory_name')
+    @patch('os.path.exists')
+    @patch('os.path.getsize')
+    @patch('kiwi.builder.disk.SystemSetup')
+    @patch('kiwi.builder.disk.ImageSystem')
+    @patch('kiwi.builder.disk.VeritySetup')
+    @patch('kiwi.builder.disk.Temporary.new_file')
+    @patch('os.access')
     def test_create_disk_standard_root_with_dracut_initrd(
-        self, mock_Temporary_new_file, mock_VeritySetup,
+        self, mock_os_access, mock_Temporary_new_file, mock_VeritySetup,
         mock_ImageSystem, mock_SystemSetup, mock_os_path_getsize,
         mock_path, mock_grub_dir, mock_command, mock_rand, mock_fs,
         mock_LoopDevice, mock_create_boot_loader_config,
         mock_Disk
     ):
+        mock_os_access.return_value = True
+        system = Mock()
+        system.mountpoint.return_value = 'diskroot'
+        mock_ImageSystem.return_value.__enter__.return_value = system
         disk = self._get_disk_instance()
         mock_Disk.return_value.__enter__.return_value = disk
         bootloader_config = Mock()
@@ -814,20 +895,30 @@ class TestDiskBuilder:
             call('boot_dir/config.partids', 'w'),
             call('root_dir/boot/mbrid', 'w'),
             call('boot_dir/config.bootoptions', 'w'),
-            call('/dev/some-loop', 'wb')
-        ]
-        assert m_open.return_value.write.call_args_list == [
-            call('kiwi_BootPart="1"\n'),
-            call('kiwi_RootPart="1"\n'),
-            call('0x0f0f0f0f\n'),
-            call('boot_cmdline\n'),
-            call(bytes(b'\x0f\x0f\x0f\x0f'))
+            call('/dev/some-loop', 'wb'),
+            call('root_dir/etc/veritytab', 'w')
         ]
+        m_open.return_value.write.assert_has_calls(
+            [
+                call('kiwi_BootPart="1"\n'),
+                call('kiwi_RootPart="1"\n'),
+                call('0x0f0f0f0f\n'),
+                call('boot_cmdline\n'),
+                call(bytes(b'\x0f\x0f\x0f\x0f')),
+            ]
+        )
         assert mock_command.call_args_list == [
             call(['cp', 'root_dir/recovery.partition.size', 'boot_dir']),
             call(['mv', 'initrd', 'root_dir/boot/initramfs-1.2.3.img']),
             call(['blockdev', '--getsize64', '/dev/root-device']),
-            call(['dd', 'if=tempfile', 'of=/dev/root-device'])
+            call(['dd', 'if=tempfile', 'of=/dev/root-device']),
+            call(
+                [
+                    'blkid', '/dev/readonly-root-device',
+                    '-s', 'UUID', '-o', 'value'
+                ]
+            ),
+            call(['mv', 'initrd', 'diskroot/boot/initramfs-1.2.3.img'])
         ]
         self.block_operation.get_blkid.assert_has_calls(
             [call('PARTUUID')]
@@ -840,7 +931,8 @@ class TestDiskBuilder:
         )
         assert self.boot_image_task.include_file.call_args_list == [
             call('/config.partids'),
-            call('/recovery.partition.size')
+            call('/recovery.partition.size'),
+            call(filename='/etc/veritytab', delete_after_include=True)
         ]
         self.boot_image_task.include_module.assert_called_once_with(
             'kiwi-repart'
@@ -902,7 +994,6 @@ class TestDiskBuilder:
     @patch('kiwi.builder.disk.LoopDevice')
     @patch('kiwi.builder.disk.DeviceProvider')
     @patch('kiwi.builder.disk.FileSystem.new')
-    @patch('kiwi.builder.disk.FileSystemSquashFs')
     @patch('kiwi.builder.disk.Command.run')
     @patch('kiwi.builder.disk.Defaults.get_grub_boot_directory_name')
     @patch('os.path.exists')
@@ -914,7 +1005,7 @@ class TestDiskBuilder:
     def test_create_disk_standard_root_is_overlay(
         self, mock_MountManager, mock_BlockID, mock_rand, mock_temp,
         mock_getsize, mock_exists, mock_grub_dir, mock_command,
-        mock_squashfs, mock_fs, mock_DeviceProvider,
+        mock_fs, mock_DeviceProvider,
         mock_LoopDevice, mock_create_boot_loader_config,
         mock_Disk, mock_Path
     ):
@@ -940,8 +1031,12 @@ class TestDiskBuilder:
         self.disk_builder.integrity = True
         self.disk_builder.root_filesystem_embed_integrity_metadata = True
         self.disk_builder.volume_manager_name = None
-        squashfs = Mock()
-        mock_squashfs.return_value.__enter__.return_value = squashfs
+        fs = Mock()
+        fs.veritysetup.verity_hash_offset = 'Some'
+        fs.veritysetup.verity_dict = {
+            'Roothash': 'Some'
+        }
+        mock_fs.return_value.__enter__.return_value = fs
         mock_getsize.return_value = 1048576
         tempfile = Mock()
         tempfile.name = 'kiwi-tempname'
@@ -954,27 +1049,28 @@ class TestDiskBuilder:
         with patch('builtins.open', m_open, create=True):
             self.disk_builder.create_disk()
 
-        assert mock_squashfs.call_args_list == [
-            call(
-                custom_args={'compression': None},
-                device_provider=mock_DeviceProvider.return_value,
-                root_dir='root_dir'
-            ), call(
-                custom_args={'compression': None},
-                device_provider=mock_DeviceProvider.return_value,
-                root_dir='root_dir'
-            )
-        ]
-        assert squashfs.create_on_file.call_args_list == [
-            call(exclude=['var/cache/kiwi'], filename='kiwi-tempname'),
-            call(exclude=[
-                '.kconfig', 'run/*', 'tmp/*',
-                '.buildenv', 'var/cache/kiwi',
-                'image/*'
-            ], filename='kiwi-tempname')
-        ]
-        squashfs.create_verity_layer.assert_called_once_with(10)
-        squashfs.create_verification_metadata.assert_called_once_with(
+        mock_fs.assert_has_calls(
+            [
+                call(
+                    'squashfs',
+                    custom_args={'compression': None},
+                    device_provider=mock_DeviceProvider.return_value,
+                    root_dir='root_dir'
+                )
+            ]
+        )
+        fs.create_on_file.assert_has_calls(
+            [
+                call(exclude=['var/cache/kiwi'], filename='kiwi-tempname'),
+                call(exclude=[
+                    '.kconfig', 'run/*', 'tmp/*',
+                    '.buildenv', 'var/cache/kiwi',
+                    'image/*'
+                ], filename='kiwi-tempname')
+            ], any_order=True
+        )
+        fs.create_verity_layer.assert_called_once_with(10)
+        fs.create_verification_metadata.assert_called_once_with(
             '/dev/integrityRoot'
         )
         self.integrity_root.create_integrity_metadata.assert_called_once_with()
@@ -989,17 +1085,19 @@ class TestDiskBuilder:
         assert mock_command.call_args_list[3] == call(
             ['dd', 'if=kiwi-tempname', 'of=/dev/integrityRoot']
         )
-        assert m_open.return_value.write.call_args_list == [
-            # config.partids
-            call('kiwi_BootPart="1"\n'),
-            call('kiwi_RootPart="1"\n'),
-            # mbrid
-            call('0x0f0f0f0f\n'),
-            # config.bootoptions
-            call('boot_cmdline\n'),
-            # some-loop
-            call(b'\x0f\x0f\x0f\x0f')
-        ]
+        m_open.return_value.write.assert_has_calls(
+            [
+                # config.partids
+                call('kiwi_BootPart="1"\n'),
+                call('kiwi_RootPart="1"\n'),
+                # mbrid
+                call('0x0f0f0f0f\n'),
+                # config.bootoptions
+                call('boot_cmdline\n'),
+                # some-loop
+                call(b'\x0f\x0f\x0f\x0f')
+            ], any_order=True
+        )
         assert self.boot_image_task.include_module.call_args_list == [
             call('kiwi-overlay'), call('kiwi-repart')
         ]
diff --git a/test/unit/runtime_checker_test.py b/test/unit/runtime_checker_test.py
index fb800a29..b4c40b35 100644
--- a/test/unit/runtime_checker_test.py
+++ b/test/unit/runtime_checker_test.py
@@ -348,17 +348,6 @@ class TestRuntimeChecker:
             runtime_checker.\
                 check_dracut_module_for_disk_overlay_in_package_list()
 
-    def test_check_efi_mode_for_disk_overlay_correctly_setup(self):
-        self.xml_state.build_type.get_overlayroot = Mock(
-            return_value=True
-        )
-        self.xml_state.build_type.get_firmware = Mock(
-            return_value='uefi'
-        )
-        with raises(KiwiRuntimeError):
-            self.runtime_checker.\
-                check_efi_mode_for_disk_overlay_correctly_setup()
-
     @pytest.mark.parametrize("tool_name, tool_binary", [("isomd5sum", "implantisomd5"), ("checkmedia", "tagmedia")])
     @patch('kiwi.runtime_checker.Path.which')
     @patch('kiwi.runtime_checker.RuntimeConfig')
diff --git a/test/unit/tasks/system_build_test.py b/test/unit/tasks/system_build_test.py
index 7170f365..cf9c2668 100644
--- a/test/unit/tasks/system_build_test.py
+++ b/test/unit/tasks/system_build_test.py
@@ -156,9 +156,6 @@ class TestSystemBuildTask:
         self.runtime_checker.\
             check_dracut_module_for_oem_install_in_package_list.\
             assert_called_once_with()
-        self.runtime_checker.\
-            check_efi_mode_for_disk_overlay_correctly_setup.\
-            assert_called_once_with()
         system_prepare.setup_repositories.assert_called_once_with(
             False, ['some_key', 'some_other_key'], None
         )
diff --git a/test/unit/tasks/system_prepare_test.py b/test/unit/tasks/system_prepare_test.py
index 32259e5a..3c963140 100644
--- a/test/unit/tasks/system_prepare_test.py
+++ b/test/unit/tasks/system_prepare_test.py
@@ -142,9 +142,6 @@ class TestSystemPrepareTask:
         self.runtime_checker.\
             check_dracut_module_for_oem_install_in_package_list.\
             assert_called_once_with()
-        self.runtime_checker.\
-            check_efi_mode_for_disk_overlay_correctly_setup.\
-            assert_called_once_with()
         system_prepare.setup_repositories.assert_called_once_with(
             True, ['some_key', 'some_other_key'], None
         )
diff --git a/test/unit/utils/veritysetup_test.py b/test/unit/utils/veritysetup_test.py
index fcbae513..e2a03ef5 100644
--- a/test/unit/utils/veritysetup_test.py
+++ b/test/unit/utils/veritysetup_test.py
@@ -38,7 +38,7 @@ class TestVeritySetup:
         mock_Command_run.assert_called_once_with(
             [
                 'veritysetup', 'format', 'image_file', 'image_file',
-                '--no-superblock', '--hash-offset=4096',
+                '--hash-offset=4096',
                 '--hash-block-size=4096', '--data-blocks=10',
                 '--data-block-size=4096',
             ]
@@ -58,7 +58,7 @@ class TestVeritySetup:
         mock_Command_run.assert_called_once_with(
             [
                 'veritysetup', 'format', 'image_file', 'tempfile',
-                '--no-superblock', '--hash-block-size=4096', '--data-blocks=10',
+                '--hash-block-size=4096', '--data-blocks=10',
                 '--data-block-size=4096',
             ]
         )
@@ -82,7 +82,7 @@ class TestVeritySetup:
         mock_Command_run.assert_called_once_with(
             [
                 'veritysetup', 'format', 'image_file', 'image_file',
-                '--no-superblock', '--hash-offset=4096',
+                '--hash-offset=4096',
                 '--hash-block-size=4096'
             ]
         )
@@ -109,7 +109,7 @@ class TestVeritySetup:
             call('UUID: \n'),
             call('Root hashoffset: 4096'),
             call('\n'),
-            call('Superblock: --no-superblock'),
+            call('Superblock:'),
             call('\n')
         ]