sysext: Get verity user certs from given --root=

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.
This commit is contained in:
Kai Lueke
2026-01-05 06:53:50 +09:00
committed by Yu Watanabe
parent 2c751f3420
commit fd8a1deb0b
11 changed files with 94 additions and 19 deletions
+1
View 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,
+4 -4
View 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");
}
+1 -1
View 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)
+1
View File
@@ -540,6 +540,7 @@ static int vl_method_mount_image(
r = dissected_image_decrypt(
di,
/* root= */ NULL,
p.password,
&verity,
use_policy,
+1 -1
View 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");
}
+2 -1
View 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,
+1 -1
View 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);
+12 -8
View 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);
+1 -1
View 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);
+2 -2
View 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);
}
+68
View File
@@ -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