mirror of
https://github.com/morgan9e/systemd
synced 2026-04-14 08:25:20 +09:00
dissect: add support for verity-protected bare filesystems via mountfsd (#39325)
Needed to implement support for RootHashSignature=/RootVerity=/RootHash= and friends when going through mountfsd, for example with user units, so that system and user units provide the same features at the same level
This commit is contained in:
@@ -275,7 +275,7 @@
|
||||
image. There's currently no option to configure the root hash for the <filename>/usr/</filename> file
|
||||
system via the unit file directly.</para>
|
||||
|
||||
<xi:include href="system-only.xml" xpointer="singular"/>
|
||||
<xi:include href="system-or-user-ns-mountfsd.xml" xpointer="singular"/>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v246"/></listitem>
|
||||
</varlistentry>
|
||||
@@ -298,7 +298,7 @@
|
||||
configure the root hash signature for the <filename>/usr/</filename> via the unit file
|
||||
directly.</para>
|
||||
|
||||
<xi:include href="system-only.xml" xpointer="singular"/>
|
||||
<xi:include href="system-or-user-ns-mountfsd.xml" xpointer="singular"/>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v246"/></listitem>
|
||||
</varlistentry>
|
||||
@@ -319,7 +319,7 @@
|
||||
<ulink url="https://uapi-group.org/specifications/specs/discoverable_partitions_specification">
|
||||
Discoverable Partitions Specification</ulink>.</para>
|
||||
|
||||
<xi:include href="system-only.xml" xpointer="singular"/>
|
||||
<xi:include href="system-or-user-ns-mountfsd.xml" xpointer="singular"/>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v246"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
@@ -28,6 +28,7 @@ rm -f /etc/resolv.conf
|
||||
|
||||
for f in "$BUILDROOT"/usr/share/*.verity.sig; do
|
||||
jq --join-output '.rootHash' "$f" >"${f%.verity.sig}.roothash"
|
||||
jq --join-output '.signature' "$f" | base64 --decode >"${f%.verity.sig}.roothash.p7s"
|
||||
done
|
||||
|
||||
# We want /var/log/journal to be created on first boot so it can be created with the right chattr settings by
|
||||
|
||||
@@ -2604,6 +2604,7 @@ int setup_namespace(const NamespaceParameters *p, char **reterr_path) {
|
||||
p->root_image,
|
||||
userns_fd,
|
||||
p->root_image_policy,
|
||||
p->verity,
|
||||
dissect_image_flags,
|
||||
&dissected_image);
|
||||
if (r < 0)
|
||||
|
||||
@@ -2311,6 +2311,7 @@ static int run(int argc, char *argv[]) {
|
||||
arg_image,
|
||||
userns_fd,
|
||||
arg_image_policy,
|
||||
&arg_verity_settings,
|
||||
arg_flags,
|
||||
&m);
|
||||
if (r < 0)
|
||||
|
||||
@@ -25,6 +25,24 @@
|
||||
#include "unit-name.h"
|
||||
#include "user-util.h"
|
||||
|
||||
int json_dispatch_unhex_iovec(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
|
||||
_cleanup_free_ void *buffer = NULL;
|
||||
struct iovec *iov = ASSERT_PTR(userdata);
|
||||
size_t sz;
|
||||
int r;
|
||||
|
||||
if (!sd_json_variant_is_string(variant))
|
||||
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
|
||||
|
||||
r = sd_json_variant_unhex(variant, &buffer, &sz);
|
||||
if (r < 0)
|
||||
return json_log(variant, flags, r, "JSON field '%s' is not valid hex data.", strna(name));
|
||||
|
||||
free_and_replace(iov->iov_base, buffer);
|
||||
iov->iov_len = sz;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int json_dispatch_unbase64_iovec(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
|
||||
_cleanup_free_ void *buffer = NULL;
|
||||
struct iovec *iov = ASSERT_PTR(userdata);
|
||||
|
||||
@@ -108,6 +108,7 @@ int json_log_internal(sd_json_variant *variant, int level, int error, const char
|
||||
#define json_log_oom(variant, flags) \
|
||||
json_log(variant, flags, SYNTHETIC_ERRNO(ENOMEM), "Out of memory.")
|
||||
|
||||
int json_dispatch_unhex_iovec(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata);
|
||||
int json_dispatch_unbase64_iovec(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata);
|
||||
int json_dispatch_byte_array_iovec(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata);
|
||||
int json_dispatch_user_group_name(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata);
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "hashmap.h"
|
||||
#include "image-policy.h"
|
||||
#include "io-util.h"
|
||||
#include "iovec-util.h"
|
||||
#include "json-util.h"
|
||||
#include "loop-util.h"
|
||||
#include "main-func.h"
|
||||
@@ -92,6 +93,9 @@ typedef struct MountImageParameters {
|
||||
char *password;
|
||||
ImagePolicy *image_policy;
|
||||
bool verity_sharing;
|
||||
struct iovec verity_root_hash;
|
||||
struct iovec verity_root_hash_sig;
|
||||
unsigned verity_data_fd_idx;
|
||||
} MountImageParameters;
|
||||
|
||||
static void mount_image_parameters_done(MountImageParameters *p) {
|
||||
@@ -99,6 +103,8 @@ static void mount_image_parameters_done(MountImageParameters *p) {
|
||||
|
||||
p->password = erase_and_free(p->password);
|
||||
p->image_policy = image_policy_free(p->image_policy);
|
||||
iovec_done(&p->verity_root_hash);
|
||||
iovec_done(&p->verity_root_hash_sig);
|
||||
}
|
||||
|
||||
static int validate_image_fd(int fd, MountImageParameters *p) {
|
||||
@@ -286,13 +292,16 @@ static int vl_method_mount_image(
|
||||
void *userdata) {
|
||||
|
||||
static const sd_json_dispatch_field dispatch_table[] = {
|
||||
{ "imageFileDescriptor", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uint, offsetof(MountImageParameters, image_fd_idx), SD_JSON_MANDATORY },
|
||||
{ "userNamespaceFileDescriptor", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uint, offsetof(MountImageParameters, userns_fd_idx), 0 },
|
||||
{ "readOnly", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, offsetof(MountImageParameters, read_only), 0 },
|
||||
{ "growFileSystems", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, offsetof(MountImageParameters, growfs), 0 },
|
||||
{ "password", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(MountImageParameters, password), 0 },
|
||||
{ "imagePolicy", SD_JSON_VARIANT_STRING, json_dispatch_image_policy, offsetof(MountImageParameters, image_policy), 0 },
|
||||
{ "veritySharing", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(MountImageParameters, verity_sharing), 0 },
|
||||
{ "imageFileDescriptor", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uint, offsetof(MountImageParameters, image_fd_idx), SD_JSON_MANDATORY },
|
||||
{ "userNamespaceFileDescriptor", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uint, offsetof(MountImageParameters, userns_fd_idx), 0 },
|
||||
{ "readOnly", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, offsetof(MountImageParameters, read_only), 0 },
|
||||
{ "growFileSystems", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, offsetof(MountImageParameters, growfs), 0 },
|
||||
{ "password", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(MountImageParameters, password), 0 },
|
||||
{ "imagePolicy", SD_JSON_VARIANT_STRING, json_dispatch_image_policy, offsetof(MountImageParameters, image_policy), 0 },
|
||||
{ "veritySharing", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(MountImageParameters, verity_sharing), 0 },
|
||||
{ "verityDataFileDescriptor", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uint, offsetof(MountImageParameters, verity_data_fd_idx), 0 },
|
||||
{ "verityRootHash", SD_JSON_VARIANT_STRING, json_dispatch_unhex_iovec, offsetof(MountImageParameters, verity_root_hash), 0 },
|
||||
{ "verityRootHashSignature", SD_JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(MountImageParameters, verity_root_hash_sig), 0 },
|
||||
VARLINK_DISPATCH_POLKIT_FIELD,
|
||||
{}
|
||||
};
|
||||
@@ -301,13 +310,14 @@ static int vl_method_mount_image(
|
||||
_cleanup_(mount_image_parameters_done) MountImageParameters p = {
|
||||
.image_fd_idx = UINT_MAX,
|
||||
.userns_fd_idx = UINT_MAX,
|
||||
.verity_data_fd_idx = UINT_MAX,
|
||||
.read_only = -1,
|
||||
.growfs = -1,
|
||||
};
|
||||
_cleanup_(dissected_image_unrefp) DissectedImage *di = NULL;
|
||||
_cleanup_(loop_device_unrefp) LoopDevice *loop = NULL;
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *aj = NULL;
|
||||
_cleanup_close_ int image_fd = -EBADF, userns_fd = -EBADF;
|
||||
_cleanup_close_ int image_fd = -EBADF, userns_fd = -EBADF, verity_data_fd = -EBADF;
|
||||
_cleanup_(image_policy_freep) ImagePolicy *use_policy = NULL;
|
||||
Hashmap **polkit_registry = ASSERT_PTR(userdata);
|
||||
_cleanup_free_ char *ps = NULL;
|
||||
@@ -323,6 +333,13 @@ static int vl_method_mount_image(
|
||||
if (r != 0)
|
||||
return r;
|
||||
|
||||
/* Verity data and roothash have to be either both set, or both unset. The sig can be set only if
|
||||
* the roothash is set. */
|
||||
if ((p.verity_data_fd_idx != UINT_MAX) != (p.verity_root_hash.iov_len > 0))
|
||||
return sd_varlink_error_invalid_parameter_name(link, "verityDataFileDescriptor");
|
||||
if (p.verity_root_hash_sig.iov_len > 0 && p.verity_root_hash.iov_len == 0)
|
||||
return sd_varlink_error_invalid_parameter_name(link, "verityRootHashSignature");
|
||||
|
||||
if (p.image_fd_idx != UINT_MAX) {
|
||||
image_fd = sd_varlink_peek_dup_fd(link, p.image_fd_idx);
|
||||
if (image_fd < 0)
|
||||
@@ -348,6 +365,30 @@ static int vl_method_mount_image(
|
||||
return r;
|
||||
image_is_trusted = r;
|
||||
|
||||
if (p.verity_data_fd_idx != UINT_MAX) {
|
||||
verity_data_fd = sd_varlink_peek_dup_fd(link, p.verity_data_fd_idx);
|
||||
if (verity_data_fd < 0)
|
||||
return log_debug_errno(verity_data_fd, "Failed to peek verity data fd from client: %m");
|
||||
|
||||
r = fd_verify_safe_flags(verity_data_fd);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Verity data file descriptor has unsafe flags set: %m");
|
||||
|
||||
verity.data_path = strdup(FORMAT_PROC_FD_PATH(verity_data_fd));
|
||||
if (!verity.data_path)
|
||||
return -ENOMEM;
|
||||
|
||||
verity.designator = PARTITION_ROOT;
|
||||
|
||||
verity.root_hash = TAKE_PTR(p.verity_root_hash.iov_base);
|
||||
verity.root_hash_size = p.verity_root_hash.iov_len;
|
||||
p.verity_root_hash.iov_len = 0;
|
||||
|
||||
verity.root_hash_sig = TAKE_PTR(p.verity_root_hash_sig.iov_base);
|
||||
verity.root_hash_sig_size = p.verity_root_hash_sig.iov_len;
|
||||
p.verity_root_hash_sig.iov_len = 0;
|
||||
}
|
||||
|
||||
const char *polkit_details[] = {
|
||||
"read_only", one_zero(p.read_only > 0),
|
||||
NULL,
|
||||
@@ -408,6 +449,7 @@ static int vl_method_mount_image(
|
||||
DISSECT_IMAGE_ADD_PARTITION_DEVICES |
|
||||
DISSECT_IMAGE_PIN_PARTITION_DEVICES |
|
||||
(p.verity_sharing ? DISSECT_IMAGE_VERITY_SHARE : 0) |
|
||||
(p.verity_data_fd_idx != UINT_MAX ? DISSECT_IMAGE_NO_PARTITION_TABLE : 0) |
|
||||
DISSECT_IMAGE_ALLOW_USERSPACE_VERITY;
|
||||
|
||||
/* Let's see if we have acquired the privilege to mount untrusted images already */
|
||||
|
||||
@@ -6349,6 +6349,7 @@ static int run(int argc, char *argv[]) {
|
||||
arg_image,
|
||||
userns_fd,
|
||||
arg_image_policy,
|
||||
&arg_verity_settings,
|
||||
dissect_image_flags,
|
||||
&dissected_image);
|
||||
if (r < 0)
|
||||
|
||||
@@ -881,7 +881,7 @@ static int dissect_image(
|
||||
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;
|
||||
found_flags = verity->root_hash_sig_size > 0 ? PARTITION_POLICY_SIGNED : PARTITION_POLICY_VERITY;
|
||||
else
|
||||
found_flags = encrypted ? PARTITION_POLICY_ENCRYPTED : PARTITION_POLICY_UNPROTECTED;
|
||||
|
||||
@@ -4513,6 +4513,7 @@ int verity_dissect_and_mount(
|
||||
src_fd >= 0 ? FORMAT_PROC_FD_PATH(src_fd) : src,
|
||||
userns_fd,
|
||||
image_policy,
|
||||
verity,
|
||||
dissect_image_flags,
|
||||
&dissected_image);
|
||||
if (r < 0)
|
||||
@@ -4679,6 +4680,7 @@ int mountfsd_mount_image(
|
||||
const char *path,
|
||||
int userns_fd,
|
||||
const ImagePolicy *image_policy,
|
||||
const VeritySettings *verity,
|
||||
DissectImageFlags flags,
|
||||
DissectedImage **ret) {
|
||||
|
||||
@@ -4695,13 +4697,14 @@ int mountfsd_mount_image(
|
||||
};
|
||||
|
||||
_cleanup_(dissected_image_unrefp) DissectedImage *di = NULL;
|
||||
_cleanup_close_ int image_fd = -EBADF;
|
||||
_cleanup_close_ int image_fd = -EBADF, verity_data_fd = -EBADF;
|
||||
_cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL;
|
||||
_cleanup_free_ char *ps = NULL;
|
||||
const char *error_id;
|
||||
int r;
|
||||
|
||||
assert(path);
|
||||
assert(verity);
|
||||
assert(ret);
|
||||
|
||||
r = sd_varlink_connect_address(&vl, "/run/systemd/io.systemd.MountFileSystem");
|
||||
@@ -4736,6 +4739,16 @@ int mountfsd_mount_image(
|
||||
return log_error_errno(r, "Failed to format image policy to string: %m");
|
||||
}
|
||||
|
||||
if (verity->data_path) {
|
||||
verity_data_fd = open(verity->data_path, O_RDONLY|O_CLOEXEC);
|
||||
if (verity_data_fd < 0)
|
||||
return log_error_errno(errno, "Failed to open verity data file '%s': %m", verity->data_path);
|
||||
|
||||
r = sd_varlink_push_dup_fd(vl, verity_data_fd);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to push verity data fd into varlink connection: %m");
|
||||
}
|
||||
|
||||
sd_json_variant *reply = NULL;
|
||||
r = varlink_callbo_and_log(
|
||||
vl,
|
||||
@@ -4748,6 +4761,9 @@ int mountfsd_mount_image(
|
||||
SD_JSON_BUILD_PAIR("growFileSystems", SD_JSON_BUILD_BOOLEAN(FLAGS_SET(flags, DISSECT_IMAGE_GROWFS))),
|
||||
SD_JSON_BUILD_PAIR_CONDITION(!!ps, "imagePolicy", SD_JSON_BUILD_STRING(ps)),
|
||||
SD_JSON_BUILD_PAIR("veritySharing", SD_JSON_BUILD_BOOLEAN(FLAGS_SET(flags, DISSECT_IMAGE_VERITY_SHARE))),
|
||||
SD_JSON_BUILD_PAIR_CONDITION(verity_data_fd >= 0, "verityDataFileDescriptor", SD_JSON_BUILD_UNSIGNED(userns_fd >= 0 ? 2 : 1)),
|
||||
JSON_BUILD_PAIR_IOVEC_HEX("verityRootHash", &((struct iovec) { .iov_base = verity->root_hash, .iov_len = verity->root_hash_size })),
|
||||
JSON_BUILD_PAIR_IOVEC_BASE64("verityRootHashSignature", &((struct iovec) { .iov_base = verity->root_hash_sig, .iov_len = verity->root_hash_sig_size })),
|
||||
SD_JSON_BUILD_PAIR("allowInteractiveAuthentication", SD_JSON_BUILD_BOOLEAN(FLAGS_SET(flags, DISSECT_IMAGE_ALLOW_INTERACTIVE_AUTH))));
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@@ -257,5 +257,5 @@ static inline const char* dissected_partition_fstype(const DissectedPartition *m
|
||||
|
||||
int get_common_dissect_directory(char **ret);
|
||||
|
||||
int mountfsd_mount_image(const char *path, int userns_fd, const ImagePolicy *image_policy, DissectImageFlags flags, DissectedImage **ret);
|
||||
int mountfsd_mount_image(const char *path, int userns_fd, const ImagePolicy *image_policy, const VeritySettings *verity, DissectImageFlags flags, DissectedImage **ret);
|
||||
int mountfsd_mount_directory(const char *path, int userns_fd, DissectImageFlags flags, int *ret_mount_fd);
|
||||
|
||||
@@ -62,6 +62,12 @@ static SD_VARLINK_DEFINE_METHOD(
|
||||
SD_VARLINK_DEFINE_INPUT(imagePolicy, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("Whether to automatically reuse already set up dm-verity devices that share the same roothash."),
|
||||
SD_VARLINK_DEFINE_INPUT(veritySharing, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("File descriptor of the file containing the dm-verity data, if the image is a bare filesystem rather than a DDI."),
|
||||
SD_VARLINK_DEFINE_INPUT(verityDataFileDescriptor, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("The expected dm-verity root hash as an hex encoded string, if the image is a bare filesystem rather than a DDI."),
|
||||
SD_VARLINK_DEFINE_INPUT(verityRootHash, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("The expected signature for the dm-verity root hash as a Base64 encoded string, if the image is a bare filesystem rather than a DDI."),
|
||||
SD_VARLINK_DEFINE_INPUT(verityRootHashSignature, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
|
||||
VARLINK_DEFINE_POLKIT_INPUT,
|
||||
SD_VARLINK_FIELD_COMMENT("An array with information about contained partitions that have been prepared for mounting, as well as their mount file descriptors."),
|
||||
SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(partitions, PartitionInfo, SD_VARLINK_ARRAY),
|
||||
|
||||
@@ -72,6 +72,25 @@ if [ "$VERITY_SIG_SUPPORTED" -eq 1 ]; then
|
||||
systemd-run -M testuser@ --user --pipe --wait \
|
||||
--property RootImage="$MINIMAL_IMAGE.gpt" \
|
||||
test -e "/dev/mapper/${MINIMAL_IMAGE_ROOTHASH}-verity"
|
||||
|
||||
systemd-run -M testuser@ --user --pipe --wait \
|
||||
--property RootImage="$MINIMAL_IMAGE.raw" \
|
||||
--property ExtensionImages=/tmp/app0.raw \
|
||||
sh -c "test -e \"/dev/mapper/${MINIMAL_IMAGE_ROOTHASH}-verity\" && test -e \"/dev/mapper/$(</tmp/app0.roothash)-verity\""
|
||||
|
||||
# Without a signature this should not work, as mountfsd should reject it, even if we explicitly ask to
|
||||
# trust it
|
||||
mv /tmp/app0.roothash.p7s /tmp/app0.roothash.p7s.bak
|
||||
(! systemd-run -M testuser@ --user --pipe --wait \
|
||||
--property RootImage="$MINIMAL_IMAGE.raw" \
|
||||
--property ExtensionImages=/tmp/app0.raw \
|
||||
sh -c "test -e \"/dev/mapper/${MINIMAL_IMAGE_ROOTHASH}-verity\" && test -e \"/dev/mapper/$(</tmp/app0.roothash)-verity\"")
|
||||
(! systemd-run -M testuser@ --user --pipe --wait \
|
||||
--property RootImage="$MINIMAL_IMAGE.raw" \
|
||||
--property ExtensionImages=/tmp/app0.raw \
|
||||
--property ExtensionImagePolicy=root=verity+signed+absent:usr=verity+signed+absent \
|
||||
sh -c "test -e \"/dev/mapper/${MINIMAL_IMAGE_ROOTHASH}-verity\" && test -e \"/dev/mapper/$(</tmp/app0.roothash)-verity\"")
|
||||
mv /tmp/app0.roothash.p7s.bak /tmp/app0.roothash.p7s
|
||||
fi
|
||||
|
||||
# Install key in keychain
|
||||
|
||||
Reference in New Issue
Block a user