From d452335aa47fb1f1b11dc75bc462697431e64af3 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 30 Nov 2022 18:39:45 +0100 Subject: [PATCH 01/14] dissect: add image dissection policy framework --- src/shared/image-policy.c | 679 +++++++++++++++++++++++++++++++++++ src/shared/image-policy.h | 96 +++++ src/shared/meson.build | 1 + src/test/meson.build | 1 + src/test/test-image-policy.c | 121 +++++++ 5 files changed, 898 insertions(+) create mode 100644 src/shared/image-policy.c create mode 100644 src/shared/image-policy.h create mode 100644 src/test/test-image-policy.c diff --git a/src/shared/image-policy.c b/src/shared/image-policy.c new file mode 100644 index 00000000000..98c58c09010 --- /dev/null +++ b/src/shared/image-policy.c @@ -0,0 +1,679 @@ +/* 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_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 00000000000..278c06c36a6 --- /dev/null +++ b/src/shared/image-policy.h @@ -0,0 +1,96 @@ +/* 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; +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 0f2e2d1a675..df82778f9dd 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/test/meson.build b/src/test/meson.build index d20c911e2b4..85c3115e14f 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -95,6 +95,7 @@ simple_tests += files( 'test-hostname-setup.c', 'test-hostname-util.c', 'test-id128.c', + 'test-image-policy.c', 'test-import-util.c', 'test-in-addr-prefix-util.c', 'test-in-addr-util.c', diff --git a/src/test/test-image-policy.c b/src/test/test-image-policy.c new file mode 100644 index 00000000000..8dc2044c4a5 --- /dev/null +++ b/src/test/test-image-policy.c @@ -0,0 +1,121 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "image-policy.h" +#include "pretty-print.h" +#include "string-util.h" +#include "tests.h" +#include "pager.h" + +static void test_policy(const ImagePolicy *p, const char *name) { + _cleanup_free_ char *as_string = NULL, *as_string_simplified = NULL; + _cleanup_free_ ImagePolicy *parsed = NULL; + + assert_se(image_policy_to_string(p, /* simplified= */ false, &as_string) >= 0); + assert_se(image_policy_to_string(p, /* simplified= */ true, &as_string_simplified) >= 0); + + printf("%s%s", ansi_underline(), name); + + if (!streq(as_string_simplified, name)) { + printf(" → %s", as_string_simplified); + + if (!streq(as_string, as_string_simplified)) + printf(" (aka %s)", as_string); + } + + printf("%s\n", ansi_normal()); + + assert_se(image_policy_from_string(as_string, &parsed) >= 0); + assert_se(image_policy_equal(p, parsed)); + parsed = image_policy_free(parsed); + + assert_se(image_policy_from_string(as_string_simplified, &parsed) >= 0); + assert_se(image_policy_equivalent(p, parsed)); + parsed = image_policy_free(parsed); + + for (PartitionDesignator d = 0; d < _PARTITION_DESIGNATOR_MAX; d++) { + _cleanup_free_ char *k = NULL; + PartitionPolicyFlags f; + + f = image_policy_get(p, d); + if (f < 0) { + f = image_policy_get_exhaustively(p, d); + assert_se(f >= 0); + assert_se(partition_policy_flags_to_string(f, /* simplified= */ true, &k) >= 0); + + printf("%s\t%s → n/a (exhaustively: %s)%s\n", ansi_grey(), partition_designator_to_string(d), k, ansi_normal()); + } else { + assert_se(partition_policy_flags_to_string(f, /* simplified= */ true, &k) >= 0); + printf("\t%s → %s\n", partition_designator_to_string(d), k); + } + } + + _cleanup_free_ char *w = NULL; + assert_se(partition_policy_flags_to_string(image_policy_default(p), /* simplified= */ true, &w) >= 0); + printf("\tdefault → %s\n", w); +} + +static void test_policy_string(const char *t) { + _cleanup_free_ ImagePolicy *parsed = NULL; + + assert_se(image_policy_from_string(t, &parsed) >= 0); + test_policy(parsed, t); +} + +static void test_policy_equiv(const char *s, bool (*func)(const ImagePolicy *p)) { + _cleanup_(image_policy_freep) ImagePolicy *p = NULL; + + assert_se(image_policy_from_string(s, &p) >= 0); + + assert_se(func(p)); + assert_se(func == image_policy_equiv_ignore || !image_policy_equiv_ignore(p)); + assert_se(func == image_policy_equiv_allow || !image_policy_equiv_allow(p)); + assert_se(func == image_policy_equiv_deny || !image_policy_equiv_deny(p)); +} + +TEST_RET(test_image_policy_to_string) { + test_policy(&image_policy_allow, "*"); + test_policy(&image_policy_ignore, "-"); + test_policy(&image_policy_deny, "~"); + test_policy(&image_policy_sysext, "sysext"); + test_policy(&image_policy_container, "container"); + test_policy(&image_policy_host, "host"); + test_policy(&image_policy_service, "service"); + test_policy(NULL, "null"); + + test_policy_string(""); + test_policy_string("-"); + test_policy_string("*"); + test_policy_string("~"); + test_policy_string("swap=open"); + test_policy_string("swap=open:root=signed"); + test_policy_string("swap=open:root=signed+read-only-on+growfs-off:=absent"); + test_policy_string("=-"); + test_policy_string("="); + + test_policy_equiv("", image_policy_equiv_ignore); + test_policy_equiv("-", image_policy_equiv_ignore); + test_policy_equiv("*", image_policy_equiv_allow); + test_policy_equiv("~", image_policy_equiv_deny); + test_policy_equiv("=absent", image_policy_equiv_deny); + test_policy_equiv("=open", image_policy_equiv_allow); + test_policy_equiv("=verity+signed+encrypted+unprotected+unused+absent", image_policy_equiv_allow); + test_policy_equiv("=signed+verity+encrypted+unused+unprotected+absent", image_policy_equiv_allow); + test_policy_equiv("=ignore", image_policy_equiv_ignore); + test_policy_equiv("=absent+unused", image_policy_equiv_ignore); + test_policy_equiv("=unused+absent", image_policy_equiv_ignore); + test_policy_equiv("root=ignore:=ignore", image_policy_equiv_ignore); + + assert_se(image_policy_from_string("pfft", NULL) == -EINVAL); + assert_se(image_policy_from_string("öäüß", NULL) == -EINVAL); + assert_se(image_policy_from_string(":", NULL) == -EINVAL); + assert_se(image_policy_from_string("a=", NULL) == -EBADSLT); + assert_se(image_policy_from_string("=a", NULL) == -EBADRQC); + assert_se(image_policy_from_string("==", NULL) == -EBADRQC); + assert_se(image_policy_from_string("root=verity:root=encrypted", NULL) == -ENOTUNIQ); + assert_se(image_policy_from_string("root=grbl", NULL) == -EBADRQC); + assert_se(image_policy_from_string("wowza=grbl", NULL) == -EBADSLT); + + return 0; +} + +DEFINE_TEST_MAIN(LOG_INFO); From 84be0c710d9d562f6d2cf986cc2a8ff4c98a138b Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 30 Nov 2022 18:43:18 +0100 Subject: [PATCH 02/14] tree-wide: hook up image dissection policy logic everywhere --- man/org.freedesktop.systemd1.xml | 72 ++++++++++++++++++++ src/analyze/analyze.c | 18 +++++ src/analyze/analyze.h | 1 + src/boot/bootctl.c | 19 ++++++ src/boot/bootctl.h | 2 + src/core/dbus-execute.c | 61 +++++++++++++++++ src/core/dbus-service.c | 24 ++++--- src/core/execute.c | 71 +++++++++++-------- src/core/execute.h | 2 + src/core/load-fragment-gperf.gperf.in | 3 + src/core/load-fragment.c | 39 +++++++++++ src/core/load-fragment.h | 1 + src/core/namespace.c | 36 +++++++--- src/core/namespace.h | 3 + src/coredump/coredumpctl.c | 54 ++++++++++----- src/dissect/dissect.c | 20 +++++- src/firstboot/firstboot.c | 19 +++++- src/gpt-auto-generator/gpt-auto-generator.c | 22 +++++- src/journal/journalctl.c | 20 +++++- src/machine-id-setup/machine-id-setup-main.c | 41 +++++++---- src/machine/image-dbus.c | 8 +-- src/nspawn/nspawn.c | 20 +++++- src/partition/repart.c | 20 ++++++ src/portable/portable.c | 30 +++++++- src/portable/portable.h | 5 +- src/portable/portabled-image-bus.c | 5 +- src/shared/bus-unit-util.c | 5 +- src/shared/discover-image.c | 6 +- src/shared/discover-image.h | 3 +- src/shared/dissect-image.c | 22 ++++-- src/shared/dissect-image.h | 19 +++--- src/shared/mount-util.c | 10 +-- src/shared/mount-util.h | 2 +- src/sysext/sysext.c | 36 +++++++--- src/systemctl/systemctl.c | 21 +++++- src/systemctl/systemctl.h | 2 + src/sysupdate/sysupdate.c | 24 ++++++- src/sysusers/sysusers.c | 35 +++++++--- src/test/test-loop-block.c | 10 +-- src/test/test-namespace.c | 3 + src/test/test-ns.c | 3 + src/tmpfiles/tmpfiles.c | 17 +++++ 42 files changed, 691 insertions(+), 143 deletions(-) diff --git a/man/org.freedesktop.systemd1.xml b/man/org.freedesktop.systemd1.xml index 50680e6b37a..05c8b3770e2 100644 --- a/man/org.freedesktop.systemd1.xml +++ b/man/org.freedesktop.systemd1.xml @@ -3167,6 +3167,12 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly s IPCNamespacePath = '...'; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly s RootImagePolicy = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly s MountImagePolicy = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly s ExtensionImagePolicy = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly s KillMode = '...'; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly i KillSignal = ...; @@ -3724,6 +3730,12 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { + + + + + + @@ -4380,6 +4392,12 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { + + + + + + @@ -5147,6 +5165,12 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket { @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly s IPCNamespacePath = '...'; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly s RootImagePolicy = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly s MountImagePolicy = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly s ExtensionImagePolicy = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly s KillMode = '...'; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly i KillSignal = ...; @@ -5718,6 +5742,12 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket { + + + + + + @@ -6356,6 +6386,12 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket { + + + + + + @@ -7002,6 +7038,12 @@ node /org/freedesktop/systemd1/unit/home_2emount { @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly s IPCNamespacePath = '...'; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly s RootImagePolicy = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly s MountImagePolicy = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly s ExtensionImagePolicy = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly s KillMode = '...'; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly i KillSignal = ...; @@ -7501,6 +7543,12 @@ node /org/freedesktop/systemd1/unit/home_2emount { + + + + + + @@ -8057,6 +8105,12 @@ node /org/freedesktop/systemd1/unit/home_2emount { + + + + + + @@ -8830,6 +8884,12 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap { @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly s IPCNamespacePath = '...'; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly s RootImagePolicy = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly s MountImagePolicy = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly s ExtensionImagePolicy = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly s KillMode = '...'; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly i KillSignal = ...; @@ -9315,6 +9375,12 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap { + + + + + + @@ -9857,6 +9923,12 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap { + + + + + + diff --git a/src/analyze/analyze.c b/src/analyze/analyze.c index 0246da4b45d..8bc533b20d8 100644 --- a/src/analyze/analyze.c +++ b/src/analyze/analyze.c @@ -109,6 +109,7 @@ bool arg_quiet = false; char *arg_profile = NULL; bool arg_legend = true; bool arg_table = false; +ImagePolicy *arg_image_policy = NULL; STATIC_DESTRUCTOR_REGISTER(arg_dot_from_patterns, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_dot_to_patterns, strv_freep); @@ -117,6 +118,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_image, freep); STATIC_DESTRUCTOR_REGISTER(arg_security_policy, freep); STATIC_DESTRUCTOR_REGISTER(arg_unit, freep); STATIC_DESTRUCTOR_REGISTER(arg_profile, freep); +STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); int acquire_bus(sd_bus **bus, bool *use_full_bus) { int r; @@ -268,6 +270,7 @@ static int help(int argc, char *argv[], void *userdata) { " -q --quiet Do not emit hints\n" " --root=PATH Operate on an alternate filesystem root\n" " --image=PATH Operate on disk image as filesystem root\n" + " --image-policy=POLICY Specify disk image dissection policy\n" "\nSee the %s for details.\n", program_invocation_short_name, ansi_highlight(), @@ -307,6 +310,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_PROFILE, ARG_TABLE, ARG_NO_LEGEND, + ARG_IMAGE_POLICY, }; static const struct option options[] = { @@ -339,6 +343,7 @@ static int parse_argv(int argc, char *argv[]) { { "profile", required_argument, NULL, ARG_PROFILE }, { "table", optional_argument, NULL, ARG_TABLE }, { "no-legend", optional_argument, NULL, ARG_NO_LEGEND }, + { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, {} }; @@ -522,6 +527,18 @@ static int parse_argv(int argc, char *argv[]) { arg_legend = false; break; + case ARG_IMAGE_POLICY: { + _cleanup_(image_policy_freep) ImagePolicy *p = NULL; + + r = image_policy_from_string(optarg, &p); + if (r < 0) + return log_error_errno(r, "Failed to parse image policy: %s", optarg); + + image_policy_free(arg_image_policy); + arg_image_policy = TAKE_PTR(p); + break; + } + case '?': return -EINVAL; @@ -643,6 +660,7 @@ static int run(int argc, char *argv[]) { r = mount_image_privately_interactively( arg_image, + arg_image_policy, DISSECT_IMAGE_GENERIC_ROOT | DISSECT_IMAGE_RELAX_VAR_CHECK | DISSECT_IMAGE_READ_ONLY, diff --git a/src/analyze/analyze.h b/src/analyze/analyze.h index 2f623e32012..84575cd9a9d 100644 --- a/src/analyze/analyze.h +++ b/src/analyze/analyze.h @@ -38,6 +38,7 @@ extern bool arg_quiet; extern char *arg_profile; extern bool arg_legend; extern bool arg_table; +extern ImagePolicy *arg_image_policy; int acquire_bus(sd_bus **bus, bool *use_full_bus); diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c index 82c7e498ba6..b9d034d5503 100644 --- a/src/boot/bootctl.c +++ b/src/boot/bootctl.c @@ -52,6 +52,7 @@ char *arg_image = NULL; InstallSource arg_install_source = ARG_INSTALL_SOURCE_AUTO; char *arg_efi_boot_option_description = NULL; bool arg_dry_run = false; +ImagePolicy *arg_image_policy = NULL; STATIC_DESTRUCTOR_REGISTER(arg_esp_path, freep); STATIC_DESTRUCTOR_REGISTER(arg_xbootldr_path, freep); @@ -60,6 +61,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_entry_token, freep); STATIC_DESTRUCTOR_REGISTER(arg_root, freep); STATIC_DESTRUCTOR_REGISTER(arg_image, freep); STATIC_DESTRUCTOR_REGISTER(arg_efi_boot_option_description, freep); +STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); int acquire_esp( bool unprivileged_mode, @@ -168,6 +170,8 @@ static int help(int argc, char *argv[], void *userdata) { " --boot-path=PATH Path to the $BOOT partition\n" " --root=PATH Operate on an alternate filesystem root\n" " --image=PATH Operate on disk image as filesystem root\n" + " --image-policy=POLICY\n" + " Specify disk image dissection policy\n" " --install-source=auto|image|host\n" " Where to pick files when using --root=/--image=\n" " -p --print-esp-path Print path to the EFI System Partition mount point\n" @@ -218,6 +222,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_ARCH_ALL, ARG_EFI_BOOT_OPTION_DESCRIPTION, ARG_DRY_RUN, + ARG_IMAGE_POLICY, }; static const struct option options[] = { @@ -244,6 +249,7 @@ static int parse_argv(int argc, char *argv[]) { { "all-architectures", no_argument, NULL, ARG_ARCH_ALL }, { "efi-boot-option-description", required_argument, NULL, ARG_EFI_BOOT_OPTION_DESCRIPTION }, { "dry-run", no_argument, NULL, ARG_DRY_RUN }, + { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, {} }; @@ -376,6 +382,18 @@ static int parse_argv(int argc, char *argv[]) { arg_dry_run = true; break; + case ARG_IMAGE_POLICY: { + _cleanup_(image_policy_freep) ImagePolicy *p = NULL; + + r = image_policy_from_string(optarg, &p); + if (r < 0) + return log_error_errno(r, "Failed to parse image policy: %s", optarg); + + image_policy_free(arg_image_policy); + arg_image_policy = TAKE_PTR(p); + break; + } + case '?': return -EINVAL; @@ -478,6 +496,7 @@ static int run(int argc, char *argv[]) { r = mount_image_privately_interactively( arg_image, + arg_image_policy, DISSECT_IMAGE_GENERIC_ROOT | DISSECT_IMAGE_RELAX_VAR_CHECK, &unlink_dir, diff --git a/src/boot/bootctl.h b/src/boot/bootctl.h index c87d43694f8..dd98b959c29 100644 --- a/src/boot/bootctl.h +++ b/src/boot/bootctl.h @@ -4,6 +4,7 @@ #include "sd-id128.h" #include "boot-entry.h" +#include "image-policy.h" #include "json.h" #include "pager.h" @@ -34,6 +35,7 @@ extern char *arg_image; extern InstallSource arg_install_source; extern char *arg_efi_boot_option_description; extern bool arg_dry_run; +extern ImagePolicy *arg_image_policy; static inline const char *arg_dollar_boot_path(void) { /* $BOOT shall be the XBOOTLDR partition if it exists, and otherwise the ESP */ diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c index d5ef796e522..d77842bdabd 100644 --- a/src/core/dbus-execute.c +++ b/src/core/dbus-execute.c @@ -1156,6 +1156,30 @@ static int bus_property_get_exec_dir_symlink( return sd_bus_message_close_container(reply); } +static int property_get_image_policy( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + ImagePolicy **pp = ASSERT_PTR(userdata); + _cleanup_free_ char *s = NULL; + int r; + + assert(bus); + assert(property); + assert(reply); + + r = image_policy_to_string(*pp ?: &image_policy_service, /* simplify= */ true, &s); + if (r < 0) + return r; + + return sd_bus_message_append(reply, "s", s); +} + const sd_bus_vtable bus_exec_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_PROPERTY("Environment", "as", NULL, offsetof(ExecContext, environment), SD_BUS_VTABLE_PROPERTY_CONST), @@ -1324,6 +1348,9 @@ const sd_bus_vtable bus_exec_vtable[] = { SD_BUS_PROPERTY("ProtectHostname", "b", bus_property_get_bool, offsetof(ExecContext, protect_hostname), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("NetworkNamespacePath", "s", NULL, offsetof(ExecContext, network_namespace_path), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("IPCNamespacePath", "s", NULL, offsetof(ExecContext, ipc_namespace_path), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("RootImagePolicy", "s", property_get_image_policy, offsetof(ExecContext, root_image_policy), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("MountImagePolicy", "s", property_get_image_policy, offsetof(ExecContext, mount_image_policy), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("ExtensionImagePolicy", "s", property_get_image_policy, offsetof(ExecContext, extension_image_policy), SD_BUS_VTABLE_PROPERTY_CONST), /* Obsolete/redundant properties: */ SD_BUS_PROPERTY("Capabilities", "s", property_get_empty_string, 0, SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN), @@ -3900,6 +3927,40 @@ int bus_exec_context_set_transient_property( return 1; + } else if (STR_IN_SET(name, "RootImagePolicy", "MountImagePolicy", "ExtensionImagePolicy")) { + _cleanup_(image_policy_freep) ImagePolicy *p = NULL; + const char *s; + + r = sd_bus_message_read(message, "s", &s); + if (r < 0) + return r; + + r = image_policy_from_string(s, &p); + if (r < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Failed to parse image policy string: %s", s); + + if (!UNIT_WRITE_FLAGS_NOOP(flags)) { + _cleanup_free_ char *t = NULL; + ImagePolicy **pp = + streq(name, "RootImagePolicy") ? &c->root_image_policy : + streq(name, "MountImagePolicy") ? &c->mount_image_policy : + &c->extension_image_policy; + + r = image_policy_to_string(p, /* simplify= */ true, &t); + if (r < 0) + return r; + + image_policy_free(*pp); + *pp = TAKE_PTR(p); + + unit_write_settingf( + u, flags, name, + "%s=%s", + name, + t); /* no escaping necessary */ + } + + return 1; } return 0; diff --git a/src/core/dbus-service.c b/src/core/dbus-service.c index 0f6e3152330..6041a37e560 100644 --- a/src/core/dbus-service.c +++ b/src/core/dbus-service.c @@ -197,15 +197,23 @@ static int bus_service_method_mount(sd_bus_message *message, void *userdata, sd_ propagate_directory = strjoina("/run/systemd/propagate/", u->id); if (is_image) - r = mount_image_in_namespace(unit_pid, - propagate_directory, - "/run/systemd/incoming/", - src, dest, read_only, make_file_or_directory, options); + r = mount_image_in_namespace( + unit_pid, + propagate_directory, + "/run/systemd/incoming/", + src, dest, + read_only, + make_file_or_directory, + options, + c->mount_image_policy ?: &image_policy_service); else - r = bind_mount_in_namespace(unit_pid, - propagate_directory, - "/run/systemd/incoming/", - src, dest, read_only, make_file_or_directory); + r = bind_mount_in_namespace( + unit_pid, + propagate_directory, + "/run/systemd/incoming/", + src, dest, + read_only, + make_file_or_directory); if (r < 0) return sd_bus_error_set_errnof(error, r, "Failed to mount %s on %s in unit's namespace: %m", src, dest); diff --git a/src/core/execute.c b/src/core/execute.c index aff07e7b744..cd11683407e 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -3823,36 +3823,43 @@ static int apply_mount_namespace( goto finalize; } - r = setup_namespace(root_dir, root_image, context->root_image_options, - &ns_info, read_write_paths, - needs_sandboxing ? context->read_only_paths : NULL, - needs_sandboxing ? context->inaccessible_paths : NULL, - needs_sandboxing ? context->exec_paths : NULL, - needs_sandboxing ? context->no_exec_paths : NULL, - empty_directories, - symlinks, - bind_mounts, - n_bind_mounts, - context->temporary_filesystems, - context->n_temporary_filesystems, - context->mount_images, - context->n_mount_images, - tmp_dir, - var_tmp_dir, - creds_path, - context->log_namespace, - context->mount_propagation_flag, - context->root_hash, context->root_hash_size, context->root_hash_path, - context->root_hash_sig, context->root_hash_sig_size, context->root_hash_sig_path, - context->root_verity, - context->extension_images, - context->n_extension_images, - context->extension_directories, - propagate_dir, - incoming_dir, - extension_dir, - root_dir || root_image ? params->notify_socket : NULL, - error_path); + r = setup_namespace( + root_dir, + root_image, + context->root_image_options, + context->root_image_policy ?: &image_policy_service, + &ns_info, + read_write_paths, + needs_sandboxing ? context->read_only_paths : NULL, + needs_sandboxing ? context->inaccessible_paths : NULL, + needs_sandboxing ? context->exec_paths : NULL, + needs_sandboxing ? context->no_exec_paths : NULL, + empty_directories, + symlinks, + bind_mounts, + n_bind_mounts, + context->temporary_filesystems, + context->n_temporary_filesystems, + context->mount_images, + context->n_mount_images, + context->mount_image_policy ?: &image_policy_service, + tmp_dir, + var_tmp_dir, + creds_path, + context->log_namespace, + context->mount_propagation_flag, + context->root_hash, context->root_hash_size, context->root_hash_path, + context->root_hash_sig, context->root_hash_sig_size, context->root_hash_sig_path, + context->root_verity, + context->extension_images, + context->n_extension_images, + context->extension_image_policy ?: &image_policy_sysext, + context->extension_directories, + propagate_dir, + incoming_dir, + extension_dir, + root_dir || root_image ? params->notify_socket : NULL, + error_path); /* If we couldn't set up the namespace this is probably due to a missing capability. setup_namespace() reports * that with a special, recognizable error ENOANO. In this case, silently proceed, but only if exclusively @@ -5789,6 +5796,10 @@ void exec_context_done(ExecContext *c) { c->load_credentials = hashmap_free(c->load_credentials); c->set_credentials = hashmap_free(c->set_credentials); + + c->root_image_policy = image_policy_free(c->root_image_policy); + c->mount_image_policy = image_policy_free(c->mount_image_policy); + c->extension_image_policy = image_policy_free(c->extension_image_policy); } int exec_context_destroy_runtime_directory(const ExecContext *c, const char *runtime_prefix) { diff --git a/src/core/execute.h b/src/core/execute.h index 254a1ee2d13..123fc1ec604 100644 --- a/src/core/execute.h +++ b/src/core/execute.h @@ -359,6 +359,8 @@ struct ExecContext { Hashmap *set_credentials; /* output id → ExecSetCredential */ Hashmap *load_credentials; /* output id → ExecLoadCredential */ + + ImagePolicy *root_image_policy, *mount_image_policy, *extension_image_policy; }; static inline bool exec_context_restrict_namespaces_set(const ExecContext *c) { diff --git a/src/core/load-fragment-gperf.gperf.in b/src/core/load-fragment-gperf.gperf.in index 50ff57a9f8d..f35c7436550 100644 --- a/src/core/load-fragment-gperf.gperf.in +++ b/src/core/load-fragment-gperf.gperf.in @@ -6,12 +6,15 @@ {{type}}.RootDirectory, config_parse_unit_path_printf, true, offsetof({{type}}, exec_context.root_directory) {{type}}.RootImage, config_parse_unit_path_printf, true, offsetof({{type}}, exec_context.root_image) {{type}}.RootImageOptions, config_parse_root_image_options, 0, offsetof({{type}}, exec_context) +{{type}}.RootImagePolicy, config_parse_image_policy, 0, offsetof({{type}}, exec_context.root_image_policy) {{type}}.RootHash, config_parse_exec_root_hash, 0, offsetof({{type}}, exec_context) {{type}}.RootHashSignature, config_parse_exec_root_hash_sig, 0, offsetof({{type}}, exec_context) {{type}}.RootVerity, config_parse_unit_path_printf, true, offsetof({{type}}, exec_context.root_verity) {{type}}.ExtensionDirectories, config_parse_namespace_path_strv, 0, offsetof({{type}}, exec_context.extension_directories) {{type}}.ExtensionImages, config_parse_extension_images, 0, offsetof({{type}}, exec_context) +{{type}}.ExtensionImagePolicy, config_parse_image_policy, 0, offsetof({{type}}, exec_context.extension_image_policy) {{type}}.MountImages, config_parse_mount_images, 0, offsetof({{type}}, exec_context) +{{type}}.MountImagePolicy, config_parse_image_policy, 0, offsetof({{type}}, exec_context.mount_image_policy) {{type}}.User, config_parse_user_group_compat, 0, offsetof({{type}}, exec_context.user) {{type}}.Group, config_parse_user_group_compat, 0, offsetof({{type}}, exec_context.group) {{type}}.SupplementaryGroups, config_parse_user_group_strv_compat, 0, offsetof({{type}}, exec_context.supplementary_groups) diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index 533c09f72ea..99d40b74908 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -1705,6 +1705,45 @@ int config_parse_root_image_options( return 0; } +int config_parse_image_policy( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(image_policy_freep) ImagePolicy *np = NULL; + ImagePolicy **p = ASSERT_PTR(data); + int r; + + assert(rvalue); + + if (isempty(rvalue)) { + *p = image_policy_free(*p); + return 0; + } + + r = image_policy_from_string(rvalue, &np); + if (r == -ENOTUNIQ) + return log_syntax(unit, LOG_ERR, filename, line, r, "Duplicate rule in image policy, refusing: %s", rvalue); + if (r == -EBADSLT) + return log_syntax(unit, LOG_ERR, filename, line, r, "Unknown partition type in image policy, refusing: %s", rvalue); + if (r == -EBADRQC) + return log_syntax(unit, LOG_ERR, filename, line, r, "Unknown partition policy flag in image policy, refusing: %s", rvalue); + if (r < 0) + return log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse image policy, refusing: %s", rvalue); + + image_policy_free(*p); + *p = TAKE_PTR(np); + + return 0; +} + int config_parse_exec_root_hash( const char *unit, const char *filename, diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h index 91dc9174584..ab682ee23e7 100644 --- a/src/core/load-fragment.h +++ b/src/core/load-fragment.h @@ -52,6 +52,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_exec_cpu_affinity); CONFIG_PARSER_PROTOTYPE(config_parse_exec_mount_apivfs); CONFIG_PARSER_PROTOTYPE(config_parse_exec_secure_bits); CONFIG_PARSER_PROTOTYPE(config_parse_root_image_options); +CONFIG_PARSER_PROTOTYPE(config_parse_image_policy); CONFIG_PARSER_PROTOTYPE(config_parse_exec_root_hash); CONFIG_PARSER_PROTOTYPE(config_parse_exec_root_hash_sig); CONFIG_PARSER_PROTOTYPE(config_parse_capability_set); diff --git a/src/core/namespace.c b/src/core/namespace.c index 8b141a24848..2668bca1bb1 100644 --- a/src/core/namespace.c +++ b/src/core/namespace.c @@ -1240,7 +1240,10 @@ static int mount_mqueuefs(const MountEntry *m) { return 0; } -static int mount_image(const MountEntry *m, const char *root_directory) { +static int mount_image( + const MountEntry *m, + const char *root_directory, + const ImagePolicy *image_policy) { _cleanup_free_ char *host_os_release_id = NULL, *host_os_release_version_id = NULL, *host_os_release_sysext_level = NULL; @@ -1262,8 +1265,15 @@ static int mount_image(const MountEntry *m, const char *root_directory) { } r = verity_dissect_and_mount( - /* src_fd= */ -1, mount_entry_source(m), mount_entry_path(m), m->image_options, - host_os_release_id, host_os_release_version_id, host_os_release_sysext_level, NULL); + /* src_fd= */ -1, + mount_entry_source(m), + mount_entry_path(m), + m->image_options, + image_policy, + host_os_release_id, + host_os_release_version_id, + host_os_release_sysext_level, + NULL); if (r == -ENOENT && m->ignore) return 0; if (r == -ESTALE && host_os_release_id) @@ -1336,6 +1346,8 @@ static int follow_symlink( static int apply_one_mount( const char *root_directory, MountEntry *m, + const ImagePolicy *mount_image_policy, + const ImagePolicy *extension_image_policy, const NamespaceInfo *ns_info) { _cleanup_free_ char *inaccessible = NULL; @@ -1505,10 +1517,10 @@ static int apply_one_mount( return mount_mqueuefs(m); case MOUNT_IMAGES: - return mount_image(m, NULL); + return mount_image(m, NULL, mount_image_policy); case EXTENSION_IMAGES: - return mount_image(m, root_directory); + return mount_image(m, root_directory, extension_image_policy); case OVERLAY_MOUNT: return mount_overlay(m); @@ -1778,6 +1790,8 @@ static int create_symlinks_from_tuples(const char *root, char **strv_symlinks) { static int apply_mounts( const char *root, + const ImagePolicy *mount_image_policy, + const ImagePolicy *extension_image_policy, const NamespaceInfo *ns_info, MountEntry *mounts, size_t *n_mounts, @@ -1832,7 +1846,7 @@ static int apply_mounts( break; } - r = apply_one_mount(root, m, ns_info); + r = apply_one_mount(root, m, mount_image_policy, extension_image_policy, ns_info); if (r < 0) { if (error_path && mount_entry_path(m)) *error_path = strdup(mount_entry_path(m)); @@ -2011,7 +2025,8 @@ static int verity_settings_prepare( int setup_namespace( const char* root_directory, const char* root_image, - const MountOptions *root_image_options, + const MountOptions *root_image_mount_options, + const ImagePolicy *root_image_policy, const NamespaceInfo *ns_info, char** read_write_paths, char** read_only_paths, @@ -2026,6 +2041,7 @@ int setup_namespace( size_t n_temporary_filesystems, const MountImage *mount_images, size_t n_mount_images, + const ImagePolicy *mount_image_policy, const char* tmp_dir, const char* var_tmp_dir, const char *creds_path, @@ -2040,6 +2056,7 @@ int setup_namespace( const char *verity_data_path, const MountImage *extension_images, size_t n_extension_images, + const ImagePolicy *extension_image_policy, char **extension_directories, const char *propagate_dir, const char *incoming_dir, @@ -2113,7 +2130,8 @@ int setup_namespace( r = dissect_loop_device( loop_device, &verity, - root_image_options, + root_image_mount_options, + root_image_policy, dissect_image_flags, &dissected_image); if (r < 0) @@ -2501,7 +2519,7 @@ int setup_namespace( (void) base_filesystem_create(root, UID_INVALID, GID_INVALID); /* Now make the magic happen */ - r = apply_mounts(root, ns_info, mounts, &n_mounts, exec_dir_symlinks, error_path); + r = apply_mounts(root, mount_image_policy, extension_image_policy, ns_info, mounts, &n_mounts, exec_dir_symlinks, error_path); if (r < 0) goto finish; diff --git a/src/core/namespace.h b/src/core/namespace.h index 1cd4fdd9213..39b510f41d9 100644 --- a/src/core/namespace.h +++ b/src/core/namespace.h @@ -103,6 +103,7 @@ int setup_namespace( const char *root_directory, const char *root_image, const MountOptions *root_image_options, + const ImagePolicy *root_image_policy, const NamespaceInfo *ns_info, char **read_write_paths, char **read_only_paths, @@ -117,6 +118,7 @@ int setup_namespace( size_t n_temporary_filesystems, const MountImage *mount_images, size_t n_mount_images, + const ImagePolicy *mount_image_policy, const char *tmp_dir, const char *var_tmp_dir, const char *creds_path, @@ -131,6 +133,7 @@ int setup_namespace( const char *root_verity, const MountImage *extension_images, size_t n_extension_images, + const ImagePolicy *extension_image_policy, char **extension_directories, const char *propagate_dir, const char *incoming_dir, diff --git a/src/coredump/coredumpctl.c b/src/coredump/coredumpctl.c index 60da536d4eb..076b35f0987 100644 --- a/src/coredump/coredumpctl.c +++ b/src/coredump/coredumpctl.c @@ -64,9 +64,11 @@ static const char* arg_output = NULL; static bool arg_reverse = false; static bool arg_quiet = false; static bool arg_all = false; +static ImagePolicy *arg_image_policy = NULL; STATIC_DESTRUCTOR_REGISTER(arg_debugger_args, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_file, strv_freep); +STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); static int add_match(sd_journal *j, const char *match) { _cleanup_free_ char *p = NULL; @@ -198,6 +200,7 @@ static int verb_help(int argc, char **argv, void *userdata) { " --all Look at all journal files instead of local ones\n" " --root=PATH Operate on an alternate filesystem root\n" " --image=PATH Operate on disk image as filesystem root\n" + " --image-policy=POLICY Specify disk image dissection policy\n" "\nSee the %2$s for details.\n", program_invocation_short_name, link, @@ -220,29 +223,31 @@ static int parse_argv(int argc, char *argv[]) { ARG_ROOT, ARG_IMAGE, ARG_ALL, + ARG_IMAGE_POLICY, }; int c, r; static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version" , no_argument, NULL, ARG_VERSION }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, - { "debugger", required_argument, NULL, ARG_DEBUGGER }, - { "debugger-arguments", required_argument, NULL, 'A' }, - { "output", required_argument, NULL, 'o' }, - { "field", required_argument, NULL, 'F' }, - { "file", required_argument, NULL, ARG_FILE }, - { "directory", required_argument, NULL, 'D' }, - { "reverse", no_argument, NULL, 'r' }, - { "since", required_argument, NULL, 'S' }, - { "until", required_argument, NULL, 'U' }, - { "quiet", no_argument, NULL, 'q' }, - { "json", required_argument, NULL, ARG_JSON }, - { "root", required_argument, NULL, ARG_ROOT }, - { "image", required_argument, NULL, ARG_IMAGE }, - { "all", no_argument, NULL, ARG_ALL }, + { "help", no_argument, NULL, 'h' }, + { "version" , no_argument, NULL, ARG_VERSION }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, + { "debugger", required_argument, NULL, ARG_DEBUGGER }, + { "debugger-arguments", required_argument, NULL, 'A' }, + { "output", required_argument, NULL, 'o' }, + { "field", required_argument, NULL, 'F' }, + { "file", required_argument, NULL, ARG_FILE }, + { "directory", required_argument, NULL, 'D' }, + { "reverse", no_argument, NULL, 'r' }, + { "since", required_argument, NULL, 'S' }, + { "until", required_argument, NULL, 'U' }, + { "quiet", no_argument, NULL, 'q' }, + { "json", required_argument, NULL, ARG_JSON }, + { "root", required_argument, NULL, ARG_ROOT }, + { "image", required_argument, NULL, ARG_IMAGE }, + { "all", no_argument, NULL, ARG_ALL }, + { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, {} }; @@ -363,6 +368,18 @@ static int parse_argv(int argc, char *argv[]) { arg_all = true; break; + case ARG_IMAGE_POLICY: { + _cleanup_(image_policy_freep) ImagePolicy *p = NULL; + + r = image_policy_from_string(optarg, &p); + if (r < 0) + return log_error_errno(r, "Failed to parse image policy: %s", optarg); + + image_policy_free(arg_image_policy); + arg_image_policy = TAKE_PTR(p); + break; + } + case '?': return -EINVAL; @@ -1361,6 +1378,7 @@ static int run(int argc, char *argv[]) { r = mount_image_privately_interactively( arg_image, + arg_image_policy, DISSECT_IMAGE_GENERIC_ROOT | DISSECT_IMAGE_REQUIRE_ROOT | DISSECT_IMAGE_RELAX_VAR_CHECK | diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index ff00c4f567d..b3f20e193e5 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -83,6 +83,7 @@ static bool arg_rmdir = false; static bool arg_in_memory = false; static char **arg_argv = NULL; static char *arg_loop_ref = NULL; +static ImagePolicy* arg_image_policy = NULL; STATIC_DESTRUCTOR_REGISTER(arg_image, freep); STATIC_DESTRUCTOR_REGISTER(arg_path, freep); @@ -126,6 +127,8 @@ static int help(void) { " 'base64:'\n" " --verity-data=PATH Specify data file with hash tree for verity if it is\n" " not embedded in IMAGE\n" + " --image-policy=POLICY\n" + " Specify image dissection policy\n" " --json=pretty|short|off\n" " Generate JSON output\n" " --loop-ref=NAME Set reference string for loopback device\n" @@ -221,6 +224,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_ATTACH, ARG_DETACH, ARG_LOOP_REF, + ARG_IMAGE_POLICY, }; static const struct option options[] = { @@ -250,6 +254,7 @@ static int parse_argv(int argc, char *argv[]) { { "json", required_argument, NULL, ARG_JSON }, { "discover", no_argument, NULL, ARG_DISCOVER }, { "loop-ref", required_argument, NULL, ARG_LOOP_REF }, + { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, {} }; @@ -457,6 +462,18 @@ static int parse_argv(int argc, char *argv[]) { return r; break; + case ARG_IMAGE_POLICY: { + _cleanup_(image_policy_freep) ImagePolicy *p = NULL; + + r = image_policy_from_string(optarg, &p); + if (r < 0) + return log_error_errno(r, "Failed to parse image policy: %s", optarg); + + image_policy_free(arg_image_policy); + arg_image_policy = TAKE_PTR(p); + break; + } + case '?': return -EINVAL; @@ -1750,7 +1767,8 @@ static int run(int argc, char *argv[]) { r = dissect_loop_device_and_warn( d, &arg_verity_settings, - NULL, + /* mount_options= */ NULL, + arg_image_policy, arg_flags, &m); if (r < 0) diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c index 3e68ed1cb02..54c72eceaf5 100644 --- a/src/firstboot/firstboot.c +++ b/src/firstboot/firstboot.c @@ -73,6 +73,7 @@ static bool arg_delete_root_password = false; static bool arg_root_password_is_hashed = false; static bool arg_welcome = true; static bool arg_reset = false; +static ImagePolicy *arg_image_policy = NULL; STATIC_DESTRUCTOR_REGISTER(arg_root, freep); STATIC_DESTRUCTOR_REGISTER(arg_image, freep); @@ -82,6 +83,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_keymap, freep); STATIC_DESTRUCTOR_REGISTER(arg_timezone, freep); STATIC_DESTRUCTOR_REGISTER(arg_hostname, freep); STATIC_DESTRUCTOR_REGISTER(arg_root_password, erase_and_freep); +STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); static bool press_any_key(void) { char k = 0; @@ -1145,7 +1147,8 @@ static int help(void) { " -h --help Show this help\n" " --version Show package version\n" " --root=PATH Operate on an alternate filesystem root\n" - " --image=PATH Operate on an alternate filesystem image\n" + " --image=PATH Operate on disk image as filesystem root\n" + " --image-policy=POLICY Specify disk image dissection policy\n" " --locale=LOCALE Set primary locale (LANG=)\n" " --locale-messages=LOCALE Set message locale (LC_MESSAGES=)\n" " --keymap=KEYMAP Set keymap\n" @@ -1216,6 +1219,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_DELETE_ROOT_PASSWORD, ARG_WELCOME, ARG_RESET, + ARG_IMAGE_POLICY, }; static const struct option options[] = { @@ -1252,6 +1256,7 @@ static int parse_argv(int argc, char *argv[]) { { "delete-root-password", no_argument, NULL, ARG_DELETE_ROOT_PASSWORD }, { "welcome", required_argument, NULL, ARG_WELCOME }, { "reset", no_argument, NULL, ARG_RESET }, + { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, {} }; @@ -1458,6 +1463,17 @@ static int parse_argv(int argc, char *argv[]) { arg_reset = true; break; + case ARG_IMAGE_POLICY: { + _cleanup_(image_policy_freep) ImagePolicy *p = NULL; + + r = image_policy_from_string(optarg, &p); + if (r < 0) + return log_error_errno(r, "Failed to parse image policy: %s", optarg); + + image_policy_free(arg_image_policy); + arg_image_policy = TAKE_PTR(p); + break; + } case '?': return -EINVAL; @@ -1518,6 +1534,7 @@ static int run(int argc, char *argv[]) { r = mount_image_privately_interactively( arg_image, + arg_image_policy, DISSECT_IMAGE_GENERIC_ROOT | DISSECT_IMAGE_REQUIRE_ROOT | DISSECT_IMAGE_VALIDATE_OS | diff --git a/src/gpt-auto-generator/gpt-auto-generator.c b/src/gpt-auto-generator/gpt-auto-generator.c index 9ccd78af658..09c63a31b2f 100644 --- a/src/gpt-auto-generator/gpt-auto-generator.c +++ b/src/gpt-auto-generator/gpt-auto-generator.c @@ -23,6 +23,7 @@ #include "fstab-util.h" #include "generator.h" #include "gpt.h" +#include "image-policy.h" #include "initrd-util.h" #include "mkdir.h" #include "mountpoint-util.h" @@ -43,6 +44,9 @@ static bool arg_root_enabled = true; static char *arg_root_fstype = NULL; static char *arg_root_options = NULL; static int arg_root_rw = -1; +static ImagePolicy *arg_image_policy = NULL; + +STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); STATIC_DESTRUCTOR_REGISTER(arg_root_fstype, freep); STATIC_DESTRUCTOR_REGISTER(arg_root_options, freep); @@ -744,7 +748,9 @@ static int enumerate_partitions(dev_t devnum) { r = dissect_loop_device( loop, - NULL, NULL, + /* verity= */ NULL, + /* mount_options= */ NULL, + arg_image_policy ?: &image_policy_host, DISSECT_IMAGE_GPT_ONLY| DISSECT_IMAGE_USR_NO_ROOT| DISSECT_IMAGE_DISKSEQ_DEVNODE, @@ -882,6 +888,20 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat arg_root_rw = true; else if (proc_cmdline_key_streq(key, "ro") && !value) arg_root_rw = false; + else if (proc_cmdline_key_streq(key, "systemd.image_policy")) { + _cleanup_(image_policy_freep) ImagePolicy *p = NULL; + + if (proc_cmdline_value_missing(key, value)) + return 0; + + r = image_policy_from_string(value, &p); + if (r < 0) + return log_error_errno(r, "Failed to parse image policy: %s", value); + + image_policy_free(arg_image_policy); + arg_image_policy = TAKE_PTR(p); + return 0; + } return 0; } diff --git a/src/journal/journalctl.c b/src/journal/journalctl.c index 97e9c1aafc4..abacbb03066 100644 --- a/src/journal/journalctl.c +++ b/src/journal/journalctl.c @@ -134,6 +134,7 @@ static Set *arg_output_fields = NULL; static const char *arg_pattern = NULL; static pcre2_code *arg_compiled_pattern = NULL; static PatternCompileCase arg_case = PATTERN_COMPILE_CASE_AUTO; +ImagePolicy *arg_image_policy = NULL; STATIC_DESTRUCTOR_REGISTER(arg_file, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_facilities, set_freep); @@ -145,6 +146,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_root, freep); STATIC_DESTRUCTOR_REGISTER(arg_image, freep); STATIC_DESTRUCTOR_REGISTER(arg_output_fields, set_freep); STATIC_DESTRUCTOR_REGISTER(arg_compiled_pattern, pattern_freep); +STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); static enum { ACTION_SHOW, @@ -326,8 +328,9 @@ static int help(void) { " -m --merge Show entries from all available journals\n" " -D --directory=PATH Show journal files from directory\n" " --file=PATH Show journal file\n" - " --root=ROOT Operate on files below a root directory\n" - " --image=IMAGE Operate on files in filesystem image\n" + " --root=PATH Operate on an alternate filesystem root\n" + " --image=PATH Operate on disk image as filesystem root\n" + " --image-policy=POLICY Specify disk image dissection policy\n" " --namespace=NAMESPACE Show journal data from specified journal namespace\n" "\n%3$sFiltering Options:%4$s\n" " -S --since=DATE Show entries not older than the specified date\n" @@ -444,6 +447,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_NO_HOSTNAME, ARG_OUTPUT_FIELDS, ARG_NAMESPACE, + ARG_IMAGE_POLICY, }; static const struct option options[] = { @@ -511,6 +515,7 @@ static int parse_argv(int argc, char *argv[]) { { "no-hostname", no_argument, NULL, ARG_NO_HOSTNAME }, { "output-fields", required_argument, NULL, ARG_OUTPUT_FIELDS }, { "namespace", required_argument, NULL, ARG_NAMESPACE }, + { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, {} }; @@ -1033,7 +1038,17 @@ static int parse_argv(int argc, char *argv[]) { break; } + case ARG_IMAGE_POLICY: { + _cleanup_(image_policy_freep) ImagePolicy *p = NULL; + r = image_policy_from_string(optarg, &p); + if (r < 0) + return log_error_errno(r, "Failed to parse image policy: %s", optarg); + + image_policy_free(arg_image_policy); + arg_image_policy = TAKE_PTR(p); + break; + } case '?': return -EINVAL; @@ -2126,6 +2141,7 @@ static int run(int argc, char *argv[]) { r = mount_image_privately_interactively( arg_image, + arg_image_policy, DISSECT_IMAGE_GENERIC_ROOT | DISSECT_IMAGE_REQUIRE_ROOT | DISSECT_IMAGE_VALIDATE_OS | diff --git a/src/machine-id-setup/machine-id-setup-main.c b/src/machine-id-setup/machine-id-setup-main.c index 223164ea1bd..c5b22d5d768 100644 --- a/src/machine-id-setup/machine-id-setup-main.c +++ b/src/machine-id-setup/machine-id-setup-main.c @@ -22,9 +22,11 @@ static char *arg_root = NULL; static char *arg_image = NULL; static bool arg_commit = false; static bool arg_print = false; +static ImagePolicy *arg_image_policy = NULL; STATIC_DESTRUCTOR_REGISTER(arg_root, freep); STATIC_DESTRUCTOR_REGISTER(arg_image, freep); +STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); static int help(void) { _cleanup_free_ char *link = NULL; @@ -36,12 +38,13 @@ static int help(void) { printf("%s [OPTIONS...]\n" "\n%sInitialize /etc/machine-id from a random source.%s\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --root=PATH Operate relative to root path\n" - " --image=PATH Operate relative to image file\n" - " --commit Commit transient ID\n" - " --print Print used machine ID\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --root=PATH Operate on an alternate filesystem root\n" + " --image=PATH Operate on disk image as filesystem root\n" + " --image-policy=POLICY Specify disk image dissection policy\n" + " --commit Commit transient ID\n" + " --print Print used machine ID\n" "\nSee the %s for details.\n", program_invocation_short_name, ansi_highlight(), @@ -59,15 +62,17 @@ static int parse_argv(int argc, char *argv[]) { ARG_IMAGE, ARG_COMMIT, ARG_PRINT, + ARG_IMAGE_POLICY, }; static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "root", required_argument, NULL, ARG_ROOT }, - { "image", required_argument, NULL, ARG_IMAGE }, - { "commit", no_argument, NULL, ARG_COMMIT }, - { "print", no_argument, NULL, ARG_PRINT }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "root", required_argument, NULL, ARG_ROOT }, + { "image", required_argument, NULL, ARG_IMAGE }, + { "commit", no_argument, NULL, ARG_COMMIT }, + { "print", no_argument, NULL, ARG_PRINT }, + { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, {} }; @@ -106,6 +111,17 @@ static int parse_argv(int argc, char *argv[]) { arg_print = true; break; + case ARG_IMAGE_POLICY: { + _cleanup_(image_policy_freep) ImagePolicy *p = NULL; + + r = image_policy_from_string(optarg, &p); + if (r < 0) + return log_error_errno(r, "Failed to parse image policy: %s", optarg); + + image_policy_free(arg_image_policy); + arg_image_policy = TAKE_PTR(p); + break; + } case '?': return -EINVAL; @@ -141,6 +157,7 @@ static int run(int argc, char *argv[]) { r = mount_image_privately_interactively( arg_image, + arg_image_policy, DISSECT_IMAGE_REQUIRE_ROOT | DISSECT_IMAGE_VALIDATE_OS | DISSECT_IMAGE_RELAX_VAR_CHECK | diff --git a/src/machine/image-dbus.c b/src/machine/image-dbus.c index bf65eecfdd4..336b42b7e51 100644 --- a/src/machine/image-dbus.c +++ b/src/machine/image-dbus.c @@ -313,7 +313,7 @@ int bus_image_method_get_hostname( int r; if (!image->metadata_valid) { - r = image_read_metadata(image); + r = image_read_metadata(image, &image_policy_container); if (r < 0) return sd_bus_error_set_errnof(error, r, "Failed to read image metadata: %m"); } @@ -331,7 +331,7 @@ int bus_image_method_get_machine_id( int r; if (!image->metadata_valid) { - r = image_read_metadata(image); + r = image_read_metadata(image, &image_policy_container); if (r < 0) return sd_bus_error_set_errnof(error, r, "Failed to read image metadata: %m"); } @@ -359,7 +359,7 @@ int bus_image_method_get_machine_info( int r; if (!image->metadata_valid) { - r = image_read_metadata(image); + r = image_read_metadata(image, &image_policy_container); if (r < 0) return sd_bus_error_set_errnof(error, r, "Failed to read image metadata: %m"); } @@ -376,7 +376,7 @@ int bus_image_method_get_os_release( int r; if (!image->metadata_valid) { - r = image_read_metadata(image); + r = image_read_metadata(image, &image_policy_container); if (r < 0) return sd_bus_error_set_errnof(error, r, "Failed to read image metadata: %m"); } diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 8abb0167910..6b74a3df131 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -234,6 +234,7 @@ static char **arg_bind_user = NULL; static bool arg_suppress_sync = false; static char *arg_settings_filename = NULL; static Architecture arg_architecture = _ARCHITECTURE_INVALID; +static ImagePolicy *arg_image_policy = NULL; STATIC_DESTRUCTOR_REGISTER(arg_directory, freep); STATIC_DESTRUCTOR_REGISTER(arg_template, freep); @@ -268,6 +269,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_cpu_set, cpu_set_reset); STATIC_DESTRUCTOR_REGISTER(arg_sysctl, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_bind_user, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_settings_filename, freep); +STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); static int handle_arg_console(const char *arg) { if (streq(arg, "help")) { @@ -330,6 +332,7 @@ static int help(void) { " remove it after exit\n" " -i --image=PATH Root file system disk image (or device node) for\n" " the container\n" + " --image-policy=POLICY Specify disk image dissection policy\n" " --oci-bundle=PATH OCI bundle directory\n" " --read-only Mount the root directory read-only\n" " --volatile[=MODE] Run the system in volatile mode\n" @@ -732,6 +735,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_LOAD_CREDENTIAL, ARG_BIND_USER, ARG_SUPPRESS_SYNC, + ARG_IMAGE_POLICY, }; static const struct option options[] = { @@ -805,6 +809,7 @@ static int parse_argv(int argc, char *argv[]) { { "load-credential", required_argument, NULL, ARG_LOAD_CREDENTIAL }, { "bind-user", required_argument, NULL, ARG_BIND_USER }, { "suppress-sync", required_argument, NULL, ARG_SUPPRESS_SYNC }, + { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, {} }; @@ -1696,6 +1701,18 @@ static int parse_argv(int argc, char *argv[]) { arg_settings_mask |= SETTING_SUPPRESS_SYNC; break; + case ARG_IMAGE_POLICY: { + _cleanup_(image_policy_freep) ImagePolicy *p = NULL; + + r = image_policy_from_string(optarg, &p); + if (r < 0) + return log_error_errno(r, "Failed to parse image policy: %s", optarg); + + image_policy_free(arg_image_policy); + arg_image_policy = TAKE_PTR(p); + break; + } + case '?': return -EINVAL; @@ -5755,7 +5772,8 @@ static int run(int argc, char *argv[]) { r = dissect_loop_device_and_warn( loop, &arg_verity_settings, - NULL, + /* mount_options=*/ NULL, + arg_image_policy ?: &image_policy_container, dissect_image_flags, &dissected_image); if (r == -ENOPKG) { diff --git a/src/partition/repart.c b/src/partition/repart.c index e4ae8a2c89f..bc3bfa15fb6 100644 --- a/src/partition/repart.c +++ b/src/partition/repart.c @@ -148,6 +148,7 @@ static FilterPartitionsType arg_filter_partitions_type = FILTER_PARTITIONS_NONE; static sd_id128_t *arg_defer_partitions = NULL; static size_t arg_n_defer_partitions = 0; static uint64_t arg_sector_size = 0; +static ImagePolicy *arg_image_policy = NULL; STATIC_DESTRUCTOR_REGISTER(arg_root, freep); STATIC_DESTRUCTOR_REGISTER(arg_image, freep); @@ -158,6 +159,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_certificate, X509_freep); STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep); STATIC_DESTRUCTOR_REGISTER(arg_tpm2_public_key, freep); STATIC_DESTRUCTOR_REGISTER(arg_filter_partitions, freep); +STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); typedef struct FreeArea FreeArea; @@ -5632,6 +5634,8 @@ static int help(void) { " --can-factory-reset Test whether factory reset is defined\n" " --root=PATH Operate relative to root path\n" " --image=PATH Operate relative to image file\n" + " --image-policy=POLICY\n" + " Specify disk image dissection policy\n" " --definitions=DIR Find partition definitions in specified directory\n" " --key-file=PATH Key to use when encrypting partitions\n" " --private-key=PATH Private key to use when generating verity roothash\n" @@ -5697,6 +5701,8 @@ static int parse_argv(int argc, char *argv[]) { ARG_EXCLUDE_PARTITIONS, ARG_DEFER_PARTITIONS, ARG_SECTOR_SIZE, + ARG_SKIP_PARTITIONS, + ARG_IMAGE_POLICY, }; static const struct option options[] = { @@ -5728,6 +5734,7 @@ static int parse_argv(int argc, char *argv[]) { { "exclude-partitions", required_argument, NULL, ARG_EXCLUDE_PARTITIONS }, { "defer-partitions", required_argument, NULL, ARG_DEFER_PARTITIONS }, { "sector-size", required_argument, NULL, ARG_SECTOR_SIZE }, + { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, {} }; @@ -6022,6 +6029,18 @@ static int parse_argv(int argc, char *argv[]) { break; + case ARG_IMAGE_POLICY: { + _cleanup_(image_policy_freep) ImagePolicy *p = NULL; + + r = image_policy_from_string(optarg, &p); + if (r < 0) + return log_error_errno(r, "Failed to parse image policy: %s", optarg); + + image_policy_free(arg_image_policy); + arg_image_policy = TAKE_PTR(p); + break; + } + case '?': return -EINVAL; @@ -6522,6 +6541,7 @@ static int run(int argc, char *argv[]) { * systems */ r = mount_image_privately_interactively( arg_image, + arg_image_policy, DISSECT_IMAGE_MOUNT_READ_ONLY | (arg_node ? DISSECT_IMAGE_DEVICE_READ_ONLY : 0) | /* If a different node to make changes to is specified let's open the device in read-only mode) */ DISSECT_IMAGE_GPT_ONLY | diff --git a/src/portable/portable.c b/src/portable/portable.c index adfd846bab7..23420abab25 100644 --- a/src/portable/portable.c +++ b/src/portable/portable.c @@ -324,6 +324,7 @@ static int portable_extract_by_path( bool path_is_extension, bool relax_extension_release_check, char **matches, + const ImagePolicy *image_policy, PortableMetadata **ret_os_release, Hashmap **ret_unit_files, sd_bus_error *error) { @@ -369,7 +370,9 @@ static int portable_extract_by_path( r = dissect_loop_device( d, - NULL, NULL, + /* verity= */ NULL, + /* mount_options= */ NULL, + image_policy, DISSECT_IMAGE_READ_ONLY | DISSECT_IMAGE_GENERIC_ROOT | DISSECT_IMAGE_REQUIRE_ROOT | @@ -510,6 +513,7 @@ static int extract_image_and_extensions( char **extension_image_paths, bool validate_sysext, bool relax_extension_release_check, + const ImagePolicy *image_policy, Image **ret_image, OrderedHashmap **ret_extension_images, OrderedHashmap **ret_extension_releases, @@ -558,7 +562,15 @@ static int extract_image_and_extensions( } } - r = portable_extract_by_path(image->path, /* path_is_extension= */ false, /* relax_extension_release_check= */ false, matches, &os_release, &unit_files, error); + r = portable_extract_by_path( + image->path, + /* path_is_extension= */ false, + /* relax_extension_release_check= */ false, + matches, + image_policy, + &os_release, + &unit_files, + error); if (r < 0) return r; @@ -591,7 +603,15 @@ static int extract_image_and_extensions( _cleanup_strv_free_ char **extension_release = NULL; const char *e; - r = portable_extract_by_path(ext->path, /* path_is_extension= */ true, relax_extension_release_check, matches, &extension_release_meta, &extra_unit_files, error); + r = portable_extract_by_path( + ext->path, + /* path_is_extension= */ true, + relax_extension_release_check, + matches, + image_policy, + &extension_release_meta, + &extra_unit_files, + error); if (r < 0) return r; @@ -657,6 +677,7 @@ int portable_extract( const char *name_or_path, char **matches, char **extension_image_paths, + const ImagePolicy *image_policy, PortableFlags flags, PortableMetadata **ret_os_release, OrderedHashmap **ret_extension_releases, @@ -679,6 +700,7 @@ int portable_extract( extension_image_paths, /* validate_sysext= */ false, /* relax_extension_release_check= */ FLAGS_SET(flags, PORTABLE_FORCE_SYSEXT), + image_policy, &image, &extension_images, &extension_releases, @@ -1392,6 +1414,7 @@ int portable_attach( char **matches, const char *profile, char **extension_image_paths, + const ImagePolicy *image_policy, PortableFlags flags, PortableChange **changes, size_t *n_changes, @@ -1412,6 +1435,7 @@ int portable_attach( extension_image_paths, /* validate_sysext= */ true, /* relax_extension_release_check= */ FLAGS_SET(flags, PORTABLE_FORCE_SYSEXT), + image_policy, &image, &extension_images, &extension_releases, diff --git a/src/portable/portable.h b/src/portable/portable.h index 1a33f30944c..c61d65fed35 100644 --- a/src/portable/portable.h +++ b/src/portable/portable.h @@ -3,6 +3,7 @@ #include "sd-bus.h" +#include "dissect-image.h" #include "hashmap.h" #include "macro.h" #include "set.h" @@ -67,9 +68,9 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(PortableMetadata*, portable_metadata_unref); int portable_metadata_hashmap_to_sorted_array(Hashmap *unit_files, PortableMetadata ***ret); -int portable_extract(const char *image, char **matches, char **extension_image_paths, PortableFlags flags, PortableMetadata **ret_os_release, OrderedHashmap **ret_extension_releases, Hashmap **ret_unit_files, char ***ret_valid_prefixes, sd_bus_error *error); +int portable_extract(const char *image, char **matches, char **extension_image_paths, const ImagePolicy *image_policy, PortableFlags flags, PortableMetadata **ret_os_release, OrderedHashmap **ret_extension_releases, Hashmap **ret_unit_files, char ***ret_valid_prefixes, sd_bus_error *error); -int portable_attach(sd_bus *bus, const char *name_or_path, char **matches, const char *profile, char **extension_images, PortableFlags flags, PortableChange **changes, size_t *n_changes, sd_bus_error *error); +int portable_attach(sd_bus *bus, const char *name_or_path, char **matches, const char *profile, char **extension_images, const ImagePolicy* image_policy, PortableFlags flags, PortableChange **changes, size_t *n_changes, sd_bus_error *error); int portable_detach(sd_bus *bus, const char *name_or_path, char **extension_image_paths, PortableFlags flags, PortableChange **changes, size_t *n_changes, sd_bus_error *error); int portable_get_state(sd_bus *bus, const char *name_or_path, char **extension_image_paths, PortableFlags flags, PortableState *ret, sd_bus_error *error); diff --git a/src/portable/portabled-image-bus.c b/src/portable/portabled-image-bus.c index 6c4cb6ec9de..262518d15cc 100644 --- a/src/portable/portabled-image-bus.c +++ b/src/portable/portabled-image-bus.c @@ -60,7 +60,7 @@ int bus_image_common_get_os_release( return 1; if (!image->metadata_valid) { - r = image_read_metadata(image); + r = image_read_metadata(image, &image_policy_service); if (r < 0) return sd_bus_error_set_errnof(error, r, "Failed to read image metadata: %m"); } @@ -163,6 +163,7 @@ int bus_image_common_get_metadata( image->path, matches, extension_images, + /* image_policy= */ NULL, flags, &os_release, &extension_releases, @@ -385,6 +386,7 @@ int bus_image_common_attach( matches, profile, extension_images, + /* image_policy= */ NULL, flags, &changes, &n_changes, @@ -729,6 +731,7 @@ int bus_image_common_reattach( matches, profile, extension_images, + /* image_policy= */ NULL, flags, &changes_attached, &n_changes_attached, diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c index 6966cfd8389..81eef8b0583 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 eed0a5629e4..86ff5d6d93a 100644 --- a/src/shared/discover-image.c +++ b/src/shared/discover-image.c @@ -1133,7 +1133,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; @@ -1214,7 +1214,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 | diff --git a/src/shared/discover-image.h b/src/shared/discover-image.h index 3c6928619c0..c423132a62b 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 "path-util.h" @@ -85,7 +86,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 6000af0ce05..83b0581ff1a 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -543,6 +543,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; @@ -1331,6 +1332,7 @@ int dissect_image_file( const char *path, const VeritySettings *verity, const MountOptions *mount_options, + const ImagePolicy *image_policy, DissectImageFlags flags, DissectedImage **ret) { @@ -1358,7 +1360,7 @@ 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; @@ -3250,6 +3252,7 @@ int dissect_loop_device( LoopDevice *loop, const VeritySettings *verity, const MountOptions *mount_options, + const ImagePolicy *image_policy, DissectImageFlags flags, DissectedImage **ret) { @@ -3267,7 +3270,7 @@ 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; @@ -3282,6 +3285,7 @@ int dissect_loop_device_and_warn( LoopDevice *loop, const VeritySettings *verity, const MountOptions *mount_options, + const ImagePolicy *image_policy, DissectImageFlags flags, DissectedImage **ret) { @@ -3293,7 +3297,7 @@ int dissect_loop_device_and_warn( name = ASSERT_PTR(loop->backing_file ?: loop->node); - r = dissect_loop_device(loop, verity, mount_options, flags, ret); + r = dissect_loop_device(loop, verity, mount_options, image_policy, flags, ret); switch (r) { case -EOPNOTSUPP: @@ -3407,6 +3411,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 +3454,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 +3524,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 +3568,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 +3577,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 2e741e82676..3af0c439bcf 100644 --- a/src/shared/dissect-image.h +++ b/src/shared/dissect-image.h @@ -133,6 +133,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 +144,10 @@ 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_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 +184,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/mount-util.c b/src/shared/mount-util.c index 1eac51b81ea..edf01fe0921 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 84ea4b63927..f52687828a6 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); diff --git a/src/sysext/sysext.c b/src/sysext/sysext.c index 5632b72f3d2..ce076f665a6 100644 --- a/src/sysext/sysext.c +++ b/src/sysext/sysext.c @@ -45,9 +45,11 @@ static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF; static PagerFlags arg_pager_flags = 0; static bool arg_legend = true; static bool arg_force = false; +static ImagePolicy *arg_image_policy = NULL; STATIC_DESTRUCTOR_REGISTER(arg_hierarchies, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_root, freep); +STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); static int is_our_mount_point(const char *p) { _cleanup_free_ char *buf = NULL, *f = NULL; @@ -523,7 +525,8 @@ static int merge_subprocess(Hashmap *images, const char *workspace) { r = dissect_loop_device_and_warn( d, &verity_settings, - NULL, + /* mount_options= */ NULL, + arg_image_policy ?: &image_policy_sysext, flags, &m); if (r < 0) @@ -734,7 +737,7 @@ static int image_discover_and_read_metadata(Hashmap **ret_images) { return log_error_errno(r, "Failed to discover extension images: %m"); HASHMAP_FOREACH(img, images) { - r = image_read_metadata(img); + r = image_read_metadata(img, &image_policy_sysext); if (r < 0) return log_error_errno(r, "Failed to read metadata for image %s: %m", img->name); } @@ -886,6 +889,8 @@ static int verb_help(int argc, char **argv, void *userdata) { " --json=pretty|short|off\n" " Generate JSON output\n" " --force Ignore version incompatibilities\n" + " --image-policy=POLICY\n" + " Specify disk image dissection policy\n" "\nSee the %2$s for details.\n", program_invocation_short_name, link, @@ -906,16 +911,18 @@ static int parse_argv(int argc, char *argv[]) { ARG_ROOT, ARG_JSON, ARG_FORCE, + ARG_IMAGE_POLICY, }; static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, - { "root", required_argument, NULL, ARG_ROOT }, - { "json", required_argument, NULL, ARG_JSON }, - { "force", no_argument, NULL, ARG_FORCE }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, + { "root", required_argument, NULL, ARG_ROOT }, + { "json", required_argument, NULL, ARG_JSON }, + { "force", no_argument, NULL, ARG_FORCE }, + { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, {} }; @@ -959,6 +966,17 @@ static int parse_argv(int argc, char *argv[]) { arg_force = true; break; + case ARG_IMAGE_POLICY: { + _cleanup_(image_policy_freep) ImagePolicy *p = NULL; + + r = image_policy_from_string(optarg, &p); + if (r < 0) + return log_error_errno(r, "Failed to parse image policy: %s", optarg); + + image_policy_free(arg_image_policy); + arg_image_policy = TAKE_PTR(p); + break; + } case '?': return -EINVAL; diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index 1a5beabc722..df4cf684355 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -121,6 +121,7 @@ bool arg_read_only = false; bool arg_mkdir = false; bool arg_marked = false; const char *arg_drop_in = NULL; +ImagePolicy *arg_image_policy = NULL; STATIC_DESTRUCTOR_REGISTER(arg_types, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_states, strv_freep); @@ -135,6 +136,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_host, unsetp); STATIC_DESTRUCTOR_REGISTER(arg_boot_loader_entry, unsetp); STATIC_DESTRUCTOR_REGISTER(arg_clean_what, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_drop_in, unsetp); +STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); static int systemctl_help(void) { _cleanup_free_ char *link = NULL; @@ -305,7 +307,9 @@ static int systemctl_help(void) { " --root=PATH Edit/enable/disable/mask unit files in the specified\n" " root directory\n" " --image=PATH Edit/enable/disable/mask unit files in the specified\n" - " image\n" + " disk image\n" + " --image-policy=POLICY\n" + " Specify disk image dissection policy\n" " -n --lines=INTEGER Number of journal entries to show\n" " -o --output=STRING Change journal output mode (short, short-precise,\n" " short-iso, short-iso-precise, short-full,\n" @@ -450,6 +454,7 @@ static int systemctl_parse_argv(int argc, char *argv[]) { ARG_NO_WARN, ARG_DROP_IN, ARG_WHEN, + ARG_IMAGE_POLICY, }; static const struct option options[] = { @@ -515,6 +520,7 @@ static int systemctl_parse_argv(int argc, char *argv[]) { { "marked", no_argument, NULL, ARG_MARKED }, { "drop-in", required_argument, NULL, ARG_DROP_IN }, { "when", required_argument, NULL, ARG_WHEN }, + { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, {} }; @@ -1003,6 +1009,18 @@ static int systemctl_parse_argv(int argc, char *argv[]) { break; + case ARG_IMAGE_POLICY: { + _cleanup_(image_policy_freep) ImagePolicy *p = NULL; + + r = image_policy_from_string(optarg, &p); + if (r < 0) + return log_error_errno(r, "Failed to parse image policy: %s", optarg); + + image_policy_free(arg_image_policy); + arg_image_policy = TAKE_PTR(p); + break; + } + case '.': /* Output an error mimicking getopt, and print a hint afterwards */ log_error("%s: invalid option -- '.'", program_invocation_name); @@ -1248,6 +1266,7 @@ static int run(int argc, char *argv[]) { r = mount_image_privately_interactively( arg_image, + arg_image_policy, DISSECT_IMAGE_GENERIC_ROOT | DISSECT_IMAGE_REQUIRE_ROOT | DISSECT_IMAGE_RELAX_VAR_CHECK | diff --git a/src/systemctl/systemctl.h b/src/systemctl/systemctl.h index 8c96ee0b4f0..03645624ee8 100644 --- a/src/systemctl/systemctl.h +++ b/src/systemctl/systemctl.h @@ -5,6 +5,7 @@ #include "bus-print-properties.h" #include "bus-util.h" +#include "image-policy.h" #include "install.h" #include "output-mode.h" #include "pager.h" @@ -100,6 +101,7 @@ extern bool arg_read_only; extern bool arg_mkdir; extern bool arg_marked; extern const char *arg_drop_in; +extern ImagePolicy *arg_image_policy; static inline const char* arg_job_mode(void) { return _arg_job_mode ?: "replace"; diff --git a/src/sysupdate/sysupdate.c b/src/sysupdate/sysupdate.c index 6cb09dae50d..f62e1930562 100644 --- a/src/sysupdate/sysupdate.c +++ b/src/sysupdate/sysupdate.c @@ -46,11 +46,13 @@ static char *arg_image = NULL; static bool arg_reboot = false; static char *arg_component = NULL; static int arg_verify = -1; +static ImagePolicy *arg_image_policy = NULL; STATIC_DESTRUCTOR_REGISTER(arg_definitions, freep); STATIC_DESTRUCTOR_REGISTER(arg_root, freep); STATIC_DESTRUCTOR_REGISTER(arg_image, freep); STATIC_DESTRUCTOR_REGISTER(arg_component, freep); +STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); typedef struct Context { Transfer **transfers; @@ -872,6 +874,7 @@ static int process_image( r = mount_image_privately_interactively( arg_image, + arg_image_policy, (ro ? DISSECT_IMAGE_READ_ONLY : 0) | DISSECT_IMAGE_FSCK | DISSECT_IMAGE_MKDIR | @@ -1022,7 +1025,7 @@ static int verb_pending_or_reboot(int argc, char **argv, void *userdata) { if (arg_image || arg_root) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "The --root=/--image switches may not be combined with the '%s' operation.", argv[0]); + "The --root=/--image= switches may not be combined with the '%s' operation.", argv[0]); r = context_make_offline(&context, NULL); if (r < 0) @@ -1205,8 +1208,10 @@ static int verb_help(int argc, char **argv, void *userdata) { "\n%3$sOptions:%4$s\n" " -C --component=NAME Select component to update\n" " --definitions=DIR Find transfer definitions in specified directory\n" - " --root=PATH Operate relative to root path\n" - " --image=PATH Operate relative to image file\n" + " --root=PATH Operate on an alternate filesystem root\n" + " --image=PATH Operate on disk image as filesystem root\n" + " --image-policy=POLICY\n" + " Specify disk image dissection policy\n" " -m --instances-max=INT How many instances to maintain\n" " --sync=BOOL Controls whether to sync data to disk\n" " --verify=BOOL Force signature verification on or off\n" @@ -1238,6 +1243,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_IMAGE, ARG_REBOOT, ARG_VERIFY, + ARG_IMAGE_POLICY, }; static const struct option options[] = { @@ -1254,6 +1260,7 @@ static int parse_argv(int argc, char *argv[]) { { "reboot", no_argument, NULL, ARG_REBOOT }, { "component", required_argument, NULL, 'C' }, { "verify", required_argument, NULL, ARG_VERIFY }, + { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, {} }; @@ -1351,6 +1358,17 @@ static int parse_argv(int argc, char *argv[]) { break; } + case ARG_IMAGE_POLICY: { + _cleanup_(image_policy_freep) ImagePolicy *p = NULL; + + r = image_policy_from_string(optarg, &p); + if (r < 0) + return log_error_errno(r, "Failed to parse image policy: %s", optarg); + + image_policy_free(arg_image_policy); + arg_image_policy = TAKE_PTR(p); + break; + } case '?': return -EINVAL; diff --git a/src/sysusers/sysusers.c b/src/sysusers/sysusers.c index f9f694bd549..a2d62121e0b 100644 --- a/src/sysusers/sysusers.c +++ b/src/sysusers/sysusers.c @@ -99,6 +99,7 @@ static const char *arg_replace = NULL; static bool arg_dry_run = false; static bool arg_inline = false; static PagerFlags arg_pager_flags = 0; +static ImagePolicy *arg_image_policy = NULL; static OrderedHashmap *users = NULL, *groups = NULL; static OrderedHashmap *todo_uids = NULL, *todo_gids = NULL; @@ -128,6 +129,7 @@ STATIC_DESTRUCTOR_REGISTER(database_groups, set_free_freep); STATIC_DESTRUCTOR_REGISTER(uid_range, uid_range_freep); STATIC_DESTRUCTOR_REGISTER(arg_root, freep); STATIC_DESTRUCTOR_REGISTER(arg_image, freep); +STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); static int errno_is_not_exists(int code) { /* See getpwnam(3) and getgrnam(3): those codes and others can be returned if the user or group are @@ -1964,6 +1966,7 @@ static int help(void) { " --cat-config Show configuration files\n" " --root=PATH Operate on an alternate filesystem root\n" " --image=PATH Operate on disk image as filesystem root\n" + " --image-policy=POLICY Specify disk image dissection policy\n" " --replace=PATH Treat arguments as replacement for PATH\n" " --dry-run Just print what would be done\n" " --inline Treat arguments as configuration lines\n" @@ -1986,18 +1989,20 @@ static int parse_argv(int argc, char *argv[]) { ARG_DRY_RUN, ARG_INLINE, ARG_NO_PAGER, + ARG_IMAGE_POLICY, }; static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "cat-config", no_argument, NULL, ARG_CAT_CONFIG }, - { "root", required_argument, NULL, ARG_ROOT }, - { "image", required_argument, NULL, ARG_IMAGE }, - { "replace", required_argument, NULL, ARG_REPLACE }, - { "dry-run", no_argument, NULL, ARG_DRY_RUN }, - { "inline", no_argument, NULL, ARG_INLINE }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "cat-config", no_argument, NULL, ARG_CAT_CONFIG }, + { "root", required_argument, NULL, ARG_ROOT }, + { "image", required_argument, NULL, ARG_IMAGE }, + { "replace", required_argument, NULL, ARG_REPLACE }, + { "dry-run", no_argument, NULL, ARG_DRY_RUN }, + { "inline", no_argument, NULL, ARG_INLINE }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, {} }; @@ -2058,6 +2063,17 @@ static int parse_argv(int argc, char *argv[]) { arg_pager_flags |= PAGER_DISABLE; break; + case ARG_IMAGE_POLICY: { + _cleanup_(image_policy_freep) ImagePolicy *p = NULL; + + r = image_policy_from_string(optarg, &p); + if (r < 0) + return log_error_errno(r, "Failed to parse image policy: %s", optarg); + + image_policy_free(arg_image_policy); + arg_image_policy = TAKE_PTR(p); + break; + } case '?': return -EINVAL; @@ -2173,6 +2189,7 @@ static int run(int argc, char *argv[]) { r = mount_image_privately_interactively( arg_image, + arg_image_policy, DISSECT_IMAGE_GENERIC_ROOT | DISSECT_IMAGE_REQUIRE_ROOT | DISSECT_IMAGE_VALIDATE_OS | diff --git a/src/test/test-loop-block.c b/src/test/test-loop-block.c index 97c2f66ac99..d8f48798cb0 100644 --- a/src/test/test-loop-block.c +++ b/src/test/test-loop-block.c @@ -82,7 +82,7 @@ static void* thread_func(void *ptr) { log_notice("Acquired loop device %s, will mount on %s", loop->node, mounted); - r = dissect_loop_device(loop, NULL, NULL, DISSECT_IMAGE_READ_ONLY|DISSECT_IMAGE_ADD_PARTITION_DEVICES|DISSECT_IMAGE_PIN_PARTITION_DEVICES, &dissected); + r = dissect_loop_device(loop, NULL, NULL, NULL, DISSECT_IMAGE_READ_ONLY|DISSECT_IMAGE_ADD_PARTITION_DEVICES|DISSECT_IMAGE_PIN_PARTITION_DEVICES, &dissected); if (r < 0) log_error_errno(r, "Failed dissect loopback device %s: %m", loop->node); assert_se(r >= 0); @@ -213,7 +213,7 @@ static int run(int argc, char *argv[]) { sfdisk = NULL; #if HAVE_BLKID - assert_se(dissect_image_file(p, NULL, NULL, 0, &dissected) >= 0); + assert_se(dissect_image_file(p, NULL, NULL, NULL, 0, &dissected) >= 0); verify_dissected_image(dissected); dissected = dissected_image_unref(dissected); #endif @@ -231,7 +231,7 @@ static int run(int argc, char *argv[]) { assert_se(loop_device_make(fd, O_RDWR, 0, UINT64_MAX, 0, LO_FLAGS_PARTSCAN, LOCK_EX, &loop) >= 0); #if HAVE_BLKID - assert_se(dissect_loop_device(loop, NULL, NULL, DISSECT_IMAGE_ADD_PARTITION_DEVICES|DISSECT_IMAGE_PIN_PARTITION_DEVICES, &dissected) >= 0); + assert_se(dissect_loop_device(loop, NULL, NULL, NULL, DISSECT_IMAGE_ADD_PARTITION_DEVICES|DISSECT_IMAGE_PIN_PARTITION_DEVICES, &dissected) >= 0); verify_dissected_image(dissected); FOREACH_STRING(fs, "vfat", "ext4") { @@ -267,12 +267,12 @@ static int run(int argc, char *argv[]) { /* Try to read once, without pinning or adding partitions, i.e. by only accessing the whole block * device. */ - assert_se(dissect_loop_device(loop, NULL, NULL, 0, &dissected) >= 0); + assert_se(dissect_loop_device(loop, NULL, NULL, NULL, 0, &dissected) >= 0); verify_dissected_image_harder(dissected); dissected = dissected_image_unref(dissected); /* Now go via the loopback device after all, but this time add/pin, because now we want to mount it. */ - assert_se(dissect_loop_device(loop, NULL, NULL, DISSECT_IMAGE_ADD_PARTITION_DEVICES|DISSECT_IMAGE_PIN_PARTITION_DEVICES, &dissected) >= 0); + assert_se(dissect_loop_device(loop, NULL, NULL, NULL, DISSECT_IMAGE_ADD_PARTITION_DEVICES|DISSECT_IMAGE_PIN_PARTITION_DEVICES, &dissected) >= 0); verify_dissected_image_harder(dissected); assert_se(mkdtemp_malloc(NULL, &mounted) >= 0); diff --git a/src/test/test-namespace.c b/src/test/test-namespace.c index 72155127c1e..82be09dd6a6 100644 --- a/src/test/test-namespace.c +++ b/src/test/test-namespace.c @@ -176,6 +176,7 @@ TEST(protect_kernel_logs) { assert_se(fd > 0); r = setup_namespace(NULL, + NULL, NULL, NULL, &ns_info, @@ -193,6 +194,7 @@ TEST(protect_kernel_logs) { NULL, NULL, NULL, + NULL, 0, NULL, 0, @@ -208,6 +210,7 @@ TEST(protect_kernel_logs) { NULL, NULL, NULL, + NULL, NULL); assert_se(r == 0); diff --git a/src/test/test-ns.c b/src/test/test-ns.c index 7eb29d109d1..485069670b4 100644 --- a/src/test/test-ns.c +++ b/src/test/test-ns.c @@ -77,6 +77,7 @@ int main(int argc, char *argv[]) { log_info("Not chrooted"); r = setup_namespace(root_directory, + NULL, NULL, NULL, &ns_info, @@ -91,6 +92,7 @@ int main(int argc, char *argv[]) { &(TemporaryFileSystem) { .path = (char*) "/var", .options = (char*) "ro" }, 1, NULL, 0, + NULL, tmp_dir, var_tmp_dir, NULL, @@ -110,6 +112,7 @@ int main(int argc, char *argv[]) { NULL, NULL, NULL, + NULL, NULL); if (r < 0) { log_error_errno(r, "Failed to set up namespace: %m"); diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index 6cd76e8df84..fdabd7d2c56 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -206,6 +206,7 @@ static char **arg_exclude_prefixes = NULL; static char *arg_root = NULL; static char *arg_image = NULL; static char *arg_replace = NULL; +static ImagePolicy *arg_image_policy = NULL; #define MAX_DEPTH 256 @@ -219,6 +220,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_include_prefixes, freep); STATIC_DESTRUCTOR_REGISTER(arg_exclude_prefixes, freep); STATIC_DESTRUCTOR_REGISTER(arg_root, freep); STATIC_DESTRUCTOR_REGISTER(arg_image, freep); +STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); static const char *const creation_mode_verb_table[_CREATION_MODE_MAX] = { [CREATION_NORMAL] = "Created", @@ -3699,6 +3701,7 @@ static int help(void) { " -E Ignore rules prefixed with /dev, /proc, /run, /sys\n" " --root=PATH Operate on an alternate filesystem root\n" " --image=PATH Operate on disk image as filesystem root\n" + " --image-policy=POLICY Specify disk image dissection policy\n" " --replace=PATH Treat arguments as replacement for PATH\n" " --no-pager Do not pipe output into a pager\n" "\nSee the %s for details.\n", @@ -3726,6 +3729,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_IMAGE, ARG_REPLACE, ARG_NO_PAGER, + ARG_IMAGE_POLICY, }; static const struct option options[] = { @@ -3743,6 +3747,7 @@ static int parse_argv(int argc, char *argv[]) { { "image", required_argument, NULL, ARG_IMAGE }, { "replace", required_argument, NULL, ARG_REPLACE }, { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, {} }; @@ -3833,6 +3838,17 @@ static int parse_argv(int argc, char *argv[]) { arg_pager_flags |= PAGER_DISABLE; break; + case ARG_IMAGE_POLICY: { + _cleanup_(image_policy_freep) ImagePolicy *p = NULL; + + r = image_policy_from_string(optarg, &p); + if (r < 0) + return log_error_errno(r, "Failed to parse image policy: %s", optarg); + + image_policy_free(arg_image_policy); + arg_image_policy = TAKE_PTR(p); + break; + } case '?': return -EINVAL; @@ -4153,6 +4169,7 @@ static int run(int argc, char *argv[]) { r = mount_image_privately_interactively( arg_image, + arg_image_policy, DISSECT_IMAGE_GENERIC_ROOT | DISSECT_IMAGE_REQUIRE_ROOT | DISSECT_IMAGE_VALIDATE_OS | From cd22d8562dd085f5c234cf26b4dd773029418833 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 30 Nov 2022 18:56:28 +0100 Subject: [PATCH 03/14] dissect: actually enforce policy --- src/shared/dissect-image.c | 275 +++++++++++++++++++++++++++++++------ src/shared/dissect-image.h | 1 + 2 files changed, 235 insertions(+), 41 deletions(-) diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index 83b0581ff1a..e1cfb7622f5 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; @@ -651,6 +762,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); @@ -658,10 +797,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) @@ -682,7 +817,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); @@ -1050,6 +1185,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 @@ -1140,6 +1287,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; @@ -1224,41 +1381,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; + } + + 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, + }; } - - 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, - }; } } @@ -1320,7 +1485,35 @@ static int dissect_image( } } - r = dissected_image_probe_filesystems(m, fd); + /* 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; + + 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; + } + } + + r = dissected_image_probe_filesystems(m, fd, policy); if (r < 0) return r; diff --git a/src/shared/dissect-image.h b/src/shared/dissect-image.h index 3af0c439bcf..af9798b9407 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 */ From 7cd7a19568ffcac3064a5fa4502315a01a19b9be Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 2 Dec 2022 10:47:20 +0100 Subject: [PATCH 04/14] dissect: add dissect_image_file_and_warn() This is to dissect_image_file() what dissect_loop_device_and_warn() is to dissect_loop_device(), i.e. it dissects the image file and logs an error string if that fails instead of just returning an error. --- src/shared/dissect-image.c | 108 ++++++++++++++++++++++--------------- src/shared/dissect-image.h | 1 + 2 files changed, 65 insertions(+), 44 deletions(-) diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index e1cfb7622f5..76251b3a079 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -1564,6 +1564,66 @@ int dissect_image_file( #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); + + 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; @@ -3482,53 +3542,13 @@ int dissect_loop_device_and_warn( DissectImageFlags flags, DissectedImage **ret) { - const char *name; - int r; - assert(loop); - assert(loop->fd >= 0); - name = ASSERT_PTR(loop->backing_file ?: loop->node); + return dissect_log_error( + dissect_loop_device(loop, verity, mount_options, image_policy, flags, ret), + loop->backing_file ?: loop->node, + verity); - r = dissect_loop_device(loop, verity, mount_options, image_policy, 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); - - 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) { diff --git a/src/shared/dissect-image.h b/src/shared/dissect-image.h index af9798b9407..3043a3d9e56 100644 --- a/src/shared/dissect-image.h +++ b/src/shared/dissect-image.h @@ -147,6 +147,7 @@ static inline int probe_filesystem(const char *path, char **ret_fstype) { } 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); From 93a8a85be3585c6f011853dad2888e5d0d3159d6 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 2 Dec 2022 10:48:47 +0100 Subject: [PATCH 05/14] dissect: make returning of DissectedImage object optional Sometimes, we just want to validate if an image (or image policy) works, hence let's make the returning optional. --- src/shared/dissect-image.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index 76251b3a079..9cc8d431476 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -1535,7 +1535,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) @@ -1557,7 +1556,8 @@ int dissect_image_file( if (r < 0) return r; - *ret = TAKE_PTR(m); + if (ret) + *ret = TAKE_PTR(m); return 0; #else return -EOPNOTSUPP; @@ -3514,7 +3514,6 @@ int dissect_loop_device( int r; assert(loop); - assert(ret); r = dissected_image_new(loop->backing_file ?: loop->node, &m); if (r < 0) @@ -3527,7 +3526,9 @@ int dissect_loop_device( if (r < 0) return r; - *ret = TAKE_PTR(m); + if (ret) + *ret = TAKE_PTR(m); + return 0; #else return -EOPNOTSUPP; From dee4a6237ab5913a6cac3f2afa5ba6ae1f35080a Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 2 Dec 2022 15:00:44 +0100 Subject: [PATCH 06/14] dissect: add new --validate command This allows unprivileged validation of DDIs. Only superficial structure, i.e. not mounting or so. This becomes particularly handy in the integration tests, and to validate image policies. --- man/systemd-dissect.xml | 14 ++++++++++++ src/dissect/dissect.c | 48 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/man/systemd-dissect.xml b/man/systemd-dissect.xml index 06c57a22ecd..f388cde3c65 100644 --- a/man/systemd-dissect.xml +++ b/man/systemd-dissect.xml @@ -281,6 +281,20 @@ on. + + + + Validates the partition arrangement of a disk image (DDI), and ensures it matches the + image policy specified via , if one is specified. This parses the + partition table and probes the file systems in the image, but does not attempt to mount them (nor to + set up disk encryption/authentication via LUKS/Verity). It does this taking the configured image + dissection policy into account. Since this operation does not mount file systems, this command – + unlike all other commands implemented by this tool – requires no privileges other than the ability to + access the specified file. Prints "OK" and returns zero if the image appears to be in order and + matches the specified image dissection policy. Otherwise prints an error message and returns + non-zero. + + diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index b3f20e193e5..f89a9dff204 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -61,6 +61,7 @@ static enum { ACTION_COPY_FROM, ACTION_COPY_TO, ACTION_DISCOVER, + ACTION_VALIDATE, } arg_action = ACTION_DISSECT; static char *arg_image = NULL; static char *arg_path = NULL; @@ -148,6 +149,7 @@ static int help(void) { " -x --copy-from Copy files from image to host\n" " -a --copy-to Copy files from host to image\n" " --discover Discover DDIs in well known directories\n" + " --validate Validate image and image policy\n" "\nSee the %2$s for details.\n", program_invocation_short_name, link, @@ -225,6 +227,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_DETACH, ARG_LOOP_REF, ARG_IMAGE_POLICY, + ARG_VALIDATE, }; static const struct option options[] = { @@ -255,6 +258,7 @@ static int parse_argv(int argc, char *argv[]) { { "discover", no_argument, NULL, ARG_DISCOVER }, { "loop-ref", required_argument, NULL, ARG_LOOP_REF }, { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, + { "validate", no_argument, NULL, ARG_VALIDATE }, {} }; @@ -474,6 +478,10 @@ static int parse_argv(int argc, char *argv[]) { break; } + case ARG_VALIDATE: + arg_action = ACTION_VALIDATE; + break; + case '?': return -EINVAL; @@ -610,7 +618,19 @@ static int parse_argv(int argc, char *argv[]) { if (optind != argc) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected no argument."); + break; + case ACTION_VALIDATE: + if (optind + 1 != argc) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Expected an image file path as only argument."); + + r = parse_path_argument(argv[optind], /* suppress_root= */ false, &arg_image); + if (r < 0) + return r; + + arg_flags |= DISSECT_IMAGE_READ_ONLY; + arg_flags &= ~(DISSECT_IMAGE_PIN_PARTITION_DEVICES|DISSECT_IMAGE_ADD_PARTITION_DEVICES); break; default: @@ -1706,6 +1726,31 @@ static int action_detach(const char *path) { return 0; } +static int action_validate(void) { + int r; + + r = dissect_image_file_and_warn( + arg_image, + &arg_verity_settings, + NULL, + arg_image_policy, + arg_flags, + NULL); + if (r < 0) + return r; + + if (isatty(STDOUT_FILENO) && emoji_enabled()) + printf("%s ", special_glyph(SPECIAL_GLYPH_SPARKLES)); + + printf("%sOK%s", ansi_highlight_green(), ansi_normal()); + + if (isatty(STDOUT_FILENO) && emoji_enabled()) + printf(" %s", special_glyph(SPECIAL_GLYPH_SPARKLES)); + + putc('\n', stdout); + return 0; +} + static int run(int argc, char *argv[]) { _cleanup_(dissected_image_unrefp) DissectedImage *m = NULL; _cleanup_(loop_device_unrefp) LoopDevice *d = NULL; @@ -1748,6 +1793,9 @@ static int run(int argc, char *argv[]) { * available we turn off partition table * support */ + if (arg_action == ACTION_VALIDATE) + return action_validate(); + open_flags = FLAGS_SET(arg_flags, DISSECT_IMAGE_DEVICE_READ_ONLY) ? O_RDONLY : O_RDWR; loop_flags = FLAGS_SET(arg_flags, DISSECT_IMAGE_NO_PARTITION_TABLE) ? 0 : LO_FLAGS_PARTSCAN; From 598fd4da1cf9665834110583fd9133073cc12481 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 2 Dec 2022 15:05:49 +0100 Subject: [PATCH 07/14] dissect: disallow empty partition tables If we don't find a single useful partition table, refusing dissection. (Except in systemd-dissect, when we are supposed to show DDI information, in that case allow this to run and show general DDI information, i.e. size, UUID and name at least) --- src/dissect/dissect.c | 3 ++- src/shared/dissect-image.c | 10 ++++++++++ src/shared/dissect-image.h | 1 + 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index f89a9dff204..b53c2cc3571 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -501,7 +501,8 @@ static int parse_argv(int argc, char *argv[]) { if (r < 0) return r; - arg_flags |= DISSECT_IMAGE_READ_ONLY; + /* when dumping image info be even more liberal than otherwise, do not even require a single valid partition */ + arg_flags |= DISSECT_IMAGE_READ_ONLY|DISSECT_IMAGE_ALLOW_EMPTY; break; case ACTION_MOUNT: diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index 9cc8d431476..97414d2c8f9 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -1485,6 +1485,8 @@ static int dissect_image( } } + 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) */ @@ -1492,6 +1494,8 @@ static int dissect_image( PartitionDesignator vi, si; PartitionPolicyFlags found_flags; + any = any || m->partitions[di].found; + vi = partition_verity_of(di); si = partition_verity_sig_of(di); @@ -1513,6 +1517,9 @@ static int dissect_image( } } + if (!any && !FLAGS_SET(flags, DISSECT_IMAGE_ALLOW_EMPTY)) + return -ENOMSG; + r = dissected_image_probe_filesystems(m, fd, policy); if (r < 0) return r; @@ -1605,6 +1612,9 @@ static int dissect_log_error(int r, const char *name, const VeritySettings *veri 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); } diff --git a/src/shared/dissect-image.h b/src/shared/dissect-image.h index 3043a3d9e56..a55ad63d2d0 100644 --- a/src/shared/dissect-image.h +++ b/src/shared/dissect-image.h @@ -80,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 { From 97ce55e3e59f0361af4da6b09cce7e239927f812 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 2 Dec 2022 15:06:09 +0100 Subject: [PATCH 08/14] dissect: update error code comment a bit --- src/shared/dissect-image.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index 97414d2c8f9..5ec4fbf2e13 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -684,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; From a62e12dad12434aeecccd58e78bcae4cf1b0d730 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 6 Dec 2022 21:15:06 +0100 Subject: [PATCH 09/14] analyze: add 'image-policy' tool for analyzing image dissection policies --- src/analyze/analyze-image-policy.c | 152 +++++++++++++++++++++++++++++ src/analyze/analyze-image-policy.h | 3 + src/analyze/analyze.c | 2 + src/analyze/meson.build | 1 + test/units/testsuite-65.sh | 12 +++ 5 files changed, 170 insertions(+) create mode 100644 src/analyze/analyze-image-policy.c create mode 100644 src/analyze/analyze-image-policy.h diff --git a/src/analyze/analyze-image-policy.c b/src/analyze/analyze-image-policy.c new file mode 100644 index 00000000000..e670fe5c4da --- /dev/null +++ b/src/analyze/analyze-image-policy.c @@ -0,0 +1,152 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "analyze-image-policy.h" +#include "analyze.h" +#include "format-table.h" +#include "terminal-util.h" + +static int table_add_designator_line(Table *table, PartitionDesignator d, PartitionPolicyFlags f) { + _cleanup_free_ char *q = NULL; + const char *color; + int r; + + assert(table); + assert(f >= 0); + + if (partition_policy_flags_to_string(f & _PARTITION_POLICY_USE_MASK, /* simplify= */ true, &q) < 0) + return log_oom(); + + color = (f & _PARTITION_POLICY_USE_MASK) == PARTITION_POLICY_IGNORE ? ansi_grey() : + ((f & (PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ABSENT)) == + (PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ABSENT)) ? ansi_highlight_yellow() : + (f & _PARTITION_POLICY_USE_MASK) == PARTITION_POLICY_ABSENT ? ansi_highlight_red() : + !(f & PARTITION_POLICY_UNPROTECTED) ? ansi_highlight_green() : NULL; + + if (d < 0) + r = table_add_many(table, + TABLE_STRING, "default", + TABLE_SET_COLOR, ansi_highlight_green(), + TABLE_STRING, q, + TABLE_SET_COLOR, color); + else + r = table_add_many(table, + TABLE_STRING, partition_designator_to_string(d), + TABLE_SET_COLOR, ansi_normal(), + TABLE_STRING, q, + TABLE_SET_COLOR, color); + if (r < 0) + return table_log_add_error(r); + + switch (f & _PARTITION_POLICY_READ_ONLY_MASK) { + + case PARTITION_POLICY_READ_ONLY_ON: + r = table_add_many(table, TABLE_BOOLEAN, true); + break; + + case PARTITION_POLICY_READ_ONLY_OFF: + r = table_add_many(table, TABLE_BOOLEAN, false); + break; + + default: + r = table_add_many(table, TABLE_EMPTY); + break; + } + if (r < 0) + return table_log_add_error(r); + + switch (f & _PARTITION_POLICY_GROWFS_MASK) { + + case PARTITION_POLICY_GROWFS_ON: + r = table_add_many(table, TABLE_BOOLEAN, true); + break; + + case PARTITION_POLICY_GROWFS_OFF: + r = table_add_many(table, TABLE_BOOLEAN, false); + break; + + default: + r = table_add_many(table, TABLE_EMPTY); + break; + } + + if (r < 0) + return table_log_add_error(r); + + return 0; +} + +int verb_image_policy(int argc, char *argv[], void *userdata) { + int r; + + for (int i = 1; i < argc; i++) { + _cleanup_(table_unrefp) Table *table = NULL; + _cleanup_(image_policy_freep) ImagePolicy *pbuf = NULL; + _cleanup_free_ char *as_string = NULL, *as_string_simplified = NULL; + const ImagePolicy *p; + + /* NB: The magic '@' strings are not officially documented for now, since we might change + * around defaults (and in particular where precisely to reuse policy). We should document + * them once the dust has settled a bit. For now it's just useful for debugging and + * introspect our own defaults without guaranteeing API safety. */ + if (streq(argv[i], "@sysext")) + p = &image_policy_sysext; + else if (streq(argv[i], "@container")) + p = &image_policy_container; + else if (streq(argv[i], "@service")) + p = &image_policy_service; + else if (streq(argv[i], "@host")) + p = &image_policy_host; + else { + r = image_policy_from_string(argv[i], &pbuf); + if (r < 0) + return log_error_errno(r, "Failed to parse image policy '%s': %m", argv[i]); + + p = pbuf; + } + + r = image_policy_to_string(p, /* simplify= */ false, &as_string); + if (r < 0) + return log_error_errno(r, "Failed to format policy '%s' as string: %m", argv[i]); + + r = image_policy_to_string(p, /* simplify= */ true, &as_string_simplified); + if (r < 0) + return log_error_errno(r, "Failed to format policy '%s' as string: %m", argv[i]); + + pager_open(arg_pager_flags); + + if (streq(as_string, as_string_simplified)) + printf("Analyzing policy: %s%s%s\n", ansi_highlight_magenta_underline(), as_string, ansi_normal()); + else + printf("Analyzing policy: %s%s%s\n" + " Long form: %s%s%s\n", + ansi_highlight(), as_string_simplified, ansi_normal(), + ansi_grey(), as_string, ansi_normal()); + + table = table_new("partition", "mode", "read-only", "growfs"); + if (!table) + return log_oom(); + + (void) table_set_ersatz_string(table, TABLE_ERSATZ_DASH); + + for (PartitionDesignator d = 0; d < _PARTITION_DESIGNATOR_MAX; d++) { + PartitionPolicyFlags f = image_policy_get_exhaustively(p, d); + assert(f >= 0); + + r = table_add_designator_line(table, d, f); + if (r < 0) + return r; + } + + r = table_add_designator_line(table, _PARTITION_DESIGNATOR_INVALID, image_policy_default(p)); + if (r < 0) + return r; + + putc('\n', stdout); + + r = table_print(table, NULL); + if (r < 0) + return r; + } + + return EXIT_SUCCESS; +} diff --git a/src/analyze/analyze-image-policy.h b/src/analyze/analyze-image-policy.h new file mode 100644 index 00000000000..fa08447822a --- /dev/null +++ b/src/analyze/analyze-image-policy.h @@ -0,0 +1,3 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +int verb_image_policy(int argc, char *argv[], void *userdata); diff --git a/src/analyze/analyze.c b/src/analyze/analyze.c index 8bc533b20d8..ddc71b98b07 100644 --- a/src/analyze/analyze.c +++ b/src/analyze/analyze.c @@ -39,6 +39,7 @@ #include "analyze-unit-files.h" #include "analyze-unit-paths.h" #include "analyze-verify.h" +#include "analyze-image-policy.h" #include "build.h" #include "bus-error.h" #include "bus-locator.h" @@ -640,6 +641,7 @@ static int run(int argc, char *argv[]) { { "inspect-elf", 2, VERB_ANY, 0, verb_elf_inspection }, { "malloc", VERB_ANY, VERB_ANY, 0, verb_malloc }, { "fdstore", 2, VERB_ANY, 0, verb_fdstore }, + { "image-policy", 2, 2, 0, verb_image_policy }, {} }; diff --git a/src/analyze/meson.build b/src/analyze/meson.build index 695089a0bed..c50c35f09f6 100644 --- a/src/analyze/meson.build +++ b/src/analyze/meson.build @@ -13,6 +13,7 @@ systemd_analyze_sources = files( 'analyze-exit-status.c', 'analyze-fdstore.c', 'analyze-filesystems.c', + 'analyze-image-policy.c', 'analyze-inspect-elf.c', 'analyze-log-control.c', 'analyze-malloc.c', diff --git a/test/units/testsuite-65.sh b/test/units/testsuite-65.sh index edaf6671071..f28edf275e5 100755 --- a/test/units/testsuite-65.sh +++ b/test/units/testsuite-65.sh @@ -816,6 +816,18 @@ name=$(echo "$output" | awk '{ print $4 }') check deny yes /run/systemd/transient/"$name" check deny no "$name" +# Let's also test the "image-policy" verb + +systemd-analyze image-policy '*' 2>&1 | grep -q -F "Long form: =verity+signed+encrypted+unprotected+unused+absent" +systemd-analyze image-policy '-' 2>&1 | grep -q -F "Long form: =unused+absent" +systemd-analyze image-policy 'home=encrypted:usr=verity' 2>&1 | grep -q -F "Long form: usr=verity:home=encrypted:=unused+absent" +systemd-analyze image-policy 'home=encrypted:usr=verity' 2>&1 | grep -q -e '^home \+encrypted \+' +systemd-analyze image-policy 'home=encrypted:usr=verity' 2>&1 | grep -q -e '^usr \+verity \+' +systemd-analyze image-policy 'home=encrypted:usr=verity' 2>&1 | grep -q -e '^root \+ignore \+' +systemd-analyze image-policy 'home=encrypted:usr=verity' 2>&1 | grep -q -e '^usr-verity \+unprotected \+' + +(! systemd-analyze image-policy 'doedel' ) + systemd-analyze log-level info echo OK >/testok From 73740c9f842a6fda8bba9af65dff82658a9aec90 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 13 Dec 2022 16:27:38 +0100 Subject: [PATCH 10/14] discover-image: automaticaly pick up sysext images from /.extra/sysext --- src/shared/discover-image.c | 28 +++++++++++++++++++++++++--- units/systemd-sysext.service | 1 + 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/shared/discover-image.c b/src/shared/discover-image.c index 86ff5d6d93a..df3d5b77656 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" @@ -68,6 +69,19 @@ static const char* const image_search_path[_IMAGE_CLASS_MAX] = { "/var/lib/extensions\0", /* the main place for images */ }; +/* 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); @@ -441,6 +455,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, @@ -456,7 +478,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; @@ -555,7 +577,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; @@ -1284,7 +1306,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/units/systemd-sysext.service b/units/systemd-sysext.service index 9a8d4ebc5f8..5999d38d35c 100644 --- a/units/systemd-sysext.service +++ b/units/systemd-sysext.service @@ -15,6 +15,7 @@ ConditionCapability=CAP_SYS_ADMIN ConditionDirectoryNotEmpty=|/etc/extensions ConditionDirectoryNotEmpty=|/run/extensions ConditionDirectoryNotEmpty=|/var/lib/extensions +ConditionDirectoryNotEmpty=|/.extra/sysext DefaultDependencies=no After=local-fs.target From a594288d79f27147d95662927aa67c0567deb6cc Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 13 Dec 2022 16:27:48 +0100 Subject: [PATCH 11/14] sysext: default to a stricter image policy when reading /.extra/sysext/ DDIs --- src/analyze/analyze-image-policy.c | 2 ++ src/shared/image-policy.c | 10 ++++++++++ src/shared/image-policy.h | 3 ++- src/sysext/sysext.c | 20 +++++++++++++++++++- src/test/test-image-policy.c | 1 + 5 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/analyze/analyze-image-policy.c b/src/analyze/analyze-image-policy.c index e670fe5c4da..026216629c6 100644 --- a/src/analyze/analyze-image-policy.c +++ b/src/analyze/analyze-image-policy.c @@ -90,6 +90,8 @@ int verb_image_policy(int argc, char *argv[], void *userdata) { * introspect our own defaults without guaranteeing API safety. */ if (streq(argv[i], "@sysext")) p = &image_policy_sysext; + else if (streq(argv[i], "@sysext-strict")) + p = &image_policy_sysext_strict; else if (streq(argv[i], "@container")) p = &image_policy_container; else if (streq(argv[i], "@service")) diff --git a/src/shared/image-policy.c b/src/shared/image-policy.c index 98c58c09010..5baeac4c5d8 100644 --- a/src/shared/image-policy.c +++ b/src/shared/image-policy.c @@ -631,6 +631,16 @@ const ImagePolicy image_policy_sysext = { .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, diff --git a/src/shared/image-policy.h b/src/shared/image-policy.h index 278c06c36a6..a5e37642afa 100644 --- a/src/shared/image-policy.h +++ b/src/shared/image-policy.h @@ -57,7 +57,8 @@ struct ImagePolicy { extern const ImagePolicy image_policy_allow; extern const ImagePolicy image_policy_deny; extern const ImagePolicy image_policy_ignore; -extern const ImagePolicy image_policy_sysext; +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; diff --git a/src/sysext/sysext.c b/src/sysext/sysext.c index ce076f665a6..f784627e820 100644 --- a/src/sysext/sysext.c +++ b/src/sysext/sysext.c @@ -410,6 +410,24 @@ static int strverscmp_improvedp(char *const* a, char *const* b) { return strverscmp_improved(*a, *b); } +static const ImagePolicy *pick_image_policy(const Image *img) { + assert(img); + assert(img->path); + + /* Explicitly specified policy always wins */ + if (arg_image_policy) + return arg_image_policy; + + /* If located in /.extra/sysext/ in the initrd, then it was placed there by systemd-stub, and was + * picked up from an untrusted ESP. Thus, require a stricter policy by default for them. (For the + * other directories we assume the appropriate level of trust was already established already. */ + + if (in_initrd() && path_startswith(img->path, "/.extra/sysext/")) + return &image_policy_sysext_strict; + + return &image_policy_sysext; +} + static int merge_subprocess(Hashmap *images, const char *workspace) { _cleanup_free_ char *host_os_release_id = NULL, *host_os_release_version_id = NULL, *host_os_release_sysext_level = NULL, *buf = NULL; @@ -526,7 +544,7 @@ static int merge_subprocess(Hashmap *images, const char *workspace) { d, &verity_settings, /* mount_options= */ NULL, - arg_image_policy ?: &image_policy_sysext, + pick_image_policy(img), flags, &m); if (r < 0) diff --git a/src/test/test-image-policy.c b/src/test/test-image-policy.c index 8dc2044c4a5..41941704d42 100644 --- a/src/test/test-image-policy.c +++ b/src/test/test-image-policy.c @@ -77,6 +77,7 @@ TEST_RET(test_image_policy_to_string) { test_policy(&image_policy_ignore, "-"); test_policy(&image_policy_deny, "~"); test_policy(&image_policy_sysext, "sysext"); + test_policy(&image_policy_sysext_strict, "sysext-strict"); test_policy(&image_policy_container, "container"); test_policy(&image_policy_host, "host"); test_policy(&image_policy_service, "service"); From f1f42aeaf1ba5444f4a4e0f2d0d4fb304fc34a49 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 2 Dec 2022 17:16:57 +0100 Subject: [PATCH 12/14] test: add integration test for image policy --- test/units/testsuite-50.sh | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/test/units/testsuite-50.sh b/test/units/testsuite-50.sh index 546a915a2e6..02a02301912 100755 --- a/test/units/testsuite-50.sh +++ b/test/units/testsuite-50.sh @@ -231,6 +231,33 @@ fi systemd-dissect --root-hash "${roothash}" "${image}.gpt" | grep -q -F "MARKER=1" systemd-dissect --root-hash "${roothash}" "${image}.gpt" | grep -q -F -f <(sed 's/"//g' "$os_release") +# Test image policies +systemd-dissect --validate "${image}.gpt" +systemd-dissect --validate "${image}.gpt" --image-policy='*' +(! systemd-dissect --validate "${image}.gpt" --image-policy='~') +(! systemd-dissect --validate "${image}.gpt" --image-policy='-') +(! systemd-dissect --validate "${image}.gpt" --image-policy=root=absent) +(! systemd-dissect --validate "${image}.gpt" --image-policy=swap=unprotected+encrypted+verity) +systemd-dissect --validate "${image}.gpt" --image-policy=root=unprotected +systemd-dissect --validate "${image}.gpt" --image-policy=root=verity +systemd-dissect --validate "${image}.gpt" --image-policy=root=verity:root-verity-sig=unused+absent +systemd-dissect --validate "${image}.gpt" --image-policy=root=verity:swap=absent +systemd-dissect --validate "${image}.gpt" --image-policy=root=verity:swap=absent+unprotected +(! systemd-dissect --validate "${image}.gpt" --image-policy=root=verity:root-verity=unused+absent) +systemd-dissect --validate "${image}.gpt" --image-policy=root=signed +(! systemd-dissect --validate "${image}.gpt" --image-policy=root=signed:root-verity-sig=unused+absent) +(! systemd-dissect --validate "${image}.gpt" --image-policy=root=signed:root-verity=unused+absent) + +# Test RootImagePolicy= unit file setting +systemd-run --wait -P -p RootImage="${image}.gpt" -p RootHash="${roothash}" -p MountAPIVFS=yes cat /usr/lib/os-release | grep -q -F "MARKER=1" +systemd-run --wait -P -p RootImage="${image}.gpt" -p RootHash="${roothash}" -p RootImagePolicy='*' -p MountAPIVFS=yes cat /usr/lib/os-release | grep -q -F "MARKER=1" +(! systemd-run --wait -P -p RootImage="${image}.gpt" -p RootHash="${roothash}" -p RootImagePolicy='~' -p MountAPIVFS=yes cat /usr/lib/os-release | grep -q -F "MARKER=1") +(! systemd-run --wait -P -p RootImage="${image}.gpt" -p RootHash="${roothash}" -p RootImagePolicy='-' -p MountAPIVFS=yes cat /usr/lib/os-release | grep -q -F "MARKER=1") +(! systemd-run --wait -P -p RootImage="${image}.gpt" -p RootHash="${roothash}" -p RootImagePolicy='root=absent' -p MountAPIVFS=yes cat /usr/lib/os-release | grep -q -F "MARKER=1") +systemd-run --wait -P -p RootImage="${image}.gpt" -p RootHash="${roothash}" -p RootImagePolicy='root=verity' -p MountAPIVFS=yes cat /usr/lib/os-release | grep -q -F "MARKER=1" +systemd-run --wait -P -p RootImage="${image}.gpt" -p RootHash="${roothash}" -p RootImagePolicy='root=signed' -p MountAPIVFS=yes cat /usr/lib/os-release | grep -q -F "MARKER=1" +(! systemd-run --wait -P -p RootImage="${image}.gpt" -p RootHash="${roothash}" -p RootImagePolicy='root=encrypted' -p MountAPIVFS=yes cat /usr/lib/os-release | grep -q -F "MARKER=1") + systemd-dissect --root-hash "${roothash}" --mount "${image}.gpt" "${image_dir}/mount" grep -q -F -f "$os_release" "${image_dir}/mount/usr/lib/os-release" grep -q -F -f "$os_release" "${image_dir}/mount/etc/os-release" From 9ea811914fce034c2fe9d5f7d5712d49462ac6a4 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 1 Dec 2022 22:41:47 +0100 Subject: [PATCH 13/14] man: document image policy syntax and semantics, and the hooks in the various components --- man/bootctl.xml | 2 + man/coredumpctl.xml | 2 + man/journalctl.xml | 2 + man/kernel-command-line.xml | 14 ++- man/rules/meson.build | 1 + man/standard-options.xml | 11 ++ man/systemctl.xml | 2 + man/systemd-analyze.xml | 41 +++++++ man/systemd-dissect.xml | 1 + man/systemd-gpt-auto-generator.xml | 10 ++ man/systemd-machine-id-setup.xml | 2 + man/systemd-nspawn.xml | 11 ++ man/systemd-repart.xml | 2 + man/systemd-sysext.xml | 23 +++- man/systemd-sysupdate.xml | 2 + man/systemd-sysusers.xml | 2 + man/systemd-tmpfiles.xml | 2 + man/systemd.exec.xml | 24 ++++ man/systemd.image-policy.xml | 191 +++++++++++++++++++++++++++++ 19 files changed, 341 insertions(+), 4 deletions(-) create mode 100644 man/systemd.image-policy.xml diff --git a/man/bootctl.xml b/man/bootctl.xml index a6f1fc1c4cf..5f98486343d 100644 --- a/man/bootctl.xml +++ b/man/bootctl.xml @@ -305,6 +305,8 @@ switch of the same name. + + When installing binaries with or diff --git a/man/coredumpctl.xml b/man/coredumpctl.xml index 79632eb2d4f..0f4a2e83e67 100644 --- a/man/coredumpctl.xml +++ b/man/coredumpctl.xml @@ -268,6 +268,8 @@ switch of the same name. + + diff --git a/man/journalctl.xml b/man/journalctl.xml index ae86c50d624..aa124dd98f0 100644 --- a/man/journalctl.xml +++ b/man/journalctl.xml @@ -182,6 +182,8 @@ switch of the same name. + + diff --git a/man/kernel-command-line.xml b/man/kernel-command-line.xml index 6f026318d8a..27ef72da361 100644 --- a/man/kernel-command-line.xml +++ b/man/kernel-command-line.xml @@ -396,12 +396,22 @@ rd.systemd.gpt_auto= - Configures whether GPT based partition auto-discovery - shall be attempted. For details, see + Configures whether GPT-based partition auto-discovery shall be attempted. For details, see systemd-gpt-auto-generator8. + + systemd.image_policy= + rd.systemd.image_policy= + + When GPT-based partition auto-discovery is used, configures the image dissection + policy string to apply, as per + systemd.image-policy7. For + details see + systemd-gpt-auto-generator8. + + systemd.default_timeout_start_sec= diff --git a/man/rules/meson.build b/man/rules/meson.build index 63a68c32110..42c546f18d8 100644 --- a/man/rules/meson.build +++ b/man/rules/meson.build @@ -1104,6 +1104,7 @@ manpages = [ ['systemd.environment-generator', '7', [], 'ENABLE_ENVIRONMENT_D'], ['systemd.exec', '5', [], ''], ['systemd.generator', '7', [], ''], + ['systemd.image-policy', '7', [], ''], ['systemd.journal-fields', '7', [], ''], ['systemd.kill', '5', [], ''], ['systemd.link', '5', [], ''], diff --git a/man/standard-options.xml b/man/standard-options.xml index d42f3296cab..71c84958abe 100644 --- a/man/standard-options.xml +++ b/man/standard-options.xml @@ -86,4 +86,15 @@ numerical signal numbers and the program will exit immediately. + + + + + Takes an image policy string as argument, as per + systemd.image-policy7. The + policy is enforced when operating on the disk image specified via , see + above. If not specified defaults to the * policy, i.e. all recognized file systems + in the image are used. + + diff --git a/man/systemctl.xml b/man/systemctl.xml index f930034cb1d..1a881d10495 100644 --- a/man/systemctl.xml +++ b/man/systemctl.xml @@ -2276,6 +2276,8 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err switch of the same name. + + diff --git a/man/systemd-analyze.xml b/man/systemd-analyze.xml index 9fd28e6f45c..7176e3c0468 100644 --- a/man/systemd-analyze.xml +++ b/man/systemd-analyze.xml @@ -162,6 +162,12 @@ fdstore UNIT + + systemd-analyze + OPTIONS + image-policy + POLICY + @@ -840,6 +846,39 @@ stored sock 0:8 4213190 - socket:[4213190] ro "DEVNO". + + <command>systemd-analyze image-policy <optional><replaceable>POLICY</replaceable>…</optional></command> + + This command analyzes the specified image policy string, as per + systemd.image-policy7. The + policy is normalized and simplified. For each currently defined partition identifier (as per the Discoverable + Partitions Specification the effect of the image policy string is shown in tabular form. + + + Example Output + + $ systemd-analyze image-policy swap=encrypted:usr=read-only-on+verity:root=encrypted +Analyzing policy: root=encrypted:usr=verity+read-only-on:swap=encrypted + Long form: root=encrypted:usr=verity+read-only-on:swap=encrypted:=unused+absent + +PARTITION MODE READ-ONLY GROWFS +root encrypted - - +usr verity yes - +home ignore - - +srv ignore - - +esp ignore - - +xbootldr ignore - - +swap encrypted - - +root-verity ignore - - +usr-verity unprotected yes - +root-verity-sig ignore - - +usr-verity-sig ignore - - +tmp ignore - - +var ignore - - +default ignore - - + + @@ -967,6 +1006,8 @@ stored sock 0:8 4213190 - socket:[4213190] ro operate on files inside the specified image path PATH. + + diff --git a/man/systemd-dissect.xml b/man/systemd-dissect.xml index f388cde3c65..2a83477357a 100644 --- a/man/systemd-dissect.xml +++ b/man/systemd-dissect.xml @@ -419,6 +419,7 @@ cfdisk /dev/loop/by-ref/quux. + diff --git a/man/systemd-gpt-auto-generator.xml b/man/systemd-gpt-auto-generator.xml index bd542cb7f72..1730039b62c 100644 --- a/man/systemd-gpt-auto-generator.xml +++ b/man/systemd-gpt-auto-generator.xml @@ -249,6 +249,16 @@ + + systemd.image_policy= + rd.systemd.image_policy= + + Takes an image dissection policy string as argument (as per + systemd.image-policy7), + and allows enforcing a policy on dissection and use of the automatically discovered GPT partition + table entries. + + root= rootfstype= diff --git a/man/systemd-machine-id-setup.xml b/man/systemd-machine-id-setup.xml index f1695b6ddb2..c07a853418e 100644 --- a/man/systemd-machine-id-setup.xml +++ b/man/systemd-machine-id-setup.xml @@ -95,6 +95,8 @@ tree. + + Commit a transient machine ID to disk. This diff --git a/man/systemd-nspawn.xml b/man/systemd-nspawn.xml index e2c751692f1..39a6febb3c6 100644 --- a/man/systemd-nspawn.xml +++ b/man/systemd-nspawn.xml @@ -310,6 +310,17 @@ together with , . + + + + Takes an image policy string as argument, as per + systemd.image-policy7. The + policy is enforced when operating on the disk image specified via , see + above. If not specified defaults to + root=verity+signed+encrypted+unprotected+absent:usr=verity+signed+encrypted+unprotected+absent:home=encrypted+unprotected+absent:srv=encrypted+unprotected+absent:esp=unprotected+absent:xbootldr=unprotected+absent:tmp=encrypted+unprotected+absent:var=encrypted+unprotected+absent, + i.e. all recognized file systems in the image are used, but not the swap partition. + + diff --git a/man/systemd-repart.xml b/man/systemd-repart.xml index 9033ef76d69..98ca1c431a6 100644 --- a/man/systemd-repart.xml +++ b/man/systemd-repart.xml @@ -269,6 +269,8 @@ , see above. + + diff --git a/man/systemd-sysext.xml b/man/systemd-sysext.xml index 96e40ddf954..2b7a87f5105 100644 --- a/man/systemd-sysext.xml +++ b/man/systemd-sysext.xml @@ -89,7 +89,12 @@ carrying large binary images, however are still useful for carrying symlinks to them. The primary place for installing system extensions is /var/lib/extensions/. Any directories found in these search directories are considered directory based extension images; any files with the - .raw suffix are considered disk image based extension images. + .raw suffix are considered disk image based extension images. When invoked in the + initrd, the additional directory /.extra/sysext/ is included in the directories that + are searched for extension images. Note however, that by default a tighter image policy applies to images + found there, though, see below. This directory is populated by + systemd-stub7 with + extension images found in the system's EFI System Partition. During boot OS extension images are activated automatically, if the systemd-sysext.service is enabled. Note that this service runs only after the @@ -230,6 +235,19 @@ not. + + + + Takes an image policy string as argument, as per + systemd.image-policy7. The + policy is enforced when operating on system extension disk images. If not specified defaults to + root=verity+signed+encrypted+unprotected+absent:usr=verity+signed+encrypted+unprotected+absent, + i.e. only the root and /usr/ file systems in the image are used. When run in the + initrd and operating on a system extension image stored in the /.extra/sysext/ + directory a slightly stricter policy is used by default: + root=signed+absent:usr=signed+absent, see above for details. + + @@ -246,7 +264,8 @@ See Also systemd1, - systemd-nspawn1 + systemd-nspawn1, + systemd-stub7 diff --git a/man/systemd-sysupdate.xml b/man/systemd-sysupdate.xml index 77c1635b9d9..409281c19fa 100644 --- a/man/systemd-sysupdate.xml +++ b/man/systemd-sysupdate.xml @@ -229,6 +229,8 @@ inside the specified disk image. + + diff --git a/man/systemd-sysusers.xml b/man/systemd-sysusers.xml index aba275024f9..f7ee5e79d91 100644 --- a/man/systemd-sysusers.xml +++ b/man/systemd-sysusers.xml @@ -80,6 +80,8 @@ switch of the same name. + + When this option is given, one or more positional arguments diff --git a/man/systemd-tmpfiles.xml b/man/systemd-tmpfiles.xml index 49eda985b49..5612b4803d8 100644 --- a/man/systemd-tmpfiles.xml +++ b/man/systemd-tmpfiles.xml @@ -202,6 +202,8 @@ Implies . + + When this option is given, one or more positional arguments diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml index 17be33c56a2..1d99c586011 100644 --- a/man/systemd.exec.xml +++ b/man/systemd.exec.xml @@ -260,6 +260,30 @@ + + RootImagePolicy= + MountImagePolicy= + ExtensionImagePolicy= + + Takes an image policy string as per + systemd.image-policy7 + to use when mounting the disk images (DDI) specified in RootImage=, + MountImage=, ExtensionImage=, respectively. If not specified + the following policy string is the default for RootImagePolicy= and MountImagePolicy: + + root=verity+signed+encrypted+unprotected+absent: \ + usr=verity+signed+encrypted+unprotected+absent: \ + home=encrypted+unprotected+absent: \ + srv=encrypted+unprotected+absent: \ + tmp=encrypted+unprotected+absent: \ + var=encrypted+unprotected+absent + + The default policy for ExtensionImagePolicy= is: + + root=verity+signed+encrypted+unprotected+absent: \ + usr=verity+signed+encrypted+unprotected+absent + + MountAPIVFS= diff --git a/man/systemd.image-policy.xml b/man/systemd.image-policy.xml new file mode 100644 index 00000000000..4f7b0986b62 --- /dev/null +++ b/man/systemd.image-policy.xml @@ -0,0 +1,191 @@ + + + + + + + + systemd.image-policy + systemd + + + + systemd.image-policy + 7 + + + + systemd.image-policy + Disk Image Dissection Policy + + + + Description + + In systemd, whenever a disk image (DDI) implementing the Discoverable + Partitions Specification is activated, a policy may be specified controlling which partitions to + mount and what kind of cryptographic protection to require. Such a disk image dissection policy is a + string that contains per-partition-type rules, separated by colons (:). The individual + rules consist of a partition identifier, an equal sign (=), and one or more flags + which may be set per partition. If multiple flags are specified per partition they are separated by a + plus sign (+). + + The partition identifiers currently defined are: , , + , , , , + , , , + , , , + . These identifiers match the relevant partition types in the Discoverable Partitions + Specification, but are agnostic to CPU architectures. If the partition identifier is left empty it + defines the default policy for partitions defined in the Discoverable Parition + Specification for which no policy flags are explicitly listed in the policy string. + + The following partition policy flags are defined that dictate the existence/absence, the use, and + the protection level of partitions: + + + for partitions that shall exist and be used, but shall + come without cryptographic protection, lacking both Verity authentication and LUKS + encryption. + + for partitions that shall exist and be used, with Verity + authentication. (Note: if a DDI image carries a data partition, along with a Verity partition and a + signature partition for it, and only the flag is set – and + is not –, then the image will be set up with Verity, but the signature data will + not be used. Or in other words: any DDI with a set of partitions that qualify for + also implicitly qualifies for , and in fact + ). + + for partitions that shall exist and be used, with Verity + authentication, which are also accompanied by a PKCS#7 signature of the Verity root + hash. + + for partitions which shall exist and be used and are + encrypted with LUKS. + + for partitions that shall exist but shall not be + used. + + for partitions that shall not exist on the + image. + + + By setting a combination of the flags above, alternatives can be declared. For example the + combination unused+absent means: the partition may exist (in which case it shall not + be used) or may be absent. The combination of + unprotected+verity+signed+encrypted+unused+absent may be specified via the special + shortcut open, and indicates that the partition may exist or may be absent, but if it + exists is used, regardless of the protection level. + + As special rule: if none of the flags above are set for a listed partition identifier, the default + policy of is implied, i.e. setting none of these flags listed above means + effectively all flags listed above will be set. + + The following partition policy flags are defined that dictate the state of specific GPT partition + flags: + + + , to require that the + partitions have the read-only partition flag off or on. + + , to require that the + partitions have the growfs partition flag off or on. + + + If both and are set for a partition, + then the state of the read-only flag on the partition is not dictated by the policy. Setting neither flag + is equivalent to setting both, i.e. setting neither of these two flags means effectively both will be + set. A similar logic applies to /. + + If partitions are not listed within an image policy string, the default policy flags are applied + (configurable via an empty partition identifier, see above). If no default policy flags are configured in + the policy string, it is implied to be absent+unused, except for the Verity partition + and their signature partitions where the policy is automatically derived from minimal protection level of + the data partition they protect, as encoded in the policy. + + + + Special Policies + + The special image policy string * is short for "use everything", i.e. is + equivalent to: + + =verity+signed+encrypted+unprotected+unused+absent + + The special image policy string - is short for "use nothing", i.e. is equivalent + to: + + =unused+absent + + The special image policy string ~ is short for "everything must be absent", + i.e. is equivalent to: + + =absent + + + + + Use + + Most systemd components that support operating with disk images support a + command line option to specify the image policy to use, and default to + relatively open policies by default (typically the * policy, as described above), + under the assumption that trust in disk images is established before the images are passed to the program + in question. + + For the host image itself + systemd-gpt-auto-generator8 + is responsible for processing the GPT partition table and making use of the included discoverable + partitions. It accepts an image policy via the kernel command line option + . + + Note that image policies do not dictate how the components will mount and use disk images — they + only dictate which parts to avoid and which protection level and arrangement to require while + mounting/using them. For example, + systemd-sysext8 only + cares for the /usr/ and /opt/ trees inside a disk image, and + thus ignores any /home/ partitions (and similar) in all cases, which might be + included in the image, regardless whether the configured image policy would allow access to it or + not. Similar, + systemd-nspawn1 is not + going to make use of any discovered swap device, regardless if the policy would allow that or not. + + Use the image-policy command of the + systemd-analyze8 tool + to analyze image policy strings, and determine what a specific policy string means for a specific + partition. + + + + Examples + + The following image policy string dictates one read-only Verity-enabled /usr/ + partition must exist, plus encrypted root and swap partitions. All other partitions are ignored: + + usr=verity+read-only-on:root=encrypted:swap=encrypted + + The following image policy string dictates an encrypted, writable root file system, and optional + /srv/ file system that must be encrypted if it exists and no swap partition may + exist: + + root=encrypted+read-only-off:srv=encrypted+absent:swap=absent + + The following image policy string dictates a single root partition that may be encrypted, but + doesn't have to be, and ignores swap partitions, and uses all other partitions if they are available, possibly with encryption. + + root=unprotected+encrypted:swap=absent+unused:=unprotected+encrypted+absent + + + + See Also + + systemd1, + systemd-dissect1, + systemd-gpt-auto-generator8, + systemd-sysext8, + systemd-analyze8 + + + + From 3bcf564530bfa7e001354dd94e653905523c418d Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 1 Dec 2022 22:21:45 +0100 Subject: [PATCH 14/14] update TODO --- TODO | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/TODO b/TODO index b0e4665ad86..e9d6c61108d 100644 --- a/TODO +++ b/TODO @@ -293,9 +293,6 @@ Features: userspace to allow ordering boots (for example in journalctl). The counter would be monotonically increased on every boot. -* systemd-sysext: for sysext DDIs picked up via EFI stub, set much stricter - image policy by default - * pam_systemd_home: add module parameter to control whether to only accept only password or only pcks11/fido2 auth, and then use this to hook nicely into two of the three PAM stacks gdm provides. @@ -836,9 +833,6 @@ Features: virtio-fs. * for vendor-built signed initrds: - - make sysext run in the initrd - - sysext should pick up sysext images from /.extra/ in the initrd, and insist - on verification if in secureboot mode - kernel-install should be able to install pre-built unified kernel images in type #2 drop-in dir in the ESP. - kernel-install should be able install encrypted creds automatically for @@ -1046,9 +1040,6 @@ Features: CapabilityQuintet we already have. (This likely allows us to drop libcap dep in the base OS image) -* sysext: automatically activate sysext images dropped in via new sd-stub - sysext pickup logic. (must insist on verity + signature on those though) - * add concept for "exitrd" as inverse of "initrd", that we can transition to at shutdown, and has similar security semantics. This should then take the place of dracut's shutdown logic. Should probably support sysexts too. Care needs @@ -1078,22 +1069,6 @@ Features: keys of /etc/crypttab. That way people can store/provide the roothash externally and provide to us on demand only. -* add high-level lockdown level for GPT dissection logic: e.g. an enum that can - be ANY (to mount anything), TRUSTED (to require that /usr is on signed - verity, but rest doesn't matter), LOCKEDDOWN (to require that everything is - on signed verity, except for ESP), SUPERLOCKDOWN (like LOCKEDDOWN but ESP not - allowed). And then maybe some flavours of that that declare what is expected - from home/srv/var… Then, add a new cmdline flag to all tools that parse such - images, to configure this. Also, add a kernel cmdline option for this, to be - honoured by the gpt auto generator. - - Alternative idea: add "systemd.gpt_auto_policy=rhvs" to allow gpt-auto to - only mount root dir, /home/ dir, /var/ and /srv/, but nothing else. And then - minor extension to this, insisting on encryption, for example - "systemd.gpt_auto_policy=r+v+h" to require encryption for root and var but not - for /home/, and similar. Similar add --image-dissect-policy= to tools that - take --image= that take the same short string. - * we probably should extend the root verity hash of the root fs into some PCR on boot. (i.e. maybe add a veritytab option tpm2-measure=12 or so to measure it into PCR 12); Similar: we probably should extend the LUKS volume key of @@ -1106,10 +1081,6 @@ Features: (i.e. sysext, root verity) from those inherently local (i.e. encryption key), which is useful if they shall be signed separately. -* add a "policy" to the dissection logic. i.e. a bit mask what is OK to mount, - what must be read-only, what requires encryption, and what requires - authentication. - * in uefi stub: query firmware regarding which PCR banks are being used, store that in EFI var. then use this when enrolling TPM2 in cryptsetup to verify that the selected PCRs actually are used by firmware.