portable: pin attached image via image-policy
When attaching images generate a policy in the portable drop-in that matches the partition types and content found while dissecting, so that it can no longer be changed later without a reattach.
This commit is contained in:
@@ -141,6 +141,11 @@
|
||||
immediately started (blocking operation unless <option>--no-block</option> is passed) and/or enabled after
|
||||
attaching the image.</para>
|
||||
|
||||
<para>When images are attached, an image policy will be generated that pins the attached image by
|
||||
the exact content that was found while attaching, so that it cannot be swapped downgrading security
|
||||
(e.g.: removing dm-verity protection) without a full reinstallation. For more details on policies, see
|
||||
<citerefentry><refentrytitle>systemd.image-policy</refentrytitle><manvolnum>7</manvolnum></citerefentry>.</para>
|
||||
|
||||
<xi:include href="vpick.xml" xpointer="image"/>
|
||||
<xi:include href="vpick.xml" xpointer="directory"/>
|
||||
<xi:include href="version-info.xml" xpointer="v239"/>
|
||||
|
||||
+109
-3
@@ -27,6 +27,7 @@
|
||||
#include "fileio.h"
|
||||
#include "fs-util.h"
|
||||
#include "glyph-util.h"
|
||||
#include "image-policy.h"
|
||||
#include "install.h"
|
||||
#include "iovec-util.h"
|
||||
#include "libmount-util.h"
|
||||
@@ -364,10 +365,12 @@ static int portable_extract_by_path(
|
||||
const ImagePolicy *image_policy,
|
||||
PortableMetadata **ret_os_release,
|
||||
Hashmap **ret_unit_files,
|
||||
ImagePolicy **ret_pinned_image_policy,
|
||||
sd_bus_error *error) {
|
||||
|
||||
_cleanup_hashmap_free_ Hashmap *unit_files = NULL;
|
||||
_cleanup_(portable_metadata_unrefp) PortableMetadata* os_release = NULL;
|
||||
_cleanup_(image_policy_freep) ImagePolicy *pinned_image_policy = NULL;
|
||||
_cleanup_(loop_device_unrefp) LoopDevice *d = NULL;
|
||||
int r;
|
||||
|
||||
@@ -397,6 +400,7 @@ static int portable_extract_by_path(
|
||||
} else if (r < 0)
|
||||
return log_debug_errno(r, "Failed to set up loopback device for %s: %m", path);
|
||||
else {
|
||||
_cleanup_(verity_settings_done) VeritySettings verity = VERITY_SETTINGS_DEFAULT;
|
||||
_cleanup_(dissected_image_unrefp) DissectedImage *m = NULL;
|
||||
_cleanup_(rmdir_and_freep) char *tmpdir = NULL;
|
||||
_cleanup_close_pair_ int seq[2] = EBADF_PAIR;
|
||||
@@ -449,6 +453,24 @@ static int portable_extract_by_path(
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = verity_settings_load(&verity, path, NULL, NULL);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to load root hash: %m");
|
||||
|
||||
r = dissected_image_load_verity_sig_partition(m, d->fd, &verity);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = dissected_image_guess_verity_roothash(m, &verity);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (ret_pinned_image_policy) {
|
||||
pinned_image_policy = image_policy_new_from_dissected(m, &verity);
|
||||
if (!pinned_image_policy)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, seq) < 0)
|
||||
return log_debug_errno(errno, "Failed to allocated SOCK_SEQPACKET socket: %m");
|
||||
|
||||
@@ -558,6 +580,9 @@ static int portable_extract_by_path(
|
||||
if (ret_os_release)
|
||||
*ret_os_release = TAKE_PTR(os_release);
|
||||
|
||||
if (ret_pinned_image_policy)
|
||||
*ret_pinned_image_policy = TAKE_PTR(pinned_image_policy);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -575,9 +600,12 @@ static int extract_image_and_extensions(
|
||||
PortableMetadata **ret_os_release,
|
||||
Hashmap **ret_unit_files,
|
||||
char ***ret_valid_prefixes,
|
||||
ImagePolicy **ret_pinned_root_image_policy,
|
||||
ImagePolicy **ret_pinned_ext_image_policy,
|
||||
sd_bus_error *error) {
|
||||
|
||||
_cleanup_free_ char *id = NULL, *id_like = NULL, *version_id = NULL, *sysext_level = NULL, *confext_level = NULL;
|
||||
_cleanup_(image_policy_freep) ImagePolicy *pinned_root_image_policy = NULL, *pinned_ext_image_policy = NULL;
|
||||
_cleanup_(portable_metadata_unrefp) PortableMetadata *os_release = NULL;
|
||||
_cleanup_ordered_hashmap_free_ OrderedHashmap *extension_images = NULL, *extension_releases = NULL;
|
||||
_cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL;
|
||||
@@ -669,6 +697,7 @@ static int extract_image_and_extensions(
|
||||
image_policy,
|
||||
&os_release,
|
||||
&unit_files,
|
||||
&pinned_root_image_policy,
|
||||
error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
@@ -700,6 +729,7 @@ static int extract_image_and_extensions(
|
||||
|
||||
ORDERED_HASHMAP_FOREACH(ext, extension_images) {
|
||||
_cleanup_(portable_metadata_unrefp) PortableMetadata *extension_release_meta = NULL;
|
||||
_cleanup_(image_policy_freep) ImagePolicy *policy = NULL;
|
||||
_cleanup_hashmap_free_ Hashmap *extra_unit_files = NULL;
|
||||
_cleanup_strv_free_ char **extension_release = NULL;
|
||||
const char *e;
|
||||
@@ -713,6 +743,7 @@ static int extract_image_and_extensions(
|
||||
image_policy,
|
||||
&extension_release_meta,
|
||||
&extra_unit_files,
|
||||
&policy,
|
||||
error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
@@ -721,6 +752,19 @@ static int extract_image_and_extensions(
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (!pinned_ext_image_policy && policy)
|
||||
pinned_ext_image_policy = TAKE_PTR(policy);
|
||||
else if (policy) {
|
||||
_cleanup_(image_policy_freep) ImagePolicy *intersected_policy = NULL;
|
||||
|
||||
/* There is a single policy for all extension images, so we need a union */
|
||||
r = image_policy_union(pinned_ext_image_policy, policy, &intersected_policy);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to merge extension image policies: %m");
|
||||
|
||||
free_and_replace(pinned_ext_image_policy, intersected_policy);
|
||||
}
|
||||
|
||||
if (!validate_extension && !ret_valid_prefixes && !ret_extension_releases)
|
||||
continue;
|
||||
|
||||
@@ -768,6 +812,10 @@ static int extract_image_and_extensions(
|
||||
*ret_unit_files = TAKE_PTR(unit_files);
|
||||
if (ret_valid_prefixes)
|
||||
*ret_valid_prefixes = TAKE_PTR(valid_prefixes);
|
||||
if (ret_pinned_root_image_policy)
|
||||
*ret_pinned_root_image_policy = TAKE_PTR(pinned_root_image_policy);
|
||||
if (ret_pinned_ext_image_policy)
|
||||
*ret_pinned_ext_image_policy = TAKE_PTR(pinned_ext_image_policy);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -808,6 +856,8 @@ int portable_extract(
|
||||
&os_release,
|
||||
&unit_files,
|
||||
ret_valid_prefixes ? &valid_prefixes : NULL,
|
||||
/* pinned_root_image_policy= */ NULL,
|
||||
/* pinned_ext_image_policy= */ NULL,
|
||||
error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
@@ -1110,6 +1160,8 @@ static int install_chroot_dropin(
|
||||
ImageType type,
|
||||
OrderedHashmap *extension_images,
|
||||
OrderedHashmap *extension_releases,
|
||||
const ImagePolicy *pinned_root_image_policy,
|
||||
const ImagePolicy *pinned_ext_image_policy,
|
||||
const PortableMetadata *m,
|
||||
const PortableMetadata *os_release,
|
||||
const char *dropin_dir,
|
||||
@@ -1152,6 +1204,18 @@ static int install_chroot_dropin(
|
||||
"LogExtraFields=PORTABLE=", base_name, "\n"))
|
||||
return -ENOMEM;
|
||||
|
||||
if (pinned_root_image_policy) {
|
||||
_cleanup_free_ char *policy_str = NULL;
|
||||
|
||||
r = image_policy_to_string(pinned_root_image_policy, /* simplify= */ true, &policy_str);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to serialize pinned image policy: %m");
|
||||
|
||||
if (!strextend(&text,
|
||||
"RootImagePolicy=", policy_str, "\n"))
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* If we have a single image then PORTABLE= will point to it, so we add
|
||||
* PORTABLE_NAME_AND_VERSION= with the os-release fields and we are done. But if we have
|
||||
* extensions, PORTABLE= will point to the image where the current unit was found in. So we
|
||||
@@ -1201,6 +1265,18 @@ static int install_chroot_dropin(
|
||||
"LogExtraFields=PORTABLE_EXTENSION=", extension_base_name, "\n"))
|
||||
return -ENOMEM;
|
||||
|
||||
if (pinned_ext_image_policy) {
|
||||
_cleanup_free_ char *policy_str = NULL;
|
||||
|
||||
r = image_policy_to_string(pinned_ext_image_policy, /* simplify= */ true, &policy_str);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to serialize pinned image policy: %m");
|
||||
|
||||
if (!strextend(&text,
|
||||
"ExtensionImagePolicy=", policy_str, "\n"))
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* Look for image/version identifiers in the extension release files. We
|
||||
* look for all possible IDs, but typically only 1 or 2 will be set, so
|
||||
* the number of fields added shouldn't be too large. We prefix the DDI
|
||||
@@ -1317,6 +1393,8 @@ static int attach_unit_file(
|
||||
ImageType type,
|
||||
OrderedHashmap *extension_images,
|
||||
OrderedHashmap *extension_releases,
|
||||
const ImagePolicy *pinned_root_image_policy,
|
||||
const ImagePolicy *pinned_ext_image_policy,
|
||||
const PortableMetadata *m,
|
||||
const PortableMetadata *os_release,
|
||||
const char *profile,
|
||||
@@ -1362,7 +1440,20 @@ static int attach_unit_file(
|
||||
* is reloaded while we are creating things here: as long as only the drop-ins exist the unit doesn't exist at
|
||||
* all for PID 1. */
|
||||
|
||||
r = install_chroot_dropin(image_path, type, extension_images, extension_releases, m, os_release, dropin_dir, flags, &chroot_dropin, changes, n_changes);
|
||||
r = install_chroot_dropin(
|
||||
image_path,
|
||||
type,
|
||||
extension_images,
|
||||
extension_releases,
|
||||
pinned_root_image_policy,
|
||||
pinned_ext_image_policy,
|
||||
m,
|
||||
os_release,
|
||||
dropin_dir,
|
||||
flags,
|
||||
&chroot_dropin,
|
||||
changes,
|
||||
n_changes);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@@ -1631,6 +1722,7 @@ int portable_attach(
|
||||
size_t *n_changes,
|
||||
sd_bus_error *error) {
|
||||
|
||||
_cleanup_(image_policy_freep) ImagePolicy *pinned_root_image_policy = NULL, *pinned_ext_image_policy = NULL;
|
||||
_cleanup_ordered_hashmap_free_ OrderedHashmap *extension_images = NULL, *extension_releases = NULL;
|
||||
_cleanup_(portable_metadata_unrefp) PortableMetadata *os_release = NULL;
|
||||
_cleanup_hashmap_free_ Hashmap *unit_files = NULL;
|
||||
@@ -1656,6 +1748,8 @@ int portable_attach(
|
||||
&os_release,
|
||||
&unit_files,
|
||||
&valid_prefixes,
|
||||
&pinned_root_image_policy,
|
||||
&pinned_ext_image_policy,
|
||||
error);
|
||||
if (r < 0)
|
||||
return r;
|
||||
@@ -1720,8 +1814,20 @@ int portable_attach(
|
||||
}
|
||||
|
||||
HASHMAP_FOREACH(item, unit_files) {
|
||||
r = attach_unit_file(&paths, image->path, image->type, extension_images, extension_releases,
|
||||
item, os_release, profile, flags, changes, n_changes);
|
||||
r = attach_unit_file(
|
||||
&paths,
|
||||
image->path,
|
||||
image->type,
|
||||
extension_images,
|
||||
extension_releases,
|
||||
pinned_root_image_policy,
|
||||
pinned_ext_image_policy,
|
||||
item,
|
||||
os_release,
|
||||
profile,
|
||||
flags,
|
||||
changes,
|
||||
n_changes);
|
||||
if (r < 0)
|
||||
return sd_bus_error_set_errnof(error, r, "Failed to attach unit '%s': %m", item->name);
|
||||
}
|
||||
|
||||
@@ -25,6 +25,10 @@ systemctl is-active minimal-app0.service
|
||||
systemctl is-active minimal-app0-foo.service
|
||||
systemctl is-active minimal-app0-bar.service && exit 1
|
||||
|
||||
# Ensure pinning by policy works
|
||||
cat /run/systemd/system.attached/minimal-app0-foo.service.d/20-portable.conf
|
||||
grep -q -F 'root=signed+squashfs:' /run/systemd/system.attached/minimal-app0-foo.service.d/20-portable.conf
|
||||
|
||||
portablectl "${ARGS[@]}" reattach --now --runtime /usr/share/minimal_1.raw minimal-app0
|
||||
|
||||
portablectl is-attached minimal-app0
|
||||
@@ -91,6 +95,9 @@ status="$(portablectl is-attached --extension app0 minimal_0)"
|
||||
grep -q -F "LogExtraFields=PORTABLE_ROOT=minimal_0.raw" /run/systemd/system.attached/app0.service.d/20-portable.conf
|
||||
grep -q -F "LogExtraFields=PORTABLE_EXTENSION=app0.raw" /run/systemd/system.attached/app0.service.d/20-portable.conf
|
||||
grep -q -F "LogExtraFields=PORTABLE_EXTENSION_NAME_AND_VERSION=app" /run/systemd/system.attached/app0.service.d/20-portable.conf
|
||||
# Ensure pinning by policy works
|
||||
grep -q -F 'RootImagePolicy=root=signed+squashfs:' /run/systemd/system.attached/app0.service.d/20-portable.conf >/dev/null
|
||||
grep -q -F 'ExtensionImagePolicy=root=signed+squashfs:' /run/systemd/system.attached/app0.service.d/20-portable.conf >/dev/null
|
||||
|
||||
portablectl "${ARGS[@]}" reattach --now --runtime --extension /tmp/app0.raw /usr/share/minimal_1.raw app0
|
||||
|
||||
|
||||
Reference in New Issue
Block a user