summaryrefslogtreecommitdiff
path: root/src/shared
diff options
context:
space:
mode:
Diffstat (limited to 'src/shared')
-rw-r--r--src/shared/bus-unit-util.c5
-rw-r--r--src/shared/discover-image.c34
-rw-r--r--src/shared/discover-image.h3
-rw-r--r--src/shared/dissect-image.c428
-rw-r--r--src/shared/dissect-image.h22
-rw-r--r--src/shared/image-policy.c689
-rw-r--r--src/shared/image-policy.h97
-rw-r--r--src/shared/meson.build1
-rw-r--r--src/shared/mount-util.c10
-rw-r--r--src/shared/mount-util.h2
10 files changed, 1176 insertions, 115 deletions
diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c
index 3307691f28..1c991ae54f 100644
--- a/src/shared/bus-unit-util.c
+++ b/src/shared/bus-unit-util.c
@@ -959,7 +959,10 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con
"ProcSubset",
"NetworkNamespacePath",
"IPCNamespacePath",
- "LogNamespace"))
+ "LogNamespace",
+ "RootImagePolicy",
+ "MountImagePolicy",
+ "ExtensionImagePolicy"))
return bus_append_string(m, field, eq);
if (STR_IN_SET(field, "IgnoreSIGPIPE",
diff --git a/src/shared/discover-image.c b/src/shared/discover-image.c
index d0b3245a27..ac6a8033dd 100644
--- a/src/shared/discover-image.c
+++ b/src/shared/discover-image.c
@@ -28,6 +28,7 @@
#include "hashmap.h"
#include "hostname-setup.h"
#include "id128-util.h"
+#include "initrd-util.h"
#include "lock-util.h"
#include "log.h"
#include "loop-util.h"
@@ -73,6 +74,19 @@ static const char* const image_search_path[_IMAGE_CLASS_MAX] = {
"/usr/lib/confexts\0",
};
+/* Inside the initrd, use a slightly different set of search path (i.e. include .extra/sysext in extension
+ * search dir) */
+static const char* const image_search_path_initrd[_IMAGE_CLASS_MAX] = {
+ /* (entries that aren't listed here will get the same search path as for the non initrd-case) */
+
+ [IMAGE_EXTENSION] = "/etc/extensions\0" /* only place symlinks here */
+ "/run/extensions\0" /* and here too */
+ "/var/lib/extensions\0" /* the main place for images */
+ "/usr/local/lib/extensions\0"
+ "/usr/lib/extensions\0"
+ "/.extra/sysext\0" /* put sysext picked up by systemd-stub last, since not trusted */
+};
+
static Image *image_free(Image *i) {
assert(i);
@@ -446,6 +460,14 @@ static int image_make(
return -EMEDIUMTYPE;
}
+static const char *pick_image_search_path(ImageClass class) {
+ if (class < 0 || class >= _IMAGE_CLASS_MAX)
+ return NULL;
+
+ /* Use the initrd search path if there is one, otherwise use the common one */
+ return in_initrd() && image_search_path_initrd[class] ? image_search_path_initrd[class] : image_search_path[class];
+}
+
int image_find(ImageClass class,
const char *name,
const char *root,
@@ -461,7 +483,7 @@ int image_find(ImageClass class,
if (!image_name_is_valid(name))
return -ENOENT;
- NULSTR_FOREACH(path, image_search_path[class]) {
+ NULSTR_FOREACH(path, pick_image_search_path(class)) {
_cleanup_free_ char *resolved = NULL;
_cleanup_closedir_ DIR *d = NULL;
struct stat st;
@@ -560,7 +582,7 @@ int image_discover(
assert(class < _IMAGE_CLASS_MAX);
assert(h);
- NULSTR_FOREACH(path, image_search_path[class]) {
+ NULSTR_FOREACH(path, pick_image_search_path(class)) {
_cleanup_free_ char *resolved = NULL;
_cleanup_closedir_ DIR *d = NULL;
@@ -1138,7 +1160,7 @@ int image_set_limit(Image *i, uint64_t referenced_max) {
return btrfs_subvol_set_subtree_quota_limit(i->path, 0, referenced_max);
}
-int image_read_metadata(Image *i) {
+int image_read_metadata(Image *i, const ImagePolicy *image_policy) {
_cleanup_(release_lock_file) LockFile global_lock = LOCK_FILE_INIT, local_lock = LOCK_FILE_INIT;
int r;
@@ -1219,7 +1241,9 @@ int image_read_metadata(Image *i) {
r = dissect_loop_device(
d,
- NULL, NULL,
+ /* verity= */ NULL,
+ /* mount_options= */ NULL,
+ image_policy,
DISSECT_IMAGE_GENERIC_ROOT |
DISSECT_IMAGE_REQUIRE_ROOT |
DISSECT_IMAGE_RELAX_VAR_CHECK |
@@ -1287,7 +1311,7 @@ bool image_in_search_path(
assert(image);
- NULSTR_FOREACH(path, image_search_path[class]) {
+ NULSTR_FOREACH(path, pick_image_search_path(class)) {
const char *p, *q;
size_t k;
diff --git a/src/shared/discover-image.h b/src/shared/discover-image.h
index 342b161577..edfb1412a4 100644
--- a/src/shared/discover-image.h
+++ b/src/shared/discover-image.h
@@ -7,6 +7,7 @@
#include "sd-id128.h"
#include "hashmap.h"
+#include "image-policy.h"
#include "lock-util.h"
#include "macro.h"
#include "os-util.h"
@@ -75,7 +76,7 @@ int image_name_lock(const char *name, int operation, LockFile *ret);
int image_set_limit(Image *i, uint64_t referenced_max);
-int image_read_metadata(Image *i);
+int image_read_metadata(Image *i, const ImagePolicy *image_policy);
bool image_in_search_path(ImageClass class, const char *root, const char *image);
diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c
index a68610b80e..46c42cfc95 100644
--- a/src/shared/dissect-image.c
+++ b/src/shared/dissect-image.c
@@ -301,7 +301,99 @@ not_found:
}
#if HAVE_BLKID
-static int dissected_image_probe_filesystems(DissectedImage *m, int fd) {
+static int image_policy_may_use(
+ const ImagePolicy *policy,
+ PartitionDesignator designator) {
+
+ PartitionPolicyFlags f;
+
+ /* For each partition we find in the partition table do a first check if it may exist at all given
+ * the policy, or if it shall be ignored. */
+
+ f = image_policy_get_exhaustively(policy, designator);
+ if (f < 0)
+ return f;
+
+ if ((f & _PARTITION_POLICY_USE_MASK) == PARTITION_POLICY_ABSENT)
+ /* only flag set in policy is "absent"? then this partition may not exist at all */
+ return log_debug_errno(
+ SYNTHETIC_ERRNO(ERFKILL),
+ "Partition of designator '%s' exists, but not allowed by policy, refusing.",
+ partition_designator_to_string(designator));
+ if ((f & _PARTITION_POLICY_USE_MASK & ~PARTITION_POLICY_ABSENT) == PARTITION_POLICY_UNUSED) {
+ /* only "unused" or "unused" + "absent" are set? then don't use it */
+ log_debug("Partition of designator '%s' exists, and policy dictates to ignore it, doing so.",
+ partition_designator_to_string(designator));
+ return false; /* ignore! */
+ }
+
+ return true; /* use! */
+}
+
+static int image_policy_check_protection(
+ const ImagePolicy *policy,
+ PartitionDesignator designator,
+ PartitionPolicyFlags found_flags) {
+
+ PartitionPolicyFlags policy_flags;
+
+ /* Checks if the flags in the policy for the designated partition overlap the flags of what we found */
+
+ if (found_flags < 0)
+ return found_flags;
+
+ policy_flags = image_policy_get_exhaustively(policy, designator);
+ if (policy_flags < 0)
+ return policy_flags;
+
+ if ((found_flags & policy_flags) == 0) {
+ _cleanup_free_ char *found_flags_string = NULL, *policy_flags_string = NULL;
+
+ (void) partition_policy_flags_to_string(found_flags, /* simplify= */ true, &found_flags_string);
+ (void) partition_policy_flags_to_string(policy_flags, /* simplify= */ true, &policy_flags_string);
+
+ return log_debug_errno(SYNTHETIC_ERRNO(ERFKILL), "Partition %s discovered with policy '%s' but '%s' was required, refusing.",
+ partition_designator_to_string(designator),
+ strnull(found_flags_string), strnull(policy_flags_string));
+ }
+
+ return 0;
+}
+
+static int image_policy_check_partition_flags(
+ const ImagePolicy *policy,
+ PartitionDesignator designator,
+ uint64_t gpt_flags) {
+
+ PartitionPolicyFlags policy_flags;
+ bool b;
+
+ /* Checks if the partition flags in the policy match reality */
+
+ policy_flags = image_policy_get_exhaustively(policy, designator);
+ if (policy_flags < 0)
+ return policy_flags;
+
+ b = FLAGS_SET(gpt_flags, SD_GPT_FLAG_READ_ONLY);
+ if ((policy_flags & _PARTITION_POLICY_READ_ONLY_MASK) == (b ? PARTITION_POLICY_READ_ONLY_OFF : PARTITION_POLICY_READ_ONLY_ON))
+ return log_debug_errno(SYNTHETIC_ERRNO(ERFKILL), "Partition %s has 'read-only' flag incorrectly set (must be %s, is %s), refusing.",
+ partition_designator_to_string(designator),
+ one_zero(!b), one_zero(b));
+
+ b = FLAGS_SET(gpt_flags, SD_GPT_FLAG_GROWFS);
+ if ((policy_flags & _PARTITION_POLICY_GROWFS_MASK) == (b ? PARTITION_POLICY_GROWFS_OFF : PARTITION_POLICY_GROWFS_ON))
+ return log_debug_errno(SYNTHETIC_ERRNO(ERFKILL), "Partition %s has 'growfs' flag incorrectly set (must be %s, is %s), refusing.",
+ partition_designator_to_string(designator),
+ one_zero(!b), one_zero(b));
+
+ return 0;
+}
+
+static int dissected_image_probe_filesystems(
+ DissectedImage *m,
+ int fd,
+ const ImagePolicy *policy) {
+
int r;
assert(m);
@@ -310,6 +402,7 @@ static int dissected_image_probe_filesystems(DissectedImage *m, int fd) {
for (PartitionDesignator i = 0; i < _PARTITION_DESIGNATOR_MAX; i++) {
DissectedPartition *p = m->partitions + i;
+ PartitionPolicyFlags found_flags;
if (!p->found)
continue;
@@ -325,14 +418,34 @@ static int dissected_image_probe_filesystems(DissectedImage *m, int fd) {
return r;
}
- if (streq_ptr(p->fstype, "crypto_LUKS"))
+ if (streq_ptr(p->fstype, "crypto_LUKS")) {
m->encrypted = true;
+ found_flags = PARTITION_POLICY_ENCRYPTED; /* found this one, and its definitely encrypted */
+ } else
+ /* found it, but it's definitely not encrypted, hence mask the encrypted flag, but
+ * set all other ways that indicate "present". */
+ found_flags = PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED;
if (p->fstype && fstype_is_ro(p->fstype))
p->rw = false;
if (!p->rw)
p->growfs = false;
+
+ /* We might have learnt more about the file system now (i.e. whether it is encrypted or not),
+ * hence we need to validate this against policy again, to see if the policy still matches
+ * with this new information. Note that image_policy_check_protection() will check for
+ * overlap between what's allowed in the policy and what we pass as 'found_policy' here. In
+ * the unencrypted case we thus might pass an overly unspecific mask here (i.e. unprotected
+ * OR verity OR signed), but that's fine since the earlier policy check already checked more
+ * specific which of those three cases where OK. Keep in mind that this function here only
+ * looks at specific partitions (and thus can only deduce encryption or not) but not the
+ * overall partition table (and thus cannot deduce verity or not). The earlier dissection
+ * checks already did the relevant checks that look at the whole partition table, and
+ * enforced policy there as needed. */
+ r = image_policy_check_protection(policy, i, found_flags);
+ if (r < 0)
+ return r;
}
return 0;
@@ -363,9 +476,7 @@ static void check_partition_flags(
log_debug("Unexpected partition flag %llu set on %s!", bit, node);
}
}
-#endif
-#if HAVE_BLKID
static int dissected_image_new(const char *path, DissectedImage **ret) {
_cleanup_(dissected_image_unrefp) DissectedImage *m = NULL;
_cleanup_free_ char *name = NULL;
@@ -543,6 +654,7 @@ static int dissect_image(
const char *devname,
const VeritySettings *verity,
const MountOptions *mount_options,
+ const ImagePolicy *policy,
DissectImageFlags flags) {
sd_id128_t root_uuid = SD_ID128_NULL, root_verity_uuid = SD_ID128_NULL;
@@ -572,7 +684,11 @@ static int dissect_image(
* Returns -ENOPKG if no suitable partition table or file system could be found.
* Returns -EADDRNOTAVAIL if a root hash was specified but no matching root/verity partitions found.
* Returns -ENXIO if we couldn't find any partition suitable as root or /usr partition
- * Returns -ENOTUNIQ if we only found multiple generic partitions and thus don't know what to do with that */
+ * Returns -ENOTUNIQ if we only found multiple generic partitions and thus don't know what to do with that
+ * Returns -ERFKILL if image doesn't match image policy
+ * Returns -EBADR if verity data was provided externally for an image that has a GPT partition table (i.e. is not just a naked fs)
+ * Returns -EPROTONOSUPPORT if DISSECT_IMAGE_ADD_PARTITION_DEVICES is set but the block device does not have partition logic enabled
+ * Returns -ENOMSG if we didn't find a single usable partition (and DISSECT_IMAGE_REFUSE_EMPTY is set) */
uint64_t diskseq = m->loop ? m->loop->diskseq : 0;
@@ -650,6 +766,34 @@ static int dissect_image(
const char *fstype = NULL, *options = NULL, *suuid = NULL;
_cleanup_close_ int mount_node_fd = -EBADF;
sd_id128_t uuid = SD_ID128_NULL;
+ PartitionPolicyFlags found_flags;
+ bool encrypted;
+
+ /* OK, we have found a file system, that's our root partition then. */
+
+ r = image_policy_may_use(policy, PARTITION_ROOT);
+ if (r < 0)
+ return r;
+ if (r == 0) /* policy says ignore this, so we ignore it */
+ return -ENOPKG;
+
+ (void) blkid_probe_lookup_value(b, "TYPE", &fstype, NULL);
+ (void) blkid_probe_lookup_value(b, "UUID", &suuid, NULL);
+
+ encrypted = streq_ptr(fstype, "crypto_LUKS");
+
+ if (verity_settings_data_covers(verity, PARTITION_ROOT))
+ found_flags = verity->root_hash_sig ? PARTITION_POLICY_SIGNED : PARTITION_POLICY_VERITY;
+ else
+ found_flags = encrypted ? PARTITION_POLICY_ENCRYPTED : PARTITION_POLICY_UNPROTECTED;
+
+ r = image_policy_check_protection(policy, PARTITION_ROOT, found_flags);
+ if (r < 0)
+ return r;
+
+ r = image_policy_check_partition_flags(policy, PARTITION_ROOT, 0); /* we have no gpt partition flags, hence check against all bits off */
+ if (r < 0)
+ return r;
if (FLAGS_SET(flags, DISSECT_IMAGE_PIN_PARTITION_DEVICES)) {
mount_node_fd = open_partition(devname, /* is_partition = */ false, m->loop);
@@ -657,10 +801,6 @@ static int dissect_image(
return mount_node_fd;
}
- /* OK, we have found a file system, that's our root partition then. */
- (void) blkid_probe_lookup_value(b, "TYPE", &fstype, NULL);
- (void) blkid_probe_lookup_value(b, "UUID", &suuid, NULL);
-
if (fstype) {
t = strdup(fstype);
if (!t)
@@ -681,7 +821,7 @@ static int dissect_image(
return r;
m->single_file_system = true;
- m->encrypted = streq_ptr(fstype, "crypto_LUKS");
+ m->encrypted = encrypted;
m->has_verity = verity && verity->data_path;
m->verity_ready = verity_settings_data_covers(verity, PARTITION_ROOT);
@@ -1049,6 +1189,18 @@ static int dissect_image(
_cleanup_close_ int mount_node_fd = -EBADF;
const char *options = NULL;
+ r = image_policy_may_use(policy, type.designator);
+ if (r < 0)
+ return r;
+ if (r == 0) {
+ /* Policy says: ignore; Remember this fact, so that we later can distinguish between "found but ignored" and "not found at all" */
+
+ if (!m->partitions[type.designator].found)
+ m->partitions[type.designator].ignored = true;
+
+ continue;
+ }
+
if (m->partitions[type.designator].found) {
/* For most partition types the first one we see wins. Except for the
* rootfs and /usr, where we do a version compare of the label, and
@@ -1139,6 +1291,16 @@ static int dissect_image(
sd_id128_t id = SD_ID128_NULL;
const char *options = NULL;
+ r = image_policy_may_use(policy, PARTITION_XBOOTLDR);
+ if (r < 0)
+ return r;
+ if (r == 0) { /* policy says: ignore */
+ if (!m->partitions[PARTITION_XBOOTLDR].found)
+ m->partitions[PARTITION_XBOOTLDR].ignored = true;
+
+ continue;
+ }
+
/* First one wins */
if (m->partitions[PARTITION_XBOOTLDR].found)
continue;
@@ -1223,41 +1385,49 @@ static int dissect_image(
/* If we didn't find a generic node, then we can't fix this up either */
if (generic_node) {
- _cleanup_close_ int mount_node_fd = -EBADF;
- _cleanup_free_ char *o = NULL, *n = NULL;
- const char *options;
-
- if (FLAGS_SET(flags, DISSECT_IMAGE_PIN_PARTITION_DEVICES)) {
- mount_node_fd = open_partition(generic_node, /* is_partition = */ true, m->loop);
- if (mount_node_fd < 0)
- return mount_node_fd;
- }
-
- r = make_partition_devname(devname, diskseq, generic_nr, flags, &n);
+ r = image_policy_may_use(policy, PARTITION_ROOT);
if (r < 0)
return r;
+ if (r == 0)
+ /* Policy says: ignore; remember that we did */
+ m->partitions[PARTITION_ROOT].ignored = true;
+ else {
+ _cleanup_close_ int mount_node_fd = -EBADF;
+ _cleanup_free_ char *o = NULL, *n = NULL;
+ const char *options;
- options = mount_options_from_designator(mount_options, PARTITION_ROOT);
- if (options) {
- o = strdup(options);
- if (!o)
- return -ENOMEM;
- }
+ if (FLAGS_SET(flags, DISSECT_IMAGE_PIN_PARTITION_DEVICES)) {
+ mount_node_fd = open_partition(generic_node, /* is_partition = */ true, m->loop);
+ if (mount_node_fd < 0)
+ return mount_node_fd;
+ }
- assert(generic_nr >= 0);
- m->partitions[PARTITION_ROOT] = (DissectedPartition) {
- .found = true,
- .rw = generic_rw,
- .growfs = generic_growfs,
- .partno = generic_nr,
- .architecture = _ARCHITECTURE_INVALID,
- .node = TAKE_PTR(n),
- .uuid = generic_uuid,
- .mount_options = TAKE_PTR(o),
- .mount_node_fd = TAKE_FD(mount_node_fd),
- .offset = UINT64_MAX,
- .size = UINT64_MAX,
- };
+ r = make_partition_devname(devname, diskseq, generic_nr, flags, &n);
+ if (r < 0)
+ return r;
+
+ options = mount_options_from_designator(mount_options, PARTITION_ROOT);
+ if (options) {
+ o = strdup(options);
+ if (!o)
+ return -ENOMEM;
+ }
+
+ assert(generic_nr >= 0);
+ m->partitions[PARTITION_ROOT] = (DissectedPartition) {
+ .found = true,
+ .rw = generic_rw,
+ .growfs = generic_growfs,
+ .partno = generic_nr,
+ .architecture = _ARCHITECTURE_INVALID,
+ .node = TAKE_PTR(n),
+ .uuid = generic_uuid,
+ .mount_options = TAKE_PTR(o),
+ .mount_node_fd = TAKE_FD(mount_node_fd),
+ .offset = UINT64_MAX,
+ .size = UINT64_MAX,
+ };
+ }
}
}
@@ -1319,7 +1489,42 @@ static int dissect_image(
}
}
- r = dissected_image_probe_filesystems(m, fd);
+ bool any = false;
+
+ /* After we discovered all partitions let's see if the verity requirements match the policy. (Note:
+ * we don't check encryption requirements here, because we haven't probed the file system yet, hence
+ * don't know if this is encrypted or not) */
+ for (PartitionDesignator di = 0; di < _PARTITION_DESIGNATOR_MAX; di++) {
+ PartitionDesignator vi, si;
+ PartitionPolicyFlags found_flags;
+
+ any = any || m->partitions[di].found;
+
+ vi = partition_verity_of(di);
+ si = partition_verity_sig_of(di);
+
+ /* Determine the verity protection level for this partition. */
+ found_flags = m->partitions[di].found ?
+ (vi >= 0 && m->partitions[vi].found ?
+ (si >= 0 && m->partitions[si].found ? PARTITION_POLICY_SIGNED : PARTITION_POLICY_VERITY) :
+ PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED) :
+ (m->partitions[di].ignored ? PARTITION_POLICY_UNUSED : PARTITION_POLICY_ABSENT);
+
+ r = image_policy_check_protection(policy, di, found_flags);
+ if (r < 0)
+ return r;
+
+ if (m->partitions[di].found) {
+ r = image_policy_check_partition_flags(policy, di, m->partitions[di].gpt_flags);
+ if (r < 0)
+ return r;
+ }
+ }
+
+ if (!any && !FLAGS_SET(flags, DISSECT_IMAGE_ALLOW_EMPTY))
+ return -ENOMSG;
+
+ r = dissected_image_probe_filesystems(m, fd, policy);
if (r < 0)
return r;
@@ -1331,6 +1536,7 @@ int dissect_image_file(
const char *path,
const VeritySettings *verity,
const MountOptions *mount_options,
+ const ImagePolicy *image_policy,
DissectImageFlags flags,
DissectedImage **ret) {
@@ -1340,7 +1546,6 @@ int dissect_image_file(
int r;
assert(path);
- assert(ret);
fd = open(path, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
if (fd < 0)
@@ -1358,17 +1563,81 @@ int dissect_image_file(
if (r < 0)
return r;
- r = dissect_image(m, fd, path, verity, mount_options, flags);
+ r = dissect_image(m, fd, path, verity, mount_options, image_policy, flags);
if (r < 0)
return r;
- *ret = TAKE_PTR(m);
+ if (ret)
+ *ret = TAKE_PTR(m);
return 0;
#else
return -EOPNOTSUPP;
#endif
}
+static int dissect_log_error(int r, const char *name, const VeritySettings *verity) {
+ assert(name);
+
+ switch (r) {
+
+ case 0 ... INT_MAX: /* success! */
+ return r;
+
+ case -EOPNOTSUPP:
+ return log_error_errno(r, "Dissecting images is not supported, compiled without blkid support.");
+
+ case -ENOPKG:
+ return log_error_errno(r, "%s: Couldn't identify a suitable partition table or file system.", name);
+
+ case -ENOMEDIUM:
+ return log_error_errno(r, "%s: The image does not pass os-release/extension-release validation.", name);
+
+ case -EADDRNOTAVAIL:
+ return log_error_errno(r, "%s: No root partition for specified root hash found.", name);
+
+ case -ENOTUNIQ:
+ return log_error_errno(r, "%s: Multiple suitable root partitions found in image.", name);
+
+ case -ENXIO:
+ return log_error_errno(r, "%s: No suitable root partition found in image.", name);
+
+ case -EPROTONOSUPPORT:
+ return log_error_errno(r, "Device '%s' is a loopback block device with partition scanning turned off, please turn it on.", name);
+
+ case -ENOTBLK:
+ return log_error_errno(r, "%s: Image is not a block device.", name);
+
+ case -EBADR:
+ return log_error_errno(r,
+ "Combining partitioned images (such as '%s') with external Verity data (such as '%s') not supported. "
+ "(Consider setting $SYSTEMD_DISSECT_VERITY_SIDECAR=0 to disable automatic discovery of external Verity data.)",
+ name, strna(verity ? verity->data_path : NULL));
+
+ case -ERFKILL:
+ return log_error_errno(r, "%s: image does not match image policy.", name);
+
+ case -ENOMSG:
+ return log_error_errno(r, "%s: no suitable partitions found.", name);
+
+ default:
+ return log_error_errno(r, "Failed to dissect image '%s': %m", name);
+ }
+}
+
+int dissect_image_file_and_warn(
+ const char *path,
+ const VeritySettings *verity,
+ const MountOptions *mount_options,
+ const ImagePolicy *image_policy,
+ DissectImageFlags flags,
+ DissectedImage **ret) {
+
+ return dissect_log_error(
+ dissect_image_file(path, verity, mount_options, image_policy, flags, ret),
+ path,
+ verity);
+}
+
DissectedImage* dissected_image_unref(DissectedImage *m) {
if (!m)
return NULL;
@@ -3250,6 +3519,7 @@ int dissect_loop_device(
LoopDevice *loop,
const VeritySettings *verity,
const MountOptions *mount_options,
+ const ImagePolicy *image_policy,
DissectImageFlags flags,
DissectedImage **ret) {
@@ -3258,7 +3528,6 @@ int dissect_loop_device(
int r;
assert(loop);
- assert(ret);
r = dissected_image_new(loop->backing_file ?: loop->node, &m);
if (r < 0)
@@ -3267,11 +3536,13 @@ int dissect_loop_device(
m->loop = loop_device_ref(loop);
m->sector_size = m->loop->sector_size;
- r = dissect_image(m, loop->fd, loop->node, verity, mount_options, flags);
+ r = dissect_image(m, loop->fd, loop->node, verity, mount_options, image_policy, flags);
if (r < 0)
return r;
- *ret = TAKE_PTR(m);
+ if (ret)
+ *ret = TAKE_PTR(m);
+
return 0;
#else
return -EOPNOTSUPP;
@@ -3282,56 +3553,17 @@ int dissect_loop_device_and_warn(
LoopDevice *loop,
const VeritySettings *verity,
const MountOptions *mount_options,
+ const ImagePolicy *image_policy,
DissectImageFlags flags,
DissectedImage **ret) {
- const char *name;
- int r;
-
assert(loop);
- assert(loop->fd >= 0);
-
- name = ASSERT_PTR(loop->backing_file ?: loop->node);
-
- r = dissect_loop_device(loop, verity, mount_options, flags, ret);
- switch (r) {
- case -EOPNOTSUPP:
- return log_error_errno(r, "Dissecting images is not supported, compiled without blkid support.");
-
- case -ENOPKG:
- return log_error_errno(r, "%s: Couldn't identify a suitable partition table or file system.", name);
-
- case -ENOMEDIUM:
- return log_error_errno(r, "%s: The image does not pass validation.", name);
+ return dissect_log_error(
+ dissect_loop_device(loop, verity, mount_options, image_policy, flags, ret),
+ loop->backing_file ?: loop->node,
+ verity);
- case -EADDRNOTAVAIL:
- return log_error_errno(r, "%s: No root partition for specified root hash found.", name);
-
- case -ENOTUNIQ:
- return log_error_errno(r, "%s: Multiple suitable root partitions found in image.", name);
-
- case -ENXIO:
- return log_error_errno(r, "%s: No suitable root partition found in image.", name);
-
- case -EPROTONOSUPPORT:
- return log_error_errno(r, "Device '%s' is loopback block device with partition scanning turned off, please turn it on.", name);
-
- case -ENOTBLK:
- return log_error_errno(r, "%s: Image is not a block device.", name);
-
- case -EBADR:
- return log_error_errno(r,
- "Combining partitioned images (such as '%s') with external Verity data (such as '%s') not supported. "
- "(Consider setting $SYSTEMD_DISSECT_VERITY_SIDECAR=0 to disable automatic discovery of external Verity data.)",
- name, strna(verity ? verity->data_path : NULL));
-
- default:
- if (r < 0)
- return log_error_errno(r, "Failed to dissect image '%s': %m", name);
-
- return r;
- }
}
bool dissected_image_verity_candidate(const DissectedImage *image, PartitionDesignator partition_designator) {
@@ -3407,6 +3639,7 @@ const char* mount_options_from_designator(const MountOptions *options, Partition
int mount_image_privately_interactively(
const char *image,
+ const ImagePolicy *image_policy,
DissectImageFlags flags,
char **ret_directory,
int *ret_dir_fd,
@@ -3449,7 +3682,13 @@ int mount_image_privately_interactively(
if (r < 0)
return log_error_errno(r, "Failed to set up loopback device for %s: %m", image);
- r = dissect_loop_device_and_warn(d, &verity, NULL, flags, &dissected_image);
+ r = dissect_loop_device_and_warn(
+ d,
+ &verity,
+ /* mount_options= */ NULL,
+ image_policy,
+ flags,
+ &dissected_image);
if (r < 0)
return r;
@@ -3513,6 +3752,7 @@ int verity_dissect_and_mount(
const char *src,
const char *dest,
const MountOptions *options,
+ const ImagePolicy *image_policy,
const char *required_host_os_release_id,
const char *required_host_os_release_version_id,
const char *required_host_os_release_sysext_level,
@@ -3556,6 +3796,7 @@ int verity_dissect_and_mount(
loop_device,
&verity,
options,
+ image_policy,
dissect_image_flags,
&dissected_image);
/* No partition table? Might be a single-filesystem image, try again */
@@ -3564,6 +3805,7 @@ int verity_dissect_and_mount(
loop_device,
&verity,
options,
+ image_policy,
dissect_image_flags | DISSECT_IMAGE_NO_PARTITION_TABLE,
&dissected_image);
if (r < 0)
diff --git a/src/shared/dissect-image.h b/src/shared/dissect-image.h
index 2e741e8267..a55ad63d2d 100644
--- a/src/shared/dissect-image.h
+++ b/src/shared/dissect-image.h
@@ -19,6 +19,7 @@ typedef struct VeritySettings VeritySettings;
struct DissectedPartition {
bool found:1;
+ bool ignored:1;
bool rw:1;
bool growfs:1;
int partno; /* -1 if there was no partition and the images contains a file system directly */
@@ -79,6 +80,7 @@ typedef enum DissectImageFlags {
DISSECT_IMAGE_PIN_PARTITION_DEVICES = 1 << 21, /* Open dissected partitions and decrypted partitions and pin them by fd */
DISSECT_IMAGE_RELAX_SYSEXT_CHECK = 1 << 22, /* Don't insist that the extension-release file name matches the image name */
DISSECT_IMAGE_DISKSEQ_DEVNODE = 1 << 23, /* Prefer /dev/disk/by-diskseq/… device nodes */
+ DISSECT_IMAGE_ALLOW_EMPTY = 1 << 24, /* Allow that no usable partitions is present */
} DissectImageFlags;
struct DissectedImage {
@@ -133,6 +135,9 @@ struct VeritySettings {
.designator = _PARTITION_DESIGNATOR_INVALID \
}
+/* We include image-policy.h down here, since ImagePolicy wants a complete definition of PartitionDesignator first. */
+#include "image-policy.h"
+
MountOptions* mount_options_free_all(MountOptions *options);
DEFINE_TRIVIAL_CLEANUP_FUNC(MountOptions*, mount_options_free_all);
const char* mount_options_from_designator(const MountOptions *options, PartitionDesignator designator);
@@ -141,14 +146,11 @@ int probe_filesystem_full(int fd, const char *path, uint64_t offset, uint64_t si
static inline int probe_filesystem(const char *path, char **ret_fstype) {
return probe_filesystem_full(-1, path, 0, UINT64_MAX, ret_fstype);
}
-int dissect_image_file(
- const char *path,
- const VeritySettings *verity,
- const MountOptions *mount_options,
- DissectImageFlags flags,
- DissectedImage **ret);
-int dissect_loop_device(LoopDevice *loop, const VeritySettings *verity, const MountOptions *mount_options, DissectImageFlags flags, DissectedImage **ret);
-int dissect_loop_device_and_warn(LoopDevice *loop, const VeritySettings *verity, const MountOptions *mount_options, DissectImageFlags flags, DissectedImage **ret);
+
+int dissect_image_file(const char *path, const VeritySettings *verity, const MountOptions *mount_options, const ImagePolicy *image_policy, DissectImageFlags flags, DissectedImage **ret);
+int dissect_image_file_and_warn(const char *path, const VeritySettings *verity, const MountOptions *mount_options, const ImagePolicy *image_policy, DissectImageFlags flags, DissectedImage **ret);
+int dissect_loop_device(LoopDevice *loop, const VeritySettings *verity, const MountOptions *mount_options, const ImagePolicy *image_policy, DissectImageFlags flags, DissectedImage **ret);
+int dissect_loop_device_and_warn(LoopDevice *loop, const VeritySettings *verity, const MountOptions *mount_options, const ImagePolicy *image_policy, DissectImageFlags flags, DissectedImage **ret);
DissectedImage* dissected_image_unref(DissectedImage *m);
DEFINE_TRIVIAL_CLEANUP_FUNC(DissectedImage*, dissected_image_unref);
@@ -185,9 +187,9 @@ bool dissected_image_verity_candidate(const DissectedImage *image, PartitionDesi
bool dissected_image_verity_ready(const DissectedImage *image, PartitionDesignator d);
bool dissected_image_verity_sig_ready(const DissectedImage *image, PartitionDesignator d);
-int mount_image_privately_interactively(const char *path, DissectImageFlags flags, char **ret_directory, int *ret_dir_fd, LoopDevice **ret_loop_device);
+int mount_image_privately_interactively(const char *path, const ImagePolicy *image_policy, DissectImageFlags flags, char **ret_directory, int *ret_dir_fd, LoopDevice **ret_loop_device);
-int verity_dissect_and_mount(int src_fd, const char *src, const char *dest, const MountOptions *options, const char *required_host_os_release_id, const char *required_host_os_release_version_id, const char *required_host_os_release_sysext_level, const char *required_sysext_scope);
+int verity_dissect_and_mount(int src_fd, const char *src, const char *dest, const MountOptions *options, const ImagePolicy *image_policy, const char *required_host_os_release_id, const char *required_host_os_release_version_id, const char *required_host_os_release_sysext_level, const char *required_sysext_scope);
int dissect_fstype_ok(const char *fstype);
diff --git a/src/shared/image-policy.c b/src/shared/image-policy.c
new file mode 100644
index 0000000000..5baeac4c5d
--- /dev/null
+++ b/src/shared/image-policy.c
@@ -0,0 +1,689 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "alloc-util.h"
+#include "extract-word.h"
+#include "image-policy.h"
+#include "logarithm.h"
+#include "sort-util.h"
+#include "string-util.h"
+#include "strv.h"
+
+/* Rationale for the chosen syntax:
+ *
+ * → one line, so that it can be reasonably added to a shell command line, for example via `systemd-dissect
+ * --image-policy=…` or to the kernel command line via `systemd.image_policy=`.
+ *
+ * → no use of "," or ";" as separators, so that it can be included in mount/fstab-style option strings and
+ * doesn't require escaping. Instead, separators are ":", "=", "+" which should be fine both in shell
+ * command lines and in mount/fstab style option strings.
+ */
+
+static int partition_policy_compare(const PartitionPolicy *a, const PartitionPolicy *b) {
+ return CMP(ASSERT_PTR(a)->designator, ASSERT_PTR(b)->designator);
+}
+
+static PartitionPolicy* image_policy_bsearch(const ImagePolicy *policy, PartitionDesignator designator) {
+ if (!policy)
+ return NULL;
+
+ return typesafe_bsearch(
+ &(PartitionPolicy) { .designator = designator },
+ ASSERT_PTR(policy)->policies,
+ ASSERT_PTR(policy)->n_policies,
+ partition_policy_compare);
+}
+
+static PartitionPolicyFlags partition_policy_normalized_flags(const PartitionPolicy *policy) {
+ PartitionPolicyFlags flags = ASSERT_PTR(policy)->flags;
+
+ /* This normalizes the per-partition policy flags. This means if the user left some things
+ * unspecified, we'll fill in the appropriate "dontcare" policy instead. We'll also mask out bits
+ * that do not make any sense for specific partition types. */
+
+ /* If no protection flag is set, then this means all are set */
+ if ((flags & _PARTITION_POLICY_USE_MASK) == 0)
+ flags |= PARTITION_POLICY_OPEN;
+
+ /* If this is a verity or verity signature designator, then mask off all protection bits, this after
+ * all needs no protection, because it *is* the protection */
+ if (partition_verity_to_data(policy->designator) >= 0 ||
+ partition_verity_sig_to_data(policy->designator) >= 0)
+ flags &= ~(PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED);
+
+ /* if this designator has no verity concept, then mask off verity protection flags */
+ if (partition_verity_of(policy->designator) < 0)
+ flags &= ~(PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED);
+
+ if ((flags & _PARTITION_POLICY_USE_MASK) == PARTITION_POLICY_ABSENT)
+ /* If the partition must be absent, then the gpt flags don't matter */
+ flags &= ~(_PARTITION_POLICY_READ_ONLY_MASK|_PARTITION_POLICY_GROWFS_MASK);
+ else {
+ /* If the gpt flags bits are not specified, set both options for each */
+ if ((flags & _PARTITION_POLICY_READ_ONLY_MASK) == 0)
+ flags |= PARTITION_POLICY_READ_ONLY_ON|PARTITION_POLICY_READ_ONLY_OFF;
+ if ((flags & _PARTITION_POLICY_GROWFS_MASK) == 0)
+ flags |= PARTITION_POLICY_GROWFS_ON|PARTITION_POLICY_GROWFS_OFF;
+ }
+
+ return flags;
+}
+
+PartitionPolicyFlags image_policy_get(const ImagePolicy *policy, PartitionDesignator designator) {
+ PartitionDesignator data_designator = _PARTITION_DESIGNATOR_INVALID;
+ PartitionPolicy *pp;
+
+ /* No policy means: everything may be used in any mode */
+ if (!policy)
+ return partition_policy_normalized_flags(
+ &(const PartitionPolicy) {
+ .flags = PARTITION_POLICY_OPEN,
+ .designator = designator,
+ });
+
+ pp = image_policy_bsearch(policy, designator);
+ if (pp)
+ return partition_policy_normalized_flags(pp);
+
+ /* Hmm, so this didn't work, then let's see if we can derive some policy from the underlying data
+ * partition in case of verity/signature partitions */
+
+ data_designator = partition_verity_to_data(designator);
+ if (data_designator >= 0) {
+ PartitionPolicyFlags data_flags;
+
+ /* So we are asked for the policy for a verity partition, and there's no explicit policy for
+ * that case. Let's synthesize a policy from the protection setting for the underlying data
+ * partition. */
+
+ data_flags = image_policy_get(policy, data_designator);
+ if (data_flags < 0)
+ return data_flags;
+
+ /* We need verity if verity or verity with sig is requested */
+ if (!(data_flags & (PARTITION_POLICY_SIGNED|PARTITION_POLICY_VERITY)))
+ return _PARTITION_POLICY_FLAGS_INVALID;
+
+ /* If the data partition may be unused or absent, then the verity partition may too. Also, inherit the partition flags policy */
+ return partition_policy_normalized_flags(
+ &(const PartitionPolicy) {
+ .flags = PARTITION_POLICY_UNPROTECTED | (data_flags & (PARTITION_POLICY_UNUSED|PARTITION_POLICY_ABSENT)) |
+ (data_flags & _PARTITION_POLICY_PFLAGS_MASK),
+ .designator = designator,
+ });
+ }
+
+ data_designator = partition_verity_sig_to_data(designator);
+ if (data_designator >= 0) {
+ PartitionPolicyFlags data_flags;
+
+ /* Similar case as for verity partitions, but slightly more strict rules */
+
+ data_flags = image_policy_get(policy, data_designator);
+ if (data_flags < 0)
+ return data_flags;
+
+ if (!(data_flags & PARTITION_POLICY_SIGNED))
+ return _PARTITION_POLICY_FLAGS_INVALID;
+
+ return partition_policy_normalized_flags(
+ &(const PartitionPolicy) {
+ .flags = PARTITION_POLICY_UNPROTECTED | (data_flags & (PARTITION_POLICY_UNUSED|PARTITION_POLICY_ABSENT)) |
+ (data_flags & _PARTITION_POLICY_PFLAGS_MASK),
+ .designator = designator,
+ });
+ }
+
+ return _PARTITION_POLICY_FLAGS_INVALID; /* got nothing */
+}
+
+PartitionPolicyFlags image_policy_get_exhaustively(const ImagePolicy *policy, PartitionDesignator designator) {
+ PartitionPolicyFlags flags;
+
+ /* This is just like image_policy_get() but whenever there is no policy for a specific designator, we
+ * return the default policy. */
+
+ flags = image_policy_get(policy, designator);
+ if (flags < 0)
+ return partition_policy_normalized_flags(
+ &(const PartitionPolicy) {
+ .flags = image_policy_default(policy),
+ .designator = designator,
+ });
+
+ return flags;
+}
+
+static PartitionPolicyFlags policy_flag_from_string_one(const char *s) {
+ assert(s);
+
+ /* This is a bitmask (i.e. not dense), hence we don't use the "string-table.h" stuff here. */
+
+ if (streq(s, "verity"))
+ return PARTITION_POLICY_VERITY;
+ if (streq(s, "signed"))
+ return PARTITION_POLICY_SIGNED;
+ if (streq(s, "encrypted"))
+ return PARTITION_POLICY_ENCRYPTED;
+ if (streq(s, "unprotected"))
+ return PARTITION_POLICY_UNPROTECTED;
+ if (streq(s, "unused"))
+ return PARTITION_POLICY_UNUSED;
+ if (streq(s, "absent"))
+ return PARTITION_POLICY_ABSENT;
+ if (streq(s, "open")) /* shortcut alias */
+ return PARTITION_POLICY_OPEN;
+ if (streq(s, "ignore")) /* ditto */
+ return PARTITION_POLICY_IGNORE;
+ if (streq(s, "read-only-on"))
+ return PARTITION_POLICY_READ_ONLY_ON;
+ if (streq(s, "read-only-off"))
+ return PARTITION_POLICY_READ_ONLY_OFF;
+ if (streq(s, "growfs-on"))
+ return PARTITION_POLICY_GROWFS_ON;
+ if (streq(s, "growfs-off"))
+ return PARTITION_POLICY_GROWFS_OFF;
+
+ return _PARTITION_POLICY_FLAGS_INVALID;
+}
+
+PartitionPolicyFlags partition_policy_flags_from_string(const char *s) {
+ PartitionPolicyFlags flags = 0;
+ int r;
+
+ assert(s);
+
+ if (empty_or_dash(s))
+ return 0;
+
+ for (;;) {
+ _cleanup_free_ char *f = NULL;
+ PartitionPolicyFlags ff;
+
+ r = extract_first_word(&s, &f, "+", EXTRACT_DONT_COALESCE_SEPARATORS);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+
+ ff = policy_flag_from_string_one(strstrip(f));
+ if (ff < 0)
+ return -EBADRQC; /* recognizable error */
+
+ flags |= ff;
+ }
+
+ return flags;
+}
+
+static ImagePolicy* image_policy_new(size_t n_policies) {
+ ImagePolicy *p;
+
+ if (n_policies > (SIZE_MAX - offsetof(ImagePolicy, policies)) / sizeof(PartitionPolicy)) /* overflow check */
+ return NULL;
+
+ p = malloc(offsetof(ImagePolicy, policies) + sizeof(PartitionPolicy) * n_policies);
+ if (!p)
+ return NULL;
+
+ *p = (ImagePolicy) {
+ .default_flags = PARTITION_POLICY_IGNORE,
+ };
+ return p;
+}
+
+int image_policy_from_string(const char *s, ImagePolicy **ret) {
+ _cleanup_free_ ImagePolicy *p = NULL;
+ uint64_t dmask = 0;
+ ImagePolicy *t;
+ PartitionPolicyFlags symbolic_policy;
+ int r;
+
+ assert(s);
+ assert_cc(sizeof(dmask) * 8 >= _PARTITION_DESIGNATOR_MAX);
+
+ /* Recognizable errors:
+ *
+ * ENOTUNIQ → Two or more rules for the same partition
+ * EBADSLT → Unknown partition designator
+ * EBADRQC → Unknown policy flags
+ */
+
+ /* First, let's handle "symbolic" policies, i.e. "-", "*", "~" */
+ if (empty_or_dash(s))
+ /* ignore policy: everything may exist, but nothing used */
+ symbolic_policy = PARTITION_POLICY_IGNORE;
+ else if (streq(s, "*"))
+ /* allow policy: everything is allowed */
+ symbolic_policy = PARTITION_POLICY_OPEN;
+ else if (streq(s, "~"))
+ /* deny policy: nothing may exist */
+ symbolic_policy = PARTITION_POLICY_ABSENT;
+ else
+ symbolic_policy = _PARTITION_POLICY_FLAGS_INVALID;
+
+ if (symbolic_policy >= 0) {
+ if (!ret)
+ return 0;
+
+ p = image_policy_new(0);
+ if (!p)
+ return -ENOMEM;
+
+ p->default_flags = symbolic_policy;
+ *ret = TAKE_PTR(p);
+ return 0;
+ }
+
+ /* Allocate the policy at maximum size, i.e. for all designators. We might overshoot a bit, but the
+ * items are cheap, and we can return unused space to libc once we know we don't need it */
+ p = image_policy_new(_PARTITION_DESIGNATOR_MAX);
+ if (!p)
+ return -ENOMEM;
+
+ const char *q = s;
+ bool default_specified = false;
+ for (;;) {
+ _cleanup_free_ char *e = NULL, *d = NULL;
+ PartitionDesignator designator;
+ PartitionPolicyFlags flags;
+ char *f, *ds, *fs;
+
+ r = extract_first_word(&q, &e, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+
+ f = e;
+ r = extract_first_word((const char**) &f, &d, "=", EXTRACT_DONT_COALESCE_SEPARATORS);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Expected designator name followed by '='; got instead: %s", e);
+ if (!f) /* no separator? */
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Missing '=' in policy expression: %s", e);
+
+ ds = strstrip(d);
+ if (isempty(ds)) {
+ /* Not partition name? then it's the default policy */
+ if (default_specified)
+ return log_debug_errno(SYNTHETIC_ERRNO(ENOTUNIQ), "Default partition policy flags specified more than once.");
+
+ designator = _PARTITION_DESIGNATOR_INVALID;
+ default_specified = true;
+ } else {
+ designator = partition_designator_from_string(ds);
+ if (designator < 0)
+ return log_debug_errno(SYNTHETIC_ERRNO(EBADSLT), "Unknown partition designator: %s", ds); /* recognizable error */
+ if (dmask & (UINT64_C(1) << designator))
+ return log_debug_errno(SYNTHETIC_ERRNO(ENOTUNIQ), "Partition designator specified more than once: %s", ds);
+ dmask |= UINT64_C(1) << designator;
+ }
+
+ fs = strstrip(f);
+ flags = partition_policy_flags_from_string(fs);
+ if (flags == -EBADRQC)
+ return log_debug_errno(flags, "Unknown partition policy flag: %s", fs);
+ if (flags < 0)
+ return log_debug_errno(flags, "Failed to parse partition policy flags '%s': %m", fs);
+
+ if (designator < 0)
+ p->default_flags = flags;
+ else {
+ p->policies[p->n_policies++] = (PartitionPolicy) {
+ .designator = designator,
+ .flags = flags,
+ };
+ }
+ };
+
+ assert(p->n_policies <= _PARTITION_DESIGNATOR_MAX);
+
+ /* Return unused space to libc */
+ t = realloc(p, offsetof(ImagePolicy, policies) + sizeof(PartitionPolicy) * p->n_policies);
+ if (t)
+ p = t;
+
+ typesafe_qsort(p->policies, p->n_policies, partition_policy_compare);
+
+ if (ret)
+ *ret = TAKE_PTR(p);
+
+ return 0;
+}
+
+int partition_policy_flags_to_string(PartitionPolicyFlags flags, bool simplify, char **ret) {
+ _cleanup_free_ char *buf = NULL;
+ const char *l[CONST_LOG2U(_PARTITION_POLICY_MASK) + 1]; /* one string per known flag at most */
+ size_t m = 0;
+
+ assert(ret);
+
+ if (flags < 0)
+ return -EINVAL;
+
+ /* If 'simplify' is false we'll output the precise value of every single flag.
+ *
+ * If 'simplify' is true we'll try to make the output shorter, by doing the following:
+ *
+ * → we'll spell the long form "verity+signed+encrypted+unprotected+unused+absent" via its
+ * equivalent shortcut form "open" (which we happily parse btw, see above)
+ *
+ * → we'll spell the long form "unused+absent" via its shortcut "ignore" (which we are also happy
+ * to parse)
+ *
+ * → if the read-only/growfs policy flags are both set, we suppress them. this thus removes the
+ * distinction between "user explicitly declared don't care" and "we implied don't care because
+ * user didn't say anything".
+ *
+ * net result: the resulting string is shorter, but the effective policy declared that way will have
+ * the same results as the long form. */
+
+ if (simplify && (flags & _PARTITION_POLICY_USE_MASK) == PARTITION_POLICY_OPEN)
+ l[m++] = "open";
+ else if (simplify && (flags & _PARTITION_POLICY_USE_MASK) == PARTITION_POLICY_IGNORE)
+ l[m++] = "ignore";
+ else {
+ if (flags & PARTITION_POLICY_VERITY)
+ l[m++] = "verity";
+ if (flags & PARTITION_POLICY_SIGNED)
+ l[m++] = "signed";
+ if (flags & PARTITION_POLICY_ENCRYPTED)
+ l[m++] = "encrypted";
+ if (flags & PARTITION_POLICY_UNPROTECTED)
+ l[m++] = "unprotected";
+ if (flags & PARTITION_POLICY_UNUSED)
+ l[m++] = "unused";
+ if (flags & PARTITION_POLICY_ABSENT)
+ l[m++] = "absent";
+ }
+
+ if (!simplify || (!(flags & PARTITION_POLICY_READ_ONLY_ON) != !(flags & PARTITION_POLICY_READ_ONLY_OFF))) {
+ if (flags & PARTITION_POLICY_READ_ONLY_ON)
+ l[m++] = "read-only-on";
+ if (flags & PARTITION_POLICY_READ_ONLY_OFF)
+ l[m++] = "read-only-off";
+ }
+
+ if (!simplify || (!(flags & PARTITION_POLICY_GROWFS_ON) != !(flags & PARTITION_POLICY_GROWFS_OFF))) {
+ if (flags & PARTITION_POLICY_GROWFS_OFF)
+ l[m++] = "growfs-off";
+ if (flags & PARTITION_POLICY_GROWFS_ON)
+ l[m++] = "growfs-on";
+ }
+
+ if (m == 0)
+ buf = strdup("-");
+ else {
+ assert(m+1 < ELEMENTSOF(l));
+ l[m] = NULL;
+
+ buf = strv_join((char**) l, "+");
+ }
+ if (!buf)
+ return -ENOMEM;
+
+ *ret = TAKE_PTR(buf);
+ return 0;
+}
+
+static int image_policy_flags_all_match(const ImagePolicy *policy, PartitionPolicyFlags expected) {
+
+ if (expected < 0)
+ return -EINVAL;
+
+ if (image_policy_default(policy) != expected)
+ return false;
+
+ for (PartitionDesignator d = 0; d < _PARTITION_DESIGNATOR_MAX; d++) {
+ PartitionPolicyFlags f, w;
+
+ f = image_policy_get_exhaustively(policy, d);
+ if (f < 0)
+ return f;
+
+ w = partition_policy_normalized_flags(
+ &(const PartitionPolicy) {
+ .flags = expected,
+ .designator = d,
+ });
+ if (w < 0)
+ return w;
+ if (f != w)
+ return false;
+ }
+
+ return true;
+}
+
+bool image_policy_equiv_ignore(const ImagePolicy *policy) {
+ /* Checks if this is the ignore policy (or equivalent to it), i.e. everything is ignored, aka '-', aka '' */
+ return image_policy_flags_all_match(policy, PARTITION_POLICY_IGNORE);
+}
+
+bool image_policy_equiv_allow(const ImagePolicy *policy) {
+ /* Checks if this is the allow policy (or equivalent to it), i.e. everything is allowed, aka '*' */
+ return image_policy_flags_all_match(policy, PARTITION_POLICY_OPEN);
+}
+
+bool image_policy_equiv_deny(const ImagePolicy *policy) {
+ /* Checks if this is the deny policy (or equivalent to it), i.e. everything must be absent, aka '~' */
+ return image_policy_flags_all_match(policy, PARTITION_POLICY_ABSENT);
+}
+
+int image_policy_to_string(const ImagePolicy *policy, bool simplify, char **ret) {
+ _cleanup_free_ char *s = NULL;
+ int r;
+
+ assert(ret);
+
+ if (simplify) {
+ const char *fixed;
+
+ if (image_policy_equiv_allow(policy))
+ fixed = "*";
+ else if (image_policy_equiv_ignore(policy))
+ fixed = "-";
+ else if (image_policy_equiv_deny(policy))
+ fixed = "~";
+ else
+ fixed = NULL;
+
+ if (fixed) {
+ s = strdup(fixed);
+ if (!s)
+ return -ENOMEM;
+
+ *ret = TAKE_PTR(s);
+ return 0;
+ }
+ }
+
+ for (size_t i = 0; i < image_policy_n_entries(policy); i++) {
+ const PartitionPolicy *p = policy->policies + i;
+ _cleanup_free_ char *f = NULL;
+ const char *t;
+
+ assert(i == 0 || p->designator > policy->policies[i-1].designator); /* Validate perfect ordering */
+
+ assert_se(t = partition_designator_to_string(p->designator));
+
+ if (simplify) {
+ /* Skip policy entries that match the default anyway */
+ PartitionPolicyFlags df;
+
+ df = partition_policy_normalized_flags(
+ &(const PartitionPolicy) {
+ .flags = image_policy_default(policy),
+ .designator = p->designator,
+ });
+ if (df < 0)
+ return df;
+
+ if (df == p->flags)
+ continue;
+ }
+
+ r = partition_policy_flags_to_string(p->flags, simplify, &f);
+ if (r < 0)
+ return r;
+
+ if (!strextend(&s, isempty(s) ? "" : ":", t, "=", f))
+ return -ENOMEM;
+ }
+
+ if (!simplify || image_policy_default(policy) != PARTITION_POLICY_IGNORE) {
+ _cleanup_free_ char *df = NULL;
+
+ r = partition_policy_flags_to_string(image_policy_default(policy), simplify, &df);
+ if (r < 0)
+ return r;
+
+ if (!strextend(&s, isempty(s) ? "" : ":", "=", df))
+ return -ENOMEM;
+ }
+
+ if (isempty(s)) { /* no rule and default policy? then let's return "-" */
+ s = strdup("-");
+ if (!s)
+ return -ENOMEM;
+ }
+
+ *ret = TAKE_PTR(s);
+ return 0;
+}
+
+bool image_policy_equal(const ImagePolicy *a, const ImagePolicy *b) {
+ if (a == b)
+ return true;
+ if (image_policy_n_entries(a) != image_policy_n_entries(b))
+ return false;
+ if (image_policy_default(a) != image_policy_default(b))
+ return false;
+ for (size_t i = 0; i < image_policy_n_entries(a); i++) {
+ if (a->policies[i].designator != b->policies[i].designator)
+ return false;
+ if (a->policies[i].flags != b->policies[i].flags)
+ return false;
+ }
+
+ return true;
+}
+
+int image_policy_equivalent(const ImagePolicy *a, const ImagePolicy *b) {
+
+ /* The image_policy_equal() function checks if the policy is defined the exact same way. This
+ * function here instead looks at the outcome of the two policies instead. Where does this come to
+ * different results you ask? We imply some logic regarding Verity/Encryption: when no rule is
+ * defined for a verity partition we can synthesize it from the protection level of the data
+ * partition it protects. Or: any per-partition rule that is identical to the default rule is
+ * redundant, and will be recognized as such by image_policy_equivalent() but not by
+ * image_policy_equal()- */
+
+ if (image_policy_default(a) != image_policy_default(b))
+ return false;
+
+ for (PartitionDesignator d = 0; d < _PARTITION_DESIGNATOR_MAX; d++) {
+ PartitionPolicyFlags f, w;
+
+ f = image_policy_get_exhaustively(a, d);
+ if (f < 0)
+ return f;
+
+ w = image_policy_get_exhaustively(b, d);
+ if (w < 0)
+ return w;
+
+ if (f != w)
+ return false;
+ }
+
+ return true;
+}
+
+const ImagePolicy image_policy_allow = {
+ /* Allow policy */
+ .n_policies = 0,
+ .default_flags = PARTITION_POLICY_OPEN,
+};
+
+const ImagePolicy image_policy_deny = {
+ /* Allow policy */
+ .n_policies = 0,
+ .default_flags = PARTITION_POLICY_ABSENT,
+};
+
+const ImagePolicy image_policy_ignore = {
+ /* Allow policy */
+ .n_policies = 0,
+ .default_flags = PARTITION_POLICY_IGNORE,
+};
+
+const ImagePolicy image_policy_sysext = {
+ /* For system extensions, honour root file system, and /usr/ and ignore everything else. After all,
+ * we are only interested in /usr/ + /opt/ trees anyway, and that's really the only place they can
+ * be. */
+ .n_policies = 2,
+ .policies = {
+ { PARTITION_ROOT, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+ { PARTITION_USR, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+ },
+ .default_flags = PARTITION_POLICY_IGNORE,
+};
+
+const ImagePolicy image_policy_sysext_strict = {
+ /* For system extensions, requiring signing */
+ .n_policies = 2,
+ .policies = {
+ { PARTITION_ROOT, PARTITION_POLICY_SIGNED|PARTITION_POLICY_ABSENT },
+ { PARTITION_USR, PARTITION_POLICY_SIGNED|PARTITION_POLICY_ABSENT },
+ },
+ .default_flags = PARTITION_POLICY_IGNORE,
+};
+
+const ImagePolicy image_policy_container = {
+ /* For systemd-nspawn containers we use all partitions, with the exception of swap */
+ .n_policies = 8,
+ .policies = {
+ { PARTITION_ROOT, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+ { PARTITION_USR, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+ { PARTITION_HOME, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+ { PARTITION_SRV, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+ { PARTITION_ESP, PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+ { PARTITION_XBOOTLDR, PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+ { PARTITION_TMP, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+ { PARTITION_VAR, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+ },
+ .default_flags = PARTITION_POLICY_IGNORE,
+};
+
+const ImagePolicy image_policy_host = {
+ /* For the host policy we basically use everything */
+ .n_policies = 9,
+ .policies = {
+ { PARTITION_ROOT, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+ { PARTITION_USR, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+ { PARTITION_HOME, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+ { PARTITION_SRV, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+ { PARTITION_ESP, PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+ { PARTITION_XBOOTLDR, PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+ { PARTITION_SWAP, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+ { PARTITION_TMP, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+ { PARTITION_VAR, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+ },
+ .default_flags = PARTITION_POLICY_IGNORE,
+};
+
+const ImagePolicy image_policy_service = {
+ /* For RootImage= in services we skip ESP/XBOOTLDR and swap */
+ .n_policies = 6,
+ .policies = {
+ { PARTITION_ROOT, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+ { PARTITION_USR, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+ { PARTITION_HOME, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+ { PARTITION_SRV, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+ { PARTITION_TMP, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+ { PARTITION_VAR, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
+ },
+ .default_flags = PARTITION_POLICY_IGNORE,
+};
diff --git a/src/shared/image-policy.h b/src/shared/image-policy.h
new file mode 100644
index 0000000000..a5e37642af
--- /dev/null
+++ b/src/shared/image-policy.h
@@ -0,0 +1,97 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+typedef struct ImagePolicy ImagePolicy;
+
+#include "dissect-image.h"
+#include "errno-list.h"
+
+typedef enum PartitionPolicyFlags {
+ /* Not all policy flags really make sense on all partition types, see comments. But even if they
+ * don't make sense we'll parse them anyway, because maybe one day we'll add them for more partition
+ * types, too. Moreover, we allow configuring a "default" policy for all partition types for which no
+ * explicit policy is specified. It's useful if we can use policy flags in there and apply this
+ * default policy gracefully even to partition types where they don't really make too much sense
+ * on. Example: a default policy of "verity+encrypted" certainly makes sense, but for /home/
+ * partitions this gracefully degrades to "encrypted" (as we do not have a concept of verity for
+ * /home/), and so on. */
+ PARTITION_POLICY_VERITY = 1 << 0, /* must exist, activate with verity (only applies to root/usr partitions) */
+ PARTITION_POLICY_SIGNED = 1 << 1, /* must exist, activate with signed verity (only applies to root/usr partitions) */
+ PARTITION_POLICY_ENCRYPTED = 1 << 2, /* must exist, activate with LUKS encryption (applies to any data partition, but not to verity/signature partitions */
+ PARTITION_POLICY_UNPROTECTED = 1 << 3, /* must exist, activate without encryption/verity */
+ PARTITION_POLICY_UNUSED = 1 << 4, /* must exist, don't use */
+ PARTITION_POLICY_ABSENT = 1 << 5, /* must not exist */
+ PARTITION_POLICY_OPEN = PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|
+ PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_UNUSED|PARTITION_POLICY_ABSENT,
+ PARTITION_POLICY_IGNORE = PARTITION_POLICY_UNUSED|PARTITION_POLICY_ABSENT,
+ _PARTITION_POLICY_USE_MASK = PARTITION_POLICY_OPEN,
+
+ PARTITION_POLICY_READ_ONLY_OFF = 1 << 6, /* State of GPT partition flag "read-only" must be on */
+ PARTITION_POLICY_READ_ONLY_ON = 1 << 7,
+ _PARTITION_POLICY_READ_ONLY_MASK = PARTITION_POLICY_READ_ONLY_OFF|PARTITION_POLICY_READ_ONLY_ON,
+ PARTITION_POLICY_GROWFS_OFF = 1 << 8, /* State of GPT partition flag "growfs" must be on */
+ PARTITION_POLICY_GROWFS_ON = 1 << 9,
+ _PARTITION_POLICY_GROWFS_MASK = PARTITION_POLICY_GROWFS_OFF|PARTITION_POLICY_GROWFS_ON,
+ _PARTITION_POLICY_PFLAGS_MASK = _PARTITION_POLICY_READ_ONLY_MASK|_PARTITION_POLICY_GROWFS_MASK,
+
+ _PARTITION_POLICY_MASK = _PARTITION_POLICY_USE_MASK|_PARTITION_POLICY_READ_ONLY_MASK|_PARTITION_POLICY_GROWFS_MASK,
+
+ _PARTITION_POLICY_FLAGS_INVALID = -EINVAL,
+ _PARTITION_POLICY_FLAGS_ERRNO_MAX = -ERRNO_MAX, /* Ensure the whole errno range fits into this enum */
+} PartitionPolicyFlags;
+
+assert_cc((_PARTITION_POLICY_USE_MASK | _PARTITION_POLICY_PFLAGS_MASK) >= 0); /* ensure flags don't collide with errno range */
+
+typedef struct PartitionPolicy {
+ PartitionDesignator designator;
+ PartitionPolicyFlags flags;
+} PartitionPolicy;
+
+struct ImagePolicy {
+ PartitionPolicyFlags default_flags; /* for any designator not listed in the list below */
+ size_t n_policies;
+ PartitionPolicy policies[]; /* sorted by designator, hence suitable for binary search */
+};
+
+/* Default policies for various usecases */
+extern const ImagePolicy image_policy_allow;
+extern const ImagePolicy image_policy_deny;
+extern const ImagePolicy image_policy_ignore;
+extern const ImagePolicy image_policy_sysext; /* No verity required */
+extern const ImagePolicy image_policy_sysext_strict; /* Signed verity required */
+extern const ImagePolicy image_policy_container;
+extern const ImagePolicy image_policy_service;
+extern const ImagePolicy image_policy_host;
+
+PartitionPolicyFlags image_policy_get(const ImagePolicy *policy, PartitionDesignator designator);
+PartitionPolicyFlags image_policy_get_exhaustively(const ImagePolicy *policy, PartitionDesignator designator);
+
+/* We want that the NULL image policy means "everything" allowed, hence use these simple accessors to make
+ * NULL policies work reasonably */
+static inline PartitionPolicyFlags image_policy_default(const ImagePolicy *policy) {
+ return policy ? policy->default_flags : PARTITION_POLICY_OPEN;
+}
+
+static inline size_t image_policy_n_entries(const ImagePolicy *policy) {
+ return policy ? policy->n_policies : 0;
+}
+
+PartitionPolicyFlags partition_policy_flags_from_string(const char *s);
+int partition_policy_flags_to_string(PartitionPolicyFlags flags, bool simplify, char **ret);
+
+int image_policy_from_string(const char *s, ImagePolicy **ret);
+int image_policy_to_string(const ImagePolicy *policy, bool simplify, char **ret);
+
+/* Recognizes three special policies by equivalence */
+bool image_policy_equiv_ignore(const ImagePolicy *policy);
+bool image_policy_equiv_allow(const ImagePolicy *policy);
+bool image_policy_equiv_deny(const ImagePolicy *policy);
+
+bool image_policy_equal(const ImagePolicy *a, const ImagePolicy *b); /* checks if defined the same way, i.e. has literally the same ruleset */
+int image_policy_equivalent(const ImagePolicy *a, const ImagePolicy *b); /* checks if the outcome is the same, i.e. for all partitions results in the same decisions. */
+
+static inline ImagePolicy* image_policy_free(ImagePolicy *p) {
+ return mfree(p);
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(ImagePolicy*, image_policy_free);
diff --git a/src/shared/meson.build b/src/shared/meson.build
index 0f2e2d1a67..df82778f9d 100644
--- a/src/shared/meson.build
+++ b/src/shared/meson.build
@@ -81,6 +81,7 @@ shared_sources = files(
'id128-print.c',
'idn-util.c',
'ima-util.c',
+ 'image-policy.c',
'import-util.c',
'in-addr-prefix-util.c',
'install-file.c',
diff --git a/src/shared/mount-util.c b/src/shared/mount-util.c
index 1eac51b81e..edf01fe092 100644
--- a/src/shared/mount-util.c
+++ b/src/shared/mount-util.c
@@ -805,6 +805,7 @@ static int mount_in_namespace(
bool read_only,
bool make_file_or_directory,
const MountOptions *options,
+ const ImagePolicy *image_policy,
bool is_image) {
_cleanup_close_pair_ int errno_pipe_fd[2] = PIPE_EBADF;
@@ -892,7 +893,7 @@ static int mount_in_namespace(
mount_tmp_created = true;
if (is_image)
- r = verity_dissect_and_mount(chased_src_fd, chased_src_path, mount_tmp, options, NULL, NULL, NULL, NULL);
+ r = verity_dissect_and_mount(chased_src_fd, chased_src_path, mount_tmp, options, image_policy, NULL, NULL, NULL, NULL);
else
r = mount_follow_verbose(LOG_DEBUG, FORMAT_PROC_FD_PATH(chased_src_fd), mount_tmp, NULL, MS_BIND, NULL);
if (r < 0)
@@ -1042,7 +1043,7 @@ int bind_mount_in_namespace(
bool read_only,
bool make_file_or_directory) {
- return mount_in_namespace(target, propagate_path, incoming_path, src, dest, read_only, make_file_or_directory, NULL, false);
+ return mount_in_namespace(target, propagate_path, incoming_path, src, dest, read_only, make_file_or_directory, /* options= */ NULL, /* image_policy= */ NULL, /* is_image= */ false);
}
int mount_image_in_namespace(
@@ -1053,9 +1054,10 @@ int mount_image_in_namespace(
const char *dest,
bool read_only,
bool make_file_or_directory,
- const MountOptions *options) {
+ const MountOptions *options,
+ const ImagePolicy *image_policy) {
- return mount_in_namespace(target, propagate_path, incoming_path, src, dest, read_only, make_file_or_directory, options, true);
+ return mount_in_namespace(target, propagate_path, incoming_path, src, dest, read_only, make_file_or_directory, options, image_policy, /* is_image=*/ true);
}
int make_mount_point(const char *path) {
diff --git a/src/shared/mount-util.h b/src/shared/mount-util.h
index 84ea4b6392..f52687828a 100644
--- a/src/shared/mount-util.h
+++ b/src/shared/mount-util.h
@@ -81,7 +81,7 @@ static inline char* umount_and_rmdir_and_free(char *p) {
DEFINE_TRIVIAL_CLEANUP_FUNC(char*, umount_and_rmdir_and_free);
int bind_mount_in_namespace(pid_t target, const char *propagate_path, const char *incoming_path, const char *src, const char *dest, bool read_only, bool make_file_or_directory);
-int mount_image_in_namespace(pid_t target, const char *propagate_path, const char *incoming_path, const char *src, const char *dest, bool read_only, bool make_file_or_directory, const MountOptions *options);
+int mount_image_in_namespace(pid_t target, const char *propagate_path, const char *incoming_path, const char *src, const char *dest, bool read_only, bool make_file_or_directory, const MountOptions *options, const ImagePolicy *image_policy);
int make_mount_point(const char *path);