Merge pull request #20902 from tasleson/integritysetup-generator
Add stand-alone dm integrity support
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#include "integrity-util.h"
|
||||
|
||||
#include "extract-word.h"
|
||||
#include "fileio.h"
|
||||
#include "path-util.h"
|
||||
#include "percent-util.h"
|
||||
|
||||
|
||||
static int supported_integrity_algorithm(char *user_supplied) {
|
||||
if (!STR_IN_SET(user_supplied, "crc32", "crc32c", "sha1", "sha256", "hmac-sha256"))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unsupported integrity algorithm (%s)", user_supplied);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int parse_integrity_options(
|
||||
const char *options,
|
||||
uint32_t *ret_activate_flags,
|
||||
int *ret_percent,
|
||||
usec_t *ret_commit_time,
|
||||
char **ret_data_device,
|
||||
char **ret_integrity_alg) {
|
||||
int r;
|
||||
|
||||
for (;;) {
|
||||
_cleanup_free_ char *word = NULL;
|
||||
char *val;
|
||||
|
||||
r = extract_first_word(&options, &word, ",", EXTRACT_DONT_COALESCE_SEPARATORS | EXTRACT_UNESCAPE_SEPARATORS);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse options: %m");
|
||||
if (r == 0)
|
||||
break;
|
||||
else if (streq(word, "allow-discards")) {
|
||||
if (ret_activate_flags)
|
||||
*ret_activate_flags |= CRYPT_ACTIVATE_ALLOW_DISCARDS;
|
||||
} else if ((val = startswith(word, "journal-watermark="))) {
|
||||
r = parse_percent(val);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse journal-watermark value or value out of range (%s)", val);
|
||||
if (ret_percent)
|
||||
*ret_percent = r;
|
||||
} else if ((val = startswith(word, "journal-commit-time="))) {
|
||||
usec_t tmp_commit_time;
|
||||
r = parse_sec(val, &tmp_commit_time);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse journal-commit-time value (%s)", val);
|
||||
if (ret_commit_time)
|
||||
*ret_commit_time = tmp_commit_time;
|
||||
} else if ((val = startswith(word, "data-device="))) {
|
||||
r = free_and_strdup(ret_data_device, val);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
} else if ((val = startswith(word, "integrity-algorithm="))) {
|
||||
r = free_and_strdup(ret_integrity_alg, val);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
r = supported_integrity_algorithm(*ret_integrity_alg);
|
||||
if (r < 0)
|
||||
return r;
|
||||
} else
|
||||
log_warning("Encountered unknown option '%s', ignoring.", word);
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "cryptsetup-util.h"
|
||||
#include "time-util.h"
|
||||
|
||||
|
||||
int parse_integrity_options(
|
||||
const char *options,
|
||||
uint32_t *ret_activate_flags,
|
||||
int *ret_percent,
|
||||
usec_t *ret_commit_time,
|
||||
char **ret_data_device,
|
||||
char **ret_integrity_alg);
|
||||
|
||||
#define DM_HMAC_256 "hmac(sha256)"
|
||||
#define DM_MAX_KEY_SIZE 4096 /* Maximum size of key allowed for dm-integrity */
|
||||
@@ -0,0 +1,181 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "fd-util.h"
|
||||
#include "fileio.h"
|
||||
#include "fstab-util.h"
|
||||
#include "generator.h"
|
||||
#include "hexdecoct.h"
|
||||
#include "id128-util.h"
|
||||
#include "integrity-util.h"
|
||||
#include "main-func.h"
|
||||
#include "mkdir.h"
|
||||
#include "parse-util.h"
|
||||
#include "path-util.h"
|
||||
#include "proc-cmdline.h"
|
||||
#include "specifier.h"
|
||||
#include "string-util.h"
|
||||
#include "unit-name.h"
|
||||
|
||||
static const char *arg_dest = NULL;
|
||||
static const char *arg_integritytab = NULL;
|
||||
static char *arg_options = NULL;
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_options, freep);
|
||||
|
||||
static int create_disk(
|
||||
const char *name,
|
||||
const char *device,
|
||||
const char *key_file,
|
||||
const char *options) {
|
||||
|
||||
_cleanup_free_ char *n = NULL, *dd = NULL, *e = NULL, *name_escaped = NULL, *key_file_escaped = NULL;
|
||||
_cleanup_fclose_ FILE *f = NULL;
|
||||
int r;
|
||||
char *dmname = NULL;
|
||||
|
||||
assert(name);
|
||||
assert(device);
|
||||
|
||||
name_escaped = specifier_escape(name);
|
||||
if (!name_escaped)
|
||||
return log_oom();
|
||||
|
||||
e = unit_name_escape(name);
|
||||
if (!e)
|
||||
return log_oom();
|
||||
|
||||
r = unit_name_build("systemd-integritysetup", e, ".service", &n);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to generate unit name: %m");
|
||||
|
||||
r = unit_name_from_path(device, ".device", &dd);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to generate unit name: %m");
|
||||
|
||||
r = generator_open_unit_file(arg_dest, NULL, n, &f);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (key_file) {
|
||||
if (!path_is_absolute(key_file))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "key file not absolute file path %s", key_file);
|
||||
|
||||
key_file_escaped = specifier_escape(key_file);
|
||||
if (!key_file_escaped)
|
||||
return log_oom();
|
||||
}
|
||||
|
||||
if (options) {
|
||||
r = parse_integrity_options(options, NULL, NULL, NULL, NULL, NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
fprintf(f,
|
||||
"[Unit]\n"
|
||||
"Description=Integrity Setup for %%I\n"
|
||||
"Documentation=man:integritytab(5) man:systemd-integritysetup-generator(8) man:systemd-integritysetup@.service(8)\n"
|
||||
"SourcePath=%s\n"
|
||||
"DefaultDependencies=no\n"
|
||||
"IgnoreOnIsolate=true\n"
|
||||
"After=integritysetup-pre.target systemd-udevd-kernel.socket\n"
|
||||
"Before=blockdev@dev-mapper-%%i.target\n"
|
||||
"Wants=blockdev@dev-mapper-%%i.target\n"
|
||||
"Conflicts=umount.target\n"
|
||||
"Before=integritysetup.target\n"
|
||||
"BindsTo=%s\n"
|
||||
"After=%s\n"
|
||||
"Before=umount.target\n",
|
||||
arg_integritytab,
|
||||
dd, dd);
|
||||
|
||||
fprintf(f,
|
||||
"\n"
|
||||
"[Service]\n"
|
||||
"Type=oneshot\n"
|
||||
"RemainAfterExit=yes\n"
|
||||
"TimeoutSec=0\n"
|
||||
"ExecStart=" ROOTLIBEXECDIR "/systemd-integritysetup attach '%s' '%s' '%s' '%s'\n"
|
||||
"ExecStop=" ROOTLIBEXECDIR "/systemd-integritysetup detach '%s'\n",
|
||||
name_escaped, device, empty_to_dash(key_file_escaped), empty_to_dash(options),
|
||||
name_escaped);
|
||||
|
||||
r = fflush_and_check(f);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to write unit file %s: %m", n);
|
||||
|
||||
r = generator_add_symlink(arg_dest, "integritysetup.target", "requires", n);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
dmname = strjoina("dev-mapper-", e, ".device");
|
||||
return generator_add_symlink(arg_dest, dmname, "requires", n);
|
||||
}
|
||||
|
||||
static int add_integritytab_devices(void) {
|
||||
_cleanup_fclose_ FILE *f = NULL;
|
||||
unsigned integritytab_line = 0;
|
||||
int r;
|
||||
|
||||
r = fopen_unlocked(arg_integritytab, "re", &f);
|
||||
if (r < 0) {
|
||||
if (errno != ENOENT)
|
||||
log_error_errno(errno, "Failed to open %s: %m", arg_integritytab);
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
_cleanup_free_ char *line = NULL, *name = NULL, *device_id = NULL, *device_path = NULL, *key_file = NULL, *options = NULL;
|
||||
char *l;
|
||||
|
||||
r = read_line(f, LONG_LINE_MAX, &line);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to read %s: %m", arg_integritytab);
|
||||
if (r == 0)
|
||||
break;
|
||||
|
||||
integritytab_line++;
|
||||
|
||||
l = strstrip(line);
|
||||
if (!l)
|
||||
continue;
|
||||
|
||||
if (IN_SET(l[0], 0, '#'))
|
||||
continue;
|
||||
|
||||
/* The key file and the options are optional */
|
||||
r = sscanf(l, "%ms %ms %ms %ms", &name, &device_id, &key_file, &options);
|
||||
if (!IN_SET(r, 2, 3, 4)) {
|
||||
log_error("Failed to parse %s:%u, ignoring.", l, integritytab_line);
|
||||
continue;
|
||||
}
|
||||
|
||||
device_path = fstab_node_to_udev_node(device_id);
|
||||
if (!device_path) {
|
||||
log_error("Failed to find device %s:%u, ignoring.", device_id, integritytab_line);
|
||||
continue;
|
||||
}
|
||||
|
||||
r = create_disk(name, device_path, empty_or_dash_to_null(key_file), empty_or_dash_to_null(options));
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int run(const char *dest, const char *dest_early, const char *dest_late) {
|
||||
assert_se(arg_dest = dest);
|
||||
|
||||
arg_integritytab = getenv("SYSTEMD_INTEGRITYTAB") ?: "/etc/integritytab";
|
||||
|
||||
return add_integritytab_devices();
|
||||
}
|
||||
|
||||
DEFINE_MAIN_GENERATOR_FUNCTION(run);
|
||||
@@ -0,0 +1,197 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "cryptsetup-util.h"
|
||||
#include "fileio.h"
|
||||
#include "hexdecoct.h"
|
||||
#include "integrity-util.h"
|
||||
#include "log.h"
|
||||
#include "main-func.h"
|
||||
#include "memory-util.h"
|
||||
#include "path-util.h"
|
||||
#include "parse-util.h"
|
||||
#include "pretty-print.h"
|
||||
#include "string-util.h"
|
||||
#include "terminal-util.h"
|
||||
|
||||
static uint32_t arg_activate_flags;
|
||||
static int arg_percent;
|
||||
static usec_t arg_commit_time;
|
||||
static char *arg_existing_data_device;
|
||||
static char *arg_integrity_algorithm;
|
||||
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_existing_data_device, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_integrity_algorithm, freep);
|
||||
|
||||
static int help(void) {
|
||||
_cleanup_free_ char *link = NULL;
|
||||
int r;
|
||||
|
||||
r = terminal_urlify_man("systemd-integritysetup@.service", "8", &link);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
printf("%s attach VOLUME DEVICE [HMAC_KEY_FILE|-] [OPTIONS]\n"
|
||||
"%s detach VOLUME\n\n"
|
||||
"Attach or detach an integrity protected block device.\n"
|
||||
"\nSee the %s for details.\n",
|
||||
program_invocation_short_name,
|
||||
program_invocation_short_name,
|
||||
link);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int load_key_file(
|
||||
const char *key_file,
|
||||
void **ret_key_file_contents,
|
||||
size_t *ret_key_file_size) {
|
||||
int r;
|
||||
_cleanup_(erase_and_freep) char *tmp_key_file_contents = NULL;
|
||||
size_t tmp_key_file_size;
|
||||
|
||||
if (!path_is_absolute(key_file))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "key file not absolute path: %s", key_file);
|
||||
|
||||
r = read_full_file_full(
|
||||
AT_FDCWD, key_file, UINT64_MAX, DM_MAX_KEY_SIZE,
|
||||
READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE|READ_FULL_FILE_CONNECT_SOCKET|READ_FULL_FILE_FAIL_WHEN_LARGER,
|
||||
NULL,
|
||||
&tmp_key_file_contents, &tmp_key_file_size);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to process key file: %m");
|
||||
|
||||
if (ret_key_file_contents && ret_key_file_size) {
|
||||
*ret_key_file_contents = TAKE_PTR(tmp_key_file_contents);
|
||||
*ret_key_file_size = tmp_key_file_size;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const char *integrity_algorithm_select(const void *key_file_buf) {
|
||||
/* To keep a bit of sanity for end users, the subset of integrity
|
||||
algorithms we support will match what is used in integritysetup */
|
||||
if (arg_integrity_algorithm) {
|
||||
if (streq("hmac-sha256", arg_integrity_algorithm))
|
||||
return DM_HMAC_256;
|
||||
return arg_integrity_algorithm;
|
||||
} else if (key_file_buf)
|
||||
return DM_HMAC_256;
|
||||
return "crc32c";
|
||||
}
|
||||
|
||||
static int run(int argc, char *argv[]) {
|
||||
_cleanup_(crypt_freep) struct crypt_device *cd = NULL;
|
||||
int r;
|
||||
char *action, *volume;
|
||||
|
||||
if (argc <= 1 ||
|
||||
strv_contains(strv_skip(argv, 1), "--help") ||
|
||||
strv_contains(strv_skip(argv, 1), "-h") ||
|
||||
streq(argv[1], "help"))
|
||||
return help();
|
||||
|
||||
if (argc < 3)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program requires at least two arguments.");
|
||||
|
||||
action = argv[1];
|
||||
volume = argv[2];
|
||||
|
||||
log_setup();
|
||||
|
||||
cryptsetup_enable_logging(NULL);
|
||||
|
||||
umask(0022);
|
||||
|
||||
if (streq(action, "attach")) {
|
||||
/* attach name device optional_key_file optional_options */
|
||||
|
||||
crypt_status_info status;
|
||||
_cleanup_(erase_and_freep) void *key_buf = NULL;
|
||||
const char *device, *key_file, *options;
|
||||
size_t key_buf_size = 0;
|
||||
|
||||
if (argc < 4)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "attach requires at least three arguments.");
|
||||
|
||||
if (argc > 6)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "attach has a maximum of five arguments.");
|
||||
|
||||
device = argv[3];
|
||||
key_file = (argc > 4) ? empty_or_dash_to_null(argv[4]) : NULL;
|
||||
options = (argc > 5) ? empty_or_dash_to_null(argv[5]) : NULL;
|
||||
|
||||
if (key_file) {
|
||||
r = load_key_file(key_file, &key_buf, &key_buf_size);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
if (options) {
|
||||
r = parse_integrity_options(options, &arg_activate_flags, &arg_percent,
|
||||
&arg_commit_time, &arg_existing_data_device, &arg_integrity_algorithm);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
r = crypt_init(&cd, device);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to open integrity device %s: %m", device);
|
||||
|
||||
cryptsetup_enable_logging(cd);
|
||||
|
||||
status = crypt_status(cd, volume);
|
||||
if (IN_SET(status, CRYPT_ACTIVE, CRYPT_BUSY)) {
|
||||
log_info("Volume %s already active.", volume);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!isempty(arg_existing_data_device)) {
|
||||
r = crypt_init_data_device(&cd, device, arg_existing_data_device);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to add separate data device: %m");
|
||||
}
|
||||
|
||||
r = crypt_load(cd,
|
||||
CRYPT_INTEGRITY,
|
||||
&(struct crypt_params_integrity) {
|
||||
.journal_watermark = arg_percent,
|
||||
.journal_commit_time = DIV_ROUND_UP(arg_commit_time, USEC_PER_SEC),
|
||||
.integrity = integrity_algorithm_select(key_buf),
|
||||
});
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to load integrity superblock: %m");
|
||||
|
||||
r = crypt_activate_by_volume_key(cd, volume, key_buf, key_buf_size, arg_activate_flags);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to set up integrity device: %m");
|
||||
|
||||
} else if (streq(action, "detach")) {
|
||||
|
||||
if (argc > 3)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "detach has a maximum of two arguments.");
|
||||
|
||||
r = crypt_init_by_name(&cd, volume);
|
||||
if (r == -ENODEV)
|
||||
return 0;
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "crypt_init_by_name() failed: %m");
|
||||
|
||||
cryptsetup_enable_logging(cd);
|
||||
|
||||
r = crypt_deactivate(cd, volume);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to deactivate: %m");
|
||||
|
||||
} else
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown verb %s.", action);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
DEFINE_MAIN_FUNCTION(run);
|
||||
@@ -100,7 +100,7 @@ static int create_device(void) {
|
||||
|
||||
fprintf(f,
|
||||
"[Unit]\n"
|
||||
"Description=Integrity Protection Setup for %%I\n"
|
||||
"Description=Verity Protection Setup for %%I\n"
|
||||
"Documentation=man:systemd-veritysetup-generator(8) man:systemd-veritysetup@.service(8)\n"
|
||||
"SourcePath=/proc/cmdline\n"
|
||||
"DefaultDependencies=no\n"
|
||||
|
||||
@@ -30,7 +30,7 @@ static int help(void) {
|
||||
|
||||
printf("%s attach VOLUME DATADEVICE HASHDEVICE ROOTHASH [OPTIONS]\n"
|
||||
"%s detach VOLUME\n\n"
|
||||
"Attach or detach an integrity protected block device.\n"
|
||||
"Attach or detach a verity protected block device.\n"
|
||||
"\nSee the %s for details.\n",
|
||||
program_invocation_short_name,
|
||||
program_invocation_short_name,
|
||||
|
||||
Reference in New Issue
Block a user