core: add RootHash and RootVerity service parameters

Allow to explicitly pass root hash (explicitly or as a file) and verity
device/file as unit options. Take precedence over implicit checks.
This commit is contained in:
Luca Boccassi 2020-06-03 09:50:45 +01:00 committed by Lennart Poettering
parent 6fe01ced0e
commit 0389f4fa81
17 changed files with 262 additions and 25 deletions

View File

@ -145,6 +145,43 @@
<xi:include href="system-only.xml" xpointer="singular"/></listitem>
</varlistentry>
<varlistentry>
<term><varname>RootHash=</varname></term>
<listitem><para>Takes a data integrity (dm-verity) root hash specified in hexadecimal, or the path to a file
containing a root hash in ASCII hexadecimal format. This option enables data integrity checks using dm-verity,
if the used image contains the appropriate integrity data (see above) or if <varname>RootVerity=</varname> is used.
The specified hash must match the root hash of integrity data, and is usually at least 256 bits (and hence 64
formatted hexadecimal characters) long (in case of SHA256 for example). If this option is not specified, but
the image file carries the <literal>user.verity.roothash</literal> extended file attribute (see <citerefentry
project='man-pages'><refentrytitle>xattr</refentrytitle><manvolnum>7</manvolnum></citerefentry>), then the root
hash is read from it, also as formatted hexadecimal characters. If the extended file attribute is not found (or
is not supported by the underlying file system), but a file with the <filename>.roothash</filename> suffix is
found next to the image file, bearing otherwise the same name (except if the image has the
<filename>.raw</filename> suffix, in which case the root hash file must not have it in its name), the root hash
is read from it and automatically used, also as formatted hexadecimal characters.</para>
<xi:include href="system-only.xml" xpointer="singular"/></listitem>
</varlistentry>
<varlistentry>
<term><varname>RootVerity=</varname></term>
<listitem><para>Takes the path to a data integrity (dm-verity) file. This option enables data integrity checks
using dm-verity, if <varname>RootImage=</varname> is used and a root-hash is passed and if the used image itself
does not contains the integrity data. The integrity data must be matched by the root hash. If this option is not
specified, but a file with the <filename>.verity</filename> suffix is found next to the image file, bearing otherwise
the same name (except if the image has the <filename>.raw</filename> suffix, in which case the verity data file must
not have it in its name), the verity data is read from it and automatically used.</para>
<para>This option is supported only for disk images that contain a single file system, without an enveloping partition
table. Images that contain a GPT partition table should instead include both root file system and matching Verity
data in the same image, implementing the
[Discoverable Partition Specification](https://systemd.io/DISCOVERABLE_PARTITIONS)</para>
<xi:include href="system-only.xml" xpointer="singular"/></listitem>
</varlistentry>
<varlistentry>
<term><varname>MountAPIVFS=</varname></term>

View File

@ -746,6 +746,25 @@ static int property_get_log_extra_fields(
return sd_bus_message_close_container(reply);
}
static int property_get_root_hash(
sd_bus *bus,
const char *path,
const char *interface,
const char *property,
sd_bus_message *reply,
void *userdata,
sd_bus_error *error) {
ExecContext *c = userdata;
assert(bus);
assert(c);
assert(property);
assert(reply);
return sd_bus_message_append_array(reply, 'y', c->root_hash, c->root_hash_size);
}
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),
@ -788,6 +807,9 @@ const sd_bus_vtable bus_exec_vtable[] = {
SD_BUS_PROPERTY("WorkingDirectory", "s", property_get_working_directory, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RootDirectory", "s", NULL, offsetof(ExecContext, root_directory), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RootImage", "s", NULL, offsetof(ExecContext, root_image), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RootHash", "ay", property_get_root_hash, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RootHashPath", "s", NULL, offsetof(ExecContext, root_hash_path), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RootVerity", "s", NULL, offsetof(ExecContext, root_verity), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("OOMScoreAdjust", "i", property_get_oom_score_adjust, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("CoredumpFilter", "t", property_get_coredump_filter, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("Nice", "i", property_get_nice, 0, SD_BUS_VTABLE_PROPERTY_CONST),
@ -1258,6 +1280,55 @@ int bus_exec_context_set_transient_property(
if (streq(name, "RootImage"))
return bus_set_transient_path(u, name, &c->root_image, message, flags, error);
if (streq(name, "RootHash")) {
const void *roothash_decoded;
size_t roothash_decoded_size;
r = sd_bus_message_read_array(message, 'y', &roothash_decoded, &roothash_decoded_size);
if (r < 0)
return r;
if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
_cleanup_free_ char *encoded = NULL;
if (roothash_decoded_size == 0) {
c->root_hash_path = mfree(c->root_hash_path);
c->root_hash = mfree(c->root_hash);
c->root_hash_size = 0;
unit_write_settingf(u, flags, name, "RootHash=");
} else {
_cleanup_free_ void *p;
encoded = hexmem(roothash_decoded, roothash_decoded_size);
if (!encoded)
return -ENOMEM;
p = memdup(roothash_decoded, roothash_decoded_size);
if (!p)
return -ENOMEM;
free_and_replace(c->root_hash, p);
c->root_hash_size = roothash_decoded_size;
c->root_hash_path = mfree(c->root_hash_path);
unit_write_settingf(u, flags, name, "RootHash=%s", encoded);
}
}
return 1;
}
if (streq(name, "RootHashPath")) {
c->root_hash_size = 0;
c->root_hash = mfree(c->root_hash);
return bus_set_transient_path(u, "RootHash", &c->root_hash_path, message, flags, error);
}
if (streq(name, "RootVerity"))
return bus_set_transient_path(u, name, &c->root_verity, message, flags, error);
if (streq(name, "RootDirectory"))
return bus_set_transient_path(u, name, &c->root_directory, message, flags, error);

View File

@ -54,6 +54,7 @@
#include "format-util.h"
#include "fs-util.h"
#include "glob-util.h"
#include "hexdecoct.h"
#include "io-util.h"
#include "ioprio.h"
#include "label.h"
@ -2666,6 +2667,7 @@ static int apply_mount_namespace(
needs_sandboxing ? context->protect_home : PROTECT_HOME_NO,
needs_sandboxing ? context->protect_system : PROTECT_SYSTEM_NO,
context->mount_flags,
context->root_hash, context->root_hash_size, context->root_hash_path, context->root_verity,
DISSECT_IMAGE_DISCARD_ON_LOOP|DISSECT_IMAGE_RELAX_VAR_CHECK|DISSECT_IMAGE_FSCK,
error_path);
@ -4195,6 +4197,10 @@ void exec_context_done(ExecContext *c) {
c->working_directory = mfree(c->working_directory);
c->root_directory = mfree(c->root_directory);
c->root_image = mfree(c->root_image);
c->root_hash = mfree(c->root_hash);
c->root_hash_size = 0;
c->root_hash_path = mfree(c->root_hash_path);
c->root_verity = mfree(c->root_verity);
c->tty_path = mfree(c->tty_path);
c->syslog_identifier = mfree(c->syslog_identifier);
c->user = mfree(c->user);
@ -4599,6 +4605,19 @@ void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix) {
if (c->root_image)
fprintf(f, "%sRootImage: %s\n", prefix, c->root_image);
if (c->root_hash) {
_cleanup_free_ char *encoded = NULL;
encoded = hexmem(c->root_hash, c->root_hash_size);
if (encoded)
fprintf(f, "%sRootHash: %s\n", prefix, encoded);
}
if (c->root_hash_path)
fprintf(f, "%sRootHash: %s\n", prefix, c->root_hash_path);
if (c->root_verity)
fprintf(f, "%sRootVerity: %s\n", prefix, c->root_verity);
STRV_FOREACH(e, c->environment)
fprintf(f, "%sEnvironment: %s\n", prefix, *e);

View File

@ -155,7 +155,9 @@ struct ExecContext {
char **unset_environment;
struct rlimit *rlimit[_RLIMIT_MAX];
char *working_directory, *root_directory, *root_image;
char *working_directory, *root_directory, *root_image, *root_verity, *root_hash_path;
void *root_hash;
size_t root_hash_size;
bool working_directory_missing_ok:1;
bool working_directory_home:1;

View File

@ -23,6 +23,8 @@ m4_define(`EXEC_CONTEXT_CONFIG_ITEMS',
`$1.WorkingDirectory, config_parse_working_directory, 0, offsetof($1, exec_context)
$1.RootDirectory, config_parse_unit_path_printf, true, offsetof($1, exec_context.root_directory)
$1.RootImage, config_parse_unit_path_printf, true, offsetof($1, exec_context.root_image)
$1.RootHash, config_parse_exec_root_hash, 0, offsetof($1, exec_context)
$1.RootVerity, config_parse_unit_path_printf, true, offsetof($1, exec_context.root_verity)
$1.User, config_parse_user_group_compat, 0, offsetof($1, exec_context.user)
$1.Group, config_parse_user_group_compat, 0, offsetof($1, exec_context.group)
$1.SupplementaryGroups, config_parse_user_group_strv_compat, 0, offsetof($1, exec_context.supplementary_groups)

View File

@ -29,6 +29,7 @@
#include "errno-list.h"
#include "escape.h"
#include "fd-util.h"
#include "fileio.h"
#include "fs-util.h"
#include "hexdecoct.h"
#include "io-util.h"
@ -1413,6 +1414,64 @@ int config_parse_exec_cpu_sched_prio(const char *unit,
return 0;
}
int config_parse_exec_root_hash(
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_free_ void *roothash_decoded = NULL;
ExecContext *c = data;
size_t roothash_decoded_size = 0;
int r;
assert(data);
assert(filename);
assert(line);
assert(rvalue);
if (isempty(rvalue)) {
/* Reset if the empty string is assigned */
c->root_hash_path = mfree(c->root_hash_path);
c->root_hash = mfree(c->root_hash);
c->root_hash_size = 0;
return 0;
}
if (path_is_absolute(rvalue)) {
/* We have the path to a roothash to load and decode, eg: RootHash=/foo/bar.roothash */
_cleanup_free_ char *p = NULL;
p = strdup(rvalue);
if (!p)
return -ENOMEM;
free_and_replace(c->root_hash_path, p);
c->root_hash = mfree(c->root_hash);
c->root_hash_size = 0;
return 0;
}
/* We have a roothash to decode, eg: RootHash=012345789abcdef */
r = unhexmem(rvalue, strlen(rvalue), &roothash_decoded, &roothash_decoded_size);
if (r < 0)
return log_syntax(unit, LOG_ERR, filename, line, r, "Failed to decode RootHash=, ignoring: %s", rvalue);
if (roothash_decoded_size < sizeof(sd_id128_t))
return log_syntax(unit, LOG_ERR, filename, line, SYNTHETIC_ERRNO(EINVAL), "RootHash= is too short, ignoring: %s", rvalue);
free_and_replace(c->root_hash, roothash_decoded);
c->root_hash_size = roothash_decoded_size;
c->root_hash_path = mfree(c->root_hash_path);
return 0;
}
int config_parse_exec_cpu_affinity(const char *unit,
const char *filename,
unsigned line,

View File

@ -44,6 +44,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_exec_cpu_sched_policy);
CONFIG_PARSER_PROTOTYPE(config_parse_exec_cpu_sched_prio);
CONFIG_PARSER_PROTOTYPE(config_parse_exec_cpu_affinity);
CONFIG_PARSER_PROTOTYPE(config_parse_exec_secure_bits);
CONFIG_PARSER_PROTOTYPE(config_parse_exec_root_hash);
CONFIG_PARSER_PROTOTYPE(config_parse_capability_set);
CONFIG_PARSER_PROTOTYPE(config_parse_exec_mount_flags);
CONFIG_PARSER_PROTOTYPE(config_parse_timer);

View File

@ -1257,16 +1257,20 @@ int setup_namespace(
ProtectHome protect_home,
ProtectSystem protect_system,
unsigned long mount_flags,
const void *root_hash,
size_t root_hash_size,
const char *root_hash_path,
const char *root_verity,
DissectImageFlags dissect_image_flags,
char **error_path) {
_cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
_cleanup_(decrypted_image_unrefp) DecryptedImage *decrypted_image = NULL;
_cleanup_(dissected_image_unrefp) DissectedImage *dissected_image = NULL;
_cleanup_free_ void *root_hash = NULL;
_cleanup_free_ void *root_hash_decoded = NULL;
_cleanup_free_ char *verity_data = NULL;
MountEntry *m = NULL, *mounts = NULL;
size_t n_mounts, root_hash_size = 0;
size_t n_mounts;
bool require_prefix = false;
const char *root;
int r = 0;
@ -1295,16 +1299,16 @@ int setup_namespace(
if (r < 0)
return log_debug_errno(r, "Failed to create loop device for root image: %m");
r = verity_metadata_load(root_image, &root_hash, &root_hash_size, &verity_data);
r = verity_metadata_load(root_image, root_hash_path, root_hash ? NULL : &root_hash_decoded, root_hash ? NULL : &root_hash_size, root_verity ? NULL : &verity_data);
if (r < 0)
return log_debug_errno(r, "Failed to load root hash: %m");
dissect_image_flags |= verity_data ? DISSECT_IMAGE_NO_PARTITION_TABLE : 0;
dissect_image_flags |= root_verity || verity_data ? DISSECT_IMAGE_NO_PARTITION_TABLE : 0;
r = dissect_image(loop_device->fd, root_hash, root_hash_size, verity_data, dissect_image_flags, &dissected_image);
r = dissect_image(loop_device->fd, root_hash ?: root_hash_decoded, root_hash_size, root_verity ?: verity_data, dissect_image_flags, &dissected_image);
if (r < 0)
return log_debug_errno(r, "Failed to dissect image: %m");
r = dissected_image_decrypt(dissected_image, NULL, root_hash, root_hash_size, verity_data, dissect_image_flags, &decrypted_image);
r = dissected_image_decrypt(dissected_image, NULL, root_hash ?: root_hash_decoded, root_hash_size, root_verity ?: verity_data, dissect_image_flags, &decrypted_image);
if (r < 0)
return log_debug_errno(r, "Failed to decrypt dissected image: %m");
}

View File

@ -88,6 +88,10 @@ int setup_namespace(
ProtectHome protect_home,
ProtectSystem protect_system,
unsigned long mount_flags,
const void *root_hash,
size_t root_hash_size,
const char *root_hash_path,
const char *root_verity,
DissectImageFlags dissected_image_flags,
char **error_path);

View File

@ -201,7 +201,7 @@ static int run(int argc, char *argv[]) {
if (r < 0)
return log_error_errno(r, "Failed to set up loopback device: %m");
r = verity_metadata_load(arg_image, arg_root_hash ? NULL : &arg_root_hash, &arg_root_hash_size,
r = verity_metadata_load(arg_image, NULL, arg_root_hash ? NULL : &arg_root_hash, &arg_root_hash_size,
arg_verity_data ? NULL : &arg_verity_data);
if (r < 0)
return log_error_errno(r, "Failed to read verity artefacts for %s: %m", arg_image);

View File

@ -5141,7 +5141,7 @@ static int run(int argc, char *argv[]) {
goto finish;
}
r = verity_metadata_load(arg_image, arg_root_hash ? NULL : &arg_root_hash, &arg_root_hash_size,
r = verity_metadata_load(arg_image, NULL, arg_root_hash ? NULL : &arg_root_hash, &arg_root_hash_size,
arg_verity_data ? NULL : &arg_verity_data);
if (r < 0) {
log_error_errno(r, "Failed to read verity artefacts for %s: %m", arg_image);

View File

@ -13,6 +13,7 @@
#include "escape.h"
#include "exec-util.h"
#include "exit-status.h"
#include "fileio.h"
#include "hexdecoct.h"
#include "hostname-util.h"
#include "in-addr-util.h"
@ -24,6 +25,7 @@
#include "nsflags.h"
#include "numa-util.h"
#include "parse-util.h"
#include "path-util.h"
#include "process-util.h"
#include "rlimit-util.h"
#include "securebits-util.h"
@ -849,6 +851,7 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con
"ProtectHome",
"SELinuxContext",
"RootImage",
"RootVerity",
"RuntimeDirectoryPreserve",
"Personality",
"KeyringMode",
@ -1415,6 +1418,24 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con
return 1;
}
if (streq(field, "RootHash")) {
_cleanup_free_ void *roothash_decoded = NULL;
size_t roothash_decoded_size = 0;
/* We have the path to a roothash to load and decode, eg: RootHash=/foo/bar.roothash */
if (path_is_absolute(eq))
return bus_append_string(m, "RootHashPath", eq);
/* We have a roothash to decode, eg: RootHash=012345789abcdef */
r = unhexmem(eq, strlen(eq), &roothash_decoded, &roothash_decoded_size);
if (r < 0)
return log_error_errno(r, "Failed to decode RootHash= '%s': %m", eq);
if (roothash_decoded_size < sizeof(sd_id128_t))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "RootHash= '%s' is too short: %m", eq);
return bus_append_byte_array(m, field, roothash_decoded, roothash_decoded_size);
}
return 0;
}

View File

@ -1421,7 +1421,7 @@ int decrypted_image_relinquish(DecryptedImage *d) {
return 0;
}
int verity_metadata_load(const char *image, void **ret_roothash, size_t *ret_roothash_size, char **ret_verity_data) {
int verity_metadata_load(const char *image, const char *root_hash_path, void **ret_roothash, size_t *ret_roothash_size, char **ret_verity_data) {
_cleanup_free_ char *verity_filename = NULL;
_cleanup_free_ void *roothash_decoded = NULL;
size_t roothash_decoded_size = 0;
@ -1465,24 +1465,31 @@ int verity_metadata_load(const char *image, void **ret_roothash, size_t *ret_roo
_cleanup_free_ char *text = NULL;
assert(ret_roothash_size);
r = getxattr_malloc(image, "user.verity.roothash", &text, true);
if (r < 0) {
char *fn, *e, *n;
if (!IN_SET(r, -ENODATA, -EOPNOTSUPP, -ENOENT))
if (root_hash_path) {
/* We have the path to a roothash to load and decode, eg: RootHash=/foo/bar.roothash */
r = read_one_line_file(root_hash_path, &text);
if (r < 0)
return r;
} else {
r = getxattr_malloc(image, "user.verity.roothash", &text, true);
if (r < 0) {
char *fn, *e, *n;
fn = newa(char, strlen(image) + STRLEN(".roothash") + 1);
n = stpcpy(fn, image);
e = endswith(fn, ".raw");
if (e)
n = e;
if (!IN_SET(r, -ENODATA, -EOPNOTSUPP, -ENOENT))
return r;
strcpy(n, ".roothash");
fn = newa(char, strlen(image) + STRLEN(".roothash") + 1);
n = stpcpy(fn, image);
e = endswith(fn, ".raw");
if (e)
n = e;
r = read_one_line_file(fn, &text);
if (r < 0 && r != -ENOENT)
return r;
strcpy(n, ".roothash");
r = read_one_line_file(fn, &text);
if (r < 0 && r != -ENOENT)
return r;
}
}
if (text) {

View File

@ -100,6 +100,6 @@ int decrypted_image_relinquish(DecryptedImage *d);
const char* partition_designator_to_string(int i) _const_;
int partition_designator_from_string(const char *name) _pure_;
int verity_metadata_load(const char *image, void **ret_roothash, size_t *ret_roothash_size, char **ret_verity_data);
int verity_metadata_load(const char *image, const char *root_hash_path, void **ret_roothash, size_t *ret_roothash_size, char **ret_verity_data);
bool dissected_image_can_do_verity(const DissectedImage *image, unsigned partition_designator);
bool dissected_image_has_verity(const DissectedImage *image, unsigned partition_designator);

View File

@ -152,6 +152,10 @@ static void test_protect_kernel_logs(void) {
PROTECT_HOME_NO,
PROTECT_SYSTEM_NO,
0,
NULL,
0,
NULL,
NULL,
0,
NULL);
assert_se(r == 0);

View File

@ -76,6 +76,10 @@ int main(int argc, char *argv[]) {
PROTECT_HOME_NO,
PROTECT_SYSTEM_NO,
0,
NULL,
0,
NULL,
NULL,
0,
NULL);
if (r < 0) {

View File

@ -196,6 +196,8 @@ ReusePort=
RootDirectory=
RootDirectoryStartOnly=
RootImage=
RootHash=
RootVerity=
RuntimeMaxSec=
SELinuxContextFromNet=
SecureBits=