]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
sysext: Get verity user certs from given --root=
authorKai Lueke <kailuke@microsoft.com>
Thu, 27 Nov 2025 08:49:15 +0000 (17:49 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Sun, 4 Jan 2026 21:53:50 +0000 (06:53 +0900)
The verity user certs weren't looked up in the given --root= for
systemd-sysext which made it fail to set up extensions with a strict
image policy.
Look up verity user certs from inside the --root= when we operate on
images in it. The main use case where this matters is when the initrd
sets up the extensions for the final system and thus systemd-sysext
should do the same thing as it would do in the final system.

src/core/namespace.c
src/machine/image-dbus.c
src/machine/machined-varlink.c
src/mountfsd/mountwork.c
src/portable/portabled-image-bus.c
src/shared/discover-image.c
src/shared/discover-image.h
src/shared/dissect-image.c
src/shared/dissect-image.h
src/sysext/sysext.c
test/units/TEST-50-DISSECT.sysext.sh

index c4e2721b370c325446afefce37d10e40ff62f0cb..d75d70f1bc2f0a0d1103fc6190bc504ab4f72113 100644 (file)
@@ -2605,6 +2605,7 @@ int setup_namespace(const NamespaceParameters *p, char **reterr_path) {
 
                                 r = dissected_image_decrypt(
                                                 dissected_image,
+                                                /* root= */ NULL,
                                                 /* passphrase= */ NULL,
                                                 p->verity,
                                                 p->root_image_policy,
index 9579ae0c12aee984e555ca5413917a1ef38c6556..341c4a228df4d1c982219dd4fd6707b2609df4b0 100644 (file)
@@ -295,7 +295,7 @@ int bus_image_method_get_hostname(
         int r;
 
         if (!image->metadata_valid) {
-                r = image_read_metadata(image, &image_policy_container, m->runtime_scope);
+                r = image_read_metadata(image, /* root= */ NULL, &image_policy_container, m->runtime_scope);
                 if (r < 0)
                         return sd_bus_error_set_errnof(error, r, "Failed to read image metadata: %m");
         }
@@ -314,7 +314,7 @@ int bus_image_method_get_machine_id(
         int r;
 
         if (!image->metadata_valid) {
-                r = image_read_metadata(image, &image_policy_container, m->runtime_scope);
+                r = image_read_metadata(image, /* root= */ NULL, &image_policy_container, m->runtime_scope);
                 if (r < 0)
                         return sd_bus_error_set_errnof(error, r, "Failed to read image metadata: %m");
         }
@@ -343,7 +343,7 @@ int bus_image_method_get_machine_info(
         int r;
 
         if (!image->metadata_valid) {
-                r = image_read_metadata(image, &image_policy_container, m->runtime_scope);
+                r = image_read_metadata(image, /* root= */ NULL, &image_policy_container, m->runtime_scope);
                 if (r < 0)
                         return sd_bus_error_set_errnof(error, r, "Failed to read image metadata: %m");
         }
@@ -361,7 +361,7 @@ int bus_image_method_get_os_release(
         int r;
 
         if (!image->metadata_valid) {
-                r = image_read_metadata(image, &image_policy_container, m->runtime_scope);
+                r = image_read_metadata(image, /* root= */ NULL, &image_policy_container, m->runtime_scope);
                 if (r < 0)
                         return sd_bus_error_set_errnof(error, r, "Failed to read image metadata: %m");
         }
index 56abcb9438bc86aa39028da6b751ad30116be516..2697a87b6dd4a912c715a510bc2f33c1c3b117ac 100644 (file)
@@ -627,7 +627,7 @@ static int list_image_one_and_maybe_read_metadata(Manager *m, sd_varlink *link,
         assert(image);
 
         if (should_acquire_metadata(am) && !image->metadata_valid) {
-                r = image_read_metadata(image, &image_policy_container, m->runtime_scope);
+                r = image_read_metadata(image, /* root= */ NULL, &image_policy_container, m->runtime_scope);
                 if (r < 0 && am != ACQUIRE_METADATA_GRACEFUL)
                         return log_debug_errno(r, "Failed to read image metadata: %m");
                 if (r < 0)
index 7de8784e326d0eab8ed38992af8c633b1e3df286..78b546fcfe34f7c1cced7caa0133ce733df87f8b 100644 (file)
@@ -540,6 +540,7 @@ static int vl_method_mount_image(
 
         r = dissected_image_decrypt(
                         di,
+                        /* root= */ NULL,
                         p.password,
                         &verity,
                         use_policy,
index 6d361a8b6131aa1dbdee9168ab961ee4a8a8ae80..a9cd48f1fc0189d649179368bd12903eef1e991c 100644 (file)
@@ -61,7 +61,7 @@ int bus_image_common_get_os_release(
                 return 1;
 
         if (!image->metadata_valid) {
-                r = image_read_metadata(image, &image_policy_service, m->runtime_scope);
+                r = image_read_metadata(image, /* root= */ NULL, &image_policy_service, m->runtime_scope);
                 if (r < 0)
                         return sd_bus_error_set_errnof(error, r, "Failed to read image metadata: %m");
         }
index 9904d5491bcf0d50a7db5e81941f69d154ed6ac6..0be11229e05c2a995af62c889e2c197e31c4c9bc 100644 (file)
@@ -2027,7 +2027,7 @@ int image_setup_pool(RuntimeScope scope, ImageClass class, bool use_btrfs_subvol
         return 0;
 }
 
-int image_read_metadata(Image *i, const ImagePolicy *image_policy, RuntimeScope scope) {
+int image_read_metadata(Image *i, const char *root, const ImagePolicy *image_policy, RuntimeScope scope) {
         _cleanup_(release_lock_file) LockFile global_lock = LOCK_FILE_INIT, local_lock = LOCK_FILE_INIT;
         int r;
 
@@ -2153,6 +2153,7 @@ int image_read_metadata(Image *i, const ImagePolicy *image_policy, RuntimeScope
 
                 r = dissected_image_decrypt(
                                 m,
+                                root,
                                 /* passphrase= */ NULL,
                                 &verity,
                                 image_policy,
index 728e447cdd19a43d37fe27e72170881e2087d84a..1ed8b335aaca8ff882919107d5d5fcf4ee91584b 100644 (file)
@@ -71,7 +71,7 @@ int image_get_pool_usage(RuntimeScope scope, ImageClass class, uint64_t *ret);
 int image_get_pool_limit(RuntimeScope scope, ImageClass class, uint64_t *ret);
 int image_setup_pool(RuntimeScope scope, ImageClass class, bool use_btrfs_subvol, bool use_btrfs_quota);
 
-int image_read_metadata(Image *i, const ImagePolicy *image_policy, RuntimeScope scope);
+int image_read_metadata(Image *i, const char *root, const ImagePolicy *image_policy, RuntimeScope scope);
 
 bool image_in_search_path(RuntimeScope scope, ImageClass class, const char *root, const char *image);
 
index 1093a81ec00470e31fec31982b594476180b48ea..cc37c429f67a4efe33cdc6813cf6678a7b2fe226 100644 (file)
@@ -3093,7 +3093,7 @@ static char* dm_deferred_remove_clean(char *name) {
 }
 DEFINE_TRIVIAL_CLEANUP_FUNC(char *, dm_deferred_remove_clean);
 
-static int validate_signature_userspace(const VeritySettings *verity, DissectImageFlags flags) {
+static int validate_signature_userspace(const VeritySettings *verity, const char *root, DissectImageFlags flags) {
         int r;
 
         /* Returns > 0 if signature checks out, == 0 if not, < 0 on unexpected errors */
@@ -3138,7 +3138,7 @@ static int validate_signature_userspace(const VeritySettings *verity, DissectIma
         /* Because installing a signature certificate into the kernel chain is so messy, let's optionally do
          * userspace validation. */
 
-        r = conf_files_list_nulstr(&certs, ".crt", NULL, CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED, CONF_PATHS_NULSTR("verity.d"));
+        r = conf_files_list_nulstr(&certs, ".crt", root, CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED, CONF_PATHS_NULSTR("verity.d"));
         if (r < 0)
                 return log_debug_errno(r, "Failed to enumerate certificates: %m");
         if (strv_isempty(certs)) {
@@ -3200,6 +3200,7 @@ static int validate_signature_userspace(const VeritySettings *verity, DissectIma
 
 static int do_crypt_activate_verity(
                 struct crypt_device *cd,
+                const char *root,
                 const char *name,
                 const VeritySettings *verity,
                 DissectImageFlags flags,
@@ -3249,7 +3250,7 @@ static int do_crypt_activate_verity(
 
                 /* Preferably propagate the original kernel error, so that the fallback logic can work,
                  * as the device-mapper is finicky around concurrent activations of the same volume */
-                k = validate_signature_userspace(verity, flags);
+                k = validate_signature_userspace(verity, root, flags);
                 if (k < 0)
                         return k;
                 if (k == 0) {
@@ -3309,6 +3310,7 @@ static int verity_partition(
                 PartitionDesignator designator,
                 DissectedPartition *m, /* data partition */
                 DissectedPartition *v, /* verity partition */
+                const char *root, /* The root to get user verity certs from (for a sysext) */
                 const VeritySettings *verity,
                 DissectImageFlags flags,
                 PartitionPolicyFlags policy_flags,
@@ -3394,7 +3396,7 @@ static int verity_partition(
                         goto check; /* The device already exists. Let's check it. */
 
                 /* The symlink to the device node does not exist yet. Assume not activated, and let's activate it. */
-                r = do_crypt_activate_verity(cd, name, verity, flags, policy_flags);
+                r = do_crypt_activate_verity(cd, root, name, verity, flags, policy_flags);
                 if (r >= 0)
                         goto try_open; /* The device is activated. Let's open it. */
                 /* libdevmapper can return EINVAL when the device is already in the activation stage.
@@ -3488,7 +3490,7 @@ static int verity_partition(
                  */
                 sym_crypt_free(cd);
                 cd = NULL;
-                return verity_partition(designator, m, v, verity, flags & ~DISSECT_IMAGE_VERITY_SHARE, policy_flags, d);
+                return verity_partition(designator, m, v, root, verity, flags & ~DISSECT_IMAGE_VERITY_SHARE, policy_flags, d);
         }
 
         return log_debug_errno(SYNTHETIC_ERRNO(EBUSY), "All attempts to activate verity device %s failed.", name);
@@ -3508,6 +3510,7 @@ success:
 
 int dissected_image_decrypt(
                 DissectedImage *m,
+                const char *root, /* The root to get user verity certs from (for a sysext) */
                 const char *passphrase,
                 const VeritySettings *verity,
                 const ImagePolicy *policy,
@@ -3564,7 +3567,7 @@ int dissected_image_decrypt(
 
                 k = partition_verity_hash_of(i);
                 if (k >= 0) {
-                        r = verity_partition(i, p, m->partitions + k, verity, flags, fl, d);
+                        r = verity_partition(i, p, m->partitions + k, root, verity, flags, fl, d);
                         if (r < 0)
                                 return r;
                 }
@@ -3597,7 +3600,7 @@ int dissected_image_decrypt_interactively(
                 n--;
 
         for (;;) {
-                r = dissected_image_decrypt(m, passphrase, verity, image_policy, flags);
+                r = dissected_image_decrypt(m, /* root= */ NULL, passphrase, verity, image_policy, flags);
                 if (r >= 0)
                         return r;
                 if (r == -EKEYREJECTED)
@@ -4862,7 +4865,8 @@ int verity_dissect_and_mount(
 
                         r = dissected_image_decrypt(
                                         dissected_image,
-                                        NULL,
+                                        /* root= */ NULL,
+                                        /* passphrase= */ NULL,
                                         verity,
                                         image_policy,
                                         dissect_image_flags);
index e22f14c61b2ad0d43b143d6d3e2ee29e72cfe91a..c442d624d42480c697d7c8102acc7ac86a6851a4 100644 (file)
@@ -171,7 +171,7 @@ void dissected_image_close(DissectedImage *m);
 DissectedImage* dissected_image_unref(DissectedImage *m);
 DEFINE_TRIVIAL_CLEANUP_FUNC(DissectedImage*, dissected_image_unref);
 
-int dissected_image_decrypt(DissectedImage *m, const char *passphrase, const VeritySettings *verity, const ImagePolicy *image_policy, DissectImageFlags flags);
+int dissected_image_decrypt(DissectedImage *m, const char *root, const char *passphrase, const VeritySettings *verity, const ImagePolicy *image_policy, DissectImageFlags flags);
 int dissected_image_decrypt_interactively(DissectedImage *m, const char *passphrase, const VeritySettings *verity, const ImagePolicy *image_policy, DissectImageFlags flags);
 int dissected_image_mount(DissectedImage *m, const char *where, uid_t uid_shift, uid_t uid_range, int userns_fd, DissectImageFlags flags);
 int dissected_image_mount_and_warn(DissectedImage *m, const char *where, uid_t uid_shift, uid_t uid_range, int userns_fd, DissectImageFlags flags);
index 221f4f9f87608c83c4499d5db94d7321f81ee232..072181ea46f389348c84176ecf167d7bfe07feb7 100644 (file)
@@ -1923,7 +1923,7 @@ static int merge_subprocess(
                         if (r < 0)
                                 return r;
 
-                        r = dissected_image_decrypt(m, /* passphrase= */ NULL, &verity_settings, pick_image_policy(img), flags);
+                        r = dissected_image_decrypt(m, arg_root, /* passphrase= */ NULL, &verity_settings, pick_image_policy(img), flags);
                         if (r < 0)
                                 return r;
 
@@ -2199,7 +2199,7 @@ static int image_discover_and_read_metadata(ImageClass image_class, Hashmap **re
                 return log_error_errno(r, "Failed to discover images: %m");
 
         HASHMAP_FOREACH(img, images) {
-                r = image_read_metadata(img, image_class_info[image_class].default_image_policy, RUNTIME_SCOPE_SYSTEM);
+                r = image_read_metadata(img, arg_root, image_class_info[image_class].default_image_policy, RUNTIME_SCOPE_SYSTEM);
                 if (r < 0)
                         return log_error_errno(r, "Failed to read metadata for image %s: %m", img->name);
         }
index c9ba89398594a1293f5c84e3d0936ca535d3d40b..866e932f16c73f637f89e3d8c64d16f14ad145b6 100755 (executable)
@@ -181,6 +181,52 @@ prepare_extension_image_raw() {
     prepend_trap "rm -rf ${ext_dir@Q}.raw"
 }
 
+prepare_extension_image_raw_verity() {
+    local root=${1:-}
+    local hierarchy=${2:?}
+    local ext_dir ext_release name tmpcrt
+
+    name="test-extension"
+    ext_dir="$root/var/lib/extensions/$name"
+    ext_release="$ext_dir/usr/lib/extension-release.d/extension-release.$name"
+    tmpcrt=$(mktemp --directory "/tmp/test-sysext.crt.XXXXXXXXXX")
+
+    prepend_trap "rm -rf ${ext_dir@Q} ${ext_dir@Q}.raw '$root/etc/verity.d/test-ext.crt' '$tmpcrt'"
+
+    mkdir -p "${ext_release%/*}"
+    echo "ID=_any" >"$ext_release"
+    mkdir -p "$ext_dir/$hierarchy"
+    touch "$ext_dir$hierarchy/preexisting-file-in-extension-image"
+    tee >"$tmpcrt/verity.openssl.cnf" <<EOF
+[ req ]
+prompt = no
+distinguished_name = req_distinguished_name
+[ req_distinguished_name ]
+C = DE
+ST = Test State
+L = Test Locality
+O = Org Name
+OU = Org Unit Name
+CN = Common Name
+emailAddress = test@email.com
+EOF
+    openssl req \
+        -config "$tmpcrt/verity.openssl.cnf" \
+        -new -x509 \
+        -newkey rsa:1024 \
+        -keyout "$tmpcrt/test-ext.key" \
+        -out "$tmpcrt/test-ext.crt" \
+        -days 365 \
+        -nodes
+    systemd-repart --make-ddi=sysext \
+        --private-key="$tmpcrt/test-ext.key" --certificate="$tmpcrt/test-ext.crt" \
+        --copy-source="$ext_dir" "$ext_dir.raw"
+    rm -rf "$ext_dir"
+    mkdir -p "$root/etc/verity.d"
+    mv "$tmpcrt/test-ext.crt" "$root/etc/verity.d/"
+    rm -rf "$tmpcrt"
+}
+
 prepare_extension_mutable_dir() {
     local dir=${1:?}
 
@@ -1239,6 +1285,28 @@ extension_verify_after_merge "$fake_root" "$hierarchy" -e -h
 run_systemd_sysext "$fake_root" unmerge
 )
 
+( init_trap
+: "Check if verity user certs get loaded from --root="
+fake_root=${roots_dir:+"$roots_dir/verity-user-cert-from-root"}
+hierarchy=/opt
+
+# On OpenSUSE Tumbleweed EROFS is not supported
+if [ -e /usr/lib/modprobe.d/60-blacklist_fs-erofs.conf ]; then
+    echo >&2 "Skipping test due to missing erofs support"
+    exit 0
+fi
+
+prepare_root "$fake_root" "$hierarchy"
+prepare_extension_image_raw_verity "$fake_root" "$hierarchy"
+prepare_read_only_hierarchy "$fake_root" "$hierarchy"
+
+run_systemd_sysext "$fake_root" merge --image-policy=root=signed+absent:usr=signed+absent
+extension_verify_after_merge "$fake_root" "$hierarchy" -e -h
+
+run_systemd_sysext "$fake_root" unmerge
+extension_verify_after_unmerge "$fake_root" "$hierarchy" -h
+)
+
 } # End of run_sysext_tests