mirror of
https://github.com/morgan9e/systemd
synced 2026-04-14 08:25:20 +09:00
Let's rename the existing partition_verity_to_data() to partition_verity_hash_to_data() and make a new partition_verity_to_data() that handles both verity hash and verity signature partitions. Rename other functions to match the new naming.
936 lines
39 KiB
C
936 lines
39 KiB
C
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
|
|
|
#include "alloc-util.h"
|
|
#include "extract-word.h"
|
|
#include "image-policy.h"
|
|
#include "log.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 const PartitionPolicy* image_policy_bsearch(const ImagePolicy *policy, PartitionDesignator designator) {
|
|
if (!policy)
|
|
return NULL;
|
|
|
|
return typesafe_bsearch(
|
|
&(const PartitionPolicy) { .designator = designator },
|
|
ASSERT_PTR(policy)->policies,
|
|
ASSERT_PTR(policy)->n_policies,
|
|
partition_policy_compare);
|
|
}
|
|
|
|
PartitionPolicyFlags partition_policy_flags_extend(PartitionPolicyFlags flags) {
|
|
/* If some parts of a flags field are left unspecified, let's fill in all options. */
|
|
|
|
/* If no protection flag is set, then this means all are set */
|
|
if ((flags & _PARTITION_POLICY_USE_MASK) == 0)
|
|
flags |= PARTITION_POLICY_OPEN;
|
|
|
|
/* 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 partition_policy_flags_reduce(PartitionPolicyFlags flags) {
|
|
/* The reverse of partition_policy_flags_extend(): if some parts of the flags field allow all
|
|
* possible options, let's remove it from the flags to make them shorter */
|
|
|
|
if (FLAGS_SET(flags, _PARTITION_POLICY_USE_MASK))
|
|
flags &= ~_PARTITION_POLICY_USE_MASK;
|
|
if (FLAGS_SET(flags, _PARTITION_POLICY_READ_ONLY_MASK))
|
|
flags &= ~_PARTITION_POLICY_READ_ONLY_MASK;
|
|
if (FLAGS_SET(flags, _PARTITION_POLICY_GROWFS_MASK))
|
|
flags &= ~_PARTITION_POLICY_GROWFS_MASK;
|
|
|
|
return flags;
|
|
}
|
|
|
|
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. */
|
|
|
|
flags = partition_policy_flags_extend(flags);
|
|
|
|
/* 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_hash_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_hash_of(policy->designator) < 0)
|
|
flags &= ~(PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED);
|
|
|
|
/* If the partition must be absent, then the gpt flags don't matter */
|
|
if ((flags & _PARTITION_POLICY_USE_MASK) == PARTITION_POLICY_ABSENT)
|
|
flags &= ~(_PARTITION_POLICY_READ_ONLY_MASK|_PARTITION_POLICY_GROWFS_MASK);
|
|
|
|
return flags;
|
|
}
|
|
|
|
PartitionPolicyFlags image_policy_get(const ImagePolicy *policy, PartitionDesignator designator) {
|
|
PartitionDesignator data_designator = _PARTITION_DESIGNATOR_INVALID;
|
|
const 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_hash_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) + 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 < ELEMENTSOF(l));
|
|
l[m] = NULL;
|
|
|
|
buf = strv_join((char**) l, "+");
|
|
}
|
|
if (!buf)
|
|
return -ENOMEM;
|
|
|
|
*ret = TAKE_PTR(buf);
|
|
return 0;
|
|
}
|
|
|
|
static bool partition_policy_flags_extended_equal(PartitionPolicyFlags a, PartitionPolicyFlags b) {
|
|
return partition_policy_flags_extend(a) == partition_policy_flags_extend(b);
|
|
}
|
|
|
|
static int image_policy_flags_all_match(const ImagePolicy *policy, PartitionPolicyFlags expected) {
|
|
|
|
if (expected < 0)
|
|
return -EINVAL;
|
|
|
|
if (!partition_policy_flags_extended_equal(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 || !partition_policy_flags_extended_equal(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 (!partition_policy_flags_extended_equal(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;
|
|
}
|
|
|
|
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);
|
|
|
|
return free_and_replace_full(*p, np, image_policy_free);
|
|
}
|
|
|
|
int parse_image_policy_argument(const char *s, ImagePolicy **policy) {
|
|
_cleanup_(image_policy_freep) ImagePolicy *np = NULL;
|
|
int r;
|
|
|
|
assert(s);
|
|
assert(policy);
|
|
|
|
/*
|
|
* This function is intended to be used in command line parsers.
|
|
*
|
|
* NOTE THAT THIS WILL FREE THE PREVIOUS ARGUMENT POINTER ON SUCCESS!
|
|
* Hence, do not pass in uninitialized pointers.
|
|
*/
|
|
|
|
r = image_policy_from_string(s, &np);
|
|
if (r == -ENOTUNIQ)
|
|
return log_error_errno(r, "Duplicate rule in image policy: %s", s);
|
|
if (r == -EBADSLT)
|
|
return log_error_errno(r, "Unknown partition type in image policy: %s", s);
|
|
if (r == -EBADRQC)
|
|
return log_error_errno(r, "Unknown partition policy flag in image policy: %s", s);
|
|
if (r < 0)
|
|
return log_error_errno(r, "Failed to parse image policy: %s", s);
|
|
|
|
return free_and_replace_full(*policy, np, image_policy_free);
|
|
}
|
|
|
|
static bool partition_policy_flags_has_unspecified(PartitionPolicyFlags flags) {
|
|
|
|
if ((flags & _PARTITION_POLICY_USE_MASK) == 0)
|
|
return true;
|
|
if ((flags & _PARTITION_POLICY_READ_ONLY_MASK) == 0)
|
|
return true;
|
|
if ((flags & _PARTITION_POLICY_GROWFS_MASK) == 0)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
int image_policy_intersect(const ImagePolicy *a, const ImagePolicy *b, ImagePolicy **ret) {
|
|
_cleanup_(image_policy_freep) ImagePolicy *p = NULL;
|
|
|
|
/* Calculates the intersection of the specified policies, i.e. only what is permitted in both. This
|
|
* might fail with -ENAVAIL if the intersection is an "impossible policy". For example, if a root
|
|
* partition my neither be used, nor be absent, nor be unused then this is considered
|
|
* "impossible". */
|
|
|
|
p = image_policy_new(_PARTITION_DESIGNATOR_MAX);
|
|
if (!p)
|
|
return -ENOMEM;
|
|
|
|
p->default_flags =
|
|
partition_policy_flags_extend(image_policy_default(a)) &
|
|
partition_policy_flags_extend(image_policy_default(b));
|
|
|
|
if (partition_policy_flags_has_unspecified(p->default_flags)) /* Intersection empty? */
|
|
return -ENAVAIL;
|
|
|
|
p->default_flags = partition_policy_flags_reduce(p->default_flags);
|
|
|
|
for (PartitionDesignator d = 0; d < _PARTITION_DESIGNATOR_MAX; d++) {
|
|
PartitionPolicyFlags x, y, z, df;
|
|
|
|
/* If this designator has no entry in either policy we don't need to include it in the intersection either. */
|
|
if (!image_policy_bsearch(a, d) && !image_policy_bsearch(b, d))
|
|
continue;
|
|
|
|
/* Expand this policy flags field to the "long" form, i.e. for each part of the flags that
|
|
* are left unspcified add in all possible options */
|
|
x = image_policy_get_exhaustively(a, d);
|
|
if (x < 0)
|
|
return x;
|
|
|
|
y = image_policy_get_exhaustively(b, d);
|
|
if (y < 0)
|
|
return y;
|
|
|
|
/* Mask it */
|
|
z = x & y;
|
|
|
|
/* Check if the intersection is empty for this partition. If so, generate a clear error */
|
|
if (partition_policy_flags_has_unspecified(z))
|
|
return -ENAVAIL;
|
|
|
|
df = partition_policy_normalized_flags(
|
|
&(const PartitionPolicy) {
|
|
.flags = image_policy_default(p),
|
|
.designator = d,
|
|
});
|
|
if (df < 0)
|
|
return df;
|
|
if (df == z) /* Same as default? then let's skip this */
|
|
continue;
|
|
|
|
/* image_policy_get_exhaustively() may have extended the flags mask to include all
|
|
* read-only/growfs flags if not set. Let's remove them again, if they are both set to
|
|
* minimize the policy again. */
|
|
z = partition_policy_flags_reduce(z);
|
|
|
|
p->policies[p->n_policies++] = (struct PartitionPolicy) {
|
|
.designator = d,
|
|
.flags = z,
|
|
};
|
|
}
|
|
|
|
if (ret)
|
|
*ret = TAKE_PTR(p);
|
|
|
|
return 0;
|
|
}
|
|
|
|
ImagePolicy* image_policy_free(ImagePolicy *p) {
|
|
return mfree(p);
|
|
}
|
|
|
|
int image_policy_ignore_designators(const ImagePolicy *p, const PartitionDesignator table[], size_t n_table, ImagePolicy **ret) {
|
|
assert(p);
|
|
assert(table || n_table == 0);
|
|
assert(ret);
|
|
|
|
/* Patches the specified image policy, replacing the policy for the specified designators by an
|
|
* "ignore" policy. Returns a patched copy. This is useful in context where only some of the
|
|
* available partitions shall be mounted, and hence the policy for the others really doesn't
|
|
* matter. */
|
|
|
|
_cleanup_(image_policy_freep) ImagePolicy *np = image_policy_new(_PARTITION_DESIGNATOR_MAX);
|
|
if (!np)
|
|
return -ENOMEM;
|
|
|
|
FOREACH_ARRAY(t, table, n_table) {
|
|
assert(*t >= 0);
|
|
assert(*t < _PARTITION_DESIGNATOR_MAX);
|
|
|
|
if (image_policy_bsearch(np, *t))
|
|
continue;
|
|
|
|
/* Insert an ignore policy for this entry, and sort it to the right place, so that image_policy_bsearch() can work */
|
|
np->policies[np->n_policies++] = (PartitionPolicy) {
|
|
.designator = *t,
|
|
.flags = PARTITION_POLICY_IGNORE,
|
|
};
|
|
typesafe_qsort(np->policies, np->n_policies, partition_policy_compare);
|
|
}
|
|
|
|
FOREACH_ARRAY(i, p->policies, p->n_policies) {
|
|
|
|
if (image_policy_bsearch(np, i->designator))
|
|
continue;
|
|
|
|
/* Copy the policy entry from the old image policy, and sort it to the right place, so that image_policy_bsearch() can work */
|
|
np->policies[np->n_policies++] = *i;
|
|
typesafe_qsort(np->policies, np->n_policies, partition_policy_compare);
|
|
}
|
|
|
|
np->default_flags = p->default_flags;
|
|
|
|
/* Return unused space to libc */
|
|
ImagePolicy *t = realloc(np, offsetof(ImagePolicy, policies) + sizeof(PartitionPolicy) * np->n_policies);
|
|
if (t)
|
|
np = t;
|
|
|
|
*ret = TAKE_PTR(np);
|
|
return 0;
|
|
}
|
|
|
|
const ImagePolicy image_policy_allow = {
|
|
/* Allow policy */
|
|
.n_policies = 0,
|
|
.default_flags = PARTITION_POLICY_OPEN,
|
|
};
|
|
|
|
const ImagePolicy image_policy_deny = {
|
|
/* Deny policy */
|
|
.n_policies = 0,
|
|
.default_flags = PARTITION_POLICY_ABSENT,
|
|
};
|
|
|
|
const ImagePolicy image_policy_ignore = {
|
|
/* Ignore policy */
|
|
.n_policies = 0,
|
|
.default_flags = PARTITION_POLICY_IGNORE,
|
|
};
|
|
|
|
const ImagePolicy image_policy_sysext = {
|
|
/* For system extensions, honour root file system, and /usr/ and ignore everything else. After all,
|
|
* we are only interested in /usr/ + /opt/ trees anyway, and that's really the only place they can
|
|
* be. */
|
|
.n_policies = 2,
|
|
.policies = {
|
|
{ PARTITION_ROOT, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
|
|
{ PARTITION_USR, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
|
|
},
|
|
.default_flags = PARTITION_POLICY_IGNORE,
|
|
};
|
|
|
|
const ImagePolicy image_policy_sysext_strict = {
|
|
/* For system extensions, requiring signing */
|
|
.n_policies = 2,
|
|
.policies = {
|
|
{ PARTITION_ROOT, PARTITION_POLICY_SIGNED|PARTITION_POLICY_ABSENT },
|
|
{ PARTITION_USR, PARTITION_POLICY_SIGNED|PARTITION_POLICY_ABSENT },
|
|
},
|
|
.default_flags = PARTITION_POLICY_IGNORE,
|
|
};
|
|
|
|
const ImagePolicy image_policy_confext = {
|
|
/* For configuration extensions, honour root file system, and ignore everything else. After all, we
|
|
* are only interested in the /etc/ tree anyway, and that's really the only place it can be. */
|
|
.n_policies = 1,
|
|
.policies = {
|
|
{ PARTITION_ROOT, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
|
|
},
|
|
.default_flags = PARTITION_POLICY_IGNORE,
|
|
};
|
|
|
|
const ImagePolicy image_policy_confext_strict = {
|
|
.n_policies = 1,
|
|
.policies = {
|
|
{ PARTITION_ROOT, PARTITION_POLICY_SIGNED|PARTITION_POLICY_ABSENT },
|
|
},
|
|
.default_flags = PARTITION_POLICY_IGNORE,
|
|
};
|
|
|
|
const ImagePolicy image_policy_container = {
|
|
/* For systemd-nspawn containers we use all partitions, with the exception of swap */
|
|
.n_policies = 8,
|
|
.policies = {
|
|
{ PARTITION_ROOT, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
|
|
{ PARTITION_USR, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
|
|
{ PARTITION_HOME, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
|
|
{ PARTITION_SRV, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
|
|
{ PARTITION_ESP, PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
|
|
{ PARTITION_XBOOTLDR, PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
|
|
{ PARTITION_TMP, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
|
|
{ PARTITION_VAR, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
|
|
},
|
|
.default_flags = PARTITION_POLICY_IGNORE,
|
|
};
|
|
|
|
const ImagePolicy image_policy_host = {
|
|
/* For the host policy we basically use everything */
|
|
.n_policies = 9,
|
|
.policies = {
|
|
{ PARTITION_ROOT, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
|
|
{ PARTITION_USR, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
|
|
{ PARTITION_HOME, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
|
|
{ PARTITION_SRV, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
|
|
{ PARTITION_ESP, PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
|
|
{ PARTITION_XBOOTLDR, PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
|
|
{ PARTITION_SWAP, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
|
|
{ PARTITION_TMP, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
|
|
{ PARTITION_VAR, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
|
|
},
|
|
.default_flags = PARTITION_POLICY_IGNORE,
|
|
};
|
|
|
|
const ImagePolicy image_policy_service = {
|
|
/* For RootImage= in services we skip ESP/XBOOTLDR and swap */
|
|
.n_policies = 6,
|
|
.policies = {
|
|
{ PARTITION_ROOT, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
|
|
{ PARTITION_USR, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
|
|
{ PARTITION_HOME, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
|
|
{ PARTITION_SRV, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
|
|
{ PARTITION_TMP, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
|
|
{ PARTITION_VAR, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT },
|
|
},
|
|
.default_flags = PARTITION_POLICY_IGNORE,
|
|
};
|