diff options
Diffstat (limited to 'src/shared')
227 files changed, 13465 insertions, 4492 deletions
diff --git a/src/shared/ask-password-api.c b/src/shared/ask-password-api.c index 0e323f4..bf79dc2 100644 --- a/src/shared/ask-password-api.c +++ b/src/shared/ask-password-api.c @@ -147,18 +147,17 @@ static int add_to_keyring_and_log(const char *keyname, AskPasswordFlags flags, c return 0; } -static int ask_password_keyring(const char *keyname, AskPasswordFlags flags, char ***ret) { - +static int ask_password_keyring(const AskPasswordRequest *req, AskPasswordFlags flags, char ***ret) { key_serial_t serial; int r; - assert(keyname); + assert(req); assert(ret); if (!FLAGS_SET(flags, ASK_PASSWORD_ACCEPT_CACHED)) return -EUNATCH; - r = lookup_key(keyname, &serial); + r = lookup_key(req->keyring, &serial); if (ERRNO_IS_NEG_NOT_SUPPORTED(r) || r == -EPERM) /* When retrieving, the distinction between "kernel or container manager don't support or * allow this" and "no matching key known" doesn't matter. Note that we propagate EACCESS @@ -205,7 +204,7 @@ static int backspace_string(int ttyfd, const char *str) { } int ask_password_plymouth( - const char *message, + const AskPasswordRequest *req, usec_t until, AskPasswordFlags flags, const char *flag_file, @@ -225,8 +224,10 @@ int ask_password_plymouth( assert(ret); - if (!message) - message = "Password:"; + if (FLAGS_SET(flags, ASK_PASSWORD_HEADLESS)) + return -ENOEXEC; + + const char *message = req && req->message ? req->message : "Password:"; if (flag_file) { notify = inotify_init1(IN_CLOEXEC|IN_NONBLOCK); @@ -357,8 +358,7 @@ int ask_password_plymouth( int ask_password_tty( int ttyfd, - const char *message, - const char *keyname, + const AskPasswordRequest *req, usec_t until, AskPasswordFlags flags, const char *flag_file, @@ -381,16 +381,19 @@ int ask_password_tty( assert(ret); + if (FLAGS_SET(flags, ASK_PASSWORD_HEADLESS)) + return -ENOEXEC; + if (FLAGS_SET(flags, ASK_PASSWORD_NO_TTY)) return -EUNATCH; - if (!message) - message = "Password:"; + const char *message = req && req->message ? req->message : "Password:"; + const char *keyring = req ? req->keyring : NULL; if (!FLAGS_SET(flags, ASK_PASSWORD_HIDE_EMOJI) && emoji_enabled()) message = strjoina(special_glyph(SPECIAL_GLYPH_LOCK_AND_KEY), " ", message); - if (flag_file || (FLAGS_SET(flags, ASK_PASSWORD_ACCEPT_CACHED) && keyname)) { + if (flag_file || (FLAGS_SET(flags, ASK_PASSWORD_ACCEPT_CACHED) && keyring)) { notify = inotify_init1(IN_CLOEXEC|IN_NONBLOCK); if (notify < 0) return -errno; @@ -399,8 +402,8 @@ int ask_password_tty( if (inotify_add_watch(notify, flag_file, IN_ATTRIB /* for the link count */) < 0) return -errno; } - if (FLAGS_SET(flags, ASK_PASSWORD_ACCEPT_CACHED) && keyname) { - r = ask_password_keyring(keyname, flags, ret); + if (FLAGS_SET(flags, ASK_PASSWORD_ACCEPT_CACHED) && req && keyring) { + r = ask_password_keyring(req, flags, ret); if (r >= 0) return 0; else if (r != -ENOKEY) @@ -443,9 +446,7 @@ int ask_password_tty( (void) loop_write(ttyfd, ANSI_NORMAL, SIZE_MAX); new_termios = old_termios; - new_termios.c_lflag &= ~(ICANON|ECHO); - new_termios.c_cc[VMIN] = 1; - new_termios.c_cc[VTIME] = 0; + termios_disable_echo(&new_termios); r = RET_NERRNO(tcsetattr(ttyfd, TCSADRAIN, &new_termios)); if (r < 0) @@ -489,10 +490,10 @@ int ask_password_tty( goto finish; } - if (notify >= 0 && pollfd[POLL_INOTIFY].revents != 0 && keyname) { + if (notify >= 0 && pollfd[POLL_INOTIFY].revents != 0 && keyring) { (void) flush_fd(notify); - r = ask_password_keyring(keyname, flags, ret); + r = ask_password_keyring(req, flags, ret); if (r >= 0) { r = 0; goto finish; @@ -631,8 +632,8 @@ skipped: if (strv_isempty(l)) r = log_debug_errno(SYNTHETIC_ERRNO(ECANCELED), "Password query was cancelled."); else { - if (keyname) - (void) add_to_keyring_and_log(keyname, flags, l); + if (keyring) + (void) add_to_keyring_and_log(keyring, flags, l); *ret = TAKE_PTR(l); r = 0; @@ -681,10 +682,7 @@ static int create_socket(char **ret) { } int ask_password_agent( - const char *message, - const char *icon, - const char *id, - const char *keyname, + const AskPasswordRequest *req, usec_t until, AskPasswordFlags flags, char ***ret) { @@ -708,17 +706,20 @@ int ask_password_agent( assert(ret); + if (FLAGS_SET(flags, ASK_PASSWORD_HEADLESS)) + return -ENOEXEC; + if (FLAGS_SET(flags, ASK_PASSWORD_NO_AGENT)) return -EUNATCH; assert_se(sigemptyset(&mask) >= 0); - assert_se(sigset_add_many(&mask, SIGINT, SIGTERM, -1) >= 0); + assert_se(sigset_add_many(&mask, SIGINT, SIGTERM) >= 0); assert_se(sigprocmask(SIG_BLOCK, &mask, &oldmask) >= 0); (void) mkdir_p_label("/run/systemd/ask-password", 0755); - if (FLAGS_SET(flags, ASK_PASSWORD_ACCEPT_CACHED) && keyname) { - r = ask_password_keyring(keyname, flags, ret); + if (FLAGS_SET(flags, ASK_PASSWORD_ACCEPT_CACHED) && req && req->keyring) { + r = ask_password_keyring(req, flags, ret); if (r >= 0) { r = 0; goto finish; @@ -777,14 +778,16 @@ int ask_password_agent( until, FLAGS_SET(flags, ASK_PASSWORD_SILENT)); - if (message) - fprintf(f, "Message=%s\n", message); + if (req) { + if (req->message) + fprintf(f, "Message=%s\n", req->message); - if (icon) - fprintf(f, "Icon=%s\n", icon); + if (req->icon) + fprintf(f, "Icon=%s\n", req->icon); - if (id) - fprintf(f, "Id=%s\n", id); + if (req->id) + fprintf(f, "Id=%s\n", req->id); + } r = fflush_and_check(f); if (r < 0) @@ -839,12 +842,14 @@ int ask_password_agent( if (notify >= 0 && pollfd[FD_INOTIFY].revents != 0) { (void) flush_fd(notify); - r = ask_password_keyring(keyname, flags, ret); - if (r >= 0) { - r = 0; - goto finish; - } else if (r != -ENOKEY) - goto finish; + if (req && req->keyring) { + r = ask_password_keyring(req, flags, ret); + if (r >= 0) { + r = 0; + goto finish; + } else if (r != -ENOKEY) + goto finish; + } } if (pollfd[FD_SOCKET].revents == 0) @@ -923,8 +928,8 @@ int ask_password_agent( log_debug("Invalid packet"); } - if (keyname) - (void) add_to_keyring_and_log(keyname, flags, l); + if (req && req->keyring) + (void) add_to_keyring_and_log(req->keyring, flags, l); *ret = TAKE_PTR(l); r = 0; @@ -942,16 +947,17 @@ finish: return r; } -static int ask_password_credential(const char *credential_name, AskPasswordFlags flags, char ***ret) { +static int ask_password_credential(const AskPasswordRequest *req, AskPasswordFlags flags, char ***ret) { _cleanup_(erase_and_freep) char *buffer = NULL; size_t size; char **l; int r; - assert(credential_name); + assert(req); + assert(req->credential); assert(ret); - r = read_credential(credential_name, (void**) &buffer, &size); + r = read_credential(req->credential, (void**) &buffer, &size); if (IN_SET(r, -ENXIO, -ENOENT)) /* No credentials passed or this credential not defined? */ return -ENOKEY; @@ -964,11 +970,7 @@ static int ask_password_credential(const char *credential_name, AskPasswordFlags } int ask_password_auto( - const char *message, - const char *icon, - const char *id, /* id in "ask-password" protocol */ - const char *key_name, /* name in kernel keyring */ - const char *credential_name, /* name in $CREDENTIALS_DIRECTORY directory */ + const AskPasswordRequest *req, usec_t until, AskPasswordFlags flags, char ***ret) { @@ -977,26 +979,26 @@ int ask_password_auto( assert(ret); - if (!FLAGS_SET(flags, ASK_PASSWORD_NO_CREDENTIAL) && credential_name) { - r = ask_password_credential(credential_name, flags, ret); + if (!FLAGS_SET(flags, ASK_PASSWORD_NO_CREDENTIAL) && req && req->credential) { + r = ask_password_credential(req, flags, ret); if (r != -ENOKEY) return r; } if (FLAGS_SET(flags, ASK_PASSWORD_ACCEPT_CACHED) && - key_name && + req && req->keyring && (FLAGS_SET(flags, ASK_PASSWORD_NO_TTY) || !isatty(STDIN_FILENO)) && FLAGS_SET(flags, ASK_PASSWORD_NO_AGENT)) { - r = ask_password_keyring(key_name, flags, ret); + r = ask_password_keyring(req, flags, ret); if (r != -ENOKEY) return r; } if (!FLAGS_SET(flags, ASK_PASSWORD_NO_TTY) && isatty(STDIN_FILENO)) - return ask_password_tty(-1, message, key_name, until, flags, NULL, ret); + return ask_password_tty(-EBADF, req, until, flags, NULL, ret); if (!FLAGS_SET(flags, ASK_PASSWORD_NO_AGENT)) - return ask_password_agent(message, icon, id, key_name, until, flags, ret); + return ask_password_agent(req, until, flags, ret); return -EUNATCH; } diff --git a/src/shared/ask-password-api.h b/src/shared/ask-password-api.h index 7464e7f..e851d6d 100644 --- a/src/shared/ask-password-api.h +++ b/src/shared/ask-password-api.h @@ -15,9 +15,19 @@ typedef enum AskPasswordFlags { ASK_PASSWORD_CONSOLE_COLOR = 1 << 6, /* Use color if /dev/console points to a console that supports color */ ASK_PASSWORD_NO_CREDENTIAL = 1 << 7, /* never use $CREDENTIALS_DIRECTORY data */ ASK_PASSWORD_HIDE_EMOJI = 1 << 8, /* hide the lock and key emoji */ + ASK_PASSWORD_HEADLESS = 1 << 9, /* headless mode: never query interactively */ } AskPasswordFlags; -int ask_password_tty(int tty_fd, const char *message, const char *key_name, usec_t until, AskPasswordFlags flags, const char *flag_file, char ***ret); -int ask_password_plymouth(const char *message, usec_t until, AskPasswordFlags flags, const char *flag_file, char ***ret); -int ask_password_agent(const char *message, const char *icon, const char *id, const char *key_name, usec_t until, AskPasswordFlags flag, char ***ret); -int ask_password_auto(const char *message, const char *icon, const char *id, const char *key_name, const char *credential_name, usec_t until, AskPasswordFlags flag, char ***ret); +/* Encapsulates the mostly static fields of a password query */ +typedef struct AskPasswordRequest { + const char *message; /* The human readable password prompt when asking interactively */ + const char *keyring; /* kernel keyring key name (key of "user" type) */ + const char *icon; /* freedesktop icon spec name */ + const char *id; /* some identifier used for this prompt for the "ask-password" protocol */ + const char *credential; /* $CREDENTIALS_DIRECTORY credential name */ +} AskPasswordRequest; + +int ask_password_tty(int tty_fd, const AskPasswordRequest *req, usec_t until, AskPasswordFlags flags, const char *flag_file, char ***ret); +int ask_password_plymouth(const AskPasswordRequest *req, usec_t until, AskPasswordFlags flags, const char *flag_file, char ***ret); +int ask_password_agent(const AskPasswordRequest *req, usec_t until, AskPasswordFlags flag, char ***ret); +int ask_password_auto(const AskPasswordRequest *req, usec_t until, AskPasswordFlags flag, char ***ret); diff --git a/src/shared/async.c b/src/shared/async.c index 41f6b97..bbb8b81 100644 --- a/src/shared/async.c +++ b/src/shared/async.c @@ -94,7 +94,7 @@ int asynchronous_close(int fd) { pid = clone_with_nested_stack(close_func, CLONE_FILES | ((v & NEED_DOUBLE_FORK) ? 0 : SIGCHLD), UINT_TO_PTR(v)); if (pid < 0) - assert_se(close_nointr(fd) != -EBADF); /* local fallback */ + safe_close(fd); /* local fallback */ else if (v & NEED_DOUBLE_FORK) { /* Reap the intermediate child. Key here is that we specify __WCLONE, since we didn't ask for diff --git a/src/shared/bitmap.c b/src/shared/bitmap.c index 6cf08b8..9aa7661 100644 --- a/src/shared/bitmap.c +++ b/src/shared/bitmap.c @@ -163,9 +163,9 @@ bool bitmap_iterate(const Bitmap *b, Iterator *i, unsigned *n) { rem = BITMAP_NUM_TO_REM(i->idx); bitmask = UINT64_C(1) << rem; - for (; offset < b->n_bitmaps; offset ++) { + for (; offset < b->n_bitmaps; offset++) { if (b->bitmaps[offset]) { - for (; bitmask; bitmask <<= 1, rem ++) { + for (; bitmask; bitmask <<= 1, rem++) { if (b->bitmaps[offset] & bitmask) { *n = BITMAP_OFFSET_TO_NUM(offset, rem); i->idx = *n + 1; diff --git a/src/shared/blockdev-util.c b/src/shared/blockdev-util.c index 7a2dd1c..2055550 100644 --- a/src/shared/blockdev-util.c +++ b/src/shared/blockdev-util.c @@ -57,23 +57,12 @@ static int fd_get_devnum(int fd, BlockDeviceLookupFlag flags, dev_t *ret) { } int block_device_is_whole_disk(sd_device *dev) { - const char *s; - int r; - assert(dev); - r = sd_device_get_subsystem(dev, &s); - if (r < 0) - return r; - - if (!streq(s, "block")) + if (!device_in_subsystem(dev, "block")) return -ENOTBLK; - r = sd_device_get_devtype(dev, &s); - if (r < 0) - return r; - - return streq(s, "disk"); + return device_is_devtype(dev, "disk"); } int block_device_get_whole_disk(sd_device *dev, sd_device **ret) { @@ -380,15 +369,44 @@ int blockdev_partscan_enabled(int fd) { * is 1, which can be check with 'ext_range' sysfs attribute. Explicit flag ('GENHD_FL_NO_PART_SCAN') * can be obtained from 'capability' sysattr. * - * With https://github.com/torvalds/linux/commit/1ebe2e5f9d68e94c524aba876f27b945669a7879 (v5.17), we - * can check the flag from 'ext_range' sysfs attribute directly. + * With https://github.com/torvalds/linux/commit/46e7eac647b34ed4106a8262f8bedbb90801fadd (v5.17), + * the flag is renamed to GENHD_FL_NO_PART. + * + * With https://github.com/torvalds/linux/commit/1ebe2e5f9d68e94c524aba876f27b945669a7879 (v5.17), + * we can check the flag from 'ext_range' sysfs attribute directly. + * + * With https://github.com/torvalds/linux/commit/430cc5d3ab4d0ba0bd011cfbb0035e46ba92920c (v5.17), + * the value of GENHD_FL_NO_PART is changed from 0x0200 to 0x0004. 💣💣💣 + * Note, the new value was used by the GENHD_FL_MEDIA_CHANGE_NOTIFY flag, which was introduced by + * 86ce18d7b7925bfd6b64c061828ca2a857ee83b8 (v2.6.22), and removed by + * 9243c6f3e012a92dd900d97ef45efaf8a8edc448 (v5.7). If we believe the commit message of + * e81cd5a983bb35dabd38ee472cf3fea1c63e0f23, the flag was never used. So, fortunately, we can use + * both the new and old values safely. + * + * With https://github.com/torvalds/linux/commit/b9684a71fca793213378dd410cd11675d973eaa1 (v5.19), + * another flag GD_SUPPRESS_PART_SCAN is introduced for loopback block device, and partition scanning + * is done only when both GENHD_FL_NO_PART and GD_SUPPRESS_PART_SCAN are not set. Before the commit, + * LO_FLAGS_PARTSCAN flag was directly tied with GENHD_FL_NO_PART. But with this change now it is + * tied with GD_SUPPRESS_PART_SCAN. So, LO_FLAGS_PARTSCAN cannot be obtained from 'ext_range' + * sysattr, which corresponds to GENHD_FL_NO_PART, and we need to read 'loop/partscan'. 💣💣💣 + * + * With https://github.com/torvalds/linux/commit/73a166d9749230d598320fdae3b687cdc0e2e205 (v6.3), + * the GD_SUPPRESS_PART_SCAN flag is also introduced for userspace block device (ublk). Though, not + * sure if we should support the device... * * With https://github.com/torvalds/linux/commit/e81cd5a983bb35dabd38ee472cf3fea1c63e0f23 (v6.3), - * the 'capability' sysfs attribute is deprecated, hence we cannot check the flag from it. + * the 'capability' sysfs attribute is deprecated, hence we cannot check flags from it. 💣💣💣 + * + * With https://github.com/torvalds/linux/commit/a4217c6740dc64a3eb6815868a9260825e8c68c6 (v6.10, + * backported to v6.6+), the partscan status is directly exposed as 'partscan' sysattr. * - * To support both old and new kernels, we need to do the following: first check 'ext_range' sysfs - * attribute, and if '1' we can conclude partition scanning is disabled, otherwise check 'capability' - * sysattr for older version. */ + * To support both old and new kernels, we need to do the following: + * 1) check 'partscan' sysfs attribute where the information is made directly available, + * 2) check if the blockdev refers to a partition, where partscan is not supported, + * 3) check 'loop/partscan' sysfs attribute for loopback block devices, and if '0' we can conclude + * partition scanning is disabled, + * 4) check 'ext_range' sysfs attribute, and if '1' we can conclude partition scanning is disabled, + * 5) otherwise check 'capability' sysfs attribute for ancient version. */ assert(fd >= 0); @@ -396,6 +414,21 @@ int blockdev_partscan_enabled(int fd) { if (r < 0) return r; + /* For v6.10 or newer. */ + r = device_get_sysattr_bool(dev, "partscan"); + if (r != -ENOENT) + return r; + + /* Partition block devices never have partition scanning on, there's no concept of sub-partitions for + * partitions. */ + if (device_is_devtype(dev, "partition")) + return false; + + /* For loopback block device, especially for v5.19 or newer. Even if this is enabled, we also need to + * check GENHD_FL_NO_PART flag through 'ext_range' and 'capability' sysfs attributes below. */ + if (device_get_sysattr_bool(dev, "loop/partscan") == 0) + return false; + r = device_get_sysattr_int(dev, "ext_range", &ext_range); if (r == -ENOENT) /* If the ext_range file doesn't exist then we are most likely looking at a * partition block device, not the whole block device. And that means we have no @@ -405,7 +438,7 @@ int blockdev_partscan_enabled(int fd) { if (r < 0) return r; - if (ext_range <= 1) /* The valus should be always positive, but the kernel uses '%d' for the + if (ext_range <= 1) /* The value should be always positive, but the kernel uses '%d' for the * attribute. Let's gracefully handle zero or negative. */ return false; @@ -415,12 +448,10 @@ int blockdev_partscan_enabled(int fd) { if (r < 0) return r; -#ifndef GENHD_FL_NO_PART_SCAN -#define GENHD_FL_NO_PART_SCAN (0x0200) -#endif - - /* If 0x200 is set, part scanning is definitely off. */ - if (FLAGS_SET(capability, GENHD_FL_NO_PART_SCAN)) +#define GENHD_FL_NO_PART_OLD 0x0200 +#define GENHD_FL_NO_PART_NEW 0x0004 + /* If one of the NO_PART flags is set, part scanning is definitely off. */ + if ((capability & (GENHD_FL_NO_PART_OLD | GENHD_FL_NO_PART_NEW)) != 0) return false; /* Otherwise, assume part scanning is on, we have no further checks available. Assume the best. */ @@ -801,6 +832,21 @@ int blockdev_get_sector_size(int fd, uint32_t *ret) { return 0; } +int blockdev_get_device_size(int fd, uint64_t *ret) { + uint64_t sz = 0; + + assert(fd >= 0); + assert(ret); + + /* This is just a type-safe wrapper around BLKGETSIZE64 that gets us around having to include messy linux/fs.h in various clients */ + + if (ioctl(fd, BLKGETSIZE64, &sz) < 0) + return -errno; + + *ret = sz; + return 0; +} + int blockdev_get_root(int level, dev_t *ret) { _cleanup_free_ char *p = NULL; dev_t devno; diff --git a/src/shared/blockdev-util.h b/src/shared/blockdev-util.h index 954a23d..28aede2 100644 --- a/src/shared/blockdev-util.h +++ b/src/shared/blockdev-util.h @@ -57,5 +57,6 @@ int block_device_has_partitions(sd_device *dev); int blockdev_reread_partition_table(sd_device *dev); int blockdev_get_sector_size(int fd, uint32_t *ret); +int blockdev_get_device_size(int fd, uint64_t *ret); int blockdev_get_root(int level, dev_t *ret); diff --git a/src/shared/boot-entry.c b/src/shared/boot-entry.c index e726073..72d3cbe 100644 --- a/src/shared/boot-entry.c +++ b/src/shared/boot-entry.c @@ -15,20 +15,18 @@ bool boot_entry_token_valid(const char *p) { return utf8_is_valid(p) && string_is_safe(p) && filename_is_valid(p); } -static int entry_token_load(int rfd, const char *etc_kernel, BootEntryTokenType *type, char **token) { +static int entry_token_load_one(int rfd, const char *dir, BootEntryTokenType *type, char **token) { _cleanup_free_ char *buf = NULL, *p = NULL; _cleanup_fclose_ FILE *f = NULL; int r; assert(rfd >= 0 || rfd == AT_FDCWD); + assert(dir); assert(type); assert(*type == BOOT_ENTRY_TOKEN_AUTO); assert(token); - if (!etc_kernel) - return 0; - - p = path_join(etc_kernel, "entry-token"); + p = path_join(dir, "entry-token"); if (!p) return log_oom(); @@ -55,6 +53,26 @@ static int entry_token_load(int rfd, const char *etc_kernel, BootEntryTokenType return 1; } +static int entry_token_load(int rfd, const char *conf_root, BootEntryTokenType *type, char **token) { + int r; + + assert(rfd >= 0 || rfd == AT_FDCWD); + assert(type); + assert(*type == BOOT_ENTRY_TOKEN_AUTO); + assert(token); + + if (conf_root) + return entry_token_load_one(rfd, conf_root, type, token); + + FOREACH_STRING(path, "/etc/kernel", "/usr/lib/kernel") { + r = entry_token_load_one(rfd, path, type, token); + if (r != 0) + return r; + } + + return 0; +} + static int entry_token_from_machine_id(sd_id128_t machine_id, BootEntryTokenType *type, char **token) { char *p; @@ -123,7 +141,7 @@ static int entry_token_from_os_release(int rfd, BootEntryTokenType *type, char * int boot_entry_token_ensure_at( int rfd, - const char *etc_kernel, + const char *conf_root, sd_id128_t machine_id, bool machine_id_is_random, BootEntryTokenType *type, @@ -141,7 +159,7 @@ int boot_entry_token_ensure_at( switch (*type) { case BOOT_ENTRY_TOKEN_AUTO: - r = entry_token_load(rfd, etc_kernel, type, token); + r = entry_token_load(rfd, conf_root, type, token); if (r != 0) return r; @@ -198,7 +216,7 @@ int boot_entry_token_ensure_at( int boot_entry_token_ensure( const char *root, - const char *etc_kernel, + const char *conf_root, sd_id128_t machine_id, bool machine_id_is_random, BootEntryTokenType *type, @@ -215,7 +233,7 @@ int boot_entry_token_ensure( if (rfd < 0) return -errno; - return boot_entry_token_ensure_at(rfd, etc_kernel, machine_id, machine_id_is_random, type, token); + return boot_entry_token_ensure_at(rfd, conf_root, machine_id, machine_id_is_random, type, token); } int parse_boot_entry_token_type(const char *s, BootEntryTokenType *type, char **token) { diff --git a/src/shared/boot-entry.h b/src/shared/boot-entry.h index f3a6f28..836b637 100644 --- a/src/shared/boot-entry.h +++ b/src/shared/boot-entry.h @@ -17,14 +17,14 @@ bool boot_entry_token_valid(const char *p); int boot_entry_token_ensure( const char *root, - const char *etc_kernel, /* will be prefixed with root, typically /etc/kernel. */ + const char *conf_root, /* will be prefixed with root, typically /etc/kernel. */ sd_id128_t machine_id, bool machine_id_is_random, BootEntryTokenType *type, /* input and output */ char **token); /* output, but do not pass uninitialized value. */ int boot_entry_token_ensure_at( int rfd, - const char *etc_kernel, + const char *conf_root, sd_id128_t machine_id, bool machine_id_is_random, BootEntryTokenType *type, diff --git a/src/shared/bootspec.c b/src/shared/bootspec.c index f4b2fdc..4bc3ae7 100644 --- a/src/shared/bootspec.c +++ b/src/shared/bootspec.c @@ -57,6 +57,7 @@ static void boot_entry_free(BootEntry *entry) { free(entry->machine_id); free(entry->architecture); strv_free(entry->options); + free(entry->local_addons.items); free(entry->kernel); free(entry->efi); strv_free(entry->initrd); @@ -78,10 +79,7 @@ static int mangle_path( assert(ret); /* Spec leaves open if prefixed with "/" or not, let's normalize that */ - if (path_is_absolute(p)) - c = strdup(p); - else - c = strjoin("/", p); + c = path_make_absolute(p, "/"); if (!c) return -ENOMEM; @@ -288,7 +286,6 @@ static int boot_entry_load_type1( BootEntry *entry) { _cleanup_(boot_entry_free) BootEntry tmp = BOOT_ENTRY_INIT(BOOT_ENTRY_CONF); - unsigned line = 1; char *c; int r; @@ -323,18 +320,16 @@ static int boot_entry_load_type1( if (!tmp.root) return log_oom(); - for (;;) { + for (unsigned line = 1;; line++) { _cleanup_free_ char *buf = NULL, *field = NULL; r = read_stripped_line(f, LONG_LINE_MAX, &buf); - if (r == 0) - break; if (r == -ENOBUFS) return log_syntax(NULL, LOG_ERR, tmp.path, line, r, "Line too long."); if (r < 0) return log_syntax(NULL, LOG_ERR, tmp.path, line, r, "Error while reading: %m"); - - line++; + if (r == 0) + break; if (IN_SET(buf[0], '#', '\0')) continue; @@ -421,44 +416,36 @@ void boot_config_free(BootConfig *config) { assert(config); free(config->default_pattern); - free(config->timeout); - free(config->editor); - free(config->auto_entries); - free(config->auto_firmware); - free(config->console_mode); - free(config->beep); free(config->entry_oneshot); free(config->entry_default); free(config->entry_selected); - for (size_t i = 0; i < config->n_entries; i++) - boot_entry_free(config->entries + i); + FOREACH_ARRAY(i, config->entries, config->n_entries) + boot_entry_free(i); free(config->entries); + free(config->global_addons.items); set_free(config->inodes_seen); } int boot_loader_read_conf(BootConfig *config, FILE *file, const char *path) { - unsigned line = 1; int r; assert(config); assert(file); assert(path); - for (;;) { + for (unsigned line = 1;; line++) { _cleanup_free_ char *buf = NULL, *field = NULL; r = read_stripped_line(file, LONG_LINE_MAX, &buf); - if (r == 0) - break; if (r == -ENOBUFS) return log_syntax(NULL, LOG_ERR, path, line, r, "Line too long."); if (r < 0) return log_syntax(NULL, LOG_ERR, path, line, r, "Error while reading: %m"); - - line++; + if (r == 0) + break; if (IN_SET(buf[0], '#', '\0')) continue; @@ -480,20 +467,10 @@ int boot_loader_read_conf(BootConfig *config, FILE *file, const char *path) { if (streq(field, "default")) r = free_and_strdup(&config->default_pattern, p); - else if (streq(field, "timeout")) - r = free_and_strdup(&config->timeout, p); - else if (streq(field, "editor")) - r = free_and_strdup(&config->editor, p); - else if (streq(field, "auto-entries")) - r = free_and_strdup(&config->auto_entries, p); - else if (streq(field, "auto-firmware")) - r = free_and_strdup(&config->auto_firmware, p); - else if (streq(field, "console-mode")) - r = free_and_strdup(&config->console_mode, p); - else if (streq(field, "random-seed-mode")) - log_syntax(NULL, LOG_WARNING, path, line, 0, "'random-seed-mode' has been deprecated, ignoring."); - else if (streq(field, "beep")) - r = free_and_strdup(&config->beep, p); + else if (STR_IN_SET(field, "timeout", "editor", "auto-entries", "auto-firmware", + "auto-poweroff", "auto-reboot", "beep", "reboot-for-bitlocker", + "secure-boot-enroll", "console-mode")) + r = 0; /* we don't parse these in userspace, but they are OK */ else { log_syntax(NULL, LOG_WARNING, path, line, 0, "Unknown line '%s', ignoring.", field); continue; @@ -609,8 +586,8 @@ static int boot_entries_find_type1( if (r < 0) return log_error_errno(r, "Failed to read directory '%s': %m", full); - for (size_t i = 0; i < dentries->n_entries; i++) { - const struct dirent *de = dentries->entries[i]; + FOREACH_ARRAY(i, dentries->entries, dentries->n_entries) { + const struct dirent *de = *i; _cleanup_fclose_ FILE *f = NULL; if (!dirent_is_file(de)) @@ -633,7 +610,7 @@ static int boot_entries_find_type1( r = boot_config_load_type1(config, f, root, full, de->d_name); if (r == -ENOMEM) /* ignore all other errors */ - return r; + return log_oom(); } return 0; @@ -753,13 +730,12 @@ static int boot_entry_load_unified( static int find_sections( int fd, const char *path, - char **ret_osrelease, - char **ret_cmdline) { + IMAGE_SECTION_HEADER **ret_sections, + PeHeader **ret_pe_header) { - _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL; _cleanup_free_ IMAGE_DOS_HEADER *dos_header = NULL; - _cleanup_free_ char *osrel = NULL, *cmdline = NULL; - _cleanup_free_ PeHeader *pe_header = NULL; + IMAGE_SECTION_HEADER *sections; + PeHeader *pe_header; int r; assert(fd >= 0); @@ -773,25 +749,253 @@ static int find_sections( if (r < 0) return log_warning_errno(r, "Failed to parse PE sections of '%s': %m", path); - if (!pe_is_uki(pe_header, sections)) - return log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "Parsed PE file '%s' is not a UKI.", path); + if (ret_pe_header) + *ret_pe_header = TAKE_PTR(pe_header); + if (ret_sections) + *ret_sections = TAKE_PTR(sections); - r = pe_read_section_data(fd, pe_header, sections, ".osrel", PE_SECTION_SIZE_MAX, (void**) &osrel, NULL); - if (r < 0) - return log_warning_errno(r, "Failed to read .osrel section of '%s': %m", path); + return 0; +} + +static int find_cmdline_section( + int fd, + const char *path, + IMAGE_SECTION_HEADER *sections, + PeHeader *pe_header, + char **ret_cmdline) { + + int r; + char *cmdline = NULL, *t = NULL; + _cleanup_free_ char *word = NULL; + + assert(path); + + if (!ret_cmdline) + return 0; r = pe_read_section_data(fd, pe_header, sections, ".cmdline", PE_SECTION_SIZE_MAX, (void**) &cmdline, NULL); - if (r < 0 && r != -ENXIO) /* cmdline is optional */ + if (r == -ENXIO) { /* cmdline is optional */ + *ret_cmdline = NULL; + return 0; + } + if (r < 0) return log_warning_errno(r, "Failed to read .cmdline section of '%s': %m", path); - if (ret_osrelease) - *ret_osrelease = TAKE_PTR(osrel); - if (ret_cmdline) + word = strdup(cmdline); + if (!word) + return log_oom(); + + /* Quick test to check if there is actual content in the addon cmdline */ + t = delete_chars(word, NULL); + if (isempty(t)) + *ret_cmdline = NULL; + else *ret_cmdline = TAKE_PTR(cmdline); return 0; } +static int find_osrel_section( + int fd, + const char *path, + IMAGE_SECTION_HEADER *sections, + PeHeader *pe_header, + char **ret_osrelease) { + + int r; + + if (!ret_osrelease) + return 0; + + r = pe_read_section_data(fd, pe_header, sections, ".osrel", PE_SECTION_SIZE_MAX, (void**) ret_osrelease, NULL); + if (r < 0) + return log_warning_errno(r, "Failed to read .osrel section of '%s': %m", path); + + return 0; +} + +static int find_uki_sections( + int fd, + const char *path, + char **ret_osrelease, + char **ret_cmdline) { + + _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL; + _cleanup_free_ PeHeader *pe_header = NULL; + int r; + + r = find_sections(fd, path, §ions, &pe_header); + if (r < 0) + return r; + + if (!pe_is_uki(pe_header, sections)) + return log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "Parsed PE file '%s' is not a UKI.", path); + + r = find_osrel_section(fd, path, sections, pe_header, ret_osrelease); + if (r < 0) + return r; + + r = find_cmdline_section(fd, path, sections, pe_header, ret_cmdline); + if (r < 0) + return r; + + return 0; +} + +static int find_addon_sections( + int fd, + const char *path, + char **ret_cmdline) { + + _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL; + _cleanup_free_ PeHeader *pe_header = NULL; + int r; + + r = find_sections(fd, path, §ions, &pe_header); + if (r < 0) + return r; + + r = find_cmdline_section(fd, path, sections, pe_header, ret_cmdline); + /* If addon cmdline is empty or contains just separators, + * don't bother tracking it. + * Don't check r because it cannot return <0 if cmdline is empty, + * as cmdline is always optional. */ + if (!ret_cmdline) + return log_warning_errno(SYNTHETIC_ERRNO(ENOENT), "Addon %s contains empty cmdline and will be therefore ignored.", path); + + return r; +} + +static int insert_boot_entry_addon( + BootEntryAddons *addons, + char *location, + char *cmdline) { + + assert(addons); + + if (!GREEDY_REALLOC(addons->items, addons->n_items + 1)) + return log_oom(); + + addons->items[addons->n_items++] = (BootEntryAddon) { + .location = location, + .cmdline = cmdline, + }; + + return 0; +} + +static void boot_entry_addons_done(BootEntryAddons *addons) { + assert(addons); + + FOREACH_ARRAY(addon, addons->items, addons->n_items) { + free(addon->cmdline); + free(addon->location); + } + addons->items = mfree(addons->items); + addons->n_items = 0; +} + +static int boot_entries_find_unified_addons( + BootConfig *config, + int d_fd, + const char *addon_dir, + const char *root, + BootEntryAddons *ret_addons) { + + _cleanup_closedir_ DIR *d = NULL; + _cleanup_free_ char *full = NULL; + _cleanup_(boot_entry_addons_done) BootEntryAddons addons = {}; + int r; + + assert(ret_addons); + assert(config); + + r = chase_and_opendirat(d_fd, addon_dir, CHASE_AT_RESOLVE_IN_ROOT, &full, &d); + if (r == -ENOENT) + return 0; + if (r < 0) + return log_error_errno(r, "Failed to open '%s/%s': %m", root, addon_dir); + + FOREACH_DIRENT(de, d, return log_error_errno(errno, "Failed to read %s: %m", full)) { + _cleanup_free_ char *j = NULL, *cmdline = NULL, *location = NULL; + _cleanup_close_ int fd = -EBADF; + + if (!dirent_is_file(de)) + continue; + + if (!endswith_no_case(de->d_name, ".addon.efi")) + continue; + + fd = openat(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOFOLLOW|O_NOCTTY); + if (fd < 0) { + log_warning_errno(errno, "Failed to open %s/%s, ignoring: %m", full, de->d_name); + continue; + } + + r = config_check_inode_relevant_and_unseen(config, fd, de->d_name); + if (r < 0) + return r; + if (r == 0) /* inode already seen or otherwise not relevant */ + continue; + + j = path_join(full, de->d_name); + if (!j) + return log_oom(); + + if (find_addon_sections(fd, j, &cmdline) < 0) + continue; + + location = strdup(j); + if (!location) + return log_oom(); + + r = insert_boot_entry_addon(&addons, location, cmdline); + if (r < 0) + return r; + + TAKE_PTR(location); + TAKE_PTR(cmdline); + } + + *ret_addons = TAKE_STRUCT(addons); + return 0; +} + +static int boot_entries_find_unified_global_addons( + BootConfig *config, + const char *root, + const char *d_name) { + + int r; + _cleanup_closedir_ DIR *d = NULL; + + r = chase_and_opendir(root, NULL, CHASE_PROHIBIT_SYMLINKS, NULL, &d); + if (r == -ENOENT) + return 0; + if (r < 0) + return log_error_errno(r, "Failed to open '%s/%s': %m", root, d_name); + + return boot_entries_find_unified_addons(config, dirfd(d), d_name, root, &config->global_addons); +} + +static int boot_entries_find_unified_local_addons( + BootConfig *config, + int d_fd, + const char *d_name, + const char *root, + BootEntry *ret) { + + _cleanup_free_ char *addon_dir = NULL; + + assert(ret); + + addon_dir = strjoin(d_name, ".extra.d"); + if (!addon_dir) + return log_oom(); + + return boot_entries_find_unified_addons(config, d_fd, addon_dir, root, &ret->local_addons); +} + static int boot_entries_find_unified( BootConfig *config, const char *root, @@ -839,13 +1043,18 @@ static int boot_entries_find_unified( if (!j) return log_oom(); - if (find_sections(fd, j, &osrelease, &cmdline) < 0) + if (find_uki_sections(fd, j, &osrelease, &cmdline) < 0) continue; r = boot_entry_load_unified(root, j, osrelease, cmdline, config->entries + config->n_entries); if (r < 0) continue; + /* look for .efi.extra.d */ + r = boot_entries_find_unified_local_addons(config, dirfd(d), de->d_name, full, config->entries + config->n_entries); + if (r < 0) + continue; + config->n_entries++; } @@ -1062,6 +1271,7 @@ int boot_config_load( int r; assert(config); + config->global_addons = (BootEntryAddons) {}; if (esp_path) { r = boot_loader_read_conf_path(config, esp_path, "/loader/loader.conf"); @@ -1075,6 +1285,10 @@ int boot_config_load( r = boot_entries_find_unified(config, esp_path, "/EFI/Linux/"); if (r < 0) return r; + + r = boot_entries_find_unified_global_addons(config, esp_path, "/loader/addons/"); + if (r < 0) + return r; } if (xbootldr_path) { @@ -1238,13 +1452,155 @@ static void boot_entry_file_list( *ret_status = status; } +static void print_addon( + BootEntryAddon *addon, + const char *addon_str) { + + printf(" %s: %s\n", addon_str, addon->location); + printf(" options: %s%s\n", special_glyph(SPECIAL_GLYPH_TREE_RIGHT), addon->cmdline); +} + +static int indent_embedded_newlines(char *cmdline, char **ret_cmdline) { + _cleanup_free_ char *t = NULL; + _cleanup_strv_free_ char **ts = NULL; + + assert(ret_cmdline); + + ts = strv_split_newlines(cmdline); + if (!ts) + return -ENOMEM; + + t = strv_join(ts, "\n "); + if (!t) + return -ENOMEM; + + *ret_cmdline = TAKE_PTR(t); + + return 0; +} + +static int print_cmdline( + const BootEntry *e, + const BootEntryAddons *global_arr) { + + _cleanup_free_ char *options = NULL, *combined_cmdline = NULL, *t2 = NULL; + + assert(e); + + if (!strv_isempty(e->options)) { + _cleanup_free_ char *t = NULL; + + options = strv_join(e->options, " "); + if (!options) + return log_oom(); + + if (indent_embedded_newlines(options, &t) < 0) + return log_oom(); + + printf(" options: %s\n", t); + t2 = strdup(options); + if (!t2) + return log_oom(); + } + + FOREACH_ARRAY(addon, global_arr->items, global_arr->n_items) { + print_addon(addon, "global-addon"); + if (!strextend(&t2, " ", addon->cmdline)) + return log_oom(); + } + + FOREACH_ARRAY(addon, e->local_addons.items, e->local_addons.n_items) { + /* Add space at the beginning of addon_str to align it correctly */ + print_addon(addon, " local-addon"); + if (!strextend(&t2, " ", addon->cmdline)) + return log_oom(); + } + + /* Don't print the combined cmdline if it's same as options. */ + if (streq_ptr(t2, options)) + return 0; + + if (indent_embedded_newlines(t2, &combined_cmdline) < 0) + return log_oom(); + + if (combined_cmdline) + printf(" cmdline: %s\n", combined_cmdline); + + return 0; +} + +static int json_addon( + BootEntryAddon *addon, + const char *addon_str, + JsonVariant **array) { + + int r; + + assert(addon); + assert(addon_str); + + r = json_variant_append_arrayb( + array, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR(addon_str, JSON_BUILD_STRING(addon->location)), + JSON_BUILD_PAIR("options", JSON_BUILD_STRING(addon->cmdline)))); + if (r < 0) + return log_oom(); + + return 0; +} + +static int json_cmdline( + const BootEntry *e, + const BootEntryAddons *global_arr, + const char *def_cmdline, + JsonVariant **v) { + + _cleanup_free_ char *combined_cmdline = NULL; + _cleanup_(json_variant_unrefp) JsonVariant *addons_array = NULL; + int r; + + assert(e); + + if (def_cmdline) { + combined_cmdline = strdup(def_cmdline); + if (!combined_cmdline) + return log_oom(); + } + + FOREACH_ARRAY(addon, global_arr->items, global_arr->n_items) { + r = json_addon(addon, "globalAddon", &addons_array); + if (r < 0) + return r; + if (!strextend(&combined_cmdline, " ", addon->cmdline)) + return log_oom(); + } + + FOREACH_ARRAY(addon, e->local_addons.items, e->local_addons.n_items) { + r = json_addon(addon, "localAddon", &addons_array); + if (r < 0) + return r; + if (!strextend(&combined_cmdline, " ", addon->cmdline)) + return log_oom(); + } + + r = json_variant_merge_objectb( + v, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("addons", JSON_BUILD_VARIANT(addons_array)), + JSON_BUILD_PAIR_CONDITION(combined_cmdline, "cmdline", JSON_BUILD_STRING(combined_cmdline)))); + if (r < 0) + return log_oom(); + return 0; +} + int show_boot_entry( const BootEntry *e, + const BootEntryAddons *global_addons, bool show_as_default, bool show_as_selected, bool show_reported) { - int status = 0; + int status = 0, r = 0; /* Returns 0 on success, negative on processing error, and positive if something is wrong with the boot entry itself. */ @@ -1323,24 +1679,9 @@ int show_boot_entry( *s, &status); - if (!strv_isempty(e->options)) { - _cleanup_free_ char *t = NULL, *t2 = NULL; - _cleanup_strv_free_ char **ts = NULL; - - t = strv_join(e->options, " "); - if (!t) - return log_oom(); - - ts = strv_split_newlines(t); - if (!ts) - return log_oom(); - - t2 = strv_join(ts, "\n "); - if (!t2) - return log_oom(); - - printf(" options: %s\n", t2); - } + r = print_cmdline(e, global_addons); + if (r < 0) + return r; if (e->device_tree) boot_entry_file_list("devicetree", e->root, e->device_tree, &status); @@ -1354,6 +1695,71 @@ int show_boot_entry( return -status; } +int boot_entry_to_json(const BootConfig *c, size_t i, JsonVariant **ret) { + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + _cleanup_free_ char *opts = NULL; + const BootEntry *e; + int r; + + assert(c); + assert(ret); + + if (i >= c->n_entries) { + *ret = NULL; + return 0; + } + + e = c->entries + i; + + if (!strv_isempty(e->options)) { + opts = strv_join(e->options, " "); + if (!opts) + return log_oom(); + } + + r = json_variant_merge_objectb( + &v, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("type", JSON_BUILD_STRING(boot_entry_type_json_to_string(e->type))), + JSON_BUILD_PAIR_CONDITION(e->id, "id", JSON_BUILD_STRING(e->id)), + JSON_BUILD_PAIR_CONDITION(e->path, "path", JSON_BUILD_STRING(e->path)), + JSON_BUILD_PAIR_CONDITION(e->root, "root", JSON_BUILD_STRING(e->root)), + JSON_BUILD_PAIR_CONDITION(e->title, "title", JSON_BUILD_STRING(e->title)), + JSON_BUILD_PAIR_CONDITION(boot_entry_title(e), "showTitle", JSON_BUILD_STRING(boot_entry_title(e))), + JSON_BUILD_PAIR_CONDITION(e->sort_key, "sortKey", JSON_BUILD_STRING(e->sort_key)), + JSON_BUILD_PAIR_CONDITION(e->version, "version", JSON_BUILD_STRING(e->version)), + JSON_BUILD_PAIR_CONDITION(e->machine_id, "machineId", JSON_BUILD_STRING(e->machine_id)), + JSON_BUILD_PAIR_CONDITION(e->architecture, "architecture", JSON_BUILD_STRING(e->architecture)), + JSON_BUILD_PAIR_CONDITION(opts, "options", JSON_BUILD_STRING(opts)), + JSON_BUILD_PAIR_CONDITION(e->kernel, "linux", JSON_BUILD_STRING(e->kernel)), + JSON_BUILD_PAIR_CONDITION(e->efi, "efi", JSON_BUILD_STRING(e->efi)), + JSON_BUILD_PAIR_CONDITION(!strv_isempty(e->initrd), "initrd", JSON_BUILD_STRV(e->initrd)), + JSON_BUILD_PAIR_CONDITION(e->device_tree, "devicetree", JSON_BUILD_STRING(e->device_tree)), + JSON_BUILD_PAIR_CONDITION(!strv_isempty(e->device_tree_overlay), "devicetreeOverlay", JSON_BUILD_STRV(e->device_tree_overlay)))); + if (r < 0) + return log_oom(); + + /* Sanitizers (only memory sanitizer?) do not like function call with too many + * arguments and trigger false positive warnings. Let's not add too many json objects + * at once. */ + r = json_variant_merge_objectb( + &v, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("isReported", JSON_BUILD_BOOLEAN(e->reported_by_loader)), + JSON_BUILD_PAIR_CONDITION(e->tries_left != UINT_MAX, "triesLeft", JSON_BUILD_UNSIGNED(e->tries_left)), + JSON_BUILD_PAIR_CONDITION(e->tries_done != UINT_MAX, "triesDone", JSON_BUILD_UNSIGNED(e->tries_done)), + JSON_BUILD_PAIR_CONDITION(c->default_entry >= 0, "isDefault", JSON_BUILD_BOOLEAN(i == (size_t) c->default_entry)), + JSON_BUILD_PAIR_CONDITION(c->selected_entry >= 0, "isSelected", JSON_BUILD_BOOLEAN(i == (size_t) c->selected_entry)))); + + if (r < 0) + return log_oom(); + + r = json_cmdline(e, &c->global_addons, opts, &v); + if (r < 0) + return log_oom(); + + *ret = TAKE_PTR(v); + return 1; +} + int show_boot_entries(const BootConfig *config, JsonFormatFlags json_format) { int r; @@ -1363,48 +1769,9 @@ int show_boot_entries(const BootConfig *config, JsonFormatFlags json_format) { _cleanup_(json_variant_unrefp) JsonVariant *array = NULL; for (size_t i = 0; i < config->n_entries; i++) { - _cleanup_free_ char *opts = NULL; - const BootEntry *e = config->entries + i; _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; - if (!strv_isempty(e->options)) { - opts = strv_join(e->options, " "); - if (!opts) - return log_oom(); - } - - r = json_variant_merge_objectb( - &v, JSON_BUILD_OBJECT( - JSON_BUILD_PAIR("type", JSON_BUILD_STRING(boot_entry_type_json_to_string(e->type))), - JSON_BUILD_PAIR_CONDITION(e->id, "id", JSON_BUILD_STRING(e->id)), - JSON_BUILD_PAIR_CONDITION(e->path, "path", JSON_BUILD_STRING(e->path)), - JSON_BUILD_PAIR_CONDITION(e->root, "root", JSON_BUILD_STRING(e->root)), - JSON_BUILD_PAIR_CONDITION(e->title, "title", JSON_BUILD_STRING(e->title)), - JSON_BUILD_PAIR_CONDITION(boot_entry_title(e), "showTitle", JSON_BUILD_STRING(boot_entry_title(e))), - JSON_BUILD_PAIR_CONDITION(e->sort_key, "sortKey", JSON_BUILD_STRING(e->sort_key)), - JSON_BUILD_PAIR_CONDITION(e->version, "version", JSON_BUILD_STRING(e->version)), - JSON_BUILD_PAIR_CONDITION(e->machine_id, "machineId", JSON_BUILD_STRING(e->machine_id)), - JSON_BUILD_PAIR_CONDITION(e->architecture, "architecture", JSON_BUILD_STRING(e->architecture)), - JSON_BUILD_PAIR_CONDITION(opts, "options", JSON_BUILD_STRING(opts)), - JSON_BUILD_PAIR_CONDITION(e->kernel, "linux", JSON_BUILD_STRING(e->kernel)), - JSON_BUILD_PAIR_CONDITION(e->efi, "efi", JSON_BUILD_STRING(e->efi)), - JSON_BUILD_PAIR_CONDITION(!strv_isempty(e->initrd), "initrd", JSON_BUILD_STRV(e->initrd)), - JSON_BUILD_PAIR_CONDITION(e->device_tree, "devicetree", JSON_BUILD_STRING(e->device_tree)), - JSON_BUILD_PAIR_CONDITION(!strv_isempty(e->device_tree_overlay), "devicetreeOverlay", JSON_BUILD_STRV(e->device_tree_overlay)))); - if (r < 0) - return log_oom(); - - /* Sanitizers (only memory sanitizer?) do not like function call with too many - * arguments and trigger false positive warnings. Let's not add too many json objects - * at once. */ - r = json_variant_merge_objectb( - &v, JSON_BUILD_OBJECT( - JSON_BUILD_PAIR("isReported", JSON_BUILD_BOOLEAN(e->reported_by_loader)), - JSON_BUILD_PAIR_CONDITION(e->tries_left != UINT_MAX, "triesLeft", JSON_BUILD_UNSIGNED(e->tries_left)), - JSON_BUILD_PAIR_CONDITION(e->tries_done != UINT_MAX, "triesDone", JSON_BUILD_UNSIGNED(e->tries_done)), - JSON_BUILD_PAIR_CONDITION(config->default_entry >= 0, "isDefault", JSON_BUILD_BOOLEAN(i == (size_t) config->default_entry)), - JSON_BUILD_PAIR_CONDITION(config->selected_entry >= 0, "isSelected", JSON_BUILD_BOOLEAN(i == (size_t) config->selected_entry)))); - + r = boot_entry_to_json(config, i, &v); if (r < 0) return log_oom(); @@ -1413,12 +1780,12 @@ int show_boot_entries(const BootConfig *config, JsonFormatFlags json_format) { return log_oom(); } - json_variant_dump(array, json_format | JSON_FORMAT_EMPTY_ARRAY, NULL, NULL); - - } else { + return json_variant_dump(array, json_format | JSON_FORMAT_EMPTY_ARRAY, NULL, NULL); + } else for (size_t n = 0; n < config->n_entries; n++) { r = show_boot_entry( config->entries + n, + &config->global_addons, /* show_as_default= */ n == (size_t) config->default_entry, /* show_as_selected= */ n == (size_t) config->selected_entry, /* show_discovered= */ true); @@ -1428,7 +1795,6 @@ int show_boot_entries(const BootConfig *config, JsonFormatFlags json_format) { if (n+1 < config->n_entries) putchar('\n'); } - } return 0; } diff --git a/src/shared/bootspec.h b/src/shared/bootspec.h index ddd149e..1885a88 100644 --- a/src/shared/bootspec.h +++ b/src/shared/bootspec.h @@ -20,6 +20,18 @@ typedef enum BootEntryType { _BOOT_ENTRY_TYPE_INVALID = -EINVAL, } BootEntryType; +typedef struct BootEntryAddon { + char *location; + char *cmdline; +} BootEntryAddon; + +typedef struct BootEntryAddons { + BootEntryAddon *items; + size_t n_items; +} BootEntryAddons; + +BootEntryAddon* boot_entry_addon_free(BootEntryAddon *t); + typedef struct BootEntry { BootEntryType type; bool reported_by_loader; @@ -34,6 +46,7 @@ typedef struct BootEntry { char *machine_id; char *architecture; char **options; + BootEntryAddons local_addons; char *kernel; /* linux is #defined to 1, yikes! */ char *efi; char **initrd; @@ -52,12 +65,6 @@ typedef struct BootEntry { typedef struct BootConfig { char *default_pattern; - char *timeout; - char *editor; - char *auto_entries; - char *auto_firmware; - char *console_mode; - char *beep; char *entry_oneshot; char *entry_default; @@ -66,6 +73,8 @@ typedef struct BootConfig { BootEntry *entries; size_t n_entries; + BootEntryAddons global_addons; + ssize_t default_entry; ssize_t selected_entry; @@ -119,6 +128,7 @@ static inline const char* boot_entry_title(const BootEntry *entry) { int show_boot_entry( const BootEntry *e, + const BootEntryAddons *global_addons, bool show_as_default, bool show_as_selected, bool show_reported); @@ -127,3 +137,5 @@ int show_boot_entries( JsonFormatFlags json_format); int boot_filename_extract_tries(const char *fname, char **ret_stripped, unsigned *ret_tries_left, unsigned *ret_tries_done); + +int boot_entry_to_json(const BootConfig *c, size_t i, JsonVariant **ret); diff --git a/src/shared/bpf-compat.h b/src/shared/bpf-compat.h index 9ccb7d8..5a7945c 100644 --- a/src/shared/bpf-compat.h +++ b/src/shared/bpf-compat.h @@ -26,6 +26,7 @@ struct bpf_map_create_opts; * When removing this file move these back to bpf-dlopen.h */ extern int (*sym_bpf_map_create)(enum bpf_map_type, const char *, __u32, __u32, __u32, const struct bpf_map_create_opts *); extern int (*sym_libbpf_probe_bpf_prog_type)(enum bpf_prog_type, const void *); +extern struct bpf_map* (*sym_bpf_object__next_map)(const struct bpf_object *obj, const struct bpf_map *map); /* compat symbols removed in libbpf 1.0 */ extern int (*sym_bpf_create_map)(enum bpf_map_type, int key_size, int value_size, int max_entries, __u32 map_flags); diff --git a/src/shared/bpf-dlopen.c b/src/shared/bpf-dlopen.c index f00dbea..50491fc 100644 --- a/src/shared/bpf-dlopen.c +++ b/src/shared/bpf-dlopen.c @@ -18,26 +18,41 @@ #define MODERN_LIBBPF 0 #endif -struct bpf_link* (*sym_bpf_program__attach_cgroup)(const struct bpf_program *, int); -struct bpf_link* (*sym_bpf_program__attach_lsm)(const struct bpf_program *); -int (*sym_bpf_link__fd)(const struct bpf_link *); -int (*sym_bpf_link__destroy)(struct bpf_link *); -int (*sym_bpf_map__fd)(const struct bpf_map *); -const char* (*sym_bpf_map__name)(const struct bpf_map *); +DLSYM_FUNCTION(bpf_link__destroy); +DLSYM_FUNCTION(bpf_link__fd); +DLSYM_FUNCTION(bpf_link__open); +DLSYM_FUNCTION(bpf_link__pin); +DLSYM_FUNCTION(bpf_map__fd); +DLSYM_FUNCTION(bpf_map__name); +DLSYM_FUNCTION(bpf_map__set_inner_map_fd); +DLSYM_FUNCTION(bpf_map__set_max_entries); +DLSYM_FUNCTION(bpf_map__set_pin_path); +DLSYM_FUNCTION(bpf_map_delete_elem); +DLSYM_FUNCTION(bpf_map_get_fd_by_id); +DLSYM_FUNCTION(bpf_map_lookup_elem); +DLSYM_FUNCTION(bpf_map_update_elem); +DLSYM_FUNCTION(bpf_object__attach_skeleton); +DLSYM_FUNCTION(bpf_object__destroy_skeleton); +DLSYM_FUNCTION(bpf_object__detach_skeleton); +DLSYM_FUNCTION(bpf_object__load_skeleton); +DLSYM_FUNCTION(bpf_object__name); +DLSYM_FUNCTION(bpf_object__open_skeleton); +DLSYM_FUNCTION(bpf_object__pin_maps); +DLSYM_FUNCTION(bpf_program__attach); +DLSYM_FUNCTION(bpf_program__attach_cgroup); +DLSYM_FUNCTION(bpf_program__attach_lsm); +DLSYM_FUNCTION(bpf_program__name); +DLSYM_FUNCTION(libbpf_get_error); +DLSYM_FUNCTION(libbpf_set_print); +DLSYM_FUNCTION(ring_buffer__epoll_fd); +DLSYM_FUNCTION(ring_buffer__free); +DLSYM_FUNCTION(ring_buffer__new); +DLSYM_FUNCTION(ring_buffer__poll); + +/* new symbols available from libbpf 0.7.0 */ int (*sym_bpf_map_create)(enum bpf_map_type, const char *, __u32, __u32, __u32, const struct bpf_map_create_opts *); -int (*sym_bpf_map__set_max_entries)(struct bpf_map *, __u32); -int (*sym_bpf_map_update_elem)(int, const void *, const void *, __u64); -int (*sym_bpf_map_delete_elem)(int, const void *); -int (*sym_bpf_map__set_inner_map_fd)(struct bpf_map *, int); -int (*sym_bpf_object__open_skeleton)(struct bpf_object_skeleton *, const struct bpf_object_open_opts *); -int (*sym_bpf_object__load_skeleton)(struct bpf_object_skeleton *); -int (*sym_bpf_object__attach_skeleton)(struct bpf_object_skeleton *); -void (*sym_bpf_object__detach_skeleton)(struct bpf_object_skeleton *); -void (*sym_bpf_object__destroy_skeleton)(struct bpf_object_skeleton *); int (*sym_libbpf_probe_bpf_prog_type)(enum bpf_prog_type, const void *); -const char* (*sym_bpf_program__name)(const struct bpf_program *); -libbpf_print_fn_t (*sym_libbpf_set_print)(libbpf_print_fn_t); -long (*sym_libbpf_get_error)(const void *); +struct bpf_map* (*sym_bpf_object__next_map)(const struct bpf_object *obj, const struct bpf_map *map); /* compat symbols removed in libbpf 1.0 */ int (*sym_bpf_create_map)(enum bpf_map_type, int key_size, int value_size, int max_entries, __u32 map_flags); @@ -61,6 +76,11 @@ int dlopen_bpf(void) { void *dl; int r; + ELF_NOTE_DLOPEN("bpf", + "Support firewalling and sandboxing with BPF", + ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + "libbpf.so.1", "libbpf.so.0"); + DISABLE_WARNING_DEPRECATED_DECLARATIONS; dl = dlopen("libbpf.so.1", RTLD_LAZY); @@ -88,6 +108,8 @@ int dlopen_bpf(void) { DLSYM_ARG(bpf_probe_prog_type) #endif ); + + /* NB: we don't try to load bpf_object__next_map() on old versions */ } else { log_debug("Loaded 'libbpf.so.1' via dlopen()"); @@ -96,11 +118,13 @@ int dlopen_bpf(void) { dl, LOG_DEBUG, #if MODERN_LIBBPF DLSYM_ARG(bpf_map_create), - DLSYM_ARG(libbpf_probe_bpf_prog_type) + DLSYM_ARG(libbpf_probe_bpf_prog_type), + DLSYM_ARG(bpf_object__next_map) #else /* These symbols did not exist in old libbpf, hence we cannot type check them */ DLSYM_ARG_FORCE(bpf_map_create), - DLSYM_ARG_FORCE(libbpf_probe_bpf_prog_type) + DLSYM_ARG_FORCE(libbpf_probe_bpf_prog_type), + DLSYM_ARG_FORCE(bpf_object__next_map) #endif ); } @@ -111,28 +135,41 @@ int dlopen_bpf(void) { dl, LOG_DEBUG, DLSYM_ARG(bpf_link__destroy), DLSYM_ARG(bpf_link__fd), + DLSYM_ARG(bpf_link__open), + DLSYM_ARG(bpf_link__pin), DLSYM_ARG(bpf_map__fd), DLSYM_ARG(bpf_map__name), + DLSYM_ARG(bpf_map__set_inner_map_fd), DLSYM_ARG(bpf_map__set_max_entries), - DLSYM_ARG(bpf_map_update_elem), + DLSYM_ARG(bpf_map__set_pin_path), DLSYM_ARG(bpf_map_delete_elem), - DLSYM_ARG(bpf_map__set_inner_map_fd), - DLSYM_ARG(bpf_object__open_skeleton), - DLSYM_ARG(bpf_object__load_skeleton), + DLSYM_ARG(bpf_map_get_fd_by_id), + DLSYM_ARG(bpf_map_lookup_elem), + DLSYM_ARG(bpf_map_update_elem), DLSYM_ARG(bpf_object__attach_skeleton), - DLSYM_ARG(bpf_object__detach_skeleton), DLSYM_ARG(bpf_object__destroy_skeleton), + DLSYM_ARG(bpf_object__detach_skeleton), + DLSYM_ARG(bpf_object__load_skeleton), + DLSYM_ARG(bpf_object__name), + DLSYM_ARG(bpf_object__open_skeleton), + DLSYM_ARG(bpf_object__pin_maps), #if MODERN_LIBBPF + DLSYM_ARG(bpf_program__attach), DLSYM_ARG(bpf_program__attach_cgroup), DLSYM_ARG(bpf_program__attach_lsm), #else /* libbpf added a "const" to function parameters where it should not have, ignore this type incompatibility */ + DLSYM_ARG_FORCE(bpf_program__attach), DLSYM_ARG_FORCE(bpf_program__attach_cgroup), DLSYM_ARG_FORCE(bpf_program__attach_lsm), #endif DLSYM_ARG(bpf_program__name), + DLSYM_ARG(libbpf_get_error), DLSYM_ARG(libbpf_set_print), - DLSYM_ARG(libbpf_get_error)); + DLSYM_ARG(ring_buffer__epoll_fd), + DLSYM_ARG(ring_buffer__free), + DLSYM_ARG(ring_buffer__new), + DLSYM_ARG(ring_buffer__poll)); if (r < 0) return r; @@ -144,6 +181,23 @@ int dlopen_bpf(void) { return r; } +int bpf_get_error_translated(const void *ptr) { + int r; + + r = sym_libbpf_get_error(ptr); + + switch (r) { + case -524: + /* Workaround for kernel bug, BPF returns an internal error instead of translating it, until + * it is fixed: + * https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/errno.h?h=v6.9&id=a38297e3fb012ddfa7ce0321a7e5a8daeb1872b6#n27 + */ + return -EOPNOTSUPP; + default: + return r; + } +} + #else int dlopen_bpf(void) { diff --git a/src/shared/bpf-dlopen.h b/src/shared/bpf-dlopen.h index 0750abc..df12d08 100644 --- a/src/shared/bpf-dlopen.h +++ b/src/shared/bpf-dlopen.h @@ -7,27 +7,44 @@ #include <bpf/libbpf.h> #include "bpf-compat.h" +#include "dlfcn-util.h" -extern struct bpf_link* (*sym_bpf_program__attach_cgroup)(const struct bpf_program *, int); -extern struct bpf_link* (*sym_bpf_program__attach_lsm)(const struct bpf_program *); -extern int (*sym_bpf_link__fd)(const struct bpf_link *); -extern int (*sym_bpf_link__destroy)(struct bpf_link *); -extern int (*sym_bpf_map__fd)(const struct bpf_map *); -extern const char* (*sym_bpf_map__name)(const struct bpf_map *); -extern int (*sym_bpf_map__set_max_entries)(struct bpf_map *, __u32); -extern int (*sym_bpf_map_update_elem)(int, const void *, const void *, __u64); -extern int (*sym_bpf_map_delete_elem)(int, const void *); -extern int (*sym_bpf_map__set_inner_map_fd)(struct bpf_map *, int); +DLSYM_PROTOTYPE(bpf_link__destroy); +DLSYM_PROTOTYPE(bpf_link__fd); +DLSYM_PROTOTYPE(bpf_link__open); +DLSYM_PROTOTYPE(bpf_link__pin); +DLSYM_PROTOTYPE(bpf_map__fd); +DLSYM_PROTOTYPE(bpf_map__name); +DLSYM_PROTOTYPE(bpf_map__set_inner_map_fd); +DLSYM_PROTOTYPE(bpf_map__set_max_entries); +DLSYM_PROTOTYPE(bpf_map__set_pin_path); +DLSYM_PROTOTYPE(bpf_map_delete_elem); +DLSYM_PROTOTYPE(bpf_map_get_fd_by_id); +DLSYM_PROTOTYPE(bpf_map_lookup_elem); +DLSYM_PROTOTYPE(bpf_map_update_elem); /* The *_skeleton APIs are autogenerated by bpftool, the targets can be found * in ./build/src/core/bpf/socket_bind/socket-bind.skel.h */ -extern int (*sym_bpf_object__open_skeleton)(struct bpf_object_skeleton *, const struct bpf_object_open_opts *); -extern int (*sym_bpf_object__load_skeleton)(struct bpf_object_skeleton *); -extern int (*sym_bpf_object__attach_skeleton)(struct bpf_object_skeleton *); -extern void (*sym_bpf_object__detach_skeleton)(struct bpf_object_skeleton *); -extern void (*sym_bpf_object__destroy_skeleton)(struct bpf_object_skeleton *); -extern const char* (*sym_bpf_program__name)(const struct bpf_program *); -extern libbpf_print_fn_t (*sym_libbpf_set_print)(libbpf_print_fn_t); -extern long (*sym_libbpf_get_error)(const void *); +DLSYM_PROTOTYPE(bpf_object__attach_skeleton); +DLSYM_PROTOTYPE(bpf_object__destroy_skeleton); +DLSYM_PROTOTYPE(bpf_object__detach_skeleton); +DLSYM_PROTOTYPE(bpf_object__load_skeleton); +DLSYM_PROTOTYPE(bpf_object__name); +DLSYM_PROTOTYPE(bpf_object__open_skeleton); +DLSYM_PROTOTYPE(bpf_object__pin_maps); +DLSYM_PROTOTYPE(bpf_program__attach); +DLSYM_PROTOTYPE(bpf_program__attach_cgroup); +DLSYM_PROTOTYPE(bpf_program__attach_lsm); +DLSYM_PROTOTYPE(bpf_program__name); +DLSYM_PROTOTYPE(libbpf_set_print); +DLSYM_PROTOTYPE(ring_buffer__epoll_fd); +DLSYM_PROTOTYPE(ring_buffer__free); +DLSYM_PROTOTYPE(ring_buffer__new); +DLSYM_PROTOTYPE(ring_buffer__poll); + +/* libbpf sometimes returns error codes that make sense only in the kernel, like 524 for EOPNOTSUPP. Use + * this helper instead of libbpf_get_error() to ensure some of the known ones are translated into errnos + * we understand. */ +int bpf_get_error_translated(const void *ptr); #endif diff --git a/src/shared/bpf-link.c b/src/shared/bpf-link.c index fea49b2..77f6a4e 100644 --- a/src/shared/bpf-link.c +++ b/src/shared/bpf-link.c @@ -16,7 +16,7 @@ bool bpf_can_link_program(struct bpf_program *prog) { link = sym_bpf_program__attach_cgroup(prog, /*cgroup_fd=*/-1); /* EBADF indicates that bpf_link is supported by kernel. */ - return sym_libbpf_get_error(link) == -EBADF; + return bpf_get_error_translated(link) == -EBADF; } int bpf_serialize_link(FILE *f, FDSet *fds, const char *key, struct bpf_link *link) { @@ -25,7 +25,7 @@ int bpf_serialize_link(FILE *f, FDSet *fds, const char *key, struct bpf_link *li if (!link) return -ENOENT; - if (sym_libbpf_get_error(link) != 0) + if (bpf_get_error_translated(link) != 0) return -EINVAL; return serialize_fd(f, fds, key, sym_bpf_link__fd(link)); diff --git a/src/shared/bpf-program.c b/src/shared/bpf-program.c index bbdd4f6..ac92ec8 100644 --- a/src/shared/bpf-program.c +++ b/src/shared/bpf-program.c @@ -321,7 +321,7 @@ int bpf_map_new( /* The map name is primarily informational for debugging purposes, and typically too short * to carry the full unit name, hence we employ a trivial lossy escaping to make it fit * (truncation + only alphanumerical, "." and "_" are allowed as per - * https://www.kernel.org/doc/html/next/bpf/maps.html#usage-notes) */ + * https://docs.kernel.org/bpf/maps.html#usage-notes) */ for (size_t i = 0; i < sizeof(attr.map_name) - 1 && *n; i++, n++) attr.map_name[i] = strchr(ALPHANUMERICAL ".", *n) ? *n : '_'; diff --git a/src/shared/btrfs-util.c b/src/shared/btrfs-util.c index 2ed6bf2..f6055a8 100644 --- a/src/shared/btrfs-util.c +++ b/src/shared/btrfs-util.c @@ -262,24 +262,49 @@ static int btrfs_ioctl_search_args_compare(const struct btrfs_ioctl_search_args } typedef struct BtrfsForeachIterator { - const void *p; - size_t i; + const struct btrfs_ioctl_search_args *args; + size_t offset; + unsigned index; + struct btrfs_ioctl_search_header *header; + const void **body; } BtrfsForeachIterator; +static int btrfs_iterate(BtrfsForeachIterator *i) { + assert(i); + assert(i->args); + assert(i->header); + assert(i->body); + + if (i->index >= i->args->key.nr_items) + return 0; /* end */ + + assert_cc(BTRFS_SEARCH_ARGS_BUFSIZE >= sizeof(struct btrfs_ioctl_search_header)); + if (i->offset > BTRFS_SEARCH_ARGS_BUFSIZE - sizeof(struct btrfs_ioctl_search_header)) + return -EBADMSG; + + struct btrfs_ioctl_search_header h; + memcpy(&h, (const uint8_t*) i->args->buf + i->offset, sizeof(struct btrfs_ioctl_search_header)); + + if (i->offset > BTRFS_SEARCH_ARGS_BUFSIZE - sizeof(struct btrfs_ioctl_search_header) - h.len) + return -EBADMSG; + + *i->body = (const uint8_t*) i->args->buf + i->offset + sizeof(struct btrfs_ioctl_search_header); + *i->header = h; + i->offset += sizeof(struct btrfs_ioctl_search_header) + h.len; + i->index++; + + return 1; +} + /* Iterates through a series of struct btrfs_file_extent_item elements. They are unfortunately not aligned, * hence we copy out the header from them */ -#define FOREACH_BTRFS_IOCTL_SEARCH_HEADER(sh, body, args) \ +#define FOREACH_BTRFS_IOCTL_SEARCH_HEADER(_sh, _body, _args) \ for (BtrfsForeachIterator iterator = { \ - .p = ({ \ - memcpy(&(sh), (args).buf, sizeof(struct btrfs_ioctl_search_header)); \ - (body) = (const void*) ((const uint8_t*) (args).buf + sizeof(struct btrfs_ioctl_search_header)); \ - (args).buf; \ - }), \ + .args = &(_args), \ + .header = &(_sh), \ + .body = &(_body), \ }; \ - iterator.i < (args).key.nr_items; \ - iterator.i++, \ - memcpy(&(sh), iterator.p = (const uint8_t*) iterator.p + sizeof(struct btrfs_ioctl_search_header) + (sh).len, sizeof(struct btrfs_ioctl_search_header)), \ - (body) = (const void*) ((const uint8_t*) iterator.p + sizeof(struct btrfs_ioctl_search_header))) + btrfs_iterate(&iterator) > 0; ) int btrfs_subvol_get_info_fd(int fd, uint64_t subvol_id, BtrfsSubvolInfo *ret) { struct btrfs_ioctl_search_args args = { @@ -1162,6 +1187,8 @@ static int copy_quota_hierarchy(int fd, uint64_t old_subvol_id, uint64_t new_sub if (n_old_qgroups <= 0) /* Nothing to copy */ return n_old_qgroups; + assert(old_qgroups); /* Coverity gets confused by the macro iterator allocating this, add a hint */ + r = btrfs_subvol_get_parent(fd, old_subvol_id, &old_parent_id); if (r == -ENXIO) /* We have no parent, hence nothing to copy. */ @@ -1785,6 +1812,24 @@ int btrfs_subvol_auto_qgroup(const char *path, uint64_t subvol_id, bool create_i return btrfs_subvol_auto_qgroup_fd(fd, subvol_id, create_intermediary_qgroup); } +int btrfs_subvol_make_default(const char *path) { + _cleanup_close_ int fd = -EBADF; + uint64_t id; + int r; + + assert(path); + + fd = open(path, O_NOCTTY|O_CLOEXEC|O_DIRECTORY); + if (fd < 0) + return -errno; + + r = btrfs_subvol_get_id_fd(fd, &id); + if (r < 0) + return r; + + return RET_NERRNO(ioctl(fd, BTRFS_IOC_DEFAULT_SUBVOL, &id)); +} + int btrfs_subvol_get_parent(int fd, uint64_t subvol_id, uint64_t *ret) { struct btrfs_ioctl_search_args args = { diff --git a/src/shared/btrfs-util.h b/src/shared/btrfs-util.h index cd80903..6108a26 100644 --- a/src/shared/btrfs-util.h +++ b/src/shared/btrfs-util.h @@ -108,6 +108,8 @@ int btrfs_subvol_set_subtree_quota_limit_fd(int fd, uint64_t subvol_id, uint64_t int btrfs_subvol_auto_qgroup_fd(int fd, uint64_t subvol_id, bool new_qgroup); int btrfs_subvol_auto_qgroup(const char *path, uint64_t subvol_id, bool create_intermediary_qgroup); +int btrfs_subvol_make_default(const char *path); + int btrfs_qgroupid_make(uint64_t level, uint64_t id, uint64_t *ret); int btrfs_qgroupid_split(uint64_t qgroupid, uint64_t *level, uint64_t *id); diff --git a/src/shared/bus-map-properties.c b/src/shared/bus-map-properties.c index 809759d..a4833a5 100644 --- a/src/shared/bus-map-properties.c +++ b/src/shared/bus-map-properties.c @@ -8,21 +8,12 @@ int bus_map_id128(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) { sd_id128_t *p = userdata; - const void *v; - size_t n; int r; - r = sd_bus_message_read_array(m, SD_BUS_TYPE_BYTE, &v, &n); + r = bus_message_read_id128(m, p); if (r < 0) return bus_log_parse_error_debug(r); - if (n == 0) - *p = SD_ID128_NULL; - else if (n == 16) - memcpy((*p).bytes, v, n); - else - return -EINVAL; - return 0; } diff --git a/src/shared/bus-polkit.c b/src/shared/bus-polkit.c index 904b897..0382d0b 100644 --- a/src/shared/bus-polkit.c +++ b/src/shared/bus-polkit.c @@ -4,10 +4,11 @@ #include "bus-message.h" #include "bus-polkit.h" #include "bus-util.h" +#include "process-util.h" #include "strv.h" #include "user-util.h" -static int check_good_user(sd_bus_message *m, uid_t good_user) { +static int bus_message_check_good_user(sd_bus_message *m, uid_t good_user) { _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; uid_t sender_uid; int r; @@ -15,7 +16,7 @@ static int check_good_user(sd_bus_message *m, uid_t good_user) { assert(m); if (good_user == UID_INVALID) - return 0; + return false; r = sd_bus_query_sender_creds(m, SD_BUS_CREDS_EUID, &creds); if (r < 0) @@ -47,14 +48,10 @@ static int bus_message_append_strv_key_value(sd_bus_message *m, const char **l) return r; } - r = sd_bus_message_close_container(m); - if (r < 0) - return r; - - return r; + return sd_bus_message_close_container(m); } -static int bus_message_new_polkit_auth_call( +static int bus_message_new_polkit_auth_call_for_bus( sd_bus_message *m, const char *action, const char **details, @@ -102,7 +99,6 @@ static int bus_message_new_polkit_auth_call( int bus_test_polkit( sd_bus_message *call, - int capability, const char *action, const char **details, uid_t good_user, @@ -116,11 +112,11 @@ int bus_test_polkit( /* Tests non-interactively! */ - r = check_good_user(call, good_user); + r = bus_message_check_good_user(call, good_user); if (r != 0) return r; - r = sd_bus_query_sender_privilege(call, capability); + r = sd_bus_query_sender_privilege(call, -1); if (r < 0) return r; if (r > 0) @@ -130,7 +126,7 @@ int bus_test_polkit( _cleanup_(sd_bus_message_unrefp) sd_bus_message *request = NULL, *reply = NULL; int authorized = false, challenge = false; - r = bus_message_new_polkit_auth_call(call, action, details, /* interactive = */ false, &request); + r = bus_message_new_polkit_auth_call_for_bus(call, action, details, /* interactive = */ false, &request); if (r < 0) return r; @@ -189,18 +185,21 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(AsyncPolkitQueryAction*, async_polkit_query_action_f typedef struct AsyncPolkitQuery { unsigned n_ref; - AsyncPolkitQueryAction *action; + AsyncPolkitQueryAction *action; /* action currently being processed */ - sd_bus_message *request; + sd_bus *bus; + sd_bus_message *request; /* the original bus method call that triggered the polkit auth, NULL in case of varlink */ sd_bus_slot *slot; + Varlink *link; /* the original varlink method call that triggered the polkit auth, NULL in case of bus */ Hashmap *registry; sd_event_source *defer_event_source; - LIST_HEAD(AsyncPolkitQueryAction, authorized_actions); - AsyncPolkitQueryAction *denied_action; - AsyncPolkitQueryAction *error_action; - sd_bus_error error; + LIST_HEAD(AsyncPolkitQueryAction, authorized_actions); /* actions we successfully were authorized for */ + AsyncPolkitQueryAction *denied_action; /* if we received denial for an action, it's this one */ + AsyncPolkitQueryAction *absent_action; /* If polkit was absent for some action, it's this one */ + AsyncPolkitQueryAction *error_action; /* if we encountered any other error, it's this one */ + sd_bus_error error; /* the precise error, in case error_action is set */ } AsyncPolkitQuery; static AsyncPolkitQuery *async_polkit_query_free(AsyncPolkitQuery *q) { @@ -209,11 +208,18 @@ static AsyncPolkitQuery *async_polkit_query_free(AsyncPolkitQuery *q) { sd_bus_slot_unref(q->slot); - if (q->registry && q->request) - hashmap_remove(q->registry, q->request); + if (q->registry) { + if (q->request) + hashmap_remove(q->registry, q->request); + if (q->link) + hashmap_remove(q->registry, q->link); + } sd_bus_message_unref(q->request); + sd_bus_unref(q->bus); + varlink_unref(q->link); + async_polkit_query_action_free(q->action); sd_event_source_disable_unref(q->defer_event_source); @@ -221,6 +227,7 @@ static AsyncPolkitQuery *async_polkit_query_free(AsyncPolkitQuery *q) { LIST_CLEAR(authorized, q->authorized_actions, async_polkit_query_action_free); async_polkit_query_action_free(q->denied_action); + async_polkit_query_action_free(q->absent_action); async_polkit_query_action_free(q->error_action); sd_bus_error_free(&q->error); @@ -231,6 +238,14 @@ static AsyncPolkitQuery *async_polkit_query_free(AsyncPolkitQuery *q) { DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC(AsyncPolkitQuery, async_polkit_query, async_polkit_query_free); DEFINE_TRIVIAL_CLEANUP_FUNC(AsyncPolkitQuery*, async_polkit_query_unref); +DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR( + async_polkit_query_hash_ops, + void, + trivial_hash_func, + trivial_compare_func, + AsyncPolkitQuery, + async_polkit_query_unref); + static int async_polkit_defer(sd_event_source *s, void *userdata) { AsyncPolkitQuery *q = ASSERT_PTR(userdata); @@ -252,23 +267,34 @@ static int async_polkit_read_reply(sd_bus_message *reply, AsyncPolkitQuery *q) { /* Processing of a PolicyKit checks is canceled on the first auth. error. */ assert(!q->denied_action); + assert(!q->absent_action); assert(!q->error_action); assert(!sd_bus_error_is_set(&q->error)); - assert(q->action); - a = TAKE_PTR(q->action); + a = ASSERT_PTR(TAKE_PTR(q->action)); if (sd_bus_message_is_method_error(reply, NULL)) { const sd_bus_error *e; e = sd_bus_message_get_error(reply); - if (bus_error_is_unknown_service(e)) - /* Treat no PK available as access denied */ + if (bus_error_is_unknown_service(e)) { + /* If PK is absent, then store this away, as it depends on the callers flags whether + * this means deny or allow */ + log_debug("Polkit found to be unavailable while trying to authorize action '%s'.", a->action); + q->absent_action = TAKE_PTR(a); + } else if (sd_bus_error_has_names( + e, + "org.freedesktop.PolicyKit1.Error.Failed", + "org.freedesktop.PolicyKit1.Error.Cancelled", + "org.freedesktop.PolicyKit1.Error.NotAuthorized")) { + /* Treat some of the well-known PK errors as denial. */ + log_debug("Polkit authorization for action '%s' failed with an polkit error: %s", a->action, e->name); q->denied_action = TAKE_PTR(a); - else { + } else { /* Save error from polkit reply, so it can be returned when the same authorization * is attempted for second time */ + log_debug("Polkit authorization for action '%s' failed with an unexpected error: %s", a->action, e->name); q->error_action = TAKE_PTR(a); r = sd_bus_error_copy(&q->error, e); if (r == -ENOMEM) @@ -284,13 +310,17 @@ static int async_polkit_read_reply(sd_bus_message *reply, AsyncPolkitQuery *q) { if (r < 0) return r; - if (authorized) + if (authorized) { + log_debug("Polkit authorization for action '%s' succeeded.", a->action); LIST_PREPEND(authorized, q->authorized_actions, TAKE_PTR(a)); - else if (challenge) { + } else if (challenge) { + log_debug("Polkit authorization for action requires '%s' interactive authentication, which we didn't allow.", a->action); q->error_action = TAKE_PTR(a); sd_bus_error_set_const(&q->error, SD_BUS_ERROR_INTERACTIVE_AUTHORIZATION_REQUIRED, "Interactive authentication required."); - } else + } else { + log_debug("Polkit authorization for action '%s' denied.", a->action); q->denied_action = TAKE_PTR(a); + } return 0; } @@ -317,7 +347,7 @@ static int async_polkit_process_reply(sd_bus_message *reply, AsyncPolkitQuery *q if (!q->defer_event_source) { r = sd_event_add_defer( - sd_bus_get_event(sd_bus_message_get_bus(reply)), + sd_bus_get_event(q->bus), &q->defer_event_source, async_polkit_defer, q); @@ -333,13 +363,21 @@ static int async_polkit_process_reply(sd_bus_message *reply, AsyncPolkitQuery *q if (r < 0) return r; - r = sd_bus_message_rewind(q->request, true); - if (r < 0) - return r; + if (q->request) { + r = sd_bus_message_rewind(q->request, true); + if (r < 0) + return r; - r = sd_bus_enqueue_for_read(sd_bus_message_get_bus(q->request), q->request); - if (r < 0) - return r; + r = sd_bus_enqueue_for_read(q->bus, q->request); + if (r < 0) + return r; + } + + if (q->link) { + r = varlink_dispatch_again(q->link); + if (r < 0) + return r; + } return 1; } @@ -353,35 +391,54 @@ static int async_polkit_callback(sd_bus_message *reply, void *userdata, sd_bus_e r = async_polkit_process_reply(reply, q); if (r < 0) { log_debug_errno(r, "Processing asynchronous PolicyKit reply failed, ignoring: %m"); - (void) sd_bus_reply_method_errno(q->request, r, NULL); + if (q->request) + (void) sd_bus_reply_method_errno(q->request, r, NULL); + if (q->link) + (void) varlink_error_errno(q->link, r); async_polkit_query_unref(q); } return r; } +static bool async_polkit_query_have_action( + AsyncPolkitQuery *q, + const char *action, + const char **details) { + + assert(q); + assert(action); + + LIST_FOREACH(authorized, a, q->authorized_actions) + if (streq(a->action, action) && strv_equal(a->details, (char**) details)) + return true; + + return false; +} + static int async_polkit_query_check_action( AsyncPolkitQuery *q, const char *action, const char **details, + PolkitFlags flags, sd_bus_error *ret_error) { assert(q); assert(action); - assert(ret_error); - LIST_FOREACH(authorized, a, q->authorized_actions) - if (streq(a->action, action) && strv_equal(a->details, (char**) details)) - return 1; + if (async_polkit_query_have_action(q, action, details)) + return 1; /* Allow! */ if (q->error_action && streq(q->error_action->action, action)) return sd_bus_error_copy(ret_error, &q->error); if (q->denied_action && streq(q->denied_action->action, action)) - return -EACCES; + return -EACCES; /* Deny! */ - return 0; -} + if (q->absent_action) + return FLAGS_SET(flags, POLKIT_DEFAULT_ALLOW) ? 1 /* Allow! */ : -EACCES /* Deny! */; + return 0; /* no reply yet */ +} #endif /* bus_verify_polkit_async() handles verification of D-Bus calls with polkit. Because the polkit API @@ -465,24 +522,24 @@ static int async_polkit_query_check_action( * <- async_polkit_defer(q) */ -int bus_verify_polkit_async( +int bus_verify_polkit_async_full( sd_bus_message *call, - int capability, const char *action, const char **details, - bool interactive, uid_t good_user, + PolkitFlags flags, Hashmap **registry, - sd_bus_error *ret_error) { + sd_bus_error *error) { int r; assert(call); assert(action); assert(registry); - assert(ret_error); - r = check_good_user(call, good_user); + log_debug("Trying to acquire polkit authentication for '%s'.", action); + + r = bus_message_check_good_user(call, good_user); if (r != 0) return r; @@ -493,20 +550,25 @@ int bus_verify_polkit_async( /* This is a repeated invocation of this function, hence let's check if we've already got * a response from polkit for this action */ if (q) { - r = async_polkit_query_check_action(q, action, details, ret_error); - if (r != 0) + r = async_polkit_query_check_action(q, action, details, flags, error); + if (r != 0) { + log_debug("Found matching previous polkit authentication for '%s'.", action); return r; + } } #endif - r = sd_bus_query_sender_privilege(call, capability); - if (r < 0) - return r; - if (r > 0) - return 1; + if (!FLAGS_SET(flags, POLKIT_ALWAYS_QUERY)) { + /* Don't query PK if client is privileged */ + r = sd_bus_query_sender_privilege(call, /* capability= */ -1); + if (r < 0) + return r; + if (r > 0) + return 1; + } #if ENABLE_POLKIT - _cleanup_(sd_bus_message_unrefp) sd_bus_message *pk = NULL; + bool interactive = FLAGS_SET(flags, POLKIT_ALLOW_INTERACTIVE); int c = sd_bus_message_get_allow_interactive_authorization(call); if (c < 0) @@ -514,11 +576,8 @@ int bus_verify_polkit_async( if (c > 0) interactive = true; - r = hashmap_ensure_allocated(registry, NULL); - if (r < 0) - return r; - - r = bus_message_new_polkit_auth_call(call, action, details, interactive, &pk); + _cleanup_(sd_bus_message_unrefp) sd_bus_message *pk = NULL; + r = bus_message_new_polkit_auth_call_for_bus(call, action, details, interactive, &pk); if (r < 0) return r; @@ -530,6 +589,7 @@ int bus_verify_polkit_async( *q = (AsyncPolkitQuery) { .n_ref = 1, .request = sd_bus_message_ref(call), + .bus = sd_bus_ref(sd_bus_message_get_bus(call)), }; } @@ -546,7 +606,7 @@ int bus_verify_polkit_async( return -ENOMEM; if (!q->registry) { - r = hashmap_put(*registry, call, q); + r = hashmap_ensure_put(registry, &async_polkit_query_hash_ops, call, q); if (r < 0) return r; @@ -560,16 +620,264 @@ int bus_verify_polkit_async( TAKE_PTR(q); return 0; +#else + return FLAGS_SET(flags, POLKIT_DEFAULT_ALLOW) ? 1 : -EACCES; #endif +} - return -EACCES; +static int varlink_check_good_user(Varlink *link, uid_t good_user) { + int r; + + assert(link); + + if (good_user == UID_INVALID) + return false; + + uid_t peer_uid; + r = varlink_get_peer_uid(link, &peer_uid); + if (r < 0) + return r; + + return good_user == peer_uid; +} + +static int varlink_check_peer_privilege(Varlink *link) { + int r; + + assert(link); + + uid_t peer_uid; + r = varlink_get_peer_uid(link, &peer_uid); + if (r < 0) + return r; + + uid_t our_uid = getuid(); + return peer_uid == our_uid || + (our_uid != 0 && peer_uid == 0); +} + +#if ENABLE_POLKIT +static int bus_message_new_polkit_auth_call_for_varlink( + sd_bus *bus, + Varlink *link, + const char *action, + const char **details, + bool interactive, + sd_bus_message **ret) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *c = NULL; + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + int r; + + assert(bus); + assert(link); + assert(action); + assert(ret); + + r = varlink_get_peer_pidref(link, &pidref); + if (r < 0) + return r; + if (r == 0) /* if we couldn't get a pidfd this returns == 0 */ + return log_debug_errno(SYNTHETIC_ERRNO(EPERM), "Failed to get peer pidfd, cannot securely authenticate."); + + uid_t uid; + r = varlink_get_peer_uid(link, &uid); + if (r < 0) + return r; + + r = sd_bus_message_new_method_call( + bus, + &c, + "org.freedesktop.PolicyKit1", + "/org/freedesktop/PolicyKit1/Authority", + "org.freedesktop.PolicyKit1.Authority", + "CheckAuthorization"); + if (r < 0) + return r; + + r = sd_bus_message_append( + c, + "(sa{sv})s", + "unix-process", 2, + "pidfd", "h", (uint32_t) pidref.fd, + "uid", "i", (int32_t) uid, + action); + if (r < 0) + return r; + + r = bus_message_append_strv_key_value(c, details); + if (r < 0) + return r; + + r = sd_bus_message_append(c, "us", interactive, NULL); + if (r < 0) + return r; + + *ret = TAKE_PTR(c); + return 0; +} + +static bool varlink_allow_interactive_authentication(Varlink *link) { + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + int r; + + assert(link); + + /* We look for the allowInteractiveAuthentication field in the message currently being dispatched, + * always under the same name. */ + + r = varlink_get_current_parameters(link, &v); + if (r < 0) { + log_debug_errno(r, "Unable to query current parameters: %m"); + return false; + } + + JsonVariant *b; + b = json_variant_by_key(v, "allowInteractiveAuthentication"); + if (b) { + if (json_variant_is_boolean(b)) + return json_variant_boolean(b); + + log_debug("Incoming 'allowInteractiveAuthentication' field is not a boolean, ignoring."); + } + + return false; +} +#endif + +int varlink_verify_polkit_async_full( + Varlink *link, + sd_bus *bus, + const char *action, + const char **details, + uid_t good_user, + PolkitFlags flags, + Hashmap **registry) { + + int r; + + assert(link); + assert(registry); + + log_debug("Trying to acquire polkit authentication for '%s'.", action); + + /* This is the same as bus_verify_polkit_async_full(), but authenticates the peer of a varlink + * connection rather than the sender of a bus message. */ + + r = varlink_check_good_user(link, good_user); + if (r != 0) + return r; + + if (!FLAGS_SET(flags, POLKIT_ALWAYS_QUERY)) { + r = varlink_check_peer_privilege(link); + if (r != 0) + return r; + } + +#if ENABLE_POLKIT + _cleanup_(async_polkit_query_unrefp) AsyncPolkitQuery *q = NULL; + + q = async_polkit_query_ref(hashmap_get(*registry, link)); + /* This is a repeated invocation of this function, hence let's check if we've already got + * a response from polkit for this action */ + if (q) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + r = async_polkit_query_check_action(q, action, details, flags, &error); + if (r != 0) + log_debug("Found matching previous polkit authentication for '%s'.", action); + if (r < 0) { + /* Reply with a nice error */ + if (sd_bus_error_has_name(&error, SD_BUS_ERROR_INTERACTIVE_AUTHORIZATION_REQUIRED)) + (void) varlink_error(link, VARLINK_ERROR_INTERACTIVE_AUTHENTICATION_REQUIRED, NULL); + else if (ERRNO_IS_NEG_PRIVILEGE(r)) + (void) varlink_error(link, VARLINK_ERROR_PERMISSION_DENIED, NULL); + + return r; + } + if (r > 0) + return r; + } + + _cleanup_(sd_bus_unrefp) sd_bus *mybus = NULL; + if (!bus) { + r = sd_bus_open_system(&mybus); + if (r < 0) + return r; + + r = sd_bus_attach_event(mybus, varlink_get_event(link), 0); + if (r < 0) + return r; + + bus = mybus; + } + + bool interactive = + FLAGS_SET(flags, POLKIT_ALLOW_INTERACTIVE) || + varlink_allow_interactive_authentication(link); + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *pk = NULL; + r = bus_message_new_polkit_auth_call_for_varlink(bus, link, action, details, interactive, &pk); + if (r < 0) + return r; + + if (!q) { + q = new(AsyncPolkitQuery, 1); + if (!q) + return -ENOMEM; + + *q = (AsyncPolkitQuery) { + .n_ref = 1, + .link = varlink_ref(link), + .bus = sd_bus_ref(bus), + }; + } + + assert(!q->action); + q->action = new(AsyncPolkitQueryAction, 1); + if (!q->action) + return -ENOMEM; + + *q->action = (AsyncPolkitQueryAction) { + .action = strdup(action), + .details = strv_copy((char**) details), + }; + if (!q->action->action || !q->action->details) + return -ENOMEM; + + if (!q->registry) { + r = hashmap_ensure_put(registry, &async_polkit_query_hash_ops, link, q); + if (r < 0) + return r; + + q->registry = *registry; + } + + r = sd_bus_call_async(bus, &q->slot, pk, async_polkit_callback, q, 0); + if (r < 0) + return r; + + TAKE_PTR(q); + + return 0; +#else + return FLAGS_SET(flags, POLKIT_DEFAULT_ALLOW) ? 1 : -EACCES; +#endif } -Hashmap *bus_verify_polkit_async_registry_free(Hashmap *registry) { +bool varlink_has_polkit_action(Varlink *link, const char *action, const char **details, Hashmap **registry) { + assert(link); + assert(action); + assert(registry); + + /* Checks if we already have acquired some action previously */ + #if ENABLE_POLKIT - return hashmap_free_with_destructor(registry, async_polkit_query_unref); + AsyncPolkitQuery *q = hashmap_get(*registry, link); + if (!q) + return false; + + return async_polkit_query_have_action(q, action, details); #else - assert(hashmap_isempty(registry)); - return hashmap_free(registry); + return false; #endif } diff --git a/src/shared/bus-polkit.h b/src/shared/bus-polkit.h index e2a3b7e..f3741b2 100644 --- a/src/shared/bus-polkit.h +++ b/src/shared/bus-polkit.h @@ -4,8 +4,33 @@ #include "sd-bus.h" #include "hashmap.h" +#include "user-util.h" +#include "varlink.h" -int bus_test_polkit(sd_bus_message *call, int capability, const char *action, const char **details, uid_t good_user, bool *_challenge, sd_bus_error *e); +typedef enum PolkitFLags { + POLKIT_ALLOW_INTERACTIVE = 1 << 0, /* Allow interactive auth (typically not required, because can be derived from bus message/link automatically) */ + POLKIT_ALWAYS_QUERY = 1 << 1, /* Query polkit even if client is privileged */ + POLKIT_DEFAULT_ALLOW = 1 << 2, /* If polkit is not around, assume "allow" rather than the usual "deny" */ +} PolkitFlags; -int bus_verify_polkit_async(sd_bus_message *call, int capability, const char *action, const char **details, bool interactive, uid_t good_user, Hashmap **registry, sd_bus_error *error); -Hashmap *bus_verify_polkit_async_registry_free(Hashmap *registry); +int bus_test_polkit(sd_bus_message *call, const char *action, const char **details, uid_t good_user, bool *_challenge, sd_bus_error *e); + +int bus_verify_polkit_async_full(sd_bus_message *call, const char *action, const char **details, uid_t good_user, PolkitFlags flags, Hashmap **registry, sd_bus_error *error); +static inline int bus_verify_polkit_async(sd_bus_message *call, const char *action, const char **details, Hashmap **registry, sd_bus_error *error) { + return bus_verify_polkit_async_full(call, action, details, UID_INVALID, 0, registry, error); +} + +int varlink_verify_polkit_async_full(Varlink *link, sd_bus *bus, const char *action, const char **details, uid_t good_user, PolkitFlags flags, Hashmap **registry); +static inline int varlink_verify_polkit_async(Varlink *link, sd_bus *bus, const char *action, const char **details, Hashmap **registry) { + return varlink_verify_polkit_async_full(link, bus, action, details, UID_INVALID, 0, registry); +} + +/* A JsonDispatch initializer that makes sure the allowInteractiveAuthentication boolean field we want for + * polkit support in Varlink calls is ignored while regular dispatching (and does not result in errors + * regarding unexpected fields) */ +#define VARLINK_DISPATCH_POLKIT_FIELD { \ + .name = "allowInteractiveAuthentication", \ + .type = JSON_VARIANT_BOOLEAN, \ + } + +bool varlink_has_polkit_action(Varlink *link, const char *action, const char **details, Hashmap **registry); diff --git a/src/shared/bus-print-properties.c b/src/shared/bus-print-properties.c index 6704e1e..99b1cc7 100644 --- a/src/shared/bus-print-properties.c +++ b/src/shared/bus-print-properties.c @@ -164,9 +164,11 @@ static int bus_print_property(const char *name, const char *expected_value, sd_b bus_print_property_value(name, expected_value, flags, "[not set]"); - else if ((ENDSWITH_SET(name, "MemoryLow", "MemoryMin", "MemoryHigh", "MemoryMax", "MemorySwapMax", "MemoryZSwapMax", "MemoryLimit") && + else if ((ENDSWITH_SET(name, "MemoryLow", "MemoryMin", + "MemoryHigh", "MemoryMax", + "MemorySwapMax", "MemoryZSwapMax", "MemoryLimit") && u == CGROUP_LIMIT_MAX) || - (STR_IN_SET(name, "TasksMax", "DefaultTasksMax") && u == UINT64_MAX) || + (endswith(name, "TasksMax") && u == UINT64_MAX) || (startswith(name, "Limit") && u == UINT64_MAX) || (startswith(name, "DefaultLimit") && u == UINT64_MAX)) diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c index 50de989..da83422 100644 --- a/src/shared/bus-unit-util.c +++ b/src/shared/bus-unit-util.c @@ -2,6 +2,7 @@ #include "af-list.h" #include "alloc-util.h" +#include "bus-common-errors.h" #include "bus-error.h" #include "bus-locator.h" #include "bus-unit-util.h" @@ -494,14 +495,14 @@ static int bus_append_nft_set(sd_bus_message *m, const char *field, const char * if (r == 0) break; if (isempty(tuple)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse %s", field); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse %s.", field); q = tuple; - r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE, &source_str, &nfproto_str, &table, &set, NULL); + r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE, &source_str, &nfproto_str, &table, &set); if (r == -ENOMEM) return log_oom(); if (r != 4 || !isempty(q)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse %s", field); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse %s.", field); assert(source_str); assert(nfproto_str); @@ -510,11 +511,11 @@ static int bus_append_nft_set(sd_bus_message *m, const char *field, const char * source = nft_set_source_from_string(source_str); if (!IN_SET(source, NFT_SET_SOURCE_CGROUP, NFT_SET_SOURCE_USER, NFT_SET_SOURCE_GROUP)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse %s", field); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse %s.", field); nfproto = nfproto_from_string(nfproto_str); if (nfproto < 0 || !nft_identifier_valid(table) || !nft_identifier_valid(set)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse %s", field); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse %s.", field); r = sd_bus_message_append(m, "(iiss)", source, nfproto, table, set); if (r < 0) @@ -562,6 +563,7 @@ static int bus_append_cgroup_property(sd_bus_message *m, const char *field, cons if (STR_IN_SET(field, "CPUAccounting", "MemoryAccounting", + "MemoryZSwapWriteback", "IOAccounting", "BlockIOAccounting", "TasksAccounting", @@ -678,8 +680,7 @@ static int bus_append_cgroup_property(sd_bus_message *m, const char *field, cons else { r = parse_permyriad_unbounded(eq); if (r == 0) - return log_error_errno(SYNTHETIC_ERRNO(ERANGE), - "CPU quota too small."); + return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "CPU quota too small."); if (r < 0) return log_error_errno(r, "CPU quota '%s' invalid.", eq); @@ -1213,7 +1214,7 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con _cleanup_free_ void *decoded = NULL; size_t decoded_size; - r = unbase64mem(p, SIZE_MAX, &decoded, &decoded_size); + r = unbase64mem(p, &decoded, &decoded_size); if (r < 0) return log_error_errno(r, "Failed to base64 decode encrypted credential: %m"); @@ -1400,7 +1401,7 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con _cleanup_free_ void *decoded = NULL; size_t sz; - r = unbase64mem(eq, SIZE_MAX, &decoded, &sz); + r = unbase64mem(eq, &decoded, &sz); if (r < 0) return log_error_errno(r, "Failed to decode base64 data '%s': %m", eq); @@ -1787,11 +1788,11 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con 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); + r = unhexmem(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 log_error_errno(SYNTHETIC_ERRNO(EINVAL), "RootHash= '%s' is too short.", eq); return bus_append_byte_array(m, field, roothash_decoded, roothash_decoded_size); } @@ -1806,10 +1807,10 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con return bus_append_string(m, "RootHashSignaturePath", eq); if (!(value = startswith(eq, "base64:"))) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to decode RootHashSignature= '%s', not a path but doesn't start with 'base64:': %m", eq); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to decode RootHashSignature= '%s', not a path but doesn't start with 'base64:'.", eq); /* We have a roothash signature to decode, eg: RootHashSignature=base64:012345789abcdef */ - r = unbase64mem(value, strlen(value), &roothash_sig_decoded, &roothash_sig_decoded_size); + r = unbase64mem(value, &roothash_sig_decoded, &roothash_sig_decoded_size); if (r < 0) return log_error_errno(r, "Failed to decode RootHashSignature= '%s': %m", eq); @@ -1894,7 +1895,7 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con break; q = tuple; - r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS, &first, &second, NULL); + r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS, &first, &second); if (r < 0) return log_error_errno(r, "Failed to parse MountImages= property: %s", eq); if (r == 0) @@ -1926,7 +1927,7 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con for (;;) { _cleanup_free_ char *partition = NULL, *mount_options = NULL; - r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS, &partition, &mount_options, NULL); + r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS, &partition, &mount_options); if (r < 0) return log_error_errno(r, "Failed to parse MountImages= property: %s", eq); if (r == 0) @@ -2027,7 +2028,7 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con for (;;) { _cleanup_free_ char *partition = NULL, *mount_options = NULL; - r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS, &partition, &mount_options, NULL); + r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS, &partition, &mount_options); if (r < 0) return log_error_errno(r, "Failed to parse ExtensionImages= property: %s", eq); if (r == 0) @@ -2088,7 +2089,7 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con break; const char *t = tuple; - r = extract_many_words(&t, ":", EXTRACT_UNQUOTE|EXTRACT_DONT_COALESCE_SEPARATORS, &source, &destination, NULL); + r = extract_many_words(&t, ":", EXTRACT_UNQUOTE|EXTRACT_DONT_COALESCE_SEPARATORS, &source, &destination); if (r <= 0) return log_error_errno(r ?: SYNTHETIC_ERRNO(EINVAL), "Failed to parse argument: %m"); @@ -2449,6 +2450,7 @@ static int bus_append_socket_property(sd_bus_message *m, const char *field, cons "Transparent", "Broadcast", "PassCredentials", + "PassFileDescriptorsToExec", "PassSecurity", "PassPacketInfo", "ReusePort", @@ -2643,6 +2645,7 @@ static int bus_append_unit_property(sd_bus_message *m, const char *field, const if (unit_dependency_from_string(field) >= 0 || STR_IN_SET(field, "Documentation", "RequiresMountsFor", + "WantsMountsFor", "Markers")) return bus_append_strv(m, field, eq, EXTRACT_UNQUOTE); @@ -2819,13 +2822,13 @@ int bus_append_unit_property_assignment_many(sd_bus_message *m, UnitType t, char return 0; } -int bus_append_scope_pidref(sd_bus_message *m, const PidRef *pidref) { +int bus_append_scope_pidref(sd_bus_message *m, const PidRef *pidref, bool allow_pidfd) { assert(m); if (!pidref_is_set(pidref)) return -ESRCH; - if (pidref->fd >= 0) + if (pidref->fd >= 0 && allow_pidfd) return sd_bus_message_append( m, "(sv)", "PIDFDs", "ah", 1, pidref->fd); @@ -2936,3 +2939,107 @@ int bus_service_manager_reload(sd_bus *bus) { return 0; } + +typedef struct UnitFreezer { + char *name; + sd_bus *bus; +} UnitFreezer; + +/* Wait for 60 seconds at maximum for freezer operation */ +#define FREEZE_BUS_CALL_TIMEOUT (60 * USEC_PER_SEC) + +UnitFreezer* unit_freezer_free(UnitFreezer *f) { + if (!f) + return NULL; + + free(f->name); + sd_bus_flush_close_unref(f->bus); + + return mfree(f); +} + +int unit_freezer_new(const char *name, UnitFreezer **ret) { + _cleanup_(unit_freezer_freep) UnitFreezer *f = NULL; + int r; + + assert(name); + assert(ret); + + f = new(UnitFreezer, 1); + if (!f) + return log_oom(); + + *f = (UnitFreezer) { + .name = strdup(name), + }; + if (!f->name) + return log_oom(); + + r = bus_connect_system_systemd(&f->bus); + if (r < 0) + return log_error_errno(r, "Failed to open connection to systemd: %m"); + + (void) sd_bus_set_method_call_timeout(f->bus, FREEZE_BUS_CALL_TIMEOUT); + + *ret = TAKE_PTR(f); + return 0; +} + +static int unit_freezer_action(UnitFreezer *f, bool freeze) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + assert(f); + assert(f->name); + assert(f->bus); + + r = bus_call_method(f->bus, bus_systemd_mgr, + freeze ? "FreezeUnit" : "ThawUnit", + &error, + /* reply = */ NULL, + "s", + f->name); + if (r < 0) { + if (sd_bus_error_has_names(&error, + BUS_ERROR_NO_SUCH_UNIT, + BUS_ERROR_UNIT_INACTIVE, + SD_BUS_ERROR_NOT_SUPPORTED)) { + + log_debug_errno(r, "Skipping freezer for '%s': %s", f->name, bus_error_message(&error, r)); + return 0; + } + + return log_error_errno(r, "Failed to %s unit '%s': %s", + freeze ? "freeze" : "thaw", f->name, bus_error_message(&error, r)); + } + + log_info("Successfully %s unit '%s'.", freeze ? "froze" : "thawed", f->name); + return 1; +} + +int unit_freezer_freeze(UnitFreezer *f) { + return unit_freezer_action(f, true); +} + +int unit_freezer_thaw(UnitFreezer *f) { + return unit_freezer_action(f, false); +} + +int unit_freezer_new_freeze(const char *name, UnitFreezer **ret) { + _cleanup_(unit_freezer_freep) UnitFreezer *f = NULL; + int r; + + assert(name); + assert(ret); + + r = unit_freezer_new(name, &f); + if (r < 0) + return r; + + r = unit_freezer_freeze(f); + if (r < 0) + return r; + + *ret = TAKE_PTR(f); + return 0; +} diff --git a/src/shared/bus-unit-util.h b/src/shared/bus-unit-util.h index d52c847..ea4056c 100644 --- a/src/shared/bus-unit-util.h +++ b/src/shared/bus-unit-util.h @@ -26,7 +26,7 @@ int bus_parse_unit_info(sd_bus_message *message, UnitInfo *u); int bus_append_unit_property_assignment(sd_bus_message *m, UnitType t, const char *assignment); int bus_append_unit_property_assignment_many(sd_bus_message *m, UnitType t, char **l); -int bus_append_scope_pidref(sd_bus_message *m, const PidRef *pidref); +int bus_append_scope_pidref(sd_bus_message *m, const PidRef *pidref, bool allow_pidfd); int bus_deserialize_and_dump_unit_file_changes(sd_bus_message *m, bool quiet); @@ -35,3 +35,15 @@ int unit_load_state(sd_bus *bus, const char *name, char **load_state); int unit_info_compare(const UnitInfo *a, const UnitInfo *b); int bus_service_manager_reload(sd_bus *bus); + +typedef struct UnitFreezer UnitFreezer; + +UnitFreezer* unit_freezer_free(UnitFreezer *f); +DEFINE_TRIVIAL_CLEANUP_FUNC(UnitFreezer*, unit_freezer_free); + +int unit_freezer_new(const char *name, UnitFreezer **ret); + +int unit_freezer_freeze(UnitFreezer *f); +int unit_freezer_thaw(UnitFreezer *f); + +int unit_freezer_new_freeze(const char *name, UnitFreezer **ret); diff --git a/src/shared/bus-util.c b/src/shared/bus-util.c index 4123152..30f9602 100644 --- a/src/shared/bus-util.c +++ b/src/shared/bus-util.c @@ -18,12 +18,17 @@ #include "bus-internal.h" #include "bus-label.h" #include "bus-util.h" +#include "capsule-util.h" +#include "chase.h" +#include "daemon-util.h" #include "data-fd-util.h" #include "fd-util.h" +#include "format-util.h" #include "memstream-util.h" #include "path-util.h" #include "socket-util.h" #include "stdio-util.h" +#include "uid-classification.h" static int name_owner_change_callback(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { sd_event *e = ASSERT_PTR(userdata); @@ -128,8 +133,8 @@ int bus_event_loop_with_idle( if (r == 0 && !exiting && idle) { /* Inform the service manager that we are going down, so that it will queue all - * further start requests, instead of assuming we are already running. */ - sd_notify(false, "STOPPING=1"); + * further start requests, instead of assuming we are still running. */ + (void) sd_notify(false, NOTIFY_STOPPING); r = bus_async_unregister_and_exit(e, bus, name); if (r < 0) @@ -267,6 +272,131 @@ int bus_connect_user_systemd(sd_bus **ret_bus) { return 0; } +static int pin_capsule_socket(const char *capsule, const char *suffix, uid_t *ret_uid, gid_t *ret_gid) { + _cleanup_close_ int inode_fd = -EBADF; + _cleanup_free_ char *p = NULL; + struct stat st; + int r; + + assert(capsule); + assert(suffix); + assert(ret_uid); + assert(ret_gid); + + p = path_join("/run/capsules", capsule, suffix); + if (!p) + return -ENOMEM; + + /* We enter territory owned by the user, hence let's be paranoid about symlinks and ownership */ + r = chase(p, /* root= */ NULL, CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS, /* ret_path= */ NULL, &inode_fd); + if (r < 0) + return r; + + if (fstat(inode_fd, &st) < 0) + return negative_errno(); + + /* Paranoid safety check */ + if (uid_is_system(st.st_uid) || gid_is_system(st.st_gid)) + return -EPERM; + + *ret_uid = st.st_uid; + *ret_gid = st.st_gid; + + return TAKE_FD(inode_fd); +} + +static int bus_set_address_capsule(sd_bus *bus, const char *capsule, const char *suffix, int *ret_pin_fd) { + _cleanup_close_ int inode_fd = -EBADF; + _cleanup_free_ char *pp = NULL; + uid_t uid; + gid_t gid; + int r; + + assert(bus); + assert(capsule); + assert(suffix); + assert(ret_pin_fd); + + /* Connects to a capsule's user bus. We need to do so under the capsule's UID/GID, otherwise + * the service manager might refuse our connection. Hence fake it. */ + + r = capsule_name_is_valid(capsule); + if (r < 0) + return r; + if (r == 0) + return -EINVAL; + + inode_fd = pin_capsule_socket(capsule, suffix, &uid, &gid); + if (inode_fd < 0) + return inode_fd; + + pp = bus_address_escape(FORMAT_PROC_FD_PATH(inode_fd)); + if (!pp) + return -ENOMEM; + + if (asprintf(&bus->address, "unix:path=%s,uid=" UID_FMT ",gid=" GID_FMT, pp, uid, gid) < 0) + return -ENOMEM; + + *ret_pin_fd = TAKE_FD(inode_fd); /* This fd must be kept pinned until the connection has been established */ + return 0; +} + +int bus_set_address_capsule_bus(sd_bus *bus, const char *capsule, int *ret_pin_fd) { + return bus_set_address_capsule(bus, capsule, "bus", ret_pin_fd); +} + +int bus_connect_capsule_systemd(const char *capsule, sd_bus **ret_bus) { + _cleanup_(sd_bus_close_unrefp) sd_bus *bus = NULL; + _cleanup_close_ int inode_fd = -EBADF; + int r; + + assert(capsule); + assert(ret_bus); + + r = sd_bus_new(&bus); + if (r < 0) + return r; + + r = bus_set_address_capsule(bus, capsule, "systemd/private", &inode_fd); + if (r < 0) + return r; + + r = sd_bus_start(bus); + if (r < 0) + return r; + + *ret_bus = TAKE_PTR(bus); + return 0; +} + +int bus_connect_capsule_bus(const char *capsule, sd_bus **ret_bus) { + _cleanup_(sd_bus_close_unrefp) sd_bus *bus = NULL; + _cleanup_close_ int inode_fd = -EBADF; + int r; + + assert(capsule); + assert(ret_bus); + + r = sd_bus_new(&bus); + if (r < 0) + return r; + + r = bus_set_address_capsule_bus(bus, capsule, &inode_fd); + if (r < 0) + return r; + + r = sd_bus_set_bus_client(bus, true); + if (r < 0) + return r; + + r = sd_bus_start(bus); + if (r < 0) + return r; + + *ret_bus = TAKE_PTR(bus); + return 0; +} + int bus_connect_transport( BusTransport transport, const char *host, @@ -280,12 +410,10 @@ int bus_connect_transport( assert(transport < _BUS_TRANSPORT_MAX); assert(ret); - assert_return((transport == BUS_TRANSPORT_LOCAL) == !host, -EINVAL); - assert_return(transport != BUS_TRANSPORT_REMOTE || runtime_scope == RUNTIME_SCOPE_SYSTEM, -EOPNOTSUPP); - switch (transport) { case BUS_TRANSPORT_LOCAL: + assert_return(!host, -EINVAL); switch (runtime_scope) { @@ -307,11 +435,12 @@ int bus_connect_transport( break; case BUS_TRANSPORT_REMOTE: + assert_return(runtime_scope == RUNTIME_SCOPE_SYSTEM, -EOPNOTSUPP); + r = sd_bus_open_system_remote(&bus, host); break; case BUS_TRANSPORT_MACHINE: - switch (runtime_scope) { case RUNTIME_SCOPE_USER: @@ -328,6 +457,12 @@ int bus_connect_transport( break; + case BUS_TRANSPORT_CAPSULE: + assert_return(runtime_scope == RUNTIME_SCOPE_USER, -EINVAL); + + r = bus_connect_capsule_bus(host, &bus); + break; + default: assert_not_reached(); } @@ -342,28 +477,32 @@ int bus_connect_transport( return 0; } -int bus_connect_transport_systemd(BusTransport transport, const char *host, RuntimeScope runtime_scope, sd_bus **bus) { +int bus_connect_transport_systemd( + BusTransport transport, + const char *host, + RuntimeScope runtime_scope, + sd_bus **ret_bus) { + assert(transport >= 0); assert(transport < _BUS_TRANSPORT_MAX); - assert(bus); - - assert_return((transport == BUS_TRANSPORT_LOCAL) == !host, -EINVAL); - assert_return(transport == BUS_TRANSPORT_LOCAL || runtime_scope == RUNTIME_SCOPE_SYSTEM, -EOPNOTSUPP); + assert(ret_bus); switch (transport) { case BUS_TRANSPORT_LOCAL: + assert_return(!host, -EINVAL); + switch (runtime_scope) { case RUNTIME_SCOPE_USER: - return bus_connect_user_systemd(bus); + return bus_connect_user_systemd(ret_bus); case RUNTIME_SCOPE_SYSTEM: if (sd_booted() <= 0) /* Print a friendly message when the local system is actually not running systemd as PID 1. */ return log_error_errno(SYNTHETIC_ERRNO(EHOSTDOWN), "System has not been booted with systemd as init system (PID 1). Can't operate."); - return bus_connect_system_systemd(bus); + return bus_connect_system_systemd(ret_bus); default: assert_not_reached(); @@ -372,10 +511,16 @@ int bus_connect_transport_systemd(BusTransport transport, const char *host, Runt break; case BUS_TRANSPORT_REMOTE: - return sd_bus_open_system_remote(bus, host); + assert_return(runtime_scope == RUNTIME_SCOPE_SYSTEM, -EOPNOTSUPP); + return sd_bus_open_system_remote(ret_bus, host); case BUS_TRANSPORT_MACHINE: - return sd_bus_open_system_machine(bus, host); + assert_return(runtime_scope == RUNTIME_SCOPE_SYSTEM, -EOPNOTSUPP); + return sd_bus_open_system_machine(ret_bus, host); + + case BUS_TRANSPORT_CAPSULE: + assert_return(runtime_scope == RUNTIME_SCOPE_USER, -EINVAL); + return bus_connect_capsule_systemd(host, ret_bus); default: assert_not_reached(); @@ -626,7 +771,7 @@ static int method_dump_memory_state_by_fd(sd_bus_message *message, void *userdat if (r < 0) return r; - fd = acquire_data_fd(dump, dump_size, 0); + fd = acquire_data_fd_full(dump, dump_size, /* flags = */ 0); if (fd < 0) return fd; @@ -709,3 +854,75 @@ int bus_property_get_string_set( return bus_message_append_string_set(reply, *s); } + +int bus_creds_get_pidref( + sd_bus_creds *c, + PidRef *ret) { + + int pidfd = -EBADF; + pid_t pid; + int r; + + assert(c); + assert(ret); + + r = sd_bus_creds_get_pid(c, &pid); + if (r < 0) + return r; + + r = sd_bus_creds_get_pidfd_dup(c, &pidfd); + if (r < 0 && r != -ENODATA) + return r; + + *ret = (PidRef) { + .pid = pid, + .fd = pidfd, + }; + + return 0; +} + +int bus_query_sender_pidref( + sd_bus_message *m, + PidRef *ret) { + + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; + int r; + + assert(m); + assert(ret); + + r = sd_bus_query_sender_creds(m, SD_BUS_CREDS_PID|SD_BUS_CREDS_PIDFD, &creds); + if (r < 0) + return r; + + return bus_creds_get_pidref(creds, ret); +} + +int bus_message_read_id128(sd_bus_message *m, sd_id128_t *ret) { + const void *a; + size_t sz; + int r; + + assert(m); + + r = sd_bus_message_read_array(m, 'y', &a, &sz); + if (r < 0) + return r; + + switch (sz) { + case 0: + if (ret) + *ret = SD_ID128_NULL; + return 0; + + case sizeof(sd_id128_t): + if (ret) + memcpy(ret, a, sz); + return !memeqzero(a, sz); /* This mimics sd_id128_is_null(), but ret may be NULL, + * and a may be misaligned, so use memeqzero() here. */ + + default: + return -EINVAL; + } +} diff --git a/src/shared/bus-util.h b/src/shared/bus-util.h index 869c639..9c6f01d 100644 --- a/src/shared/bus-util.h +++ b/src/shared/bus-util.h @@ -11,6 +11,7 @@ #include "errno-util.h" #include "macro.h" +#include "pidref.h" #include "runtime-scope.h" #include "set.h" #include "string-util.h" @@ -20,6 +21,7 @@ typedef enum BusTransport { BUS_TRANSPORT_LOCAL, BUS_TRANSPORT_REMOTE, BUS_TRANSPORT_MACHINE, + BUS_TRANSPORT_CAPSULE, _BUS_TRANSPORT_MAX, _BUS_TRANSPORT_INVALID = -EINVAL, } BusTransport; @@ -35,8 +37,12 @@ bool bus_error_is_unknown_service(const sd_bus_error *error); int bus_check_peercred(sd_bus *c); +int bus_set_address_capsule_bus(sd_bus *bus, const char *capsule, int *ret_pin_fd); + int bus_connect_system_systemd(sd_bus **ret_bus); int bus_connect_user_systemd(sd_bus **ret_bus); +int bus_connect_capsule_systemd(const char *capsule, sd_bus **ret_bus); +int bus_connect_capsule_bus(const char *capsule, sd_bus **ret_bus); int bus_connect_transport(BusTransport transport, const char *host, RuntimeScope runtime_scope, sd_bus **bus); int bus_connect_transport_systemd(BusTransport transport, const char *host, RuntimeScope runtime_scope, sd_bus **bus); @@ -73,3 +79,8 @@ extern const struct hash_ops bus_message_hash_ops; int bus_message_append_string_set(sd_bus_message *m, Set *s); int bus_property_get_string_set(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error); + +int bus_creds_get_pidref(sd_bus_creds *c, PidRef *ret); +int bus_query_sender_pidref(sd_bus_message *m, PidRef *ret); + +int bus_message_read_id128(sd_bus_message *m, sd_id128_t *ret); diff --git a/src/shared/bus-wait-for-jobs.c b/src/shared/bus-wait-for-jobs.c index 969c629..e12189f 100644 --- a/src/shared/bus-wait-for-jobs.c +++ b/src/shared/bus-wait-for-jobs.c @@ -1,13 +1,13 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "alloc-util.h" -#include "bus-wait-for-jobs.h" -#include "set.h" -#include "bus-util.h" #include "bus-internal.h" -#include "unit-def.h" +#include "bus-util.h" +#include "bus-wait-for-jobs.h" #include "escape.h" +#include "set.h" #include "strv.h" +#include "unit-def.h" typedef struct BusWaitForJobs { sd_bus *bus; @@ -23,60 +23,56 @@ typedef struct BusWaitForJobs { sd_bus_slot *slot_disconnected; } BusWaitForJobs; +BusWaitForJobs* bus_wait_for_jobs_free(BusWaitForJobs *d) { + if (!d) + return NULL; + + set_free(d->jobs); + + sd_bus_slot_unref(d->slot_disconnected); + sd_bus_slot_unref(d->slot_job_removed); + + sd_bus_unref(d->bus); + + free(d->name); + free(d->result); + + return mfree(d); +} + static int match_disconnected(sd_bus_message *m, void *userdata, sd_bus_error *error) { assert(m); - log_error("Warning! D-Bus connection terminated."); + log_warning("D-Bus connection terminated while waiting for jobs."); sd_bus_close(sd_bus_message_get_bus(m)); return 0; } static int match_job_removed(sd_bus_message *m, void *userdata, sd_bus_error *error) { - const char *path, *unit, *result; BusWaitForJobs *d = ASSERT_PTR(userdata); - uint32_t id; - char *found; + _cleanup_free_ char *job_found = NULL; + const char *path, *unit, *result; int r; assert(m); - r = sd_bus_message_read(m, "uoss", &id, &path, &unit, &result); + r = sd_bus_message_read(m, "uoss", /* id = */ NULL, &path, &unit, &result); if (r < 0) { bus_log_parse_error(r); return 0; } - found = set_remove(d->jobs, (char*) path); - if (!found) + job_found = set_remove(d->jobs, (char*) path); + if (!job_found) return 0; - free(found); - - (void) free_and_strdup(&d->result, empty_to_null(result)); - (void) free_and_strdup(&d->name, empty_to_null(unit)); + (void) free_and_strdup(&d->result, empty_to_null(result)); return 0; } -BusWaitForJobs* bus_wait_for_jobs_free(BusWaitForJobs *d) { - if (!d) - return NULL; - - set_free(d->jobs); - - sd_bus_slot_unref(d->slot_disconnected); - sd_bus_slot_unref(d->slot_job_removed); - - sd_bus_unref(d->bus); - - free(d->name); - free(d->result); - - return mfree(d); -} - int bus_wait_for_jobs_new(sd_bus *bus, BusWaitForJobs **ret) { _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *d = NULL; int r; @@ -92,9 +88,8 @@ int bus_wait_for_jobs_new(sd_bus *bus, BusWaitForJobs **ret) { .bus = sd_bus_ref(bus), }; - /* When we are a bus client we match by sender. Direct - * connections OTOH have no initialized sender field, and - * hence we ignore the sender then */ + /* When we are a bus client we match by sender. Direct connections OTOH have no initialized sender + * field, and hence we ignore the sender then */ r = sd_bus_match_signal_async( bus, &d->slot_job_removed, @@ -138,12 +133,12 @@ static int bus_process_wait(sd_bus *bus) { } } -static int bus_job_get_service_result(BusWaitForJobs *d, char **result) { +static int bus_job_get_service_result(BusWaitForJobs *d, char **ret) { _cleanup_free_ char *dbus_path = NULL; assert(d); assert(d->name); - assert(result); + assert(ret); if (!endswith(d->name, ".service")) return -EINVAL; @@ -158,67 +153,57 @@ static int bus_job_get_service_result(BusWaitForJobs *d, char **result) { "org.freedesktop.systemd1.Service", "Result", NULL, - result); + ret); } static void log_job_error_with_service_result(const char* service, const char *result, const char* const* extra_args) { - _cleanup_free_ char *service_shell_quoted = NULL; - const char *systemctl = "systemctl", *journalctl = "journalctl"; static const struct { const char *result, *explanation; } explanations[] = { - { "resources", "of unavailable resources or another system error" }, + { "resources", "of unavailable resources or another system error" }, { "protocol", "the service did not take the steps required by its unit configuration" }, - { "timeout", "a timeout was exceeded" }, - { "exit-code", "the control process exited with error code" }, - { "signal", "a fatal signal was delivered to the control process" }, + { "timeout", "a timeout was exceeded" }, + { "exit-code", "the control process exited with error code" }, + { "signal", "a fatal signal was delivered to the control process" }, { "core-dump", "a fatal signal was delivered causing the control process to dump core" }, - { "watchdog", "the service failed to send watchdog ping" }, - { "start-limit", "start of the service was attempted too often" } + { "watchdog", "the service failed to send watchdog ping" }, + { "start-limit", "start of the service was attempted too often" }, }; + _cleanup_free_ char *service_shell_quoted = NULL; + const char *systemctl = "systemctl", *journalctl = "journalctl"; + assert(service); service_shell_quoted = shell_maybe_quote(service, 0); - if (!strv_isempty((char**) extra_args)) { + if (!strv_isempty((char* const*) extra_args)) { _cleanup_free_ char *t = NULL; - t = strv_join((char**) extra_args, " "); + t = strv_join((char* const*) extra_args, " "); systemctl = strjoina("systemctl ", t ?: "<args>"); journalctl = strjoina("journalctl ", t ?: "<args>"); } - if (!isempty(result)) { - size_t i; - - for (i = 0; i < ELEMENTSOF(explanations); ++i) - if (streq(result, explanations[i].result)) - break; - - if (i < ELEMENTSOF(explanations)) { - log_error("Job for %s failed because %s.\n" - "See \"%s status %s\" and \"%s -xeu %s\" for details.\n", - service, - explanations[i].explanation, - systemctl, - service_shell_quoted ?: "<service>", - journalctl, - service_shell_quoted ?: "<service>"); - goto finish; - } - } + if (!isempty(result)) + FOREACH_ELEMENT(i, explanations) + if (streq(result, i->result)) { + log_error("Job for %s failed because %s.\n" + "See \"%s status %s\" and \"%s -xeu %s\" for details.\n", + service, i->explanation, + systemctl, service_shell_quoted ?: "<service>", + journalctl, service_shell_quoted ?: "<service>"); + goto extra; + } log_error("Job for %s failed.\n" "See \"%s status %s\" and \"%s -xeu %s\" for details.\n", service, - systemctl, - service_shell_quoted ?: "<service>", - journalctl, - service_shell_quoted ?: "<service>"); + systemctl, service_shell_quoted ?: "<service>", + journalctl, service_shell_quoted ?: "<service>"); -finish: +extra: /* For some results maybe additional explanation is required */ if (streq_ptr(result, "start-limit")) log_info("To force a start use \"%1$s reset-failed %2$s\"\n" @@ -227,42 +212,56 @@ finish: service_shell_quoted ?: "<service>"); } -static int check_wait_response(BusWaitForJobs *d, bool quiet, const char* const* extra_args) { +static int check_wait_response(BusWaitForJobs *d, WaitJobsFlags flags, const char* const* extra_args) { + int r; + assert(d); assert(d->name); assert(d->result); - if (!quiet) { + if (streq(d->result, "done")) { + if (FLAGS_SET(flags, BUS_WAIT_JOBS_LOG_SUCCESS)) + log_info("Job for %s finished.", d->name); + + return 0; + } else if (streq(d->result, "skipped")) { + if (FLAGS_SET(flags, BUS_WAIT_JOBS_LOG_SUCCESS)) + log_info("Job for %s was skipped.", d->name); + + return 0; + } + + if (FLAGS_SET(flags, BUS_WAIT_JOBS_LOG_ERROR)) { if (streq(d->result, "canceled")) - log_error("Job for %s canceled.", strna(d->name)); + log_error("Job for %s canceled.", d->name); else if (streq(d->result, "timeout")) - log_error("Job for %s timed out.", strna(d->name)); + log_error("Job for %s timed out.", d->name); else if (streq(d->result, "dependency")) - log_error("A dependency job for %s failed. See 'journalctl -xe' for details.", strna(d->name)); + log_error("A dependency job for %s failed. See 'journalctl -xe' for details.", d->name); else if (streq(d->result, "invalid")) - log_error("%s is not active, cannot reload.", strna(d->name)); + log_error("%s is not active, cannot reload.", d->name); else if (streq(d->result, "assert")) - log_error("Assertion failed on job for %s.", strna(d->name)); + log_error("Assertion failed on job for %s.", d->name); else if (streq(d->result, "unsupported")) - log_error("Operation on or unit type of %s not supported on this system.", strna(d->name)); + log_error("Operation on or unit type of %s not supported on this system.", d->name); else if (streq(d->result, "collected")) - log_error("Queued job for %s was garbage collected.", strna(d->name)); + log_error("Queued job for %s was garbage collected.", d->name); else if (streq(d->result, "once")) - log_error("Unit %s was started already once and can't be started again.", strna(d->name)); - else if (!STR_IN_SET(d->result, "done", "skipped")) { - - if (d->name && endswith(d->name, ".service")) { - _cleanup_free_ char *result = NULL; - int q; - - q = bus_job_get_service_result(d, &result); - if (q < 0) - log_debug_errno(q, "Failed to get Result property of unit %s: %m", d->name); - - log_job_error_with_service_result(d->name, result, extra_args); - } else - log_error("Job failed. See \"journalctl -xe\" for details."); - } + log_error("Unit %s was started already once and can't be started again.", d->name); + else if (streq(d->result, "frozen")) + log_error("Cannot perform operation on frozen unit %s.", d->name); + else if (endswith(d->name, ".service")) { + /* Job result is unknown. For services, let's also try Result property. */ + _cleanup_free_ char *result = NULL; + + r = bus_job_get_service_result(d, &result); + if (r < 0) + log_debug_errno(r, "Failed to get Result property of unit %s, ignoring: %m", + d->name); + + log_job_error_with_service_result(d->name, result, extra_args); + } else /* Otherwise we just show a generic message. */ + log_error("Job failed. See \"journalctl -xe\" for details."); } if (STR_IN_SET(d->result, "canceled", "collected")) @@ -279,14 +278,15 @@ static int check_wait_response(BusWaitForJobs *d, bool quiet, const char* const* return -EOPNOTSUPP; else if (streq(d->result, "once")) return -ESTALE; - else if (STR_IN_SET(d->result, "done", "skipped")) - return 0; + else if (streq(d->result, "frozen")) + return -EDEADLK; - return log_debug_errno(SYNTHETIC_ERRNO(EIO), - "Unexpected job result, assuming server side newer than us: %s", d->result); + return log_debug_errno(SYNTHETIC_ERRNO(ENOMEDIUM), + "Unexpected job result '%s' for unit '%s', assuming server side newer than us.", + d->result, d->name); } -int bus_wait_for_jobs(BusWaitForJobs *d, bool quiet, const char* const* extra_args) { +int bus_wait_for_jobs(BusWaitForJobs *d, WaitJobsFlags flags, const char* const* extra_args) { int r = 0; assert(d); @@ -299,14 +299,12 @@ int bus_wait_for_jobs(BusWaitForJobs *d, bool quiet, const char* const* extra_ar return log_error_errno(q, "Failed to wait for response: %m"); if (d->name && d->result) { - q = check_wait_response(d, quiet, extra_args); - /* Return the first error as it is most likely to be - * meaningful. */ - if (q < 0 && r == 0) - r = q; + q = check_wait_response(d, flags, extra_args); + /* Return the first error as it is most likely to be meaningful. */ + RET_GATHER(r, q); log_full_errno_zerook(LOG_DEBUG, q, - "Got result %s/%m for job %s", d->result, d->name); + "Got result %s/%m for job %s.", d->result, d->name); } d->name = mfree(d->name); @@ -322,12 +320,12 @@ int bus_wait_for_jobs_add(BusWaitForJobs *d, const char *path) { return set_put_strdup(&d->jobs, path); } -int bus_wait_for_jobs_one(BusWaitForJobs *d, const char *path, bool quiet, const char* const* extra_args) { +int bus_wait_for_jobs_one(BusWaitForJobs *d, const char *path, WaitJobsFlags flags, const char* const* extra_args) { int r; r = bus_wait_for_jobs_add(d, path); if (r < 0) return log_oom(); - return bus_wait_for_jobs(d, quiet, extra_args); + return bus_wait_for_jobs(d, flags, extra_args); } diff --git a/src/shared/bus-wait-for-jobs.h b/src/shared/bus-wait-for-jobs.h index 5acf8b9..2336b13 100644 --- a/src/shared/bus-wait-for-jobs.h +++ b/src/shared/bus-wait-for-jobs.h @@ -7,10 +7,15 @@ typedef struct BusWaitForJobs BusWaitForJobs; -int bus_wait_for_jobs_new(sd_bus *bus, BusWaitForJobs **ret); -BusWaitForJobs* bus_wait_for_jobs_free(BusWaitForJobs *d); -int bus_wait_for_jobs_add(BusWaitForJobs *d, const char *path); -int bus_wait_for_jobs(BusWaitForJobs *d, bool quiet, const char* const* extra_args); -int bus_wait_for_jobs_one(BusWaitForJobs *d, const char *path, bool quiet, const char* const* extra_args); +typedef enum WaitJobsFlags { + BUS_WAIT_JOBS_LOG_ERROR = 1 << 0, + BUS_WAIT_JOBS_LOG_SUCCESS = 1 << 1, +} WaitJobsFlags; +BusWaitForJobs* bus_wait_for_jobs_free(BusWaitForJobs *d); DEFINE_TRIVIAL_CLEANUP_FUNC(BusWaitForJobs*, bus_wait_for_jobs_free); + +int bus_wait_for_jobs_new(sd_bus *bus, BusWaitForJobs **ret); +int bus_wait_for_jobs_add(BusWaitForJobs *d, const char *path); +int bus_wait_for_jobs(BusWaitForJobs *d, WaitJobsFlags flags, const char* const* extra_args); +int bus_wait_for_jobs_one(BusWaitForJobs *d, const char *path, WaitJobsFlags flags, const char* const* extra_args); diff --git a/src/shared/bus-wait-for-units.c b/src/shared/bus-wait-for-units.c index 0dd2a29..6ccf822 100644 --- a/src/shared/bus-wait-for-units.c +++ b/src/shared/bus-wait-for-units.c @@ -18,7 +18,7 @@ typedef struct WaitForItem { sd_bus_slot *slot_get_all; sd_bus_slot *slot_properties_changed; - bus_wait_for_units_unit_callback unit_callback; + bus_wait_for_units_unit_callback_t unit_callback; void *userdata; char *active_state; @@ -32,11 +32,6 @@ typedef struct BusWaitForUnits { Hashmap *items; - bus_wait_for_units_ready_callback ready_callback; - void *userdata; - - WaitForItem *current; - BusWaitForUnitsState state; bool has_failed:1; } BusWaitForUnits; @@ -63,10 +58,7 @@ static WaitForItem *wait_for_item_free(WaitForItem *item) { log_debug_errno(r, "Failed to drop reference to unit %s, ignoring: %m", item->bus_path); } - assert_se(hashmap_remove(item->parent->items, item->bus_path) == item); - - if (item->parent->current == item) - item->parent->current = NULL; + assert_se(hashmap_remove_value(item->parent->items, item->bus_path, item)); } sd_bus_slot_unref(item->slot_properties_changed); @@ -82,8 +74,6 @@ static WaitForItem *wait_for_item_free(WaitForItem *item) { DEFINE_TRIVIAL_CLEANUP_FUNC(WaitForItem*, wait_for_item_free); static void call_unit_callback_and_wait(BusWaitForUnits *d, WaitForItem *item, bool good) { - d->current = item; - if (item->unit_callback) item->unit_callback(d, item->bus_path, good, item->userdata); @@ -109,14 +99,10 @@ static int match_disconnected(sd_bus_message *m, void *userdata, sd_bus_error *e assert(m); - log_error("Warning! D-Bus connection terminated."); + log_warning("D-Bus connection terminated while waiting for unit."); bus_wait_for_units_clear(d); - - if (d->ready_callback) - d->ready_callback(d, false, d->userdata); - else /* If no ready callback is specified close the connection so that the event loop exits */ - sd_bus_close(sd_bus_message_get_bus(m)); + sd_bus_close(sd_bus_message_get_bus(m)); return 0; } @@ -172,13 +158,6 @@ static bool bus_wait_for_units_is_ready(BusWaitForUnits *d) { return hashmap_isempty(d->items); } -void bus_wait_for_units_set_ready_callback(BusWaitForUnits *d, bus_wait_for_units_ready_callback callback, void *userdata) { - assert(d); - - d->ready_callback = callback; - d->userdata = userdata; -} - static void bus_wait_for_units_check_ready(BusWaitForUnits *d) { assert(d); @@ -186,9 +165,6 @@ static void bus_wait_for_units_check_ready(BusWaitForUnits *d) { return; d->state = d->has_failed ? BUS_WAIT_FAILURE : BUS_WAIT_SUCCESS; - - if (d->ready_callback) - d->ready_callback(d, d->state, d->userdata); } static void wait_for_item_check_ready(WaitForItem *item) { @@ -221,32 +197,26 @@ static void wait_for_item_check_ready(WaitForItem *item) { bus_wait_for_units_check_ready(d); } -static int property_map_job( +static int property_map_job_id( sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) { - WaitForItem *item = ASSERT_PTR(userdata); - const char *path; - uint32_t id; - int r; + uint32_t *job_id = ASSERT_PTR(userdata); - r = sd_bus_message_read(m, "(uo)", &id, &path); - if (r < 0) - return r; + assert(m); - item->job_id = id; - return 0; + return sd_bus_message_read(m, "(uo)", job_id, /* path = */ NULL); } static int wait_for_item_parse_properties(WaitForItem *item, sd_bus_message *m) { static const struct bus_properties_map map[] = { - { "ActiveState", "s", NULL, offsetof(WaitForItem, active_state) }, - { "Job", "(uo)", property_map_job, 0 }, - { "CleanResult", "s", NULL, offsetof(WaitForItem, clean_result) }, + { "ActiveState", "s", NULL, offsetof(WaitForItem, active_state) }, + { "Job", "(uo)", property_map_job_id, offsetof(WaitForItem, job_id) }, + { "CleanResult", "s", NULL, offsetof(WaitForItem, clean_result) }, {} }; @@ -315,20 +285,23 @@ int bus_wait_for_units_add_unit( BusWaitForUnits *d, const char *unit, BusWaitForUnitsFlags flags, - bus_wait_for_units_unit_callback callback, + bus_wait_for_units_unit_callback_t callback, void *userdata) { _cleanup_(wait_for_item_freep) WaitForItem *item = NULL; + _cleanup_free_ char *bus_path = NULL; int r; assert(d); assert(unit); + assert((flags & _BUS_WAIT_FOR_TARGET) != 0); - assert(flags != 0); + bus_path = unit_dbus_path_from_name(unit); + if (!bus_path) + return -ENOMEM; - r = hashmap_ensure_allocated(&d->items, &string_hash_ops); - if (r < 0) - return r; + if (hashmap_contains(d->items, bus_path)) + return 0; item = new(WaitForItem, 1); if (!item) @@ -336,15 +309,12 @@ int bus_wait_for_units_add_unit( *item = (WaitForItem) { .flags = flags, - .bus_path = unit_dbus_path_from_name(unit), + .bus_path = TAKE_PTR(bus_path), .unit_callback = callback, .userdata = userdata, .job_id = UINT32_MAX, }; - if (!item->bus_path) - return -ENOMEM; - if (!FLAGS_SET(item->flags, BUS_WAIT_REFFED)) { r = sd_bus_call_method_async( d->bus, @@ -388,14 +358,16 @@ int bus_wait_for_units_add_unit( if (r < 0) return log_debug_errno(r, "Failed to request properties of unit %s: %m", unit); - r = hashmap_put(d->items, item->bus_path, item); + r = hashmap_ensure_put(&d->items, &string_hash_ops, item->bus_path, item); if (r < 0) return r; + assert(r > 0); d->state = BUS_WAIT_RUNNING; item->parent = d; TAKE_PTR(item); - return 0; + + return 1; } int bus_wait_for_units_run(BusWaitForUnits *d) { diff --git a/src/shared/bus-wait-for-units.h b/src/shared/bus-wait-for-units.h index 2623e72..a4a4dc4 100644 --- a/src/shared/bus-wait-for-units.h +++ b/src/shared/bus-wait-for-units.h @@ -8,7 +8,7 @@ typedef struct BusWaitForUnits BusWaitForUnits; typedef enum BusWaitForUnitsState { BUS_WAIT_SUCCESS, /* Nothing to wait for anymore and nothing failed */ - BUS_WAIT_FAILURE, /* dito, but something failed */ + BUS_WAIT_FAILURE, /* ditto, but something failed */ BUS_WAIT_RUNNING, /* Still something to wait for */ _BUS_WAIT_FOR_UNITS_STATE_MAX, _BUS_WAIT_FOR_UNITS_STATE_INVALID = -EINVAL, @@ -19,17 +19,22 @@ typedef enum BusWaitForUnitsFlags { BUS_WAIT_FOR_INACTIVE = 1 << 1, /* Wait until the unit is back in inactive or dead state */ BUS_WAIT_NO_JOB = 1 << 2, /* Wait until there's no more job pending */ BUS_WAIT_REFFED = 1 << 3, /* The unit is already reffed with RefUnit() */ + _BUS_WAIT_FOR_TARGET = BUS_WAIT_FOR_MAINTENANCE_END|BUS_WAIT_FOR_INACTIVE|BUS_WAIT_NO_JOB, } BusWaitForUnitsFlags; -typedef void (*bus_wait_for_units_ready_callback)(BusWaitForUnits *d, BusWaitForUnitsState state, void *userdata); -typedef void (*bus_wait_for_units_unit_callback)(BusWaitForUnits *d, const char *unit_path, bool good, void *userdata); +typedef void (*bus_wait_for_units_unit_callback_t)(BusWaitForUnits *d, const char *unit_path, bool good, void *userdata); int bus_wait_for_units_new(sd_bus *bus, BusWaitForUnits **ret); + BusWaitForUnits* bus_wait_for_units_free(BusWaitForUnits *d); +DEFINE_TRIVIAL_CLEANUP_FUNC(BusWaitForUnits*, bus_wait_for_units_free); -BusWaitForUnitsState bus_wait_for_units_state(BusWaitForUnits *d); -void bus_wait_for_units_set_ready_callback(BusWaitForUnits *d, bus_wait_for_units_ready_callback callback, void *userdata); -int bus_wait_for_units_add_unit(BusWaitForUnits *d, const char *unit, BusWaitForUnitsFlags flags, bus_wait_for_units_unit_callback callback, void *userdata); -int bus_wait_for_units_run(BusWaitForUnits *d); +int bus_wait_for_units_add_unit( + BusWaitForUnits *d, + const char *unit, + BusWaitForUnitsFlags flags, + bus_wait_for_units_unit_callback_t callback, + void *userdata); -DEFINE_TRIVIAL_CLEANUP_FUNC(BusWaitForUnits*, bus_wait_for_units_free); +int bus_wait_for_units_run(BusWaitForUnits *d); +BusWaitForUnitsState bus_wait_for_units_state(BusWaitForUnits *d); diff --git a/src/shared/capsule-util.c b/src/shared/capsule-util.c new file mode 100644 index 0000000..3689a78 --- /dev/null +++ b/src/shared/capsule-util.c @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "capsule-util.h" +#include "path-util.h" +#include "user-util.h" + +int capsule_name_is_valid(const char *name) { + + if (!filename_is_valid(name)) + return false; + + _cleanup_free_ char *prefixed = strjoin("c-", name); + if (!prefixed) + return -ENOMEM; + + return valid_user_group_name(prefixed, /* flags= */ 0); +} diff --git a/src/shared/capsule-util.h b/src/shared/capsule-util.h new file mode 100644 index 0000000..437153b --- /dev/null +++ b/src/shared/capsule-util.h @@ -0,0 +1,4 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +int capsule_name_is_valid(const char *name); diff --git a/src/shared/cgroup-setup.c b/src/shared/cgroup-setup.c index 934a16e..093b6d0 100644 --- a/src/shared/cgroup-setup.c +++ b/src/shared/cgroup-setup.c @@ -51,7 +51,7 @@ static int cg_any_controller_used_for_v1(void) { continue; const char *p = *line; - r = extract_many_words(&p, NULL, 0, &name, &hierarchy_id, &num, &enabled, NULL); + r = extract_many_words(&p, NULL, 0, &name, &hierarchy_id, &num, &enabled); if (r < 0) return log_debug_errno(r, "Error parsing /proc/cgroups line, ignoring: %m"); else if (r < 4) { @@ -81,9 +81,6 @@ static int cg_any_controller_used_for_v1(void) { bool cg_is_unified_wanted(void) { static thread_local int wanted = -1; - bool b; - const bool is_default = DEFAULT_HIERARCHY == CGROUP_UNIFIED_ALL; - _cleanup_free_ char *c = NULL; int r; /* If we have a cached value, return that. */ @@ -96,21 +93,20 @@ bool cg_is_unified_wanted(void) { return (wanted = r >= CGROUP_UNIFIED_ALL); /* If we were explicitly passed systemd.unified_cgroup_hierarchy, respect that. */ + bool b; r = proc_cmdline_get_bool("systemd.unified_cgroup_hierarchy", /* flags = */ 0, &b); if (r > 0) return (wanted = b); /* If we passed cgroup_no_v1=all with no other instructions, it seems highly unlikely that we want to * use hybrid or legacy hierarchy. */ + _cleanup_free_ char *c = NULL; r = proc_cmdline_get_key("cgroup_no_v1", 0, &c); if (r > 0 && streq_ptr(c, "all")) return (wanted = true); /* If any controller is in use as v1, don't use unified. */ - if (cg_any_controller_used_for_v1() > 0) - return (wanted = false); - - return (wanted = is_default); + return (wanted = (cg_any_controller_used_for_v1() <= 0)); } bool cg_is_legacy_wanted(void) { @@ -132,10 +128,6 @@ bool cg_is_legacy_wanted(void) { bool cg_is_hybrid_wanted(void) { static thread_local int wanted = -1; int r; - bool b; - const bool is_default = DEFAULT_HIERARCHY >= CGROUP_UNIFIED_SYSTEMD; - /* We default to true if the default is "hybrid", obviously, but also when the default is "unified", - * because if we get called, it means that unified hierarchy was not mounted. */ /* If we have a cached value, return that. */ if (wanted >= 0) @@ -146,12 +138,33 @@ bool cg_is_hybrid_wanted(void) { return (wanted = false); /* Otherwise, let's see what the kernel command line has to say. Since checking is expensive, cache - * a non-error result. */ + * a non-error result. + * The meaning of the kernel option is reversed wrt. to the return value of this function, hence the + * negation. */ + bool b; r = proc_cmdline_get_bool("systemd.legacy_systemd_cgroup_controller", /* flags = */ 0, &b); + if (r > 0) + return (wanted = !b); - /* The meaning of the kernel option is reversed wrt. to the return value of this function, hence the - * negation. */ - return (wanted = r > 0 ? !b : is_default); + /* The default hierarchy is "unified". But if this is reached, it means that unified hierarchy was + * not mounted, so return true too. */ + return (wanted = true); +} + +bool cg_is_legacy_force_enabled(void) { + bool force; + + if (!cg_is_legacy_wanted()) + return false; + + /* If in container, we have to follow host's cgroup hierarchy. */ + if (detect_container() > 0) + return true; + + if (proc_cmdline_get_bool("SYSTEMD_CGROUP_ENABLE_LEGACY_FORCE", /* flags = */ 0, &force) < 0) + return false; + + return force; } int cg_weight_parse(const char *s, uint64_t *ret) { @@ -371,6 +384,20 @@ int cg_attach(const char *controller, const char *path, pid_t pid) { return 0; } +int cg_fd_attach(int fd, pid_t pid) { + char c[DECIMAL_STR_MAX(pid_t) + 2]; + + assert(fd >= 0); + assert(pid >= 0); + + if (pid == 0) + pid = getpid_cached(); + + xsprintf(c, PID_FMT "\n", pid); + + return write_string_file_at(fd, "cgroup.procs", c, WRITE_STRING_FILE_DISABLE_BUFFER); +} + int cg_attach_fallback(const char *controller, const char *path, pid_t pid) { int r; @@ -571,75 +598,56 @@ int cg_migrate( bool done = false; _cleanup_set_free_ Set *s = NULL; int r, ret = 0; - pid_t my_pid; assert(cfrom); assert(pfrom); assert(cto); assert(pto); - s = set_new(NULL); - if (!s) - return -ENOMEM; - - my_pid = getpid_cached(); - do { _cleanup_fclose_ FILE *f = NULL; - pid_t pid = 0; + pid_t pid; + done = true; r = cg_enumerate_processes(cfrom, pfrom, &f); - if (r < 0) { - if (ret >= 0 && r != -ENOENT) - return r; - - return ret; - } + if (r < 0) + return RET_GATHER(ret, r); - while ((r = cg_read_pid(f, &pid)) > 0) { + while ((r = cg_read_pid(f, &pid, flags)) > 0) { + /* Throw an error if unmappable PIDs are in output, we can't migrate those. */ + if (pid == 0) + return -EREMOTE; - /* This might do weird stuff if we aren't a - * single-threaded program. However, we - * luckily know we are not */ - if ((flags & CGROUP_IGNORE_SELF) && pid == my_pid) + /* This might do weird stuff if we aren't a single-threaded program. However, we + * luckily know we are. */ + if (FLAGS_SET(flags, CGROUP_IGNORE_SELF) && pid == getpid_cached()) continue; - if (set_get(s, PID_TO_PTR(pid)) == PID_TO_PTR(pid)) + if (set_contains(s, PID_TO_PTR(pid))) continue; - /* Ignore kernel threads. Since they can only - * exist in the root cgroup, we only check for - * them there. */ - if (cfrom && - empty_or_root(pfrom) && + /* Ignore kernel threads. Since they can only exist in the root cgroup, we only + * check for them there. */ + if (cfrom && empty_or_root(pfrom) && pid_is_kernel_thread(pid) > 0) continue; r = cg_attach(cto, pto, pid); if (r < 0) { - if (ret >= 0 && r != -ESRCH) - ret = r; + if (r != -ESRCH) + RET_GATHER(ret, r); } else if (ret == 0) ret = 1; done = false; - r = set_put(s, PID_TO_PTR(pid)); - if (r < 0) { - if (ret >= 0) - return r; - - return ret; - } - } - - if (r < 0) { - if (ret >= 0) - return r; - - return ret; + r = set_ensure_put(&s, /* hash_ops = */ NULL, PID_TO_PTR(pid)); + if (r < 0) + return RET_GATHER(ret, r); } + if (r < 0) + return RET_GATHER(ret, r); } while (!done); return ret; diff --git a/src/shared/cgroup-setup.h b/src/shared/cgroup-setup.h index 1b6f071..283ab67 100644 --- a/src/shared/cgroup-setup.h +++ b/src/shared/cgroup-setup.h @@ -10,6 +10,7 @@ bool cg_is_unified_wanted(void); bool cg_is_legacy_wanted(void); bool cg_is_hybrid_wanted(void); +bool cg_is_legacy_force_enabled(void); int cg_weight_parse(const char *s, uint64_t *ret); int cg_cpu_weight_parse(const char *s, uint64_t *ret); @@ -20,6 +21,7 @@ int cg_trim(const char *controller, const char *path, bool delete_root); int cg_create(const char *controller, const char *path); int cg_attach(const char *controller, const char *path, pid_t pid); +int cg_fd_attach(int fd, pid_t pid); int cg_attach_fallback(const char *controller, const char *path, pid_t pid); int cg_create_and_attach(const char *controller, const char *path, pid_t pid); diff --git a/src/shared/cgroup-show.c b/src/shared/cgroup-show.c index c2ee1c5..8717731 100644 --- a/src/shared/cgroup-show.c +++ b/src/shared/cgroup-show.c @@ -108,7 +108,7 @@ static int show_cgroup_one_by_path( * From https://docs.kernel.org/admin-guide/cgroup-v2.html#threads, * “cgroup.procs” in a threaded domain cgroup contains the PIDs of all processes in * the subtree and is not readable in the subtree proper. */ - r = cg_read_pid(f, &pid); + r = cg_read_pid(f, &pid, /* flags = */ 0); if (IN_SET(r, 0, -EOPNOTSUPP)) break; if (r < 0) @@ -150,18 +150,9 @@ static int show_cgroup_name( delegate = r > 0; if (FLAGS_SET(flags, OUTPUT_CGROUP_ID)) { - cg_file_handle fh = CG_FILE_HANDLE_INIT; - int mnt_id = -1; - - if (name_to_handle_at( - fd, - "", - &fh.file_handle, - &mnt_id, - AT_EMPTY_PATH) < 0) - log_debug_errno(errno, "Failed to determine cgroup ID of %s, ignoring: %m", path); - else - cgroupid = CG_FILE_HANDLE_CGROUPID(fh); + r = cg_fd_get_cgroupid(fd, &cgroupid); + if (r < 0) + log_debug_errno(r, "Failed to determine cgroup ID of %s, ignoring: %m", path); } r = path_extract_filename(path, &b); diff --git a/src/shared/clean-ipc.c b/src/shared/clean-ipc.c index bbb343f..1e90cc2 100644 --- a/src/shared/clean-ipc.c +++ b/src/shared/clean-ipc.c @@ -58,7 +58,7 @@ static int clean_sysvipc_shm(uid_t delete_uid, gid_t delete_gid, bool rm) { r = read_line(f, LONG_LINE_MAX, &line); if (r < 0) - return log_warning_errno(errno, "Failed to read /proc/sysvipc/shm: %m"); + return log_warning_errno(r, "Failed to read /proc/sysvipc/shm: %m"); if (r == 0) break; diff --git a/src/shared/clock-util.c b/src/shared/clock-util.c index b0cbe30..37d0232 100644 --- a/src/shared/clock-util.c +++ b/src/shared/clock-util.c @@ -27,10 +27,11 @@ int clock_get_hwclock(struct tm *tm) { if (fd < 0) return -errno; - /* This leaves the timezone fields of struct tm - * uninitialized! */ + /* This leaves the timezone fields of struct tm uninitialized! */ if (ioctl(fd, RTC_RD_TIME, tm) < 0) - return -errno; + /* Some drivers return -EINVAL in case the time could not be kept, i.e. power loss + * happened. Let's turn that into a clearly recognizable error */ + return errno == EINVAL ? -ENODATA : -errno; /* We don't know daylight saving, so we reset this in order not * to confuse mktime(). */ diff --git a/src/shared/color-util.c b/src/shared/color-util.c new file mode 100644 index 0000000..9d714c0 --- /dev/null +++ b/src/shared/color-util.c @@ -0,0 +1,80 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <math.h> + +#include "color-util.h" +#include "macro.h" + +void rgb_to_hsv(double r, double g, double b, + double *ret_h, double *ret_s, double *ret_v) { + + assert(r >= 0 && r <= 1); + assert(g >= 0 && g <= 1); + assert(b >= 0 && b <= 1); + + double max_color = fmax(r, fmax(g, b)); + double min_color = fmin(r, fmin(g, b)); + double delta = max_color - min_color; + + if (ret_v) + *ret_v = max_color * 100.0; + + if (max_color <= 0) { + if (ret_s) + *ret_s = 0; + if (ret_h) + *ret_h = NAN; + return; + } + + if (ret_s) + *ret_s = delta / max_color * 100.0; + + if (ret_h) { + if (delta > 0) { + if (r >= max_color) + *ret_h = 60 * fmod((g - b) / delta, 6); + else if (g >= max_color) + *ret_h = 60 * (((b - r) / delta) + 2); + else if (b >= max_color) + *ret_h = 60 * (((r - g) / delta) + 4); + + *ret_h = fmod(*ret_h, 360); + } else + *ret_h = NAN; + } +} + +void hsv_to_rgb(double h, double s, double v, + uint8_t* ret_r, uint8_t *ret_g, uint8_t *ret_b) { + + double c, x, m, r, g, b; + + assert(s >= 0 && s <= 100); + assert(v >= 0 && v <= 100); + assert(ret_r); + assert(ret_g); + assert(ret_b); + + h = fmod(h, 360); + c = (s / 100.0) * (v / 100.0); + x = c * (1 - fabs(fmod(h / 60.0, 2) - 1)); + m = (v / 100) - c; + + if (h >= 0 && h < 60) + r = c, g = x, b = 0.0; + else if (h >= 60 && h < 120) + r = x, g = c, b = 0.0; + else if (h >= 120 && h < 180) + r = 0.0, g = c, b = x; + else if (h >= 180 && h < 240) + r = 0.0, g = x, b = c; + else if (h >= 240 && h < 300) + r = x, g = 0.0, b = c; + else + r = c, g = 0.0, b = x; + + *ret_r = (uint8_t) ((r + m) * 255); + *ret_g = (uint8_t) ((g + m) * 255); + *ret_b = (uint8_t) ((b + m) * 255); +} diff --git a/src/shared/color-util.h b/src/shared/color-util.h new file mode 100644 index 0000000..a4ae9eb --- /dev/null +++ b/src/shared/color-util.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include <inttypes.h> + +void rgb_to_hsv(double r, double g, double b, + double *ret_h, double *ret_s, double *ret_v); + +void hsv_to_rgb( + double h, double s, double v, + uint8_t* ret_r, uint8_t *ret_g, uint8_t *ret_b); diff --git a/src/shared/compare-operator.c b/src/shared/compare-operator.c index 0da28fc..a13db8e 100644 --- a/src/shared/compare-operator.c +++ b/src/shared/compare-operator.c @@ -6,6 +6,7 @@ #include "string-util.h" CompareOperator parse_compare_operator(const char **s, CompareOperatorParseFlags flags) { + static const struct { CompareOperator op; const char *str; @@ -40,19 +41,19 @@ CompareOperator parse_compare_operator(const char **s, CompareOperatorParseFlags * parse_compare_operator() are use on the same string? */ return _COMPARE_OPERATOR_INVALID; - for (size_t i = 0; i < ELEMENTSOF(table); i ++) { + FOREACH_ELEMENT(i, table) { const char *e; - if (table[i].need_mask != 0 && !FLAGS_SET(flags, table[i].need_mask)) + if (i->need_mask != 0 && !FLAGS_SET(flags, i->need_mask)) continue; - e = startswith(*s, table[i].str); + e = startswith(*s, i->str); if (e) { - if (table[i].valid_mask != 0 && !FLAGS_SET(flags, table[i].valid_mask)) + if (i->valid_mask != 0 && !FLAGS_SET(flags, i->valid_mask)) return _COMPARE_OPERATOR_INVALID; *s = e; - return table[i].op; + return i->op; } } diff --git a/src/shared/condition.c b/src/shared/condition.c index d3446e8..b53b2ef 100644 --- a/src/shared/condition.c +++ b/src/shared/condition.c @@ -59,7 +59,7 @@ #include "string-util.h" #include "tomoyo-util.h" #include "tpm2-util.h" -#include "uid-alloc-range.h" +#include "uid-classification.h" #include "user-util.h" #include "virt.h" @@ -141,7 +141,6 @@ static int condition_test_kernel_command_line(Condition *c, char **env) { } static int condition_test_credential(Condition *c, char **env) { - int (*gd)(const char **ret); int r; assert(c); @@ -155,7 +154,8 @@ static int condition_test_credential(Condition *c, char **env) { if (!credential_name_valid(c->parameter)) /* credentials with invalid names do not exist */ return false; - FOREACH_POINTER(gd, get_credentials_dir, get_encrypted_credentials_dir) { + int (*gd)(const char **ret); + FOREACH_ARGUMENT(gd, get_credentials_dir, get_encrypted_credentials_dir) { _cleanup_free_ char *j = NULL; const char *cd; @@ -260,13 +260,13 @@ static int condition_test_osrelease(Condition *c, char **env) { /* The os-release spec mandates env-var-like key names */ if (r == 0 || isempty(word) || !env_name_is_valid(key)) return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), - "Failed to parse parameter, key/value format expected: %m"); + "Failed to parse parameter, key/value format expected."); /* Do not allow whitespace after the separator, as that's not a valid os-release format */ operator = parse_compare_operator(&word, COMPARE_ALLOW_FNMATCH|COMPARE_EQUAL_BY_STRING); if (operator < 0 || isempty(word) || strchr(WHITESPACE, *word) != NULL) return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), - "Failed to parse parameter, key/value format expected: %m"); + "Failed to parse parameter, key/value format expected."); r = parse_os_release(NULL, key, &actual_value); if (r < 0) @@ -543,7 +543,7 @@ static int condition_test_firmware_smbios_field(const char *expression) { /* Read actual value from sysfs */ if (!filename_is_valid(field)) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid SMBIOS field name"); + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid SMBIOS field name."); const char *p = strjoina("/sys/class/dmi/id/", field); r = read_virtual_file(p, SIZE_MAX, &actual_value, NULL); @@ -599,7 +599,7 @@ static int condition_test_firmware(Condition *c, char **env) { end = strrchr(arg, ')'); if (!end || *(end + 1) != '\0') - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Malformed ConditionFirmware=%s: %m", c->parameter); + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Malformed ConditionFirmware=%s.", c->parameter); smbios_arg = strndup(arg, end - arg); if (!smbios_arg) @@ -751,9 +751,9 @@ static int condition_test_needs_update(Condition *c, char **env) { assert(c->parameter); assert(c->type == CONDITION_NEEDS_UPDATE); - r = proc_cmdline_get_bool("systemd.condition-needs-update", /* flags = */ 0, &b); + r = proc_cmdline_get_bool("systemd.condition_needs_update", /* flags = */ 0, &b); if (r < 0) - log_debug_errno(r, "Failed to parse systemd.condition-needs-update= kernel command line argument, ignoring: %m"); + log_debug_errno(r, "Failed to parse systemd.condition_needs_update= kernel command line argument, ignoring: %m"); if (r > 0) return b; @@ -931,7 +931,7 @@ static int condition_test_path_is_mount_point(Condition *c, char **env) { assert(c->parameter); assert(c->type == CONDITION_PATH_IS_MOUNT_POINT); - return path_is_mount_point(c->parameter, NULL, AT_SYMLINK_FOLLOW) > 0; + return path_is_mount_point_full(c->parameter, /* root = */ NULL, AT_SYMLINK_FOLLOW) > 0; } static int condition_test_path_is_read_write(Condition *c, char **env) { @@ -1024,7 +1024,7 @@ static int condition_test_psi(Condition *c, char **env) { "io"; p = c->parameter; - r = extract_many_words(&p, ":", 0, &first, &second, NULL); + r = extract_many_words(&p, ":", 0, &first, &second); if (r <= 0) return log_debug_errno(r < 0 ? r : SYNTHETIC_ERRNO(EINVAL), "Failed to parse condition parameter %s: %m", c->parameter); /* If only one parameter is passed, then we look at the global system pressure rather than a specific cgroup. */ @@ -1046,7 +1046,7 @@ static int condition_test_psi(Condition *c, char **env) { slice = strstrip(first); if (!slice) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse condition parameter %s: %m", c->parameter); + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse condition parameter %s.", c->parameter); r = cg_all_unified(); if (r < 0) @@ -1099,7 +1099,7 @@ static int condition_test_psi(Condition *c, char **env) { /* If a value including a specific timespan (in the intervals allowed by the kernel), * parse it, otherwise we assume just a plain percentage that will be checked if it is * smaller or equal to the current pressure average over 5 minutes. */ - r = extract_many_words(&value, "/", 0, &third, &fourth, NULL); + r = extract_many_words(&value, "/", 0, &third, &fourth); if (r <= 0) return log_debug_errno(r < 0 ? r : SYNTHETIC_ERRNO(EINVAL), "Failed to parse condition parameter %s: %m", c->parameter); if (r == 1) @@ -1109,7 +1109,7 @@ static int condition_test_psi(Condition *c, char **env) { timespan = skip_leading_chars(fourth, NULL); if (!timespan) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse condition parameter %s: %m", c->parameter); + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse condition parameter %s.", c->parameter); if (startswith(timespan, "10sec")) current = &pressure.avg10; @@ -1118,12 +1118,12 @@ static int condition_test_psi(Condition *c, char **env) { else if (startswith(timespan, "5min")) current = &pressure.avg300; else - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse condition parameter %s: %m", c->parameter); + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse condition parameter %s.", c->parameter); } value = strstrip(third); if (!value) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse condition parameter %s: %m", c->parameter); + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse condition parameter %s.", c->parameter); r = parse_permyriad(value); if (r < 0) diff --git a/src/shared/conf-parser.c b/src/shared/conf-parser.c index e8ecd9b..fcc45c6 100644 --- a/src/shared/conf-parser.c +++ b/src/shared/conf-parser.c @@ -8,6 +8,7 @@ #include <sys/types.h> #include "alloc-util.h" +#include "chase.h" #include "conf-files.h" #include "conf-parser.h" #include "constants.h" @@ -159,7 +160,11 @@ static int next_assignment( /* Warn about unknown non-extension fields. */ if (!(flags & CONFIG_PARSE_RELAXED) && !startswith(lvalue, "X-")) log_syntax(unit, LOG_WARNING, filename, line, 0, - "Unknown key name '%s' in section '%s', ignoring.", lvalue, section); + "Unknown key '%s'%s%s%s, ignoring.", + lvalue, + section ? " in section [" : "", + strempty(section), + section ? "]" : ""); return 0; } @@ -480,6 +485,7 @@ int hashmap_put_stats_by_path(Hashmap **stats_by_path, const char *path, const s } static int config_parse_many_files( + const char *root, const char* const* conf_files, char **files, const char *sections, @@ -502,19 +508,16 @@ static int config_parse_many_files( } STRV_FOREACH(fn, files) { - _cleanup_free_ struct stat *st_dropin = NULL; _cleanup_fclose_ FILE *f = NULL; - int fd; + _cleanup_free_ char *fname = NULL; - f = fopen(*fn, "re"); - if (!f) { - if (errno == ENOENT) - continue; - - return -errno; - } + r = chase_and_fopen_unlocked(*fn, root, CHASE_AT_RESOLVE_IN_ROOT, "re", &fname, &f); + if (r == -ENOENT) + continue; + if (r < 0) + return r; - fd = fileno(f); + int fd = fileno(f); r = ordered_hashmap_ensure_put(&dropins, &config_file_hash_ops_fclose, *fn, f); if (r < 0) { @@ -527,7 +530,7 @@ static int config_parse_many_files( /* Get inodes for all drop-ins. Later we'll verify if main config is a symlink to or is * symlinked as one of them. If so, we skip reading main config file directly. */ - st_dropin = new(struct stat, 1); + _cleanup_free_ struct stat *st_dropin = new(struct stat, 1); if (!st_dropin) return -ENOMEM; @@ -543,13 +546,11 @@ static int config_parse_many_files( STRV_FOREACH(fn, conf_files) { _cleanup_fclose_ FILE *f = NULL; - f = fopen(*fn, "re"); - if (!f) { - if (errno == ENOENT) - continue; - - return -errno; - } + r = chase_and_fopen_unlocked(*fn, root, CHASE_AT_RESOLVE_IN_ROOT, "re", NULL, &f); + if (r == -ENOENT) + continue; + if (r < 0) + return r; if (inodes) { if (fstat(fileno(f), &st) < 0) @@ -561,7 +562,7 @@ static int config_parse_many_files( } } - r = config_parse(NULL, *fn, f, sections, lookup, table, flags, userdata, &st); + r = config_parse(/* unit= */ NULL, *fn, f, sections, lookup, table, flags, userdata, &st); if (r < 0) return r; assert(r > 0); @@ -580,7 +581,7 @@ static int config_parse_many_files( const char *path_dropin; FILE *f_dropin; ORDERED_HASHMAP_FOREACH_KEY(f_dropin, path_dropin, dropins) { - r = config_parse(NULL, path_dropin, f_dropin, sections, lookup, table, flags, userdata, &st); + r = config_parse(/* unit= */ NULL, path_dropin, f_dropin, sections, lookup, table, flags, userdata, &st); if (r < 0) return r; assert(r > 0); @@ -598,54 +599,6 @@ static int config_parse_many_files( return 0; } -/* Parse one main config file located in /etc/systemd and its drop-ins, which is what all systemd daemons - * do. */ -int config_parse_config_file( - const char *conf_file, - const char *sections, - ConfigItemLookup lookup, - const void *table, - ConfigParseFlags flags, - void *userdata) { - - _cleanup_strv_free_ char **dropins = NULL, **dropin_dirs = NULL; - char **conf_paths = CONF_PATHS_STRV(""); - int r; - - assert(conf_file); - - /* build the dropin dir list */ - dropin_dirs = new0(char*, strv_length(conf_paths) + 1); - if (!dropin_dirs) { - if (flags & CONFIG_PARSE_WARN) - return log_oom(); - return -ENOMEM; - } - - size_t i = 0; - STRV_FOREACH(p, conf_paths) { - char *d; - - d = strjoin(*p, "systemd/", conf_file, ".d"); - if (!d) { - if (flags & CONFIG_PARSE_WARN) - return log_oom(); - return -ENOMEM; - } - - dropin_dirs[i++] = d; - } - - r = conf_files_list_strv(&dropins, ".conf", NULL, 0, (const char**) dropin_dirs); - if (r < 0) - return r; - - const char *sysconf_file = strjoina(PKGSYSCONFDIR, "/", conf_file); - - return config_parse_many_files(STRV_MAKE_CONST(sysconf_file), dropins, - sections, lookup, table, flags, userdata, NULL); -} - /* Parse each config file in the directories specified as strv. */ int config_parse_many( const char* const* conf_files, @@ -665,14 +618,13 @@ int config_parse_many( assert(conf_file_dirs); assert(dropin_dirname); - assert(sections); assert(table); r = conf_files_list_dropins(&files, dropin_dirname, root, conf_file_dirs); if (r < 0) return r; - r = config_parse_many_files(conf_files, files, sections, lookup, table, flags, userdata, ret_stats_by_path); + r = config_parse_many_files(root, conf_files, files, sections, lookup, table, flags, userdata, ret_stats_by_path); if (r < 0) return r; @@ -682,6 +634,50 @@ int config_parse_many( return 0; } +int config_parse_standard_file_with_dropins_full( + const char *root, + const char *main_file, /* A path like "systemd/frobnicator.conf" */ + const char *sections, + ConfigItemLookup lookup, + const void *table, + ConfigParseFlags flags, + void *userdata, + Hashmap **ret_stats_by_path, + char ***ret_dropin_files) { + + const char* const *conf_paths = (const char* const*) CONF_PATHS_STRV(""); + _cleanup_strv_free_ char **configs = NULL; + int r; + + /* Build the list of main config files */ + r = strv_extend_strv_biconcat(&configs, root, conf_paths, main_file); + if (r < 0) { + if (flags & CONFIG_PARSE_WARN) + log_oom(); + return r; + } + + _cleanup_free_ char *dropin_dirname = strjoin(main_file, ".d"); + if (!dropin_dirname) { + if (flags & CONFIG_PARSE_WARN) + log_oom(); + return -ENOMEM; + } + + return config_parse_many( + (const char* const*) configs, + conf_paths, + dropin_dirname, + root, + sections, + lookup, + table, + flags, + userdata, + ret_stats_by_path, + ret_dropin_files); +} + static int dropins_get_stats_by_path( const char* conf_file, const char* const* conf_file_dirs, @@ -795,12 +791,12 @@ bool stats_by_path_equal(Hashmap *a, Hashmap *b) { return true; } -static void config_section_hash_func(const ConfigSection *c, struct siphash *state) { +void config_section_hash_func(const ConfigSection *c, struct siphash *state) { siphash24_compress_string(c->filename, state); - siphash24_compress(&c->line, sizeof(c->line), state); + siphash24_compress_typesafe(c->line, state); } -static int config_section_compare_func(const ConfigSection *x, const ConfigSection *y) { +int config_section_compare_func(const ConfigSection *x, const ConfigSection *y) { int r; r = strcmp(x->filename, y->filename); @@ -1062,7 +1058,7 @@ int config_parse_tristate( if (isempty(rvalue)) { *t = -1; - return 0; + return 1; } r = parse_tristate(rvalue, t); @@ -1072,7 +1068,7 @@ int config_parse_tristate( return 0; } - return 0; + return 1; } int config_parse_string( @@ -1088,6 +1084,7 @@ int config_parse_string( void *userdata) { char **s = ASSERT_PTR(data); + int r; assert(filename); assert(lvalue); @@ -1095,7 +1092,7 @@ int config_parse_string( if (isempty(rvalue)) { *s = mfree(*s); - return 0; + return 1; } if (FLAGS_SET(ltype, CONFIG_PARSE_STRING_SAFE) && !string_is_safe(rvalue)) { @@ -1116,7 +1113,11 @@ int config_parse_string( return 0; } - return free_and_strdup_warn(s, empty_to_null(rvalue)); + r = free_and_strdup_warn(s, empty_to_null(rvalue)); + if (r < 0) + return r; + + return 1; } int config_parse_dns_name( @@ -1592,7 +1593,7 @@ int config_parse_mtu( return 0; } - return 0; + return 1; } int config_parse_rlimit( @@ -1982,3 +1983,37 @@ int config_parse_unsigned_bounded( DEFINE_CONFIG_PARSE(config_parse_percent, parse_percent, "Failed to parse percent value"); DEFINE_CONFIG_PARSE(config_parse_permyriad, parse_permyriad, "Failed to parse permyriad value"); DEFINE_CONFIG_PARSE_PTR(config_parse_sec_fix_0, parse_sec_fix_0, usec_t, "Failed to parse time value"); + +int config_parse_timezone( + 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) { + + char **tz = ASSERT_PTR(data); + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + *tz = mfree(*tz); + return 0; + } + + r = verify_timezone(rvalue, LOG_WARNING); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Timezone is not valid, ignoring assignment: %s", rvalue); + return 0; + } + + return free_and_strdup_warn(tz, rvalue); +} diff --git a/src/shared/conf-parser.h b/src/shared/conf-parser.h index a1768cd..35e203c 100644 --- a/src/shared/conf-parser.h +++ b/src/shared/conf-parser.h @@ -93,20 +93,12 @@ int config_parse( void *userdata, struct stat *ret_stat); /* possibly NULL */ -int config_parse_config_file( - const char *conf_file, - const char *sections, /* nulstr */ - ConfigItemLookup lookup, - const void *table, - ConfigParseFlags flags, - void *userdata); - int config_parse_many( const char* const* conf_files, /* possibly empty */ const char* const* conf_file_dirs, const char *dropin_dirname, const char *root, - const char *sections, /* nulstr */ + const char *sections, /* nulstr */ ConfigItemLookup lookup, const void *table, ConfigParseFlags flags, @@ -114,6 +106,36 @@ int config_parse_many( Hashmap **ret_stats_by_path, /* possibly NULL */ char ***ret_drop_in_files); /* possibly NULL */ +int config_parse_standard_file_with_dropins_full( + const char *root, + const char *main_file, /* A path like "systemd/frobnicator.conf" */ + const char *sections, + ConfigItemLookup lookup, + const void *table, + ConfigParseFlags flags, + void *userdata, + Hashmap **ret_stats_by_path, /* possibly NULL */ + char ***ret_dropin_files); /* possibly NULL */ + +static inline int config_parse_standard_file_with_dropins( + const char *main_file, /* A path like "systemd/frobnicator.conf" */ + const char *sections, /* nulstr */ + ConfigItemLookup lookup, + const void *table, + ConfigParseFlags flags, + void *userdata) { + return config_parse_standard_file_with_dropins_full( + /* root= */ NULL, + main_file, + sections, + lookup, + table, + flags, + userdata, + /* ret_stats_by_path= */ NULL, + /* ret_dropin_files= */ NULL); +} + int config_get_stats_by_path( const char *suffix, const char *root, @@ -137,7 +159,11 @@ static inline ConfigSection* config_section_free(ConfigSection *cs) { DEFINE_TRIVIAL_CLEANUP_FUNC(ConfigSection*, config_section_free); int config_section_new(const char *filename, unsigned line, ConfigSection **ret); + +void config_section_hash_func(const ConfigSection *c, struct siphash *state); +int config_section_compare_func(const ConfigSection *x, const ConfigSection *y); extern const struct hash_ops config_section_hash_ops; + int _hashmap_by_section_find_unused_line( HashmapBase *entries_by_section, const char *filename, @@ -224,6 +250,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_percent); CONFIG_PARSER_PROTOTYPE(config_parse_permyriad); CONFIG_PARSER_PROTOTYPE(config_parse_pid); CONFIG_PARSER_PROTOTYPE(config_parse_sec_fix_0); +CONFIG_PARSER_PROTOTYPE(config_parse_timezone); typedef enum Disabled { DISABLED_CONFIGURATION, diff --git a/src/shared/copy.c b/src/shared/copy.c index 2b87cba..8389774 100644 --- a/src/shared/copy.c +++ b/src/shared/copy.c @@ -170,13 +170,14 @@ int copy_bytes_full( assert(fdt >= 0); assert(!FLAGS_SET(copy_flags, COPY_LOCK_BSD)); - /* Tries to copy bytes from the file descriptor 'fdf' to 'fdt' in the smartest possible way. Copies a maximum - * of 'max_bytes', which may be specified as UINT64_MAX, in which no maximum is applied. Returns negative on - * error, zero if EOF is hit before the bytes limit is hit and positive otherwise. If the copy fails for some - * reason but we read but didn't yet write some data an ret_remains/ret_remains_size is not NULL, then it will - * be initialized with an allocated buffer containing this "remaining" data. Note that these two parameters are - * initialized with a valid buffer only on failure and only if there's actually data already read. Otherwise - * these parameters if non-NULL are set to NULL. */ + /* Tries to copy bytes from the file descriptor 'fdf' to 'fdt' in the smartest possible way. Copies a + * maximum of 'max_bytes', which may be specified as UINT64_MAX, in which no maximum is applied. + * Returns negative on error, zero if EOF is hit before the bytes limit is hit and positive + * otherwise. If the copy fails for some reason but we read but didn't yet write some data and + * ret_remains/ret_remains_size is not NULL, then it will be initialized with an allocated buffer + * containing this "remaining" data. Note that these two parameters are initialized with a valid + * buffer only on failure and only if there's actually data already read. Otherwise these parameters + * if non-NULL are set to NULL. */ if (ret_remains) *ret_remains = NULL; diff --git a/src/shared/cpu-set-util.c b/src/shared/cpu-set-util.c index d096576..1112de1 100644 --- a/src/shared/cpu-set-util.c +++ b/src/shared/cpu-set-util.c @@ -11,6 +11,7 @@ #include "errno-util.h" #include "extract-word.h" #include "fd-util.h" +#include "hexdecoct.h" #include "log.h" #include "macro.h" #include "memory-util.h" @@ -82,6 +83,63 @@ char *cpu_set_to_range_string(const CPUSet *set) { return TAKE_PTR(str) ?: strdup(""); } +char* cpu_set_to_mask_string(const CPUSet *a) { + _cleanup_free_ char *str = NULL; + size_t len = 0; + bool found_nonzero = false; + + assert(a); + + /* Return CPU set in hexadecimal bitmap mask, e.g. + * CPU 0 -> "1" + * CPU 1 -> "2" + * CPU 0,1 -> "3" + * CPU 0-3 -> "f" + * CPU 0-7 -> "ff" + * CPU 4-7 -> "f0" + * CPU 7 -> "80" + * None -> "0" + * + * When there are more than 32 CPUs, separate every 32 CPUs by comma, e.g. + * CPU 0-47 -> "ffff,ffffffff" + * CPU 0-63 -> "ffffffff,ffffffff" + * CPU 0-71 -> "ff,ffffffff,ffffffff" */ + + for (ssize_t i = a->allocated * 8; i >= 0; i -= 4) { + uint8_t m = 0; + + for (size_t j = 0; j < 4; j++) + if (CPU_ISSET_S(i + j, a->allocated, a->set)) + m |= 1U << j; + + if (!found_nonzero) + found_nonzero = m > 0; + + if (!found_nonzero && m == 0) + /* Skip leading zeros */ + continue; + + if (!GREEDY_REALLOC(str, len + 3)) + return NULL; + + str[len++] = hexchar(m); + if (i >= 4 && i % 32 == 0) + /* Separate by comma for each 32 CPUs. */ + str[len++] = ','; + str[len] = 0; + } + + return TAKE_PTR(str) ?: strdup("0"); +} + +CPUSet* cpu_set_free(CPUSet *c) { + if (!c) + return c; + + cpu_set_reset(c); + return mfree(c); +} + int cpu_set_realloc(CPUSet *cpu_set, unsigned ncpus) { size_t need; @@ -290,3 +348,22 @@ int cpu_set_from_dbus(const uint8_t *bits, size_t size, CPUSet *set) { *set = TAKE_STRUCT(s); return 0; } + +int cpu_mask_add_all(CPUSet *mask) { + long m; + int r; + + assert(mask); + + m = sysconf(_SC_NPROCESSORS_ONLN); + if (m < 0) + return -errno; + + for (unsigned i = 0; i < (unsigned) m; i++) { + r = cpu_set_add(mask, i); + if (r < 0) + return r; + } + + return 0; +} diff --git a/src/shared/cpu-set-util.h b/src/shared/cpu-set-util.h index 3c63a58..618fe1b 100644 --- a/src/shared/cpu-set-util.h +++ b/src/shared/cpu-set-util.h @@ -19,11 +19,15 @@ static inline void cpu_set_reset(CPUSet *a) { *a = (CPUSet) {}; } +CPUSet* cpu_set_free(CPUSet *c); +DEFINE_TRIVIAL_CLEANUP_FUNC(CPUSet*, cpu_set_free); + int cpu_set_add_all(CPUSet *a, const CPUSet *b); int cpu_set_add(CPUSet *a, unsigned cpu); char* cpu_set_to_string(const CPUSet *a); char *cpu_set_to_range_string(const CPUSet *a); +char* cpu_set_to_mask_string(const CPUSet *a); int cpu_set_realloc(CPUSet *cpu_set, unsigned ncpus); int parse_cpu_set_full( @@ -50,3 +54,4 @@ int cpu_set_to_dbus(const CPUSet *set, uint8_t **ret, size_t *allocated); int cpu_set_from_dbus(const uint8_t *bits, size_t size, CPUSet *set); int cpus_in_affinity_mask(void); +int cpu_mask_add_all(CPUSet *mask); diff --git a/src/shared/creds-util.c b/src/shared/creds-util.c index fa8ebe0..1d8bd91 100644 --- a/src/shared/creds-util.c +++ b/src/shared/creds-util.c @@ -12,23 +12,28 @@ #include "capability-util.h" #include "chattr-util.h" #include "constants.h" +#include "copy.h" #include "creds-util.h" #include "efi-api.h" #include "env-util.h" #include "fd-util.h" #include "fileio.h" +#include "format-util.h" #include "fs-util.h" #include "io-util.h" #include "memory-util.h" -#include "mkdir.h" +#include "mkdir-label.h" #include "openssl-util.h" #include "parse-util.h" #include "path-util.h" #include "random-util.h" +#include "recurse-dir.h" #include "sparse-endian.h" #include "stat-util.h" +#include "tmpfile-util.h" #include "tpm2-util.h" -#include "virt.h" +#include "user-util.h" +#include "varlink.h" #define PUBLIC_KEY_MAX (UINT32_C(1024) * UINT32_C(1024)) @@ -100,6 +105,17 @@ int get_encrypted_credentials_dir(const char **ret) { return get_credentials_dir_internal("ENCRYPTED_CREDENTIALS_DIRECTORY", ret); } +int open_credentials_dir(void) { + const char *d; + int r; + + r = get_credentials_dir(&d); + if (r < 0) + return r; + + return RET_NERRNO(open(d, O_CLOEXEC|O_DIRECTORY)); +} + int read_credential(const char *name, void **ret, size_t *ret_size) { _cleanup_free_ char *fn = NULL; const char *d; @@ -127,14 +143,13 @@ int read_credential(const char *name, void **ret, size_t *ret_size) { } int read_credential_with_decryption(const char *name, void **ret, size_t *ret_size) { + _cleanup_(iovec_done_erase) struct iovec ret_iovec = {}; _cleanup_(erase_and_freep) void *data = NULL; _cleanup_free_ char *fn = NULL; size_t sz = 0; const char *d; int r; - assert(ret); - /* Just like read_credential() but will also look for encrypted credentials. Note that services only * receive decrypted credentials, hence use read_credential() for those. This helper here is for * generators, i.e. code that runs outside of service context, and thus has no decrypted credentials @@ -177,23 +192,37 @@ int read_credential_with_decryption(const char *name, void **ret, size_t *ret_si if (r < 0) return log_error_errno(r, "Failed to read encrypted credential data: %m"); - r = decrypt_credential_and_warn( - name, - now(CLOCK_REALTIME), - /* tpm2_device = */ NULL, - /* tpm2_signature_path = */ NULL, - data, - sz, - ret, - ret_size); + if (geteuid() != 0) + r = ipc_decrypt_credential( + name, + now(CLOCK_REALTIME), + getuid(), + &IOVEC_MAKE(data, sz), + CREDENTIAL_ANY_SCOPE, + &ret_iovec); + else + r = decrypt_credential_and_warn( + name, + now(CLOCK_REALTIME), + /* tpm2_device= */ NULL, + /* tpm2_signature_path= */ NULL, + getuid(), + &IOVEC_MAKE(data, sz), + CREDENTIAL_ANY_SCOPE, + &ret_iovec); if (r < 0) return r; + if (ret) + *ret = TAKE_PTR(ret_iovec.iov_base); + if (ret_size) + *ret_size = ret_iovec.iov_len; + return 1; /* found */ not_found: - *ret = NULL; - + if (ret) + *ret = NULL; if (ret_size) *ret_size = 0; @@ -205,6 +234,7 @@ int read_credential_strings_many_internal( ...) { _cleanup_free_ void *b = NULL; + bool all = true; int r, ret = 0; /* Reads a bunch of credentials into the specified buffers. If the specified buffers are already @@ -220,10 +250,11 @@ int read_credential_strings_many_internal( r = read_credential(first_name, &b, NULL); if (r == -ENXIO) /* No creds passed at all? Bail immediately. */ return 0; - if (r < 0) { - if (r != -ENOENT) - ret = r; - } else + if (r == -ENOENT) + all = false; + else if (r < 0) + RET_GATHER(ret, r); + else free_and_replace(*first_value, b); va_list ap; @@ -238,20 +269,19 @@ int read_credential_strings_many_internal( if (!name) break; - value = va_arg(ap, char **); - if (*value) - continue; + value = ASSERT_PTR(va_arg(ap, char **)); r = read_credential(name, &bb, NULL); - if (r < 0) { - if (ret >= 0 && r != -ENOENT) - ret = r; - } else + if (r == -ENOENT) + all = false; + else if (r < 0) + RET_GATHER(ret, r); + else free_and_replace(*value, bb); } va_end(ap); - return ret; + return ret < 0 ? ret : all; } int read_credential_bool(const char *name) { @@ -341,8 +371,7 @@ static int make_credential_host_secret( CredentialSecretFlags flags, const char *dirname, const char *fn, - void **ret_data, - size_t *ret_size) { + struct iovec *ret) { _cleanup_free_ char *t = NULL; _cleanup_close_ int fd = -EBADF; @@ -351,22 +380,9 @@ static int make_credential_host_secret( assert(dfd >= 0); assert(fn); - /* For non-root users creating a temporary file using the openat(2) over "." will fail later, in the - * linkat(2) step at the end. The reason is that linkat(2) requires the CAP_DAC_READ_SEARCH - * capability when it uses the AT_EMPTY_PATH flag. */ - if (have_effective_cap(CAP_DAC_READ_SEARCH) > 0) { - fd = openat(dfd, ".", O_CLOEXEC|O_WRONLY|O_TMPFILE, 0400); - if (fd < 0) - log_debug_errno(errno, "Failed to create temporary credential file with O_TMPFILE, proceeding without: %m"); - } - if (fd < 0) { - if (asprintf(&t, "credential.secret.%016" PRIx64, random_u64()) < 0) - return -ENOMEM; - - fd = openat(dfd, t, O_CLOEXEC|O_WRONLY|O_CREAT|O_EXCL|O_NOFOLLOW, 0400); - if (fd < 0) - return -errno; - } + fd = open_tmpfile_linkable_at(dfd, fn, O_CLOEXEC|O_WRONLY, &t); + if (fd < 0) + return log_debug_errno(fd, "Failed to create temporary file for credential host secret: %m"); r = chattr_secret(fd, 0); if (r < 0) @@ -386,44 +402,34 @@ static int make_credential_host_secret( if (r < 0) goto fail; - if (fsync(fd) < 0) { + if (fchmod(fd, 0400) < 0) { r = -errno; goto fail; } - warn_not_encrypted(fd, flags, dirname, fn); - - if (t) { - r = rename_noreplace(dfd, t, dfd, fn); - if (r < 0) - goto fail; - - t = mfree(t); - } else if (linkat(fd, "", dfd, fn, AT_EMPTY_PATH) < 0) { + if (fsync(fd) < 0) { r = -errno; goto fail; } - if (fsync(dfd) < 0) { - r = -errno; + warn_not_encrypted(fd, flags, dirname, fn); + + r = link_tmpfile_at(fd, dfd, t, fn, LINK_TMPFILE_SYNC); + if (r < 0) { + log_debug_errno(r, "Failed to link host key into place: %m"); goto fail; } - if (ret_data) { + if (ret) { void *copy; copy = memdup(buf.data, sizeof(buf.data)); - if (!copy) { - r = -ENOMEM; - goto fail; - } + if (!copy) + return -ENOMEM; - *ret_data = copy; + *ret = IOVEC_MAKE(copy, sizeof(buf.data)); } - if (ret_size) - *ret_size = sizeof(buf.data); - return 0; fail: @@ -433,7 +439,7 @@ fail: return r; } -int get_credential_host_secret(CredentialSecretFlags flags, void **ret, size_t *ret_size) { +int get_credential_host_secret(CredentialSecretFlags flags, struct iovec *ret) { _cleanup_free_ char *_dirname = NULL, *_filename = NULL; _cleanup_close_ int dfd = -EBADF; sd_id128_t machine_id; @@ -501,7 +507,7 @@ int get_credential_host_secret(CredentialSecretFlags flags, void **ret, size_t * "Failed to open %s/%s: %m", dirname, filename); - r = make_credential_host_secret(dfd, machine_id, flags, dirname, filename, ret, ret_size); + r = make_credential_host_secret(dfd, machine_id, flags, dirname, filename, ret); if (r == -EEXIST) { log_debug_errno(r, "Credential secret %s/%s appeared while we were creating it, rereading.", dirname, filename); @@ -549,7 +555,7 @@ int get_credential_host_secret(CredentialSecretFlags flags, void **ret, size_t * "Failed to read %s/%s: %m", dirname, filename); if ((size_t) n != l) /* What? The size changed? */ return log_debug_errno(SYNTHETIC_ERRNO(EIO), - "Failed to read %s/%s: %m", dirname, filename); + "Failed to read %s/%s.", dirname, filename); if (sd_id128_equal(machine_id, f->machine_id)) { size_t sz; @@ -568,12 +574,9 @@ int get_credential_host_secret(CredentialSecretFlags flags, void **ret, size_t * if (!copy) return log_oom_debug(); - *ret = copy; + *ret = IOVEC_MAKE(copy, sz); } - if (ret_size) - *ret_size = sz; - return 0; } @@ -658,6 +661,11 @@ struct _packed_ tpm2_public_key_credential_header { /* Followed by NUL bytes until next 8 byte boundary */ }; +struct _packed_ scoped_credential_header { + le64_t flags; /* SCOPE_HASH_DATA_BASE_FLAGS for now */ +}; + +/* This header is encrypted */ struct _packed_ metadata_credential_header { le64_t timestamp; le64_t not_after; @@ -666,23 +674,38 @@ struct _packed_ metadata_credential_header { /* Followed by NUL bytes until next 8 byte boundary */ }; +struct _packed_ scoped_hash_data { + le64_t flags; /* copy of the scoped_credential_header.flags */ + le32_t uid; + sd_id128_t machine_id; + char username[]; /* followed by the username */ + /* Later on we might want to extend this: with a cgroup path to allow per-app secrets, and with the user's $HOME encryption key */ +}; + +enum { + /* Flags for scoped_hash_data.flags and scoped_credential_header.flags */ + SCOPE_HASH_DATA_HAS_UID = 1 << 0, + SCOPE_HASH_DATA_HAS_MACHINE = 1 << 1, + SCOPE_HASH_DATA_HAS_USERNAME = 1 << 2, + + SCOPE_HASH_DATA_BASE_FLAGS = SCOPE_HASH_DATA_HAS_UID | SCOPE_HASH_DATA_HAS_USERNAME | SCOPE_HASH_DATA_HAS_MACHINE, +}; + /* Some generic limit for parts of the encrypted credential for which we don't know the right size ahead of * time, but where we are really sure it won't be larger than this. Should be larger than any possible IV, * padding, tag size and so on. This is purely used for early filtering out of invalid sizes. */ #define CREDENTIAL_FIELD_SIZE_MAX (16U*1024U) static int sha256_hash_host_and_tpm2_key( - const void *host_key, - size_t host_key_size, - const void *tpm2_key, - size_t tpm2_key_size, + const struct iovec *host_key, + const struct iovec *tpm2_key, uint8_t ret[static SHA256_DIGEST_LENGTH]) { _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *md = NULL; unsigned l; - assert(host_key_size == 0 || host_key); - assert(tpm2_key_size == 0 || tpm2_key); + assert(iovec_is_valid(host_key)); + assert(iovec_is_valid(tpm2_key)); assert(ret); /* Combines the host key and the TPM2 HMAC hash into a SHA256 hash value we'll use as symmetric encryption key. */ @@ -694,10 +717,10 @@ static int sha256_hash_host_and_tpm2_key( if (EVP_DigestInit_ex(md, EVP_sha256(), NULL) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initial SHA256 context."); - if (host_key && EVP_DigestUpdate(md, host_key, host_key_size) != 1) + if (iovec_is_set(host_key) && EVP_DigestUpdate(md, host_key->iov_base, host_key->iov_len) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to hash host key."); - if (tpm2_key && EVP_DigestUpdate(md, tpm2_key, tpm2_key_size) != 1) + if (iovec_is_set(tpm2_key) && EVP_DigestUpdate(md, tpm2_key->iov_base, tpm2_key->iov_len) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to hash TPM2 key."); assert(EVP_MD_CTX_size(md) == SHA256_DIGEST_LENGTH); @@ -709,6 +732,58 @@ static int sha256_hash_host_and_tpm2_key( return 0; } +static int mangle_uid_into_key( + uid_t uid, + uint8_t md[static SHA256_DIGEST_LENGTH]) { + + sd_id128_t mid; + int r; + + assert(uid_is_valid(uid)); + assert(md); + + /* If we shall encrypt for a specific user, we HMAC() a structure with the user's credentials + * (specifically, UID, user name, machine ID) with the key we'd otherwise use for system credentials, + * and use the resulting hash as actual encryption key. */ + + errno = 0; + struct passwd *pw = getpwuid(uid); + if (!pw) + return log_error_errno( + IN_SET(errno, 0, ENOENT) ? SYNTHETIC_ERRNO(ESRCH) : errno, + "Failed to resolve UID " UID_FMT ": %m", uid); + + r = sd_id128_get_machine(&mid); + if (r < 0) + return log_error_errno(r, "Failed to read machine ID: %m"); + + size_t sz = offsetof(struct scoped_hash_data, username) + strlen(pw->pw_name) + 1; + _cleanup_free_ struct scoped_hash_data *d = malloc0(sz); + if (!d) + return log_oom(); + + d->flags = htole64(SCOPE_HASH_DATA_BASE_FLAGS); + d->uid = htole32(uid); + d->machine_id = mid; + + strcpy(d->username, pw->pw_name); + + _cleanup_(erase_and_freep) void *buf = NULL; + size_t buf_size = 0; + r = openssl_hmac_many( + "sha256", + md, SHA256_DIGEST_LENGTH, + &IOVEC_MAKE(d, sz), 1, + &buf, &buf_size); + if (r < 0) + return r; + + assert(buf_size == SHA256_DIGEST_LENGTH); + memcpy(md, buf, buf_size); + + return 0; +} + int encrypt_credential_and_warn( sd_id128_t with_key, const char *name, @@ -718,38 +793,39 @@ int encrypt_credential_and_warn( uint32_t tpm2_hash_pcr_mask, const char *tpm2_pubkey_path, uint32_t tpm2_pubkey_pcr_mask, - const void *input, - size_t input_size, - void **ret, - size_t *ret_size) { + uid_t uid, + const struct iovec *input, + CredentialFlags flags, + struct iovec *ret) { + _cleanup_(iovec_done) struct iovec tpm2_blob = {}, tpm2_policy_hash = {}, iv = {}, pubkey = {}; + _cleanup_(iovec_done_erase) struct iovec tpm2_key = {}, output = {}, host_key = {}; _cleanup_(EVP_CIPHER_CTX_freep) EVP_CIPHER_CTX *context = NULL; - _cleanup_(erase_and_freep) void *host_key = NULL, *tpm2_key = NULL; - size_t host_key_size = 0, tpm2_key_size = 0, tpm2_blob_size = 0, tpm2_policy_hash_size = 0, output_size, p, ml; - _cleanup_free_ void *tpm2_blob = NULL, *tpm2_policy_hash = NULL, *iv = NULL, *output = NULL; _cleanup_free_ struct metadata_credential_header *m = NULL; uint16_t tpm2_pcr_bank = 0, tpm2_primary_alg = 0; struct encrypted_credential_header *h; int ksz, bsz, ivsz, tsz, added, r; - _cleanup_free_ void *pubkey = NULL; - size_t pubkey_size = 0; uint8_t md[SHA256_DIGEST_LENGTH]; const EVP_CIPHER *cc; sd_id128_t id; + size_t p, ml; - assert(input || input_size == 0); + assert(iovec_is_valid(input)); assert(ret); - assert(ret_size); if (!sd_id128_in_set(with_key, _CRED_AUTO, _CRED_AUTO_INITRD, + _CRED_AUTO_SCOPED, CRED_AES256_GCM_BY_HOST, + CRED_AES256_GCM_BY_HOST_SCOPED, CRED_AES256_GCM_BY_TPM2_HMAC, CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC, + CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_SCOPED, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK, - CRED_AES256_GCM_BY_TPM2_ABSENT)) + CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED, + CRED_AES256_GCM_BY_NULL)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid key type: " SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(with_key)); if (name && !credential_name_valid(name)) @@ -770,18 +846,31 @@ int encrypt_credential_and_warn( } if (sd_id128_in_set(with_key, + _CRED_AUTO_SCOPED, + CRED_AES256_GCM_BY_HOST_SCOPED, + CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_SCOPED, + CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED)) { + if (!uid_is_valid(uid)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Scoped credential selected, but no UID specified."); + } else + uid = UID_INVALID; + + if (sd_id128_in_set(with_key, _CRED_AUTO, + _CRED_AUTO_SCOPED, CRED_AES256_GCM_BY_HOST, + CRED_AES256_GCM_BY_HOST_SCOPED, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC, - CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK)) { + CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_SCOPED, + CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK, + CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED)) { r = get_credential_host_secret( CREDENTIAL_SECRET_GENERATE| CREDENTIAL_SECRET_WARN_NOT_ENCRYPTED| - (sd_id128_equal(with_key, _CRED_AUTO) ? CREDENTIAL_SECRET_FAIL_ON_TEMPORARY_FS : 0), - &host_key, - &host_key_size); - if (r == -ENOMEDIUM && sd_id128_equal(with_key, _CRED_AUTO)) + (sd_id128_in_set(with_key, _CRED_AUTO, _CRED_AUTO_SCOPED) ? CREDENTIAL_SECRET_FAIL_ON_TEMPORARY_FS : 0), + &host_key); + if (r == -ENOMEDIUM && sd_id128_in_set(with_key, _CRED_AUTO, _CRED_AUTO_SCOPED)) log_debug_errno(r, "Credential host secret location on temporary file system, not using."); else if (r < 0) return log_error_errno(r, "Failed to determine local credential host secret: %m"); @@ -789,7 +878,7 @@ int encrypt_credential_and_warn( #if HAVE_TPM2 bool try_tpm2; - if (sd_id128_in_set(with_key, _CRED_AUTO, _CRED_AUTO_INITRD)) { + if (sd_id128_in_set(with_key, _CRED_AUTO, _CRED_AUTO_INITRD, _CRED_AUTO_SCOPED)) { /* If automatic mode is selected lets see if a TPM2 it is present. If we are running in a * container tpm2_support will detect this, and will return a different flag combination of * TPM2_SUPPORT_FULL, effectively skipping the use of TPM2 when inside one. */ @@ -802,27 +891,31 @@ int encrypt_credential_and_warn( CRED_AES256_GCM_BY_TPM2_HMAC, CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC, - CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK); + CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_SCOPED, + CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK, + CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED); if (try_tpm2) { if (sd_id128_in_set(with_key, _CRED_AUTO, _CRED_AUTO_INITRD, + _CRED_AUTO_SCOPED, CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK, - CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK)) { + CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK, + CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED)) { /* Load public key for PCR policies, if one is specified, or explicitly requested */ - r = tpm2_load_pcr_public_key(tpm2_pubkey_path, &pubkey, &pubkey_size); + r = tpm2_load_pcr_public_key(tpm2_pubkey_path, &pubkey.iov_base, &pubkey.iov_len); if (r < 0) { - if (tpm2_pubkey_path || r != -ENOENT || !sd_id128_in_set(with_key, _CRED_AUTO, _CRED_AUTO_INITRD)) + if (tpm2_pubkey_path || r != -ENOENT || !sd_id128_in_set(with_key, _CRED_AUTO, _CRED_AUTO_INITRD, _CRED_AUTO_SCOPED)) return log_error_errno(r, "Failed read TPM PCR public key: %m"); log_debug_errno(r, "Failed to read TPM2 PCR public key, proceeding without: %m"); } } - if (!pubkey) + if (!iovec_is_set(&pubkey)) tpm2_pubkey_pcr_mask = 0; _cleanup_(tpm2_context_unrefp) Tpm2Context *tpm2_context = NULL; @@ -844,8 +937,8 @@ int encrypt_credential_and_warn( return log_error_errno(r, "Could not read PCR values: %m"); TPM2B_PUBLIC public; - if (pubkey) { - r = tpm2_tpm2b_public_from_pem(pubkey, pubkey_size, &public); + if (iovec_is_set(&pubkey)) { + r = tpm2_tpm2b_public_from_pem(pubkey.iov_base, pubkey.iov_len, &public); if (r < 0) return log_error_errno(r, "Could not convert public key to TPM2B_PUBLIC: %m"); } @@ -854,7 +947,7 @@ int encrypt_credential_and_warn( r = tpm2_calculate_sealing_policy( tpm2_hash_pcr_values, tpm2_n_hash_pcr_values, - pubkey ? &public : NULL, + iovec_is_set(&pubkey) ? &public : NULL, /* use_pin= */ false, /* pcrlock_policy= */ NULL, &tpm2_policy); @@ -865,56 +958,61 @@ int encrypt_credential_and_warn( /* seal_key_handle= */ 0, &tpm2_policy, /* pin= */ NULL, - &tpm2_key, &tpm2_key_size, - &tpm2_blob, &tpm2_blob_size, + &tpm2_key, + &tpm2_blob, &tpm2_primary_alg, - /* ret_srk_buf= */ NULL, - /* ret_srk_buf_size= */ NULL); + /* ret_srk= */ NULL); if (r < 0) { if (sd_id128_equal(with_key, _CRED_AUTO_INITRD)) log_warning("TPM2 present and used, but we didn't manage to talk to it. Credential will be refused if SecureBoot is enabled."); - else if (!sd_id128_equal(with_key, _CRED_AUTO)) + else if (!sd_id128_in_set(with_key, _CRED_AUTO, _CRED_AUTO_SCOPED)) return log_error_errno(r, "Failed to seal to TPM2: %m"); log_notice_errno(r, "TPM2 sealing didn't work, continuing without TPM2: %m"); } - tpm2_policy_hash_size = tpm2_policy.size; - tpm2_policy_hash = malloc(tpm2_policy_hash_size); - if (!tpm2_policy_hash) + if (!iovec_memdup(&IOVEC_MAKE(tpm2_policy.buffer, tpm2_policy.size), &tpm2_policy_hash)) return log_oom(); - memcpy(tpm2_policy_hash, tpm2_policy.buffer, tpm2_policy_hash_size); - assert(tpm2_blob_size <= CREDENTIAL_FIELD_SIZE_MAX); - assert(tpm2_policy_hash_size <= CREDENTIAL_FIELD_SIZE_MAX); + assert(tpm2_blob.iov_len <= CREDENTIAL_FIELD_SIZE_MAX); + assert(tpm2_policy_hash.iov_len <= CREDENTIAL_FIELD_SIZE_MAX); } #endif - if (sd_id128_in_set(with_key, _CRED_AUTO, _CRED_AUTO_INITRD)) { + if (sd_id128_in_set(with_key, _CRED_AUTO, _CRED_AUTO_INITRD, _CRED_AUTO_SCOPED)) { /* Let's settle the key type in auto mode now. */ - if (host_key && tpm2_key) - id = pubkey ? CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK : CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC; - else if (tpm2_key) - id = pubkey ? CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK : CRED_AES256_GCM_BY_TPM2_HMAC; - else if (host_key) - id = CRED_AES256_GCM_BY_HOST; + if (iovec_is_set(&host_key) && iovec_is_set(&tpm2_key)) + id = iovec_is_set(&pubkey) ? (sd_id128_equal(with_key, _CRED_AUTO_SCOPED) ? + CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED : CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK) + : (sd_id128_equal(with_key, _CRED_AUTO_SCOPED) ? + CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_SCOPED : CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC); + else if (iovec_is_set(&tpm2_key) && !sd_id128_equal(with_key, _CRED_AUTO_SCOPED)) + id = iovec_is_set(&pubkey) ? CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK : CRED_AES256_GCM_BY_TPM2_HMAC; + else if (iovec_is_set(&host_key)) + id = sd_id128_equal(with_key, _CRED_AUTO_SCOPED) ? CRED_AES256_GCM_BY_HOST_SCOPED : CRED_AES256_GCM_BY_HOST; else if (sd_id128_equal(with_key, _CRED_AUTO_INITRD)) - id = CRED_AES256_GCM_BY_TPM2_ABSENT; + id = CRED_AES256_GCM_BY_NULL; else return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM2 not available and host key located on temporary file system, no encryption key available."); } else id = with_key; - if (sd_id128_equal(id, CRED_AES256_GCM_BY_TPM2_ABSENT)) + if (sd_id128_equal(id, CRED_AES256_GCM_BY_NULL) && !FLAGS_SET(flags, CREDENTIAL_ALLOW_NULL)) log_warning("Using a null key for encryption and signing. Confidentiality or authenticity will not be provided."); /* Let's now take the host key and the TPM2 key and hash it together, to use as encryption key for the data */ - r = sha256_hash_host_and_tpm2_key(host_key, host_key_size, tpm2_key, tpm2_key_size, md); + r = sha256_hash_host_and_tpm2_key(&host_key, &tpm2_key, md); if (r < 0) return r; + if (uid_is_valid(uid)) { + r = mangle_uid_into_key(uid, md); + if (r < 0) + return r; + } + assert_se(cc = EVP_aes_256_gcm()); ksz = EVP_CIPHER_key_length(cc); @@ -928,11 +1026,13 @@ int encrypt_credential_and_warn( if (ivsz > 0) { assert((size_t) ivsz <= CREDENTIAL_FIELD_SIZE_MAX); - iv = malloc(ivsz); - if (!iv) + iv.iov_base = malloc(ivsz); + if (!iv.iov_base) return log_oom(); - r = crypto_random_bytes(iv, ivsz); + iv.iov_len = ivsz; + + r = crypto_random_bytes(iv.iov_base, iv.iov_len); if (r < 0) return log_error_errno(r, "Failed to acquired randomized IV: %m"); } @@ -944,61 +1044,71 @@ int encrypt_credential_and_warn( return log_error_errno(SYNTHETIC_ERRNO(ENOMEM), "Failed to allocate encryption object: %s", ERR_error_string(ERR_get_error(), NULL)); - if (EVP_EncryptInit_ex(context, cc, NULL, md, iv) != 1) + if (EVP_EncryptInit_ex(context, cc, NULL, md, iv.iov_base) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize encryption context: %s", ERR_error_string(ERR_get_error(), NULL)); /* Just an upper estimate */ - output_size = + output.iov_len = ALIGN8(offsetof(struct encrypted_credential_header, iv) + ivsz) + - ALIGN8(tpm2_key ? offsetof(struct tpm2_credential_header, policy_hash_and_blob) + tpm2_blob_size + tpm2_policy_hash_size : 0) + - ALIGN8(pubkey ? offsetof(struct tpm2_public_key_credential_header, data) + pubkey_size : 0) + + ALIGN8(iovec_is_set(&tpm2_key) ? offsetof(struct tpm2_credential_header, policy_hash_and_blob) + tpm2_blob.iov_len + tpm2_policy_hash.iov_len : 0) + + ALIGN8(iovec_is_set(&pubkey) ? offsetof(struct tpm2_public_key_credential_header, data) + pubkey.iov_len : 0) + + ALIGN8(uid_is_valid(uid) ? sizeof(struct scoped_credential_header) : 0) + ALIGN8(offsetof(struct metadata_credential_header, name) + strlen_ptr(name)) + - input_size + 2U * (size_t) bsz + + input->iov_len + 2U * (size_t) bsz + tsz; - output = malloc0(output_size); - if (!output) + output.iov_base = malloc0(output.iov_len); + if (!output.iov_base) return log_oom(); - h = (struct encrypted_credential_header*) output; + h = (struct encrypted_credential_header*) output.iov_base; h->id = id; h->block_size = htole32(bsz); h->key_size = htole32(ksz); h->tag_size = htole32(tsz); h->iv_size = htole32(ivsz); - memcpy(h->iv, iv, ivsz); + memcpy(h->iv, iv.iov_base, ivsz); p = ALIGN8(offsetof(struct encrypted_credential_header, iv) + ivsz); - if (tpm2_key) { + if (iovec_is_set(&tpm2_key)) { struct tpm2_credential_header *t; - t = (struct tpm2_credential_header*) ((uint8_t*) output + p); + t = (struct tpm2_credential_header*) ((uint8_t*) output.iov_base + p); t->pcr_mask = htole64(tpm2_hash_pcr_mask); t->pcr_bank = htole16(tpm2_pcr_bank); t->primary_alg = htole16(tpm2_primary_alg); - t->blob_size = htole32(tpm2_blob_size); - t->policy_hash_size = htole32(tpm2_policy_hash_size); - memcpy(t->policy_hash_and_blob, tpm2_blob, tpm2_blob_size); - memcpy(t->policy_hash_and_blob + tpm2_blob_size, tpm2_policy_hash, tpm2_policy_hash_size); + t->blob_size = htole32(tpm2_blob.iov_len); + t->policy_hash_size = htole32(tpm2_policy_hash.iov_len); + memcpy(t->policy_hash_and_blob, tpm2_blob.iov_base, tpm2_blob.iov_len); + memcpy(t->policy_hash_and_blob + tpm2_blob.iov_len, tpm2_policy_hash.iov_base, tpm2_policy_hash.iov_len); - p += ALIGN8(offsetof(struct tpm2_credential_header, policy_hash_and_blob) + tpm2_blob_size + tpm2_policy_hash_size); + p += ALIGN8(offsetof(struct tpm2_credential_header, policy_hash_and_blob) + tpm2_blob.iov_len + tpm2_policy_hash.iov_len); } - if (pubkey) { + if (iovec_is_set(&pubkey)) { struct tpm2_public_key_credential_header *z; - z = (struct tpm2_public_key_credential_header*) ((uint8_t*) output + p); + z = (struct tpm2_public_key_credential_header*) ((uint8_t*) output.iov_base + p); z->pcr_mask = htole64(tpm2_pubkey_pcr_mask); - z->size = htole32(pubkey_size); - memcpy(z->data, pubkey, pubkey_size); + z->size = htole32(pubkey.iov_len); + memcpy(z->data, pubkey.iov_base, pubkey.iov_len); + + p += ALIGN8(offsetof(struct tpm2_public_key_credential_header, data) + pubkey.iov_len); + } + + if (uid_is_valid(uid)) { + struct scoped_credential_header *w; + + w = (struct scoped_credential_header*) ((uint8_t*) output.iov_base + p); + w->flags = htole64(SCOPE_HASH_DATA_BASE_FLAGS); - p += ALIGN8(offsetof(struct tpm2_public_key_credential_header, data) + pubkey_size); + p += ALIGN8(sizeof(struct scoped_credential_header)); } - /* Pass the encrypted + TPM2 header as AAD */ - if (EVP_EncryptUpdate(context, NULL, &added, output, p) != 1) + /* Pass the encrypted + TPM2 header + scoped header as AAD */ + if (EVP_EncryptUpdate(context, NULL, &added, output.iov_base, p) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to write AAD data: %s", ERR_error_string(ERR_get_error(), NULL)); @@ -1014,53 +1124,52 @@ int encrypt_credential_and_warn( memcpy_safe(m->name, name, ml); /* And encrypt the metadata header */ - if (EVP_EncryptUpdate(context, (uint8_t*) output + p, &added, (const unsigned char*) m, ALIGN8(offsetof(struct metadata_credential_header, name) + ml)) != 1) + if (EVP_EncryptUpdate(context, (uint8_t*) output.iov_base + p, &added, (const unsigned char*) m, ALIGN8(offsetof(struct metadata_credential_header, name) + ml)) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to encrypt metadata header: %s", ERR_error_string(ERR_get_error(), NULL)); assert(added >= 0); - assert((size_t) added <= output_size - p); + assert((size_t) added <= output.iov_len - p); p += added; /* Then encrypt the plaintext */ - if (EVP_EncryptUpdate(context, (uint8_t*) output + p, &added, input, input_size) != 1) + if (EVP_EncryptUpdate(context, (uint8_t*) output.iov_base + p, &added, input->iov_base, input->iov_len) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to encrypt data: %s", ERR_error_string(ERR_get_error(), NULL)); assert(added >= 0); - assert((size_t) added <= output_size - p); + assert((size_t) added <= output.iov_len - p); p += added; /* Finalize */ - if (EVP_EncryptFinal_ex(context, (uint8_t*) output + p, &added) != 1) + if (EVP_EncryptFinal_ex(context, (uint8_t*) output.iov_base + p, &added) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to finalize data encryption: %s", ERR_error_string(ERR_get_error(), NULL)); assert(added >= 0); - assert((size_t) added <= output_size - p); + assert((size_t) added <= output.iov_len - p); p += added; - assert(p <= output_size - tsz); + assert(p <= output.iov_len - tsz); /* Append tag */ - if (EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_GET_TAG, tsz, (uint8_t*) output + p) != 1) + if (EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_GET_TAG, tsz, (uint8_t*) output.iov_base + p) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to get tag: %s", ERR_error_string(ERR_get_error(), NULL)); p += tsz; - assert(p <= output_size); + assert(p <= output.iov_len); + output.iov_len = p; - if (DEBUG_LOGGING && input_size > 0) { + if (DEBUG_LOGGING && input->iov_len > 0) { size_t base64_size; - base64_size = DIV_ROUND_UP(p * 4, 3); /* Include base64 size increase in debug output */ - assert(base64_size >= input_size); - log_debug("Input of %zu bytes grew to output of %zu bytes (+%2zu%%).", input_size, base64_size, base64_size * 100 / input_size - 100); + base64_size = DIV_ROUND_UP(output.iov_len * 4, 3); /* Include base64 size increase in debug output */ + assert(base64_size >= input->iov_len); + log_debug("Input of %zu bytes grew to output of %zu bytes (+%2zu%%).", input->iov_len, base64_size, base64_size * 100 / input->iov_len - 100); } - *ret = TAKE_PTR(output); - *ret_size = p; - + *ret = TAKE_STRUCT(output); return 0; } @@ -1069,39 +1178,39 @@ int decrypt_credential_and_warn( usec_t validate_timestamp, const char *tpm2_device, const char *tpm2_signature_path, - const void *input, - size_t input_size, - void **ret, - size_t *ret_size) { + uid_t uid, + const struct iovec *input, + CredentialFlags flags, + struct iovec *ret) { - _cleanup_(erase_and_freep) void *host_key = NULL, *tpm2_key = NULL, *plaintext = NULL; + _cleanup_(iovec_done_erase) struct iovec host_key = {}, plaintext = {}, tpm2_key = {}; _cleanup_(json_variant_unrefp) JsonVariant *signature_json = NULL; _cleanup_(EVP_CIPHER_CTX_freep) EVP_CIPHER_CTX *context = NULL; - size_t host_key_size = 0, tpm2_key_size = 0, plaintext_size, p, hs; struct encrypted_credential_header *h; struct metadata_credential_header *m; uint8_t md[SHA256_DIGEST_LENGTH]; - bool with_tpm2, with_host_key, is_tpm2_absent, with_tpm2_pk; + bool with_tpm2, with_tpm2_pk, with_host_key, with_null, with_scope; const EVP_CIPHER *cc; + size_t p, hs; int r, added; - assert(input || input_size == 0); + assert(iovec_is_valid(input)); assert(ret); - assert(ret_size); - h = (struct encrypted_credential_header*) input; + h = (struct encrypted_credential_header*) input->iov_base; /* The ID must fit in, for the current and all future formats */ - if (input_size < sizeof(h->id)) + if (input->iov_len < sizeof(h->id)) return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Encrypted file too short."); - with_host_key = sd_id128_in_set(h->id, CRED_AES256_GCM_BY_HOST, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK); - with_tpm2_pk = sd_id128_in_set(h->id, CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK); - with_tpm2 = sd_id128_in_set(h->id, CRED_AES256_GCM_BY_TPM2_HMAC, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC) || with_tpm2_pk; - is_tpm2_absent = sd_id128_equal(h->id, CRED_AES256_GCM_BY_TPM2_ABSENT); + with_host_key = sd_id128_in_set(h->id, CRED_AES256_GCM_BY_HOST, CRED_AES256_GCM_BY_HOST_SCOPED, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_SCOPED, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED); + with_tpm2_pk = sd_id128_in_set(h->id, CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED); + with_tpm2 = sd_id128_in_set(h->id, CRED_AES256_GCM_BY_TPM2_HMAC, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_SCOPED) || with_tpm2_pk; + with_null = sd_id128_equal(h->id, CRED_AES256_GCM_BY_NULL); + with_scope = sd_id128_in_set(h->id, CRED_AES256_GCM_BY_HOST_SCOPED, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_SCOPED, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED); - if (!with_host_key && !with_tpm2 && !is_tpm2_absent) - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unknown encryption format, or corrupted data: %m"); + if (!with_host_key && !with_tpm2 && !with_null) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unknown encryption format, or corrupted data."); if (with_tpm2_pk) { r = tpm2_load_pcr_signature(tpm2_signature_path, &signature_json); @@ -1109,7 +1218,7 @@ int decrypt_credential_and_warn( return log_error_errno(r, "Failed to load pcr signature: %m"); } - if (is_tpm2_absent) { + if (with_null && !FLAGS_SET(flags, CREDENTIAL_ALLOW_NULL)) { /* So this is a credential encrypted with a zero length key. We support this to cover for the * case where neither a host key not a TPM2 are available (specifically: initrd environments * where the host key is not yet accessible and no TPM2 chip exists at all), to minimize @@ -1129,8 +1238,19 @@ int decrypt_credential_and_warn( log_debug("Credential uses fixed key for use when TPM2 is absent, and TPM2 indeed is absent. Accepting."); } + if (with_scope) { + if (!uid_is_valid(uid)) + return log_error_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Encrypted file is scoped to a user, but no user selected."); + } else { + /* Refuse to unlock system credentials if user scope is requested. */ + if (uid_is_valid(uid) && !FLAGS_SET(flags, CREDENTIAL_ANY_SCOPE)) + return log_error_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Encrypted file is scoped to the system, but user scope selected."); + + uid = UID_INVALID; + } + /* Now we know the minimum header size */ - if (input_size < offsetof(struct encrypted_credential_header, iv)) + if (input->iov_len < offsetof(struct encrypted_credential_header, iv)) return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Encrypted file too short."); /* Verify some basic header values */ @@ -1145,10 +1265,11 @@ int decrypt_credential_and_warn( /* Ensure we have space for the full header now (we don't know the size of the name hence this is a * lower limit only) */ - if (input_size < + if (input->iov_len < ALIGN8(offsetof(struct encrypted_credential_header, iv) + le32toh(h->iv_size)) + ALIGN8(with_tpm2 ? offsetof(struct tpm2_credential_header, policy_hash_and_blob) : 0) + ALIGN8(with_tpm2_pk ? offsetof(struct tpm2_public_key_credential_header, data) : 0) + + ALIGN8(with_scope ? sizeof(struct scoped_credential_header) : 0) + ALIGN8(offsetof(struct metadata_credential_header, name)) + le32toh(h->tag_size)) return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Encrypted file too short."); @@ -1157,7 +1278,7 @@ int decrypt_credential_and_warn( if (with_tpm2) { #if HAVE_TPM2 - struct tpm2_credential_header* t = (struct tpm2_credential_header*) ((uint8_t*) input + p); + struct tpm2_credential_header* t = (struct tpm2_credential_header*) ((uint8_t*) input->iov_base + p); struct tpm2_public_key_credential_header *z = NULL; if (!TPM2_PCR_MASK_VALID(t->pcr_mask)) @@ -1173,10 +1294,11 @@ int decrypt_credential_and_warn( /* Ensure we have space for the full TPM2 header now (still don't know the name, and its size * though, hence still just a lower limit test only) */ - if (input_size < - ALIGN8(offsetof(struct encrypted_credential_header, iv) + le32toh(h->iv_size)) + + if (input->iov_len < + p + ALIGN8(offsetof(struct tpm2_credential_header, policy_hash_and_blob) + le32toh(t->blob_size) + le32toh(t->policy_hash_size)) + ALIGN8(with_tpm2_pk ? offsetof(struct tpm2_public_key_credential_header, data) : 0) + + ALIGN8(with_scope ? sizeof(struct scoped_credential_header) : 0) + ALIGN8(offsetof(struct metadata_credential_header, name)) + le32toh(h->tag_size)) return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Encrypted file too short."); @@ -1186,17 +1308,17 @@ int decrypt_credential_and_warn( le32toh(t->policy_hash_size)); if (with_tpm2_pk) { - z = (struct tpm2_public_key_credential_header*) ((uint8_t*) input + p); + z = (struct tpm2_public_key_credential_header*) ((uint8_t*) input->iov_base + p); if (!TPM2_PCR_MASK_VALID(le64toh(z->pcr_mask)) || le64toh(z->pcr_mask) == 0) return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "TPM2 PCR mask out of range."); if (le32toh(z->size) > PUBLIC_KEY_MAX) return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Unexpected public key size."); - if (input_size < - ALIGN8(offsetof(struct encrypted_credential_header, iv) + le32toh(h->iv_size)) + - ALIGN8(offsetof(struct tpm2_credential_header, policy_hash_and_blob) + le32toh(t->blob_size) + le32toh(t->policy_hash_size)) + + if (input->iov_len < + p + ALIGN8(offsetof(struct tpm2_public_key_credential_header, data) + le32toh(z->size)) + + ALIGN8(with_scope ? sizeof(struct scoped_credential_header) : 0) + ALIGN8(offsetof(struct metadata_credential_header, name)) + le32toh(h->tag_size)) return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Encrypted file too short."); @@ -1215,21 +1337,16 @@ int decrypt_credential_and_warn( r = tpm2_unseal(tpm2_context, le64toh(t->pcr_mask), le16toh(t->pcr_bank), - z ? z->data : NULL, - z ? le32toh(z->size) : 0, + z ? &IOVEC_MAKE(z->data, le32toh(z->size)) : NULL, z ? le64toh(z->pcr_mask) : 0, signature_json, /* pin= */ NULL, /* pcrlock_policy= */ NULL, le16toh(t->primary_alg), - t->policy_hash_and_blob, - le32toh(t->blob_size), - t->policy_hash_and_blob + le32toh(t->blob_size), - le32toh(t->policy_hash_size), - /* srk_buf= */ NULL, - /* srk_buf_size= */ 0, - &tpm2_key, - &tpm2_key_size); + &IOVEC_MAKE(t->policy_hash_and_blob, le32toh(t->blob_size)), + &IOVEC_MAKE(t->policy_hash_and_blob + le32toh(t->blob_size), le32toh(t->policy_hash_size)), + /* srk= */ NULL, + &tpm2_key); if (r < 0) return log_error_errno(r, "Failed to unseal secret using TPM2: %m"); #else @@ -1237,19 +1354,38 @@ int decrypt_credential_and_warn( #endif } + if (with_scope) { + struct scoped_credential_header* sh = (struct scoped_credential_header*) ((uint8_t*) input->iov_base + p); + + if (le64toh(sh->flags) != SCOPE_HASH_DATA_BASE_FLAGS) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Scoped credential with unsupported flags."); + + if (input->iov_len < + p + + sizeof(struct scoped_credential_header) + + ALIGN8(offsetof(struct metadata_credential_header, name)) + + le32toh(h->tag_size)) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Encrypted file too short."); + + p += sizeof(struct scoped_credential_header); + } + if (with_host_key) { - r = get_credential_host_secret( - 0, - &host_key, - &host_key_size); + r = get_credential_host_secret(/* flags= */ 0, &host_key); if (r < 0) return log_error_errno(r, "Failed to determine local credential key: %m"); } - if (is_tpm2_absent) + if (with_null && !FLAGS_SET(flags, CREDENTIAL_ALLOW_NULL)) log_warning("Warning: using a null key for decryption and authentication. Confidentiality or authenticity are not provided."); - sha256_hash_host_and_tpm2_key(host_key, host_key_size, tpm2_key, tpm2_key_size, md); + sha256_hash_host_and_tpm2_key(&host_key, &tpm2_key, md); + + if (with_scope) { + r = mangle_uid_into_key(uid, md); + if (r < 0) + return r; + } assert_se(cc = EVP_aes_256_gcm()); @@ -1276,41 +1412,41 @@ int decrypt_credential_and_warn( return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to set IV and key: %s", ERR_error_string(ERR_get_error(), NULL)); - if (EVP_DecryptUpdate(context, NULL, &added, input, p) != 1) + if (EVP_DecryptUpdate(context, NULL, &added, input->iov_base, p) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to write AAD data: %s", ERR_error_string(ERR_get_error(), NULL)); - plaintext = malloc(input_size - p - le32toh(h->tag_size)); - if (!plaintext) + plaintext.iov_base = malloc(input->iov_len - p - le32toh(h->tag_size)); + if (!plaintext.iov_base) return -ENOMEM; if (EVP_DecryptUpdate( context, - plaintext, + plaintext.iov_base, &added, - (uint8_t*) input + p, - input_size - p - le32toh(h->tag_size)) != 1) + (uint8_t*) input->iov_base + p, + input->iov_len - p - le32toh(h->tag_size)) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to decrypt data: %s", ERR_error_string(ERR_get_error(), NULL)); assert(added >= 0); - assert((size_t) added <= input_size - p - le32toh(h->tag_size)); - plaintext_size = added; + assert((size_t) added <= input->iov_len - p - le32toh(h->tag_size)); + plaintext.iov_len = added; - if (EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_SET_TAG, le32toh(h->tag_size), (uint8_t*) input + input_size - le32toh(h->tag_size)) != 1) + if (EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_SET_TAG, le32toh(h->tag_size), (uint8_t*) input->iov_base + input->iov_len - le32toh(h->tag_size)) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to set tag: %s", ERR_error_string(ERR_get_error(), NULL)); - if (EVP_DecryptFinal_ex(context, (uint8_t*) plaintext + plaintext_size, &added) != 1) + if (EVP_DecryptFinal_ex(context, (uint8_t*) plaintext.iov_base + plaintext.iov_len, &added) != 1) return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Decryption failed (incorrect key?): %s", ERR_error_string(ERR_get_error(), NULL)); - plaintext_size += added; + plaintext.iov_len += added; - if (plaintext_size < ALIGN8(offsetof(struct metadata_credential_header, name))) + if (plaintext.iov_len < ALIGN8(offsetof(struct metadata_credential_header, name))) return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Metadata header incomplete."); - m = plaintext; + m = plaintext.iov_base; if (le64toh(m->timestamp) != USEC_INFINITY && le64toh(m->not_after) != USEC_INFINITY && @@ -1321,7 +1457,7 @@ int decrypt_credential_and_warn( return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Embedded credential name too long, refusing."); hs = ALIGN8(offsetof(struct metadata_credential_header, name) + le32toh(m->name_size)); - if (plaintext_size < hs) + if (plaintext.iov_len < hs) return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Metadata header incomplete."); if (le32toh(m->name_size) > 0) { @@ -1336,7 +1472,7 @@ int decrypt_credential_and_warn( if (validate_name && !streq(embedded_name, validate_name)) { - r = getenv_bool_secure("SYSTEMD_CREDENTIAL_VALIDATE_NAME"); + r = secure_getenv_bool("SYSTEMD_CREDENTIAL_VALIDATE_NAME"); if (r < 0 && r != -ENXIO) log_debug_errno(r, "Failed to parse $SYSTEMD_CREDENTIAL_VALIDATE_NAME: %m"); if (r != 0) @@ -1352,7 +1488,7 @@ int decrypt_credential_and_warn( if (le64toh(m->not_after) != USEC_INFINITY && le64toh(m->not_after) < validate_timestamp) { - r = getenv_bool_secure("SYSTEMD_CREDENTIAL_VALIDATE_NOT_AFTER"); + r = secure_getenv_bool("SYSTEMD_CREDENTIAL_VALIDATE_NOT_AFTER"); if (r < 0 && r != -ENXIO) log_debug_errno(r, "Failed to parse $SYSTEMD_CREDENTIAL_VALIDATE_NOT_AFTER: %m"); if (r != 0) @@ -1363,33 +1499,245 @@ int decrypt_credential_and_warn( } if (ret) { - char *without_metadata; + _cleanup_(iovec_done_erase) struct iovec without_metadata = {}; - without_metadata = memdup((uint8_t*) plaintext + hs, plaintext_size - hs); - if (!without_metadata) + without_metadata.iov_len = plaintext.iov_len - hs; + without_metadata.iov_base = memdup_suffix0((uint8_t*) plaintext.iov_base + hs, without_metadata.iov_len); + if (!without_metadata.iov_base) return log_oom(); - *ret = without_metadata; + *ret = TAKE_STRUCT(without_metadata); } - if (ret_size) - *ret_size = plaintext_size - hs; - return 0; } #else -int get_credential_host_secret(CredentialSecretFlags flags, void **ret, size_t *ret_size) { +int get_credential_host_secret(CredentialSecretFlags flags, struct iovec *ret) { return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Support for encrypted credentials not available."); } -int encrypt_credential_and_warn(sd_id128_t with_key, const char *name, usec_t timestamp, usec_t not_after, const char *tpm2_device, uint32_t tpm2_hash_pcr_mask, const char *tpm2_pubkey_path, uint32_t tpm2_pubkey_pcr_mask, const void *input, size_t input_size, void **ret, size_t *ret_size) { +int encrypt_credential_and_warn(sd_id128_t with_key, const char *name, usec_t timestamp, usec_t not_after, const char *tpm2_device, uint32_t tpm2_hash_pcr_mask, const char *tpm2_pubkey_path, uint32_t tpm2_pubkey_pcr_mask, uid_t uid, const struct iovec *input, CredentialFlags flags, struct iovec *ret) { return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Support for encrypted credentials not available."); } -int decrypt_credential_and_warn(const char *validate_name, usec_t validate_timestamp, const char *tpm2_device, const char *tpm2_signature_path, const void *input, size_t input_size, void **ret, size_t *ret_size) { +int decrypt_credential_and_warn(const char *validate_name, usec_t validate_timestamp, const char *tpm2_device, const char *tpm2_signature_path, uid_t uid, const struct iovec *input, CredentialFlags flags, struct iovec *ret) { return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Support for encrypted credentials not available."); } #endif + +int ipc_encrypt_credential(const char *name, usec_t timestamp, usec_t not_after, uid_t uid, const struct iovec *input, CredentialFlags flags, struct iovec *ret) { + _cleanup_(varlink_unrefp) Varlink *vl = NULL; + int r; + + assert(input && iovec_is_valid(input)); + assert(ret); + + r = varlink_connect_address(&vl, "/run/systemd/io.systemd.Credentials"); + if (r < 0) + return log_error_errno(r, "Failed to connect to io.systemd.Credentials: %m"); + + /* Mark anything we get from the service as sensitive, given that it might use a NULL cypher, at least in theory */ + r = varlink_set_input_sensitive(vl); + if (r < 0) + return log_error_errno(r, "Failed to enable sensitive Varlink input: %m"); + + /* Create the input data blob object separately, so that we can mark it as sensitive */ + _cleanup_(json_variant_unrefp) JsonVariant *jinput = NULL; + r = json_build(&jinput, JSON_BUILD_IOVEC_BASE64(input)); + if (r < 0) + return log_error_errno(r, "Failed to create input object: %m"); + + json_variant_sensitive(jinput); + + _cleanup_(json_variant_unrefp) JsonVariant *reply = NULL; + const char *error_id = NULL; + r = varlink_callb(vl, + "io.systemd.Credentials.Encrypt", + &reply, + &error_id, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR_CONDITION(name, "name", JSON_BUILD_STRING(name)), + JSON_BUILD_PAIR("data", JSON_BUILD_VARIANT(jinput)), + JSON_BUILD_PAIR_CONDITION(timestamp != USEC_INFINITY, "timestamp", JSON_BUILD_UNSIGNED(timestamp)), + JSON_BUILD_PAIR_CONDITION(not_after != USEC_INFINITY, "notAfter", JSON_BUILD_UNSIGNED(not_after)), + JSON_BUILD_PAIR_CONDITION(!FLAGS_SET(flags, CREDENTIAL_ANY_SCOPE), "scope", JSON_BUILD_STRING(uid_is_valid(uid) ? "user" : "system")), + JSON_BUILD_PAIR_CONDITION(uid_is_valid(uid), "uid", JSON_BUILD_UNSIGNED(uid)))); + if (r < 0) + return log_error_errno(r, "Failed to call Encrypt() varlink call."); + if (!isempty(error_id)) { + if (streq(error_id, "io.systemd.Credentials.NoSuchUser")) + return log_error_errno(SYNTHETIC_ERRNO(ESRCH), "No such user."); + + return log_error_errno(varlink_error_to_errno(error_id, reply), "Failed to encrypt: %s", error_id); + } + + r = json_dispatch( + reply, + (const JsonDispatch[]) { + { "blob", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, PTR_TO_SIZE(ret), JSON_MANDATORY }, + {}, + }, + JSON_LOG|JSON_ALLOW_EXTENSIONS, + /* userdata= */ NULL); + if (r < 0) + return r; + + return 0; +} + +int ipc_decrypt_credential(const char *validate_name, usec_t validate_timestamp, uid_t uid, const struct iovec *input, CredentialFlags flags, struct iovec *ret) { + _cleanup_(varlink_unrefp) Varlink *vl = NULL; + int r; + + assert(input && iovec_is_valid(input)); + assert(ret); + + r = varlink_connect_address(&vl, "/run/systemd/io.systemd.Credentials"); + if (r < 0) + return log_error_errno(r, "Failed to connect to io.systemd.Credentials: %m"); + + r = varlink_set_input_sensitive(vl); + if (r < 0) + return log_error_errno(r, "Failed to enable sensitive Varlink input: %m"); + + /* Create the input data blob object separately, so that we can mark it as sensitive (it's supposed + * to be encrypted, but who knows maybe it uses the NULL cypher). */ + _cleanup_(json_variant_unrefp) JsonVariant *jinput = NULL; + r = json_build(&jinput, JSON_BUILD_IOVEC_BASE64(input)); + if (r < 0) + return log_error_errno(r, "Failed to create input object: %m"); + + json_variant_sensitive(jinput); + + _cleanup_(json_variant_unrefp) JsonVariant *reply = NULL; + const char *error_id = NULL; + r = varlink_callb(vl, + "io.systemd.Credentials.Decrypt", + &reply, + &error_id, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR_CONDITION(validate_name, "name", JSON_BUILD_STRING(validate_name)), + JSON_BUILD_PAIR("blob", JSON_BUILD_VARIANT(jinput)), + JSON_BUILD_PAIR_CONDITION(validate_timestamp != USEC_INFINITY, "timestamp", JSON_BUILD_UNSIGNED(validate_timestamp)), + JSON_BUILD_PAIR_CONDITION(!FLAGS_SET(flags, CREDENTIAL_ANY_SCOPE), "scope", JSON_BUILD_STRING(uid_is_valid(uid) ? "user" : "system")), + JSON_BUILD_PAIR_CONDITION(uid_is_valid(uid), "uid", JSON_BUILD_UNSIGNED(uid)))); + if (r < 0) + return log_error_errno(r, "Failed to call Decrypt() varlink call."); + if (!isempty(error_id)) { + if (streq(error_id, "io.systemd.Credentials.BadFormat")) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Bad credential format."); + if (streq(error_id, "io.systemd.Credentials.NameMismatch")) + return log_error_errno(SYNTHETIC_ERRNO(EREMOTE), "Name in credential doesn't match expectations."); + if (streq(error_id, "io.systemd.Credentials.TimeMismatch")) + return log_error_errno(SYNTHETIC_ERRNO(ESTALE), "Outside of credential validity time window."); + if (streq(error_id, "io.systemd.Credentials.NoSuchUser")) + return log_error_errno(SYNTHETIC_ERRNO(ESRCH), "No such user."); + if (streq(error_id, "io.systemd.Credentials.BadScope")) + return log_error_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Scope mismtach."); + + return log_error_errno(varlink_error_to_errno(error_id, reply), "Failed to decrypt: %s", error_id); + } + + r = json_dispatch( + reply, + (const JsonDispatch[]) { + { "data", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, PTR_TO_SIZE(ret), JSON_MANDATORY }, + {}, + }, + JSON_LOG|JSON_ALLOW_EXTENSIONS, + /* userdata= */ NULL); + if (r < 0) + return r; + + return 0; +} + +static int pick_up_credential_one( + int credential_dir_fd, + const char *credential_name, + const PickUpCredential *table_entry) { + + _cleanup_free_ char *fn = NULL, *target_path = NULL; + const char *e; + int r; + + assert(credential_dir_fd >= 0); + assert(credential_name); + assert(table_entry); + + e = startswith(credential_name, table_entry->credential_prefix); + if (!e) + return 0; /* unmatched */ + + fn = strjoin(e, table_entry->filename_suffix); + if (!fn) + return log_oom(); + + if (!filename_is_valid(fn)) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "Passed credential '%s' would result in invalid filename '%s'.", + credential_name, fn); + + r = mkdir_p_label(table_entry->target_dir, 0755); + if (r < 0) + return log_warning_errno(r, "Failed to create '%s': %m", table_entry->target_dir); + + target_path = path_join(table_entry->target_dir, fn); + if (!target_path) + return log_oom(); + + r = copy_file_at( + credential_dir_fd, credential_name, + AT_FDCWD, target_path, + /* open_flags= */ 0, + 0644, + /* flags= */ 0); + if (r < 0) + return log_warning_errno(r, "Failed to copy credential %s → file %s: %m", + credential_name, target_path); + + log_info("Installed %s from credential.", target_path); + return 1; /* done */ +} + +int pick_up_credentials(const PickUpCredential *table, size_t n_table_entry) { + _cleanup_close_ int credential_dir_fd = -EBADF; + int r, ret = 0; + + assert(table); + assert(n_table_entry > 0); + + credential_dir_fd = open_credentials_dir(); + if (IN_SET(credential_dir_fd, -ENXIO, -ENOENT)) { + /* Credential env var not set, or dir doesn't exist. */ + log_debug("No credentials found."); + return 0; + } + if (credential_dir_fd < 0) + return log_error_errno(credential_dir_fd, "Failed to open credentials directory: %m"); + + _cleanup_free_ DirectoryEntries *des = NULL; + r = readdir_all(credential_dir_fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_ENSURE_TYPE, &des); + if (r < 0) + return log_error_errno(r, "Failed to enumerate credentials: %m"); + + FOREACH_ARRAY(i, des->entries, des->n_entries) { + struct dirent *de = *i; + + if (de->d_type != DT_REG) + continue; + + FOREACH_ARRAY(t, table, n_table_entry) { + r = pick_up_credential_one(credential_dir_fd, de->d_name, t); + if (r != 0) { + RET_GATHER(ret, r); + break; /* Done, or failed. Let's move to the next credential. */ + } + } + } + + return ret; +} diff --git a/src/shared/creds-util.h b/src/shared/creds-util.h index 5e39a6a..b80755b 100644 --- a/src/shared/creds-util.h +++ b/src/shared/creds-util.h @@ -31,6 +31,8 @@ bool credential_glob_valid(const char *s); int get_credentials_dir(const char **ret); int get_encrypted_credentials_dir(const char **ret); +int open_credentials_dir(void); + /* Where creds have been passed to the system */ #define SYSTEM_CREDENTIALS_DIRECTORY "/run/credentials/@system" #define ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY "/run/credentials/@encrypted" @@ -51,21 +53,31 @@ typedef enum CredentialSecretFlags { CREDENTIAL_SECRET_FAIL_ON_TEMPORARY_FS = 1 << 2, } CredentialSecretFlags; -int get_credential_host_secret(CredentialSecretFlags flags, void **ret, size_t *ret_size); +int get_credential_host_secret(CredentialSecretFlags flags, struct iovec *ret); int get_credential_user_password(const char *username, char **ret_password, bool *ret_is_hashed); +typedef enum CredentialFlags { + CREDENTIAL_ALLOW_NULL = 1 << 0, /* allow decryption of NULL key, even if TPM is around */ + CREDENTIAL_ANY_SCOPE = 1 << 1, /* allow decryption of both system and user credentials */ +} CredentialFlags; + /* The four modes we support: keyed only by on-disk key, only by TPM2 HMAC key, and by the combination of * both, as well as one with a fixed zero length key if TPM2 is missing (the latter of course provides no * authenticity or confidentiality, but is still useful for integrity protection, and makes things simpler * for us to handle). */ #define CRED_AES256_GCM_BY_HOST SD_ID128_MAKE(5a,1c,6a,86,df,9d,40,96,b1,d5,a6,5e,08,62,f1,9a) +#define CRED_AES256_GCM_BY_HOST_SCOPED SD_ID128_MAKE(55,b9,ed,1d,38,59,4d,43,a8,31,9d,2e,bb,33,2a,c6) #define CRED_AES256_GCM_BY_TPM2_HMAC SD_ID128_MAKE(0c,7c,c0,7b,11,76,45,91,9c,4b,0b,ea,08,bc,20,fe) #define CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK SD_ID128_MAKE(fa,f7,eb,93,41,e3,41,2c,a1,a4,36,f9,5a,29,36,2f) #define CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC SD_ID128_MAKE(93,a8,94,09,48,74,44,90,90,ca,f2,fc,93,ca,b5,53) +#define CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_SCOPED \ + SD_ID128_MAKE(ef,4a,c1,36,79,a9,48,0e,a7,db,68,89,7f,9f,16,5d) #define CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK \ SD_ID128_MAKE(af,49,50,a8,49,13,4e,b1,a7,38,46,30,4f,f3,0c,05) -#define CRED_AES256_GCM_BY_TPM2_ABSENT SD_ID128_MAKE(05,84,69,da,f6,f5,43,24,80,05,49,da,0f,8e,a2,fb) +#define CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED \ + SD_ID128_MAKE(ad,bc,4c,a3,ef,b6,42,01,ba,88,1b,6f,2e,40,95,ea) +#define CRED_AES256_GCM_BY_NULL SD_ID128_MAKE(05,84,69,da,f6,f5,43,24,80,05,49,da,0f,8e,a2,fb) /* Two special IDs to pick a general automatic mode (i.e. tpm2+host if TPM2 exists, only host otherwise) or * an initrd-specific automatic mode (i.e. tpm2 if firmware can do it, otherwise fixed zero-length key, and @@ -74,6 +86,18 @@ int get_credential_user_password(const char *username, char **ret_password, bool * with an underscore. */ #define _CRED_AUTO SD_ID128_MAKE(a2,19,cb,07,85,b2,4c,04,b1,6d,18,ca,b9,d2,ee,01) #define _CRED_AUTO_INITRD SD_ID128_MAKE(02,dc,8e,de,3a,02,43,ab,a9,ec,54,9c,05,e6,a0,71) +#define _CRED_AUTO_SCOPED SD_ID128_MAKE(23,88,96,85,6f,74,48,8a,9c,78,6f,6a,b0,e7,3b,6a) + +int encrypt_credential_and_warn(sd_id128_t with_key, const char *name, usec_t timestamp, usec_t not_after, const char *tpm2_device, uint32_t tpm2_hash_pcr_mask, const char *tpm2_pubkey_path, uint32_t tpm2_pubkey_pcr_mask, uid_t uid, const struct iovec *input, CredentialFlags flags, struct iovec *ret); +int decrypt_credential_and_warn(const char *validate_name, usec_t validate_timestamp, const char *tpm2_device, const char *tpm2_signature_path, uid_t uid, const struct iovec *input, CredentialFlags flags, struct iovec *ret); + +int ipc_encrypt_credential(const char *name, usec_t timestamp, usec_t not_after, uid_t uid, const struct iovec *input, CredentialFlags flags, struct iovec *ret); +int ipc_decrypt_credential(const char *validate_name, usec_t validate_timestamp, uid_t uid, const struct iovec *input, CredentialFlags flags, struct iovec *ret); + +typedef struct PickUpCredential { + const char *credential_prefix; + const char *target_dir; + const char *filename_suffix; +} PickUpCredential; -int encrypt_credential_and_warn(sd_id128_t with_key, const char *name, usec_t timestamp, usec_t not_after, const char *tpm2_device, uint32_t tpm2_hash_pcr_mask, const char *tpm2_pubkey_path, uint32_t tpm2_pubkey_pcr_mask, const void *input, size_t input_size, void **ret, size_t *ret_size); -int decrypt_credential_and_warn(const char *validate_name, usec_t validate_timestamp, const char *tpm2_device, const char *tpm2_signature_path, const void *input, size_t input_size, void **ret, size_t *ret_size); +int pick_up_credentials(const PickUpCredential *table, size_t n_table_entry); diff --git a/src/shared/cryptsetup-fido2.c b/src/shared/cryptsetup-fido2.c index 285b82a..5ab5cef 100644 --- a/src/shared/cryptsetup-fido2.c +++ b/src/shared/cryptsetup-fido2.c @@ -24,11 +24,11 @@ int acquire_fido2_key( const void *key_data, size_t key_data_size, usec_t until, - bool headless, Fido2EnrollFlags required, + const char *askpw_credential, + AskPasswordFlags askpw_flags, void **ret_decrypted_key, - size_t *ret_decrypted_key_size, - AskPasswordFlags ask_password_flags) { + size_t *ret_decrypted_key_size) { _cleanup_(erase_and_freep) char *envpw = NULL; _cleanup_strv_free_erase_ char **pins = NULL; @@ -38,11 +38,11 @@ int acquire_fido2_key( size_t salt_size; int r; - if ((required & (FIDO2ENROLL_PIN | FIDO2ENROLL_UP | FIDO2ENROLL_UV)) && headless) + if ((required & (FIDO2ENROLL_PIN | FIDO2ENROLL_UP | FIDO2ENROLL_UV)) && FLAGS_SET(askpw_flags, ASK_PASSWORD_HEADLESS)) return log_error_errno(SYNTHETIC_ERRNO(ENOPKG), "Local verification is required to unlock this volume, but the 'headless' parameter was set."); - ask_password_flags |= ASK_PASSWORD_PUSH_CACHE | ASK_PASSWORD_ACCEPT_CACHED; + askpw_flags |= ASK_PASSWORD_PUSH_CACHE | ASK_PASSWORD_ACCEPT_CACHED; assert(cid); assert(key_file || key_data); @@ -115,15 +115,22 @@ int acquire_fido2_key( device_exists = true; /* that a PIN is needed/wasn't correct means that we managed to * talk to a device */ - if (headless) + if (FLAGS_SET(askpw_flags, ASK_PASSWORD_HEADLESS)) return log_error_errno(SYNTHETIC_ERRNO(ENOPKG), "PIN querying disabled via 'headless' option. Use the '$PIN' environment variable."); + static const AskPasswordRequest req = { + .message = "Please enter security token PIN:", + .icon = "drive-harddisk", + .keyring = "fido2-pin", + .credential = "cryptsetup.fido2-pin", + }; + pins = strv_free_erase(pins); - r = ask_password_auto("Please enter security token PIN:", "drive-harddisk", NULL, "fido2-pin", "cryptsetup.fido2-pin", until, ask_password_flags, &pins); + r = ask_password_auto(&req, until, askpw_flags, &pins); if (r < 0) return log_error_errno(r, "Failed to ask for user password: %m"); - ask_password_flags &= ~ASK_PASSWORD_ACCEPT_CACHED; + askpw_flags &= ~ASK_PASSWORD_ACCEPT_CACHED; } } @@ -133,10 +140,10 @@ int acquire_fido2_key_auto( const char *friendly_name, const char *fido2_device, usec_t until, - bool headless, + const char *askpw_credential, + AskPasswordFlags askpw_flags, void **ret_decrypted_key, - size_t *ret_decrypted_key_size, - AskPasswordFlags ask_password_flags) { + size_t *ret_decrypted_key_size) { _cleanup_free_ void *cid = NULL; size_t cid_size = 0; @@ -150,7 +157,7 @@ int acquire_fido2_key_auto( /* Loads FIDO2 metadata from LUKS2 JSON token headers. */ - for (int token = 0; token < sym_crypt_token_max(CRYPT_LUKS2); token ++) { + for (int token = 0; token < sym_crypt_token_max(CRYPT_LUKS2); token++) { _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; JsonVariant *w; _cleanup_free_ void *salt = NULL; @@ -177,7 +184,7 @@ int acquire_fido2_key_auto( return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "FIDO2 token data lacks 'fido2-credential' field."); - r = unbase64mem(json_variant_string(w), SIZE_MAX, &cid, &cid_size); + r = unbase64mem(json_variant_string(w), &cid, &cid_size); if (r < 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid base64 data in 'fido2-credential' field."); @@ -189,7 +196,7 @@ int acquire_fido2_key_auto( assert(!salt); assert(salt_size == 0); - r = unbase64mem(json_variant_string(w), SIZE_MAX, &salt, &salt_size); + r = unbase64mem(json_variant_string(w), &salt, &salt_size); if (r < 0) return log_error_errno(r, "Failed to decode base64 encoded salt."); @@ -254,10 +261,11 @@ int acquire_fido2_key_auto( /* key_file_offset= */ 0, salt, salt_size, until, - headless, required, - ret_decrypted_key, ret_decrypted_key_size, - ask_password_flags); + "cryptsetup.fido2-pin", + askpw_flags, + ret_decrypted_key, + ret_decrypted_key_size); if (ret == 0) break; } diff --git a/src/shared/cryptsetup-fido2.h b/src/shared/cryptsetup-fido2.h index d96bb40..bd25566 100644 --- a/src/shared/cryptsetup-fido2.h +++ b/src/shared/cryptsetup-fido2.h @@ -23,11 +23,11 @@ int acquire_fido2_key( const void *key_data, size_t key_data_size, usec_t until, - bool headless, Fido2EnrollFlags required, + const char *askpw_credential, + AskPasswordFlags askpw_flags, void **ret_decrypted_key, - size_t *ret_decrypted_key_size, - AskPasswordFlags ask_password_flags); + size_t *ret_decrypted_key_size); int acquire_fido2_key_auto( struct crypt_device *cd, @@ -35,10 +35,10 @@ int acquire_fido2_key_auto( const char *friendly_name, const char *fido2_device, usec_t until, - bool headless, + const char *askpw_credential, + AskPasswordFlags askpw_flags, void **ret_decrypted_key, - size_t *ret_decrypted_key_size, - AskPasswordFlags ask_password_flags); + size_t *ret_decrypted_key_size); #else @@ -55,11 +55,11 @@ static inline int acquire_fido2_key( const void *key_data, size_t key_data_size, usec_t until, - bool headless, Fido2EnrollFlags required, + const char *askpw_credential, + AskPasswordFlags askpw_flags, void **ret_decrypted_key, - size_t *ret_decrypted_key_size, - AskPasswordFlags ask_password_flags) { + size_t *ret_decrypted_key_size) { return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "FIDO2 token support not available."); @@ -71,10 +71,10 @@ static inline int acquire_fido2_key_auto( const char *friendly_name, const char *fido2_device, usec_t until, - bool headless, + const char *askpw_credential, + AskPasswordFlags askpw_flags, void **ret_decrypted_key, - size_t *ret_decrypted_key_size, - AskPasswordFlags ask_password_flags) { + size_t *ret_decrypted_key_size) { return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "FIDO2 token support not available."); diff --git a/src/shared/cryptsetup-tpm2.c b/src/shared/cryptsetup-tpm2.c new file mode 100644 index 0000000..bfd7d3a --- /dev/null +++ b/src/shared/cryptsetup-tpm2.c @@ -0,0 +1,302 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "alloc-util.h" +#include "ask-password-api.h" +#include "cryptsetup-tpm2.h" +#include "env-util.h" +#include "fileio.h" +#include "hexdecoct.h" +#include "json.h" +#include "parse-util.h" +#include "random-util.h" +#include "sha256.h" +#include "tpm2-util.h" + +static int get_pin( + usec_t until, + const char *askpw_credential, + AskPasswordFlags askpw_flags, + char **ret_pin_str) { + _cleanup_(erase_and_freep) char *pin_str = NULL; + _cleanup_strv_free_erase_ char **pin = NULL; + int r; + + assert(ret_pin_str); + + r = getenv_steal_erase("PIN", &pin_str); + if (r < 0) + return log_error_errno(r, "Failed to acquire PIN from environment: %m"); + if (!r) { + if (FLAGS_SET(askpw_flags, ASK_PASSWORD_HEADLESS)) + return log_error_errno( + SYNTHETIC_ERRNO(ENOPKG), + "PIN querying disabled via 'headless' option. " + "Use the '$PIN' environment variable."); + + AskPasswordRequest req = { + .message = "Please enter TPM2 PIN:", + .icon = "drive-harddisk", + .keyring = "tpm2-pin", + .credential = askpw_credential, + }; + + pin = strv_free_erase(pin); + r = ask_password_auto(&req, until, askpw_flags, &pin); + if (r < 0) + return log_error_errno(r, "Failed to ask for user pin: %m"); + assert(strv_length(pin) == 1); + + pin_str = strdup(pin[0]); + if (!pin_str) + return log_oom(); + } + + *ret_pin_str = TAKE_PTR(pin_str); + + return r; +} + +int acquire_tpm2_key( + const char *volume_name, + const char *device, + uint32_t hash_pcr_mask, + uint16_t pcr_bank, + const struct iovec *pubkey, + uint32_t pubkey_pcr_mask, + const char *signature_path, + const char *pcrlock_path, + uint16_t primary_alg, + const char *key_file, + size_t key_file_size, + uint64_t key_file_offset, + const struct iovec *key_data, + const struct iovec *policy_hash, + const struct iovec *salt, + const struct iovec *srk, + const struct iovec *pcrlock_nv, + TPM2Flags flags, + usec_t until, + const char *askpw_credential, + AskPasswordFlags askpw_flags, + struct iovec *ret_decrypted_key) { + + _cleanup_(json_variant_unrefp) JsonVariant *signature_json = NULL; + _cleanup_free_ void *loaded_blob = NULL; + _cleanup_free_ char *auto_device = NULL; + struct iovec blob; + int r; + + assert(iovec_is_valid(salt)); + + if (!device) { + r = tpm2_find_device_auto(&auto_device); + if (r == -ENODEV) + return -EAGAIN; /* Tell the caller to wait for a TPM2 device to show up */ + if (r < 0) + return log_error_errno(r, "Could not find TPM2 device: %m"); + + device = auto_device; + } + + if (iovec_is_set(key_data)) + blob = *key_data; + else { + _cleanup_free_ char *bindname = NULL; + + /* If we read the salt via AF_UNIX, make this client recognizable */ + if (asprintf(&bindname, "@%" PRIx64"/cryptsetup-tpm2/%s", random_u64(), volume_name) < 0) + return log_oom(); + + r = read_full_file_full( + AT_FDCWD, key_file, + key_file_offset == 0 ? UINT64_MAX : key_file_offset, + key_file_size == 0 ? SIZE_MAX : key_file_size, + READ_FULL_FILE_CONNECT_SOCKET, + bindname, + (char**) &loaded_blob, &blob.iov_len); + if (r < 0) + return r; + + blob.iov_base = loaded_blob; + } + + if (pubkey_pcr_mask != 0) { + r = tpm2_load_pcr_signature(signature_path, &signature_json); + if (r < 0) + return log_error_errno(r, "Failed to load pcr signature: %m"); + } + + _cleanup_(tpm2_pcrlock_policy_done) Tpm2PCRLockPolicy pcrlock_policy = {}; + + if (FLAGS_SET(flags, TPM2_FLAGS_USE_PCRLOCK)) { + r = tpm2_pcrlock_policy_load(pcrlock_path, &pcrlock_policy); + if (r < 0) + return r; + if (r == 0) { + /* Not found? Then search among passed credentials */ + r = tpm2_pcrlock_policy_from_credentials(srk, pcrlock_nv, &pcrlock_policy); + if (r < 0) + return r; + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(EREMOTE), "Couldn't find pcrlock policy for volume."); + } + } + + _cleanup_(tpm2_context_unrefp) Tpm2Context *tpm2_context = NULL; + r = tpm2_context_new_or_warn(device, &tpm2_context); + if (r < 0) + return r; + + if (!(flags & TPM2_FLAGS_USE_PIN)) { + r = tpm2_unseal(tpm2_context, + hash_pcr_mask, + pcr_bank, + pubkey, + pubkey_pcr_mask, + signature_json, + /* pin= */ NULL, + FLAGS_SET(flags, TPM2_FLAGS_USE_PCRLOCK) ? &pcrlock_policy : NULL, + primary_alg, + &blob, + policy_hash, + srk, + ret_decrypted_key); + if (r < 0) + return log_error_errno(r, "Failed to unseal secret using TPM2: %m"); + + return r; + } + + for (int i = 5;; i--) { + _cleanup_(erase_and_freep) char *pin_str = NULL, *b64_salted_pin = NULL; + + if (i <= 0) + return -EACCES; + + r = get_pin(until, askpw_credential, askpw_flags, &pin_str); + if (r < 0) + return r; + + if (iovec_is_set(salt)) { + uint8_t salted_pin[SHA256_DIGEST_SIZE] = {}; + CLEANUP_ERASE(salted_pin); + + r = tpm2_util_pbkdf2_hmac_sha256(pin_str, strlen(pin_str), salt->iov_base, salt->iov_len, salted_pin); + if (r < 0) + return log_error_errno(r, "Failed to perform PBKDF2: %m"); + + r = base64mem(salted_pin, sizeof(salted_pin), &b64_salted_pin); + if (r < 0) + return log_error_errno(r, "Failed to base64 encode salted pin: %m"); + } else + /* no salting needed, backwards compat with non-salted pins */ + b64_salted_pin = TAKE_PTR(pin_str); + + r = tpm2_unseal(tpm2_context, + hash_pcr_mask, + pcr_bank, + pubkey, + pubkey_pcr_mask, + signature_json, + b64_salted_pin, + FLAGS_SET(flags, TPM2_FLAGS_USE_PCRLOCK) ? &pcrlock_policy : NULL, + primary_alg, + &blob, + policy_hash, + srk, + ret_decrypted_key); + if (r < 0) { + log_error_errno(r, "Failed to unseal secret using TPM2: %m"); + + /* We get this error in case there is an authentication policy mismatch. This should + * not happen, but this avoids confusing behavior, just in case. */ + if (!IN_SET(r, -EPERM, -ENOLCK)) + continue; + } + + return r; + } +} + +int find_tpm2_auto_data( + struct crypt_device *cd, + uint32_t search_pcr_mask, + int start_token, + uint32_t *ret_hash_pcr_mask, + uint16_t *ret_pcr_bank, + struct iovec *ret_pubkey, + uint32_t *ret_pubkey_pcr_mask, + uint16_t *ret_primary_alg, + struct iovec *ret_blob, + struct iovec *ret_policy_hash, + struct iovec *ret_salt, + struct iovec *ret_srk, + struct iovec *ret_pcrlock_nv, + TPM2Flags *ret_flags, + int *ret_keyslot, + int *ret_token) { + + int r, token; + + assert(cd); + + for (token = start_token; token < sym_crypt_token_max(CRYPT_LUKS2); token++) { + _cleanup_(iovec_done) struct iovec blob = {}, policy_hash = {}, pubkey = {}, salt = {}, srk = {}, pcrlock_nv = {}; + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + uint32_t hash_pcr_mask, pubkey_pcr_mask; + uint16_t pcr_bank, primary_alg; + TPM2Flags flags; + int keyslot; + + r = cryptsetup_get_token_as_json(cd, token, "systemd-tpm2", &v); + if (IN_SET(r, -ENOENT, -EINVAL, -EMEDIUMTYPE)) + continue; + if (r < 0) + return log_error_errno(r, "Failed to read JSON token data off disk: %m"); + + r = tpm2_parse_luks2_json( + v, + &keyslot, + &hash_pcr_mask, + &pcr_bank, + &pubkey, + &pubkey_pcr_mask, + &primary_alg, + &blob, + &policy_hash, + &salt, + &srk, + &pcrlock_nv, + &flags); + if (r == -EUCLEAN) /* Gracefully handle issues in JSON fields not owned by us */ + continue; + if (r < 0) + return log_error_errno(r, "Failed to parse TPM2 JSON data: %m"); + + if (search_pcr_mask == UINT32_MAX || + search_pcr_mask == hash_pcr_mask) { + + if (start_token <= 0) + log_info("Automatically discovered security TPM2 token unlocks volume."); + + *ret_hash_pcr_mask = hash_pcr_mask; + *ret_pcr_bank = pcr_bank; + *ret_pubkey = TAKE_STRUCT(pubkey); + *ret_pubkey_pcr_mask = pubkey_pcr_mask; + *ret_primary_alg = primary_alg; + *ret_blob = TAKE_STRUCT(blob); + *ret_policy_hash = TAKE_STRUCT(policy_hash); + *ret_salt = TAKE_STRUCT(salt); + *ret_keyslot = keyslot; + *ret_token = token; + *ret_srk = TAKE_STRUCT(srk); + *ret_pcrlock_nv = TAKE_STRUCT(pcrlock_nv); + *ret_flags = flags; + return 0; + } + + /* PCR mask doesn't match what is configured, ignore this entry, let's see next */ + } + + return log_error_errno(SYNTHETIC_ERRNO(ENXIO), "No valid TPM2 token data found."); +} diff --git a/src/shared/cryptsetup-tpm2.h b/src/shared/cryptsetup-tpm2.h new file mode 100644 index 0000000..b9905f4 --- /dev/null +++ b/src/shared/cryptsetup-tpm2.h @@ -0,0 +1,108 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include <sys/types.h> + +#include "ask-password-api.h" +#include "cryptsetup-util.h" +#include "log.h" +#include "time-util.h" +#include "tpm2-util.h" + +#if HAVE_TPM2 + +int acquire_tpm2_key( + const char *volume_name, + const char *device, + uint32_t hash_pcr_mask, + uint16_t pcr_bank, + const struct iovec *pubkey, + uint32_t pubkey_pcr_mask, + const char *signature_path, + const char *pcrlock_path, + uint16_t primary_alg, + const char *key_file, + size_t key_file_size, + uint64_t key_file_offset, + const struct iovec *key_data, + const struct iovec *policy_hash, + const struct iovec *salt, + const struct iovec *srk, + const struct iovec *pcrlock_nv, + TPM2Flags flags, + usec_t until, + const char *askpw_credential, + AskPasswordFlags askpw_flags, + struct iovec *ret_decrypted_key); + +int find_tpm2_auto_data( + struct crypt_device *cd, + uint32_t search_pcr_mask, + int start_token, + uint32_t *ret_hash_pcr_mask, + uint16_t *ret_pcr_bank, + struct iovec *ret_pubkey, + uint32_t *ret_pubkey_pcr_mask, + uint16_t *ret_primary_alg, + struct iovec *ret_blob, + struct iovec *ret_policy_hash, + struct iovec *ret_salt, + struct iovec *ret_srk, + struct iovec *ret_pcrlock_nv, + TPM2Flags *ret_flags, + int *ret_keyslot, + int *ret_token); + +#else + +static inline int acquire_tpm2_key( + const char *volume_name, + const char *device, + uint32_t hash_pcr_mask, + uint16_t pcr_bank, + const struct iovec *pubkey, + uint32_t pubkey_pcr_mask, + const char *signature_path, + const char *pcrlock_path, + uint16_t primary_alg, + const char *key_file, + size_t key_file_size, + uint64_t key_file_offset, + const struct iovec *key_data, + const struct iovec *policy_hash, + const struct iovec *salt, + const struct iovec *srk, + const struct iovec *pcrlock_nv, + TPM2Flags flags, + usec_t until, + const char *askpw_credential, + AskPasswordFlags askpw_flags, + struct iovec *ret_decrypted_key) { + + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "TPM2 support not available."); +} + +static inline int find_tpm2_auto_data( + struct crypt_device *cd, + uint32_t search_pcr_mask, + int start_token, + uint32_t *ret_hash_pcr_mask, + uint16_t *ret_pcr_bank, + struct iovec *ret_pubkey, + uint32_t *ret_pubkey_pcr_mask, + uint16_t *ret_primary_alg, + struct iovec *ret_blob, + struct iovec *ret_policy_hash, + struct iovec *ret_salt, + struct iovec *ret_srk, + struct iovec *ret_pcrlock_nv, + TPM2Flags *ret_flags, + int *ret_keyslot, + int *ret_token) { + + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "TPM2 support not available."); +} + +#endif diff --git a/src/shared/cryptsetup-util.c b/src/shared/cryptsetup-util.c index ab5764d..288e6e8 100644 --- a/src/shared/cryptsetup-util.c +++ b/src/shared/cryptsetup-util.c @@ -9,58 +9,62 @@ #if HAVE_LIBCRYPTSETUP static void *cryptsetup_dl = NULL; -int (*sym_crypt_activate_by_passphrase)(struct crypt_device *cd, const char *name, int keyslot, const char *passphrase, size_t passphrase_size, uint32_t flags); +DLSYM_FUNCTION(crypt_activate_by_passphrase); #if HAVE_CRYPT_ACTIVATE_BY_SIGNED_KEY -int (*sym_crypt_activate_by_signed_key)(struct crypt_device *cd, const char *name, const char *volume_key, size_t volume_key_size, const char *signature, size_t signature_size, uint32_t flags); +DLSYM_FUNCTION(crypt_activate_by_signed_key); #endif -int (*sym_crypt_activate_by_volume_key)(struct crypt_device *cd, const char *name, const char *volume_key, size_t volume_key_size, uint32_t flags); -int (*sym_crypt_deactivate_by_name)(struct crypt_device *cd, const char *name, uint32_t flags); -int (*sym_crypt_format)(struct crypt_device *cd, const char *type, const char *cipher, const char *cipher_mode, const char *uuid, const char *volume_key, size_t volume_key_size, void *params); -void (*sym_crypt_free)(struct crypt_device *cd); -const char *(*sym_crypt_get_cipher)(struct crypt_device *cd); -const char *(*sym_crypt_get_cipher_mode)(struct crypt_device *cd); -uint64_t (*sym_crypt_get_data_offset)(struct crypt_device *cd); -const char *(*sym_crypt_get_device_name)(struct crypt_device *cd); -const char *(*sym_crypt_get_dir)(void); -const char *(*sym_crypt_get_type)(struct crypt_device *cd); -const char *(*sym_crypt_get_uuid)(struct crypt_device *cd); -int (*sym_crypt_get_verity_info)(struct crypt_device *cd, struct crypt_params_verity *vp); -int (*sym_crypt_get_volume_key_size)(struct crypt_device *cd); -int (*sym_crypt_init)(struct crypt_device **cd, const char *device); -int (*sym_crypt_init_by_name)(struct crypt_device **cd, const char *name); -int (*sym_crypt_keyslot_add_by_volume_key)(struct crypt_device *cd, int keyslot, const char *volume_key, size_t volume_key_size, const char *passphrase, size_t passphrase_size); -int (*sym_crypt_keyslot_destroy)(struct crypt_device *cd, int keyslot); -int (*sym_crypt_keyslot_max)(const char *type); -int (*sym_crypt_load)(struct crypt_device *cd, const char *requested_type, void *params); -int (*sym_crypt_resize)(struct crypt_device *cd, const char *name, uint64_t new_size); -int (*sym_crypt_resume_by_passphrase)(struct crypt_device *cd, const char *name, int keyslot, const char *passphrase, size_t passphrase_size); -int (*sym_crypt_set_data_device)(struct crypt_device *cd, const char *device); -void (*sym_crypt_set_debug_level)(int level); -void (*sym_crypt_set_log_callback)(struct crypt_device *cd, void (*log)(int level, const char *msg, void *usrptr), void *usrptr); +DLSYM_FUNCTION(crypt_activate_by_volume_key); +DLSYM_FUNCTION(crypt_deactivate_by_name); +DLSYM_FUNCTION(crypt_format); +DLSYM_FUNCTION(crypt_free); +DLSYM_FUNCTION(crypt_get_cipher); +DLSYM_FUNCTION(crypt_get_cipher_mode); +DLSYM_FUNCTION(crypt_get_data_offset); +DLSYM_FUNCTION(crypt_get_device_name); +DLSYM_FUNCTION(crypt_get_dir); +DLSYM_FUNCTION(crypt_get_type); +DLSYM_FUNCTION(crypt_get_uuid); +DLSYM_FUNCTION(crypt_get_verity_info); +DLSYM_FUNCTION(crypt_get_volume_key_size); +DLSYM_FUNCTION(crypt_init); +DLSYM_FUNCTION(crypt_init_by_name); +DLSYM_FUNCTION(crypt_keyslot_add_by_volume_key); +DLSYM_FUNCTION(crypt_keyslot_destroy); +DLSYM_FUNCTION(crypt_keyslot_max); +DLSYM_FUNCTION(crypt_load); +DLSYM_FUNCTION(crypt_resize); +#if HAVE_CRYPT_RESUME_BY_VOLUME_KEY +DLSYM_FUNCTION(crypt_resume_by_volume_key); +#endif +DLSYM_FUNCTION(crypt_set_data_device); +DLSYM_FUNCTION(crypt_set_debug_level); +DLSYM_FUNCTION(crypt_set_log_callback); #if HAVE_CRYPT_SET_METADATA_SIZE -int (*sym_crypt_set_metadata_size)(struct crypt_device *cd, uint64_t metadata_size, uint64_t keyslots_size); +DLSYM_FUNCTION(crypt_set_metadata_size); #endif -int (*sym_crypt_set_pbkdf_type)(struct crypt_device *cd, const struct crypt_pbkdf_type *pbkdf); -int (*sym_crypt_suspend)(struct crypt_device *cd, const char *name); -int (*sym_crypt_token_json_get)(struct crypt_device *cd, int token, const char **json); -int (*sym_crypt_token_json_set)(struct crypt_device *cd, int token, const char *json); +DLSYM_FUNCTION(crypt_set_pbkdf_type); +DLSYM_FUNCTION(crypt_suspend); +DLSYM_FUNCTION(crypt_token_json_get); +DLSYM_FUNCTION(crypt_token_json_set); #if HAVE_CRYPT_TOKEN_MAX -int (*sym_crypt_token_max)(const char *type); +DLSYM_FUNCTION(crypt_token_max); #endif -crypt_token_info (*sym_crypt_token_status)(struct crypt_device *cd, int token, const char **type); -int (*sym_crypt_volume_key_get)(struct crypt_device *cd, int keyslot, char *volume_key, size_t *volume_key_size, const char *passphrase, size_t passphrase_size); +DLSYM_FUNCTION(crypt_token_status); +DLSYM_FUNCTION(crypt_volume_key_get); #if HAVE_CRYPT_REENCRYPT_INIT_BY_PASSPHRASE -int (*sym_crypt_reencrypt_init_by_passphrase)(struct crypt_device *cd, const char *name, const char *passphrase, size_t passphrase_size, int keyslot_old, int keyslot_new, const char *cipher, const char *cipher_mode, const struct crypt_params_reencrypt *params); +DLSYM_FUNCTION(crypt_reencrypt_init_by_passphrase); #endif #if HAVE_CRYPT_REENCRYPT -int (*sym_crypt_reencrypt)(struct crypt_device *cd, int (*progress)(uint64_t size, uint64_t offset, void *usrptr)); +DISABLE_WARNING_DEPRECATED_DECLARATIONS; +DLSYM_FUNCTION(crypt_reencrypt); +REENABLE_WARNING; #endif -int (*sym_crypt_metadata_locking)(struct crypt_device *cd, int enable); +DLSYM_FUNCTION(crypt_metadata_locking); #if HAVE_CRYPT_SET_DATA_OFFSET -int (*sym_crypt_set_data_offset)(struct crypt_device *cd, uint64_t data_offset); +DLSYM_FUNCTION(crypt_set_data_offset); #endif -int (*sym_crypt_header_restore)(struct crypt_device *cd, const char *requested_type, const char *backup_file); -int (*sym_crypt_volume_key_keyring)(struct crypt_device *cd, int enable); +DLSYM_FUNCTION(crypt_header_restore); +DLSYM_FUNCTION(crypt_volume_key_keyring); /* Unfortunately libcryptsetup provides neither an environment variable to redirect where to look for token * modules, nor does it have an API to change the token lookup path at runtime. The maintainers suggest using @@ -248,6 +252,11 @@ int dlopen_cryptsetup(void) { DISABLE_WARNING_DEPRECATED_DECLARATIONS; + ELF_NOTE_DLOPEN("cryptsetup", + "Support for disk encryption, integrity, and authentication", + ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + "libcryptsetup.so.12"); + r = dlopen_many_sym_or_warn( &cryptsetup_dl, "libcryptsetup.so.12", LOG_DEBUG, DLSYM_ARG(crypt_activate_by_passphrase), @@ -274,7 +283,9 @@ int dlopen_cryptsetup(void) { DLSYM_ARG(crypt_keyslot_max), DLSYM_ARG(crypt_load), DLSYM_ARG(crypt_resize), - DLSYM_ARG(crypt_resume_by_passphrase), +#if HAVE_CRYPT_RESUME_BY_VOLUME_KEY + DLSYM_ARG(crypt_resume_by_volume_key), +#endif DLSYM_ARG(crypt_set_data_device), DLSYM_ARG(crypt_set_debug_level), DLSYM_ARG(crypt_set_log_callback), diff --git a/src/shared/cryptsetup-util.h b/src/shared/cryptsetup-util.h index 5ff439d..f00ac36 100644 --- a/src/shared/cryptsetup-util.h +++ b/src/shared/cryptsetup-util.h @@ -2,6 +2,7 @@ #pragma once #include "alloc-util.h" +#include "dlfcn-util.h" #include "json.h" #include "macro.h" @@ -16,43 +17,45 @@ #define CRYPT_ACTIVATE_NO_WRITE_WORKQUEUE (1 << 25) #endif -extern int (*sym_crypt_activate_by_passphrase)(struct crypt_device *cd, const char *name, int keyslot, const char *passphrase, size_t passphrase_size, uint32_t flags); +DLSYM_PROTOTYPE(crypt_activate_by_passphrase); #if HAVE_CRYPT_ACTIVATE_BY_SIGNED_KEY -extern int (*sym_crypt_activate_by_signed_key)(struct crypt_device *cd, const char *name, const char *volume_key, size_t volume_key_size, const char *signature, size_t signature_size, uint32_t flags); +DLSYM_PROTOTYPE(crypt_activate_by_signed_key); #endif -extern int (*sym_crypt_activate_by_volume_key)(struct crypt_device *cd, const char *name, const char *volume_key, size_t volume_key_size, uint32_t flags); -extern int (*sym_crypt_deactivate_by_name)(struct crypt_device *cd, const char *name, uint32_t flags); -extern int (*sym_crypt_format)(struct crypt_device *cd, const char *type, const char *cipher, const char *cipher_mode, const char *uuid, const char *volume_key, size_t volume_key_size, void *params); -extern void (*sym_crypt_free)(struct crypt_device *cd); -extern const char *(*sym_crypt_get_cipher)(struct crypt_device *cd); -extern const char *(*sym_crypt_get_cipher_mode)(struct crypt_device *cd); -extern uint64_t (*sym_crypt_get_data_offset)(struct crypt_device *cd); -extern const char *(*sym_crypt_get_device_name)(struct crypt_device *cd); -extern const char *(*sym_crypt_get_dir)(void); -extern const char *(*sym_crypt_get_type)(struct crypt_device *cd); -extern const char *(*sym_crypt_get_uuid)(struct crypt_device *cd); -extern int (*sym_crypt_get_verity_info)(struct crypt_device *cd, struct crypt_params_verity *vp); -extern int (*sym_crypt_get_volume_key_size)(struct crypt_device *cd); -extern int (*sym_crypt_init)(struct crypt_device **cd, const char *device); -extern int (*sym_crypt_init_by_name)(struct crypt_device **cd, const char *name); -extern int (*sym_crypt_keyslot_add_by_volume_key)(struct crypt_device *cd, int keyslot, const char *volume_key, size_t volume_key_size, const char *passphrase, size_t passphrase_size); -extern int (*sym_crypt_keyslot_destroy)(struct crypt_device *cd, int keyslot); -extern int (*sym_crypt_keyslot_max)(const char *type); -extern int (*sym_crypt_load)(struct crypt_device *cd, const char *requested_type, void *params); -extern int (*sym_crypt_resize)(struct crypt_device *cd, const char *name, uint64_t new_size); -extern int (*sym_crypt_resume_by_passphrase)(struct crypt_device *cd, const char *name, int keyslot, const char *passphrase, size_t passphrase_size); -extern int (*sym_crypt_set_data_device)(struct crypt_device *cd, const char *device); -extern void (*sym_crypt_set_debug_level)(int level); -extern void (*sym_crypt_set_log_callback)(struct crypt_device *cd, void (*log)(int level, const char *msg, void *usrptr), void *usrptr); +DLSYM_PROTOTYPE(crypt_activate_by_volume_key); +DLSYM_PROTOTYPE(crypt_deactivate_by_name); +DLSYM_PROTOTYPE(crypt_format); +DLSYM_PROTOTYPE(crypt_free); +DLSYM_PROTOTYPE(crypt_get_cipher); +DLSYM_PROTOTYPE(crypt_get_cipher_mode); +DLSYM_PROTOTYPE(crypt_get_data_offset); +DLSYM_PROTOTYPE(crypt_get_device_name); +DLSYM_PROTOTYPE(crypt_get_dir); +DLSYM_PROTOTYPE(crypt_get_type); +DLSYM_PROTOTYPE(crypt_get_uuid); +DLSYM_PROTOTYPE(crypt_get_verity_info); +DLSYM_PROTOTYPE(crypt_get_volume_key_size); +DLSYM_PROTOTYPE(crypt_init); +DLSYM_PROTOTYPE(crypt_init_by_name); +DLSYM_PROTOTYPE(crypt_keyslot_add_by_volume_key); +DLSYM_PROTOTYPE(crypt_keyslot_destroy); +DLSYM_PROTOTYPE(crypt_keyslot_max); +DLSYM_PROTOTYPE(crypt_load); +DLSYM_PROTOTYPE(crypt_resize); +#if HAVE_CRYPT_RESUME_BY_VOLUME_KEY +DLSYM_PROTOTYPE(crypt_resume_by_volume_key); +#endif +DLSYM_PROTOTYPE(crypt_set_data_device); +DLSYM_PROTOTYPE(crypt_set_debug_level); +DLSYM_PROTOTYPE(crypt_set_log_callback); #if HAVE_CRYPT_SET_METADATA_SIZE -extern int (*sym_crypt_set_metadata_size)(struct crypt_device *cd, uint64_t metadata_size, uint64_t keyslots_size); +DLSYM_PROTOTYPE(crypt_set_metadata_size); #endif -extern int (*sym_crypt_set_pbkdf_type)(struct crypt_device *cd, const struct crypt_pbkdf_type *pbkdf); -extern int (*sym_crypt_suspend)(struct crypt_device *cd, const char *name); -extern int (*sym_crypt_token_json_get)(struct crypt_device *cd, int token, const char **json); -extern int (*sym_crypt_token_json_set)(struct crypt_device *cd, int token, const char *json); +DLSYM_PROTOTYPE(crypt_set_pbkdf_type); +DLSYM_PROTOTYPE(crypt_suspend); +DLSYM_PROTOTYPE(crypt_token_json_get); +DLSYM_PROTOTYPE(crypt_token_json_set); #if HAVE_CRYPT_TOKEN_MAX -extern int (*sym_crypt_token_max)(const char *type); +DLSYM_PROTOTYPE(crypt_token_max); #else /* As a fallback, use the same hard-coded value libcryptsetup uses internally. */ static inline int crypt_token_max(_unused_ const char *type) { @@ -62,20 +65,22 @@ static inline int crypt_token_max(_unused_ const char *type) { } #define sym_crypt_token_max(type) crypt_token_max(type) #endif -extern crypt_token_info (*sym_crypt_token_status)(struct crypt_device *cd, int token, const char **type); -extern int (*sym_crypt_volume_key_get)(struct crypt_device *cd, int keyslot, char *volume_key, size_t *volume_key_size, const char *passphrase, size_t passphrase_size); +DLSYM_PROTOTYPE(crypt_token_status); +DLSYM_PROTOTYPE(crypt_volume_key_get); #if HAVE_CRYPT_REENCRYPT_INIT_BY_PASSPHRASE -extern int (*sym_crypt_reencrypt_init_by_passphrase)(struct crypt_device *cd, const char *name, const char *passphrase, size_t passphrase_size, int keyslot_old, int keyslot_new, const char *cipher, const char *cipher_mode, const struct crypt_params_reencrypt *params); +DLSYM_PROTOTYPE(crypt_reencrypt_init_by_passphrase); #endif #if HAVE_CRYPT_REENCRYPT -extern int (*sym_crypt_reencrypt)(struct crypt_device *cd, int (*progress)(uint64_t size, uint64_t offset, void *usrptr)); +DISABLE_WARNING_DEPRECATED_DECLARATIONS; +DLSYM_PROTOTYPE(crypt_reencrypt); +REENABLE_WARNING; #endif -extern int (*sym_crypt_metadata_locking)(struct crypt_device *cd, int enable); +DLSYM_PROTOTYPE(crypt_metadata_locking); #if HAVE_CRYPT_SET_DATA_OFFSET -extern int (*sym_crypt_set_data_offset)(struct crypt_device *cd, uint64_t data_offset); +DLSYM_PROTOTYPE(crypt_set_data_offset); #endif -extern int (*sym_crypt_header_restore)(struct crypt_device *cd, const char *requested_type, const char *backup_file); -extern int (*sym_crypt_volume_key_keyring)(struct crypt_device *cd, int enable); +DLSYM_PROTOTYPE(crypt_header_restore); +DLSYM_PROTOTYPE(crypt_volume_key_keyring); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct crypt_device *, crypt_free, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct crypt_device *, sym_crypt_free, NULL); diff --git a/src/shared/daemon-util.c b/src/shared/daemon-util.c index 32180a1..aa386b4 100644 --- a/src/shared/daemon-util.c +++ b/src/shared/daemon-util.c @@ -4,6 +4,7 @@ #include "fd-util.h" #include "log.h" #include "string-util.h" +#include "time-util.h" static int notify_remove_fd_warn(const char *name) { int r; @@ -74,3 +75,18 @@ int notify_push_fdf(int fd, const char *format, ...) { return notify_push_fd(fd, name); } + +int notify_reloading_full(const char *status) { + int r; + + r = sd_notifyf(/* unset_environment = */ false, + "RELOADING=1\n" + "MONOTONIC_USEC=" USEC_FMT + "%s%s", + now(CLOCK_MONOTONIC), + status ? "\nSTATUS=" : "", strempty(status)); + if (r < 0) + return log_debug_errno(r, "Failed to notify service manager for reloading status: %m"); + + return 0; +} diff --git a/src/shared/daemon-util.h b/src/shared/daemon-util.h index 711885b..cbefa8d 100644 --- a/src/shared/daemon-util.h +++ b/src/shared/daemon-util.h @@ -10,7 +10,7 @@ #define NOTIFY_READY "READY=1\n" "STATUS=Processing requests..." #define NOTIFY_STOPPING "STOPPING=1\n" "STATUS=Shutting down..." -static inline const char *notify_start(const char *start, const char *stop) { +static inline const char* notify_start(const char *start, const char *stop) { if (start) (void) sd_notify(false, start); @@ -26,3 +26,8 @@ static inline void notify_on_cleanup(const char **p) { int notify_remove_fd_warnf(const char *format, ...) _printf_(1, 2); int close_and_notify_warn(int fd, const char *name); int notify_push_fdf(int fd, const char *format, ...) _printf_(2, 3); + +int notify_reloading_full(const char *status); +static inline int notify_reloading(void) { + return notify_reloading_full("Reloading configuration..."); +} diff --git a/src/shared/data-fd-util.c b/src/shared/data-fd-util.c index b939206..adc7d74 100644 --- a/src/shared/data-fd-util.c +++ b/src/shared/data-fd-util.c @@ -20,16 +20,15 @@ #include "tmpfile-util.h" /* When the data is smaller or equal to 64K, try to place the copy in a memfd/pipe */ -#define DATA_FD_MEMORY_LIMIT (64U*1024U) +#define DATA_FD_MEMORY_LIMIT (64U * U64_KB) /* If memfd/pipe didn't work out, then let's use a file in /tmp up to a size of 1M. If it's large than that use /var/tmp instead. */ -#define DATA_FD_TMP_LIMIT (1024U*1024U) +#define DATA_FD_TMP_LIMIT (1U * U64_MB) -int acquire_data_fd(const void *data, size_t size, unsigned flags) { - _cleanup_close_pair_ int pipefds[2] = EBADF_PAIR; +int acquire_data_fd_full(const void *data, size_t size, DataFDFlags flags) { _cleanup_close_ int fd = -EBADF; - int isz = 0, r; ssize_t n; + int r; assert(data || size == 0); @@ -52,24 +51,25 @@ int acquire_data_fd(const void *data, size_t size, unsigned flags) { * It sucks a bit that depending on the situation we return very different objects here, but that's Linux I * figure. */ - if (size == 0 && ((flags & ACQUIRE_NO_DEV_NULL) == 0)) + if (size == SIZE_MAX) + size = strlen(data); + + if (size == 0 && !FLAGS_SET(flags, ACQUIRE_NO_DEV_NULL)) /* As a special case, return /dev/null if we have been called for an empty data block */ return RET_NERRNO(open("/dev/null", O_RDONLY|O_CLOEXEC|O_NOCTTY)); - if ((flags & ACQUIRE_NO_MEMFD) == 0) { + if (!FLAGS_SET(flags, ACQUIRE_NO_MEMFD)) { fd = memfd_new_and_seal("data-fd", data, size); - if (fd < 0) { - if (ERRNO_IS_NOT_SUPPORTED(fd)) - goto try_pipe; - + if (fd < 0 && !ERRNO_IS_NOT_SUPPORTED(fd)) return fd; - } - - return TAKE_FD(fd); + if (fd >= 0) + return TAKE_FD(fd); } -try_pipe: - if ((flags & ACQUIRE_NO_PIPE) == 0) { + if (!FLAGS_SET(flags, ACQUIRE_NO_PIPE)) { + _cleanup_close_pair_ int pipefds[2] = EBADF_PAIR; + int isz; + if (pipe2(pipefds, O_CLOEXEC|O_NONBLOCK) < 0) return -errno; @@ -106,7 +106,7 @@ try_pipe: } try_dev_shm: - if ((flags & ACQUIRE_NO_TMPFILE) == 0) { + if (!FLAGS_SET(flags, ACQUIRE_NO_TMPFILE)) { fd = open("/dev/shm", O_RDWR|O_TMPFILE|O_CLOEXEC, 0500); if (fd < 0) goto try_dev_shm_without_o_tmpfile; @@ -122,7 +122,7 @@ try_dev_shm: } try_dev_shm_without_o_tmpfile: - if ((flags & ACQUIRE_NO_REGULAR) == 0) { + if (!FLAGS_SET(flags, ACQUIRE_NO_REGULAR)) { char pattern[] = "/dev/shm/data-fd-XXXXXX"; fd = mkostemp_safe(pattern); @@ -179,7 +179,7 @@ int copy_data_fd(int fd) { * that we use the reported regular file size only as a hint, given that there are plenty special files in * /proc and /sys which report a zero file size but can be read from. */ - if (!S_ISREG(st.st_mode) || st.st_size < DATA_FD_MEMORY_LIMIT) { + if (!S_ISREG(st.st_mode) || (uint64_t) st.st_size < DATA_FD_MEMORY_LIMIT) { /* Try a memfd first */ copy_fd = memfd_new("data-fd"); @@ -252,7 +252,7 @@ int copy_data_fd(int fd) { } /* If we have reason to believe this will fit fine in /tmp, then use that as first fallback. */ - if ((!S_ISREG(st.st_mode) || st.st_size < DATA_FD_TMP_LIMIT) && + if ((!S_ISREG(st.st_mode) || (uint64_t) st.st_size < DATA_FD_TMP_LIMIT) && (DATA_FD_MEMORY_LIMIT + remains_size) < DATA_FD_TMP_LIMIT) { off_t f; diff --git a/src/shared/data-fd-util.h b/src/shared/data-fd-util.h index 6d99209..d77e09f 100644 --- a/src/shared/data-fd-util.h +++ b/src/shared/data-fd-util.h @@ -2,15 +2,20 @@ #pragma once #include <stddef.h> +#include <stdint.h> -enum { +typedef enum DataFDFlags { ACQUIRE_NO_DEV_NULL = 1 << 0, ACQUIRE_NO_MEMFD = 1 << 1, ACQUIRE_NO_PIPE = 1 << 2, ACQUIRE_NO_TMPFILE = 1 << 3, ACQUIRE_NO_REGULAR = 1 << 4, -}; +} DataFDFlags; + +int acquire_data_fd_full(const void *data, size_t size, DataFDFlags flags); +static inline int acquire_data_fd(const void *data) { + return acquire_data_fd_full(data, SIZE_MAX, 0); +} -int acquire_data_fd(const void *data, size_t size, unsigned flags); int copy_data_fd(int fd); int memfd_clone_fd(int fd, const char *name, int mode); diff --git a/src/shared/dev-setup.c b/src/shared/dev-setup.c index f7ed161..4b4b625 100644 --- a/src/shared/dev-setup.c +++ b/src/shared/dev-setup.c @@ -7,6 +7,7 @@ #include "alloc-util.h" #include "dev-setup.h" #include "fd-util.h" +#include "fs-util.h" #include "label-util.h" #include "lock-util.h" #include "log.h" @@ -21,13 +22,15 @@ int lock_dev_console(void) { _cleanup_close_ int fd = -EBADF; int r; - fd = open_terminal("/dev/console", O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); + /* NB: We do not use O_NOFOLLOW here, because some container managers might place a symlink to some + * pty in /dev/console, in which case it should be fine to lock the target TTY. */ + fd = open_terminal("/dev/console", O_RDONLY|O_CLOEXEC|O_NOCTTY); if (fd < 0) return fd; r = lock_generic(fd, LOCK_BSD, LOCK_EX); if (r < 0) - return log_error_errno(r, "Failed to lock /dev/console: %m"); + return r; return TAKE_FD(fd); } @@ -79,15 +82,11 @@ int make_inaccessible_nodes( uid_t uid, gid_t gid) { - static const struct { - const char *name; - mode_t mode; - } table[] = { - { "inaccessible", S_IFDIR | 0755 }, - { "inaccessible/reg", S_IFREG | 0000 }, - { "inaccessible/dir", S_IFDIR | 0000 }, - { "inaccessible/fifo", S_IFIFO | 0000 }, - { "inaccessible/sock", S_IFSOCK | 0000 }, + static const mode_t table[] = { + S_IFREG, + S_IFDIR, + S_IFIFO, + S_IFSOCK, /* The following two are likely to fail if we lack the privs for it (for example in an userns * environment, if CAP_SYS_MKNOD is missing, or if a device node policy prohibits creation of @@ -95,10 +94,13 @@ int make_inaccessible_nodes( * should implement falling back to use a different node then, for example * <root>/inaccessible/sock, which is close enough in behaviour and semantics for most uses. */ - { "inaccessible/chr", S_IFCHR | 0000 }, - { "inaccessible/blk", S_IFBLK | 0000 }, + S_IFCHR, + S_IFBLK, + + /* NB: S_IFLNK is not listed here, as there is no such thing as an inaccessible symlink */ }; + _cleanup_close_ int parent_fd = -EBADF, inaccessible_fd = -EBADF; int r; if (!parent_dir) @@ -106,32 +108,48 @@ int make_inaccessible_nodes( BLOCK_WITH_UMASK(0000); + parent_fd = open(parent_dir, O_DIRECTORY|O_CLOEXEC|O_PATH, 0); + if (parent_fd < 0) + return -errno; + + inaccessible_fd = open_mkdir_at_full(parent_fd, "inaccessible", O_CLOEXEC, XO_LABEL, 0755); + if (inaccessible_fd < 0) + return inaccessible_fd; + /* Set up inaccessible (and empty) file nodes of all types. This are used to as mount sources for over-mounting * ("masking") file nodes that shall become inaccessible and empty for specific containers or services. We try * to lock down these nodes as much as we can, but otherwise try to match them as closely as possible with the * underlying file, i.e. in the best case we offer the same node type as the underlying node. */ - for (size_t i = 0; i < ELEMENTSOF(table); i++) { + FOREACH_ELEMENT(m, table) { _cleanup_free_ char *path = NULL; + mode_t inode_type = *m; + const char *fn; - path = path_join(parent_dir, table[i].name); + fn = inode_type_to_string(inode_type); + path = path_join(parent_dir, fn); if (!path) return log_oom(); - if (S_ISDIR(table[i].mode)) - r = mkdir_label(path, table[i].mode & 07777); + if (S_ISDIR(inode_type)) + r = mkdirat_label(inaccessible_fd, fn, 0000); else - r = mknod_label(path, table[i].mode, makedev(0, 0)); - if (r < 0) { + r = mknodat_label(inaccessible_fd, fn, inode_type | 0000, makedev(0, 0)); + if (r == -EEXIST) { + if (fchmodat(inaccessible_fd, fn, 0000, AT_SYMLINK_NOFOLLOW) < 0) + log_debug_errno(errno, "Failed to adjust access mode of existing inode '%s', ignoring: %m", path); + } else if (r < 0) { log_debug_errno(r, "Failed to create '%s', ignoring: %m", path); continue; } - if (uid != UID_INVALID || gid != GID_INVALID) { - if (lchown(path, uid, gid) < 0) - log_debug_errno(errno, "Failed to chown '%s': %m", path); - } + if (uid_is_valid(uid) || gid_is_valid(gid)) + if (fchownat(inaccessible_fd, fn, uid, gid, AT_SYMLINK_NOFOLLOW) < 0) + log_debug_errno(errno, "Failed to chown '%s', ignoring: %m", path); } + if (fchmod(inaccessible_fd, 0555) < 0) + log_debug_errno(errno, "Failed to mark inaccessible directory read-only, ignoring: %m"); + return 0; } diff --git a/src/shared/discover-image.c b/src/shared/discover-image.c index e8f4dfb..1079d28 100644 --- a/src/shared/discover-image.c +++ b/src/shared/discover-image.c @@ -13,6 +13,7 @@ #include <unistd.h> #include "alloc-util.h" +#include "blockdev-util.h" #include "btrfs-util.h" #include "chase.h" #include "chattr-util.h" @@ -44,9 +45,10 @@ #include "strv.h" #include "time-util.h" #include "utf8.h" +#include "vpick.h" #include "xattr-util.h" -static const char* const image_search_path[_IMAGE_CLASS_MAX] = { +const char* const image_search_path[_IMAGE_CLASS_MAX] = { [IMAGE_MACHINE] = "/etc/machines\0" /* only place symlinks here */ "/run/machines\0" /* and here too */ "/var/lib/machines\0" /* the main place for images */ @@ -74,15 +76,20 @@ static const char* const image_search_path[_IMAGE_CLASS_MAX] = { "/usr/lib/confexts\0", }; -/* Inside the initrd, use a slightly different set of search path (i.e. include .extra/sysext in extension - * search dir) */ +/* Inside the initrd, use a slightly different set of search path (i.e. include .extra/sysext/ and + * .extra/confext/ 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_SYSEXT] = "/etc/extensions\0" /* only place symlinks here */ "/run/extensions\0" /* and here too */ "/var/lib/extensions\0" /* the main place for images */ - "/.extra/sysext\0" /* put sysext picked up by systemd-stub last, since not trusted */ + "/.extra/sysext\0", /* put sysext picked up by systemd-stub last, since not trusted */ + + [IMAGE_CONFEXT] = "/run/confexts\0" /* only place symlinks here */ + "/var/lib/confexts\0" /* the main place for images */ + "/usr/local/lib/confexts\0" + "/.extra/confext\0", /* put confext picked up by systemd-stub last, since not trusted */ }; static const char* image_class_suffix_table[_IMAGE_CLASS_MAX] = { @@ -92,6 +99,15 @@ static const char* image_class_suffix_table[_IMAGE_CLASS_MAX] = { DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(image_class_suffix, ImageClass); +static const char *const image_root_table[_IMAGE_CLASS_MAX] = { + [IMAGE_MACHINE] = "/var/lib/machines", + [IMAGE_PORTABLE] = "/var/lib/portables", + [IMAGE_SYSEXT] = "/var/lib/extensions", + [IMAGE_CONFEXT] = "/var/lib/confexts", +}; + +DEFINE_STRING_TABLE_LOOKUP_TO_STRING(image_root, ImageClass); + static Image *image_free(Image *i) { assert(i); @@ -209,43 +225,101 @@ static int image_new( return 0; } -static int extract_pretty( +static int extract_image_basename( const char *path, - const char *class_suffix, - const char *format_suffix, - char **ret) { + const char *class_suffix, /* e.g. ".sysext" (this is an optional suffix) */ + char **format_suffixes, /* e.g. ".raw" (one of these will be required) */ + char **ret_basename, + char **ret_suffix) { - _cleanup_free_ char *name = NULL; + _cleanup_free_ char *name = NULL, *suffix = NULL; int r; assert(path); - assert(ret); r = path_extract_filename(path, &name); if (r < 0) return r; - if (format_suffix) { - char *e = endswith(name, format_suffix); + if (format_suffixes) { + char *e = endswith_strv(name, format_suffixes); if (!e) /* Format suffix is required */ return -EINVAL; + if (ret_suffix) { + suffix = strdup(e); + if (!suffix) + return -ENOMEM; + } + *e = 0; } if (class_suffix) { char *e = endswith(name, class_suffix); - if (e) /* Class suffix is optional */ + if (e) { /* Class suffix is optional */ + if (ret_suffix) { + _cleanup_free_ char *j = strjoin(e, suffix); + if (!j) + return -ENOMEM; + + free_and_replace(suffix, j); + } + *e = 0; + } } if (!image_name_is_valid(name)) return -EINVAL; - *ret = TAKE_PTR(name); + if (ret_suffix) + *ret_suffix = TAKE_PTR(suffix); + + if (ret_basename) + *ret_basename = TAKE_PTR(name); + return 0; } +static int image_update_quota(Image *i, int fd) { + _cleanup_close_ int fd_close = -EBADF; + int r; + + assert(i); + + if (IMAGE_IS_VENDOR(i) || IMAGE_IS_HOST(i)) + return -EROFS; + + if (i->type != IMAGE_SUBVOLUME) + return -EOPNOTSUPP; + + if (fd < 0) { + fd_close = open(i->path, O_CLOEXEC|O_NOCTTY|O_DIRECTORY); + if (fd_close < 0) + return -errno; + fd = fd_close; + } + + r = btrfs_quota_scan_ongoing(fd); + if (r < 0) + return r; + if (r > 0) + return 0; + + BtrfsQuotaInfo quota; + r = btrfs_subvol_get_subtree_quota_fd(fd, 0, "a); + if (r < 0) + return r; + + i->usage = quota.referenced; + i->usage_exclusive = quota.exclusive; + i->limit = quota.referenced_max; + i->limit_exclusive = quota.exclusive_max; + + return 1; +} + static int image_make( ImageClass c, const char *pretty, @@ -297,7 +371,12 @@ static int image_make( return 0; if (!pretty) { - r = extract_pretty(filename, image_class_suffix_to_string(c), NULL, &pretty_buffer); + r = extract_image_basename( + filename, + image_class_suffix_to_string(c), + /* format_suffix= */ NULL, + &pretty_buffer, + /* ret_suffix= */ NULL); if (r < 0) return r; @@ -334,19 +413,7 @@ static int image_make( if (r < 0) return r; - if (btrfs_quota_scan_ongoing(fd) == 0) { - BtrfsQuotaInfo quota; - - r = btrfs_subvol_get_subtree_quota_fd(fd, 0, "a); - if (r >= 0) { - (*ret)->usage = quota.referenced; - (*ret)->usage_exclusive = quota.exclusive; - - (*ret)->limit = quota.referenced_max; - (*ret)->limit_exclusive = quota.exclusive_max; - } - } - + (void) image_update_quota(*ret, fd); return 0; } } @@ -384,7 +451,12 @@ static int image_make( (void) fd_getcrtime_at(dfd, filename, AT_SYMLINK_FOLLOW, &crtime); if (!pretty) { - r = extract_pretty(filename, image_class_suffix_to_string(c), ".raw", &pretty_buffer); + r = extract_image_basename( + filename, + image_class_suffix_to_string(c), + STRV_MAKE(".raw"), + &pretty_buffer, + /* ret_suffix= */ NULL); if (r < 0) return r; @@ -418,7 +490,12 @@ static int image_make( return 0; if (!pretty) { - r = extract_pretty(filename, NULL, NULL, &pretty_buffer); + r = extract_image_basename( + filename, + /* class_suffix= */ NULL, + /* format_suffix= */ NULL, + &pretty_buffer, + /* ret_suffix= */ NULL); if (r < 0) return r; @@ -446,8 +523,9 @@ static int image_make( read_only = true; } - if (ioctl(block_fd, BLKGETSIZE64, &size) < 0) - log_debug_errno(errno, "Failed to issue BLKGETSIZE64 on device %s/%s, ignoring: %m", path ?: strnull(parent), filename); + r = blockdev_get_device_size(block_fd, &size); + if (r < 0) + log_debug_errno(r, "Failed to issue BLKGETSIZE64 on device %s/%s, ignoring: %m", path ?: strnull(parent), filename); block_fd = safe_close(block_fd); } @@ -481,6 +559,37 @@ static const char *pick_image_search_path(ImageClass class) { return in_initrd() && image_search_path_initrd[class] ? image_search_path_initrd[class] : image_search_path[class]; } +static char **make_possible_filenames(ImageClass class, const char *image_name) { + _cleanup_strv_free_ char **l = NULL; + + assert(image_name); + + FOREACH_STRING(v_suffix, "", ".v") + FOREACH_STRING(format_suffix, "", ".raw") { + _cleanup_free_ char *j = NULL; + const char *class_suffix; + + class_suffix = image_class_suffix_to_string(class); + if (class_suffix) { + j = strjoin(image_name, class_suffix, format_suffix, v_suffix); + if (!j) + return NULL; + + if (strv_consume(&l, TAKE_PTR(j)) < 0) + return NULL; + } + + j = strjoin(image_name, format_suffix, v_suffix); + if (!j) + return NULL; + + if (strv_consume(&l, TAKE_PTR(j)) < 0) + return NULL; + } + + return TAKE_PTR(l); +} + int image_find(ImageClass class, const char *name, const char *root, @@ -496,6 +605,10 @@ int image_find(ImageClass class, if (!image_name_is_valid(name)) return -ENOENT; + _cleanup_strv_free_ char **names = make_possible_filenames(class, name); + if (!names) + return -ENOMEM; + NULSTR_FOREACH(path, pick_image_search_path(class)) { _cleanup_free_ char *resolved = NULL; _cleanup_closedir_ DIR *d = NULL; @@ -512,43 +625,97 @@ int image_find(ImageClass class, * to symlink block devices into the search path. (For now, we disable that when operating * relative to some root directory.) */ flags = root ? AT_SYMLINK_NOFOLLOW : 0; - if (fstatat(dirfd(d), name, &st, flags) < 0) { - _cleanup_free_ char *raw = NULL; - if (errno != ENOENT) - return -errno; + STRV_FOREACH(n, names) { + _cleanup_free_ char *fname_buf = NULL; + const char *fname = *n; - raw = strjoin(name, ".raw"); - if (!raw) - return -ENOMEM; + if (fstatat(dirfd(d), fname, &st, flags) < 0) { + if (errno != ENOENT) + return -errno; - if (fstatat(dirfd(d), raw, &st, flags) < 0) { - if (errno == ENOENT) + continue; /* Vanished while we were looking at it */ + } + + if (endswith(fname, ".raw")) { + if (!S_ISREG(st.st_mode)) { + log_debug("Ignoring non-regular file '%s' with .raw suffix.", fname); continue; + } - return -errno; - } + } else if (endswith(fname, ".v")) { - if (!S_ISREG(st.st_mode)) - continue; + if (!S_ISDIR(st.st_mode)) { + log_debug("Ignoring non-directory file '%s' with .v suffix.", fname); + continue; + } - r = image_make(class, name, dirfd(d), resolved, raw, &st, ret); + _cleanup_free_ char *suffix = NULL; + suffix = strdup(ASSERT_PTR(startswith(fname, name))); + if (!suffix) + return -ENOMEM; + + *ASSERT_PTR(endswith(suffix, ".v")) = 0; + + _cleanup_free_ char *vp = path_join(resolved, fname); + if (!vp) + return -ENOMEM; + + PickFilter filter = { + .type_mask = endswith(suffix, ".raw") ? (UINT32_C(1) << DT_REG) | (UINT32_C(1) << DT_BLK) : (UINT32_C(1) << DT_DIR), + .basename = name, + .architecture = _ARCHITECTURE_INVALID, + .suffix = STRV_MAKE(suffix), + }; + + _cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL; + r = path_pick(root, + /* toplevel_fd= */ AT_FDCWD, + vp, + &filter, + PICK_ARCHITECTURE|PICK_TRIES, + &result); + if (r < 0) { + log_debug_errno(r, "Failed to pick versioned image on '%s', skipping: %m", vp); + continue; + } + if (!result.path) { + log_debug("Found versioned directory '%s', without matching entry, skipping: %m", vp); + continue; + } + + /* Refresh the stat data for the discovered target */ + st = result.st; + + _cleanup_free_ char *bn = NULL; + r = path_extract_filename(result.path, &bn); + if (r < 0) { + log_debug_errno(r, "Failed to extract basename of image path '%s', skipping: %m", result.path); + continue; + } + + fname_buf = path_join(fname, bn); + if (!fname_buf) + return log_oom(); - } else { - if (!S_ISDIR(st.st_mode) && !S_ISBLK(st.st_mode)) + fname = fname_buf; + + } else if (!S_ISDIR(st.st_mode) && !S_ISBLK(st.st_mode)) { + log_debug("Ignoring non-directory and non-block device file '%s' without suffix.", fname); continue; + } - r = image_make(class, name, dirfd(d), resolved, name, &st, ret); - } - if (IN_SET(r, -ENOENT, -EMEDIUMTYPE)) - continue; - if (r < 0) - return r; + r = image_make(class, name, dirfd(d), resolved, fname, &st, ret); + if (IN_SET(r, -ENOENT, -EMEDIUMTYPE)) + continue; + if (r < 0) + return r; - if (ret) - (*ret)->discoverable = true; + if (ret) + (*ret)->discoverable = true; - return 1; + return 1; + } } if (class == IMAGE_MACHINE && streq(name, ".host")) { @@ -559,7 +726,7 @@ int image_find(ImageClass class, if (ret) (*ret)->discoverable = true; - return r; + return 1; } return -ENOENT; @@ -606,43 +773,133 @@ int image_discover( return r; FOREACH_DIRENT_ALL(de, d, return -errno) { + _cleanup_free_ char *pretty = NULL, *fname_buf = NULL; _cleanup_(image_unrefp) Image *image = NULL; - _cleanup_free_ char *pretty = NULL; + const char *fname = de->d_name; struct stat st; int flags; - if (dot_or_dot_dot(de->d_name)) + if (dot_or_dot_dot(fname)) continue; /* As mentioned above, we follow symlinks on this fstatat(), because we want to * permit people to symlink block devices into the search path. */ flags = root ? AT_SYMLINK_NOFOLLOW : 0; - if (fstatat(dirfd(d), de->d_name, &st, flags) < 0) { + if (fstatat(dirfd(d), fname, &st, flags) < 0) { if (errno == ENOENT) continue; return -errno; } - if (S_ISREG(st.st_mode)) - r = extract_pretty(de->d_name, image_class_suffix_to_string(class), ".raw", &pretty); - else if (S_ISDIR(st.st_mode)) - r = extract_pretty(de->d_name, image_class_suffix_to_string(class), NULL, &pretty); - else if (S_ISBLK(st.st_mode)) - r = extract_pretty(de->d_name, NULL, NULL, &pretty); - else { - log_debug("Skipping directory entry '%s', which is neither regular file, directory nor block device.", de->d_name); - continue; - } - if (r < 0) { - log_debug_errno(r, "Skipping directory entry '%s', which doesn't look like an image.", de->d_name); + if (S_ISREG(st.st_mode)) { + r = extract_image_basename( + fname, + image_class_suffix_to_string(class), + STRV_MAKE(".raw"), + &pretty, + /* suffix= */ NULL); + if (r < 0) { + log_debug_errno(r, "Skipping directory entry '%s', which doesn't look like an image.", fname); + continue; + } + } else if (S_ISDIR(st.st_mode)) { + const char *v; + + v = endswith(fname, ".v"); + if (v) { + _cleanup_free_ char *suffix = NULL, *nov = NULL; + + nov = strndup(fname, v - fname); /* Chop off the .v */ + if (!nov) + return -ENOMEM; + + r = extract_image_basename( + nov, + image_class_suffix_to_string(class), + STRV_MAKE(".raw", ""), + &pretty, + &suffix); + if (r < 0) { + log_debug_errno(r, "Skipping directory entry '%s', which doesn't look like a versioned image.", fname); + continue; + } + + _cleanup_free_ char *vp = path_join(resolved, fname); + if (!vp) + return -ENOMEM; + + PickFilter filter = { + .type_mask = endswith(suffix, ".raw") ? (UINT32_C(1) << DT_REG) | (UINT32_C(1) << DT_BLK) : (UINT32_C(1) << DT_DIR), + .basename = pretty, + .architecture = _ARCHITECTURE_INVALID, + .suffix = STRV_MAKE(suffix), + }; + + _cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL; + r = path_pick(root, + /* toplevel_fd= */ AT_FDCWD, + vp, + &filter, + PICK_ARCHITECTURE|PICK_TRIES, + &result); + if (r < 0) { + log_debug_errno(r, "Failed to pick versioned image on '%s', skipping: %m", vp); + continue; + } + if (!result.path) { + log_debug("Found versioned directory '%s', without matching entry, skipping: %m", vp); + continue; + } + + /* Refresh the stat data for the discovered target */ + st = result.st; + + _cleanup_free_ char *bn = NULL; + r = path_extract_filename(result.path, &bn); + if (r < 0) { + log_debug_errno(r, "Failed to extract basename of image path '%s', skipping: %m", result.path); + continue; + } + + fname_buf = path_join(fname, bn); + if (!fname_buf) + return log_oom(); + + fname = fname_buf; + } else { + r = extract_image_basename( + fname, + image_class_suffix_to_string(class), + /* format_suffix= */ NULL, + &pretty, + /* ret_suffix= */ NULL); + if (r < 0) { + log_debug_errno(r, "Skipping directory entry '%s', which doesn't look like an image.", fname); + continue; + } + } + + } else if (S_ISBLK(st.st_mode)) { + r = extract_image_basename( + fname, + /* class_suffix= */ NULL, + /* format_suffix= */ NULL, + &pretty, + /* ret_v_suffix= */ NULL); + if (r < 0) { + log_debug_errno(r, "Skipping directory entry '%s', which doesn't look like an image.", fname); + continue; + } + } else { + log_debug("Skipping directory entry '%s', which is neither regular file, directory nor block device.", fname); continue; } if (hashmap_contains(h, pretty)) continue; - r = image_make(class, pretty, dirfd(d), resolved, de->d_name, &st, &image); + r = image_make(class, pretty, dirfd(d), resolved, fname, &st, &image); if (IN_SET(r, -ENOENT, -EMEDIUMTYPE)) continue; if (r < 0) @@ -1056,6 +1313,7 @@ int image_read_only(Image *i, bool b) { return -EOPNOTSUPP; } + i->read_only = b; return 0; } @@ -1064,7 +1322,12 @@ static void make_lock_dir(void) { (void) mkdir("/run/systemd/nspawn/locks", 0700); } -int image_path_lock(const char *path, int operation, LockFile *global, LockFile *local) { +int image_path_lock( + const char *path, + int operation, + LockFile *ret_global, + LockFile *ret_local) { + _cleanup_free_ char *p = NULL; LockFile t = LOCK_FILE_INIT; struct stat st; @@ -1072,8 +1335,7 @@ int image_path_lock(const char *path, int operation, LockFile *global, LockFile int r; assert(path); - assert(global); - assert(local); + assert(ret_local); /* Locks an image path. This actually creates two locks: one "local" one, next to the image path * itself, which might be shared via NFS. And another "global" one, in /run, that uses the @@ -1095,7 +1357,9 @@ int image_path_lock(const char *path, int operation, LockFile *global, LockFile } if (getenv_bool("SYSTEMD_NSPAWN_LOCK") == 0) { - *local = *global = (LockFile) LOCK_FILE_INIT; + *ret_local = LOCK_FILE_INIT; + if (ret_global) + *ret_global = LOCK_FILE_INIT; return 0; } @@ -1111,19 +1375,23 @@ int image_path_lock(const char *path, int operation, LockFile *global, LockFile if (exclusive) return -EBUSY; - *local = *global = (LockFile) LOCK_FILE_INIT; + *ret_local = LOCK_FILE_INIT; + if (ret_global) + *ret_global = LOCK_FILE_INIT; return 0; } - if (stat(path, &st) >= 0) { - if (S_ISBLK(st.st_mode)) - r = asprintf(&p, "/run/systemd/nspawn/locks/block-%u:%u", major(st.st_rdev), minor(st.st_rdev)); - else if (S_ISDIR(st.st_mode) || S_ISREG(st.st_mode)) - r = asprintf(&p, "/run/systemd/nspawn/locks/inode-%lu:%lu", (unsigned long) st.st_dev, (unsigned long) st.st_ino); - else - return -ENOTTY; - if (r < 0) - return -ENOMEM; + if (ret_global) { + if (stat(path, &st) >= 0) { + if (S_ISBLK(st.st_mode)) + r = asprintf(&p, "/run/systemd/nspawn/locks/block-%u:%u", major(st.st_rdev), minor(st.st_rdev)); + else if (S_ISDIR(st.st_mode) || S_ISREG(st.st_mode)) + r = asprintf(&p, "/run/systemd/nspawn/locks/inode-%lu:%lu", (unsigned long) st.st_dev, (unsigned long) st.st_ino); + else + return -ENOTTY; + if (r < 0) + return -ENOMEM; + } } /* For block devices we don't need the "local" lock, as the major/minor lock above should be @@ -1141,19 +1409,21 @@ int image_path_lock(const char *path, int operation, LockFile *global, LockFile if (p) { make_lock_dir(); - r = make_lock_file(p, operation, global); + r = make_lock_file(p, operation, ret_global); if (r < 0) { release_lock_file(&t); return r; } - } else - *global = (LockFile) LOCK_FILE_INIT; + } else if (ret_global) + *ret_global = LOCK_FILE_INIT; - *local = t; + *ret_local = t; return 0; } int image_set_limit(Image *i, uint64_t referenced_max) { + int r; + assert(i); if (IMAGE_IS_VENDOR(i) || IMAGE_IS_HOST(i)) @@ -1169,7 +1439,12 @@ int image_set_limit(Image *i, uint64_t referenced_max) { (void) btrfs_qgroup_set_limit(i->path, 0, referenced_max); (void) btrfs_subvol_auto_qgroup(i->path, 0, true); - return btrfs_subvol_set_subtree_quota_limit(i->path, 0, referenced_max); + r = btrfs_subvol_set_subtree_quota_limit(i->path, 0, referenced_max); + if (r < 0) + return r; + + (void) image_update_quota(i, -EBADF); + return 0; } int image_read_metadata(Image *i, const ImagePolicy *image_policy) { @@ -1206,7 +1481,7 @@ int image_read_metadata(Image *i, const ImagePolicy *image_policy) { else if (r >= 0) { r = read_etc_hostname(path, &hostname); if (r < 0) - log_debug_errno(errno, "Failed to read /etc/hostname of image %s: %m", i->name); + log_debug_errno(r, "Failed to read /etc/hostname of image %s: %m", i->name); } path = mfree(path); @@ -1249,8 +1524,25 @@ int image_read_metadata(Image *i, const ImagePolicy *image_policy) { case IMAGE_BLOCK: { _cleanup_(loop_device_unrefp) LoopDevice *d = NULL; _cleanup_(dissected_image_unrefp) DissectedImage *m = NULL; - - r = loop_device_make_by_path(i->path, O_RDONLY, /* sector_size= */ UINT32_MAX, LO_FLAGS_PARTSCAN, LOCK_SH, &d); + DissectImageFlags flags = + DISSECT_IMAGE_GENERIC_ROOT | + DISSECT_IMAGE_REQUIRE_ROOT | + DISSECT_IMAGE_RELAX_VAR_CHECK | + DISSECT_IMAGE_READ_ONLY | + DISSECT_IMAGE_USR_NO_ROOT | + DISSECT_IMAGE_ADD_PARTITION_DEVICES | + DISSECT_IMAGE_PIN_PARTITION_DEVICES | + DISSECT_IMAGE_VALIDATE_OS | + DISSECT_IMAGE_VALIDATE_OS_EXT | + DISSECT_IMAGE_ALLOW_USERSPACE_VERITY; + + r = loop_device_make_by_path( + i->path, + O_RDONLY, + /* sector_size= */ UINT32_MAX, + LO_FLAGS_PARTSCAN, + LOCK_SH, + &d); if (r < 0) return r; @@ -1259,20 +1551,15 @@ int image_read_metadata(Image *i, const ImagePolicy *image_policy) { /* verity= */ NULL, /* mount_options= */ NULL, image_policy, - DISSECT_IMAGE_GENERIC_ROOT | - DISSECT_IMAGE_REQUIRE_ROOT | - DISSECT_IMAGE_RELAX_VAR_CHECK | - DISSECT_IMAGE_READ_ONLY | - DISSECT_IMAGE_USR_NO_ROOT | - DISSECT_IMAGE_ADD_PARTITION_DEVICES | - DISSECT_IMAGE_PIN_PARTITION_DEVICES, + flags, &m); if (r < 0) return r; - r = dissected_image_acquire_metadata(m, - DISSECT_IMAGE_VALIDATE_OS | - DISSECT_IMAGE_VALIDATE_OS_EXT); + r = dissected_image_acquire_metadata( + m, + /* userns_fd= */ -EBADF, + flags); if (r < 0) return r; diff --git a/src/shared/discover-image.h b/src/shared/discover-image.h index a30a3d9..6491cec 100644 --- a/src/shared/discover-image.h +++ b/src/shared/discover-image.h @@ -119,4 +119,8 @@ static inline bool IMAGE_IS_HOST(const struct Image *i) { int image_to_json(const struct Image *i, JsonVariant **ret); +const char *image_root_to_string(ImageClass c) _const_; + extern const struct hash_ops image_hash_ops; + +extern const char* const image_search_path[_IMAGE_CLASS_MAX]; diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index 84cfbcd..a9e211f 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -32,6 +32,7 @@ #include "copy.h" #include "cryptsetup-util.h" #include "device-nodes.h" +#include "device-private.h" #include "device-util.h" #include "devnum-util.h" #include "discover-image.h" @@ -60,6 +61,7 @@ #include "openssl-util.h" #include "os-util.h" #include "path-util.h" +#include "proc-cmdline.h" #include "process-util.h" #include "raw-clone.h" #include "resize-fs.h" @@ -73,6 +75,7 @@ #include "tmpfile-util.h" #include "udev-util.h" #include "user-util.h" +#include "varlink.h" #include "xattr-util.h" /* how many times to wait for the device nodes to appear */ @@ -267,16 +270,8 @@ int probe_filesystem_full( (void) blkid_probe_lookup_value(b, "TYPE", &fstype, NULL); if (fstype) { - char *t; - log_debug("Probed fstype '%s' on partition %s.", fstype, path); - - t = strdup(fstype); - if (!t) - return -ENOMEM; - - *ret_fstype = t; - return 1; + return strdup_to_full(ret_fstype, fstype); } not_found: @@ -522,6 +517,38 @@ static void dissected_partition_done(DissectedPartition *p) { } #if HAVE_BLKID +static int diskseq_should_be_used( + const char *whole_devname, + uint64_t diskseq, + DissectImageFlags flags) { + + int r; + + assert(whole_devname); + + /* No diskseq. We cannot use by-diskseq symlink. */ + if (diskseq == 0) + return false; + + /* Do not use by-diskseq link unless DISSECT_IMAGE_DISKSEQ_DEVNODE flag is explicitly set. */ + if (!FLAGS_SET(flags, DISSECT_IMAGE_DISKSEQ_DEVNODE)) + return false; + + _cleanup_(sd_device_unrefp) sd_device *dev = NULL; + r = sd_device_new_from_devname(&dev, whole_devname); + if (r < 0) + return r; + + /* When ID_IGNORE_DISKSEQ udev property is set, the by-diskseq symlink will not be created. */ + r = device_get_property_bool(dev, "ID_IGNORE_DISKSEQ"); + if (r >= 0) + return !r; /* If explicitly specified, use it. */ + if (r != -ENOENT) + return r; + + return true; +} + static int make_partition_devname( const char *whole_devname, uint64_t diskseq, @@ -536,8 +563,10 @@ static int make_partition_devname( assert(nr != 0); /* zero is not a valid partition nr */ assert(ret); - if (!FLAGS_SET(flags, DISSECT_IMAGE_DISKSEQ_DEVNODE) || diskseq == 0) { - + r = diskseq_should_be_used(whole_devname, diskseq, flags); + if (r < 0) + log_debug_errno(r, "Failed to determine if diskseq should be used for %s, assuming no, ignoring: %m", whole_devname); + if (r <= 0) { /* Given a whole block device node name (e.g. /dev/sda or /dev/loop7) generate a partition * device name (e.g. /dev/sda7 or /dev/loop7p5). The rule the kernel uses is simple: if whole * block device node name ends in a digit, then suffix a 'p', followed by the partition @@ -799,7 +828,7 @@ static int dissect_image( if (suuid) { /* blkid will return FAT's serial number as UUID, hence it is quite possible * that parsing this will fail. We'll ignore the ID, since it's just too - * short to be useful as tru identifier. */ + * short to be useful as true identifier. */ r = sd_id128_from_string(suuid, &uuid); if (r < 0) log_debug_errno(r, "Failed to parse file system UUID '%s', ignoring: %m", suuid); @@ -1547,6 +1576,7 @@ int dissect_image_file( #if HAVE_BLKID _cleanup_(dissected_image_unrefp) DissectedImage *m = NULL; _cleanup_close_ int fd = -EBADF; + struct stat st; int r; assert(path); @@ -1555,7 +1585,10 @@ int dissect_image_file( if (fd < 0) return -errno; - r = fd_verify_regular(fd); + if (fstat(fd, &st) < 0) + return -errno; + + r = stat_verify_regular(&st); if (r < 0) return r; @@ -1563,6 +1596,8 @@ int dissect_image_file( if (r < 0) return r; + m->image_size = st.st_size; + r = probe_sector_size(fd, &m->sector_size); if (r < 0) return r; @@ -1644,6 +1679,20 @@ int dissect_image_file_and_warn( verity); } +void dissected_image_close(DissectedImage *m) { + if (!m) + return; + + /* Closes all fds we keep open associated with this, but nothing else */ + + FOREACH_ARRAY(p, m->partitions, _PARTITION_DESIGNATOR_MAX) { + p->mount_node_fd = safe_close(p->mount_node_fd); + p->fsmount_fd = safe_close(p->fsmount_fd); + } + + m->loop = loop_device_unref(m->loop); +} + DissectedImage* dissected_image_unref(DissectedImage *m) { if (!m) return NULL; @@ -1759,8 +1808,9 @@ static int fs_grow(const char *node_path, int mount_fd, const char *mount_path) if (node_fd < 0) return log_debug_errno(errno, "Failed to open node device %s: %m", node_path); - if (ioctl(node_fd, BLKGETSIZE64, &size) != 0) - return log_debug_errno(errno, "Failed to get block device size of %s: %m", node_path); + r = blockdev_get_device_size(node_fd, &size); + if (r < 0) + return log_debug_errno(r, "Failed to get block device size of %s: %m", node_path); if (mount_fd < 0) { assert(mount_path); @@ -1861,9 +1911,12 @@ int partition_pick_mount_options( * access that actually modifies stuff work on such image files. Or to say this differently: if * people want their file systems to be fixed up they should just open them in writable mode, where * all these problems don't exist. */ - if (!rw && fstype && fstype_can_norecovery(fstype)) - if (!strextend_with_separator(&options, ",", "norecovery")) + if (!rw && fstype) { + const char *option = fstype_norecovery_option(fstype); + + if (option && !strextend_with_separator(&options, ",", option)) return -ENOMEM; + } if (discard && fstype && fstype_can_discard(fstype)) if (!strextend_with_separator(&options, ",", "discard")) @@ -1958,7 +2011,7 @@ static int mount_partition( if (where) { if (directory) { /* Automatically create missing mount points inside the image, if necessary. */ - r = mkdir_p_root(where, directory, uid_shift, (gid_t) uid_shift, 0755, NULL); + r = mkdir_p_root(where, directory, uid_shift, (gid_t) uid_shift, 0755); if (r < 0 && r != -EROFS) return r; @@ -2113,7 +2166,7 @@ int dissected_image_mount( * If 'where' is not NULL then we'll either mount the partitions to the right places ourselves, * or use DissectedPartition.fsmount_fd and bind it to the right places. * - * This allows splitting the setting up up the superblocks and the binding to file systems paths into + * This allows splitting the setting up the superblocks and the binding to file systems paths into * two distinct and differently privileged components: one that gets the fsmount fds, and the other * that then applies them. * @@ -2137,7 +2190,7 @@ int dissected_image_mount( if (userns_fd < 0 && need_user_mapping(uid_shift, uid_range) && FLAGS_SET(flags, DISSECT_IMAGE_MOUNT_IDMAPPED)) { - my_userns_fd = make_userns(uid_shift, uid_range, UID_INVALID, REMOUNT_IDMAPPING_HOST_ROOT); + my_userns_fd = make_userns(uid_shift, uid_range, UID_INVALID, UID_INVALID, REMOUNT_IDMAPPING_HOST_ROOT); if (my_userns_fd < 0) return my_userns_fd; @@ -2278,19 +2331,19 @@ int dissected_image_mount_and_warn( r = dissected_image_mount(m, where, uid_shift, uid_range, userns_fd, flags); if (r == -ENXIO) - return log_error_errno(r, "Not root file system found in image."); + return log_error_errno(r, "Failed to mount image: No root file system found in image."); if (r == -EMEDIUMTYPE) - return log_error_errno(r, "No suitable os-release/extension-release file in image found."); + return log_error_errno(r, "Failed to mount image: No suitable os-release/extension-release file in image found."); if (r == -EUNATCH) - return log_error_errno(r, "Encrypted file system discovered, but decryption not requested."); + return log_error_errno(r, "Failed to mount image: Encrypted file system discovered, but decryption not requested."); if (r == -EUCLEAN) - return log_error_errno(r, "File system check on image failed."); + return log_error_errno(r, "Failed to mount image: File system check on image failed."); if (r == -EBUSY) - return log_error_errno(r, "File system already mounted elsewhere."); + return log_error_errno(r, "Failed to mount image: File system already mounted elsewhere."); if (r == -EAFNOSUPPORT) - return log_error_errno(r, "File system type not supported or not known."); + return log_error_errno(r, "Failed to mount image: File system type not supported or not known."); if (r == -EIDRM) - return log_error_errno(r, "File system is too uncommon, refused."); + return log_error_errno(r, "Failed to mount image: File system is too uncommon, refused."); if (r < 0) return log_error_errno(r, "Failed to mount image: %m"); @@ -2530,7 +2583,35 @@ static char* dm_deferred_remove_clean(char *name) { } DEFINE_TRIVIAL_CLEANUP_FUNC(char *, dm_deferred_remove_clean); -static int validate_signature_userspace(const VeritySettings *verity) { +static int validate_signature_userspace(const VeritySettings *verity, DissectImageFlags flags) { + int r; + + if (!FLAGS_SET(flags, DISSECT_IMAGE_ALLOW_USERSPACE_VERITY)) { + log_debug("Userspace dm-verity signature authentication disabled via flag."); + return 0; + } + + r = secure_getenv_bool("SYSTEMD_ALLOW_USERSPACE_VERITY"); + if (r < 0 && r != -ENXIO) { + log_debug_errno(r, "Failed to parse $SYSTEMD_ALLOW_USERSPACE_VERITY environment variable, refusing userspace dm-verity signature authentication."); + return 0; + } + if (!r) { + log_debug("Userspace dm-verity signature authentication disabled via $SYSTEMD_ALLOW_USERSPACE_VERITY environment variable."); + return 0; + } + + bool b; + r = proc_cmdline_get_bool("systemd.allow_userspace_verity", PROC_CMDLINE_TRUE_WHEN_MISSING, &b); + if (r < 0) { + log_debug_errno(r, "Failed to parse systemd.allow_userspace_verity= kernel command line option, refusing userspace dm-verity signature authentication."); + return 0; + } + if (!b) { + log_debug("Userspace dm-verity signature authentication disabled via systemd.allow_userspace_verity= kernel command line variable."); + return 0; + } + #if HAVE_OPENSSL _cleanup_(sk_X509_free_allp) STACK_OF(X509) *sk = NULL; _cleanup_strv_free_ char **certs = NULL; @@ -2539,7 +2620,6 @@ static int validate_signature_userspace(const VeritySettings *verity) { _cleanup_(BIO_freep) BIO *bio = NULL; /* 'bio' must be freed first, 's' second, hence keep this order * of declaration in place, please */ const unsigned char *d; - int r; assert(verity); assert(verity->root_hash); @@ -2611,7 +2691,8 @@ static int validate_signature_userspace(const VeritySettings *verity) { static int do_crypt_activate_verity( struct crypt_device *cd, const char *name, - const VeritySettings *verity) { + const VeritySettings *verity, + DissectImageFlags flags) { bool check_signature; int r, k; @@ -2621,7 +2702,7 @@ static int do_crypt_activate_verity( assert(verity); if (verity->root_hash_sig) { - r = getenv_bool_secure("SYSTEMD_DISSECT_VERITY_SIGNATURE"); + r = secure_getenv_bool("SYSTEMD_DISSECT_VERITY_SIGNATURE"); if (r < 0 && r != -ENXIO) log_debug_errno(r, "Failed to parse $SYSTEMD_DISSECT_VERITY_SIGNATURE"); @@ -2656,7 +2737,7 @@ static int do_crypt_activate_verity( /* Preferably propagate the original kernel error, so that the fallback logic can work, * as the device-mapper is finicky around concurrent activations of the same volume */ - k = validate_signature_userspace(verity); + k = validate_signature_userspace(verity, flags); if (k < 0) return r < 0 ? r : k; if (k == 0) @@ -2777,7 +2858,7 @@ static int verity_partition( goto check; /* The device already exists. Let's check it. */ /* The symlink to the device node does not exist yet. Assume not activated, and let's activate it. */ - r = do_crypt_activate_verity(cd, name, verity); + r = do_crypt_activate_verity(cd, name, verity, flags); if (r >= 0) goto try_open; /* The device is activated. Let's open it. */ /* libdevmapper can return EINVAL when the device is already in the activation stage. @@ -2787,7 +2868,9 @@ static int verity_partition( * https://gitlab.com/cryptsetup/cryptsetup/-/merge_requests/96 */ if (r == -EINVAL && FLAGS_SET(flags, DISSECT_IMAGE_VERITY_SHARE)) break; - if (r == -ENODEV) /* Volume is being opened but not ready, crypt_init_by_name would fail, try to open again */ + /* Volume is being opened but not ready, crypt_init_by_name would fail, try to open again if + * sharing is enabled. */ + if (r == -ENODEV && FLAGS_SET(flags, DISSECT_IMAGE_VERITY_SHARE)) goto try_again; if (!IN_SET(r, -EEXIST, /* Volume has already been opened and ready to be used. */ @@ -2907,6 +2990,7 @@ int dissected_image_decrypt( * > 0 → Decrypted successfully * -ENOKEY → There's something to decrypt but no key was supplied * -EKEYREJECTED → Passed key was not correct + * -EBUSY → Generic Verity error (kernel is not very explanatory) */ if (verity && verity->root_hash && verity->root_hash_size < sizeof(sd_id128_t)) @@ -2933,7 +3017,9 @@ int dissected_image_decrypt( k = partition_verity_of(i); if (k >= 0) { - r = verity_partition(i, p, m->partitions + k, verity, flags | DISSECT_IMAGE_VERITY_SHARE, d); + flags |= getenv_bool("SYSTEMD_VERITY_SHARING") != 0 ? DISSECT_IMAGE_VERITY_SHARE : 0; + + r = verity_partition(i, p, m->partitions + k, verity, flags, d); if (r < 0) return r; } @@ -2978,9 +3064,16 @@ int dissected_image_decrypt_interactively( return log_error_errno(SYNTHETIC_ERRNO(EKEYREJECTED), "Too many retries."); - z = strv_free(z); + z = strv_free_erase(z); + + static const AskPasswordRequest req = { + .message = "Please enter image passphrase:", + .id = "dissect", + .keyring = "dissect", + .credential = "dissect.passphrase", + }; - r = ask_password_auto("Please enter image passphrase:", NULL, "dissect", "dissect", "dissect.passphrase", USEC_INFINITY, 0, &z); + r = ask_password_auto(&req, USEC_INFINITY, /* flags= */ 0, &z); if (r < 0) return log_error_errno(r, "Failed to query for passphrase: %m"); @@ -3082,7 +3175,7 @@ int verity_settings_load( if (is_device_path(image)) return 0; - r = getenv_bool_secure("SYSTEMD_DISSECT_VERITY_SIDECAR"); + r = secure_getenv_bool("SYSTEMD_DISSECT_VERITY_SIDECAR"); if (r < 0 && r != -ENXIO) log_debug_errno(r, "Failed to parse $SYSTEMD_DISSECT_VERITY_SIDECAR, ignoring: %m"); if (r == 0) @@ -3159,7 +3252,7 @@ int verity_settings_load( } if (text) { - r = unhexmem(text, strlen(text), &root_hash, &root_hash_size); + r = unhexmem(text, &root_hash, &root_hash_size); if (r < 0) return r; if (root_hash_size < sizeof(sd_id128_t)) @@ -3267,7 +3360,7 @@ int dissected_image_load_verity_sig_partition( if (verity->root_hash && verity->root_hash_sig) /* Already loaded? */ return 0; - r = getenv_bool_secure("SYSTEMD_DISSECT_VERITY_EMBEDDED"); + r = secure_getenv_bool("SYSTEMD_DISSECT_VERITY_EMBEDDED"); if (r < 0 && r != -ENXIO) log_debug_errno(r, "Failed to parse $SYSTEMD_DISSECT_VERITY_EMBEDDED, ignoring: %m"); if (r == 0) @@ -3313,7 +3406,7 @@ int dissected_image_load_verity_sig_partition( if (!json_variant_is_string(rh)) return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "'rootHash' field of signature JSON object is not a string."); - r = unhexmem(json_variant_string(rh), SIZE_MAX, &root_hash, &root_hash_size); + r = unhexmem(json_variant_string(rh), &root_hash, &root_hash_size); if (r < 0) return log_debug_errno(r, "Failed to parse root hash field: %m"); @@ -3334,7 +3427,7 @@ int dissected_image_load_verity_sig_partition( if (!json_variant_is_string(sig)) return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "'signature' field of signature JSON object is not a string."); - r = unbase64mem(json_variant_string(sig), SIZE_MAX, &root_hash_sig, &root_hash_sig_size); + r = unbase64mem(json_variant_string(sig), &root_hash_sig, &root_hash_sig_size); if (r < 0) return log_debug_errno(r, "Failed to parse signature field: %m"); @@ -3347,7 +3440,10 @@ int dissected_image_load_verity_sig_partition( return 1; } -int dissected_image_acquire_metadata(DissectedImage *m, DissectImageFlags extra_flags) { +int dissected_image_acquire_metadata( + DissectedImage *m, + int userns_fd, + DissectImageFlags extra_flags) { enum { META_HOSTNAME, @@ -3375,11 +3471,10 @@ int dissected_image_acquire_metadata(DissectedImage *m, DissectImageFlags extra_ }; _cleanup_strv_free_ char **machine_info = NULL, **os_release = NULL, **initrd_release = NULL, **sysext_release = NULL, **confext_release = NULL; + _cleanup_free_ char *hostname = NULL, *t = NULL; _cleanup_close_pair_ int error_pipe[2] = EBADF_PAIR; - _cleanup_(rmdir_and_freep) char *t = NULL; _cleanup_(sigkill_waitp) pid_t child = 0; sd_id128_t machine_id = SD_ID128_NULL; - _cleanup_free_ char *hostname = NULL; unsigned n_meta_initialized = 0; int fds[2 * _META_MAX], r, v; int has_init_system = -1; @@ -3389,11 +3484,8 @@ int dissected_image_acquire_metadata(DissectedImage *m, DissectImageFlags extra_ assert(m); - for (; n_meta_initialized < _META_MAX; n_meta_initialized ++) { - if (!paths[n_meta_initialized]) { - fds[2*n_meta_initialized] = fds[2*n_meta_initialized+1] = -EBADF; - continue; - } + for (; n_meta_initialized < _META_MAX; n_meta_initialized++) { + assert(paths[n_meta_initialized]); if (pipe2(fds + 2*n_meta_initialized, O_CLOEXEC) < 0) { r = -errno; @@ -3401,7 +3493,7 @@ int dissected_image_acquire_metadata(DissectedImage *m, DissectImageFlags extra_ } } - r = mkdtemp_malloc("/tmp/dissect-XXXXXX", &t); + r = get_common_dissect_directory(&t); if (r < 0) goto finish; @@ -3410,13 +3502,22 @@ int dissected_image_acquire_metadata(DissectedImage *m, DissectImageFlags extra_ goto finish; } - r = safe_fork("(sd-dissect)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_NEW_MOUNTNS|FORK_MOUNTNS_SLAVE, &child); + r = safe_fork("(sd-dissect)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM, &child); if (r < 0) goto finish; if (r == 0) { - /* Child in a new mount namespace */ + /* Child */ error_pipe[0] = safe_close(error_pipe[0]); + if (userns_fd < 0) + r = detach_mount_namespace_harder(0, 0); + else + r = detach_mount_namespace_userns(userns_fd); + if (r < 0) { + log_debug_errno(r, "Failed to detach mount namespace: %m"); + goto inner_fail; + } + r = dissected_image_mount( m, t, @@ -3435,8 +3536,7 @@ int dissected_image_acquire_metadata(DissectedImage *m, DissectImageFlags extra_ for (unsigned k = 0; k < _META_MAX; k++) { _cleanup_close_ int fd = -ENOENT; - if (!paths[k]) - continue; + assert(paths[k]); fds[2*k] = safe_close(fds[2*k]); @@ -3541,8 +3641,7 @@ int dissected_image_acquire_metadata(DissectedImage *m, DissectImageFlags extra_ for (unsigned k = 0; k < _META_MAX; k++) { _cleanup_fclose_ FILE *f = NULL; - if (!paths[k]) - continue; + assert(paths[k]); fds[2*k+1] = safe_close(fds[2*k+1]); @@ -3703,6 +3802,7 @@ int dissect_loop_device( return r; m->loop = loop_device_ref(loop); + m->image_size = m->loop->device_size; m->sector_size = m->loop->sector_size; r = dissect_image(m, loop->fd, loop->node, verity, mount_options, image_policy, flags); @@ -3953,10 +4053,12 @@ int verity_dissect_and_mount( if (r < 0) return log_debug_errno(r, "Failed to load root hash: %m"); - dissect_image_flags = (verity.data_path ? DISSECT_IMAGE_NO_PARTITION_TABLE : 0) | + dissect_image_flags = + (verity.data_path ? DISSECT_IMAGE_NO_PARTITION_TABLE : 0) | (relax_extension_release_check ? DISSECT_IMAGE_RELAX_EXTENSION_CHECK : 0) | DISSECT_IMAGE_ADD_PARTITION_DEVICES | - DISSECT_IMAGE_PIN_PARTITION_DEVICES; + DISSECT_IMAGE_PIN_PARTITION_DEVICES | + DISSECT_IMAGE_ALLOW_USERSPACE_VERITY; /* Note that we don't use loop_device_make here, as the FD is most likely O_PATH which would not be * accepted by LOOP_CONFIGURE, so just let loop_device_make_by_path reopen it as a regular FD. */ @@ -4067,3 +4169,236 @@ int verity_dissect_and_mount( return 0; } + +int get_common_dissect_directory(char **ret) { + _cleanup_free_ char *t = NULL; + int r; + + /* A common location we mount dissected images to. The assumption is that everyone who uses this + * function runs in their own private mount namespace (with mount propagation off on /run/systemd/, + * and thus can mount something here without affecting anyone else). */ + + t = strdup("/run/systemd/dissect-root"); + if (!t) + return log_oom_debug(); + + r = mkdir_parents(t, 0755); + if (r < 0) + return log_debug_errno(r, "Failed to create parent dirs of mount point '%s': %m", t); + + r = RET_NERRNO(mkdir(t, 0000)); /* It's supposed to be overmounted, hence let's make this inaccessible */ + if (r < 0 && r != -EEXIST) + return log_debug_errno(r, "Failed to create mount point '%s': %m", t); + + if (ret) + *ret = TAKE_PTR(t); + + return 0; +} + +#if HAVE_BLKID + +static JSON_DISPATCH_ENUM_DEFINE(dispatch_architecture, Architecture, architecture_from_string); +static JSON_DISPATCH_ENUM_DEFINE(dispatch_partition_designator, PartitionDesignator, partition_designator_from_string); + +typedef struct PartitionFields { + PartitionDesignator designator; + bool rw; + bool growfs; + unsigned partno; + Architecture architecture; + sd_id128_t uuid; + char *fstype; + char *label; + uint64_t size; + uint64_t offset; + unsigned fsmount_fd_idx; +} PartitionFields; + +static void partition_fields_done(PartitionFields *f) { + assert(f); + + f->fstype = mfree(f->fstype); + f->label = mfree(f->label); +} + +typedef struct ReplyParameters { + JsonVariant *partitions; + char *image_policy; + uint64_t image_size; + uint32_t sector_size; + sd_id128_t image_uuid; +} ReplyParameters; + +static void reply_parameters_done(ReplyParameters *p) { + assert(p); + + p->image_policy = mfree(p->image_policy); + p->partitions = json_variant_unref(p->partitions); +} + +#endif + +int mountfsd_mount_image( + const char *path, + int userns_fd, + const ImagePolicy *image_policy, + DissectImageFlags flags, + DissectedImage **ret) { + +#if HAVE_BLKID + _cleanup_(reply_parameters_done) ReplyParameters p = {}; + + static const JsonDispatch dispatch_table[] = { + { "partitions", JSON_VARIANT_ARRAY, json_dispatch_variant, offsetof(struct ReplyParameters, partitions), JSON_MANDATORY }, + { "imagePolicy", JSON_VARIANT_STRING, json_dispatch_string, offsetof(struct ReplyParameters, image_policy), 0 }, + { "imageSize", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(struct ReplyParameters, image_size), JSON_MANDATORY }, + { "sectorSize", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint32, offsetof(struct ReplyParameters, sector_size), JSON_MANDATORY }, + { "imageUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(struct ReplyParameters, image_uuid), 0 }, + {} + }; + + _cleanup_(dissected_image_unrefp) DissectedImage *di = NULL; + _cleanup_close_ int image_fd = -EBADF; + _cleanup_(varlink_unrefp) Varlink *vl = NULL; + _cleanup_free_ char *ps = NULL; + unsigned max_fd = UINT_MAX; + const char *error_id; + int r; + + assert(path); + assert(ret); + + r = varlink_connect_address(&vl, "/run/systemd/io.systemd.MountFileSystem"); + if (r < 0) + return log_error_errno(r, "Failed to connect to mountfsd: %m"); + + r = varlink_set_allow_fd_passing_input(vl, true); + if (r < 0) + return log_error_errno(r, "Failed to enable varlink fd passing for read: %m"); + + r = varlink_set_allow_fd_passing_output(vl, true); + if (r < 0) + return log_error_errno(r, "Failed to enable varlink fd passing for write: %m"); + + image_fd = open(path, O_RDONLY|O_CLOEXEC); + if (image_fd < 0) + return log_error_errno(errno, "Failed to open '%s': %m", path); + + r = varlink_push_dup_fd(vl, image_fd); + if (r < 0) + return log_error_errno(r, "Failed to push image fd into varlink connection: %m"); + + if (userns_fd >= 0) { + r = varlink_push_dup_fd(vl, userns_fd); + if (r < 0) + return log_error_errno(r, "Failed to push image fd into varlink connection: %m"); + } + + if (image_policy) { + r = image_policy_to_string(image_policy, /* simplify= */ false, &ps); + if (r < 0) + return log_error_errno(r, "Failed format image policy to string: %m"); + } + + JsonVariant *reply = NULL; + r = varlink_callb( + vl, + "io.systemd.MountFileSystem.MountImage", + &reply, + &error_id, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("imageFileDescriptor", JSON_BUILD_UNSIGNED(0)), + JSON_BUILD_PAIR_CONDITION(userns_fd >= 0, "userNamespaceFileDescriptor", JSON_BUILD_UNSIGNED(1)), + JSON_BUILD_PAIR("readOnly", JSON_BUILD_BOOLEAN(FLAGS_SET(flags, DISSECT_IMAGE_MOUNT_READ_ONLY))), + JSON_BUILD_PAIR("growFileSystems", JSON_BUILD_BOOLEAN(FLAGS_SET(flags, DISSECT_IMAGE_GROWFS))), + JSON_BUILD_PAIR_CONDITION(ps, "imagePolicy", JSON_BUILD_STRING(ps)), + JSON_BUILD_PAIR("allowInteractiveAuthentication", JSON_BUILD_BOOLEAN(FLAGS_SET(flags, DISSECT_IMAGE_ALLOW_INTERACTIVE_AUTH))))); + if (r < 0) + return log_error_errno(r, "Failed to call MountImage() varlink call: %m"); + if (!isempty(error_id)) + return log_error_errno(varlink_error_to_errno(error_id, reply), "Failed to call MountImage() varlink call: %s", error_id); + + r = json_dispatch(reply, dispatch_table, JSON_ALLOW_EXTENSIONS, &p); + if (r < 0) + return log_error_errno(r, "Failed to parse MountImage() reply: %m"); + + log_debug("Effective image policy: %s", p.image_policy); + + JsonVariant *i; + JSON_VARIANT_ARRAY_FOREACH(i, p.partitions) { + _cleanup_close_ int fsmount_fd = -EBADF; + + _cleanup_(partition_fields_done) PartitionFields pp = { + .designator = _PARTITION_DESIGNATOR_INVALID, + .architecture = _ARCHITECTURE_INVALID, + .size = UINT64_MAX, + .offset = UINT64_MAX, + .fsmount_fd_idx = UINT_MAX, + }; + + static const JsonDispatch partition_dispatch_table[] = { + { "designator", JSON_VARIANT_STRING, dispatch_partition_designator, offsetof(struct PartitionFields, designator), JSON_MANDATORY }, + { "writable", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(struct PartitionFields, rw), JSON_MANDATORY }, + { "growFileSystem", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(struct PartitionFields, growfs), JSON_MANDATORY }, + { "partitionNumber", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint, offsetof(struct PartitionFields, partno), 0 }, + { "architecture", JSON_VARIANT_STRING, dispatch_architecture, offsetof(struct PartitionFields, architecture), 0 }, + { "partitionUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(struct PartitionFields, uuid), 0 }, + { "fileSystemType", JSON_VARIANT_STRING, json_dispatch_string, offsetof(struct PartitionFields, fstype), JSON_MANDATORY }, + { "partitionLabel", JSON_VARIANT_STRING, json_dispatch_string, offsetof(struct PartitionFields, label), 0 }, + { "size", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(struct PartitionFields, size), JSON_MANDATORY }, + { "offset", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(struct PartitionFields, offset), JSON_MANDATORY }, + { "mountFileDescriptor", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint, offsetof(struct PartitionFields, fsmount_fd_idx), JSON_MANDATORY }, + {} + }; + + r = json_dispatch(i, partition_dispatch_table, JSON_ALLOW_EXTENSIONS, &pp); + if (r < 0) + return log_error_errno(r, "Failed to parse partition data: %m"); + + if (pp.fsmount_fd_idx != UINT_MAX) { + if (max_fd == UINT_MAX || pp.fsmount_fd_idx > max_fd) + max_fd = pp.fsmount_fd_idx; + + fsmount_fd = varlink_take_fd(vl, pp.fsmount_fd_idx); + if (fsmount_fd < 0) + return fsmount_fd; + } + + assert(pp.designator >= 0); + + if (!di) { + r = dissected_image_new(path, &di); + if (r < 0) + return log_error_errno(r, "Failed to allocated new dissected image structure: %m"); + } + + if (di->partitions[pp.designator].found) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Duplicate partition data for '%s'.", partition_designator_to_string(pp.designator)); + + di->partitions[pp.designator] = (DissectedPartition) { + .found = true, + .rw = pp.rw, + .growfs = pp.growfs, + .partno = pp.partno, + .architecture = pp.architecture, + .uuid = pp.uuid, + .fstype = TAKE_PTR(pp.fstype), + .label = TAKE_PTR(pp.label), + .mount_node_fd = -EBADF, + .size = pp.size, + .offset = pp.offset, + .fsmount_fd = TAKE_FD(fsmount_fd), + }; + } + + di->image_size = p.image_size; + di->sector_size = p.sector_size; + di->image_uuid = p.image_uuid; + + *ret = TAKE_PTR(di); + return 0; +#else + return -EOPNOTSUPP; +#endif +} diff --git a/src/shared/dissect-image.h b/src/shared/dissect-image.h index 15c0bf7..e31fd54 100644 --- a/src/shared/dissect-image.h +++ b/src/shared/dissect-image.h @@ -87,6 +87,8 @@ typedef enum DissectImageFlags { 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 */ DISSECT_IMAGE_TRY_ATOMIC_MOUNT_EXCHANGE = 1 << 25, /* Try to mount the image beneath the specified mountpoint, rather than on top of it, and then umount the top */ + DISSECT_IMAGE_ALLOW_USERSPACE_VERITY = 1 << 26, /* Allow userspace verity keyring in /etc/verity.d/ and related dirs */ + DISSECT_IMAGE_ALLOW_INTERACTIVE_AUTH = 1 << 27, /* Allow interactive authorization when going through mountfsd */ } DissectImageFlags; struct DissectedImage { @@ -102,6 +104,7 @@ struct DissectedImage { DecryptedImage *decrypted_image; uint32_t sector_size; + uint64_t image_size; char *image_name; sd_id128_t image_uuid; @@ -161,6 +164,7 @@ int dissect_image_file_and_warn(const char *path, const VeritySettings *verity, 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); +void dissected_image_close(DissectedImage *m); DissectedImage* dissected_image_unref(DissectedImage *m); DEFINE_TRIVIAL_CLEANUP_FUNC(DissectedImage*, dissected_image_unref); @@ -169,7 +173,7 @@ int dissected_image_decrypt_interactively(DissectedImage *m, const char *passphr int dissected_image_mount(DissectedImage *m, const char *dest, uid_t uid_shift, uid_t uid_range, int userns_fd, DissectImageFlags flags); int dissected_image_mount_and_warn(DissectedImage *m, const char *where, uid_t uid_shift, uid_t uid_range, int userns_fd, DissectImageFlags flags); -int dissected_image_acquire_metadata(DissectedImage *m, DissectImageFlags extra_flags); +int dissected_image_acquire_metadata(DissectedImage *m, int userns_fd, DissectImageFlags extra_flags); Architecture dissected_image_architecture(DissectedImage *m); @@ -196,6 +200,14 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(DecryptedImage*, decrypted_image_unref); int dissected_image_relinquish(DissectedImage *m); int verity_settings_load(VeritySettings *verity, const char *image, const char *root_hash_path, const char *root_hash_sig_path); + +static inline bool verity_settings_set(const VeritySettings *settings) { + return settings && + (settings->root_hash_size > 0 || + (settings->root_hash_sig_size > 0 || + settings->data_path)); +} + void verity_settings_done(VeritySettings *verity); static inline bool verity_settings_data_covers(const VeritySettings *verity, PartitionDesignator d) { @@ -228,3 +240,7 @@ static inline const char *dissected_partition_fstype(const DissectedPartition *m return m->decrypted_node ? m->decrypted_fstype : m->fstype; } + +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); diff --git a/src/shared/dlfcn-util.c b/src/shared/dlfcn-util.c deleted file mode 100644 index 8022f55..0000000 --- a/src/shared/dlfcn-util.c +++ /dev/null @@ -1,66 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include "dlfcn-util.h" - -static int dlsym_many_or_warnv(void *dl, int log_level, va_list ap) { - void (**fn)(void); - - /* Tries to resolve a bunch of function symbols, and logs an error about if it cannot resolve one of - * them. Note that this function possibly modifies the supplied function pointers if the whole - * operation fails. */ - - while ((fn = va_arg(ap, typeof(fn)))) { - void (*tfn)(void); - const char *symbol; - - symbol = va_arg(ap, typeof(symbol)); - - tfn = (typeof(tfn)) dlsym(dl, symbol); - if (!tfn) - return log_full_errno(log_level, - SYNTHETIC_ERRNO(ELIBBAD), - "Can't find symbol %s: %s", symbol, dlerror()); - *fn = tfn; - } - - return 0; -} - -int dlsym_many_or_warn_sentinel(void *dl, int log_level, ...) { - va_list ap; - int r; - - va_start(ap, log_level); - r = dlsym_many_or_warnv(dl, log_level, ap); - va_end(ap); - - return r; -} - -int dlopen_many_sym_or_warn_sentinel(void **dlp, const char *filename, int log_level, ...) { - _cleanup_(dlclosep) void *dl = NULL; - int r; - - if (*dlp) - return 0; /* Already loaded */ - - dl = dlopen(filename, RTLD_LAZY); - if (!dl) - return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), - "%s is not installed: %s", filename, dlerror()); - - log_debug("Loaded '%s' via dlopen()", filename); - - va_list ap; - va_start(ap, log_level); - r = dlsym_many_or_warnv(dl, log_level, ap); - va_end(ap); - - if (r < 0) - return r; - - /* Note that we never release the reference here, because there's no real reason to. After all this - * was traditionally a regular shared library dependency which lives forever too. */ - *dlp = TAKE_PTR(dl); - return 1; -} diff --git a/src/shared/dlfcn-util.h b/src/shared/dlfcn-util.h deleted file mode 100644 index 7d8cb4c..0000000 --- a/src/shared/dlfcn-util.h +++ /dev/null @@ -1,39 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#pragma once - -#include <dlfcn.h> - -#include "macro.h" - -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(void*, dlclose, NULL); - -int dlsym_many_or_warn_sentinel(void *dl, int log_level, ...) _sentinel_; -int dlopen_many_sym_or_warn_sentinel(void **dlp, const char *filename, int log_level, ...) _sentinel_; - -#define dlsym_many_or_warn(dl, log_level, ...) \ - dlsym_many_or_warn_sentinel(dl, log_level, __VA_ARGS__, NULL) -#define dlopen_many_sym_or_warn(dlp, filename, log_level, ...) \ - dlopen_many_sym_or_warn_sentinel(dlp, filename, log_level, __VA_ARGS__, NULL) - -#define DLSYM_PROTOTYPE(symbol) \ - extern typeof(symbol)* sym_##symbol -#define DLSYM_FUNCTION(symbol) \ - typeof(symbol)* sym_##symbol = NULL - -/* Macro useful for putting together variable/symbol name pairs when calling dlsym_many_or_warn(). Assumes - * that each library symbol to resolve will be placed in a variable with the "sym_" prefix, i.e. a symbol - * "foobar" is loaded into a variable "sym_foobar". */ -#define DLSYM_ARG(arg) \ - ({ assert_cc(__builtin_types_compatible_p(typeof(sym_##arg), typeof(&arg))); &sym_##arg; }), STRINGIFY(arg) - -/* libbpf is a bit confused about type-safety and API compatibility. Provide a macro that can tape over that mess. Sad. */ -#define DLSYM_ARG_FORCE(arg) \ - &sym_##arg, STRINGIFY(arg) - -static inline void *safe_dlclose(void *p) { - if (!p) - return NULL; - - assert_se(dlclose(p) == 0); - return NULL; -} diff --git a/src/shared/dns-domain.c b/src/shared/dns-domain.c index b41c9b0..ba24a77 100644 --- a/src/shared/dns-domain.c +++ b/src/shared/dns-domain.c @@ -410,7 +410,7 @@ int dns_name_concat(const char *a, const char *b, DNSLabelFlags flags, char **_r goto finish; for (;;) { - char label[DNS_LABEL_MAX]; + char label[DNS_LABEL_MAX+1]; r = dns_label_unescape(&p, label, sizeof label, flags); if (r < 0) @@ -507,7 +507,7 @@ int dns_name_compare_func(const char *a, const char *b) { y = b + strlen(b); for (;;) { - char la[DNS_LABEL_MAX], lb[DNS_LABEL_MAX]; + char la[DNS_LABEL_MAX+1], lb[DNS_LABEL_MAX+1]; if (x == NULL && y == NULL) return 0; @@ -543,7 +543,7 @@ int dns_name_equal(const char *x, const char *y) { assert(y); for (;;) { - char la[DNS_LABEL_MAX], lb[DNS_LABEL_MAX]; + char la[DNS_LABEL_MAX+1], lb[DNS_LABEL_MAX+1]; r = dns_label_unescape(&x, la, sizeof la, 0); if (r < 0) @@ -574,7 +574,7 @@ int dns_name_endswith(const char *name, const char *suffix) { s = suffix; for (;;) { - char ln[DNS_LABEL_MAX], ls[DNS_LABEL_MAX]; + char ln[DNS_LABEL_MAX+1], ls[DNS_LABEL_MAX+1]; r = dns_label_unescape(&n, ln, sizeof ln, 0); if (r < 0) @@ -612,7 +612,7 @@ int dns_name_startswith(const char *name, const char *prefix) { p = prefix; for (;;) { - char ln[DNS_LABEL_MAX], lp[DNS_LABEL_MAX]; + char ln[DNS_LABEL_MAX+1], lp[DNS_LABEL_MAX+1]; r = dns_label_unescape(&p, lp, sizeof lp, 0); if (r < 0) @@ -644,7 +644,7 @@ int dns_name_change_suffix(const char *name, const char *old_suffix, const char s = old_suffix; for (;;) { - char ln[DNS_LABEL_MAX], ls[DNS_LABEL_MAX]; + char ln[DNS_LABEL_MAX+1], ls[DNS_LABEL_MAX+1]; if (!saved_before) saved_before = n; @@ -929,7 +929,7 @@ bool dns_srv_type_is_valid(const char *name) { return false; for (;;) { - char label[DNS_LABEL_MAX]; + char label[DNS_LABEL_MAX+1]; /* This more or less implements RFC 6335, Section 5.1 */ @@ -980,6 +980,29 @@ bool dns_service_name_is_valid(const char *name) { return true; } +bool dns_subtype_name_is_valid(const char *name) { + size_t l; + + /* This more or less implements RFC 6763, Section 7.2 */ + + if (!name) + return false; + + if (!utf8_is_valid(name)) + return false; + + if (string_has_cc(name, NULL)) + return false; + + l = strlen(name); + if (l <= 0) + return false; + if (l > DNS_LABEL_MAX) + return false; + + return true; +} + int dns_service_join(const char *name, const char *type, const char *domain, char **ret) { char escaped[DNS_LABEL_ESCAPED_MAX]; _cleanup_free_ char *n = NULL; @@ -1227,7 +1250,7 @@ int dns_name_common_suffix(const char *a, const char *b, const char **ret) { return m; for (;;) { - char la[DNS_LABEL_MAX], lb[DNS_LABEL_MAX]; + char la[DNS_LABEL_MAX+1], lb[DNS_LABEL_MAX+1]; const char *x, *y; if (k >= n || k >= m) { @@ -1328,7 +1351,7 @@ int dns_name_apply_idna(const char *name, char **ret) { assert(ret); for (;;) { - char label[DNS_LABEL_MAX]; + char label[DNS_LABEL_MAX+1]; r = dns_label_unescape(&name, label, sizeof label, 0); if (r < 0) diff --git a/src/shared/dns-domain.h b/src/shared/dns-domain.h index 331fb89..8ad00d6 100644 --- a/src/shared/dns-domain.h +++ b/src/shared/dns-domain.h @@ -83,6 +83,7 @@ int dns_name_to_wire_format(const char *domain, uint8_t *buffer, size_t len, boo bool dns_srv_type_is_valid(const char *name); bool dnssd_srv_type_is_valid(const char *name); bool dns_service_name_is_valid(const char *name); +bool dns_subtype_name_is_valid(const char *name); int dns_service_join(const char *name, const char *type, const char *domain, char **ret); int dns_service_split(const char *joined, char **ret_name, char **ret_type, char **ret_domain); diff --git a/src/shared/dropin.c b/src/shared/dropin.c index d46e838..9a786d0 100644 --- a/src/shared/dropin.c +++ b/src/shared/dropin.c @@ -26,7 +26,7 @@ int drop_in_file(const char *dir, const char *unit, unsigned level, const char *name, char **ret_p, char **ret_q) { - char prefix[DECIMAL_STR_MAX(unsigned)]; + char prefix[DECIMAL_STR_MAX(unsigned) + 1] = {}; _cleanup_free_ char *b = NULL, *p = NULL, *q = NULL; assert(unit); @@ -34,7 +34,8 @@ int drop_in_file(const char *dir, const char *unit, unsigned level, assert(ret_p); assert(ret_q); - sprintf(prefix, "%u", level); + if (level != UINT_MAX) + xsprintf(prefix, "%u-", level); b = xescape(name, "/."); if (!b) @@ -44,7 +45,7 @@ int drop_in_file(const char *dir, const char *unit, unsigned level, return -EINVAL; p = strjoin(dir, "/", unit, ".d"); - q = strjoin(p, "/", prefix, "-", b, ".conf"); + q = strjoin(p, "/", prefix, b, ".conf"); if (!p || !q) return -ENOMEM; diff --git a/src/shared/edit-util.c b/src/shared/edit-util.c index 045839b..cfb2828 100644 --- a/src/shared/edit-util.c +++ b/src/shared/edit-util.c @@ -16,6 +16,15 @@ #include "strv.h" #include "tmpfile-util-label.h" +typedef struct EditFile { + EditFileContext *context; + char *path; + char *original_path; + char **comment_paths; + char *temp; + unsigned line; +} EditFile; + void edit_file_context_done(EditFileContext *context) { int r; @@ -93,41 +102,22 @@ int edit_files_add( .path = TAKE_PTR(new_path), .original_path = TAKE_PTR(new_original_path), .comment_paths = TAKE_PTR(new_comment_paths), + .line = 1, }; context->n_files++; return 1; } -static int create_edit_temp_file(EditFile *e) { - _cleanup_(unlink_and_freep) char *temp = NULL; - _cleanup_fclose_ FILE *f = NULL; - const char *source; - bool has_original, has_target; - unsigned line = 1; - int r; - +static int populate_edit_temp_file(EditFile *e, FILE *f, const char *filename) { assert(e); - assert(e->context); - assert(e->path); - assert(!e->comment_paths || (e->context->marker_start && e->context->marker_end)); + assert(f); + assert(filename); - if (e->temp) - return 0; - - r = mkdir_parents_label(e->path, 0755); - if (r < 0) - return log_error_errno(r, "Failed to create parent directories for '%s': %m", e->path); - - r = fopen_temporary_label(e->path, e->path, &f, &temp); - if (r < 0) - return log_error_errno(r, "Failed to create temporary file for '%s': %m", e->path); - - if (fchmod(fileno(f), 0644) < 0) - return log_error_errno(errno, "Failed to change mode of temporary file '%s': %m", temp); - - has_original = e->original_path && access(e->original_path, F_OK) >= 0; - has_target = access(e->path, F_OK) >= 0; + bool has_original = e->original_path && access(e->original_path, F_OK) >= 0; + bool has_target = access(e->path, F_OK) >= 0; + const char *source; + int r; if (has_original && (!has_target || e->context->overwrite_with_origin)) /* We are asked to overwrite target with original_path or target doesn't exist. */ @@ -160,7 +150,7 @@ static int create_edit_temp_file(EditFile *e) { source_contents && endswith(source_contents, "\n") ? "" : "\n", e->context->marker_end); - line = 4; /* Start editing at the contents area */ + e->line = 4; /* Start editing at the contents area */ STRV_FOREACH(path, e->comment_paths) { _cleanup_free_ char *comment = NULL; @@ -189,16 +179,54 @@ static int create_edit_temp_file(EditFile *e) { r = copy_file_fd(source, fileno(f), COPY_REFLINK); if (r < 0) { assert(r != -ENOENT); - return log_error_errno(r, "Failed to copy file '%s' to temporary file '%s': %m", source, temp); + return log_error_errno(r, "Failed to copy file '%s' to temporary file '%s': %m", + source, filename); } } + return 0; +} + +static int create_edit_temp_file(EditFile *e, const char *contents, size_t contents_size) { + _cleanup_(unlink_and_freep) char *temp = NULL; + _cleanup_fclose_ FILE *f = NULL; + int r; + + assert(e); + assert(e->context); + assert(e->path); + assert(!e->comment_paths || (e->context->marker_start && e->context->marker_end)); + assert(contents || contents_size == 0); + + if (e->temp) + return 0; + + r = mkdir_parents_label(e->path, 0755); + if (r < 0) + return log_error_errno(r, "Failed to create parent directories for '%s': %m", e->path); + + r = fopen_temporary_label(e->path, e->path, &f, &temp); + if (r < 0) + return log_error_errno(r, "Failed to create temporary file for '%s': %m", e->path); + + if (fchmod(fileno(f), 0644) < 0) + return log_error_errno(errno, "Failed to change mode of temporary file '%s': %m", temp); + + if (e->context->stdin) { + if (fwrite(contents, 1, contents_size, f) != contents_size) + return log_error_errno(SYNTHETIC_ERRNO(EIO), + "Failed to copy input to temporary file '%s'.", temp); + } else { + r = populate_edit_temp_file(e, f, temp); + if (r < 0) + return r; + } + r = fflush_and_check(f); if (r < 0) return log_error_errno(r, "Failed to write to temporary file '%s': %m", temp); e->temp = TAKE_PTR(temp); - e->line = line; return 0; } @@ -282,7 +310,7 @@ static int run_editor(const EditFileContext *context) { } static int strip_edit_temp_file(EditFile *e) { - _cleanup_free_ char *old_contents = NULL, *new_contents = NULL; + _cleanup_free_ char *old_contents = NULL, *tmp = NULL, *new_contents = NULL; const char *stripped; int r; @@ -294,15 +322,17 @@ static int strip_edit_temp_file(EditFile *e) { if (r < 0) return log_error_errno(r, "Failed to read temporary file '%s': %m", e->temp); - if (e->context->marker_start) { + tmp = strdup(old_contents); + if (!tmp) + return log_oom(); + + if (e->context->marker_start && !e->context->stdin) { /* Trim out the lines between the two markers */ char *contents_start, *contents_end; assert(e->context->marker_end); - contents_start = strstrafter(old_contents, e->context->marker_start); - if (!contents_start) - contents_start = old_contents; + contents_start = strstrafter(tmp, e->context->marker_start) ?: tmp; contents_end = strstr(contents_start, e->context->marker_end); if (contents_end) @@ -310,9 +340,13 @@ static int strip_edit_temp_file(EditFile *e) { stripped = strstrip(contents_start); } else - stripped = strstrip(old_contents); - if (isempty(stripped)) - return 0; /* File is empty (has no real changes) */ + stripped = strstrip(tmp); + + if (isempty(stripped)) { + /* File is empty (has no real changes) */ + log_notice("%s: after editing, new contents are empty, not writing file.", e->path); + return 0; + } /* Trim prefix and suffix, but ensure suffixed by single newline */ new_contents = strjoin(stripped, "\n"); @@ -320,16 +354,19 @@ static int strip_edit_temp_file(EditFile *e) { return log_oom(); if (streq(old_contents, new_contents)) /* Don't touch the file if the above didn't change a thing */ - return 1; /* Contents unchanged after stripping but has changes */ + return 1; /* Contents have real changes */ - r = write_string_file(e->temp, new_contents, WRITE_STRING_FILE_CREATE | WRITE_STRING_FILE_TRUNCATE | WRITE_STRING_FILE_AVOID_NEWLINE); + r = write_string_file(e->temp, new_contents, + WRITE_STRING_FILE_CREATE | WRITE_STRING_FILE_TRUNCATE | WRITE_STRING_FILE_AVOID_NEWLINE); if (r < 0) return log_error_errno(r, "Failed to strip temporary file '%s': %m", e->temp); - return 1; /* Contents have real changes and are changed after stripping */ + return 1; /* Contents have real changes */ } int do_edit_files_and_install(EditFileContext *context) { + _cleanup_free_ char *data = NULL; + size_t data_size = 0; int r; assert(context); @@ -337,33 +374,41 @@ int do_edit_files_and_install(EditFileContext *context) { if (context->n_files == 0) return log_debug_errno(SYNTHETIC_ERRNO(ENOENT), "Got no files to edit."); - FOREACH_ARRAY(i, context->files, context->n_files) { - r = create_edit_temp_file(i); + if (context->stdin) { + r = read_full_stream(stdin, &data, &data_size); + if (r < 0) + return log_error_errno(r, "Failed to read stdin: %m"); + } + + FOREACH_ARRAY(editfile, context->files, context->n_files) { + r = create_edit_temp_file(editfile, data, data_size); if (r < 0) return r; } - r = run_editor(context); - if (r < 0) - return r; + if (!context->stdin) { + r = run_editor(context); + if (r < 0) + return r; + } - FOREACH_ARRAY(i, context->files, context->n_files) { + FOREACH_ARRAY(editfile, context->files, context->n_files) { /* Always call strip_edit_temp_file which will tell if the temp file has actual changes */ - r = strip_edit_temp_file(i); + r = strip_edit_temp_file(editfile); if (r < 0) return r; if (r == 0) /* temp file doesn't carry actual changes, ignoring */ continue; - r = RET_NERRNO(rename(i->temp, i->path)); + r = RET_NERRNO(rename(editfile->temp, editfile->path)); if (r < 0) return log_error_errno(r, "Failed to rename temporary file '%s' to target file '%s': %m", - i->temp, - i->path); - i->temp = mfree(i->temp); + editfile->temp, + editfile->path); + editfile->temp = mfree(editfile->temp); - log_info("Successfully installed edited file '%s'.", i->path); + log_info("Successfully installed edited file '%s'.", editfile->path); } return 0; diff --git a/src/shared/edit-util.h b/src/shared/edit-util.h index 83b3df8..9d9c890 100644 --- a/src/shared/edit-util.h +++ b/src/shared/edit-util.h @@ -7,25 +7,16 @@ #define DROPIN_MARKER_END "### Edits below this comment will be discarded" typedef struct EditFile EditFile; -typedef struct EditFileContext EditFileContext; - -struct EditFile { - EditFileContext *context; - char *path; - char *original_path; - char **comment_paths; - char *temp; - unsigned line; -}; - -struct EditFileContext { + +typedef struct EditFileContext { EditFile *files; size_t n_files; const char *marker_start; const char *marker_end; bool remove_parent; - bool overwrite_with_origin; /* whether to always overwrite target with original file */ -}; + bool overwrite_with_origin; /* Always overwrite target with original file. */ + bool stdin; /* Read contents from stdin instead of launching an editor. */ +} EditFileContext; void edit_file_context_done(EditFileContext *context); diff --git a/src/shared/efi-api.c b/src/shared/efi-api.c index 4cd1091..3ca33ef 100644 --- a/src/shared/efi-api.c +++ b/src/shared/efi-api.c @@ -7,6 +7,7 @@ #include "efi-api.h" #include "efivars.h" #include "fd-util.h" +#include "fileio.h" #include "sort-util.h" #include "stat-util.h" #include "stdio-util.h" @@ -453,13 +454,13 @@ int efi_get_boot_options(uint16_t **ret_options) { FOREACH_DIRENT(de, dir, return -errno) { int id; - if (strncmp(de->d_name, "Boot", 4) != 0) + if (!startswith(de->d_name, "Boot")) continue; if (strlen(de->d_name) != 45) continue; - if (strcmp(de->d_name + 8, EFI_GLOBAL_VARIABLE_STR("")) != 0) /* generate variable suffix using macro */ + if (!streq(de->d_name + 8, EFI_GLOBAL_VARIABLE_STR(""))) /* generate variable suffix using macro */ continue; id = boot_id_hex(de->d_name + 4); @@ -481,6 +482,7 @@ int efi_get_boot_options(uint16_t **ret_options) { bool efi_has_tpm2(void) { static int cache = -1; + int r; /* Returns whether the system has a TPM2 chip which is known to the EFI firmware. */ @@ -488,30 +490,35 @@ bool efi_has_tpm2(void) { return cache; /* First, check if we are on an EFI boot at all. */ - if (!is_efi_boot()) { - cache = 0; - return cache; - } + if (!is_efi_boot()) + return (cache = false); /* Then, check if the ACPI table "TPM2" exists, which is the TPM2 event log table, see: * https://trustedcomputinggroup.org/wp-content/uploads/TCG_ACPIGeneralSpecification_v1.20_r8.pdf - * This table exists whenever the firmware is hooked up to TPM2. */ - cache = access("/sys/firmware/acpi/tables/TPM2", F_OK) >= 0; - if (cache) - return cache; - + * This table exists whenever the firmware knows ACPI and is hooked up to TPM2. */ + if (access("/sys/firmware/acpi/tables/TPM2", F_OK) >= 0) + return (cache = true); if (errno != ENOENT) log_debug_errno(errno, "Unable to test whether /sys/firmware/acpi/tables/TPM2 exists, assuming it doesn't: %m"); /* As the last try, check if the EFI firmware provides the EFI_TCG2_FINAL_EVENTS_TABLE * stored in EFI configuration table, see: - * https://trustedcomputinggroup.org/wp-content/uploads/EFI-Protocol-Specification-rev13-160330final.pdf - */ - cache = access("/sys/kernel/security/tpm0/binary_bios_measurements", F_OK) >= 0; - if (!cache && errno != ENOENT) - log_debug_errno(errno, "Unable to test whether /sys/kernel/security/tpm0/binary_bios_measurements exists, assuming it doesn't: %m"); + * + * https://trustedcomputinggroup.org/wp-content/uploads/EFI-Protocol-Specification-rev13-160330final.pdf */ + if (access("/sys/kernel/security/tpm0/binary_bios_measurements", F_OK) >= 0) { + _cleanup_free_ char *major = NULL; + + /* The EFI table might exist for TPM 1.2 as well, hence let's check explicitly which TPM version we are looking at here. */ + r = read_virtual_file("/sys/class/tpm/tpm0/tpm_version_major", SIZE_MAX, &major, /* ret_size= */ NULL); + if (r >= 0) + return (cache = streq(strstrip(major), "2")); + + log_debug_errno(r, "Unable to read /sys/class/tpm/tpm0/tpm_version_major, assuming TPM does not qualify as TPM2: %m"); + + } else if (errno != ENOENT) + log_debug_errno(errno, "Unable to test whether /sys/kernel/security/tpm0/binary_bios_measurements exists, assuming it doesn't: %m"); - return cache; + return (cache = false); } #endif diff --git a/src/shared/efi-loader.c b/src/shared/efi-loader.c index 7d6bda9..ab377aa 100644 --- a/src/shared/efi-loader.c +++ b/src/shared/efi-loader.c @@ -262,7 +262,7 @@ int efi_measured_uki(int log_level) { * being used, but it measured things into a different PCR than we are configured for in * userspace. (i.e. we expect PCR 11 being used for this by both sd-stub and us) */ - r = getenv_bool_secure("SYSTEMD_FORCE_MEASURE"); /* Give user a chance to override the variable test, + r = secure_getenv_bool("SYSTEMD_FORCE_MEASURE"); /* Give user a chance to override the variable test, * for debugging purposes */ if (r >= 0) return (cached = r); diff --git a/src/shared/elf-util.c b/src/shared/elf-util.c index 24ed16e..9d1f494 100644 --- a/src/shared/elf-util.c +++ b/src/shared/elf-util.c @@ -38,55 +38,60 @@ static void *dw_dl = NULL; static void *elf_dl = NULL; /* libdw symbols */ -Dwarf_Attribute *(*sym_dwarf_attr_integrate)(Dwarf_Die *, unsigned int, Dwarf_Attribute *); -const char *(*sym_dwarf_diename)(Dwarf_Die *); -const char *(*sym_dwarf_formstring)(Dwarf_Attribute *); -int (*sym_dwarf_getscopes)(Dwarf_Die *, Dwarf_Addr, Dwarf_Die **); -int (*sym_dwarf_getscopes_die)(Dwarf_Die *, Dwarf_Die **); -Elf *(*sym_dwelf_elf_begin)(int); +static DLSYM_FUNCTION(dwarf_attr_integrate); +static DLSYM_FUNCTION(dwarf_diename); +static DLSYM_FUNCTION(dwarf_formstring); +static DLSYM_FUNCTION(dwarf_getscopes); +static DLSYM_FUNCTION(dwarf_getscopes_die); +static DLSYM_FUNCTION(dwelf_elf_begin); #if HAVE_DWELF_ELF_E_MACHINE_STRING -const char *(*sym_dwelf_elf_e_machine_string)(int); +static DLSYM_FUNCTION(dwelf_elf_e_machine_string); #endif -ssize_t (*sym_dwelf_elf_gnu_build_id)(Elf *, const void **); -int (*sym_dwarf_tag)(Dwarf_Die *); -Dwfl_Module *(*sym_dwfl_addrmodule)(Dwfl *, Dwarf_Addr); -Dwfl *(*sym_dwfl_begin)(const Dwfl_Callbacks *); -int (*sym_dwfl_build_id_find_elf)(Dwfl_Module *, void **, const char *, Dwarf_Addr, char **, Elf **); -int (*sym_dwfl_core_file_attach)(Dwfl *, Elf *); -int (*sym_dwfl_core_file_report)(Dwfl *, Elf *, const char *); -void (*sym_dwfl_end)(Dwfl *); -const char *(*sym_dwfl_errmsg)(int); -int (*sym_dwfl_errno)(void); -bool (*sym_dwfl_frame_pc)(Dwfl_Frame *, Dwarf_Addr *, bool *); -ptrdiff_t (*sym_dwfl_getmodules)(Dwfl *, int (*)(Dwfl_Module *, void **, const char *, Dwarf_Addr, void *), void *, ptrdiff_t); -int (*sym_dwfl_getthreads)(Dwfl *, int (*)(Dwfl_Thread *, void *), void *); -Dwarf_Die *(*sym_dwfl_module_addrdie)(Dwfl_Module *, Dwarf_Addr, Dwarf_Addr *); -const char *(*sym_dwfl_module_addrname)(Dwfl_Module *, GElf_Addr); -int (*sym_dwfl_module_build_id)(Dwfl_Module *, const unsigned char **, GElf_Addr *); -Elf *(*sym_dwfl_module_getelf)(Dwfl_Module *, GElf_Addr *); -const char *(*sym_dwfl_module_info)(Dwfl_Module *, void ***, Dwarf_Addr *, Dwarf_Addr *, Dwarf_Addr *, Dwarf_Addr *, const char **, const char **); -int (*sym_dwfl_offline_section_address)(Dwfl_Module *, void **, const char *, Dwarf_Addr, const char *, GElf_Word, const GElf_Shdr *, Dwarf_Addr *); -int (*sym_dwfl_report_end)(Dwfl *, int (*)(Dwfl_Module *, void *, const char *, Dwarf_Addr, void *), void *); -int (*sym_dwfl_standard_find_debuginfo)(Dwfl_Module *, void **, const char *, Dwarf_Addr, const char *, const char *, GElf_Word, char **); -int (*sym_dwfl_thread_getframes)(Dwfl_Thread *, int (*)(Dwfl_Frame *, void *), void *); -pid_t (*sym_dwfl_thread_tid)(Dwfl_Thread *); +static DLSYM_FUNCTION(dwelf_elf_gnu_build_id); +static DLSYM_FUNCTION(dwarf_tag); +static DLSYM_FUNCTION(dwfl_addrmodule); +static DLSYM_FUNCTION(dwfl_begin); +static DLSYM_FUNCTION(dwfl_build_id_find_elf); +static DLSYM_FUNCTION(dwfl_core_file_attach); +static DLSYM_FUNCTION(dwfl_core_file_report); +static DLSYM_FUNCTION(dwfl_end); +static DLSYM_FUNCTION(dwfl_errmsg); +static DLSYM_FUNCTION(dwfl_errno); +static DLSYM_FUNCTION(dwfl_frame_pc); +static DLSYM_FUNCTION(dwfl_getmodules); +static DLSYM_FUNCTION(dwfl_getthreads); +static DLSYM_FUNCTION(dwfl_module_addrdie); +static DLSYM_FUNCTION(dwfl_module_addrname); +static DLSYM_FUNCTION(dwfl_module_build_id); +static DLSYM_FUNCTION(dwfl_module_getelf); +static DLSYM_FUNCTION(dwfl_module_info); +static DLSYM_FUNCTION(dwfl_offline_section_address); +static DLSYM_FUNCTION(dwfl_report_end); +static DLSYM_FUNCTION(dwfl_standard_find_debuginfo); +static DLSYM_FUNCTION(dwfl_thread_getframes); +static DLSYM_FUNCTION(dwfl_thread_tid); /* libelf symbols */ -Elf *(*sym_elf_begin)(int, Elf_Cmd, Elf *); -int (*sym_elf_end)(Elf *); -Elf_Data *(*sym_elf_getdata_rawchunk)(Elf *, int64_t, size_t, Elf_Type); -GElf_Ehdr *(*sym_gelf_getehdr)(Elf *, GElf_Ehdr *); -int (*sym_elf_getphdrnum)(Elf *, size_t *); -const char *(*sym_elf_errmsg)(int); -int (*sym_elf_errno)(void); -Elf *(*sym_elf_memory)(char *, size_t); -unsigned int (*sym_elf_version)(unsigned int); -GElf_Phdr *(*sym_gelf_getphdr)(Elf *, int, GElf_Phdr *); -size_t (*sym_gelf_getnote)(Elf_Data *, size_t, GElf_Nhdr *, size_t *, size_t *); +static DLSYM_FUNCTION(elf_begin); +static DLSYM_FUNCTION(elf_end); +static DLSYM_FUNCTION(elf_getdata_rawchunk); +static DLSYM_FUNCTION(gelf_getehdr); +static DLSYM_FUNCTION(elf_getphdrnum); +static DLSYM_FUNCTION(elf_errmsg); +static DLSYM_FUNCTION(elf_errno); +static DLSYM_FUNCTION(elf_memory); +static DLSYM_FUNCTION(elf_version); +static DLSYM_FUNCTION(gelf_getphdr); +static DLSYM_FUNCTION(gelf_getnote); int dlopen_dw(void) { int r; + ELF_NOTE_DLOPEN("dw", + "Support for backtrace and ELF package metadata decoding from core files", + ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + "libdw.so.1"); + r = dlopen_many_sym_or_warn( &dw_dl, "libdw.so.1", LOG_DEBUG, DLSYM_ARG(dwarf_getscopes), @@ -130,6 +135,11 @@ int dlopen_dw(void) { int dlopen_elf(void) { int r; + ELF_NOTE_DLOPEN("elf", + "Support for backtraces and reading ELF package metadata from core files", + ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + "libelf.so.1"); + r = dlopen_many_sym_or_warn( &elf_dl, "libelf.so.1", LOG_DEBUG, DLSYM_ARG(elf_begin), @@ -348,7 +358,7 @@ static int parse_package_metadata(const char *name, JsonVariant *id_json, Elf *e /* Package metadata is in PT_NOTE headers. */ program_header = sym_gelf_getphdr(elf, i, &mem); - if (!program_header || (program_header->p_type != PT_NOTE && program_header->p_type != PT_INTERP)) + if (!program_header || !IN_SET(program_header->p_type, PT_NOTE, PT_INTERP)) continue; if (program_header->p_type == PT_INTERP) { @@ -540,7 +550,7 @@ static int module_callback(Dwfl_Module *mod, void **userdata, const char *name, continue; /* Check that the end of segment is a valid address. */ - if (__builtin_add_overflow(program_header->p_vaddr, program_header->p_memsz, &end_of_segment)) { + if (!ADD_SAFE(&end_of_segment, program_header->p_vaddr, program_header->p_memsz)) { log_error("Abort due to corrupted core dump, end of segment address %#zx + %#zx overflows", (size_t)program_header->p_vaddr, (size_t)program_header->p_memsz); return DWARF_CB_ABORT; } diff --git a/src/shared/ethtool-util.c b/src/shared/ethtool-util.c index dce9e00..1e100c3 100644 --- a/src/shared/ethtool-util.c +++ b/src/shared/ethtool-util.c @@ -182,7 +182,6 @@ int ethtool_get_driver(int *ethtool_fd, const char *ifname, char **ret) { struct ifreq ifr = { .ifr_data = (void*) &ecmd, }; - char *d; int r; assert(ethtool_fd); @@ -201,12 +200,7 @@ int ethtool_get_driver(int *ethtool_fd, const char *ifname, char **ret) { if (isempty(ecmd.driver)) return -ENODATA; - d = strdup(ecmd.driver); - if (!d) - return -ENOMEM; - - *ret = d; - return 0; + return strdup_to(ret, ecmd.driver); } int ethtool_get_link_info( diff --git a/src/shared/exec-util.c b/src/shared/exec-util.c index c27f3a5..996edbf 100644 --- a/src/shared/exec-util.c +++ b/src/shared/exec-util.c @@ -36,27 +36,35 @@ /* Put this test here for a lack of better place */ assert_cc(EAGAIN == EWOULDBLOCK); -static int do_spawn(const char *path, char *argv[], int stdout_fd, pid_t *pid, bool set_systemd_exec_pid) { - pid_t _pid; +static int do_spawn( + const char *path, + char *argv[], + int stdout_fd, + bool set_systemd_exec_pid, + pid_t *ret_pid) { + int r; + assert(path); + assert(ret_pid); + if (null_or_empty_path(path) > 0) { log_debug("%s is empty (a mask).", path); return 0; } - r = safe_fork("(direxec)", FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_RLIMIT_NOFILE_SAFE, &_pid); + pid_t pid; + r = safe_fork_full( + "(direxec)", + (const int[]) { STDIN_FILENO, stdout_fd < 0 ? STDOUT_FILENO : stdout_fd, STDERR_FILENO }, + /* except_fds= */ NULL, /* n_except_fds= */ 0, + FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_RLIMIT_NOFILE_SAFE|FORK_REARRANGE_STDIO|FORK_CLOSE_ALL_FDS, + &pid); if (r < 0) return r; if (r == 0) { char *_argv[2]; - if (stdout_fd >= 0) { - r = rearrange_stdio(STDIN_FILENO, TAKE_FD(stdout_fd), STDERR_FILENO); - if (r < 0) - _exit(EXIT_FAILURE); - } - if (set_systemd_exec_pid) { r = setenv_systemd_exec_pid(false); if (r < 0) @@ -75,7 +83,7 @@ static int do_spawn(const char *path, char *argv[], int stdout_fd, pid_t *pid, b _exit(EXIT_FAILURE); } - *pid = _pid; + *ret_pid = pid; return 1; } @@ -147,7 +155,7 @@ static int do_execute( log_debug("About to execute %s%s%s", t, argv ? " " : "", argv ? strnull(args) : ""); } - r = do_spawn(t, argv, fd, &pid, FLAGS_SET(flags, EXEC_DIR_SET_SYSTEMD_EXEC_PID)); + r = do_spawn(t, argv, fd, FLAGS_SET(flags, EXEC_DIR_SET_SYSTEMD_EXEC_PID), &pid); if (r <= 0) continue; @@ -539,7 +547,7 @@ int fork_agent(const char *name, const int except[], size_t n_except, pid_t *ret r = safe_fork_full(name, NULL, - except, + (int*) except, /* safe_fork_full only changes except if you pass in FORK_PACK_FDS, which we don't */ n_except, FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_CLOSE_ALL_FDS|FORK_REOPEN_LOG|FORK_RLIMIT_NOFILE_SAFE, ret_pid); diff --git a/src/shared/fdset.c b/src/shared/fdset.c index e5b8e92..cb5a69e 100644 --- a/src/shared/fdset.c +++ b/src/shared/fdset.c @@ -3,6 +3,7 @@ #include <errno.h> #include <fcntl.h> #include <stddef.h> +#include <unistd.h> #include "sd-daemon.h" @@ -40,8 +41,8 @@ int fdset_new_array(FDSet **ret, const int fds[], size_t n_fds) { if (!s) return -ENOMEM; - for (size_t i = 0; i < n_fds; i++) { - r = fdset_put(s, fds[i]); + FOREACH_ARRAY(fd, fds, n_fds) { + r = fdset_put(s, *fd); if (r < 0) return r; } @@ -71,7 +72,7 @@ void fdset_close(FDSet *s) { log_debug("Closing set fd %i (%s)", fd, strna(path)); } - (void) close_nointr(fd); + (void) close(fd); } } @@ -246,7 +247,7 @@ int fdset_new_listen_fds(FDSet **ret, bool unset) { return -ENOMEM; n = sd_listen_fds(unset); - for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd ++) { + for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd++) { r = fdset_put(s, fd); if (r < 0) return r; diff --git a/src/shared/find-esp.c b/src/shared/find-esp.c index db87084..f830d6d 100644 --- a/src/shared/find-esp.c +++ b/src/shared/find-esp.c @@ -556,13 +556,16 @@ int find_esp_and_warn( if (rfd < 0) return -errno; - r = find_esp_and_warn_at(rfd, path, unprivileged_mode, - ret_path ? &p : NULL, - ret_part ? &part : NULL, - ret_pstart ? &pstart : NULL, - ret_psize ? &psize : NULL, - ret_uuid ? &uuid : NULL, - ret_devid ? &devid : NULL); + r = find_esp_and_warn_at( + rfd, + path, + unprivileged_mode, + ret_path ? &p : NULL, + ret_part ? &part : NULL, + ret_pstart ? &pstart : NULL, + ret_psize ? &psize : NULL, + ret_uuid ? &uuid : NULL, + ret_devid ? &devid : NULL); if (r < 0) return r; @@ -871,12 +874,12 @@ int find_xbootldr_and_warn_at( } int find_xbootldr_and_warn( - const char *root, - const char *path, - int unprivileged_mode, - char **ret_path, - sd_id128_t *ret_uuid, - dev_t *ret_devid) { + const char *root, + const char *path, + int unprivileged_mode, + char **ret_path, + sd_id128_t *ret_uuid, + dev_t *ret_devid) { _cleanup_close_ int rfd = -EBADF; _cleanup_free_ char *p = NULL; @@ -888,10 +891,13 @@ int find_xbootldr_and_warn( if (rfd < 0) return -errno; - r = find_xbootldr_and_warn_at(rfd, path, unprivileged_mode, - ret_path ? &p : NULL, - ret_uuid ? &uuid : NULL, - ret_devid ? &devid : NULL); + r = find_xbootldr_and_warn_at( + rfd, + path, + unprivileged_mode, + ret_path ? &p : NULL, + ret_uuid ? &uuid : NULL, + ret_devid ? &devid : NULL); if (r < 0) return r; diff --git a/src/shared/firewall-util-iptables.c b/src/shared/firewall-util-iptables.c index b70b740..e2e5bb3 100644 --- a/src/shared/firewall-util-iptables.c +++ b/src/shared/firewall-util-iptables.c @@ -1,19 +1,12 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -/* Temporary work-around for broken glibc vs. linux kernel header definitions - * This is already fixed upstream, remove this when distributions have updated. - */ -#define _NET_IF_H 1 - +/* Make sure the net/if.h header is included before any linux/ one */ +#include <net/if.h> #include <arpa/inet.h> #include <endian.h> #include <errno.h> #include <stddef.h> #include <string.h> -#include <net/if.h> -#ifndef IFNAMSIZ -#define IFNAMSIZ 16 -#endif #include <linux/if.h> #include <linux/netfilter_ipv4/ip_tables.h> #include <linux/netfilter/nf_nat.h> @@ -361,6 +354,11 @@ int fw_iptables_add_local_dnat( } static int dlopen_iptc(void) { + ELF_NOTE_DLOPEN("ip4tc", + "Support for firewall rules with iptables backend", + ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + "libip4tc.so.2"); + return dlopen_many_sym_or_warn( &iptc_dl, "libip4tc.so.2", LOG_DEBUG, diff --git a/src/shared/firewall-util-nft.c b/src/shared/firewall-util-nft.c index fe986ed..e9bd286 100644 --- a/src/shared/firewall-util-nft.c +++ b/src/shared/firewall-util-nft.c @@ -1316,7 +1316,7 @@ int config_parse_nft_set( return 0; q = tuple; - r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE, &source_str, &family_str, &table, &set, NULL); + r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE, &source_str, &family_str, &table, &set); if (r == -ENOMEM) return log_oom(); if (r != 4 || !isempty(q)) { diff --git a/src/shared/firewall-util.h b/src/shared/firewall-util.h index 14e35be..25ba0d4 100644 --- a/src/shared/firewall-util.h +++ b/src/shared/firewall-util.h @@ -43,7 +43,7 @@ typedef enum NFTSetSource { NFT_SET_SOURCE_GROUP, _NFT_SET_SOURCE_MAX, _NFT_SET_SOURCE_INVALID = -EINVAL, -} NFTSetSource; +} NFTSetSource; typedef struct NFTSet { NFTSetSource source; diff --git a/src/shared/format-table.c b/src/shared/format-table.c index 9a19177..9146444 100644 --- a/src/shared/format-table.c +++ b/src/shared/format-table.c @@ -77,7 +77,9 @@ typedef struct TableData { unsigned ellipsize_percent; /* 0 … 100, where to place the ellipsis when compression is needed */ unsigned align_percent; /* 0 … 100, where to pad with spaces when expanding is needed. 0: left-aligned, 100: right-aligned */ - bool uppercase; /* Uppercase string on display */ + bool uppercase:1; /* Uppercase string on display */ + bool underline:1; + bool rgap_underline:1; const char *color; /* ANSI color string to use for this cell. When written to terminal should not move cursor. Will automatically be reset after the cell */ const char *rgap_color; /* The ANSI color to use for the gap right of this cell. Usually used to underline entire rows in a gapless fashion */ @@ -401,7 +403,7 @@ static bool table_data_matches( return false; /* If a color/url is set, refuse to merge */ - if (d->color || d->rgap_color) + if (d->color || d->rgap_color || d->underline || d->rgap_underline) return false; if (d->url) return false; @@ -617,6 +619,8 @@ static int table_dedup_cell(Table *t, TableCell *cell) { nd->color = od->color; nd->rgap_color = od->rgap_color; + nd->underline = od->underline; + nd->rgap_underline = od->rgap_underline; nd->url = TAKE_PTR(curl); table_data_unref(od); @@ -759,6 +763,46 @@ int table_set_rgap_color(Table *t, TableCell *cell, const char *color) { return 0; } +int table_set_underline(Table *t, TableCell *cell, bool b) { + TableData *d; + int r; + + assert(t); + assert(cell); + + r = table_dedup_cell(t, cell); + if (r < 0) + return r; + + assert_se(d = table_get_data(t, cell)); + + if (d->underline == b) + return 0; + + d->underline = b; + return 1; +} + +int table_set_rgap_underline(Table *t, TableCell *cell, bool b) { + TableData *d; + int r; + + assert(t); + assert(cell); + + r = table_dedup_cell(t, cell); + if (r < 0) + return r; + + assert_se(d = table_get_data(t, cell)); + + if (d->rgap_underline == b) + return 0; + + d->rgap_underline = b; + return 1; +} + int table_set_url(Table *t, TableCell *cell, const char *url) { _cleanup_free_ char *copy = NULL; int r; @@ -834,6 +878,8 @@ int table_update(Table *t, TableCell *cell, TableDataType type, const void *data nd->color = od->color; nd->rgap_color = od->rgap_color; + nd->underline = od->underline; + nd->rgap_underline = od->rgap_underline; nd->url = TAKE_PTR(curl); table_data_unref(od); @@ -1101,6 +1147,31 @@ int table_add_many_internal(Table *t, TableDataType first_type, ...) { goto check; } + case TABLE_SET_UNDERLINE: { + int u = va_arg(ap, int); + r = table_set_underline(t, last_cell, u); + goto check; + } + + case TABLE_SET_RGAP_UNDERLINE: { + int u = va_arg(ap, int); + r = table_set_rgap_underline(t, last_cell, u); + goto check; + } + + case TABLE_SET_BOTH_UNDERLINES: { + int u = va_arg(ap, int); + + r = table_set_underline(t, last_cell, u); + if (r < 0) { + va_end(ap); + return r; + } + + r = table_set_rgap_underline(t, last_cell, u); + goto check; + } + case TABLE_SET_URL: { const char *u = va_arg(ap, const char*); r = table_set_url(t, last_cell, u); @@ -1641,7 +1712,7 @@ static const char *table_data_format(Table *t, TableData *d, bool avoid_uppercas if (!p) return NULL; - if (!format_bytes_full(p, FORMAT_BYTES_MAX, d->size, 0)) + if (!format_bytes_full(p, FORMAT_BYTES_MAX, d->size, FORMAT_BYTES_BELOW_POINT)) return table_ersatz_string(t); n = strlen(p); @@ -1932,7 +2003,7 @@ static const char *table_data_format(Table *t, TableData *d, bool avoid_uppercas if (d->mode == MODE_INVALID) return table_ersatz_string(t); - return inode_type_to_string(d->mode); + return inode_type_to_string(d->mode) ?: table_ersatz_string(t); case TABLE_DEVNUM: if (devnum_is_zero(d->devnum)) @@ -2130,8 +2201,6 @@ static const char* table_data_color(TableData *d) { if (d->type == TABLE_FIELD) return ansi_bright_blue(); - if (d->type == TABLE_HEADER) - return ansi_underline(); return NULL; } @@ -2139,11 +2208,29 @@ static const char* table_data_color(TableData *d) { static const char* table_data_rgap_color(TableData *d) { assert(d); - if (d->rgap_color) - return d->rgap_color; + return d->rgap_color ?: d->rgap_color; +} + +static const char* table_data_underline(TableData *d) { + assert(d); + + if (d->underline) + return /* cescape( */ansi_add_underline_grey()/* ) */; if (d->type == TABLE_HEADER) - return ansi_underline(); + return ansi_add_underline(); + + return NULL; +} + +static const char* table_data_rgap_underline(TableData *d) { + assert(d); + + if (d->rgap_underline) + return ansi_add_underline_grey(); + + if (d->type == TABLE_HEADER) + return ansi_add_underline(); return NULL; } @@ -2418,13 +2505,13 @@ int table_print(Table *t, FILE *f) { row = t->data + i * t->n_columns; do { - const char *gap_color = NULL; + const char *gap_color = NULL, *gap_underline = NULL; more_sublines = false; for (size_t j = 0; j < display_columns; j++) { _cleanup_free_ char *buffer = NULL, *extracted = NULL; bool lines_truncated = false; - const char *field, *color = NULL; + const char *field, *color = NULL, *underline = NULL; TableData *d; size_t l; @@ -2490,6 +2577,7 @@ int table_print(Table *t, FILE *f) { /* Drop trailing white spaces of last column when no cosmetics is set. */ if (j == display_columns - 1 && (!colors_enabled() || !table_data_color(d)) && + (!underline_enabled() || !table_data_underline(d)) && (!urlify_enabled() || !d->url)) delete_trailing_chars(aligned, NULL); @@ -2511,31 +2599,40 @@ int table_print(Table *t, FILE *f) { if (colors_enabled() && gap_color) fputs(gap_color, f); + if (underline_enabled() && gap_underline) + fputs(gap_underline, f); if (j > 0) fputc(' ', f); /* column separator left of cell */ + /* Undo gap color/underline */ + if ((colors_enabled() && gap_color) || + (underline_enabled() && gap_underline)) + fputs(ANSI_NORMAL, f); + if (colors_enabled()) { color = table_data_color(d); - - /* Undo gap color */ - if (gap_color) - fputs(ANSI_NORMAL, f); - if (color) fputs(color, f); } + if (underline_enabled()) { + underline = table_data_underline(d); + if (underline) + fputs(underline, f); + } + fputs(field, f); - if (colors_enabled() && color) + if (color || underline) fputs(ANSI_NORMAL, f); gap_color = table_data_rgap_color(d); + gap_underline = table_data_rgap_underline(d); } fputc('\n', f); - n_subline ++; + n_subline++; } while (more_sublines); } diff --git a/src/shared/format-table.h b/src/shared/format-table.h index 37bfbca..b169eb0 100644 --- a/src/shared/format-table.h +++ b/src/shared/format-table.h @@ -68,6 +68,9 @@ typedef enum TableDataType { TABLE_SET_COLOR, TABLE_SET_RGAP_COLOR, TABLE_SET_BOTH_COLORS, + TABLE_SET_UNDERLINE, + TABLE_SET_RGAP_UNDERLINE, + TABLE_SET_BOTH_UNDERLINES, TABLE_SET_URL, TABLE_SET_UPPERCASE, @@ -111,6 +114,8 @@ int table_set_align_percent(Table *t, TableCell *cell, unsigned percent); int table_set_ellipsize_percent(Table *t, TableCell *cell, unsigned percent); int table_set_color(Table *t, TableCell *cell, const char *color); int table_set_rgap_color(Table *t, TableCell *cell, const char *color); +int table_set_underline(Table *t, TableCell *cell, bool b); +int table_set_rgap_underline(Table *t, TableCell *cell, bool b); int table_set_url(Table *t, TableCell *cell, const char *url); int table_set_uppercase(Table *t, TableCell *cell, bool b); @@ -129,7 +134,7 @@ int table_set_sort_internal(Table *t, size_t first_column, ...); #define table_set_sort(...) table_set_sort_internal(__VA_ARGS__, SIZE_MAX) int table_set_reverse(Table *t, size_t column, bool b); int table_hide_column_from_display_internal(Table *t, ...); -#define table_hide_column_from_display(t, ...) table_hide_column_from_display_internal(t, __VA_ARGS__, (size_t) -1) +#define table_hide_column_from_display(t, ...) table_hide_column_from_display_internal(t, __VA_ARGS__, SIZE_MAX) int table_print(Table *t, FILE *f); int table_format(Table *t, char **ret); @@ -139,6 +144,12 @@ static inline TableCell* TABLE_HEADER_CELL(size_t i) { } size_t table_get_rows(Table *t); +static inline bool table_isempty(Table *t) { + if (!t) + return true; + + return table_get_rows(t) <= 1; +} size_t table_get_columns(Table *t); size_t table_get_current_column(Table *t); diff --git a/src/shared/fstab-util.c b/src/shared/fstab-util.c index 55e76b6..60ceff5 100644 --- a/src/shared/fstab-util.c +++ b/src/shared/fstab-util.c @@ -74,8 +74,8 @@ bool fstab_is_extrinsic(const char *mount, const char *opts) { "/run/initramfs", /* This should stay around from before we boot until after we shutdown */ "/run/nextroot", /* Similar (though might be updated from the host) */ "/proc", /* All of this is API VFS */ - "/sys", /* … dito … */ - "/dev")) /* … dito … */ + "/sys", /* … ditto … */ + "/dev")) /* … ditto … */ return true; /* If this is an initrd mount, and we are not in the initrd, then leave @@ -105,6 +105,34 @@ static int fstab_is_same_node(const char *what_fstab, const char *path) { return false; } +int fstab_has_mount_point_prefix_strv(char **prefixes) { + _cleanup_endmntent_ FILE *f = NULL; + + assert(prefixes); + + /* This function returns true if at least one entry in fstab has a mount point that starts with one + * of the passed prefixes. */ + + if (!fstab_enabled()) + return false; + + f = setmntent(fstab_path(), "re"); + if (!f) + return errno == ENOENT ? false : -errno; + + for (;;) { + struct mntent *me; + + errno = 0; + me = getmntent(f); + if (!me) + return errno != 0 ? -errno : false; + + if (path_startswith_strv(me->mnt_dir, prefixes)) + return true; + } +} + int fstab_is_mount_point_full(const char *where, const char *path) { _cleanup_endmntent_ FILE *f = NULL; int r; @@ -136,8 +164,6 @@ int fstab_is_mount_point_full(const char *where, const char *path) { if (r > 0 || (r < 0 && !ERRNO_IS_DEVICE_ABSENT(r))) return r; } - - return false; } int fstab_filter_options( @@ -148,16 +174,16 @@ int fstab_filter_options( char ***ret_values, char **ret_filtered) { - const char *namefound = NULL, *x; - _cleanup_strv_free_ char **stor = NULL, **values = NULL; - _cleanup_free_ char *value = NULL, **filtered = NULL; + _cleanup_strv_free_ char **values = NULL; + _cleanup_free_ char *value = NULL, *filtered = NULL; + const char *namefound = NULL; int r; - assert(names && *names); + assert(!isempty(names)); assert(!(ret_value && ret_values)); if (!opts) - goto answer; + goto finish; /* Finds any options matching 'names', and returns: * - the last matching option name in ret_namefound, @@ -169,50 +195,49 @@ int fstab_filter_options( * * Returns negative on error, true if any matching options were found, false otherwise. */ - if (ret_filtered || ret_value || ret_values) { + if (ret_value || ret_values || ret_filtered) { + _cleanup_strv_free_ char **opts_split = NULL; + _cleanup_free_ char **filtered_strv = NULL; /* strings are owned by 'opts_split' */ + /* For backwards compatibility, we need to pass-through escape characters. * The only ones we "consume" are the ones used as "\," or "\\". */ - r = strv_split_full(&stor, opts, ",", EXTRACT_UNESCAPE_SEPARATORS | EXTRACT_UNESCAPE_RELAX); + r = strv_split_full(&opts_split, opts, ",", EXTRACT_UNESCAPE_SEPARATORS|EXTRACT_UNESCAPE_RELAX); if (r < 0) return r; - filtered = memdup(stor, sizeof(char*) * (strv_length(stor) + 1)); - if (!filtered) - return -ENOMEM; + STRV_FOREACH(opt, opts_split) { + bool found = false; + const char *x; - char **t = filtered; - for (char **s = t; *s; s++) { NULSTR_FOREACH(name, names) { - x = startswith(*s, name); + x = startswith(*opt, name); if (!x) continue; - /* Match name, but when ret_values, only when followed by assignment. */ + + /* If ret_values, only accept settings followed by assignment. */ if (*x == '=' || (!ret_values && *x == '\0')) { - /* Keep the last occurrence found */ namefound = name; - goto found; + found = true; + break; } } - *t = *s; - t++; - continue; - found: - if (ret_value || ret_values) { - assert(IN_SET(*x, '=', '\0')); - - if (ret_value) { + if (found) { + if (ret_value) r = free_and_strdup(&value, *x == '=' ? x + 1 : NULL); - if (r < 0) - return r; - } else if (*x) { + else if (ret_values) r = strv_extend(&values, x + 1); - if (r < 0) - return r; - } - } + else + r = 0; + } else + r = strv_push(&filtered_strv, *opt); + if (r < 0) + return r; } - *t = NULL; + + filtered = strv_join_full(filtered_strv, ",", NULL, /* escape_separator = */ true); + if (!filtered) + return -ENOMEM; } else for (const char *word = opts;;) { const char *end = word; @@ -226,64 +251,55 @@ int fstab_filter_options( if (IN_SET(*end, ',', '\0')) break; assert(*end == '\\'); - end ++; /* Skip the backslash */ + end++; /* Skip the backslash */ if (*end != '\0') - end ++; /* Skip the escaped char, but watch out for a trailing comma */ + end++; /* Skip the escaped char, but watch out for a trailing comma */ } NULSTR_FOREACH(name, names) { - if (end < word + strlen(name)) - continue; - if (!strneq(word, name, strlen(name))) + const char *x = startswith(word, name); + if (!x || x > end) continue; /* We know that the string is NUL terminated, so *x is valid */ - x = word + strlen(name); if (IN_SET(*x, '\0', '=', ',')) { namefound = name; break; } } - if (*end) - word = end + 1; - else + if (*end == '\0') break; + + word = end + 1; } -answer: +finish: if (ret_namefound) - *ret_namefound = namefound; - if (ret_filtered) { - char *f; - - f = strv_join_full(filtered, ",", NULL, true); - if (!f) - return -ENOMEM; - - *ret_filtered = f; - } + *ret_namefound = namefound; /* owned by 'names' (passed-in) */ if (ret_value) *ret_value = TAKE_PTR(value); if (ret_values) *ret_values = TAKE_PTR(values); + if (ret_filtered) + *ret_filtered = TAKE_PTR(filtered); return !!namefound; } -int fstab_find_pri(const char *options, int *ret) { - _cleanup_free_ char *opt = NULL; +int fstab_find_pri(const char *opts, int *ret) { + _cleanup_free_ char *v = NULL; int r, pri; assert(ret); - r = fstab_filter_options(options, "pri\0", NULL, &opt, NULL, NULL); + r = fstab_filter_options(opts, "pri\0", NULL, &v, NULL, NULL); if (r < 0) return r; - if (r == 0 || !opt) + if (r == 0 || !v) return 0; - r = safe_atoi(opt, &pri); + r = safe_atoi(v, &pri); if (r < 0) return r; diff --git a/src/shared/fstab-util.h b/src/shared/fstab-util.h index 9cf34f0..040f076 100644 --- a/src/shared/fstab-util.h +++ b/src/shared/fstab-util.h @@ -25,6 +25,8 @@ static inline int fstab_has_node(const char *path) { return fstab_is_mount_point_full(NULL, path); } +int fstab_has_mount_point_prefix_strv(char **prefixes); + int fstab_filter_options( const char *opts, const char *names, @@ -32,23 +34,20 @@ int fstab_filter_options( char **ret_value, char ***ret_values, char **ret_filtered); - static inline bool fstab_test_option(const char *opts, const char *names) { - return !!fstab_filter_options(opts, names, NULL, NULL, NULL, NULL); + return fstab_filter_options(opts, names, NULL, NULL, NULL, NULL); } - -int fstab_find_pri(const char *options, int *ret); - static inline bool fstab_test_yes_no_option(const char *opts, const char *yes_no) { - const char *opt; + const char *opt_found; /* If first name given is last, return 1. * If second name given is last or neither is found, return 0. */ - assert_se(fstab_filter_options(opts, yes_no, &opt, NULL, NULL, NULL) >= 0); + assert_se(fstab_filter_options(opts, yes_no, &opt_found, NULL, NULL, NULL) >= 0); - return opt == yes_no; + return opt_found == yes_no; } +int fstab_find_pri(const char *opts, int *ret); char *fstab_node_to_udev_node(const char *p); diff --git a/src/shared/generator.c b/src/shared/generator.c index 5626587..1b3304a 100644 --- a/src/shared/generator.c +++ b/src/shared/generator.c @@ -15,6 +15,7 @@ #include "log.h" #include "macro.h" #include "mkdir-label.h" +#include "mountpoint-util.h" #include "path-util.h" #include "process-util.h" #include "special.h" @@ -29,6 +30,7 @@ int generator_open_unit_file_full( const char *source, const char *fn, FILE **ret_file, + char **ret_final_path, char **ret_temp_path) { _cleanup_free_ char *p = NULL; @@ -72,10 +74,13 @@ int generator_open_unit_file_full( program_invocation_short_name); *ret_file = f; + + if (ret_final_path) + *ret_final_path = TAKE_PTR(p); + return 0; } - int generator_add_symlink_full( const char *dir, const char *dst, @@ -88,11 +93,13 @@ int generator_add_symlink_full( assert(dir); assert(dst); - assert(dep_type); assert(src); - /* Adds a symlink from <dst>.<dep_type>/ to <src> (if src is absolute) or ../<src> (otherwise). If - * <instance> is specified, then <src> must be a template unit name, and we'll instantiate it. */ + /* If 'dep_type' is specified adds a symlink from <dst>.<dep_type>/ to <src> (if src is absolute) or ../<src> (otherwise). + * + * If 'dep_type' is NULL, it will create a symlink to <src> (i.e. create an alias. + * + * If <instance> is specified, then <src> must be a template unit name, and we'll instantiate it. */ r = path_extract_directory(src, &dn); if (r < 0 && r != -EDESTADDRREQ) /* EDESTADDRREQ → just a file name was passed */ @@ -110,11 +117,19 @@ int generator_add_symlink_full( return log_error_errno(r, "Failed to instantiate '%s' for '%s': %m", fn, instance); } - from = path_join(dn ?: "..", fn); - if (!from) - return log_oom(); + if (dep_type) { /* Create a .wants/ style dep */ + from = path_join(dn ?: "..", fn); + if (!from) + return log_oom(); + + to = strjoin(dir, "/", dst, ".", dep_type, "/", instantiated ?: fn); + } else { /* or create an alias */ + from = dn ? path_join(dn, fn) : strdup(fn); + if (!from) + return log_oom(); - to = strjoin(dir, "/", dst, ".", dep_type, "/", instantiated ?: fn); + to = strjoin(dir, "/", dst); + } if (!to) return log_oom(); @@ -694,6 +709,77 @@ int generator_hook_up_pcrfs( return generator_add_symlink_full(dir, where_unit, "wants", pcrfs_unit_path, instance); } +int generator_hook_up_quotacheck( + const char *dir, + const char *what, + const char *where, + const char *target, + const char *fstype) { + + _cleanup_free_ char *where_unit = NULL, *instance = NULL; + int r; + + assert(dir); + assert(where); + + if (isempty(fstype) || streq(fstype, "auto")) + return log_warning_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Couldn't determine filesystem type for %s, quota cannot be activated", what); + if (!fstype_needs_quota(fstype)) + return log_warning_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Quota was requested for %s, but not supported, ignoring: %s", what, fstype); + + /* quotacheck unit for system root */ + if (path_equal(where, "/")) + return generator_add_symlink(dir, SPECIAL_LOCAL_FS_TARGET, "wants", SYSTEM_DATA_UNIT_DIR "/" SPECIAL_QUOTACHECK_ROOT_SERVICE); + + r = unit_name_path_escape(where, &instance); + if (r < 0) + return log_error_errno(r, "Failed to escape path '%s': %m", where); + + if (target) { + r = generator_add_ordering(dir, target, "After", SPECIAL_QUOTACHECK_SERVICE, instance); + if (r < 0) + return r; + } + + r = unit_name_from_path(where, ".mount", &where_unit); + if (r < 0) + return log_error_errno(r, "Failed to make unit name from path '%s': %m", where); + + return generator_add_symlink_full(dir, where_unit, "wants", SYSTEM_DATA_UNIT_DIR "/" SPECIAL_QUOTACHECK_SERVICE, instance); +} + +int generator_hook_up_quotaon( + const char *dir, + const char *where, + const char *target) { + + _cleanup_free_ char *where_unit = NULL, *instance = NULL; + int r; + + assert(dir); + assert(where); + + /* quotaon unit for system root is not instantiated */ + if (path_equal(where, "/")) + return generator_add_symlink(dir, SPECIAL_LOCAL_FS_TARGET, "wants", SYSTEM_DATA_UNIT_DIR "/" SPECIAL_QUOTAON_ROOT_SERVICE); + + r = unit_name_path_escape(where, &instance); + if (r < 0) + return log_error_errno(r, "Failed to escape path '%s': %m", where); + + if (target) { + r = generator_add_ordering(dir, target, "After", SPECIAL_QUOTAON_SERVICE, instance); + if (r < 0) + return r; + } + + r = unit_name_from_path(where, ".mount", &where_unit); + if (r < 0) + return log_error_errno(r, "Failed to make unit name from path '%s': %m", where); + + return generator_add_symlink_full(dir, where_unit, "wants", SYSTEM_DATA_UNIT_DIR "/" SPECIAL_QUOTAON_SERVICE, instance); +} + int generator_enable_remount_fs_service(const char *dir) { /* Pull in systemd-remount-fs.service */ return generator_add_symlink(dir, SPECIAL_LOCAL_FS_TARGET, "wants", @@ -790,6 +876,7 @@ int generator_write_cryptsetup_service_section( "TimeoutSec=infinity\n" /* The binary handles timeouts on its own */ "KeyringMode=shared\n" /* Make sure we can share cached keys among instances */ "OOMScoreAdjust=500\n" /* Unlocking can allocate a lot of memory if Argon2 is used */ + "ImportCredential=cryptsetup.*\n" "ExecStart=" SYSTEMD_CRYPTSETUP_PATH " attach '%s' '%s' '%s' '%s'\n" "ExecStop=" SYSTEMD_CRYPTSETUP_PATH " detach '%s'\n", name_escaped, what_escaped, strempty(key_file_escaped), strempty(options_escaped), @@ -883,6 +970,5 @@ void log_setup_generator(void) { log_set_target(LOG_TARGET_JOURNAL_OR_KMSG); } - log_parse_environment(); - (void) log_open(); + log_setup(); } diff --git a/src/shared/generator.h b/src/shared/generator.h index d97d6ed..baf1daf 100644 --- a/src/shared/generator.h +++ b/src/shared/generator.h @@ -6,10 +6,10 @@ #include "macro.h" #include "main-func.h" -int generator_open_unit_file_full(const char *dest, const char *source, const char *name, FILE **ret_file, char **ret_temp_path); +int generator_open_unit_file_full(const char *dest, const char *source, const char *name, FILE **ret_file, char **ret_final_path, char **ret_temp_path); static inline int generator_open_unit_file(const char *dest, const char *source, const char *name, FILE **ret_file) { - return generator_open_unit_file_full(dest, source, name, ret_file, NULL); + return generator_open_unit_file_full(dest, source, name, ret_file, NULL, NULL); } int generator_add_symlink_full(const char *dir, const char *dst, const char *dep_type, const char *src, const char *instance); @@ -85,6 +85,16 @@ int generator_hook_up_pcrfs( const char *dir, const char *where, const char *target); +int generator_hook_up_quotacheck( + const char *dir, + const char *what, + const char *where, + const char *target, + const char *fstype); +int generator_hook_up_quotaon( + const char *dir, + const char *where, + const char *target); int generator_enable_remount_fs_service(const char *dir); @@ -102,4 +112,5 @@ void log_setup_generator(void); impl(argv[1], \ argv[argc == 4 ? 2 : 1], \ argv[argc == 4 ? 3 : 1]), \ - r < 0 ? EXIT_FAILURE : EXIT_SUCCESS) + exit_failure_if_negative, \ + exit_failure_if_negative) diff --git a/src/shared/group-record.c b/src/shared/group-record.c index 1e33bdf..6c1e41a 100644 --- a/src/shared/group-record.c +++ b/src/shared/group-record.c @@ -2,7 +2,7 @@ #include "group-record.h" #include "strv.h" -#include "uid-alloc-range.h" +#include "uid-classification.h" #include "user-util.h" GroupRecord* group_record_new(void) { @@ -102,33 +102,13 @@ static int dispatch_per_machine(const char *name, JsonVariant *variant, JsonDisp return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array.", strna(name)); JSON_VARIANT_ARRAY_FOREACH(e, variant) { - bool matching = false; - JsonVariant *m; - if (!json_variant_is_object(e)) return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of objects.", strna(name)); - m = json_variant_by_key(e, "matchMachineId"); - if (m) { - r = per_machine_id_match(m, flags); - if (r < 0) - return r; - - matching = r > 0; - } - - if (!matching) { - m = json_variant_by_key(e, "matchHostname"); - if (m) { - r = per_machine_hostname_match(m, flags); - if (r < 0) - return r; - - matching = r > 0; - } - } - - if (!matching) + r = per_machine_match(e, flags); + if (r < 0) + return r; + if (r == 0) continue; r = json_dispatch(e, per_machine_dispatch_table, flags, userdata); @@ -230,7 +210,7 @@ int group_record_load( if (r < 0) return r; - r = json_dispatch(h->json, group_dispatch_table, json_flags, h); + r = json_dispatch(h->json, group_dispatch_table, json_flags | JSON_ALLOW_EXTENSIONS, h); if (r < 0) return r; diff --git a/src/shared/hibernate-util.c b/src/shared/hibernate-util.c index c3991cf..7c21157 100644 --- a/src/shared/hibernate-util.c +++ b/src/shared/hibernate-util.c @@ -159,8 +159,9 @@ static int read_resume_config(dev_t *ret_devno, uint64_t *ret_offset) { } if (devno == 0 && offset > 0 && offset != UINT64_MAX) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), - "Found resume_offset=%" PRIu64 " but resume= is unset, refusing.", offset); + return log_debug_errno(SYNTHETIC_ERRNO(ENOMEDIUM), + "Found populated /sys/power/resume_offset (%" PRIu64 ") but /sys/power/resume is not set, refusing.", + offset); *ret_devno = devno; *ret_offset = offset; @@ -393,7 +394,7 @@ int find_suitable_hibernation_device_full(HibernationDevice *ret_device, uint64_ if (!entry) { /* No need to check n_swaps == 0, since it's rejected early */ assert(resume_config_devno > 0); - return log_debug_errno(SYNTHETIC_ERRNO(ENOSPC), "Cannot find swap entry corresponding to /sys/power/resume."); + return log_debug_errno(SYNTHETIC_ERRNO(ESTALE), "Cannot find swap entry corresponding to /sys/power/resume."); } if (ret_device) { @@ -451,11 +452,11 @@ int hibernation_is_safe(void) { bypass_space_check = getenv_bool("SYSTEMD_BYPASS_HIBERNATION_MEMORY_CHECK") > 0; r = find_suitable_hibernation_device_full(NULL, &size, &used); - if (r == -ENOSPC && bypass_space_check) - /* If we don't have any available swap space at all, and SYSTEMD_BYPASS_HIBERNATION_MEMORY_CHECK - * is set, skip all remaining checks since we can't do that properly anyway. It is quite - * possible that the user is using a setup similar to #30083. When we actually perform - * hibernation in sleep.c we'll check everything again. */ + if (IN_SET(r, -ENOSPC, -ESTALE) && bypass_space_check) + /* If we don't have any available swap space at all, or the specified resume device is missing, + * and $SYSTEMD_BYPASS_HIBERNATION_MEMORY_CHECK is set, skip all remaining checks since + * we can't do that properly anyway. It is quite possible that the user is using a setup + * similar to #30083. When we actually perform hibernation in sleep.c we'll check everything again. */ return 0; if (r < 0) return r; @@ -466,7 +467,7 @@ int hibernation_is_safe(void) { "Not running on EFI and resume= is not set. Hibernation is not safe."); if (bypass_space_check) - return true; + return 0; r = get_proc_meminfo_active(&active); if (r < 0) @@ -483,30 +484,23 @@ int hibernation_is_safe(void) { int write_resume_config(dev_t devno, uint64_t offset, const char *device) { char offset_str[DECIMAL_STR_MAX(uint64_t)]; - _cleanup_free_ char *path = NULL; const char *devno_str; int r; + assert(devno > 0); + assert(device); + devno_str = FORMAT_DEVNUM(devno); xsprintf(offset_str, "%" PRIu64, offset); - if (!device) { - r = device_path_make_canonical(S_IFBLK, devno, &path); - if (r < 0) - return log_error_errno(r, - "Failed to format canonical device path for devno '" DEVNUM_FORMAT_STR "': %m", - DEVNUM_FORMAT_VAL(devno)); - device = path; - } - /* We write the offset first since it's safer. Note that this file is only available in 4.17+, so * fail gracefully if it doesn't exist and we're only overwriting it with 0. */ r = write_string_file("/sys/power/resume_offset", offset_str, WRITE_STRING_FILE_DISABLE_BUFFER); if (r == -ENOENT) { if (offset != 0) return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), - "Can't configure hibernation offset %" PRIu64 ", kernel does not support /sys/power/resume_offset. Refusing.", - offset); + "Can't configure swap file offset %s, kernel does not support /sys/power/resume_offset. Refusing.", + offset_str); log_warning_errno(r, "/sys/power/resume_offset is unavailable, skipping writing swap file offset."); } else if (r < 0) @@ -526,3 +520,18 @@ int write_resume_config(dev_t devno, uint64_t offset, const char *device) { return 0; } + +int clear_efi_hibernate_location_and_warn(void) { + int r; + + if (!is_efi_boot()) + return 0; + + r = efi_set_variable(EFI_SYSTEMD_VARIABLE(HibernateLocation), NULL, 0); + if (r == -ENOENT) + return 0; + if (r < 0) + return log_warning_errno(r, "Failed to clear HibernateLocation EFI variable: %m"); + + return 1; +} diff --git a/src/shared/hibernate-util.h b/src/shared/hibernate-util.h index 2ae10fb..394d0b4 100644 --- a/src/shared/hibernate-util.h +++ b/src/shared/hibernate-util.h @@ -22,5 +22,7 @@ int hibernation_is_safe(void); int write_resume_config(dev_t devno, uint64_t offset, const char *device); +int clear_efi_hibernate_location_and_warn(void); + /* Only for test-fiemap */ int read_fiemap(int fd, struct fiemap **ret); diff --git a/src/shared/hostname-setup.c b/src/shared/hostname-setup.c index 137c29a..6cfd4b5 100644 --- a/src/shared/hostname-setup.c +++ b/src/shared/hostname-setup.c @@ -6,12 +6,16 @@ #include <sys/utsname.h> #include <unistd.h> +#include "sd-daemon.h" + #include "alloc-util.h" +#include "creds-util.h" #include "fd-util.h" #include "fileio.h" #include "fs-util.h" #include "hostname-setup.h" #include "hostname-util.h" +#include "initrd-util.h" #include "log.h" #include "macro.h" #include "proc-cmdline.h" @@ -23,7 +27,8 @@ static int sethostname_idempotent_full(const char *s, bool really) { assert(s); - assert_se(uname(&u) >= 0); + if (uname(&u) < 0) + return -errno; if (streq_ptr(s, u.nodename)) return 0; @@ -40,37 +45,56 @@ int sethostname_idempotent(const char *s) { } int shorten_overlong(const char *s, char **ret) { - char *h, *p; + _cleanup_free_ char *h = NULL; /* Shorten an overlong name to HOST_NAME_MAX or to the first dot, * whatever comes earlier. */ assert(s); + assert(ret); h = strdup(s); if (!h) return -ENOMEM; if (hostname_is_valid(h, 0)) { - *ret = h; + *ret = TAKE_PTR(h); return 0; } - p = strchr(h, '.'); + char *p = strchr(h, '.'); if (p) *p = 0; strshorten(h, HOST_NAME_MAX); - if (!hostname_is_valid(h, 0)) { - free(h); + if (!hostname_is_valid(h, /* flags= */ 0)) return -EDOM; - } - *ret = h; + *ret = TAKE_PTR(h); return 1; } +static int acquire_hostname_from_credential(char **ret) { + _cleanup_free_ char *cred = NULL; + int r; + + assert(ret); + + r = read_credential_with_decryption("system.hostname", (void **) &cred, /* ret_size= */ NULL); + if (r < 0) + return log_warning_errno(r, "Failed to read system.hostname credential, ignoring: %m"); + if (r == 0) /* not found */ + return -ENXIO; + + if (!hostname_is_valid(cred, VALID_HOSTNAME_TRAILING_DOT)) /* check that the hostname we return is valid */ + return log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "Hostname specified in system.hostname credential is invalid, ignoring: %s", cred); + + log_info("Initializing hostname from credential."); + *ret = TAKE_PTR(cred); + return 0; +} + int read_etc_hostname_stream(FILE *f, char **ret) { int r; @@ -126,66 +150,64 @@ void hostname_update_source_hint(const char *hostname, HostnameSource source) { r = write_string_file("/run/systemd/default-hostname", hostname, WRITE_STRING_FILE_CREATE | WRITE_STRING_FILE_ATOMIC); if (r < 0) - log_warning_errno(r, "Failed to create \"/run/systemd/default-hostname\": %m"); + log_warning_errno(r, "Failed to create \"/run/systemd/default-hostname\", ignoring: %m"); } else unlink_or_warn("/run/systemd/default-hostname"); } int hostname_setup(bool really) { - _cleanup_free_ char *b = NULL; - const char *hn = NULL; + _cleanup_free_ char *hn = NULL; HostnameSource source; bool enoent = false; int r; - r = proc_cmdline_get_key("systemd.hostname", 0, &b); + r = proc_cmdline_get_key("systemd.hostname", 0, &hn); if (r < 0) log_warning_errno(r, "Failed to retrieve system hostname from kernel command line, ignoring: %m"); else if (r > 0) { - if (hostname_is_valid(b, VALID_HOSTNAME_TRAILING_DOT)) { - hn = b; + if (hostname_is_valid(hn, VALID_HOSTNAME_TRAILING_DOT)) source = HOSTNAME_TRANSIENT; - } else { - log_warning("Hostname specified on kernel command line is invalid, ignoring: %s", b); - b = mfree(b); + else { + log_warning("Hostname specified on kernel command line is invalid, ignoring: %s", hn); + hn = mfree(hn); } } if (!hn) { - r = read_etc_hostname(NULL, &b); - if (r < 0) { - if (r == -ENOENT) - enoent = true; - else - log_warning_errno(r, "Failed to read configured hostname: %m"); - } else { - hn = b; + r = read_etc_hostname(NULL, &hn); + if (r == -ENOENT) + enoent = true; + else if (r < 0) + log_warning_errno(r, "Failed to read configured hostname, ignoring: %m"); + else source = HOSTNAME_STATIC; - } } if (!hn) { - _cleanup_free_ char *buf = NULL; + r = acquire_hostname_from_credential(&hn); + if (r >= 0) + source = HOSTNAME_TRANSIENT; + } + if (!hn) { /* Don't override the hostname if it is already set and not explicitly configured */ - r = gethostname_full(GET_HOSTNAME_ALLOW_LOCALHOST, &buf); + r = gethostname_full(GET_HOSTNAME_ALLOW_LOCALHOST, &hn); if (r == -ENOMEM) return log_oom(); if (r >= 0) { - log_debug("No hostname configured, leaving existing hostname <%s> in place.", buf); - return 0; + log_debug("No hostname configured, leaving existing hostname <%s> in place.", hn); + goto finish; } if (enoent) log_info("No hostname configured, using default hostname."); - hn = b = get_default_hostname(); + hn = get_default_hostname(); if (!hn) return log_oom(); source = HOSTNAME_DEFAULT; - } r = sethostname_idempotent_full(hn, really); @@ -201,7 +223,11 @@ int hostname_setup(bool really) { if (really) hostname_update_source_hint(hn, source); - return r; +finish: + if (!in_initrd()) + (void) sd_notifyf(/* unset_environment= */ false, "X_SYSTEMD_HOSTNAME=%s", hn); + + return 0; } static const char* const hostname_source_table[] = { diff --git a/src/shared/hwdb-util.c b/src/shared/hwdb-util.c index f67e917..d96902c 100644 --- a/src/shared/hwdb-util.c +++ b/src/shared/hwdb-util.c @@ -6,6 +6,7 @@ #include "alloc-util.h" #include "conf-files.h" +#include "env-util.h" #include "fd-util.h" #include "fileio.h" #include "fs-util.h" @@ -483,7 +484,7 @@ static int import_file(struct trie *trie, const char *filename, uint16_t file_pr if (r == 0) break; - line_number ++; + line_number++; /* comment line */ if (line[0] == '#') @@ -710,3 +711,16 @@ bool hwdb_should_reload(sd_hwdb *hwdb) { return true; return false; } + +int hwdb_bypass(void) { + int r; + + r = getenv_bool("SYSTEMD_HWDB_UPDATE_BYPASS"); + if (r < 0 && r != -ENXIO) + log_debug_errno(r, "Failed to parse $SYSTEMD_HWDB_UPDATE_BYPASS, assuming no."); + if (r <= 0) + return false; + + log_debug("$SYSTEMD_HWDB_UPDATE_BYPASS is enabled, skipping execution."); + return true; +} diff --git a/src/shared/hwdb-util.h b/src/shared/hwdb-util.h index cb93690..00610b1 100644 --- a/src/shared/hwdb-util.h +++ b/src/shared/hwdb-util.h @@ -8,3 +8,4 @@ bool hwdb_should_reload(sd_hwdb *hwdb); int hwdb_update(const char *root, const char *hwdb_bin_dir, bool strict, bool compat); int hwdb_query(const char *modalias, const char *root); +int hwdb_bypass(void); diff --git a/src/shared/idn-util.c b/src/shared/idn-util.c index 26a9d60..aa88e11 100644 --- a/src/shared/idn-util.c +++ b/src/shared/idn-util.c @@ -16,11 +16,16 @@ static void* idn_dl = NULL; #endif #if HAVE_LIBIDN2 -int (*sym_idn2_lookup_u8)(const uint8_t* src, uint8_t** lookupname, int flags) = NULL; +DLSYM_FUNCTION(idn2_lookup_u8); const char *(*sym_idn2_strerror)(int rc) _const_ = NULL; -int (*sym_idn2_to_unicode_8z8z)(const char * input, char ** output, int flags) = NULL; +DLSYM_FUNCTION(idn2_to_unicode_8z8z); int dlopen_idn(void) { + ELF_NOTE_DLOPEN("idn", + "Support for internationalized domain names", + ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + "libidn2.so.0"); + return dlopen_many_sym_or_warn( &idn_dl, "libidn2.so.0", LOG_DEBUG, DLSYM_ARG(idn2_lookup_u8), @@ -30,15 +35,20 @@ int dlopen_idn(void) { #endif #if HAVE_LIBIDN -int (*sym_idna_to_ascii_4i)(const uint32_t * in, size_t inlen, char *out, int flags); -int (*sym_idna_to_unicode_44i)(const uint32_t * in, size_t inlen, uint32_t * out, size_t * outlen, int flags); -char* (*sym_stringprep_ucs4_to_utf8)(const uint32_t * str, ssize_t len, size_t * items_read, size_t * items_written); -uint32_t* (*sym_stringprep_utf8_to_ucs4)(const char *str, ssize_t len, size_t *items_written); +DLSYM_FUNCTION(idna_to_ascii_4i); +DLSYM_FUNCTION(idna_to_unicode_44i); +DLSYM_FUNCTION(stringprep_ucs4_to_utf8); +DLSYM_FUNCTION(stringprep_utf8_to_ucs4); int dlopen_idn(void) { _cleanup_(dlclosep) void *dl = NULL; int r; + ELF_NOTE_DLOPEN("idn", + "Support for internationalized domain names", + ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + "libidn.so.12", "libidn.so.11"); + if (idn_dl) return 0; /* Already loaded */ diff --git a/src/shared/idn-util.h b/src/shared/idn-util.h index e64bd99..3ae2f13 100644 --- a/src/shared/idn-util.h +++ b/src/shared/idn-util.h @@ -11,6 +11,8 @@ #include <inttypes.h> #if HAVE_LIBIDN2 || HAVE_LIBIDN +#include "dlfcn-util.h" + int dlopen_idn(void); #else static inline int dlopen_idn(void) { @@ -19,14 +21,14 @@ static inline int dlopen_idn(void) { #endif #if HAVE_LIBIDN2 -extern int (*sym_idn2_lookup_u8)(const uint8_t* src, uint8_t** lookupname, int flags); +DLSYM_PROTOTYPE(idn2_lookup_u8); extern const char *(*sym_idn2_strerror)(int rc) _const_; -extern int (*sym_idn2_to_unicode_8z8z)(const char * input, char ** output, int flags); +DLSYM_PROTOTYPE(idn2_to_unicode_8z8z); #endif #if HAVE_LIBIDN -extern int (*sym_idna_to_ascii_4i)(const uint32_t * in, size_t inlen, char *out, int flags); -extern int (*sym_idna_to_unicode_44i)(const uint32_t * in, size_t inlen,uint32_t * out, size_t * outlen, int flags); -extern char* (*sym_stringprep_ucs4_to_utf8)(const uint32_t * str, ssize_t len, size_t * items_read, size_t * items_written); -extern uint32_t* (*sym_stringprep_utf8_to_ucs4)(const char *str, ssize_t len, size_t *items_written); +DLSYM_PROTOTYPE(idna_to_ascii_4i); +DLSYM_PROTOTYPE(idna_to_unicode_44i); +DLSYM_PROTOTYPE(stringprep_ucs4_to_utf8); +DLSYM_PROTOTYPE(stringprep_utf8_to_ucs4); #endif diff --git a/src/shared/image-policy.c b/src/shared/image-policy.c index 3c3de50..e7bd84e 100644 --- a/src/shared/image-policy.c +++ b/src/shared/image-policy.c @@ -50,6 +50,20 @@ PartitionPolicyFlags partition_policy_flags_extend(PartitionPolicyFlags flags) { 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; @@ -676,6 +690,90 @@ int parse_image_policy_argument(const char *s, ImagePolicy **policy) { 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; +} + const ImagePolicy image_policy_allow = { /* Allow policy */ .n_policies = 0, @@ -726,6 +824,14 @@ const ImagePolicy image_policy_confext = { .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, diff --git a/src/shared/image-policy.h b/src/shared/image-policy.h index f59c16e..a1a6afa 100644 --- a/src/shared/image-policy.h +++ b/src/shared/image-policy.h @@ -58,9 +58,10 @@ 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; /* No verity required */ -extern const ImagePolicy image_policy_sysext_strict; /* Signed verity required */ -extern const ImagePolicy image_policy_confext; /* No verity required */ +extern const ImagePolicy image_policy_sysext; /* No verity required */ +extern const ImagePolicy image_policy_sysext_strict; /* Signed verity required */ +extern const ImagePolicy image_policy_confext; /* No verity required */ +extern const ImagePolicy image_policy_confext_strict; /* Signed verity required */ extern const ImagePolicy image_policy_container; extern const ImagePolicy image_policy_service; extern const ImagePolicy image_policy_host; @@ -79,6 +80,7 @@ static inline size_t image_policy_n_entries(const ImagePolicy *policy) { } PartitionPolicyFlags partition_policy_flags_extend(PartitionPolicyFlags flags); +PartitionPolicyFlags partition_policy_flags_reduce(PartitionPolicyFlags flags); PartitionPolicyFlags partition_policy_flags_from_string(const char *s); int partition_policy_flags_to_string(PartitionPolicyFlags flags, bool simplify, char **ret); @@ -94,6 +96,8 @@ 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. */ +int image_policy_intersect(const ImagePolicy *a, const ImagePolicy *b, ImagePolicy **ret); + static inline ImagePolicy* image_policy_free(ImagePolicy *p) { return mfree(p); } diff --git a/src/shared/in-addr-prefix-util.c b/src/shared/in-addr-prefix-util.c index 7c0033d..edccca5 100644 --- a/src/shared/in-addr-prefix-util.c +++ b/src/shared/in-addr-prefix-util.c @@ -59,9 +59,9 @@ static void in_addr_prefix_hash_func(const struct in_addr_prefix *a, struct siph assert(a); assert(state); - siphash24_compress(&a->family, sizeof(a->family), state); - siphash24_compress(&a->prefixlen, sizeof(a->prefixlen), state); - siphash24_compress(&a->address, FAMILY_ADDRESS_SIZE(a->family), state); + siphash24_compress_typesafe(a->family, state); + siphash24_compress_typesafe(a->prefixlen, state); + in_addr_hash_func(&a->address, a->family, state); } static int in_addr_prefix_compare_func(const struct in_addr_prefix *x, const struct in_addr_prefix *y) { diff --git a/src/shared/initreq.h b/src/shared/initreq.h index da9783c..c6a1050 100644 --- a/src/shared/initreq.h +++ b/src/shared/initreq.h @@ -1,14 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.0-or-later */ /* - * initreq.h Interface to talk to init through /dev/initctl. - * - * Copyright (C) 1995-2004 Miquel van Smoorenburg - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * + * Copyright (C) 1995-2004 Miquel van Smoorenburg * Version: @(#)initreq.h 1.28 31-Mar-2004 MvS */ diff --git a/src/shared/install-file.c b/src/shared/install-file.c index 3b4d651..ea2189f 100644 --- a/src/shared/install-file.c +++ b/src/shared/install-file.c @@ -12,7 +12,7 @@ #include "rm-rf.h" #include "sync-util.h" -int fs_make_very_read_only(int fd) { +static int fs_make_very_read_only(int fd) { struct stat st; int r; diff --git a/src/shared/install-file.h b/src/shared/install-file.h index c37254f..af07ab2 100644 --- a/src/shared/install-file.h +++ b/src/shared/install-file.h @@ -1,8 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int fs_make_very_read_only(int fd); - typedef enum InstallFileFlags { INSTALL_REPLACE = 1 << 0, /* Replace an existing inode */ INSTALL_READ_ONLY = 1 << 1, /* Call fs_make_very_read_only() to make the inode comprehensively read-only */ diff --git a/src/shared/install-printf.c b/src/shared/install-printf.c index 3cc7093..bdb44b3 100644 --- a/src/shared/install-printf.c +++ b/src/shared/install-printf.c @@ -37,16 +37,11 @@ static int specifier_prefix_and_instance(char specifier, const void *data, const static int specifier_name(char specifier, const void *data, const char *root, const void *userdata, char **ret) { const InstallInfo *i = ASSERT_PTR(userdata); - char *ans; if (unit_name_is_valid(i->name, UNIT_NAME_TEMPLATE) && i->default_instance) return unit_name_replace_instance(i->name, i->default_instance, ret); - ans = strdup(i->name); - if (!ans) - return -ENOMEM; - *ret = ans; - return 0; + return strdup_to(ret, i->name); } static int specifier_prefix(char specifier, const void *data, const char *root, const void *userdata, char **ret) { @@ -86,14 +81,10 @@ static int specifier_last_component(char specifier, const void *data, const char return r; dash = strrchr(prefix, '-'); - if (dash) { - dash = strdup(dash + 1); - if (!dash) - return -ENOMEM; - *ret = dash; - } else - *ret = TAKE_PTR(prefix); + if (dash) + return strdup_to(ret, dash + 1); + *ret = TAKE_PTR(prefix); return 0; } diff --git a/src/shared/install.c b/src/shared/install.c index 27a421e..dd2bd5c 100644 --- a/src/shared/install.c +++ b/src/shared/install.c @@ -10,6 +10,7 @@ #include <unistd.h> #include "alloc-util.h" +#include "bus-common-errors.h" #include "chase.h" #include "conf-files.h" #include "conf-parser.h" @@ -161,9 +162,10 @@ static int path_is_generator(const LookupPaths *lp, const char *path) { if (r < 0) return r; - return path_equal_ptr(parent, lp->generator) || - path_equal_ptr(parent, lp->generator_early) || - path_equal_ptr(parent, lp->generator_late); + return PATH_IN_SET(parent, + lp->generator, + lp->generator_early, + lp->generator_late); } static int path_is_transient(const LookupPaths *lp, const char *path) { @@ -177,7 +179,7 @@ static int path_is_transient(const LookupPaths *lp, const char *path) { if (r < 0) return r; - return path_equal_ptr(parent, lp->transient); + return path_equal(parent, lp->transient); } static int path_is_control(const LookupPaths *lp, const char *path) { @@ -191,8 +193,9 @@ static int path_is_control(const LookupPaths *lp, const char *path) { if (r < 0) return r; - return path_equal_ptr(parent, lp->persistent_control) || - path_equal_ptr(parent, lp->runtime_control); + return PATH_IN_SET(parent, + lp->persistent_control, + lp->runtime_control); } static int path_is_config(const LookupPaths *lp, const char *path, bool check_parent) { @@ -213,8 +216,9 @@ static int path_is_config(const LookupPaths *lp, const char *path, bool check_pa path = parent; } - return path_equal_ptr(path, lp->persistent_config) || - path_equal_ptr(path, lp->runtime_config); + return PATH_IN_SET(path, + lp->persistent_config, + lp->runtime_config); } static int path_is_runtime(const LookupPaths *lp, const char *path, bool check_parent) { @@ -240,12 +244,13 @@ static int path_is_runtime(const LookupPaths *lp, const char *path, bool check_p path = parent; } - return path_equal_ptr(path, lp->runtime_config) || - path_equal_ptr(path, lp->generator) || - path_equal_ptr(path, lp->generator_early) || - path_equal_ptr(path, lp->generator_late) || - path_equal_ptr(path, lp->transient) || - path_equal_ptr(path, lp->runtime_control); + return PATH_IN_SET(path, + lp->runtime_config, + lp->generator, + lp->generator_early, + lp->generator_late, + lp->transient, + lp->runtime_control); } static int path_is_vendor_or_generator(const LookupPaths *lp, const char *path) { @@ -324,130 +329,176 @@ InstallChangeType install_changes_add( void install_changes_free(InstallChange *changes, size_t n_changes) { assert(changes || n_changes == 0); - for (size_t i = 0; i < n_changes; i++) { - free(changes[i].path); - free(changes[i].source); + FOREACH_ARRAY(i, changes, n_changes) { + free(i->path); + free(i->source); } free(changes); } -void install_changes_dump(int r, const char *verb, const InstallChange *changes, size_t n_changes, bool quiet) { - int err = 0; +static void install_change_dump_success(const InstallChange *change) { + assert(change); + assert(change->path); - assert(changes || n_changes == 0); - /* If verb is not specified, errors are not allowed! */ - assert(verb || r >= 0); + switch (change->type) { - for (size_t i = 0; i < n_changes; i++) { - assert(changes[i].path); - /* This tries to tell the compiler that it's safe to use 'verb' in a string format if there - * was an error, but the compiler doesn't care and fails anyway, so strna(verb) is used - * too. */ - assert(verb || changes[i].type >= 0); - verb = strna(verb); + case INSTALL_CHANGE_SYMLINK: + return log_info("Created symlink '%s' %s '%s'.", + change->path, special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), change->source); - /* When making changes here, make sure to also change install_error() in dbus-manager.c. */ + case INSTALL_CHANGE_UNLINK: + return log_info("Removed '%s'.", change->path); - switch (changes[i].type) { - case INSTALL_CHANGE_SYMLINK: - if (!quiet) - log_info("Created symlink %s %s %s.", - changes[i].path, - special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), - changes[i].source); - break; - case INSTALL_CHANGE_UNLINK: - if (!quiet) - log_info("Removed \"%s\".", changes[i].path); - break; - case INSTALL_CHANGE_IS_MASKED: - if (!quiet) - log_info("Unit %s is masked, ignoring.", changes[i].path); - break; - case INSTALL_CHANGE_IS_MASKED_GENERATOR: - if (!quiet) - log_info("Unit %s is masked via a generator and cannot be unmasked.", - changes[i].path); - break; - case INSTALL_CHANGE_IS_DANGLING: - if (!quiet) - log_info("Unit %s is an alias to a unit that is not present, ignoring.", - changes[i].path); - break; - case INSTALL_CHANGE_DESTINATION_NOT_PRESENT: - if (!quiet) - log_warning("Unit %s is added as a dependency to a non-existent unit %s.", - changes[i].source, changes[i].path); - break; - case INSTALL_CHANGE_AUXILIARY_FAILED: + case INSTALL_CHANGE_IS_MASKED: + return log_info("Unit %s is masked, ignoring.", change->path); + + case INSTALL_CHANGE_IS_MASKED_GENERATOR: + return log_info("Unit %s is masked via a generator and cannot be unmasked, skipping.", change->path); + + case INSTALL_CHANGE_IS_DANGLING: + return log_info("Unit %s is an alias to a non-existent unit, ignoring.", change->path); + + case INSTALL_CHANGE_DESTINATION_NOT_PRESENT: + return log_warning("Unit %s is added as a dependency to a non-existent unit %s.", + change->source, change->path); + + case INSTALL_CHANGE_AUXILIARY_FAILED: + return log_warning("Failed to enable auxiliary unit %s, ignoring.", change->path); + + default: + assert_not_reached(); + } +} + +int install_change_dump_error(const InstallChange *change, char **ret_errmsg, const char **ret_bus_error) { + char *m; + const char *bus_error; + + /* Returns 0: known error and ret_errmsg formatted + * < 0: non-recognizable error */ + + assert(change); + assert(change->path); + assert(change->type < 0); + assert(ret_errmsg); + + switch (change->type) { + + case -EEXIST: + m = strjoin("File '", change->path, "' already exists", + change->source ? " and is a symlink to " : NULL, change->source); + bus_error = BUS_ERROR_UNIT_EXISTS; + break; + + case -ERFKILL: + m = strjoin("Unit ", change->path, " is masked"); + bus_error = BUS_ERROR_UNIT_MASKED; + break; + + case -EADDRNOTAVAIL: + m = strjoin("Unit ", change->path, " is transient or generated"); + bus_error = BUS_ERROR_UNIT_GENERATED; + break; + + case -ETXTBSY: + m = strjoin("File '", change->path, "' is under the systemd unit hierarchy already"); + bus_error = BUS_ERROR_UNIT_BAD_PATH; + break; + + case -EBADSLT: + m = strjoin("Invalid specifier in unit ", change->path); + bus_error = BUS_ERROR_BAD_UNIT_SETTING; + break; + + case -EIDRM: + m = strjoin("Refusing to operate on template unit ", change->source, + " when destination unit ", change->path, " is a non-template unit"); + bus_error = BUS_ERROR_BAD_UNIT_SETTING; + break; + + case -EUCLEAN: + m = strjoin("Invalid unit name ", change->path); + bus_error = BUS_ERROR_BAD_UNIT_SETTING; + break; + + case -ELOOP: + m = strjoin("Refusing to operate on linked unit file ", change->path); + bus_error = BUS_ERROR_UNIT_LINKED; + break; + + case -EXDEV: + if (change->source) + m = strjoin("Cannot alias ", change->source, " as ", change->path); + else + m = strjoin("Invalid unit reference ", change->path); + bus_error = BUS_ERROR_BAD_UNIT_SETTING; + break; + + case -ENOENT: + m = strjoin("Unit ", change->path, " does not exist"); + bus_error = BUS_ERROR_NO_SUCH_UNIT; + break; + + case -ENOLINK: + m = strjoin("Unit ", change->path, " is an unresolvable alias"); + bus_error = BUS_ERROR_NO_SUCH_UNIT; + break; + + case -EUNATCH: + m = strjoin("Cannot resolve specifiers in unit ", change->path); + bus_error = BUS_ERROR_BAD_UNIT_SETTING; + break; + + default: + return change->type; + } + if (!m) + return -ENOMEM; + + *ret_errmsg = m; + if (ret_bus_error) + *ret_bus_error = bus_error; + + return 0; +} + +void install_changes_dump( + int error, + const char *verb, + const InstallChange *changes, + size_t n_changes, + bool quiet) { + + bool err_logged = false; + int r; + + /* If verb is not specified, errors are not allowed! */ + assert(verb || error >= 0); + assert(changes || n_changes == 0); + + FOREACH_ARRAY(i, changes, n_changes) + if (i->type >= 0) { if (!quiet) - log_warning("Failed to enable auxiliary unit %s, ignoring.", changes[i].path); - break; - case -EEXIST: - if (changes[i].source) - err = log_error_errno(changes[i].type, - "Failed to %s unit, file \"%s\" already exists and is a symlink to \"%s\".", - verb, changes[i].path, changes[i].source); - else - err = log_error_errno(changes[i].type, - "Failed to %s unit, file \"%s\" already exists.", - verb, changes[i].path); - break; - case -ERFKILL: - err = log_error_errno(changes[i].type, "Failed to %s unit, unit %s is masked.", - verb, changes[i].path); - break; - case -EADDRNOTAVAIL: - err = log_error_errno(changes[i].type, "Failed to %s unit, unit %s is transient or generated.", - verb, changes[i].path); - break; - case -ETXTBSY: - err = log_error_errno(changes[i].type, "Failed to %s unit, file %s is under the systemd unit hierarchy already.", - verb, changes[i].path); - break; - case -EBADSLT: - err = log_error_errno(changes[i].type, "Failed to %s unit, invalid specifier in \"%s\".", - verb, changes[i].path); - break; - case -EIDRM: - err = log_error_errno(changes[i].type, "Failed to %s %s, destination unit %s is a non-template unit.", - verb, changes[i].source, changes[i].path); - break; - case -EUCLEAN: - err = log_error_errno(changes[i].type, - "Failed to %s unit, \"%s\" is not a valid unit name.", - verb, changes[i].path); - break; - case -ELOOP: - err = log_error_errno(changes[i].type, "Failed to %s unit, refusing to operate on linked unit file %s.", - verb, changes[i].path); - break; - case -EXDEV: - if (changes[i].source) - err = log_error_errno(changes[i].type, "Failed to %s unit, cannot alias %s as %s.", - verb, changes[i].source, changes[i].path); + install_change_dump_success(i); + } else { + _cleanup_free_ char *err_message = NULL; + + assert(verb); + + r = install_change_dump_error(i, &err_message, /* ret_bus_error = */ NULL); + if (r == -ENOMEM) + return (void) log_oom(); + if (r < 0) + log_error_errno(r, "Failed to %s unit %s: %m", verb, i->path); else - err = log_error_errno(changes[i].type, "Failed to %s unit, invalid unit reference \"%s\".", - verb, changes[i].path); - break; - case -ENOENT: - err = log_error_errno(changes[i].type, "Failed to %s unit, unit %s does not exist.", - verb, changes[i].path); - break; - case -EUNATCH: - err = log_error_errno(changes[i].type, "Failed to %s unit, cannot resolve specifiers in \"%s\".", - verb, changes[i].path); - break; - default: - assert(changes[i].type < 0); - err = log_error_errno(changes[i].type, "Failed to %s unit, file \"%s\": %m", - verb, changes[i].path); + log_error_errno(i->type, "Failed to %s unit: %s", verb, err_message); + + err_logged = true; } - } - if (r < 0 && err >= 0) - log_error_errno(r, "Failed to %s: %m.", verb); + if (error < 0 && !err_logged) + log_error_errno(error, "Failed to %s unit: %m.", verb); } /** @@ -750,7 +801,7 @@ static int remove_marked_symlinks( assert(config_path); assert(lp); - if (set_size(remove_symlinks_to) <= 0) + if (set_isempty(remove_symlinks_to)) return 0; fd = open(config_path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC); @@ -996,7 +1047,7 @@ static int find_symlinks_in_scope( if (r > 0) { /* We found symlinks in this dir? Yay! Let's see where precisely it is enabled. */ - if (path_equal_ptr(*p, lp->persistent_config)) { + if (path_equal(*p, lp->persistent_config)) { /* This is the best outcome, let's return it immediately. */ *state = UNIT_FILE_ENABLED; return 1; @@ -1017,7 +1068,7 @@ static int find_symlinks_in_scope( enabled_at_all = true; } else if (same_name_link) { - if (path_equal_ptr(*p, lp->persistent_config)) + if (path_equal(*p, lp->persistent_config)) same_name_link_config = true; else { r = path_is_runtime(lp, *p, false); @@ -1813,7 +1864,7 @@ int unit_file_verify_alias( _cleanup_free_ char *dir = NULL; char *p; - path_alias ++; /* skip over slash */ + path_alias++; /* skip over slash */ r = path_extract_directory(dst, &dir); if (r < 0) @@ -1892,28 +1943,25 @@ static int install_info_symlink_alias( InstallChange **changes, size_t *n_changes) { - int r = 0, q; + int r, ret = 0; assert(info); assert(lp); assert(config_path); STRV_FOREACH(s, info->aliases) { - _cleanup_free_ char *alias_path = NULL, *dst = NULL, *dst_updated = NULL; - bool broken; + _cleanup_free_ char *alias_path = NULL, *alias_target = NULL, *dst = NULL, *dst_updated = NULL; - q = install_name_printf(scope, info, *s, &dst); - if (q < 0) { - install_changes_add(changes, n_changes, q, *s, NULL); - r = r < 0 ? r : q; + r = install_name_printf(scope, info, *s, &dst); + if (r < 0) { + RET_GATHER(ret, install_changes_add(changes, n_changes, r, *s, NULL)); continue; } - q = unit_file_verify_alias(info, dst, &dst_updated, changes, n_changes); - if (q == -ELOOP) - continue; - if (q < 0) { - r = r < 0 ? r : q; + r = unit_file_verify_alias(info, dst, &dst_updated, changes, n_changes); + if (r < 0) { + if (r != -ELOOP) + RET_GATHER(ret, r); continue; } @@ -1921,18 +1969,30 @@ static int install_info_symlink_alias( if (!alias_path) return -ENOMEM; - q = chase(alias_path, lp->root_dir, CHASE_NONEXISTENT, NULL, NULL); - if (q < 0 && q != -ENOENT) { - r = r < 0 ? r : q; + r = in_search_path(lp, info->path); + if (r < 0) + return r; + if (r == 0) { + /* The unit path itself is outside of the search path. To + * correctly apply the alias, we need the alias symlink to + * point to the symlink that was created in the search path. */ + alias_target = path_join(config_path, info->name); + if (!alias_target) + return -ENOMEM; + } + + bool broken; + r = chase(alias_path, lp->root_dir, CHASE_NONEXISTENT, /* ret_path = */ NULL, /* ret_fd = */ NULL); + if (r < 0 && r != -ENOENT) { + RET_GATHER(ret, r); continue; } - broken = q == 0; /* symlink target does not exist? */ + broken = r == 0; /* symlink target does not exist? */ - q = create_symlink(lp, info->path, alias_path, force || broken, changes, n_changes); - r = r < 0 ? r : q; + RET_GATHER(ret, create_symlink(lp, alias_target ?: info->path, alias_path, force || broken, changes, n_changes)); } - return r; + return ret; } static int install_info_symlink_wants( @@ -1995,10 +2055,7 @@ static int install_info_symlink_wants( q = install_name_printf(scope, info, *s, &dst); if (q < 0) { - install_changes_add(changes, n_changes, q, *s, NULL); - if (r >= 0) - r = q; - + RET_GATHER(r, install_changes_add(changes, n_changes, q, *s, NULL)); continue; } @@ -2010,15 +2067,13 @@ static int install_info_symlink_wants( * 'systemctl enable serial-getty@.service' should fail, the user should specify an * instance like in 'systemctl enable serial-getty@ttyS0.service'. */ - if (file_flags & UNIT_FILE_IGNORE_AUXILIARY_FAILURE) + if (FLAGS_SET(file_flags, UNIT_FILE_IGNORE_AUXILIARY_FAILURE)) continue; if (unit_name_is_valid(dst, UNIT_NAME_ANY)) - q = install_changes_add(changes, n_changes, -EIDRM, dst, n); + RET_GATHER(r, install_changes_add(changes, n_changes, -EIDRM, dst, n)); else - q = install_changes_add(changes, n_changes, -EUCLEAN, dst, NULL); - if (r >= 0) - r = q; + RET_GATHER(r, install_changes_add(changes, n_changes, -EUCLEAN, dst, NULL)); continue; } @@ -2027,7 +2082,7 @@ static int install_info_symlink_wants( if (!path) return -ENOMEM; - q = create_symlink(lp, info->path, path, true, changes, n_changes); + q = create_symlink(lp, info->path, path, /* force = */ true, changes, n_changes); if ((q < 0 && r >= 0) || r == 0) r = q; @@ -2259,7 +2314,7 @@ int unit_file_mask( InstallChange **changes, size_t *n_changes) { - _cleanup_(lookup_paths_free) LookupPaths lp = {}; + _cleanup_(lookup_paths_done) LookupPaths lp = {}; const char *config_path; int r; @@ -2274,13 +2329,13 @@ int unit_file_mask( if (!config_path) return -ENXIO; + r = 0; + STRV_FOREACH(name, names) { _cleanup_free_ char *path = NULL; - int q; if (!unit_name_is_valid(*name, UNIT_NAME_ANY)) { - if (r == 0) - r = -EINVAL; + RET_GATHER(r, -EINVAL); continue; } @@ -2288,9 +2343,7 @@ int unit_file_mask( if (!path) return -ENOMEM; - q = create_symlink(&lp, "/dev/null", path, flags & UNIT_FILE_FORCE, changes, n_changes); - if (q < 0 && r >= 0) - r = q; + RET_GATHER(r, create_symlink(&lp, "/dev/null", path, flags & UNIT_FILE_FORCE, changes, n_changes)); } return r; @@ -2304,7 +2357,7 @@ int unit_file_unmask( InstallChange **changes, size_t *n_changes) { - _cleanup_(lookup_paths_free) LookupPaths lp = {}; + _cleanup_(lookup_paths_done) LookupPaths lp = {}; _cleanup_set_free_free_ Set *remove_symlinks_to = NULL; _cleanup_strv_free_ char **todo = NULL; const char *config_path; @@ -2386,8 +2439,7 @@ int unit_file_unmask( if (!dry_run && unlink(path) < 0) { if (errno != ENOENT) { - if (r >= 0) - r = -errno; + RET_GATHER(r, -errno); install_changes_add(changes, n_changes, -errno, path, NULL); } @@ -2404,9 +2456,7 @@ int unit_file_unmask( return q; } - q = remove_marked_symlinks(remove_symlinks_to, config_path, &lp, dry_run, changes, n_changes); - if (r >= 0) - r = q; + RET_GATHER(r, remove_marked_symlinks(remove_symlinks_to, config_path, &lp, dry_run, changes, n_changes)); return r; } @@ -2419,11 +2469,11 @@ int unit_file_link( InstallChange **changes, size_t *n_changes) { - _cleanup_(lookup_paths_free) LookupPaths lp = {}; + _cleanup_(lookup_paths_done) LookupPaths lp = {}; _cleanup_strv_free_ char **todo = NULL; const char *config_path; size_t n_todo = 0; - int r, q; + int r; assert(scope >= 0); assert(scope < _RUNTIME_SCOPE_MAX); @@ -2492,9 +2542,7 @@ int unit_file_link( if (!new_path) return -ENOMEM; - q = create_symlink(&lp, *i, new_path, flags & UNIT_FILE_FORCE, changes, n_changes); - if (q < 0 && r >= 0) - r = q; + RET_GATHER(r, create_symlink(&lp, *i, new_path, flags & UNIT_FILE_FORCE, changes, n_changes)); } return r; @@ -2527,7 +2575,7 @@ int unit_file_revert( size_t *n_changes) { _cleanup_set_free_free_ Set *remove_symlinks_to = NULL; - _cleanup_(lookup_paths_free) LookupPaths lp = {}; + _cleanup_(lookup_paths_done) LookupPaths lp = {}; _cleanup_strv_free_ char **todo = NULL; size_t n_todo = 0; int r, q; @@ -2685,7 +2733,7 @@ int unit_file_add_dependency( InstallChange **changes, size_t *n_changes) { - _cleanup_(lookup_paths_free) LookupPaths lp = {}; + _cleanup_(lookup_paths_done) LookupPaths lp = {}; _cleanup_(install_context_done) InstallContext ctx = { .scope = scope }; InstallInfo *info, *target_info; const char *config_path; @@ -2786,7 +2834,7 @@ int unit_file_enable( InstallChange **changes, size_t *n_changes) { - _cleanup_(lookup_paths_free) LookupPaths lp = {}; + _cleanup_(lookup_paths_done) LookupPaths lp = {}; int r; assert(scope >= 0); @@ -2814,11 +2862,12 @@ static int do_unit_file_disable( _cleanup_(install_context_done) InstallContext ctx = { .scope = scope }; _cleanup_set_free_free_ Set *remove_symlinks_to = NULL; - InstallInfo *info; bool has_install_info = false; int r; STRV_FOREACH(name, names) { + InstallInfo *info; + if (!unit_name_is_valid(*name, UNIT_NAME_ANY)) return install_changes_add(changes, n_changes, -EUCLEAN, *name, NULL); @@ -2838,7 +2887,6 @@ static int do_unit_file_disable( r = install_context_mark_for_removal(&ctx, lp, &remove_symlinks_to, config_path, changes, n_changes); if (r >= 0) r = remove_marked_symlinks(remove_symlinks_to, config_path, lp, flags & UNIT_FILE_DRY_RUN, changes, n_changes); - if (r < 0) return r; @@ -2854,7 +2902,7 @@ int unit_file_disable( InstallChange **changes, size_t *n_changes) { - _cleanup_(lookup_paths_free) LookupPaths lp = {}; + _cleanup_(lookup_paths_done) LookupPaths lp = {}; int r; assert(scope >= 0); @@ -2897,7 +2945,7 @@ static int normalize_linked_files( return log_debug_errno(SYNTHETIC_ERRNO(EISDIR), "Unexpected path to a directory \"%s\", refusing.", *a); - if (!is_path(*a)) { + if (!is_path(*a) && !unit_name_is_valid(*a, UNIT_NAME_INSTANCE)) { r = install_info_discover(&ctx, lp, n, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS, &i, NULL, NULL); if (r < 0) log_debug_errno(r, "Failed to discover unit \"%s\", operating on name: %m", n); @@ -2937,7 +2985,7 @@ int unit_file_reenable( InstallChange **changes, size_t *n_changes) { - _cleanup_(lookup_paths_free) LookupPaths lp = {}; + _cleanup_(lookup_paths_done) LookupPaths lp = {}; _cleanup_strv_free_ char **names = NULL, **files = NULL; int r; @@ -2973,7 +3021,7 @@ int unit_file_set_default( InstallChange **changes, size_t *n_changes) { - _cleanup_(lookup_paths_free) LookupPaths lp = {}; + _cleanup_(lookup_paths_done) LookupPaths lp = {}; _cleanup_(install_context_done) InstallContext ctx = { .scope = scope }; InstallInfo *info; const char *new_path; @@ -3003,17 +3051,16 @@ int unit_file_set_default( int unit_file_get_default( RuntimeScope scope, const char *root_dir, - char **name) { + char **ret) { - _cleanup_(lookup_paths_free) LookupPaths lp = {}; + _cleanup_(lookup_paths_done) LookupPaths lp = {}; _cleanup_(install_context_done) InstallContext ctx = { .scope = scope }; InstallInfo *info; - char *n; int r; assert(scope >= 0); assert(scope < _RUNTIME_SCOPE_MAX); - assert(name); + assert(ret); r = lookup_paths_init(&lp, scope, 0, root_dir); if (r < 0) @@ -3024,12 +3071,7 @@ int unit_file_get_default( if (r < 0) return r; - n = strdup(info->name); - if (!n) - return -ENOMEM; - - *name = n; - return 0; + return strdup_to(ret, info->name); } int unit_file_lookup_state( @@ -3136,7 +3178,7 @@ int unit_file_get_state( const char *name, UnitFileState *ret) { - _cleanup_(lookup_paths_free) LookupPaths lp = {}; + _cleanup_(lookup_paths_done) LookupPaths lp = {}; int r; assert(scope >= 0); @@ -3150,8 +3192,10 @@ int unit_file_get_state( return unit_file_lookup_state(scope, &lp, name, ret); } -int unit_file_exists(RuntimeScope scope, const LookupPaths *lp, const char *name) { - _cleanup_(install_context_done) InstallContext c = { .scope = scope }; +int unit_file_exists_full(RuntimeScope scope, const LookupPaths *lp, const char *name, char **ret_path) { + _cleanup_(install_context_done) InstallContext c = { + .scope = scope, + }; int r; assert(lp); @@ -3160,12 +3204,31 @@ int unit_file_exists(RuntimeScope scope, const LookupPaths *lp, const char *name if (!unit_name_is_valid(name, UNIT_NAME_ANY)) return -EINVAL; - r = install_info_discover(&c, lp, name, 0, NULL, NULL, NULL); - if (r == -ENOENT) + InstallInfo *info = NULL; + r = install_info_discover( + &c, + lp, + name, + /* flags= */ 0, + ret_path ? &info : NULL, + /* changes= */ NULL, + /* n_changes= */ NULL); + if (r == -ENOENT) { + if (ret_path) + *ret_path = NULL; return 0; + } if (r < 0) return r; + if (ret_path) { + assert(info); + + r = strdup_to(ret_path, info->path); + if (r < 0) + return r; + } + return 1; } @@ -3203,8 +3266,8 @@ static int split_pattern_into_name_and_instances(const char *pattern, char **out } static int presets_find_config(RuntimeScope scope, const char *root_dir, char ***files) { - static const char* const system_dirs[] = {CONF_PATHS("systemd/system-preset"), NULL}; - static const char* const user_dirs[] = {CONF_PATHS_USR("systemd/user-preset"), NULL}; + static const char* const system_dirs[] = { CONF_PATHS("systemd/system-preset"), NULL }; + static const char* const user_dirs[] = { CONF_PATHS("systemd/user-preset"), NULL }; const char* const* dirs; assert(scope >= 0); @@ -3540,7 +3603,7 @@ int unit_file_preset( size_t *n_changes) { _cleanup_(install_context_done) InstallContext plus = {}, minus = {}; - _cleanup_(lookup_paths_free) LookupPaths lp = {}; + _cleanup_(lookup_paths_done) LookupPaths lp = {}; _cleanup_(unit_file_presets_done) UnitFilePresets presets = {}; const char *config_path; int r; @@ -3579,7 +3642,7 @@ int unit_file_preset_all( size_t *n_changes) { _cleanup_(install_context_done) InstallContext plus = {}, minus = {}; - _cleanup_(lookup_paths_free) LookupPaths lp = {}; + _cleanup_(lookup_paths_done) LookupPaths lp = {}; _cleanup_(unit_file_presets_done) UnitFilePresets presets = {}; const char *config_path = NULL; int r; @@ -3600,18 +3663,19 @@ int unit_file_preset_all( if (r < 0) return r; + r = 0; STRV_FOREACH(i, lp.search_path) { _cleanup_closedir_ DIR *d = NULL; d = opendir(*i); if (!d) { - if (errno == ENOENT) - continue; - - return -errno; + if (errno != ENOENT) + RET_GATHER(r, -errno); + continue; } - FOREACH_DIRENT(de, d, return -errno) { + FOREACH_DIRENT(de, d, RET_GATHER(r, -errno)) { + int k; if (!unit_name_is_valid(de->d_name, UNIT_NAME_ANY)) continue; @@ -3619,12 +3683,23 @@ int unit_file_preset_all( if (!IN_SET(de->d_type, DT_LNK, DT_REG)) continue; - r = preset_prepare_one(scope, &plus, &minus, &lp, de->d_name, &presets, changes, n_changes); - if (r < 0 && - !IN_SET(r, -EEXIST, -ERFKILL, -EADDRNOTAVAIL, -EBADSLT, -EIDRM, -EUCLEAN, -ELOOP, -ENOENT, -EUNATCH, -EXDEV)) + k = preset_prepare_one(scope, &plus, &minus, &lp, de->d_name, &presets, changes, n_changes); + if (k < 0 && + !IN_SET(k, -EEXIST, + -ERFKILL, + -EADDRNOTAVAIL, + -ETXTBSY, + -EBADSLT, + -EIDRM, + -EUCLEAN, + -ELOOP, + -EXDEV, + -ENOENT, + -ENOLINK, + -EUNATCH)) /* Ignore generated/transient/missing/invalid units when applying preset, propagate other errors. - * Coordinate with install_changes_dump() above. */ - return r; + * Coordinate with install_change_dump_error() above. */ + RET_GATHER(r, k); } } @@ -3656,7 +3731,7 @@ int unit_file_get_list( char **states, char **patterns) { - _cleanup_(lookup_paths_free) LookupPaths lp = {}; + _cleanup_(lookup_paths_done) LookupPaths lp = {}; int r; assert(scope >= 0); diff --git a/src/shared/install.h b/src/shared/install.h index bc0c6db..13d1630 100644 --- a/src/shared/install.h +++ b/src/shared/install.h @@ -63,9 +63,9 @@ struct InstallChange { char *source; }; -static inline bool install_changes_have_modification(const InstallChange* changes, size_t n_changes) { - for (size_t i = 0; i < n_changes; i++) - if (IN_SET(changes[i].type, INSTALL_CHANGE_SYMLINK, INSTALL_CHANGE_UNLINK)) +static inline bool install_changes_have_modification(const InstallChange *changes, size_t n_changes) { + FOREACH_ARRAY(i, changes, n_changes) + if (IN_SET(i->type, INSTALL_CHANGE_SYMLINK, INSTALL_CHANGE_UNLINK)) return true; return false; } @@ -175,7 +175,7 @@ int unit_file_set_default( int unit_file_get_default( RuntimeScope scope, const char *root_dir, - char **name); + char **ret); int unit_file_add_dependency( RuntimeScope scope, UnitFileFlags flags, @@ -193,7 +193,11 @@ int unit_file_lookup_state( UnitFileState *ret); int unit_file_get_state(RuntimeScope scope, const char *root_dir, const char *filename, UnitFileState *ret); -int unit_file_exists(RuntimeScope scope, const LookupPaths *paths, const char *name); + +int unit_file_exists_full(RuntimeScope scope, const LookupPaths *paths, const char *name, char **ret_path); +static inline int unit_file_exists(RuntimeScope scope, const LookupPaths *paths, const char *name) { + return unit_file_exists_full(scope, paths, name, NULL); +} int unit_file_get_list(RuntimeScope scope, const char *root_dir, Hashmap *h, char **states, char **patterns); @@ -201,7 +205,14 @@ extern const struct hash_ops unit_file_list_hash_ops_free; InstallChangeType install_changes_add(InstallChange **changes, size_t *n_changes, InstallChangeType type, const char *path, const char *source); void install_changes_free(InstallChange *changes, size_t n_changes); -void install_changes_dump(int r, const char *verb, const InstallChange *changes, size_t n_changes, bool quiet); + +int install_change_dump_error(const InstallChange *change, char **ret_errmsg, const char **ret_bus_error); +void install_changes_dump( + int error, + const char *verb, + const InstallChange *changes, + size_t n_changes, + bool quiet); int unit_file_verify_alias( const InstallInfo *info, diff --git a/src/shared/journal-file-util.c b/src/shared/journal-file-util.c index bdceac4..8df165d 100644 --- a/src/shared/journal-file-util.c +++ b/src/shared/journal-file-util.c @@ -404,7 +404,7 @@ JournalFile* journal_file_offline_close(JournalFile *f) { if (sd_event_source_get_enabled(f->post_change_timer, NULL) > 0) journal_file_post_change(f); - sd_event_source_disable_unref(f->post_change_timer); + f->post_change_timer = sd_event_source_disable_unref(f->post_change_timer); journal_file_set_offline(f, true); @@ -451,7 +451,7 @@ int journal_file_rotate( set_clear_with_destructor(deferred_closes, journal_file_offline_close); r = journal_file_open( - /* fd= */ -1, + /* fd= */ -EBADF, path, (*f)->open_flags, file_flags, @@ -476,14 +476,13 @@ int journal_file_open_reliably( uint64_t compress_threshold_bytes, JournalMetrics *metrics, MMapCache *mmap_cache, - JournalFile *template, JournalFile **ret) { _cleanup_(journal_file_offline_closep) JournalFile *old_file = NULL; int r; r = journal_file_open( - /* fd= */ -1, + /* fd= */ -EBADF, fname, open_flags, file_flags, @@ -491,7 +490,7 @@ int journal_file_open_reliably( compress_threshold_bytes, metrics, mmap_cache, - template, + /* template = */ NULL, ret); if (!IN_SET(r, -EBADMSG, /* Corrupted */ @@ -517,23 +516,19 @@ int journal_file_open_reliably( /* The file is corrupted. Rotate it away and try it again (but only once) */ log_warning_errno(r, "File %s corrupted or uncleanly shut down, renaming and replacing.", fname); - if (!template) { - /* The file is corrupted and no template is specified. Try opening it read-only as the - * template before rotating to inherit its sequence number and ID. */ - r = journal_file_open(-1, fname, - (open_flags & ~(O_ACCMODE|O_CREAT|O_EXCL)) | O_RDONLY, - file_flags, 0, compress_threshold_bytes, NULL, - mmap_cache, NULL, &old_file); - if (r < 0) - log_debug_errno(r, "Failed to continue sequence from file %s, ignoring: %m", fname); - else - template = old_file; - } + /* The file is corrupted. Try opening it read-only as the template before rotating to inherit its + * sequence number and ID. */ + r = journal_file_open(-EBADF, fname, + (open_flags & ~(O_ACCMODE|O_CREAT|O_EXCL)) | O_RDONLY, + file_flags, 0, compress_threshold_bytes, NULL, + mmap_cache, /* template = */ NULL, &old_file); + if (r < 0) + log_debug_errno(r, "Failed to continue sequence from file %s, ignoring: %m", fname); r = journal_file_dispose(AT_FDCWD, fname); if (r < 0) return r; - return journal_file_open(-1, fname, open_flags, file_flags, mode, compress_threshold_bytes, metrics, - mmap_cache, template, ret); + return journal_file_open(-EBADF, fname, open_flags, file_flags, mode, compress_threshold_bytes, metrics, + mmap_cache, /* template = */ old_file, ret); } diff --git a/src/shared/journal-file-util.h b/src/shared/journal-file-util.h index f9426c4..8df10a7 100644 --- a/src/shared/journal-file-util.h +++ b/src/shared/journal-file-util.h @@ -17,7 +17,6 @@ int journal_file_open_reliably( uint64_t compress_threshold_bytes, JournalMetrics *metrics, MMapCache *mmap_cache, - JournalFile *template, JournalFile **ret); JournalFile* journal_file_initiate_close(JournalFile *f, Set *deferred_closes); diff --git a/src/shared/journal-importer.c b/src/shared/journal-importer.c index 83e9834..bb0536e 100644 --- a/src/shared/journal-importer.c +++ b/src/shared/journal-importer.c @@ -93,7 +93,12 @@ static int get_line(JournalImporter *imp, char **line, size_t *size) { imp->buf + imp->filled, MALLOC_SIZEOF_SAFE(imp->buf) - imp->filled); if (n < 0) { - if (errno != EAGAIN) + if (ERRNO_IS_DISCONNECT(errno)) { + log_debug_errno(errno, "Got disconnect for importer %s.", strna(imp->name)); + return 0; + } + + if (!ERRNO_IS_TRANSIENT(errno)) log_error_errno(errno, "read(%d, ..., %zu): %m", imp->fd, MALLOC_SIZEOF_SAFE(imp->buf) - imp->filled); @@ -134,7 +139,12 @@ static int fill_fixed_size(JournalImporter *imp, void **data, size_t size) { n = read(imp->fd, imp->buf + imp->filled, MALLOC_SIZEOF_SAFE(imp->buf) - imp->filled); if (n < 0) { - if (errno != EAGAIN) + if (ERRNO_IS_DISCONNECT(errno)) { + log_debug_errno(errno, "Got disconnect for importer %s.", strna(imp->name)); + return 0; + } + + if (!ERRNO_IS_TRANSIENT(errno)) log_error_errno(errno, "read(%d, ..., %zu): %m", imp->fd, MALLOC_SIZEOF_SAFE(imp->buf) - imp->filled); return -errno; diff --git a/src/shared/journal-util.c b/src/shared/journal-util.c index d73d7c4..ab70f4d 100644 --- a/src/shared/journal-util.c +++ b/src/shared/journal-util.c @@ -145,7 +145,7 @@ int journal_access_check_and_warn(sd_journal *j, bool quiet, bool want_other_use return r; } -int journal_open_machine(sd_journal **ret, const char *machine) { +int journal_open_machine(sd_journal **ret, const char *machine, int flags) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; @@ -178,7 +178,7 @@ int journal_open_machine(sd_journal **ret, const char *machine) { if (machine_fd < 0) return log_error_errno(errno, "Failed to duplicate file descriptor: %m"); - r = sd_journal_open_directory_fd(&j, machine_fd, SD_JOURNAL_OS_ROOT | SD_JOURNAL_TAKE_DIRECTORY_FD); + r = sd_journal_open_directory_fd(&j, machine_fd, SD_JOURNAL_OS_ROOT | SD_JOURNAL_TAKE_DIRECTORY_FD | flags); if (r < 0) return log_error_errno(r, "Failed to open journal in machine '%s': %m", machine); diff --git a/src/shared/journal-util.h b/src/shared/journal-util.h index afad249..5bd8e34 100644 --- a/src/shared/journal-util.h +++ b/src/shared/journal-util.h @@ -8,4 +8,4 @@ int journal_access_blocked(sd_journal *j); int journal_access_check_and_warn(sd_journal *j, bool quiet, bool want_other_users); -int journal_open_machine(sd_journal **ret, const char *machine); +int journal_open_machine(sd_journal **ret, const char *machine, int flags); diff --git a/src/shared/json.c b/src/shared/json.c index 06c9e85..4af34e5 100644 --- a/src/shared/json.c +++ b/src/shared/json.c @@ -14,13 +14,16 @@ #include "fd-util.h" #include "fileio.h" #include "float.h" +#include "glyph-util.h" #include "hexdecoct.h" +#include "iovec-util.h" #include "json-internal.h" #include "json.h" #include "macro.h" #include "math-util.h" #include "memory-util.h" #include "memstream-util.h" +#include "path-util.h" #include "set.h" #include "string-table.h" #include "string-util.h" @@ -87,6 +90,9 @@ struct JsonVariant { /* Erase from memory when freeing */ bool sensitive:1; + /* True if we know that any referenced json object is marked sensitive */ + bool recursive_sensitive:1; + /* If this is an object the fields are strictly ordered by name */ bool sorted:1; @@ -559,7 +565,7 @@ static int _json_variant_array_put_element(JsonVariant *array, JsonVariant *elem return -ELNRNG; if (d >= array->depth) array->depth = d + 1; - array->n_elements ++; + array->n_elements++; *w = (JsonVariant) { .is_embedded = true, @@ -1451,6 +1457,33 @@ bool json_variant_is_sensitive(JsonVariant *v) { return v->sensitive; } +bool json_variant_is_sensitive_recursive(JsonVariant *v) { + if (!v) + return false; + if (json_variant_is_sensitive(v)) + return true; + if (!json_variant_is_regular(v)) + return false; + if (v->recursive_sensitive) /* Already checked this before */ + return true; + if (!IN_SET(v->type, JSON_VARIANT_ARRAY, JSON_VARIANT_OBJECT)) + return false; + if (v->is_reference) { + if (!json_variant_is_sensitive_recursive(v->reference)) + return false; + + return (v->recursive_sensitive = true); + } + + for (size_t i = 0; i < json_variant_elements(v); i++) + if (json_variant_is_sensitive_recursive(json_variant_by_index(v, i))) + return (v->recursive_sensitive = true); + + /* Note: we only cache the result here in case true, since we allow all elements down the tree to + * have their sensitive flag toggled later on (but never off) */ + return false; +} + static void json_variant_propagate_sensitive(JsonVariant *from, JsonVariant *to) { if (json_variant_is_sensitive(from)) json_variant_sensitive(to); @@ -1580,6 +1613,15 @@ static int json_format(FILE *f, JsonVariant *v, JsonFormatFlags flags, const cha assert(f); assert(v); + if (FLAGS_SET(flags, JSON_FORMAT_CENSOR_SENSITIVE) && json_variant_is_sensitive(v)) { + if (flags & JSON_FORMAT_COLOR) + fputs(ansi_red(), f); + fputs("\"<sensitive data>\"", f); + if (flags & JSON_FORMAT_COLOR) + fputs(ANSI_NORMAL, f); + return 0; + } + switch (json_variant_type(v)) { case JSON_VARIANT_REAL: { @@ -2310,7 +2352,7 @@ static int json_variant_copy(JsonVariant **nv, JsonVariant *v) { source = json_variant_string(v); k = strnlen(source, INLINE_STRING_MAX + 1); if (k <= INLINE_STRING_MAX) { - k ++; + k++; break; } @@ -2594,7 +2636,7 @@ static int json_parse_string(const char **p, char **ret) { return -ENOMEM; s[n++] = ch; - c ++; + c++; continue; } @@ -3790,7 +3832,8 @@ int json_buildv(JsonVariant **ret, va_list ap) { break; } - case _JSON_BUILD_IOVEC_BASE64: { + case _JSON_BUILD_IOVEC_BASE64: + case _JSON_BUILD_IOVEC_HEX: { const struct iovec *iov; if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) { @@ -3798,10 +3841,14 @@ int json_buildv(JsonVariant **ret, va_list ap) { goto finish; } - iov = ASSERT_PTR(va_arg(ap, const struct iovec*)); + iov = va_arg(ap, const struct iovec*); if (current->n_suppress == 0) { - r = json_variant_new_base64(&add, iov->iov_base, iov->iov_len); + if (iov) + r = command == _JSON_BUILD_IOVEC_BASE64 ? json_variant_new_base64(&add, iov->iov_base, iov->iov_len) : + json_variant_new_hex(&add, iov->iov_base, iov->iov_len); + else + r = json_variant_new_string(&add, ""); if (r < 0) goto finish; } @@ -4574,7 +4621,7 @@ int json_dispatch_full( } } - done ++; + done++; } else { /* Didn't find a matching entry! ☹️ */ @@ -4589,11 +4636,15 @@ int json_dispatch_full( return r; } else - done ++; + done++; } else { - json_log(value, flags, 0, "Unexpected object field '%s'.", json_variant_string(key)); + if (flags & JSON_ALLOW_EXTENSIONS) { + json_log(value, flags|JSON_DEBUG, 0, "Unrecognized object field '%s', assuming extension.", json_variant_string(key)); + continue; + } + json_log(value, flags, 0, "Unexpected object field '%s'.", json_variant_string(key)); if (flags & JSON_PERMISSIVE) continue; @@ -4762,6 +4813,42 @@ int json_dispatch_uint16(const char *name, JsonVariant *variant, JsonDispatchFla return 0; } +int json_dispatch_int8(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { + int8_t *i = ASSERT_PTR(userdata); + int64_t i64; + int r; + + assert(variant); + + r = json_dispatch_int64(name, variant, flags, &i64); + if (r < 0) + return r; + + if (i64 < INT8_MIN || i64 > INT8_MAX) + return json_log(variant, flags, SYNTHETIC_ERRNO(ERANGE), "JSON field '%s' out of bounds.", strna(name)); + + *i = (int8_t) i64; + return 0; +} + +int json_dispatch_uint8(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { + uint8_t *u = ASSERT_PTR(userdata); + uint64_t u64; + int r; + + assert(variant); + + r = json_dispatch_uint64(name, variant, flags, &u64); + if (r < 0) + return r; + + if (u64 > UINT8_MAX) + return json_log(variant, flags, SYNTHETIC_ERRNO(ERANGE), "JSON field '%s' out of bounds.", strna(name)); + + *u = (uint8_t) u64; + return 0; +} + int json_dispatch_string(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { char **s = ASSERT_PTR(userdata); int r; @@ -4920,6 +5007,27 @@ int json_dispatch_user_group_name(const char *name, JsonVariant *variant, JsonDi return 0; } +int json_dispatch_absolute_path(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { + const char *path; + char **p = ASSERT_PTR(userdata); + + assert(variant); + + if (!json_variant_is_string(variant)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name)); + + path = json_variant_string(variant); + if (!path_is_valid(path)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid path.", strna(name)); + if (!path_is_absolute(path)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' must be an absolute path.", strna(name)); + + if (free_and_strdup(p, path) < 0) + return json_log_oom(variant, flags); + + return 0; +} + int json_dispatch_id128(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { sd_id128_t *uuid = userdata; int r; @@ -4961,6 +5069,63 @@ int json_dispatch_unbase64_iovec(const char *name, JsonVariant *variant, JsonDis return 0; } +int json_dispatch_byte_array_iovec(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { + _cleanup_free_ uint8_t *buffer = NULL; + struct iovec *iov = ASSERT_PTR(userdata); + size_t sz, k = 0; + + assert(variant); + + if (!json_variant_is_array(variant)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array.", strna(name)); + + sz = json_variant_elements(variant); + + buffer = new(uint8_t, sz + 1); + if (!buffer) + return json_log(variant, flags, SYNTHETIC_ERRNO(ENOMEM), "Out of memory."); + + JsonVariant *i; + JSON_VARIANT_ARRAY_FOREACH(i, variant) { + uint64_t b; + + if (!json_variant_is_unsigned(i)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "Element %zu of JSON field '%s' is not an unsigned integer.", k, strna(name)); + + b = json_variant_unsigned(i); + if (b > 0xff) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), + "Element %zu of JSON field '%s' is out of range 0%s255.", + k, strna(name), special_glyph(SPECIAL_GLYPH_ELLIPSIS)); + + buffer[k++] = (uint8_t) b; + } + assert(k == sz); + + /* Append a NUL byte for safety, like we do in memdup_suffix0() and others. */ + buffer[sz] = 0; + + free_and_replace(iov->iov_base, buffer); + iov->iov_len = sz; + return 0; +} + +int json_dispatch_in_addr(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { + struct in_addr *address = ASSERT_PTR(userdata); + _cleanup_(iovec_done) struct iovec iov = {}; + int r; + + r = json_dispatch_byte_array_iovec(name, variant, flags, &iov); + if (r < 0) + return r; + + if (iov.iov_len != sizeof(struct in_addr)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is array of unexpected size.", strna(name)); + + memcpy(address, iov.iov_base, iov.iov_len); + return 0; +} + static int json_cmp_strings(const void *x, const void *y) { JsonVariant *const *a = x, *const *b = y; @@ -5107,14 +5272,14 @@ int json_variant_unbase64(JsonVariant *v, void **ret, size_t *ret_size) { if (!json_variant_is_string(v)) return -EINVAL; - return unbase64mem(json_variant_string(v), SIZE_MAX, ret, ret_size); + return unbase64mem_full(json_variant_string(v), SIZE_MAX, /* secure= */ json_variant_is_sensitive(v), ret, ret_size); } int json_variant_unhex(JsonVariant *v, void **ret, size_t *ret_size) { if (!json_variant_is_string(v)) return -EINVAL; - return unhexmem(json_variant_string(v), SIZE_MAX, ret, ret_size); + return unhexmem_full(json_variant_string(v), SIZE_MAX, /* secure= */ json_variant_is_sensitive(v), ret, ret_size); } static const char* const json_variant_type_table[_JSON_VARIANT_TYPE_MAX] = { diff --git a/src/shared/json.h b/src/shared/json.h index c40c234..6a93530 100644 --- a/src/shared/json.h +++ b/src/shared/json.h @@ -155,6 +155,7 @@ bool json_variant_equal(JsonVariant *a, JsonVariant *b); void json_variant_sensitive(JsonVariant *v); bool json_variant_is_sensitive(JsonVariant *v); +bool json_variant_is_sensitive_recursive(JsonVariant *v); struct json_variant_foreach_state { JsonVariant *variant; @@ -185,17 +186,18 @@ struct json_variant_foreach_state { int json_variant_get_source(JsonVariant *v, const char **ret_source, unsigned *ret_line, unsigned *ret_column); typedef enum JsonFormatFlags { - JSON_FORMAT_NEWLINE = 1 << 0, /* suffix with newline */ - JSON_FORMAT_PRETTY = 1 << 1, /* add internal whitespace to appeal to human readers */ - JSON_FORMAT_PRETTY_AUTO = 1 << 2, /* same, but only if connected to a tty (and JSON_FORMAT_NEWLINE otherwise) */ - JSON_FORMAT_COLOR = 1 << 3, /* insert ANSI color sequences */ - JSON_FORMAT_COLOR_AUTO = 1 << 4, /* insert ANSI color sequences if colors_enabled() says so */ - JSON_FORMAT_SOURCE = 1 << 5, /* prefix with source filename/line/column */ - JSON_FORMAT_SSE = 1 << 6, /* prefix/suffix with W3C server-sent events */ - JSON_FORMAT_SEQ = 1 << 7, /* prefix/suffix with RFC 7464 application/json-seq */ - JSON_FORMAT_FLUSH = 1 << 8, /* call fflush() after dumping JSON */ - JSON_FORMAT_EMPTY_ARRAY = 1 << 9, /* output "[]" for empty input */ - JSON_FORMAT_OFF = 1 << 10, /* make json_variant_format() fail with -ENOEXEC */ + JSON_FORMAT_NEWLINE = 1 << 0, /* suffix with newline */ + JSON_FORMAT_PRETTY = 1 << 1, /* add internal whitespace to appeal to human readers */ + JSON_FORMAT_PRETTY_AUTO = 1 << 2, /* same, but only if connected to a tty (and JSON_FORMAT_NEWLINE otherwise) */ + JSON_FORMAT_COLOR = 1 << 3, /* insert ANSI color sequences */ + JSON_FORMAT_COLOR_AUTO = 1 << 4, /* insert ANSI color sequences if colors_enabled() says so */ + JSON_FORMAT_SOURCE = 1 << 5, /* prefix with source filename/line/column */ + JSON_FORMAT_SSE = 1 << 6, /* prefix/suffix with W3C server-sent events */ + JSON_FORMAT_SEQ = 1 << 7, /* prefix/suffix with RFC 7464 application/json-seq */ + JSON_FORMAT_FLUSH = 1 << 8, /* call fflush() after dumping JSON */ + JSON_FORMAT_EMPTY_ARRAY = 1 << 9, /* output "[]" for empty input */ + JSON_FORMAT_OFF = 1 << 10, /* make json_variant_format() fail with -ENOEXEC */ + JSON_FORMAT_CENSOR_SENSITIVE = 1 << 11, /* Replace all sensitive elements with the string "<sensitive data>" */ } JsonFormatFlags; int json_variant_format(JsonVariant *v, JsonFormatFlags flags, char **ret); @@ -271,6 +273,7 @@ enum { _JSON_BUILD_IOVEC_BASE64, _JSON_BUILD_BASE32HEX, _JSON_BUILD_HEX, + _JSON_BUILD_IOVEC_HEX, _JSON_BUILD_OCTESCAPE, _JSON_BUILD_ID128, _JSON_BUILD_UUID, @@ -315,6 +318,7 @@ typedef int (*JsonBuildCallback)(JsonVariant **ret, const char *name, void *user #define JSON_BUILD_IOVEC_BASE64(iov) _JSON_BUILD_IOVEC_BASE64, (const struct iovec*) { iov } #define JSON_BUILD_BASE32HEX(p, n) _JSON_BUILD_BASE32HEX, (const void*) { p }, (size_t) { n } #define JSON_BUILD_HEX(p, n) _JSON_BUILD_HEX, (const void*) { p }, (size_t) { n } +#define JSON_BUILD_IOVEC_HEX(iov) _JSON_BUILD_IOVEC_HEX, (const struct iovec*) { iov } #define JSON_BUILD_OCTESCAPE(p, n) _JSON_BUILD_OCTESCAPE, (const void*) { p }, (size_t) { n } #define JSON_BUILD_ID128(id) _JSON_BUILD_ID128, (const sd_id128_t*) { &(id) } #define JSON_BUILD_UUID(id) _JSON_BUILD_UUID, (const sd_id128_t*) { &(id) } @@ -345,6 +349,7 @@ typedef int (*JsonBuildCallback)(JsonVariant **ret, const char *name, void *user #define JSON_BUILD_PAIR_BASE64(name, p, n) JSON_BUILD_PAIR(name, JSON_BUILD_BASE64(p, n)) #define JSON_BUILD_PAIR_IOVEC_BASE64(name, iov) JSON_BUILD_PAIR(name, JSON_BUILD_IOVEC_BASE64(iov)) #define JSON_BUILD_PAIR_HEX(name, p, n) JSON_BUILD_PAIR(name, JSON_BUILD_HEX(p, n)) +#define JSON_BUILD_PAIR_IOVEC_HEX(name, iov) JSON_BUILD_PAIR(name, JSON_BUILD_IOVEC_HEX(iov)) #define JSON_BUILD_PAIR_ID128(name, id) JSON_BUILD_PAIR(name, JSON_BUILD_ID128(id)) #define JSON_BUILD_PAIR_UUID(name, id) JSON_BUILD_PAIR(name, JSON_BUILD_UUID(id)) #define JSON_BUILD_PAIR_BYTE_ARRAY(name, v, n) JSON_BUILD_PAIR(name, JSON_BUILD_BYTE_ARRAY(v, n)) @@ -375,15 +380,16 @@ int json_buildv(JsonVariant **ret, va_list ap); * entry, as well the bitmask specified for json_log() calls */ typedef enum JsonDispatchFlags { /* The following three may be set in JsonDispatch's .flags field or the json_dispatch() flags parameter */ - JSON_PERMISSIVE = 1 << 0, /* Shall parsing errors be considered fatal for this property? */ - JSON_MANDATORY = 1 << 1, /* Should existence of this property be mandatory? */ - JSON_LOG = 1 << 2, /* Should the parser log about errors? */ - JSON_SAFE = 1 << 3, /* Don't accept "unsafe" strings in json_dispatch_string() + json_dispatch_string() */ - JSON_RELAX = 1 << 4, /* Use relaxed user name checking in json_dispatch_user_group_name */ + JSON_PERMISSIVE = 1 << 0, /* Shall parsing errors be considered fatal for this field or object? */ + JSON_MANDATORY = 1 << 1, /* Should existence of this property be mandatory? */ + JSON_LOG = 1 << 2, /* Should the parser log about errors? */ + JSON_SAFE = 1 << 3, /* Don't accept "unsafe" strings in json_dispatch_string() + json_dispatch_string() */ + JSON_RELAX = 1 << 4, /* Use relaxed user name checking in json_dispatch_user_group_name */ + JSON_ALLOW_EXTENSIONS = 1 << 5, /* Subset of JSON_PERMISSIVE: allow additional fields, but no other permissive handling */ /* The following two may be passed into log_json() in addition to those above */ - JSON_DEBUG = 1 << 5, /* Indicates that this log message is a debug message */ - JSON_WARNING = 1 << 6, /* Indicates that this log message is a warning message */ + JSON_DEBUG = 1 << 6, /* Indicates that this log message is a debug message */ + JSON_WARNING = 1 << 7, /* Indicates that this log message is a warning message */ } JsonDispatchFlags; typedef int (*JsonDispatchCallback)(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); @@ -415,11 +421,16 @@ int json_dispatch_uint32(const char *name, JsonVariant *variant, JsonDispatchFla int json_dispatch_int32(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); int json_dispatch_uint16(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); int json_dispatch_int16(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); +int json_dispatch_int8(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); +int json_dispatch_uint8(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); int json_dispatch_uid_gid(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); int json_dispatch_user_group_name(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); +int json_dispatch_absolute_path(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); int json_dispatch_id128(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); int json_dispatch_unsupported(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); int json_dispatch_unbase64_iovec(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); +int json_dispatch_byte_array_iovec(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); +int json_dispatch_in_addr(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); assert_cc(sizeof(uint32_t) == sizeof(unsigned)); #define json_dispatch_uint json_dispatch_uint32 @@ -427,6 +438,28 @@ assert_cc(sizeof(uint32_t) == sizeof(unsigned)); assert_cc(sizeof(int32_t) == sizeof(int)); #define json_dispatch_int json_dispatch_int32 +#define JSON_DISPATCH_ENUM_DEFINE(name, type, func) \ + int name(const char *n, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { \ + type *c = ASSERT_PTR(userdata); \ + \ + assert(variant); \ + \ + if (json_variant_is_null(variant)) { \ + *c = (type) -EINVAL; \ + return 0; \ + } \ + \ + if (!json_variant_is_string(variant)) \ + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(n)); \ + \ + type cc = func(json_variant_string(variant)); \ + if (cc < 0) \ + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "Value of JSON field '%s' not recognized.", strna(n)); \ + \ + *c = cc; \ + return 0; \ + } + static inline int json_dispatch_level(JsonDispatchFlags flags) { /* Did the user request no logging? If so, then never log higher than LOG_DEBUG. Also, if this is marked as @@ -470,5 +503,13 @@ int json_log_internal(JsonVariant *variant, int level, int error, const char *fi int json_variant_unbase64(JsonVariant *v, void **ret, size_t *ret_size); int json_variant_unhex(JsonVariant *v, void **ret, size_t *ret_size); +static inline int json_variant_unbase64_iovec(JsonVariant *v, struct iovec *ret) { + return json_variant_unbase64(v, ret ? &ret->iov_base : NULL, ret ? &ret->iov_len : NULL); +} + +static inline int json_variant_unhex_iovec(JsonVariant *v, struct iovec *ret) { + return json_variant_unhex(v, ret ? &ret->iov_base : NULL, ret ? &ret->iov_len : NULL); +} + const char *json_variant_type_to_string(JsonVariantType t); JsonVariantType json_variant_type_from_string(const char *s); diff --git a/src/shared/kbd-util.c b/src/shared/kbd-util.c index 2f2d161..60e0429 100644 --- a/src/shared/kbd-util.c +++ b/src/shared/kbd-util.c @@ -1,9 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "env-util.h" #include "errno-util.h" #include "kbd-util.h" #include "log.h" -#include "nulstr-util.h" #include "path-util.h" #include "recurse-dir.h" #include "set.h" @@ -11,6 +11,25 @@ #include "strv.h" #include "utf8.h" +#define KBD_KEYMAP_DIRS \ + "/usr/share/keymaps/", \ + "/usr/share/kbd/keymaps/", \ + "/usr/lib/kbd/keymaps/" + +int keymap_directories(char ***ret) { + assert(ret); + + if (getenv_path_list("SYSTEMD_KEYMAP_DIRECTORIES", ret) >= 0) + return 0; + + char **paths = strv_new(KBD_KEYMAP_DIRS); + if (!paths) + return log_oom_debug(); + + *ret = TAKE_PTR(paths); + return 0; +} + struct recurse_dir_userdata { const char *keymap_name; Set *keymaps; @@ -65,16 +84,21 @@ static int keymap_recurse_dir_callback( int get_keymaps(char ***ret) { _cleanup_set_free_free_ Set *keymaps = NULL; + _cleanup_strv_free_ char **keymap_dirs = NULL; int r; + r = keymap_directories(&keymap_dirs); + if (r < 0) + return r; + keymaps = set_new(&string_hash_ops); if (!keymaps) return -ENOMEM; - NULSTR_FOREACH(dir, KBD_KEYMAP_DIRS) { + STRV_FOREACH(dir, keymap_dirs) { r = recurse_dir_at( AT_FDCWD, - dir, + *dir, /* statx_mask= */ 0, /* n_depth_max= */ UINT_MAX, RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_ENSURE_TYPE, @@ -85,9 +109,9 @@ int get_keymaps(char ***ret) { if (r == -ENOENT) continue; if (ERRNO_IS_NEG_RESOURCE(r)) - return log_warning_errno(r, "Failed to read keymap list from %s: %m", dir); + return log_warning_errno(r, "Failed to read keymap list from %s: %m", *dir); if (r < 0) - log_debug_errno(r, "Failed to read keymap list from %s, ignoring: %m", dir); + log_debug_errno(r, "Failed to read keymap list from %s, ignoring: %m", *dir); } _cleanup_strv_free_ char **l = set_get_strv(keymaps); @@ -127,15 +151,20 @@ bool keymap_is_valid(const char *name) { } int keymap_exists(const char *name) { + _cleanup_strv_free_ char **keymap_dirs = NULL; int r; if (!keymap_is_valid(name)) return -EINVAL; - NULSTR_FOREACH(dir, KBD_KEYMAP_DIRS) { + r = keymap_directories(&keymap_dirs); + if (r < 0) + return r; + + STRV_FOREACH(dir, keymap_dirs) { r = recurse_dir_at( AT_FDCWD, - dir, + *dir, /* statx_mask= */ 0, /* n_depth_max= */ UINT_MAX, RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_ENSURE_TYPE, @@ -148,7 +177,7 @@ int keymap_exists(const char *name) { if (ERRNO_IS_NEG_RESOURCE(r)) return r; if (r < 0 && r != -ENOENT) - log_debug_errno(r, "Failed to read keymap list from %s, ignoring: %m", dir); + log_debug_errno(r, "Failed to read keymap list from %s, ignoring: %m", *dir); } return false; diff --git a/src/shared/kbd-util.h b/src/shared/kbd-util.h index aca0dee..a8e365e 100644 --- a/src/shared/kbd-util.h +++ b/src/shared/kbd-util.h @@ -3,11 +3,7 @@ #include <stdbool.h> -#define KBD_KEYMAP_DIRS \ - "/usr/share/keymaps/\0" \ - "/usr/share/kbd/keymaps/\0" \ - "/usr/lib/kbd/keymaps/\0" - -int get_keymaps(char ***l); +int keymap_directories(char ***ret); +int get_keymaps(char ***ret); bool keymap_is_valid(const char *name); int keymap_exists(const char *name); diff --git a/src/shared/kernel-config.c b/src/shared/kernel-config.c new file mode 100644 index 0000000..483ca28 --- /dev/null +++ b/src/shared/kernel-config.c @@ -0,0 +1,72 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <errno.h> + +#include "conf-parser.h" +#include "kernel-config.h" +#include "macro.h" +#include "path-util.h" +#include "strv.h" + +int load_kernel_install_conf( + const char *root, + const char *conf_root, + char **ret_machine_id, + char **ret_boot_root, + char **ret_layout, + char **ret_initrd_generator, + char **ret_uki_generator) { + + _cleanup_free_ char *machine_id = NULL, *boot_root = NULL, *layout = NULL, + *initrd_generator = NULL, *uki_generator = NULL; + const ConfigTableItem items[] = { + { NULL, "MACHINE_ID", config_parse_string, 0, &machine_id }, + { NULL, "BOOT_ROOT", config_parse_string, 0, &boot_root }, + { NULL, "layout", config_parse_string, 0, &layout }, + { NULL, "initrd_generator", config_parse_string, 0, &initrd_generator }, + { NULL, "uki_generator", config_parse_string, 0, &uki_generator }, + {} + }; + int r; + + if (conf_root) { + _cleanup_free_ char *conf = path_join(conf_root, "install.conf"); + if (!conf) + return log_oom(); + + r = config_parse_many( + STRV_MAKE_CONST(conf), + STRV_MAKE_CONST(conf_root), + "install.conf.d", + /* root= */ NULL, /* $KERNEL_INSTALL_CONF_ROOT and --root are independent */ + /* sections= */ NULL, + config_item_table_lookup, items, + CONFIG_PARSE_WARN, + /* userdata= */ NULL, + /* ret_stats_by_path= */ NULL, + /* ret_dropin_files= */ NULL); + } else + r = config_parse_standard_file_with_dropins_full( + root, + "kernel/install.conf", + /* sections= */ NULL, + config_item_table_lookup, items, + CONFIG_PARSE_WARN, + /* userdata= */ NULL, + /* ret_stats_by_path= */ NULL, + /* ret_dropin_files= */ NULL); + if (r < 0 && r != -ENOENT) + return r; + + if (ret_machine_id) + *ret_machine_id = TAKE_PTR(machine_id); + if (ret_boot_root) + *ret_boot_root = TAKE_PTR(boot_root); + if (ret_layout) + *ret_layout = TAKE_PTR(layout); + if (ret_initrd_generator) + *ret_initrd_generator = TAKE_PTR(initrd_generator); + if (ret_uki_generator) + *ret_uki_generator = TAKE_PTR(uki_generator); + return r >= 0; /* Return 0 if we got -ENOENT above, 1 otherwise. */ +} diff --git a/src/shared/kernel-config.h b/src/shared/kernel-config.h new file mode 100644 index 0000000..5681870 --- /dev/null +++ b/src/shared/kernel-config.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +int load_kernel_install_conf( + const char *root, + const char *conf_root, + char **ret_machine_id, + char **ret_boot_root, + char **ret_layout, + char **ret_initrd_generator, + char **ret_uki_generator); diff --git a/src/shared/keyring-util.c b/src/shared/keyring-util.c deleted file mode 100644 index fadd90e..0000000 --- a/src/shared/keyring-util.c +++ /dev/null @@ -1,35 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include "keyring-util.h" -#include "memory-util.h" -#include "missing_syscall.h" - -int keyring_read(key_serial_t serial, void **ret, size_t *ret_size) { - size_t bufsize = 100; - - for (;;) { - _cleanup_(erase_and_freep) uint8_t *buf = NULL; - long n; - - buf = new(uint8_t, bufsize + 1); - if (!buf) - return -ENOMEM; - - n = keyctl(KEYCTL_READ, (unsigned long) serial, (unsigned long) buf, (unsigned long) bufsize, 0); - if (n < 0) - return -errno; - - if ((size_t) n <= bufsize) { - buf[n] = 0; /* NUL terminate, just in case */ - - if (ret) - *ret = TAKE_PTR(buf); - if (ret_size) - *ret_size = n; - - return 0; - } - - bufsize = (size_t) n; - } -} diff --git a/src/shared/keyring-util.h b/src/shared/keyring-util.h deleted file mode 100644 index c8c53f1..0000000 --- a/src/shared/keyring-util.h +++ /dev/null @@ -1,11 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#pragma once - -#include <sys/types.h> - -#include "missing_keyctl.h" - -/* Like TAKE_PTR() but for key_serial_t, resetting them to -1 */ -#define TAKE_KEY_SERIAL(key_serial) TAKE_GENERIC(key_serial, key_serial_t, -1) - -int keyring_read(key_serial_t serial, void **ret, size_t *ret_size); diff --git a/src/shared/killall.c b/src/shared/killall.c index 917b773..a087364 100644 --- a/src/shared/killall.c +++ b/src/shared/killall.c @@ -116,18 +116,18 @@ static bool ignore_proc(const PidRef *pid, bool warn_rootfs) { return true; } -static void log_children_no_yet_killed(Set *pids) { +static void log_children_not_yet_killed(Set *pids) { _cleanup_free_ char *lst_child = NULL; - void *p; int r; + void *p; SET_FOREACH(p, pids) { _cleanup_free_ char *s = NULL; if (pid_get_comm(PTR_TO_PID(p), &s) >= 0) - r = strextendf(&lst_child, ", " PID_FMT " (%s)", PTR_TO_PID(p), s); + r = strextendf_with_separator(&lst_child, ", ", PID_FMT " (%s)", PTR_TO_PID(p), s); else - r = strextendf(&lst_child, ", " PID_FMT, PTR_TO_PID(p)); + r = strextendf_with_separator(&lst_child, ", ", PID_FMT, PTR_TO_PID(p)); if (r < 0) return (void) log_oom_warning(); } @@ -135,7 +135,7 @@ static void log_children_no_yet_killed(Set *pids) { if (isempty(lst_child)) return; - log_warning("Waiting for process: %s", lst_child + 2); + log_warning("Waiting for process: %s", lst_child); } static int wait_for_children(Set *pids, sigset_t *mask, usec_t timeout) { @@ -200,7 +200,7 @@ static int wait_for_children(Set *pids, sigset_t *mask, usec_t timeout) { n = now(CLOCK_MONOTONIC); if (date_log_child > 0 && n >= date_log_child) { - log_children_no_yet_killed(pids); + log_children_not_yet_killed(pids); /* Log the children not yet killed only once */ date_log_child = 0; } @@ -234,14 +234,14 @@ static int killall(int sig, Set *pids, bool send_sighup) { r = proc_dir_open(&dir); if (r < 0) - return log_warning_errno(r, "opendir(/proc) failed: %m"); + return log_warning_errno(r, "Failed to open /proc/: %m"); for (;;) { _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; r = proc_dir_read_pidref(dir, &pidref); if (r < 0) - return log_warning_errno(r, "Failed to enumerate /proc: %m"); + return log_warning_errno(r, "Failed to enumerate /proc/: %m"); if (r == 0) break; diff --git a/src/shared/label-util.c b/src/shared/label-util.c index 308fbff..2c482da 100644 --- a/src/shared/label-util.c +++ b/src/shared/label-util.c @@ -81,22 +81,23 @@ int symlink_atomic_full_label(const char *from, const char *to, bool make_relati return mac_smack_fix(to, 0); } -int mknod_label(const char *pathname, mode_t mode, dev_t dev) { +int mknodat_label(int dirfd, const char *pathname, mode_t mode, dev_t dev) { int r; + assert(dirfd >= 0 || dirfd == AT_FDCWD); assert(pathname); - r = mac_selinux_create_file_prepare(pathname, mode); + r = mac_selinux_create_file_prepare_at(dirfd, pathname, mode); if (r < 0) return r; - r = RET_NERRNO(mknod(pathname, mode, dev)); + r = RET_NERRNO(mknodat(dirfd, pathname, mode, dev)); mac_selinux_create_file_clear(); if (r < 0) return r; - return mac_smack_fix(pathname, 0); + return mac_smack_fix_full(dirfd, pathname, NULL, 0); } int btrfs_subvol_make_label(const char *path) { diff --git a/src/shared/label-util.h b/src/shared/label-util.h index 7fb98c7..5a19a4c 100644 --- a/src/shared/label-util.h +++ b/src/shared/label-util.h @@ -21,7 +21,11 @@ int symlink_atomic_full_label(const char *from, const char *to, bool make_relati static inline int symlink_atomic_label(const char *from, const char *to) { return symlink_atomic_full_label(from, to, false); } -int mknod_label(const char *pathname, mode_t mode, dev_t dev); + +int mknodat_label(int dirfd, const char *pathname, mode_t mode, dev_t dev); +static inline int mknod_label(const char *pathname, mode_t mode, dev_t dev) { + return mknodat_label(AT_FDCWD, pathname, mode, dev); +} int btrfs_subvol_make_label(const char *path); diff --git a/src/shared/libarchive-util.c b/src/shared/libarchive-util.c new file mode 100644 index 0000000..58f6554 --- /dev/null +++ b/src/shared/libarchive-util.c @@ -0,0 +1,67 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "libarchive-util.h" + +#if HAVE_LIBARCHIVE +static void *libarchive_dl = NULL; + +DLSYM_FUNCTION(archive_entry_free); +DLSYM_FUNCTION(archive_entry_new); +DLSYM_FUNCTION(archive_entry_set_ctime); +DLSYM_FUNCTION(archive_entry_set_filetype); +DLSYM_FUNCTION(archive_entry_set_gid); +DLSYM_FUNCTION(archive_entry_set_mtime); +DLSYM_FUNCTION(archive_entry_set_pathname); +DLSYM_FUNCTION(archive_entry_set_perm); +DLSYM_FUNCTION(archive_entry_set_rdevmajor); +DLSYM_FUNCTION(archive_entry_set_rdevminor); +DLSYM_FUNCTION(archive_entry_set_symlink); +DLSYM_FUNCTION(archive_entry_set_size); +DLSYM_FUNCTION(archive_entry_set_uid); +DLSYM_FUNCTION(archive_error_string); +DLSYM_FUNCTION(archive_write_close); +DLSYM_FUNCTION(archive_write_data); +DLSYM_FUNCTION(archive_write_free); +DLSYM_FUNCTION(archive_write_header); +DLSYM_FUNCTION(archive_write_new); +DLSYM_FUNCTION(archive_write_open_FILE); +DLSYM_FUNCTION(archive_write_open_fd); +DLSYM_FUNCTION(archive_write_set_format_filter_by_ext); +DLSYM_FUNCTION(archive_write_set_format_gnutar); + +int dlopen_libarchive(void) { + ELF_NOTE_DLOPEN("archive", + "Support for decompressing archive files", + ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + "libarchive.so.13"); + + return dlopen_many_sym_or_warn( + &libarchive_dl, + "libarchive.so.13", + LOG_DEBUG, + DLSYM_ARG(archive_entry_free), + DLSYM_ARG(archive_entry_new), + DLSYM_ARG(archive_entry_set_ctime), + DLSYM_ARG(archive_entry_set_filetype), + DLSYM_ARG(archive_entry_set_gid), + DLSYM_ARG(archive_entry_set_mtime), + DLSYM_ARG(archive_entry_set_pathname), + DLSYM_ARG(archive_entry_set_perm), + DLSYM_ARG(archive_entry_set_rdevmajor), + DLSYM_ARG(archive_entry_set_rdevminor), + DLSYM_ARG(archive_entry_set_size), + DLSYM_ARG(archive_entry_set_symlink), + DLSYM_ARG(archive_entry_set_uid), + DLSYM_ARG(archive_error_string), + DLSYM_ARG(archive_write_close), + DLSYM_ARG(archive_write_data), + DLSYM_ARG(archive_write_free), + DLSYM_ARG(archive_write_header), + DLSYM_ARG(archive_write_new), + DLSYM_ARG(archive_write_open_FILE), + DLSYM_ARG(archive_write_open_fd), + DLSYM_ARG(archive_write_set_format_filter_by_ext), + DLSYM_ARG(archive_write_set_format_gnutar)); +} + +#endif diff --git a/src/shared/libarchive-util.h b/src/shared/libarchive-util.h new file mode 100644 index 0000000..8003da9 --- /dev/null +++ b/src/shared/libarchive-util.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "dlfcn-util.h" + +#if HAVE_LIBARCHIVE +#include <archive.h> +#include <archive_entry.h> + +DLSYM_PROTOTYPE(archive_entry_free); +DLSYM_PROTOTYPE(archive_entry_new); +DLSYM_PROTOTYPE(archive_entry_set_ctime); +DLSYM_PROTOTYPE(archive_entry_set_filetype); +DLSYM_PROTOTYPE(archive_entry_set_gid); +DLSYM_PROTOTYPE(archive_entry_set_mtime); +DLSYM_PROTOTYPE(archive_entry_set_pathname); +DLSYM_PROTOTYPE(archive_entry_set_perm); +DLSYM_PROTOTYPE(archive_entry_set_rdevmajor); +DLSYM_PROTOTYPE(archive_entry_set_rdevminor); +DLSYM_PROTOTYPE(archive_entry_set_symlink); +DLSYM_PROTOTYPE(archive_entry_set_size); +DLSYM_PROTOTYPE(archive_entry_set_uid); +DLSYM_PROTOTYPE(archive_error_string); +DLSYM_PROTOTYPE(archive_write_close); +DLSYM_PROTOTYPE(archive_write_data); +DLSYM_PROTOTYPE(archive_write_free); +DLSYM_PROTOTYPE(archive_write_header); +DLSYM_PROTOTYPE(archive_write_new); +DLSYM_PROTOTYPE(archive_write_open_FILE); +DLSYM_PROTOTYPE(archive_write_open_fd); +DLSYM_PROTOTYPE(archive_write_set_format_filter_by_ext); +DLSYM_PROTOTYPE(archive_write_set_format_gnutar); + +int dlopen_libarchive(void); + +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct archive_entry*, sym_archive_entry_free, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct archive*, sym_archive_write_free, NULL); + +#else + +static inline int dlopen_libarchive(void) { + return -EOPNOTSUPP; +} + +#endif diff --git a/src/shared/libcrypt-util.c b/src/shared/libcrypt-util.c index 81e6f17..50b2502 100644 --- a/src/shared/libcrypt-util.c +++ b/src/shared/libcrypt-util.c @@ -114,7 +114,7 @@ static char* systemd_crypt_ra(const char *phrase, const char *setting, void **da if (!*data) { *data = new0(struct crypt_data, 1); if (!*data) { - errno = -ENOMEM; + errno = ENOMEM; return NULL; } @@ -140,7 +140,7 @@ static char* systemd_crypt_ra(const char *phrase, const char *setting, void **da int hash_password_full(const char *password, void **cd_data, int *cd_size, char **ret) { _cleanup_free_ char *salt = NULL; _cleanup_(erase_and_freep) void *_cd_data = NULL; - char *p; + const char *p; int r, _cd_size = 0; assert(!!cd_data == !!cd_size); @@ -155,12 +155,7 @@ int hash_password_full(const char *password, void **cd_data, int *cd_size, char return log_debug_errno(errno_or_else(SYNTHETIC_ERRNO(EINVAL)), CRYPT_RA_NAME "() failed: %m"); - p = strdup(p); - if (!p) - return -ENOMEM; - - *ret = p; - return 0; + return strdup_to(ret, p); } bool looks_like_hashed_password(const char *s) { diff --git a/src/shared/libfido2-util.c b/src/shared/libfido2-util.c index 1cc3afe..37f6898 100644 --- a/src/shared/libfido2-util.c +++ b/src/shared/libfido2-util.c @@ -16,53 +16,54 @@ static void *libfido2_dl = NULL; -int (*sym_fido_assert_allow_cred)(fido_assert_t *, const unsigned char *, size_t) = NULL; -void (*sym_fido_assert_free)(fido_assert_t **) = NULL; -size_t (*sym_fido_assert_hmac_secret_len)(const fido_assert_t *, size_t) = NULL; -const unsigned char* (*sym_fido_assert_hmac_secret_ptr)(const fido_assert_t *, size_t) = NULL; -fido_assert_t* (*sym_fido_assert_new)(void) = NULL; -int (*sym_fido_assert_set_clientdata_hash)(fido_assert_t *, const unsigned char *, size_t) = NULL; -int (*sym_fido_assert_set_extensions)(fido_assert_t *, int) = NULL; -int (*sym_fido_assert_set_hmac_salt)(fido_assert_t *, const unsigned char *, size_t) = NULL; -int (*sym_fido_assert_set_rp)(fido_assert_t *, const char *) = NULL; -int (*sym_fido_assert_set_up)(fido_assert_t *, fido_opt_t) = NULL; -int (*sym_fido_assert_set_uv)(fido_assert_t *, fido_opt_t) = NULL; -size_t (*sym_fido_cbor_info_extensions_len)(const fido_cbor_info_t *) = NULL; -char **(*sym_fido_cbor_info_extensions_ptr)(const fido_cbor_info_t *) = NULL; -void (*sym_fido_cbor_info_free)(fido_cbor_info_t **) = NULL; -fido_cbor_info_t* (*sym_fido_cbor_info_new)(void) = NULL; -size_t (*sym_fido_cbor_info_options_len)(const fido_cbor_info_t *) = NULL; -char** (*sym_fido_cbor_info_options_name_ptr)(const fido_cbor_info_t *) = NULL; -const bool* (*sym_fido_cbor_info_options_value_ptr)(const fido_cbor_info_t *) = NULL; -void (*sym_fido_cred_free)(fido_cred_t **) = NULL; -size_t (*sym_fido_cred_id_len)(const fido_cred_t *) = NULL; -const unsigned char* (*sym_fido_cred_id_ptr)(const fido_cred_t *) = NULL; -fido_cred_t* (*sym_fido_cred_new)(void) = NULL; -int (*sym_fido_cred_set_clientdata_hash)(fido_cred_t *, const unsigned char *, size_t) = NULL; -int (*sym_fido_cred_set_extensions)(fido_cred_t *, int) = NULL; -int (*sym_fido_cred_set_rk)(fido_cred_t *, fido_opt_t) = NULL; -int (*sym_fido_cred_set_rp)(fido_cred_t *, const char *, const char *) = NULL; -int (*sym_fido_cred_set_type)(fido_cred_t *, int) = NULL; -int (*sym_fido_cred_set_user)(fido_cred_t *, const unsigned char *, size_t, const char *, const char *, const char *) = NULL; -int (*sym_fido_cred_set_uv)(fido_cred_t *, fido_opt_t) = NULL; -void (*sym_fido_dev_free)(fido_dev_t **) = NULL; -int (*sym_fido_dev_get_assert)(fido_dev_t *, fido_assert_t *, const char *) = NULL; -int (*sym_fido_dev_get_cbor_info)(fido_dev_t *, fido_cbor_info_t *) = NULL; -void (*sym_fido_dev_info_free)(fido_dev_info_t **, size_t) = NULL; -int (*sym_fido_dev_info_manifest)(fido_dev_info_t *, size_t, size_t *) = NULL; -const char* (*sym_fido_dev_info_manufacturer_string)(const fido_dev_info_t *) = NULL; -const char* (*sym_fido_dev_info_product_string)(const fido_dev_info_t *) = NULL; -fido_dev_info_t* (*sym_fido_dev_info_new)(size_t) = NULL; -const char* (*sym_fido_dev_info_path)(const fido_dev_info_t *) = NULL; -const fido_dev_info_t* (*sym_fido_dev_info_ptr)(const fido_dev_info_t *, size_t) = NULL; -bool (*sym_fido_dev_is_fido2)(const fido_dev_t *) = NULL; -int (*sym_fido_dev_make_cred)(fido_dev_t *, fido_cred_t *, const char *) = NULL; -fido_dev_t* (*sym_fido_dev_new)(void) = NULL; -int (*sym_fido_dev_open)(fido_dev_t *, const char *) = NULL; -int (*sym_fido_dev_close)(fido_dev_t *) = NULL; -void (*sym_fido_init)(int) = NULL; -void (*sym_fido_set_log_handler)(fido_log_handler_t *) = NULL; -const char* (*sym_fido_strerr)(int) = NULL; +DLSYM_FUNCTION(fido_assert_allow_cred); +DLSYM_FUNCTION(fido_assert_free); +DLSYM_FUNCTION(fido_assert_hmac_secret_len); +DLSYM_FUNCTION(fido_assert_hmac_secret_ptr); +DLSYM_FUNCTION(fido_assert_new); +DLSYM_FUNCTION(fido_assert_set_clientdata_hash); +DLSYM_FUNCTION(fido_assert_set_extensions); +DLSYM_FUNCTION(fido_assert_set_hmac_salt); +DLSYM_FUNCTION(fido_assert_set_rp); +DLSYM_FUNCTION(fido_assert_set_up); +DLSYM_FUNCTION(fido_assert_set_uv); +DLSYM_FUNCTION(fido_cbor_info_extensions_len); +DLSYM_FUNCTION(fido_cbor_info_extensions_ptr); +DLSYM_FUNCTION(fido_cbor_info_free); +DLSYM_FUNCTION(fido_cbor_info_new); +DLSYM_FUNCTION(fido_cbor_info_options_len); +DLSYM_FUNCTION(fido_cbor_info_options_name_ptr); +DLSYM_FUNCTION(fido_cbor_info_options_value_ptr); +DLSYM_FUNCTION(fido_cred_free); +DLSYM_FUNCTION(fido_cred_id_len); +DLSYM_FUNCTION(fido_cred_id_ptr); +DLSYM_FUNCTION(fido_cred_new); +DLSYM_FUNCTION(fido_cred_set_clientdata_hash); +DLSYM_FUNCTION(fido_cred_set_extensions); +DLSYM_FUNCTION(fido_cred_set_prot); +DLSYM_FUNCTION(fido_cred_set_rk); +DLSYM_FUNCTION(fido_cred_set_rp); +DLSYM_FUNCTION(fido_cred_set_type); +DLSYM_FUNCTION(fido_cred_set_user); +DLSYM_FUNCTION(fido_cred_set_uv); +DLSYM_FUNCTION(fido_dev_free); +DLSYM_FUNCTION(fido_dev_get_assert); +DLSYM_FUNCTION(fido_dev_get_cbor_info); +DLSYM_FUNCTION(fido_dev_info_free); +DLSYM_FUNCTION(fido_dev_info_manifest); +DLSYM_FUNCTION(fido_dev_info_manufacturer_string); +DLSYM_FUNCTION(fido_dev_info_product_string); +DLSYM_FUNCTION(fido_dev_info_new); +DLSYM_FUNCTION(fido_dev_info_path); +DLSYM_FUNCTION(fido_dev_info_ptr); +DLSYM_FUNCTION(fido_dev_is_fido2); +DLSYM_FUNCTION(fido_dev_make_cred); +DLSYM_FUNCTION(fido_dev_new); +DLSYM_FUNCTION(fido_dev_open); +DLSYM_FUNCTION(fido_dev_close); +DLSYM_FUNCTION(fido_init); +DLSYM_FUNCTION(fido_set_log_handler); +DLSYM_FUNCTION(fido_strerr); static void fido_log_propagate_handler(const char *s) { log_debug("libfido2: %s", strempty(s)); @@ -71,6 +72,11 @@ static void fido_log_propagate_handler(const char *s) { int dlopen_libfido2(void) { int r; + ELF_NOTE_DLOPEN("fido2", + "Support fido2 for encryption and authentication", + ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + "libfido2.so.1"); + r = dlopen_many_sym_or_warn( &libfido2_dl, "libfido2.so.1", LOG_DEBUG, DLSYM_ARG(fido_assert_allow_cred), @@ -97,6 +103,7 @@ int dlopen_libfido2(void) { DLSYM_ARG(fido_cred_new), DLSYM_ARG(fido_cred_set_clientdata_hash), DLSYM_ARG(fido_cred_set_extensions), + DLSYM_ARG(fido_cred_set_prot), DLSYM_ARG(fido_cred_set_rk), DLSYM_ARG(fido_cred_set_rp), DLSYM_ARG(fido_cred_set_type), @@ -574,7 +581,7 @@ static int fido2_use_hmac_hash_specific_token( /* COSE_ECDH_ES256 is not usable with fido_cred_set_type() thus it's not listed here. */ static const char *fido2_algorithm_to_string(int alg) { - switch(alg) { + switch (alg) { case COSE_ES256: return "es256"; case COSE_RS256: @@ -686,7 +693,8 @@ int fido2_generate_hmac_hash( const char *user_name, const char *user_display_name, const char *user_icon, - const char *askpw_icon_name, + const char *askpw_icon, + const char *askpw_credential, Fido2EnrollFlags lock_with, int cred_alg, void **ret_cid, size_t *ret_cid_size, @@ -775,10 +783,21 @@ int fido2_generate_hmac_hash( if (!c) return log_oom(); - r = sym_fido_cred_set_extensions(c, FIDO_EXT_HMAC_SECRET); + int extensions = FIDO_EXT_HMAC_SECRET; + if (FLAGS_SET(lock_with, FIDO2ENROLL_UV)) { + /* Attempt to use the "cred protect" extension, requiring user verification (UV) for this + * credential. If the authenticator doesn't support the extension, it will be ignored. */ + extensions |= FIDO_EXT_CRED_PROTECT; + + r = sym_fido_cred_set_prot(c, FIDO_CRED_PROT_UV_REQUIRED); + if (r != FIDO_OK) + log_warning("Failed to set protection level on FIDO2 credential, ignoring: %s", sym_fido_strerr(r)); + } + + r = sym_fido_cred_set_extensions(c, extensions); if (r != FIDO_OK) return log_error_errno(SYNTHETIC_ERRNO(EIO), - "Failed to enable HMAC-SECRET extension on FIDO2 credential: %s", sym_fido_strerr(r)); + "Failed to enable extensions on FIDO2 credential: %s", sym_fido_strerr(r)); r = sym_fido_cred_set_rp(c, rp_id, rp_name); if (r != FIDO_OK) @@ -829,7 +848,17 @@ int fido2_generate_hmac_hash( emoji_enabled() ? special_glyph(SPECIAL_GLYPH_TOUCH) : "", emoji_enabled() ? " " : ""); - r = sym_fido_dev_make_cred(d, c, NULL); + /* If we are using the user PIN, then we must pass that PIN to the get_assertion call below, or + * the authenticator will use the non-user-verification HMAC secret (which differs from the one when + * the PIN is passed). + * + * Rather than potentially trying and failing to create the credential, just collect the PIN first + * and then pass it to both the make_credential and the get_assertion operations. */ + if (FLAGS_SET(lock_with, FIDO2ENROLL_PIN)) + r = FIDO_ERR_PIN_REQUIRED; + else + r = sym_fido_dev_make_cred(d, c, NULL); + if (r == FIDO_ERR_PIN_REQUIRED) { if (!has_client_pin) @@ -838,8 +867,14 @@ int fido2_generate_hmac_hash( for (;;) { _cleanup_strv_free_erase_ char **pin = NULL; - - r = ask_password_auto("Please enter security token PIN:", askpw_icon_name, NULL, "fido2-pin", "fido2-pin", USEC_INFINITY, 0, &pin); + AskPasswordRequest req = { + .message = "Please enter security token PIN:", + .icon = askpw_icon, + .keyring = "fido2-pin", + .credential = askpw_credential, + }; + + r = ask_password_auto(&req, USEC_INFINITY, /* flags= */ 0, &pin); if (r < 0) return log_error_errno(r, "Failed to acquire user PIN: %m"); diff --git a/src/shared/libfido2-util.h b/src/shared/libfido2-util.h index 4cfc95f..5e40b2a 100644 --- a/src/shared/libfido2-util.h +++ b/src/shared/libfido2-util.h @@ -17,53 +17,56 @@ typedef enum Fido2EnrollFlags { #if HAVE_LIBFIDO2 #include <fido.h> -extern int (*sym_fido_assert_allow_cred)(fido_assert_t *, const unsigned char *, size_t); -extern void (*sym_fido_assert_free)(fido_assert_t **); -extern size_t (*sym_fido_assert_hmac_secret_len)(const fido_assert_t *, size_t); -extern const unsigned char* (*sym_fido_assert_hmac_secret_ptr)(const fido_assert_t *, size_t); -extern fido_assert_t* (*sym_fido_assert_new)(void); -extern int (*sym_fido_assert_set_clientdata_hash)(fido_assert_t *, const unsigned char *, size_t); -extern int (*sym_fido_assert_set_extensions)(fido_assert_t *, int); -extern int (*sym_fido_assert_set_hmac_salt)(fido_assert_t *, const unsigned char *, size_t); -extern int (*sym_fido_assert_set_rp)(fido_assert_t *, const char *); -extern int (*sym_fido_assert_set_up)(fido_assert_t *, fido_opt_t); -extern int (*sym_fido_assert_set_uv)(fido_assert_t *, fido_opt_t); -extern size_t (*sym_fido_cbor_info_extensions_len)(const fido_cbor_info_t *); -extern char **(*sym_fido_cbor_info_extensions_ptr)(const fido_cbor_info_t *); -extern void (*sym_fido_cbor_info_free)(fido_cbor_info_t **); -extern fido_cbor_info_t* (*sym_fido_cbor_info_new)(void); -extern size_t (*sym_fido_cbor_info_options_len)(const fido_cbor_info_t *); -extern char** (*sym_fido_cbor_info_options_name_ptr)(const fido_cbor_info_t *); -extern const bool* (*sym_fido_cbor_info_options_value_ptr)(const fido_cbor_info_t *); -extern void (*sym_fido_cred_free)(fido_cred_t **); -extern size_t (*sym_fido_cred_id_len)(const fido_cred_t *); -extern const unsigned char* (*sym_fido_cred_id_ptr)(const fido_cred_t *); -extern fido_cred_t* (*sym_fido_cred_new)(void); -extern int (*sym_fido_cred_set_clientdata_hash)(fido_cred_t *, const unsigned char *, size_t); -extern int (*sym_fido_cred_set_extensions)(fido_cred_t *, int); -extern int (*sym_fido_cred_set_rk)(fido_cred_t *, fido_opt_t); -extern int (*sym_fido_cred_set_rp)(fido_cred_t *, const char *, const char *); -extern int (*sym_fido_cred_set_type)(fido_cred_t *, int); -extern int (*sym_fido_cred_set_user)(fido_cred_t *, const unsigned char *, size_t, const char *, const char *, const char *); -extern int (*sym_fido_cred_set_uv)(fido_cred_t *, fido_opt_t); -extern void (*sym_fido_dev_free)(fido_dev_t **); -extern int (*sym_fido_dev_get_assert)(fido_dev_t *, fido_assert_t *, const char *); -extern int (*sym_fido_dev_get_cbor_info)(fido_dev_t *, fido_cbor_info_t *); -extern void (*sym_fido_dev_info_free)(fido_dev_info_t **, size_t); -extern int (*sym_fido_dev_info_manifest)(fido_dev_info_t *, size_t, size_t *); -extern const char* (*sym_fido_dev_info_manufacturer_string)(const fido_dev_info_t *); -extern const char* (*sym_fido_dev_info_product_string)(const fido_dev_info_t *); -extern fido_dev_info_t* (*sym_fido_dev_info_new)(size_t); -extern const char* (*sym_fido_dev_info_path)(const fido_dev_info_t *); -extern const fido_dev_info_t* (*sym_fido_dev_info_ptr)(const fido_dev_info_t *, size_t); -extern bool (*sym_fido_dev_is_fido2)(const fido_dev_t *); -extern int (*sym_fido_dev_make_cred)(fido_dev_t *, fido_cred_t *, const char *); -extern fido_dev_t* (*sym_fido_dev_new)(void); -extern int (*sym_fido_dev_open)(fido_dev_t *, const char *); -extern int (*sym_fido_dev_close)(fido_dev_t *); -extern void (*sym_fido_init)(int); -extern void (*sym_fido_set_log_handler)(fido_log_handler_t *); -extern const char* (*sym_fido_strerr)(int); +#include "dlfcn-util.h" + +DLSYM_PROTOTYPE(fido_assert_allow_cred); +DLSYM_PROTOTYPE(fido_assert_free); +DLSYM_PROTOTYPE(fido_assert_hmac_secret_len); +DLSYM_PROTOTYPE(fido_assert_hmac_secret_ptr); +DLSYM_PROTOTYPE(fido_assert_new); +DLSYM_PROTOTYPE(fido_assert_set_clientdata_hash); +DLSYM_PROTOTYPE(fido_assert_set_extensions); +DLSYM_PROTOTYPE(fido_assert_set_hmac_salt); +DLSYM_PROTOTYPE(fido_assert_set_rp); +DLSYM_PROTOTYPE(fido_assert_set_up); +DLSYM_PROTOTYPE(fido_assert_set_uv); +DLSYM_PROTOTYPE(fido_cbor_info_extensions_len); +DLSYM_PROTOTYPE(fido_cbor_info_extensions_ptr); +DLSYM_PROTOTYPE(fido_cbor_info_free); +DLSYM_PROTOTYPE(fido_cbor_info_new); +DLSYM_PROTOTYPE(fido_cbor_info_options_len); +DLSYM_PROTOTYPE(fido_cbor_info_options_name_ptr); +DLSYM_PROTOTYPE(fido_cbor_info_options_value_ptr); +DLSYM_PROTOTYPE(fido_cred_free); +DLSYM_PROTOTYPE(fido_cred_id_len); +DLSYM_PROTOTYPE(fido_cred_id_ptr); +DLSYM_PROTOTYPE(fido_cred_new); +DLSYM_PROTOTYPE(fido_cred_set_clientdata_hash); +DLSYM_PROTOTYPE(fido_cred_set_extensions); +DLSYM_PROTOTYPE(fido_cred_set_prot); +DLSYM_PROTOTYPE(fido_cred_set_rk); +DLSYM_PROTOTYPE(fido_cred_set_rp); +DLSYM_PROTOTYPE(fido_cred_set_type); +DLSYM_PROTOTYPE(fido_cred_set_user); +DLSYM_PROTOTYPE(fido_cred_set_uv); +DLSYM_PROTOTYPE(fido_dev_free); +DLSYM_PROTOTYPE(fido_dev_get_assert); +DLSYM_PROTOTYPE(fido_dev_get_cbor_info); +DLSYM_PROTOTYPE(fido_dev_info_free); +DLSYM_PROTOTYPE(fido_dev_info_manifest); +DLSYM_PROTOTYPE(fido_dev_info_manufacturer_string); +DLSYM_PROTOTYPE(fido_dev_info_product_string); +DLSYM_PROTOTYPE(fido_dev_info_new); +DLSYM_PROTOTYPE(fido_dev_info_path); +DLSYM_PROTOTYPE(fido_dev_info_ptr); +DLSYM_PROTOTYPE(fido_dev_is_fido2); +DLSYM_PROTOTYPE(fido_dev_make_cred); +DLSYM_PROTOTYPE(fido_dev_new); +DLSYM_PROTOTYPE(fido_dev_open); +DLSYM_PROTOTYPE(fido_dev_close); +DLSYM_PROTOTYPE(fido_init); +DLSYM_PROTOTYPE(fido_set_log_handler); +DLSYM_PROTOTYPE(fido_strerr); int dlopen_libfido2(void); @@ -109,7 +112,8 @@ int fido2_generate_hmac_hash( const char *user_name, const char *user_display_name, const char *user_icon, - const char *askpw_icon_name, + const char *askpw_icon, + const char *askpw_credential, Fido2EnrollFlags lock_with, int cred_alg, void **ret_cid, size_t *ret_cid_size, diff --git a/src/shared/local-addresses.c b/src/shared/local-addresses.c index a1577de..5d5435f 100644 --- a/src/shared/local-addresses.c +++ b/src/shared/local-addresses.c @@ -25,7 +25,11 @@ static int address_compare(const struct local_address *a, const struct local_add if (r != 0) return r; - r = CMP(a->metric, b->metric); + r = CMP(a->priority, b->priority); + if (r != 0) + return r; + + r = CMP(a->weight, b->weight); if (r != 0) return r; @@ -36,6 +40,17 @@ static int address_compare(const struct local_address *a, const struct local_add return memcmp(&a->address, &b->address, FAMILY_ADDRESS_SIZE(a->family)); } +bool has_local_address(const struct local_address *addresses, size_t n_addresses, const struct local_address *needle) { + assert(addresses || n_addresses == 0); + assert(needle); + + for (size_t i = 0; i < n_addresses; i++) + if (address_compare(addresses + i, needle) == 0) + return true; + + return false; +} + static void suppress_duplicates(struct local_address *list, size_t *n_list) { size_t old_size, new_size; @@ -58,6 +73,48 @@ static void suppress_duplicates(struct local_address *list, size_t *n_list) { *n_list = new_size; } +static int add_local_address_full( + struct local_address **list, + size_t *n_list, + int ifindex, + unsigned char scope, + uint32_t priority, + uint32_t weight, + int family, + const union in_addr_union *address) { + + assert(list); + assert(n_list); + assert(ifindex > 0); + assert(IN_SET(family, AF_INET, AF_INET6)); + assert(address); + + if (!GREEDY_REALLOC(*list, *n_list + 1)) + return -ENOMEM; + + (*list)[(*n_list)++] = (struct local_address) { + .ifindex = ifindex, + .scope = scope, + .priority = priority, + .weight = weight, + .family = family, + .address = *address, + }; + + return 1; +} + +static int add_local_address( + struct local_address **list, + size_t *n_list, + int ifindex, + unsigned char scope, + int family, + const union in_addr_union *address) { + + return add_local_address_full(list, n_list, ifindex, scope, 0, 0, family, address); +} + int local_addresses( sd_netlink *context, int ifindex, @@ -91,8 +148,8 @@ int local_addresses( return r; for (sd_netlink_message *m = reply; m; m = sd_netlink_message_next(m)) { - struct local_address *a; - unsigned char flags; + union in_addr_union a; + unsigned char flags, scope; uint16_t type; int ifi, family; @@ -115,62 +172,58 @@ int local_addresses( r = sd_rtnl_message_addr_get_family(m, &family); if (r < 0) return r; + if (!IN_SET(family, AF_INET, AF_INET6)) + continue; if (af != AF_UNSPEC && af != family) continue; r = sd_rtnl_message_addr_get_flags(m, &flags); if (r < 0) return r; - if (flags & IFA_F_DEPRECATED) + if ((flags & (IFA_F_DEPRECATED|IFA_F_TENTATIVE)) != 0) continue; - if (!GREEDY_REALLOC0(list, n_list+1)) - return -ENOMEM; - - a = list + n_list; - - r = sd_rtnl_message_addr_get_scope(m, &a->scope); + r = sd_rtnl_message_addr_get_scope(m, &scope); if (r < 0) return r; - if (ifindex == 0 && IN_SET(a->scope, RT_SCOPE_HOST, RT_SCOPE_NOWHERE)) + if (ifindex == 0 && IN_SET(scope, RT_SCOPE_HOST, RT_SCOPE_NOWHERE)) continue; switch (family) { case AF_INET: - r = sd_netlink_message_read_in_addr(m, IFA_LOCAL, &a->address.in); + r = sd_netlink_message_read_in_addr(m, IFA_LOCAL, &a.in); if (r < 0) { - r = sd_netlink_message_read_in_addr(m, IFA_ADDRESS, &a->address.in); + r = sd_netlink_message_read_in_addr(m, IFA_ADDRESS, &a.in); if (r < 0) continue; } break; case AF_INET6: - r = sd_netlink_message_read_in6_addr(m, IFA_LOCAL, &a->address.in6); + r = sd_netlink_message_read_in6_addr(m, IFA_LOCAL, &a.in6); if (r < 0) { - r = sd_netlink_message_read_in6_addr(m, IFA_ADDRESS, &a->address.in6); + r = sd_netlink_message_read_in6_addr(m, IFA_ADDRESS, &a.in6); if (r < 0) continue; } break; default: - continue; + assert_not_reached(); } - a->ifindex = ifi; - a->family = family; - - n_list++; + r = add_local_address(&list, &n_list, ifi, scope, family, &a); + if (r < 0) + return r; }; - if (ret) { - typesafe_qsort(list, n_list, address_compare); - suppress_duplicates(list, &n_list); + typesafe_qsort(list, n_list, address_compare); + suppress_duplicates(list, &n_list); + + if (ret) *ret = TAKE_PTR(list); - } return (int) n_list; } @@ -178,27 +231,117 @@ int local_addresses( static int add_local_gateway( struct local_address **list, size_t *n_list, - int af, int ifindex, - uint32_t metric, - const RouteVia *via) { + uint32_t priority, + uint32_t weight, + int family, + const union in_addr_union *address) { + + return add_local_address_full(list, n_list, ifindex, 0, priority, weight, family, address); +} + +static int parse_nexthop_one( + struct local_address **list, + size_t *n_list, + bool allow_via, + int family, + uint32_t priority, + const struct rtnexthop *rtnh) { + + bool has_gw = false; + int r; + + assert(rtnh); + + size_t len = rtnh->rtnh_len - sizeof(struct rtnexthop); + for (struct rtattr *attr = RTNH_DATA(rtnh); RTA_OK(attr, len); attr = RTA_NEXT(attr, len)) + + switch (attr->rta_type) { + case RTA_GATEWAY: + if (has_gw) + return -EBADMSG; + + has_gw = true; + + if (attr->rta_len != RTA_LENGTH(FAMILY_ADDRESS_SIZE(family))) + return -EBADMSG; + + union in_addr_union a; + memcpy(&a, RTA_DATA(attr), FAMILY_ADDRESS_SIZE(family)); + r = add_local_gateway(list, n_list, rtnh->rtnh_ifindex, priority, rtnh->rtnh_hops, family, &a); + if (r < 0) + return r; + + break; + + case RTA_VIA: + if (has_gw) + return -EBADMSG; + + has_gw = true; + + if (!allow_via) + continue; + + if (family != AF_INET) + return -EBADMSG; /* RTA_VIA is only supported for IPv4 routes. */ + + if (attr->rta_len != RTA_LENGTH(sizeof(RouteVia))) + return -EBADMSG; + + RouteVia *via = RTA_DATA(attr); + if (via->family != AF_INET6) + return -EBADMSG; /* gateway address should be always IPv6. */ + + r = add_local_gateway(list, n_list, rtnh->rtnh_ifindex, priority, rtnh->rtnh_hops, via->family, + &(union in_addr_union) { .in6 = via->address.in6 }); + if (r < 0) + return r; + + break; + } + + return 0; +} + +static int parse_nexthops( + struct local_address **list, + size_t *n_list, + int ifindex, + bool allow_via, + int family, + uint32_t priority, + const struct rtnexthop *rtnh, + size_t size) { + + int r; assert(list); assert(n_list); - assert(via); + assert(IN_SET(family, AF_INET, AF_INET6)); + assert(rtnh || size == 0); - if (af != AF_UNSPEC && af != via->family) - return 0; + if (size < sizeof(struct rtnexthop)) + return -EBADMSG; - if (!GREEDY_REALLOC(*list, *n_list + 1)) - return -ENOMEM; + for (; size >= sizeof(struct rtnexthop); ) { + if (NLMSG_ALIGN(rtnh->rtnh_len) > size) + return -EBADMSG; - (*list)[(*n_list)++] = (struct local_address) { - .ifindex = ifindex, - .metric = metric, - .family = via->family, - .address = via->address, - }; + if (rtnh->rtnh_len < sizeof(struct rtnexthop)) + return -EBADMSG; + + if (ifindex > 0 && rtnh->rtnh_ifindex != ifindex) + goto next_nexthop; + + r = parse_nexthop_one(list, n_list, allow_via, family, priority, rtnh); + if (r < 0) + return r; + + next_nexthop: + size -= NLMSG_ALIGN(rtnh->rtnh_len); + rtnh = RTNH_NEXT(rtnh); + } return 0; } @@ -215,6 +358,12 @@ int local_gateways( size_t n_list = 0; int r; + /* The RTA_VIA attribute is used only for IPv4 routes with an IPv6 gateway. If IPv4 gateways are + * requested (af == AF_INET), then we do not return IPv6 gateway addresses. Similarly, if IPv6 + * gateways are requested (af == AF_INET6), then we do not return gateway addresses for IPv4 routes. + * So, the RTA_VIA attribute is only parsed when af == AF_UNSPEC. */ + bool allow_via = af == AF_UNSPEC; + if (context) rtnl = sd_netlink_ref(context); else { @@ -244,15 +393,10 @@ int local_gateways( return r; for (sd_netlink_message *m = reply; m; m = sd_netlink_message_next(m)) { - _cleanup_ordered_set_free_free_ OrderedSet *multipath_routes = NULL; - _cleanup_free_ void *rta_multipath = NULL; - union in_addr_union gateway; uint16_t type; unsigned char dst_len, src_len, table; - uint32_t ifi = 0, metric = 0; - size_t rta_len; + uint32_t ifi = 0, priority = 0; int family; - RouteVia via; r = sd_netlink_message_get_errno(m); if (r < 0) @@ -283,7 +427,7 @@ int local_gateways( if (table != RT_TABLE_MAIN) continue; - r = sd_netlink_message_read_u32(m, RTA_PRIORITY, &metric); + r = sd_netlink_message_read_u32(m, RTA_PRIORITY, &priority); if (r < 0 && r != -ENODATA) return r; @@ -292,6 +436,8 @@ int local_gateways( return r; if (!IN_SET(family, AF_INET, AF_INET6)) continue; + if (af != AF_UNSPEC && af != family) + continue; r = sd_netlink_message_read_u32(m, RTA_OIF, &ifi); if (r < 0 && r != -ENODATA) @@ -302,64 +448,73 @@ int local_gateways( if (ifindex > 0 && (int) ifi != ifindex) continue; + union in_addr_union gateway; r = netlink_message_read_in_addr_union(m, RTA_GATEWAY, family, &gateway); if (r < 0 && r != -ENODATA) return r; if (r >= 0) { - via.family = family; - via.address = gateway; - r = add_local_gateway(&list, &n_list, af, ifi, metric, &via); + r = add_local_gateway(&list, &n_list, ifi, priority, 0, family, &gateway); if (r < 0) return r; continue; } + if (!allow_via) + continue; + if (family != AF_INET) continue; + RouteVia via; r = sd_netlink_message_read(m, RTA_VIA, sizeof(via), &via); if (r < 0 && r != -ENODATA) return r; if (r >= 0) { - r = add_local_gateway(&list, &n_list, af, ifi, metric, &via); + if (via.family != AF_INET6) + return -EBADMSG; + + r = add_local_gateway(&list, &n_list, ifi, priority, 0, via.family, + &(union in_addr_union) { .in6 = via.address.in6 }); if (r < 0) return r; - - continue; } + + /* If the route has RTA_OIF, it does not have RTA_MULTIPATH. */ + continue; } + size_t rta_len; + _cleanup_free_ void *rta_multipath = NULL; r = sd_netlink_message_read_data(m, RTA_MULTIPATH, &rta_len, &rta_multipath); if (r < 0 && r != -ENODATA) return r; if (r >= 0) { - MultipathRoute *mr; - - r = rtattr_read_nexthop(rta_multipath, rta_len, family, &multipath_routes); + r = parse_nexthops(&list, &n_list, ifindex, allow_via, family, priority, rta_multipath, rta_len); if (r < 0) return r; - - ORDERED_SET_FOREACH(mr, multipath_routes) { - if (ifindex > 0 && mr->ifindex != ifindex) - continue; - - r = add_local_gateway(&list, &n_list, af, ifi, metric, &mr->gateway); - if (r < 0) - return r; - } } } - if (ret) { - typesafe_qsort(list, n_list, address_compare); - suppress_duplicates(list, &n_list); + typesafe_qsort(list, n_list, address_compare); + suppress_duplicates(list, &n_list); + + if (ret) *ret = TAKE_PTR(list); - } return (int) n_list; } +static int add_local_outbound( + struct local_address **list, + size_t *n_list, + int ifindex, + int family, + const union in_addr_union *address) { + + return add_local_address_full(list, n_list, ifindex, 0, 0, 0, family, address); +} + int local_outbounds( sd_netlink *context, int ifindex, @@ -466,29 +621,20 @@ int local_outbounds( if (in4_addr_is_null(&sa.in.sin_addr)) /* Auto-binding didn't work. :-( */ continue; - if (!GREEDY_REALLOC(list, n_list+1)) - return -ENOMEM; - - list[n_list++] = (struct local_address) { - .family = gateways[i].family, - .ifindex = gateways[i].ifindex, - .address.in = sa.in.sin_addr, - }; - + r = add_local_outbound(&list, &n_list, gateways[i].ifindex, gateways[i].family, + &(union in_addr_union) { .in = sa.in.sin_addr }); + if (r < 0) + return r; break; case AF_INET6: if (in6_addr_is_null(&sa.in6.sin6_addr)) continue; - if (!GREEDY_REALLOC(list, n_list+1)) - return -ENOMEM; - - list[n_list++] = (struct local_address) { - .family = gateways[i].family, - .ifindex = gateways[i].ifindex, - .address.in6 = sa.in6.sin6_addr, - }; + r = add_local_outbound(&list, &n_list, gateways[i].ifindex, gateways[i].family, + &(union in_addr_union) { .in6 = sa.in6.sin6_addr }); + if (r < 0) + return r; break; default: @@ -496,11 +642,11 @@ int local_outbounds( } } - if (ret) { - typesafe_qsort(list, n_list, address_compare); - suppress_duplicates(list, &n_list); + typesafe_qsort(list, n_list, address_compare); + suppress_duplicates(list, &n_list); + + if (ret) *ret = TAKE_PTR(list); - } return (int) n_list; } diff --git a/src/shared/local-addresses.h b/src/shared/local-addresses.h index 38a17d2..399d0c6 100644 --- a/src/shared/local-addresses.h +++ b/src/shared/local-addresses.h @@ -6,12 +6,16 @@ #include "in-addr-util.h" struct local_address { - int family, ifindex; + int ifindex; unsigned char scope; - uint32_t metric; + uint32_t priority; + uint32_t weight; + int family; union in_addr_union address; }; +bool has_local_address(const struct local_address *addresses, size_t n_addresses, const struct local_address *needle); + int local_addresses(sd_netlink *rtnl, int ifindex, int af, struct local_address **ret); int local_gateways(sd_netlink *rtnl, int ifindex, int af, struct local_address **ret); diff --git a/src/shared/logs-show.c b/src/shared/logs-show.c index 0a31be3..c71c868 100644 --- a/src/shared/logs-show.c +++ b/src/shared/logs-show.c @@ -5,7 +5,6 @@ #include <signal.h> #include <stdint.h> #include <stdlib.h> -#include <sys/socket.h> #include <syslog.h> #include <unistd.h> @@ -27,11 +26,9 @@ #include "log.h" #include "logs-show.h" #include "macro.h" -#include "namespace-util.h" #include "output-mode.h" #include "parse-util.h" #include "pretty-print.h" -#include "process-util.h" #include "sparse-endian.h" #include "stdio-util.h" #include "string-table.h" @@ -367,39 +364,37 @@ static int output_timestamp_realtime( sd_journal *j, OutputMode mode, OutputFlags flags, - const dual_timestamp *display_ts) { + usec_t usec) { char buf[CONST_MAX(FORMAT_TIMESTAMP_MAX, 64U)]; - int r; assert(f); assert(j); - assert(display_ts); - if (!VALID_REALTIME(display_ts->realtime)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No valid realtime timestamp available"); + if (!VALID_REALTIME(usec)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No valid realtime timestamp available."); if (IN_SET(mode, OUTPUT_SHORT_FULL, OUTPUT_WITH_UNIT)) { const char *k; if (flags & OUTPUT_UTC) - k = format_timestamp_style(buf, sizeof(buf), display_ts->realtime, TIMESTAMP_UTC); + k = format_timestamp_style(buf, sizeof(buf), usec, TIMESTAMP_UTC); else - k = format_timestamp(buf, sizeof(buf), display_ts->realtime); + k = format_timestamp(buf, sizeof(buf), usec); if (!k) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Failed to format timestamp: %" PRIu64, display_ts->realtime); + "Failed to format timestamp: %" PRIu64, usec); } else { struct tm tm; time_t t; - t = (time_t) (display_ts->realtime / USEC_PER_SEC); + t = (time_t) (usec / USEC_PER_SEC); switch (mode) { case OUTPUT_SHORT_UNIX: - xsprintf(buf, "%10"PRI_TIME".%06"PRIu64, t, display_ts->realtime % USEC_PER_SEC); + xsprintf(buf, "%10"PRI_TIME".%06"PRIu64, t, usec % USEC_PER_SEC); break; case OUTPUT_SHORT_ISO: @@ -407,13 +402,11 @@ static int output_timestamp_realtime( size_t tail = strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%S", localtime_or_gmtime_r(&t, &tm, flags & OUTPUT_UTC)); if (tail == 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Failed to format ISO time"); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to format ISO time."); /* No usec in strftime, need to append */ if (mode == OUTPUT_SHORT_ISO_PRECISE) { - assert(ELEMENTSOF(buf) - tail >= 7); - snprintf(buf + tail, ELEMENTSOF(buf) - tail, ".%06"PRI_USEC, display_ts->realtime % USEC_PER_SEC); + assert_se(snprintf_ok(buf + tail, ELEMENTSOF(buf) - tail, ".%06"PRI_USEC, usec % USEC_PER_SEC)); tail += 7; } @@ -428,19 +421,12 @@ static int output_timestamp_realtime( if (strftime(buf, sizeof(buf), "%b %d %H:%M:%S", localtime_or_gmtime_r(&t, &tm, flags & OUTPUT_UTC)) <= 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Failed to format syslog time"); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to format syslog time."); if (mode == OUTPUT_SHORT_PRECISE) { - size_t k; - assert(sizeof(buf) > strlen(buf)); - k = sizeof(buf) - strlen(buf); - - r = snprintf(buf + strlen(buf), k, ".%06"PRIu64, display_ts->realtime % USEC_PER_SEC); - if (r <= 0 || (size_t) r >= k) /* too long? */ - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Failed to format precise time"); + if (!snprintf_ok(buf + strlen(buf), sizeof(buf) - strlen(buf), ".%06"PRIu64, usec % USEC_PER_SEC)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to format precise time."); } break; @@ -453,6 +439,77 @@ static int output_timestamp_realtime( return (int) strlen(buf); } +static void parse_display_realtime( + sd_journal *j, + const char *source_realtime, + const char *source_monotonic, + usec_t *ret) { + + usec_t t, s, u; + + assert(j); + assert(ret); + + /* First, try _SOURCE_REALTIME_TIMESTAMP. */ + if (source_realtime && safe_atou64(source_realtime, &t) >= 0 && VALID_REALTIME(t)) { + *ret = t; + return; + } + + /* Read realtime timestamp in the entry header. */ + if (sd_journal_get_realtime_usec(j, &t) < 0) { + *ret = USEC_INFINITY; + return; + } + + /* If _SOURCE_MONOTONIC_TIMESTAMP is provided, adjust the header timestamp. */ + if (source_monotonic && safe_atou64(source_monotonic, &s) >= 0 && VALID_MONOTONIC(s) && + sd_journal_get_monotonic_usec(j, &u, &(sd_id128_t) {}) >= 0) { + *ret = map_clock_usec_raw(t, u, s); + return; + } + + /* Otherwise, use the header timestamp as is. */ + *ret = t; +} + +static void parse_display_timestamp( + sd_journal *j, + const char *source_realtime, + const char *source_monotonic, + dual_timestamp *ret_display_ts, + sd_id128_t *ret_boot_id) { + + dual_timestamp header_ts = DUAL_TIMESTAMP_INFINITY, source_ts = DUAL_TIMESTAMP_INFINITY; + sd_id128_t boot_id = SD_ID128_NULL; + usec_t t; + + assert(j); + assert(ret_display_ts); + assert(ret_boot_id); + + if (source_realtime && safe_atou64(source_realtime, &t) >= 0 && VALID_REALTIME(t)) + source_ts.realtime = t; + + if (source_monotonic && safe_atou64(source_monotonic, &t) >= 0 && VALID_MONOTONIC(t)) + source_ts.monotonic = t; + + (void) sd_journal_get_realtime_usec(j, &header_ts.realtime); + (void) sd_journal_get_monotonic_usec(j, &header_ts.monotonic, &boot_id); + + /* Adjust timestamp if possible. */ + if (header_ts.realtime != USEC_INFINITY && header_ts.monotonic != USEC_INFINITY) { + if (source_ts.realtime == USEC_INFINITY && source_ts.monotonic != USEC_INFINITY) + source_ts.realtime = map_clock_usec_raw(header_ts.realtime, header_ts.monotonic, source_ts.monotonic); + else if (source_ts.realtime != USEC_INFINITY && source_ts.monotonic == USEC_INFINITY) + source_ts.monotonic = map_clock_usec_raw(header_ts.monotonic, header_ts.realtime, source_ts.realtime); + } + + ret_display_ts->realtime = source_ts.realtime != USEC_INFINITY ? source_ts.realtime : header_ts.realtime; + ret_display_ts->monotonic = source_ts.monotonic != USEC_INFINITY ? source_ts.monotonic : header_ts.monotonic; + *ret_boot_id = boot_id; +} + static int output_short( FILE *f, sd_journal *j, @@ -461,42 +518,43 @@ static int output_short( OutputFlags flags, const Set *output_fields, const size_t highlight[2], - const dual_timestamp *display_ts, - const sd_id128_t *boot_id, - const dual_timestamp *previous_display_ts, - const sd_id128_t *previous_boot_id) { + dual_timestamp *previous_display_ts, /* in and out, used only when mode is OUTPUT_SHORT_MONOTONIC, OUTPUT_SHORT_DELTA. */ + sd_id128_t *previous_boot_id) { /* in and out, used only when mode is OUTPUT_SHORT_MONOTONIC, OUTPUT_SHORT_DELTA. */ int r; const void *data; size_t length, n = 0; _cleanup_free_ char *hostname = NULL, *identifier = NULL, *comm = NULL, *pid = NULL, *fake_pid = NULL, *message = NULL, *priority = NULL, *transport = NULL, - *config_file = NULL, *unit = NULL, *user_unit = NULL, *documentation_url = NULL; + *config_file = NULL, *unit = NULL, *user_unit = NULL, *documentation_url = NULL, + *realtime = NULL, *monotonic = NULL; size_t hostname_len = 0, identifier_len = 0, comm_len = 0, pid_len = 0, fake_pid_len = 0, message_len = 0, priority_len = 0, transport_len = 0, config_file_len = 0, unit_len = 0, user_unit_len = 0, documentation_url_len = 0; + dual_timestamp display_ts; + sd_id128_t boot_id; int p = LOG_INFO; bool ellipsized = false, audit; const ParseFieldVec fields[] = { - PARSE_FIELD_VEC_ENTRY("_PID=", &pid, &pid_len), - PARSE_FIELD_VEC_ENTRY("_COMM=", &comm, &comm_len), - PARSE_FIELD_VEC_ENTRY("MESSAGE=", &message, &message_len), - PARSE_FIELD_VEC_ENTRY("PRIORITY=", &priority, &priority_len), - PARSE_FIELD_VEC_ENTRY("_TRANSPORT=", &transport, &transport_len), - PARSE_FIELD_VEC_ENTRY("_HOSTNAME=", &hostname, &hostname_len), - PARSE_FIELD_VEC_ENTRY("SYSLOG_PID=", &fake_pid, &fake_pid_len), - PARSE_FIELD_VEC_ENTRY("SYSLOG_IDENTIFIER=", &identifier, &identifier_len), - PARSE_FIELD_VEC_ENTRY("CONFIG_FILE=", &config_file, &config_file_len), - PARSE_FIELD_VEC_ENTRY("_SYSTEMD_UNIT=", &unit, &unit_len), - PARSE_FIELD_VEC_ENTRY("_SYSTEMD_USER_UNIT=", &user_unit, &user_unit_len), - PARSE_FIELD_VEC_ENTRY("DOCUMENTATION=", &documentation_url, &documentation_url_len), + PARSE_FIELD_VEC_ENTRY("_PID=", &pid, &pid_len ), + PARSE_FIELD_VEC_ENTRY("_COMM=", &comm, &comm_len ), + PARSE_FIELD_VEC_ENTRY("MESSAGE=", &message, &message_len ), + PARSE_FIELD_VEC_ENTRY("PRIORITY=", &priority, &priority_len ), + PARSE_FIELD_VEC_ENTRY("_TRANSPORT=", &transport, &transport_len ), + PARSE_FIELD_VEC_ENTRY("_HOSTNAME=", &hostname, &hostname_len ), + PARSE_FIELD_VEC_ENTRY("SYSLOG_PID=", &fake_pid, &fake_pid_len ), + PARSE_FIELD_VEC_ENTRY("SYSLOG_IDENTIFIER=", &identifier, &identifier_len ), + PARSE_FIELD_VEC_ENTRY("CONFIG_FILE=", &config_file, &config_file_len ), + PARSE_FIELD_VEC_ENTRY("_SYSTEMD_UNIT=", &unit, &unit_len ), + PARSE_FIELD_VEC_ENTRY("_SYSTEMD_USER_UNIT=", &user_unit, &user_unit_len ), + PARSE_FIELD_VEC_ENTRY("DOCUMENTATION=", &documentation_url, &documentation_url_len), + PARSE_FIELD_VEC_ENTRY("_SOURCE_REALTIME_TIMESTAMP=", &realtime, NULL ), + PARSE_FIELD_VEC_ENTRY("_SOURCE_MONOTONIC_TIMESTAMP=", &monotonic, NULL ), }; size_t highlight_shifted[] = {highlight ? highlight[0] : 0, highlight ? highlight[1] : 0}; assert(f); assert(j); - assert(display_ts); - assert(boot_id); assert(previous_display_ts); assert(previous_boot_id); @@ -523,6 +581,9 @@ static int output_short( return 0; } + if (identifier && set_contains(j->exclude_syslog_identifiers, identifier)) + return 0; + if (!(flags & OUTPUT_SHOW_ALL)) strip_tab_ansi(&message, &message_len, highlight_shifted); @@ -534,10 +595,14 @@ static int output_short( audit = streq_ptr(transport, "audit"); - if (IN_SET(mode, OUTPUT_SHORT_MONOTONIC, OUTPUT_SHORT_DELTA)) - r = output_timestamp_monotonic(f, mode, display_ts, boot_id, previous_display_ts, previous_boot_id); - else - r = output_timestamp_realtime(f, j, mode, flags, display_ts); + if (IN_SET(mode, OUTPUT_SHORT_MONOTONIC, OUTPUT_SHORT_DELTA)) { + parse_display_timestamp(j, realtime, monotonic, &display_ts, &boot_id); + r = output_timestamp_monotonic(f, mode, &display_ts, &boot_id, previous_display_ts, previous_boot_id); + } else { + usec_t usec; + parse_display_realtime(j, realtime, monotonic, &usec); + r = output_timestamp_realtime(f, j, mode, flags, usec); + } if (r < 0) return r; n += r; @@ -658,9 +723,48 @@ static int output_short( if (flags & OUTPUT_CATALOG) (void) print_catalog(f, j); + if (IN_SET(mode, OUTPUT_SHORT_MONOTONIC, OUTPUT_SHORT_DELTA)) { + *previous_display_ts = display_ts; + *previous_boot_id = boot_id; + } + return ellipsized; } +static int get_display_realtime(sd_journal *j, usec_t *ret) { + const void *data; + _cleanup_free_ char *realtime = NULL, *monotonic = NULL; + size_t length; + const ParseFieldVec message_fields[] = { + PARSE_FIELD_VEC_ENTRY("_SOURCE_REALTIME_TIMESTAMP=", &realtime, NULL), + PARSE_FIELD_VEC_ENTRY("_SOURCE_MONOTONIC_TIMESTAMP=", &monotonic, NULL), + }; + int r; + + assert(j); + assert(ret); + + JOURNAL_FOREACH_DATA_RETVAL(j, data, length, r) { + r = parse_fieldv(data, length, message_fields, ELEMENTSOF(message_fields)); + if (r < 0) + return r; + + if (realtime && monotonic) + break; + } + if (r < 0) + return r; + + (void) parse_display_realtime(j, realtime, monotonic, ret); + + /* Restart all data before */ + sd_journal_restart_data(j); + sd_journal_restart_unique(j); + sd_journal_restart_fields(j); + + return 0; +} + static int output_verbose( FILE *f, sd_journal *j, @@ -669,35 +773,38 @@ static int output_verbose( OutputFlags flags, const Set *output_fields, const size_t highlight[2], - const dual_timestamp *display_ts, - const sd_id128_t *boot_id, - const dual_timestamp *previous_display_ts, - const sd_id128_t *previous_boot_id) { + dual_timestamp *previous_display_ts, /* unused */ + sd_id128_t *previous_boot_id) { /* unused */ const void *data; size_t length; _cleanup_free_ char *cursor = NULL; char buf[FORMAT_TIMESTAMP_MAX + 7]; const char *timestamp; + usec_t usec; int r; assert(f); assert(j); - assert(display_ts); - assert(boot_id); - assert(previous_display_ts); - assert(previous_boot_id); (void) sd_journal_set_data_threshold(j, 0); - if (!VALID_REALTIME(display_ts->realtime)) + r = get_display_realtime(j, &usec); + if (IN_SET(r, -EBADMSG, -EADDRNOTAVAIL)) { + log_debug_errno(r, "Skipping message we can't read: %m"); + return 0; + } + if (r < 0) + return log_error_errno(r, "Failed to get journal fields: %m"); + + if (!VALID_REALTIME(usec)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No valid realtime timestamp available"); r = sd_journal_get_cursor(j, &cursor); if (r < 0) return log_error_errno(r, "Failed to get cursor: %m"); - timestamp = format_timestamp_style(buf, sizeof buf, display_ts->realtime, + timestamp = format_timestamp_style(buf, sizeof buf, usec, flags & OUTPUT_UTC ? TIMESTAMP_US_UTC : TIMESTAMP_US); fprintf(f, "%s%s%s %s[%s]%s\n", timestamp && (flags & OUTPUT_COLOR) ? ANSI_UNDERLINE : "", @@ -786,10 +893,8 @@ static int output_export( OutputFlags flags, const Set *output_fields, const size_t highlight[2], - const dual_timestamp *display_ts, - const sd_id128_t *boot_id, - const dual_timestamp *previous_display_ts, - const sd_id128_t *previous_boot_id) { + dual_timestamp *previous_display_ts, /* unused */ + sd_id128_t *previous_boot_id) { /* unused */ sd_id128_t journal_boot_id, seqnum_id; _cleanup_free_ char *cursor = NULL; @@ -800,10 +905,6 @@ static int output_export( int r; assert(j); - assert(display_ts); - assert(boot_id); - assert(previous_display_ts); - assert(previous_boot_id); (void) sd_journal_set_data_threshold(j, 0); @@ -1055,10 +1156,8 @@ static int output_json( OutputFlags flags, const Set *output_fields, const size_t highlight[2], - const dual_timestamp *display_ts, - const sd_id128_t *boot_id, - const dual_timestamp *previous_display_ts, - const sd_id128_t *previous_boot_id) { + dual_timestamp *previous_display_ts, /* unused */ + sd_id128_t *previous_boot_id) { /* unused */ char usecbuf[CONST_MAX(DECIMAL_STR_MAX(usec_t), DECIMAL_STR_MAX(uint64_t))]; _cleanup_(json_variant_unrefp) JsonVariant *object = NULL; @@ -1073,10 +1172,6 @@ static int output_json( int r; assert(j); - assert(display_ts); - assert(boot_id); - assert(previous_display_ts); - assert(previous_boot_id); (void) sd_journal_set_data_threshold(j, flags & OUTPUT_SHOW_ALL ? 0 : JSON_THRESHOLD); @@ -1238,20 +1333,14 @@ static int output_cat( OutputFlags flags, const Set *output_fields, const size_t highlight[2], - const dual_timestamp *display_ts, - const sd_id128_t *boot_id, - const dual_timestamp *previous_display_ts, - const sd_id128_t *previous_boot_id) { + dual_timestamp *previous_display_ts, /* unused */ + sd_id128_t *previous_boot_id) { /* unused */ int r, prio = LOG_INFO; const char *field; assert(j); assert(f); - assert(display_ts); - assert(boot_id); - assert(previous_display_ts); - assert(previous_boot_id); (void) sd_journal_set_data_threshold(j, 0); @@ -1291,63 +1380,6 @@ static int output_cat( return 0; } -static int get_display_timestamp( - sd_journal *j, - dual_timestamp *ret_display_ts, - sd_id128_t *ret_boot_id) { - - const void *data; - _cleanup_free_ char *realtime = NULL, *monotonic = NULL; - size_t length = 0, realtime_len = 0, monotonic_len = 0; - const ParseFieldVec message_fields[] = { - PARSE_FIELD_VEC_ENTRY("_SOURCE_REALTIME_TIMESTAMP=", &realtime, &realtime_len), - PARSE_FIELD_VEC_ENTRY("_SOURCE_MONOTONIC_TIMESTAMP=", &monotonic, &monotonic_len), - }; - int r; - bool realtime_good = false, monotonic_good = false, boot_id_good = false; - - assert(j); - assert(ret_display_ts); - assert(ret_boot_id); - - JOURNAL_FOREACH_DATA_RETVAL(j, data, length, r) { - r = parse_fieldv(data, length, message_fields, ELEMENTSOF(message_fields)); - if (r < 0) - return r; - - if (realtime && monotonic) - break; - } - if (r < 0) - return r; - - if (realtime) - realtime_good = safe_atou64(realtime, &ret_display_ts->realtime) >= 0; - if (!realtime_good || !VALID_REALTIME(ret_display_ts->realtime)) - realtime_good = sd_journal_get_realtime_usec(j, &ret_display_ts->realtime) >= 0; - if (!realtime_good) - ret_display_ts->realtime = USEC_INFINITY; - - if (monotonic) - monotonic_good = safe_atou64(monotonic, &ret_display_ts->monotonic) >= 0; - if (!monotonic_good || !VALID_MONOTONIC(ret_display_ts->monotonic)) - monotonic_good = boot_id_good = sd_journal_get_monotonic_usec(j, &ret_display_ts->monotonic, ret_boot_id) >= 0; - if (!monotonic_good) - ret_display_ts->monotonic = USEC_INFINITY; - - if (!boot_id_good) - boot_id_good = sd_journal_get_monotonic_usec(j, NULL, ret_boot_id) >= 0; - if (!boot_id_good) - *ret_boot_id = SD_ID128_NULL; - - /* Restart all data before */ - sd_journal_restart_data(j); - sd_journal_restart_unique(j); - sd_journal_restart_fields(j); - - return 0; -} - typedef int (*output_func_t)( FILE *f, sd_journal *j, @@ -1356,10 +1388,8 @@ typedef int (*output_func_t)( OutputFlags flags, const Set *output_fields, const size_t highlight[2], - const dual_timestamp *display_ts, - const sd_id128_t *boot_id, - const dual_timestamp *previous_display_ts, - const sd_id128_t *previous_boot_id); + dual_timestamp *previous_display_ts, + sd_id128_t *previous_boot_id); static output_func_t output_funcs[_OUTPUT_MODE_MAX] = { @@ -1393,8 +1423,6 @@ int show_journal_entry( dual_timestamp *previous_display_ts, sd_id128_t *previous_boot_id) { - dual_timestamp display_ts = DUAL_TIMESTAMP_NULL; - sd_id128_t boot_id = SD_ID128_NULL; int r; assert(mode >= 0); @@ -1405,14 +1433,6 @@ int show_journal_entry( if (n_columns <= 0) n_columns = columns(); - r = get_display_timestamp(j, &display_ts, &boot_id); - if (IN_SET(r, -EBADMSG, -EADDRNOTAVAIL)) { - log_debug_errno(r, "Skipping message we can't read: %m"); - return 0; - } - if (r < 0) - return log_error_errno(r, "Failed to get journal fields: %m"); - r = output_funcs[mode]( f, j, @@ -1421,15 +1441,9 @@ int show_journal_entry( flags, output_fields, highlight, - &display_ts, - &boot_id, previous_display_ts, previous_boot_id); - /* Store timestamp and boot ID for next iteration */ - *previous_display_ts = display_ts; - *previous_boot_id = boot_id; - if (ellipsized && r > 0) *ellipsized = true; @@ -1570,202 +1584,115 @@ int show_journal( } int add_matches_for_unit(sd_journal *j, const char *unit) { - const char *m1, *m2, *m3, *m4; int r; assert(j); assert(unit); - m1 = strjoina("_SYSTEMD_UNIT=", unit); - m2 = strjoina("COREDUMP_UNIT=", unit); - m3 = strjoina("UNIT=", unit); - m4 = strjoina("OBJECT_SYSTEMD_UNIT=", unit); - - (void)( + (void) ( /* Look for messages from the service itself */ - (r = sd_journal_add_match(j, m1, 0)) || + (r = journal_add_match_pair(j, "_SYSTEMD_UNIT", unit)) || /* Look for coredumps of the service */ (r = sd_journal_add_disjunction(j)) || - (r = sd_journal_add_match(j, "MESSAGE_ID=fc2e22bc6ee647b6b90729ab34a250b1", 0)) || - (r = sd_journal_add_match(j, "_UID=0", 0)) || - (r = sd_journal_add_match(j, m2, 0)) || + (r = sd_journal_add_match(j, "MESSAGE_ID=fc2e22bc6ee647b6b90729ab34a250b1", SIZE_MAX)) || + (r = sd_journal_add_match(j, "_UID=0", SIZE_MAX)) || + (r = journal_add_match_pair(j, "COREDUMP_UNIT", unit)) || /* Look for messages from PID 1 about this service */ (r = sd_journal_add_disjunction(j)) || - (r = sd_journal_add_match(j, "_PID=1", 0)) || - (r = sd_journal_add_match(j, m3, 0)) || + (r = sd_journal_add_match(j, "_PID=1", SIZE_MAX)) || + (r = journal_add_match_pair(j, "UNIT", unit)) || /* Look for messages from authorized daemons about this service */ (r = sd_journal_add_disjunction(j)) || - (r = sd_journal_add_match(j, "_UID=0", 0)) || - (r = sd_journal_add_match(j, m4, 0)) + (r = sd_journal_add_match(j, "_UID=0", SIZE_MAX)) || + (r = journal_add_match_pair(j, "OBJECT_SYSTEMD_UNIT", unit)) ); - if (r == 0 && endswith(unit, ".slice")) { - const char *m5; - - m5 = strjoina("_SYSTEMD_SLICE=", unit); - + if (r == 0 && endswith(unit, ".slice")) /* Show all messages belonging to a slice */ - (void)( + (void) ( (r = sd_journal_add_disjunction(j)) || - (r = sd_journal_add_match(j, m5, 0)) - ); - } + (r = journal_add_match_pair(j, "_SYSTEMD_SLICE", unit)) + ); return r; } -int add_matches_for_user_unit(sd_journal *j, const char *unit, uid_t uid) { +int add_matches_for_user_unit(sd_journal *j, const char *unit) { + uid_t uid = getuid(); int r; - char *m1, *m2, *m3, *m4; - char muid[sizeof("_UID=") + DECIMAL_STR_MAX(uid_t)]; assert(j); assert(unit); - m1 = strjoina("_SYSTEMD_USER_UNIT=", unit); - m2 = strjoina("USER_UNIT=", unit); - m3 = strjoina("COREDUMP_USER_UNIT=", unit); - m4 = strjoina("OBJECT_SYSTEMD_USER_UNIT=", unit); - sprintf(muid, "_UID="UID_FMT, uid); - (void) ( /* Look for messages from the user service itself */ - (r = sd_journal_add_match(j, m1, 0)) || - (r = sd_journal_add_match(j, muid, 0)) || + (r = journal_add_match_pair(j, "_SYSTEMD_USER_UNIT", unit)) || + (r = journal_add_matchf(j, "_UID="UID_FMT, uid)) || /* Look for messages from systemd about this service */ (r = sd_journal_add_disjunction(j)) || - (r = sd_journal_add_match(j, m2, 0)) || - (r = sd_journal_add_match(j, muid, 0)) || + (r = journal_add_match_pair(j, "USER_UNIT", unit)) || + (r = journal_add_matchf(j, "_UID="UID_FMT, uid)) || /* Look for coredumps of the service */ (r = sd_journal_add_disjunction(j)) || - (r = sd_journal_add_match(j, m3, 0)) || - (r = sd_journal_add_match(j, muid, 0)) || - (r = sd_journal_add_match(j, "_UID=0", 0)) || + (r = journal_add_match_pair(j, "COREDUMP_USER_UNIT", unit)) || + (r = journal_add_matchf(j, "_UID="UID_FMT, uid)) || + (r = sd_journal_add_match(j, "_UID=0", SIZE_MAX)) || /* Look for messages from authorized daemons about this service */ (r = sd_journal_add_disjunction(j)) || - (r = sd_journal_add_match(j, m4, 0)) || - (r = sd_journal_add_match(j, muid, 0)) || - (r = sd_journal_add_match(j, "_UID=0", 0)) + (r = journal_add_match_pair(j, "OBJECT_SYSTEMD_USER_UNIT", unit)) || + (r = journal_add_matchf(j, "_UID="UID_FMT, uid)) || + (r = sd_journal_add_match(j, "_UID=0", SIZE_MAX)) ); - if (r == 0 && endswith(unit, ".slice")) { - const char *m5; - - m5 = strjoina("_SYSTEMD_USER_SLICE=", unit); - + if (r == 0 && endswith(unit, ".slice")) /* Show all messages belonging to a slice */ - (void)( + (void) ( (r = sd_journal_add_disjunction(j)) || - (r = sd_journal_add_match(j, m5, 0)) || - (r = sd_journal_add_match(j, muid, 0)) - ); - } + (r = journal_add_match_pair(j, "_SYSTEMD_USER_SLICE", unit)) || + (r = journal_add_matchf(j, "_UID="UID_FMT, uid)) + ); return r; } -static int get_boot_id_for_machine(const char *machine, sd_id128_t *boot_id) { - _cleanup_close_pair_ int pair[2] = EBADF_PAIR; - _cleanup_close_ int pidnsfd = -EBADF, mntnsfd = -EBADF, rootfd = -EBADF; - char buf[SD_ID128_UUID_STRING_MAX]; - pid_t pid, child; - ssize_t k; +int add_match_boot_id(sd_journal *j, sd_id128_t id) { int r; - assert(machine); - assert(boot_id); - - r = container_get_leader(machine, &pid); - if (r < 0) - return r; - - r = namespace_open(pid, &pidnsfd, &mntnsfd, NULL, NULL, &rootfd); - if (r < 0) - return r; - - if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) < 0) - return -errno; - - r = namespace_fork("(sd-bootidns)", "(sd-bootid)", NULL, 0, FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL, - pidnsfd, mntnsfd, -1, -1, rootfd, &child); - if (r < 0) - return r; - if (r == 0) { - int fd; - - pair[0] = safe_close(pair[0]); - - fd = open("/proc/sys/kernel/random/boot_id", O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (fd < 0) - _exit(EXIT_FAILURE); + assert(j); - r = loop_read_exact(fd, buf, 36, false); - safe_close(fd); + if (sd_id128_is_null(id)) { + r = sd_id128_get_boot(&id); if (r < 0) - _exit(EXIT_FAILURE); - - k = send(pair[1], buf, 36, MSG_NOSIGNAL); - if (k != 36) - _exit(EXIT_FAILURE); - - _exit(EXIT_SUCCESS); + return log_error_errno(r, "Failed to get boot ID: %m"); } - pair[1] = safe_close(pair[1]); - - r = wait_for_terminate_and_check("(sd-bootidns)", child, 0); - if (r < 0) - return r; - if (r != EXIT_SUCCESS) - return -EIO; - - k = recv(pair[0], buf, 36, 0); - if (k != 36) - return -EIO; - - buf[36] = 0; - r = sd_id128_from_string(buf, boot_id); + r = journal_add_match_pair(j, "_BOOT_ID", SD_ID128_TO_STRING(id)); if (r < 0) - return r; + return log_error_errno(r, "Failed to add match: %m"); return 0; } -int add_match_boot_id(sd_journal *j, sd_id128_t id) { - char match[STRLEN("_BOOT_ID=") + SD_ID128_STRING_MAX]; - - assert(j); - assert(!sd_id128_is_null(id)); - - sd_id128_to_string(id, stpcpy(match, "_BOOT_ID=")); - return sd_journal_add_match(j, match, strlen(match)); -} - int add_match_this_boot(sd_journal *j, const char *machine) { sd_id128_t boot_id; int r; assert(j); - if (machine) { - r = get_boot_id_for_machine(machine, &boot_id); - if (r < 0) - return log_error_errno(r, "Failed to get boot id of container %s: %m", machine); - } else { - r = sd_id128_get_boot(&boot_id); - if (r < 0) - return log_error_errno(r, "Failed to get boot id: %m"); - } + r = id128_get_boot_for_machine(machine, &boot_id); + if (r < 0) + return log_error_errno(r, "Failed to get boot ID%s%s: %m", + isempty(machine) ? "" : " of container ", strempty(machine)); r = add_match_boot_id(j, boot_id); if (r < 0) - return log_error_errno(r, "Failed to add match: %m"); + return r; r = sd_journal_add_conjunction(j); if (r < 0) @@ -1782,7 +1709,6 @@ int show_journal_by_unit( unsigned n_columns, usec_t not_before, unsigned how_many, - uid_t uid, OutputFlags flags, int journal_open_flags, bool system_unit, @@ -1798,14 +1724,17 @@ int show_journal_by_unit( if (how_many <= 0) return 0; - r = sd_journal_open_namespace(&j, log_namespace, journal_open_flags | SD_JOURNAL_INCLUDE_DEFAULT_NAMESPACE); + r = sd_journal_open_namespace(&j, log_namespace, + journal_open_flags | + SD_JOURNAL_INCLUDE_DEFAULT_NAMESPACE | + SD_JOURNAL_ASSUME_IMMUTABLE); if (r < 0) return log_error_errno(r, "Failed to open journal: %m"); if (system_unit) r = add_matches_for_unit(j, unit); else - r = add_matches_for_user_unit(j, unit, uid); + r = add_matches_for_user_unit(j, unit); if (r < 0) return log_error_errno(r, "Failed to add unit matches: %m"); @@ -1861,6 +1790,7 @@ static int discover_next_boot( if (r < 0) return r; if (r == 0) { + sd_journal_flush_matches(j); *ret = (BootId) {}; return 0; /* End of journal, yay. */ } @@ -1911,7 +1841,7 @@ static int discover_next_boot( goto try_again; } - r = sd_journal_get_realtime_usec(j, &boot.first_usec); + r = sd_journal_get_realtime_usec(j, advance_older ? &boot.last_usec : &boot.first_usec); if (r < 0) return r; @@ -1933,7 +1863,7 @@ static int discover_next_boot( goto try_again; } - r = sd_journal_get_realtime_usec(j, &boot.last_usec); + r = sd_journal_get_realtime_usec(j, advance_older ? &boot.first_usec : &boot.last_usec); if (r < 0) return r; @@ -1979,37 +1909,9 @@ static int discover_next_boot( } } -int journal_find_boot_by_id(sd_journal *j, sd_id128_t boot_id) { - int r; - - assert(j); - assert(!sd_id128_is_null(boot_id)); - - sd_journal_flush_matches(j); - - r = add_match_boot_id(j, boot_id); - if (r < 0) - return r; - - r = sd_journal_seek_head(j); /* seek to oldest */ - if (r < 0) - return r; - - r = sd_journal_next(j); /* read the oldest entry */ - if (r < 0) - return r; - - /* At this point the read pointer is positioned at the oldest occurrence of the reference boot ID. - * After flushing the matches, one more invocation of _previous() will hence place us at the - * following entry, which must then have an older boot ID */ - - sd_journal_flush_matches(j); - return r > 0; -} - -int journal_find_boot_by_offset(sd_journal *j, int offset, sd_id128_t *ret) { +int journal_find_boot(sd_journal *j, sd_id128_t boot_id, int offset, sd_id128_t *ret) { bool advance_older; - int r; + int r, offset_start; assert(j); assert(ret); @@ -2018,21 +1920,52 @@ int journal_find_boot_by_offset(sd_journal *j, int offset, sd_id128_t *ret) { * (chronological) first boot in the journal. */ advance_older = offset <= 0; - if (advance_older) - r = sd_journal_seek_tail(j); /* seek to newest */ - else - r = sd_journal_seek_head(j); /* seek to oldest */ - if (r < 0) - return r; + sd_journal_flush_matches(j); - /* No sd_journal_next()/_previous() here. - * - * At this point the read pointer is positioned after the newest/before the oldest entry in the whole - * journal. The next invocation of _previous()/_next() will hence position us at the newest/oldest - * entry we have. */ + if (!sd_id128_is_null(boot_id)) { + r = add_match_boot_id(j, boot_id); + if (r < 0) + return r; - sd_id128_t boot_id = SD_ID128_NULL; - for (int off = !advance_older; ; off += advance_older ? -1 : 1) { + if (advance_older) + r = sd_journal_seek_head(j); /* seek to oldest */ + else + r = sd_journal_seek_tail(j); /* seek to newest */ + if (r < 0) + return r; + + r = sd_journal_step_one(j, advance_older); + if (r < 0) + return r; + if (r == 0) { + sd_journal_flush_matches(j); + *ret = SD_ID128_NULL; + return false; + } + if (offset == 0) { + /* If boot ID is specified without an offset, then let's short cut the loop below. */ + sd_journal_flush_matches(j); + *ret = boot_id; + return true; + } + + offset_start = advance_older ? -1 : 1; + } else { + if (advance_older) + r = sd_journal_seek_tail(j); /* seek to newest */ + else + r = sd_journal_seek_head(j); /* seek to oldest */ + if (r < 0) + return r; + + offset_start = advance_older ? 0 : 1; + } + + /* At this point the cursor is positioned at the newest/oldest entry of the reference boot ID if + * specified, or whole journal otherwise. The next invocation of _previous()/_next() will hence + * position us at the newest/oldest entry we have. */ + + for (int off = offset_start; ; off += advance_older ? -1 : 1) { BootId boot; r = discover_next_boot(j, boot_id, advance_older, &boot); @@ -2046,15 +1979,20 @@ int journal_find_boot_by_offset(sd_journal *j, int offset, sd_id128_t *ret) { boot_id = boot.id; log_debug("Found boot ID %s by offset %i", SD_ID128_TO_STRING(boot_id), off); - if (off == offset) - break; + if (off == offset) { + *ret = boot_id; + return true; + } } - - *ret = boot_id; - return true; } -int journal_get_boots(sd_journal *j, BootId **ret_boots, size_t *ret_n_boots) { +int journal_get_boots( + sd_journal *j, + bool advance_older, + size_t max_ids, + BootId **ret_boots, + size_t *ret_n_boots) { + _cleanup_free_ BootId *boots = NULL; size_t n_boots = 0; int r; @@ -2063,7 +2001,12 @@ int journal_get_boots(sd_journal *j, BootId **ret_boots, size_t *ret_n_boots) { assert(ret_boots); assert(ret_n_boots); - r = sd_journal_seek_head(j); /* seek to oldest */ + sd_journal_flush_matches(j); + + if (advance_older) + r = sd_journal_seek_tail(j); /* seek to newest */ + else + r = sd_journal_seek_head(j); /* seek to oldest */ if (r < 0) return r; @@ -2076,7 +2019,10 @@ int journal_get_boots(sd_journal *j, BootId **ret_boots, size_t *ret_n_boots) { for (;;) { BootId boot; - r = discover_next_boot(j, previous_boot_id, /* advance_older = */ false, &boot); + if (n_boots >= max_ids) + break; + + r = discover_next_boot(j, previous_boot_id, advance_older, &boot); if (r < 0) return r; if (r == 0) @@ -2090,10 +2036,8 @@ int journal_get_boots(sd_journal *j, BootId **ret_boots, size_t *ret_n_boots) { * Exiting as otherwise this problem would cause an infinite loop. */ goto finish; - if (!GREEDY_REALLOC(boots, n_boots + 1)) + if (!GREEDY_REALLOC_APPEND(boots, n_boots, &boot, 1)) return -ENOMEM; - - boots[n_boots++] = boot; } finish: diff --git a/src/shared/logs-show.h b/src/shared/logs-show.h index 3a8ce8b..7e7b2af 100644 --- a/src/shared/logs-show.h +++ b/src/shared/logs-show.h @@ -49,8 +49,7 @@ int add_matches_for_unit( int add_matches_for_user_unit( sd_journal *j, - const char *unit, - uid_t uid); + const char *unit); int show_journal_by_unit( FILE *f, @@ -60,7 +59,6 @@ int show_journal_by_unit( unsigned n_columns, usec_t not_before, unsigned how_many, - uid_t uid, OutputFlags flags, int journal_open_flags, bool system_unit, @@ -72,6 +70,10 @@ void json_escape( size_t l, OutputFlags flags); -int journal_find_boot_by_id(sd_journal *j, sd_id128_t boot_id); -int journal_find_boot_by_offset(sd_journal *j, int offset, sd_id128_t *ret); -int journal_get_boots(sd_journal *j, BootId **ret_boots, size_t *ret_n_boots); +int journal_find_boot(sd_journal *j, sd_id128_t boot_id, int offset, sd_id128_t *ret); +int journal_get_boots( + sd_journal *j, + bool advance_older, + size_t max_ids, + BootId **ret_boots, + size_t *ret_n_boots); diff --git a/src/shared/loop-util.c b/src/shared/loop-util.c index 6d55df7..e295eb2 100644 --- a/src/shared/loop-util.c +++ b/src/shared/loop-util.c @@ -57,21 +57,6 @@ static int loop_is_bound(int fd) { return true; /* bound! */ } -static int get_current_uevent_seqnum(uint64_t *ret) { - _cleanup_free_ char *p = NULL; - int r; - - r = read_full_virtual_file("/sys/kernel/uevent_seqnum", &p, NULL); - if (r < 0) - return log_debug_errno(r, "Failed to read current uevent sequence number: %m"); - - r = safe_atou64(strstrip(p), ret); - if (r < 0) - return log_debug_errno(r, "Failed to parse current uevent sequence number: %s", p); - - return 0; -} - static int open_lock_fd(int primary_fd, int operation) { _cleanup_close_ int lock_fd = -EBADF; @@ -149,8 +134,9 @@ static int loop_configure_verify(int fd, const struct loop_config *c) { * effect hence. And if not use classic LOOP_SET_STATUS64. */ uint64_t z; - if (ioctl(fd, BLKGETSIZE64, &z) < 0) - return -errno; + r = blockdev_get_device_size(fd, &z); + if (r < 0) + return r; if (z != c->info.lo_sizelimit) { log_debug("LOOP_CONFIGURE is broken, doesn't honour .info.lo_sizelimit. Falling back to LOOP_SET_STATUS64."); @@ -265,8 +251,7 @@ static int loop_configure( _cleanup_(cleanup_clear_loop_close) int loop_with_fd = -EBADF; /* This must be declared before lock_fd. */ _cleanup_close_ int fd = -EBADF, lock_fd = -EBADF; _cleanup_free_ char *node = NULL; - uint64_t diskseq = 0, seqnum = UINT64_MAX; - usec_t timestamp = USEC_INFINITY; + uint64_t diskseq = 0; dev_t devno; int r; @@ -326,18 +311,6 @@ static int loop_configure( "Removed partitions on the loopback block device."); if (!loop_configure_broken) { - /* Acquire uevent seqnum immediately before attaching the loopback device. This allows - * callers to ignore all uevents with a seqnum before this one, if they need to associate - * uevent with this attachment. Doing so isn't race-free though, as uevents that happen in - * the window between this reading of the seqnum, and the LOOP_CONFIGURE call might still be - * mistaken as originating from our attachment, even though might be caused by an earlier - * use. But doing this at least shortens the race window a bit. */ - r = get_current_uevent_seqnum(&seqnum); - if (r < 0) - return log_device_debug_errno(dev, r, "Failed to get the current uevent seqnum: %m"); - - timestamp = now(CLOCK_MONOTONIC); - if (ioctl(fd, LOOP_CONFIGURE, c) < 0) { /* Do fallback only if LOOP_CONFIGURE is not supported, propagate all other * errors. Note that the kernel is weird: non-existing ioctls currently return EINVAL @@ -369,13 +342,6 @@ static int loop_configure( } if (loop_configure_broken) { - /* Let's read the seqnum again, to shorten the window. */ - r = get_current_uevent_seqnum(&seqnum); - if (r < 0) - return log_device_debug_errno(dev, r, "Failed to get the current uevent seqnum: %m"); - - timestamp = now(CLOCK_MONOTONIC); - if (ioctl(fd, LOOP_SET_FD, c->fd) < 0) return log_device_debug_errno(dev, errno, "ioctl(LOOP_SET_FD) failed: %m"); @@ -404,6 +370,11 @@ static int loop_configure( assert_not_reached(); } + uint64_t device_size; + r = blockdev_get_device_size(loop_with_fd, &device_size); + if (r < 0) + return log_device_debug_errno(dev, r, "Failed to get loopback device size: %m"); + LoopDevice *d = new(LoopDevice, 1); if (!d) return log_oom_debug(); @@ -417,9 +388,9 @@ static int loop_configure( .devno = devno, .dev = TAKE_PTR(dev), .diskseq = diskseq, - .uevent_seqnum_not_before = seqnum, - .timestamp_not_before = timestamp, .sector_size = c->block_size, + .device_size = device_size, + .created = true, }; *ret = TAKE_PTR(d); @@ -944,6 +915,11 @@ int loop_device_open( if (r < 0) return r; + uint64_t device_size; + r = blockdev_get_device_size(fd, &device_size); + if (r < 0) + return r; + r = sd_device_get_devnum(dev, &devnum); if (r < 0) return r; @@ -973,9 +949,9 @@ int loop_device_open( .relinquished = true, /* It's not ours, don't try to destroy it when this object is freed */ .devno = devnum, .diskseq = diskseq, - .uevent_seqnum_not_before = UINT64_MAX, - .timestamp_not_before = USEC_INFINITY, .sector_size = sector_size, + .device_size = device_size, + .created = false, }; *ret = d; @@ -1057,8 +1033,9 @@ static int resize_partition(int partition_fd, uint64_t offset, uint64_t size) { return -EINVAL; current_offset *= 512U; - if (ioctl(partition_fd, BLKGETSIZE64, ¤t_size) < 0) - return -EINVAL; + r = blockdev_get_device_size(partition_fd, ¤t_size); + if (r < 0) + return r; if (size == UINT64_MAX && offset == UINT64_MAX) return 0; diff --git a/src/shared/loop-util.h b/src/shared/loop-util.h index d77c314..4f56317 100644 --- a/src/shared/loop-util.h +++ b/src/shared/loop-util.h @@ -22,12 +22,12 @@ struct LoopDevice { sd_device *dev; char *backing_file; bool relinquished; + bool created; /* If we created the device */ dev_t backing_devno; /* The backing file's dev_t */ ino_t backing_inode; /* The backing file's ino_t */ uint64_t diskseq; /* Block device sequence number, monothonically incremented by the kernel on create/attach, or 0 if we don't know */ - uint64_t uevent_seqnum_not_before; /* uevent sequm right before we attached the loopback device, or UINT64_MAX if we don't know */ - usec_t timestamp_not_before; /* CLOCK_MONOTONIC timestamp taken immediately before attaching the loopback device, or USEC_INFINITY if we don't know */ uint32_t sector_size; + uint64_t device_size; }; /* Returns true if LoopDevice object is not actually a loopback device but some other block device we just wrap */ diff --git a/src/shared/loopback-setup.c b/src/shared/loopback-setup.c index a02baf8..84d7234 100644 --- a/src/shared/loopback-setup.c +++ b/src/shared/loopback-setup.c @@ -60,7 +60,7 @@ static int start_loopback(sd_netlink *rtnl, struct state *s) { if (r < 0) return r; - s->n_messages ++; + s->n_messages++; return 0; } @@ -95,7 +95,7 @@ static int add_ipv4_address(sd_netlink *rtnl, struct state *s) { if (r < 0) return r; - s->n_messages ++; + s->n_messages++; return 0; } @@ -136,7 +136,7 @@ static int add_ipv6_address(sd_netlink *rtnl, struct state *s) { if (r < 0) return r; - s->n_messages ++; + s->n_messages++; return 0; } diff --git a/src/shared/machine-credential.c b/src/shared/machine-credential.c index 17f7afc..c1e76e4 100644 --- a/src/shared/machine-credential.c +++ b/src/shared/machine-credential.c @@ -19,76 +19,82 @@ static void machine_credential_done(MachineCredential *cred) { cred->size = 0; } -void machine_credential_free_all(MachineCredential *creds, size_t n) { - assert(creds || n == 0); +void machine_credential_context_done(MachineCredentialContext *ctx) { + assert(ctx); - FOREACH_ARRAY(cred, creds, n) + FOREACH_ARRAY(cred, ctx->credentials, ctx->n_credentials) machine_credential_done(cred); - free(creds); + free(ctx->credentials); } -int machine_credential_set(MachineCredential **credentials, size_t *n_credentials, const char *cred_string) { - _cleanup_free_ char *word = NULL, *data = NULL; +bool machine_credentials_contains(const MachineCredentialContext *ctx, const char *id) { + assert(ctx); + assert(id); + + FOREACH_ARRAY(cred, ctx->credentials, ctx->n_credentials) + if (streq(cred->id, id)) + return true; + + return false; +} + +int machine_credential_set(MachineCredentialContext *ctx, const char *cred_str) { + _cleanup_(machine_credential_done) MachineCredential cred = {}; ssize_t l; int r; - const char *p = ASSERT_PTR(cred_string); - assert(credentials && n_credentials); - assert(*credentials || *n_credentials == 0); + assert(ctx); - r = extract_first_word(&p, &word, ":", EXTRACT_DONT_COALESCE_SEPARATORS); + const char *p = ASSERT_PTR(cred_str); + + r = extract_first_word(&p, &cred.id, ":", EXTRACT_DONT_COALESCE_SEPARATORS); if (r < 0) return log_error_errno(r, "Failed to parse --set-credential= parameter: %m"); if (r == 0 || !p) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Missing value for --set-credential=: %s", cred_string); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Missing value for --set-credential=: %s", cred_str); - if (!credential_name_valid(word)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Credential name is not valid: %s", word); + if (!credential_name_valid(cred.id)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Credential name is not valid: %s", cred.id); - FOREACH_ARRAY(cred, *credentials, *n_credentials) - if (streq(cred->id, word)) - return log_error_errno(SYNTHETIC_ERRNO(EEXIST), "Duplicate credential '%s', refusing.", word); + if (machine_credentials_contains(ctx, cred.id)) + return log_error_errno(SYNTHETIC_ERRNO(EEXIST), "Duplicate credential '%s', refusing.", cred.id); - l = cunescape(p, UNESCAPE_ACCEPT_NUL, &data); + l = cunescape(p, UNESCAPE_ACCEPT_NUL, &cred.data); if (l < 0) return log_error_errno(l, "Failed to unescape credential data: %s", p); + cred.size = l; - if (!GREEDY_REALLOC(*credentials, *n_credentials + 1)) + if (!GREEDY_REALLOC(ctx->credentials, ctx->n_credentials + 1)) return log_oom(); - (*credentials)[(*n_credentials)++] = (MachineCredential) { - .id = TAKE_PTR(word), - .data = TAKE_PTR(data), - .size = l, - }; + ctx->credentials[ctx->n_credentials++] = TAKE_STRUCT(cred); return 0; } -int machine_credential_load(MachineCredential **credentials, size_t *n_credentials, const char *cred_path) { +int machine_credential_load(MachineCredentialContext *ctx, const char *cred_path) { + _cleanup_(machine_credential_done) MachineCredential cred = {}; + _cleanup_free_ char *path_alloc = NULL; ReadFullFileFlags flags = READ_FULL_FILE_SECURE; - _cleanup_(erase_and_freep) char *data = NULL; - _cleanup_free_ char *word = NULL, *j = NULL; - const char *p = ASSERT_PTR(cred_path); - size_t size; int r; - assert(credentials && n_credentials); - assert(*credentials || *n_credentials == 0); + assert(ctx); + + const char *p = ASSERT_PTR(cred_path); - r = extract_first_word(&p, &word, ":", EXTRACT_DONT_COALESCE_SEPARATORS); + r = extract_first_word(&p, &cred.id, ":", EXTRACT_DONT_COALESCE_SEPARATORS); if (r < 0) return log_error_errno(r, "Failed to parse --load-credential= parameter: %m"); if (r == 0 || !p) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Missing value for --load-credential=: %s", cred_path); - if (!credential_name_valid(word)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Credential name is not valid: %s", word); + if (!credential_name_valid(cred.id)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Credential name is not valid: %s", cred.id); - FOREACH_ARRAY(cred, *credentials, *n_credentials) - if (streq(cred->id, word)) - return log_error_errno(SYNTHETIC_ERRNO(EEXIST), "Duplicate credential '%s', refusing.", word); + if (machine_credentials_contains(ctx, cred.id)) + return log_error_errno(SYNTHETIC_ERRNO(EEXIST), "Duplicate credential '%s', refusing.", cred.id); if (is_path(p) && path_is_valid(p)) flags |= READ_FULL_FILE_CONNECT_SOCKET; @@ -97,31 +103,29 @@ int machine_credential_load(MachineCredential **credentials, size_t *n_credentia r = get_credentials_dir(&e); if (r < 0) - return log_error_errno(r, "Credential not available (no credentials passed at all): %s", word); + return log_error_errno(r, + "Credential not available (no credentials passed at all): %s", cred.id); - j = path_join(e, p); - if (!j) + path_alloc = path_join(e, p); + if (!path_alloc) return log_oom(); - p = j; + p = path_alloc; } else - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Credential source appears to be neither a valid path nor a credential name: %s", p); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Credential source appears to be neither a valid path nor a credential name: %s", p); r = read_full_file_full(AT_FDCWD, p, UINT64_MAX, SIZE_MAX, flags, NULL, - &data, &size); + &cred.data, &cred.size); if (r < 0) return log_error_errno(r, "Failed to read credential '%s': %m", p); - if (!GREEDY_REALLOC(*credentials, *n_credentials + 1)) + if (!GREEDY_REALLOC(ctx->credentials, ctx->n_credentials + 1)) return log_oom(); - (*credentials)[(*n_credentials)++] = (MachineCredential) { - .id = TAKE_PTR(word), - .data = TAKE_PTR(data), - .size = size, - }; + ctx->credentials[ctx->n_credentials++] = TAKE_STRUCT(cred); return 0; } diff --git a/src/shared/machine-credential.h b/src/shared/machine-credential.h index c9044a2..182f077 100644 --- a/src/shared/machine-credential.h +++ b/src/shared/machine-credential.h @@ -5,10 +5,18 @@ typedef struct MachineCredential { char *id; - void *data; + char *data; size_t size; } MachineCredential; -void machine_credential_free_all(MachineCredential *creds, size_t n); -int machine_credential_set(MachineCredential **credentials, size_t *n_credentials, const char *cred_string); -int machine_credential_load(MachineCredential **credentials, size_t *n_credentials, const char *cred_path); +typedef struct MachineCredentialContext { + MachineCredential *credentials; + size_t n_credentials; +} MachineCredentialContext; + +void machine_credential_context_done(MachineCredentialContext *ctx); + +bool machine_credentials_contains(const MachineCredentialContext *ctx, const char *id); + +int machine_credential_set(MachineCredentialContext *ctx, const char *cred_str); +int machine_credential_load(MachineCredentialContext *ctx, const char *cred_path); diff --git a/src/shared/machine-id-setup.c b/src/shared/machine-id-setup.c index 3efba03..1a63794 100644 --- a/src/shared/machine-id-setup.c +++ b/src/shared/machine-id-setup.c @@ -5,6 +5,7 @@ #include <sys/mount.h> #include <unistd.h> +#include "sd-daemon.h" #include "sd-id128.h" #include "alloc-util.h" @@ -12,6 +13,7 @@ #include "creds-util.h" #include "fd-util.h" #include "id128-util.h" +#include "initrd-util.h" #include "io-util.h" #include "log.h" #include "machine-id-setup.h" @@ -46,13 +48,22 @@ static int acquire_machine_id_from_credential(sd_id128_t *ret) { return 0; } -static int generate_machine_id(const char *root, sd_id128_t *ret) { +static int acquire_machine_id(const char *root, sd_id128_t *ret) { _cleanup_close_ int fd = -EBADF; int r; assert(ret); - /* First, try reading the D-Bus machine id, unless it is a symlink */ + /* First, try reading the machine ID from /run/machine-id, which may not be mounted on + * /etc/machine-id yet. This is important on switching root especially on soft-reboot, Otherwise, + * machine ID may be changed after the transition. */ + if (isempty(root) && running_in_chroot() <= 0 && + id128_read("/run/machine-id", ID128_FORMAT_PLAIN, ret) >= 0) { + log_info("Reusing machine ID stored in /run/machine-id."); + return 1; /* Indicate that the machine ID is reused. */ + } + + /* Then, try reading the D-Bus machine id, unless it is a symlink */ fd = chase_and_open("/var/lib/dbus/machine-id", root, CHASE_PREFIX_ROOT | CHASE_NOFOLLOW, O_RDONLY|O_CLOEXEC|O_NOCTTY, NULL); if (fd >= 0 && id128_read_fd(fd, ID128_FORMAT_PLAIN | ID128_REFUSE_NULL, ret) >= 0) { log_info("Initializing machine ID from D-Bus machine ID."); @@ -61,9 +72,8 @@ static int generate_machine_id(const char *root, sd_id128_t *ret) { if (isempty(root) && running_in_chroot() <= 0) { /* Let's use a system credential for the machine ID if we can */ - r = acquire_machine_id_from_credential(ret); - if (r >= 0) - return r; + if (acquire_machine_id_from_credential(ret) >= 0) + return 0; /* If that didn't work, see if we are running in a container, * and a machine ID was passed in via $container_uuid the way @@ -103,7 +113,7 @@ static int generate_machine_id(const char *root, sd_id128_t *ret) { int machine_id_setup(const char *root, bool force_transient, sd_id128_t machine_id, sd_id128_t *ret) { const char *etc_machine_id, *run_machine_id; _cleanup_close_ int fd = -EBADF; - bool writable; + bool writable, write_run_machine_id = true; int r; etc_machine_id = prefix_roota(root, "/etc/machine-id"); @@ -141,13 +151,14 @@ int machine_id_setup(const char *root, bool force_transient, sd_id128_t machine_ if (sd_id128_is_null(machine_id)) { /* Try to read any existing machine ID */ - if (id128_read_fd(fd, ID128_FORMAT_PLAIN, ret) >= 0) - return 0; + if (id128_read_fd(fd, ID128_FORMAT_PLAIN, &machine_id) >= 0) + goto finish; - /* Hmm, so, the id currently stored is not useful, then let's generate one */ - r = generate_machine_id(root, &machine_id); + /* Hmm, so, the id currently stored is not useful, then let's acquire one. */ + r = acquire_machine_id(root, &machine_id); if (r < 0) return r; + write_run_machine_id = !r; } if (writable) { @@ -185,11 +196,13 @@ int machine_id_setup(const char *root, bool force_transient, sd_id128_t machine_ run_machine_id = prefix_roota(root, "/run/machine-id"); - WITH_UMASK(0022) - r = id128_write(run_machine_id, ID128_FORMAT_PLAIN, machine_id); - if (r < 0) { - (void) unlink(run_machine_id); - return log_error_errno(r, "Cannot write %s: %m", run_machine_id); + if (write_run_machine_id) { + WITH_UMASK(0022) + r = id128_write(run_machine_id, ID128_FORMAT_PLAIN, machine_id); + if (r < 0) { + (void) unlink(run_machine_id); + return log_error_errno(r, "Cannot write %s: %m", run_machine_id); + } } /* And now, let's mount it over */ @@ -207,6 +220,9 @@ int machine_id_setup(const char *root, bool force_transient, sd_id128_t machine_ return r; finish: + if (!in_initrd()) + (void) sd_notifyf(/* unset_environment= */ false, "X_SYSTEMD_MACHINE_ID=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(machine_id)); + if (ret) *ret = machine_id; @@ -237,7 +253,7 @@ int machine_id_commit(const char *root) { etc_machine_id = prefix_roota(root, "/etc/machine-id"); - r = path_is_mount_point(etc_machine_id, NULL, 0); + r = path_is_mount_point(etc_machine_id); if (r < 0) return log_error_errno(r, "Failed to determine whether %s is a mount point: %m", etc_machine_id); if (r == 0) { @@ -265,7 +281,12 @@ int machine_id_commit(const char *root) { fd = safe_close(fd); /* Store current mount namespace */ - r = namespace_open(0, NULL, &initial_mntns_fd, NULL, NULL, NULL); + r = namespace_open(0, + /* ret_pidns_fd = */ NULL, + &initial_mntns_fd, + /* ret_netns_fd = */ NULL, + /* ret_userns_fd = */ NULL, + /* ret_root_fd = */ NULL); if (r < 0) return log_error_errno(r, "Can't fetch current mount namespace: %m"); @@ -284,7 +305,11 @@ int machine_id_commit(const char *root) { return log_error_errno(r, "Cannot write %s. This is mandatory to get a persistent machine ID: %m", etc_machine_id); /* Return to initial namespace and proceed a lazy tmpfs unmount */ - r = namespace_enter(-1, initial_mntns_fd, -1, -1, -1); + r = namespace_enter(/* pidns_fd = */ -EBADF, + initial_mntns_fd, + /* netns_fd = */ -EBADF, + /* userns_fd = */ -EBADF, + /* root_fd = */ -EBADF); if (r < 0) return log_warning_errno(r, "Failed to switch back to initial mount namespace: %m.\nWe'll keep transient %s file until next reboot.", etc_machine_id); diff --git a/src/shared/main-func.h b/src/shared/main-func.h index 3f6b6a8..d0689b4 100644 --- a/src/shared/main-func.h +++ b/src/shared/main-func.h @@ -3,16 +3,22 @@ #include <stdlib.h> +#if HAVE_VALGRIND_VALGRIND_H +# include <valgrind/valgrind.h> +#endif + #include "sd-daemon.h" #include "argv-util.h" +#include "hashmap.h" #include "pager.h" #include "selinux-util.h" +#include "signal-util.h" #include "spawn-ask-password-agent.h" #include "spawn-polkit-agent.h" #include "static-destruct.h" -#define _DEFINE_MAIN_FUNCTION(intro, impl, ret) \ +#define _DEFINE_MAIN_FUNCTION(intro, impl, result_to_exit_status, result_to_return_value) \ int main(int argc, char *argv[]) { \ int r; \ assert_se(argc > 0 && !isempty(argv[0])); \ @@ -21,22 +27,55 @@ r = impl; \ if (r < 0) \ (void) sd_notifyf(0, "ERRNO=%i", -r); \ - (void) sd_notifyf(0, "EXIT_STATUS=%i", ret); \ + (void) sd_notifyf(0, "EXIT_STATUS=%i", \ + result_to_exit_status(r)); \ ask_password_agent_close(); \ polkit_agent_close(); \ pager_close(); \ mac_selinux_finish(); \ static_destruct(); \ - return ret; \ + return result_to_return_value(r); \ } +static inline int exit_failure_if_negative(int result) { + return result < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} + /* Negative return values from impl are mapped to EXIT_FAILURE, and * everything else means success! */ #define DEFINE_MAIN_FUNCTION(impl) \ - _DEFINE_MAIN_FUNCTION(,impl(argc, argv), r < 0 ? EXIT_FAILURE : EXIT_SUCCESS) + _DEFINE_MAIN_FUNCTION(,impl(argc, argv), exit_failure_if_negative, exit_failure_if_negative) + +static inline int exit_failure_if_nonzero(int result) { + return result < 0 ? EXIT_FAILURE : result; +} /* Zero is mapped to EXIT_SUCCESS, negative values are mapped to EXIT_FAILURE, * and positive values are propagated. * Note: "true" means failure! */ #define DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(impl) \ - _DEFINE_MAIN_FUNCTION(,impl(argc, argv), r < 0 ? EXIT_FAILURE : r) + _DEFINE_MAIN_FUNCTION(,impl(argc, argv), exit_failure_if_nonzero, exit_failure_if_nonzero) + +static inline int raise_or_exit_status(int ret) { + if (ret < 0) + return EXIT_FAILURE; + if (ret == 0) + return EXIT_SUCCESS; + if (!SIGNAL_VALID(ret)) + return EXIT_FAILURE; + +#if HAVE_VALGRIND_VALGRIND_H + /* If raise() below succeeds, the destructor cleanup_pools() in hashmap.c will never called. */ + if (RUNNING_ON_VALGRIND) + hashmap_trim_pools(); +#endif + + (void) raise(ret); + /* exit with failure if raise() does not immediately abort the program. */ + return EXIT_FAILURE; +} + +/* Negative return values from impl are mapped to EXIT_FAILURE, zero is mapped to EXIT_SUCCESS, + * and raise if a positive signal is returned from impl. */ +#define DEFINE_MAIN_FUNCTION_WITH_POSITIVE_SIGNAL(impl) \ + _DEFINE_MAIN_FUNCTION(,impl(argc, argv), exit_failure_if_negative, raise_or_exit_status) diff --git a/src/shared/meson.build b/src/shared/meson.build index b24a541..c5106d8 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -39,6 +39,7 @@ shared_sources = files( 'chown-recursive.c', 'clean-ipc.c', 'clock-util.c', + 'color-util.c', 'common-signal.c', 'compare-operator.c', 'condition.c', @@ -54,7 +55,6 @@ shared_sources = files( 'device-nodes.c', 'discover-image.c', 'dissect-image.c', - 'dlfcn-util.c', 'dm-util.c', 'dns-domain.c', 'dropin.c', @@ -98,10 +98,11 @@ shared_sources = files( 'journal-util.c', 'json.c', 'kbd-util.c', + 'kernel-config.c', 'kernel-image.c', - 'keyring-util.c', 'killall.c', 'label-util.c', + 'libarchive-util.c', 'libcrypt-util.c', 'libfido2-util.c', 'libmount-util.c', @@ -117,6 +118,7 @@ shared_sources = files( 'macvlan-util.c', 'mkdir-label.c', 'mkfs-util.c', + 'module-util.c', 'mount-setup.c', 'mount-util.c', 'net-condition.c', @@ -124,6 +126,7 @@ shared_sources = files( 'netif-sriov.c', 'netif-util.c', 'nsflags.c', + 'nsresource.c', 'numa-util.c', 'open-file.c', 'openssl-util.c', @@ -139,6 +142,7 @@ shared_sources = files( 'pkcs11-util.c', 'plymouth-util.c', 'pretty-print.c', + 'capsule-util.c', 'ptyfwd.c', 'qrcode-util.c', 'quota-util.c', @@ -172,11 +176,19 @@ shared_sources = files( 'varlink.c', 'varlink-idl.c', 'varlink-io.systemd.c', + 'varlink-io.systemd.BootControl.c', + 'varlink-io.systemd.Credentials.c', + 'varlink-io.systemd.Hostname.c', 'varlink-io.systemd.Journal.c', + 'varlink-io.systemd.Machine.c', 'varlink-io.systemd.ManagedOOM.c', + 'varlink-io.systemd.MountFileSystem.c', + 'varlink-io.systemd.NamespaceResource.c', + 'varlink-io.systemd.Network.c', 'varlink-io.systemd.PCRExtend.c', - 'varlink-io.systemd.Resolve.Monitor.c', + 'varlink-io.systemd.PCRLock.c', 'varlink-io.systemd.Resolve.c', + 'varlink-io.systemd.Resolve.Monitor.c', 'varlink-io.systemd.UserDatabase.c', 'varlink-io.systemd.oom.c', 'varlink-io.systemd.service.c', @@ -186,6 +198,7 @@ shared_sources = files( 'verbs.c', 'vlan-util.c', 'volatile-util.c', + 'vpick.c', 'wall.c', 'watchdog.c', 'web-util.c', @@ -194,9 +207,7 @@ shared_sources = files( ) if get_option('tests') != 'false' - shared_sources += files( - 'tests.c', - ) + shared_sources += files('tests.c') endif generate_syscall_list = find_program('generate-syscall-list.py') @@ -210,9 +221,7 @@ syscall_list_h = custom_target( capture : true) if conf.get('HAVE_ACL') == 1 - shared_sources += files( - 'devnode-acl.c', - ) + shared_sources += files('devnode-acl.c') endif if conf.get('ENABLE_UTMP') == 1 @@ -229,19 +238,11 @@ if conf.get('HAVE_LIBIPTC') == 1 endif if conf.get('HAVE_LIBBPF') == 1 - shared_sources += files( - 'bpf-link.c', - ) -endif - -if conf.get('HAVE_KMOD') == 1 - shared_sources += files('module-util.c') + shared_sources += files('bpf-link.c') endif if conf.get('HAVE_PAM') == 1 - shared_sources += files( - 'pam-util.c', - ) + shared_sources += files('pam-util.c') endif if conf.get('ENABLE_NSCD') == 1 @@ -252,6 +253,10 @@ if conf.get('HAVE_LIBFIDO2') == 1 and conf.get('HAVE_LIBCRYPTSETUP') == 1 shared_sources += files('cryptsetup-fido2.c') endif +if conf.get('HAVE_TPM2') == 1 and conf.get('HAVE_LIBCRYPTSETUP') == 1 + shared_sources += files('cryptsetup-tpm2.c') +endif + generate_ip_protocol_list = find_program('generate-ip-protocol-list.sh') ip_protocol_list_txt = custom_target( 'ip-protocol-list.txt', @@ -320,8 +325,8 @@ libshared_deps = [threads, libdl, libgcrypt, libiptc_cflags, - libkmod, - liblz4, + libkmod_cflags, + liblz4_cflags, libmount, libopenssl, libp11kit_cflags, @@ -330,8 +335,8 @@ libshared_deps = [threads, libseccomp, libselinux, libxenctrl_cflags, - libxz, - libzstd] + libxz_cflags, + libzstd_cflags] libshared_sym_path = meson.current_source_dir() / 'libshared.sym' libshared_build_dir = meson.current_build_dir() @@ -354,16 +359,13 @@ libshared = shared_library( link_depends : libshared_sym_path, link_whole : [libshared_static, libbasic, - libbasic_gcrypt, libsystemd_static], dependencies : [libshared_deps, userspace], install : true, install_dir : pkglibdir) -shared_fdisk_sources = files( - 'fdisk-util.c', -) +shared_fdisk_sources = files('fdisk-util.c') libshared_fdisk = static_library( 'shared-fdisk', diff --git a/src/shared/mkfs-util.c b/src/shared/mkfs-util.c index 4e58b6e..4d44012 100644 --- a/src/shared/mkfs-util.c +++ b/src/shared/mkfs-util.c @@ -104,7 +104,6 @@ static int mangle_fat_label(const char *s, char **ret) { static int do_mcopy(const char *node, const char *root) { _cleanup_free_ char *mcopy = NULL; _cleanup_strv_free_ char **argv = NULL; - _cleanup_close_ int rfd = -EBADF; _cleanup_free_ DirectoryEntries *de = NULL; int r; @@ -128,11 +127,7 @@ static int do_mcopy(const char *node, const char *root) { /* mcopy copies the top level directory instead of everything in it so we have to pass all * the subdirectories to mcopy instead to end up with the correct directory structure. */ - rfd = open(root, O_RDONLY|O_DIRECTORY|O_CLOEXEC); - if (rfd < 0) - return log_error_errno(errno, "Failed to open directory '%s': %m", root); - - r = readdir_all(rfd, RECURSE_DIR_SORT|RECURSE_DIR_ENSURE_TYPE, &de); + r = readdir_all_at(AT_FDCWD, root, RECURSE_DIR_SORT|RECURSE_DIR_ENSURE_TYPE, &de); if (r < 0) return log_error_errno(r, "Failed to read '%s' contents: %m", root); @@ -430,7 +425,7 @@ int make_filesystem( "-T", "default", node); - if (root && strv_extend_strv(&argv, STRV_MAKE("-d", root), false) < 0) + if (root && strv_extend_many(&argv, "-d", root) < 0) return log_oom(); if (quiet && strv_extend(&argv, "-q") < 0) @@ -455,7 +450,7 @@ int make_filesystem( if (!discard && strv_extend(&argv, "--nodiscard") < 0) return log_oom(); - if (root && strv_extend_strv(&argv, STRV_MAKE("-r", root), false) < 0) + if (root && strv_extend_many(&argv, "-r", root) < 0) return log_oom(); if (quiet && strv_extend(&argv, "-q") < 0) @@ -519,7 +514,7 @@ int make_filesystem( if (!protofile_with_opt) return -ENOMEM; - if (strv_extend_strv(&argv, STRV_MAKE("-p", protofile_with_opt), false) < 0) + if (strv_extend_many(&argv, "-p", protofile_with_opt) < 0) return log_oom(); } diff --git a/src/shared/module-util.c b/src/shared/module-util.c index 951701d..fa1e0f8 100644 --- a/src/shared/module-util.c +++ b/src/shared/module-util.c @@ -6,8 +6,52 @@ #include "proc-cmdline.h" #include "strv.h" +#if HAVE_KMOD + +static void *libkmod_dl = NULL; + +DLSYM_FUNCTION(kmod_list_next); +DLSYM_FUNCTION(kmod_load_resources); +DLSYM_FUNCTION(kmod_module_get_initstate); +DLSYM_FUNCTION(kmod_module_get_module); +DLSYM_FUNCTION(kmod_module_get_name); +DLSYM_FUNCTION(kmod_module_new_from_lookup); +DLSYM_FUNCTION(kmod_module_probe_insert_module); +DLSYM_FUNCTION(kmod_module_unref); +DLSYM_FUNCTION(kmod_module_unref_list); +DLSYM_FUNCTION(kmod_new); +DLSYM_FUNCTION(kmod_set_log_fn); +DLSYM_FUNCTION(kmod_unref); +DLSYM_FUNCTION(kmod_validate_resources); + +int dlopen_libkmod(void) { + ELF_NOTE_DLOPEN("kmod", + "Support for loading kernel modules", + ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, + "libkmod.so.2"); + + return dlopen_many_sym_or_warn( + &libkmod_dl, + "libkmod.so.2", + LOG_DEBUG, + DLSYM_ARG(kmod_list_next), + DLSYM_ARG(kmod_load_resources), + DLSYM_ARG(kmod_module_get_initstate), + DLSYM_ARG(kmod_module_get_module), + DLSYM_ARG(kmod_module_get_name), + DLSYM_ARG(kmod_module_new_from_lookup), + DLSYM_ARG(kmod_module_probe_insert_module), + DLSYM_ARG(kmod_module_unref), + DLSYM_ARG(kmod_module_unref_list), + DLSYM_ARG(kmod_new), + DLSYM_ARG(kmod_set_log_fn), + DLSYM_ARG(kmod_unref), + DLSYM_ARG(kmod_validate_resources)); +} + static int denylist_modules(const char *p, char ***denylist) { _cleanup_strv_free_ char **k = NULL; + int r; assert(p); assert(denylist); @@ -16,8 +60,9 @@ static int denylist_modules(const char *p, char ***denylist) { if (!k) return -ENOMEM; - if (strv_extend_strv(denylist, k, true) < 0) - return -ENOMEM; + r = strv_extend_strv(denylist, k, /* filter_duplicates= */ true); + if (r < 0) + return r; return 0; } @@ -39,19 +84,21 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat } int module_load_and_warn(struct kmod_ctx *ctx, const char *module, bool verbose) { - const int probe_flags = KMOD_PROBE_APPLY_BLACKLIST; - struct kmod_list *itr; - _cleanup_(kmod_module_unref_listp) struct kmod_list *modlist = NULL; + _cleanup_(sym_kmod_module_unref_listp) struct kmod_list *modlist = NULL; _cleanup_strv_free_ char **denylist = NULL; bool denylist_parsed = false; + struct kmod_list *itr; int r; + assert(ctx); + assert(module); + /* verbose==true means we should log at non-debug level if we * fail to find or load the module. */ log_debug("Loading module: %s", module); - r = kmod_module_new_from_lookup(ctx, module, &modlist); + r = sym_kmod_module_new_from_lookup(ctx, module, &modlist); if (r < 0) return log_full_errno(verbose ? LOG_ERR : LOG_DEBUG, r, "Failed to look up module alias '%s': %m", module); @@ -61,32 +108,37 @@ int module_load_and_warn(struct kmod_ctx *ctx, const char *module, bool verbose) SYNTHETIC_ERRNO(ENOENT), "Failed to find module '%s'", module); - kmod_list_foreach(itr, modlist) { - _cleanup_(kmod_module_unrefp) struct kmod_module *mod = NULL; + sym_kmod_list_foreach(itr, modlist) { + _cleanup_(sym_kmod_module_unrefp) struct kmod_module *mod = NULL; int state, err; - mod = kmod_module_get_module(itr); - state = kmod_module_get_initstate(mod); + mod = sym_kmod_module_get_module(itr); + state = sym_kmod_module_get_initstate(mod); switch (state) { case KMOD_MODULE_BUILTIN: log_full(verbose ? LOG_INFO : LOG_DEBUG, - "Module '%s' is built in", kmod_module_get_name(mod)); + "Module '%s' is built in", sym_kmod_module_get_name(mod)); break; case KMOD_MODULE_LIVE: - log_debug("Module '%s' is already loaded", kmod_module_get_name(mod)); + log_debug("Module '%s' is already loaded", sym_kmod_module_get_name(mod)); break; default: - err = kmod_module_probe_insert_module(mod, probe_flags, - NULL, NULL, NULL, NULL); + err = sym_kmod_module_probe_insert_module( + mod, + KMOD_PROBE_APPLY_BLACKLIST, + /* extra_options= */ NULL, + /* run_install= */ NULL, + /* data= */ NULL, + /* print_action= */ NULL); if (err == 0) log_full(verbose ? LOG_INFO : LOG_DEBUG, - "Inserted module '%s'", kmod_module_get_name(mod)); + "Inserted module '%s'", sym_kmod_module_get_name(mod)); else if (err == KMOD_PROBE_APPLY_BLACKLIST) log_full(verbose ? LOG_INFO : LOG_DEBUG, - "Module '%s' is deny-listed (by kmod)", kmod_module_get_name(mod)); + "Module '%s' is deny-listed (by kmod)", sym_kmod_module_get_name(mod)); else { assert(err < 0); @@ -100,9 +152,9 @@ int module_load_and_warn(struct kmod_ctx *ctx, const char *module, bool verbose) denylist_parsed = true; } - if (strv_contains(denylist, kmod_module_get_name(mod))) { + if (strv_contains(denylist, sym_kmod_module_get_name(mod))) { log_full(verbose ? LOG_INFO : LOG_DEBUG, - "Module '%s' is deny-listed (by kernel)", kmod_module_get_name(mod)); + "Module '%s' is deny-listed (by kernel)", sym_kmod_module_get_name(mod)); continue; } } @@ -113,7 +165,7 @@ int module_load_and_warn(struct kmod_ctx *ctx, const char *module, bool verbose) LOG_ERR, err, "Failed to insert module '%s': %m", - kmod_module_get_name(mod)); + sym_kmod_module_get_name(mod)); if (!IN_SET(err, -ENODEV, -ENOENT)) r = err; } @@ -122,3 +174,38 @@ int module_load_and_warn(struct kmod_ctx *ctx, const char *module, bool verbose) return r; } + +_printf_(6,0) static void systemd_kmod_log( + void *data, + int priority, + const char *file, + int line, + const char *fn, + const char *format, + va_list args) { + + log_internalv(priority, 0, file, line, fn, format, args); +} + +int module_setup_context(struct kmod_ctx **ret) { + _cleanup_(sym_kmod_unrefp) struct kmod_ctx *ctx = NULL; + int r; + + assert(ret); + + r = dlopen_libkmod(); + if (r < 0) + return r; + + ctx = sym_kmod_new(NULL, NULL); + if (!ctx) + return -ENOMEM; + + (void) sym_kmod_load_resources(ctx); + sym_kmod_set_log_fn(ctx, systemd_kmod_log, NULL); + + *ret = TAKE_PTR(ctx); + return 0; +} + +#endif diff --git a/src/shared/module-util.h b/src/shared/module-util.h index 8ca6a06..0819738 100644 --- a/src/shared/module-util.h +++ b/src/shared/module-util.h @@ -1,12 +1,56 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include "dlfcn-util.h" + +#if HAVE_KMOD + #include <libkmod.h> #include "macro.h" -DEFINE_TRIVIAL_CLEANUP_FUNC(struct kmod_ctx*, kmod_unref); -DEFINE_TRIVIAL_CLEANUP_FUNC(struct kmod_module*, kmod_module_unref); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct kmod_list*, kmod_module_unref_list, NULL); +DLSYM_PROTOTYPE(kmod_list_next); +DLSYM_PROTOTYPE(kmod_load_resources); +DLSYM_PROTOTYPE(kmod_module_get_initstate); +DLSYM_PROTOTYPE(kmod_module_get_module); +DLSYM_PROTOTYPE(kmod_module_get_name); +DLSYM_PROTOTYPE(kmod_module_new_from_lookup); +DLSYM_PROTOTYPE(kmod_module_probe_insert_module); +DLSYM_PROTOTYPE(kmod_module_unref); +DLSYM_PROTOTYPE(kmod_module_unref_list); +DLSYM_PROTOTYPE(kmod_new); +DLSYM_PROTOTYPE(kmod_set_log_fn); +DLSYM_PROTOTYPE(kmod_unref); +DLSYM_PROTOTYPE(kmod_validate_resources); + +int dlopen_libkmod(void); + +DEFINE_TRIVIAL_CLEANUP_FUNC(struct kmod_ctx*, sym_kmod_unref); +DEFINE_TRIVIAL_CLEANUP_FUNC(struct kmod_module*, sym_kmod_module_unref); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct kmod_list*, sym_kmod_module_unref_list, NULL); + +#define sym_kmod_list_foreach(list_entry, first_entry) \ + for (list_entry = first_entry; \ + list_entry != NULL; \ + list_entry = sym_kmod_list_next(first_entry, list_entry)) int module_load_and_warn(struct kmod_ctx *ctx, const char *module, bool verbose); +int module_setup_context(struct kmod_ctx **ret); + +#else + +struct kmod_ctx; + +static inline int dlopen_libkmod(void) { + return -EOPNOTSUPP; +} + +static inline int module_setup_context(struct kmod_ctx **ret) { + return -EOPNOTSUPP; +} + +static inline int module_load_and_warn(struct kmod_ctx *ctx, const char *module, bool verbose) { + return -EOPNOTSUPP; +} + +#endif diff --git a/src/shared/mount-setup.c b/src/shared/mount-setup.c index 1226ca1..ba291bd 100644 --- a/src/shared/mount-setup.c +++ b/src/shared/mount-setup.c @@ -69,7 +69,7 @@ static bool check_recursiveprot_supported(void) { r = mount_option_supported("cgroup2", "memory_recursiveprot", NULL); if (r < 0) - log_debug_errno(r, "Failed to determiner whether the 'memory_recursiveprot' mount option is supported, assuming not: %m"); + log_debug_errno(r, "Failed to determine whether the 'memory_recursiveprot' mount option is supported, assuming not: %m"); else if (r == 0) log_debug("This kernel version does not support 'memory_recursiveprot', not using mount option."); @@ -107,16 +107,6 @@ static const MountPoint mount_table[] = { cg_is_unified_wanted, MNT_IN_CONTAINER|MNT_CHECK_WRITABLE }, { "cgroup2", "/sys/fs/cgroup", "cgroup2", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, cg_is_unified_wanted, MNT_IN_CONTAINER|MNT_CHECK_WRITABLE }, - { "tmpfs", "/sys/fs/cgroup", "tmpfs", "mode=0755" TMPFS_LIMITS_SYS_FS_CGROUP, MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_STRICTATIME, - cg_is_legacy_wanted, MNT_FATAL|MNT_IN_CONTAINER }, - { "cgroup2", "/sys/fs/cgroup/unified", "cgroup2", "nsdelegate", MS_NOSUID|MS_NOEXEC|MS_NODEV, - cg_is_hybrid_wanted, MNT_IN_CONTAINER|MNT_CHECK_WRITABLE }, - { "cgroup2", "/sys/fs/cgroup/unified", "cgroup2", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, - cg_is_hybrid_wanted, MNT_IN_CONTAINER|MNT_CHECK_WRITABLE }, - { "cgroup", "/sys/fs/cgroup/systemd", "cgroup", "none,name=systemd,xattr", MS_NOSUID|MS_NOEXEC|MS_NODEV, - cg_is_legacy_wanted, MNT_IN_CONTAINER }, - { "cgroup", "/sys/fs/cgroup/systemd", "cgroup", "none,name=systemd", MS_NOSUID|MS_NOEXEC|MS_NODEV, - cg_is_legacy_wanted, MNT_FATAL|MNT_IN_CONTAINER }, #if ENABLE_PSTORE { "pstore", "/sys/fs/pstore", "pstore", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL, MNT_NONE }, @@ -135,8 +125,8 @@ bool mount_point_is_api(const char *path) { /* Checks if this mount point is considered "API", and hence * should be ignored */ - for (size_t i = 0; i < ELEMENTSOF(mount_table); i ++) - if (path_equal(path, mount_table[i].where)) + FOREACH_ELEMENT(i, mount_table) + if (path_equal(path, i->where)) return true; return path_startswith(path, "/sys/fs/cgroup/"); @@ -167,8 +157,11 @@ static int mount_one(const MountPoint *p, bool relabel) { int r, priority; assert(p); + assert(p->what); + assert(p->where); + assert(p->type); - priority = (p->mode & MNT_FATAL) ? LOG_ERR : LOG_DEBUG; + priority = FLAGS_SET(p->mode, MNT_FATAL) ? LOG_ERR : LOG_DEBUG; if (p->condition_fn && !p->condition_fn()) return 0; @@ -177,16 +170,16 @@ static int mount_one(const MountPoint *p, bool relabel) { if (relabel) (void) label_fix(p->where, LABEL_IGNORE_ENOENT|LABEL_IGNORE_EROFS); - r = path_is_mount_point(p->where, NULL, AT_SYMLINK_FOLLOW); + r = path_is_mount_point_full(p->where, /* root = */ NULL, AT_SYMLINK_FOLLOW); if (r < 0 && r != -ENOENT) { log_full_errno(priority, r, "Failed to determine whether %s is a mount point: %m", p->where); - return (p->mode & MNT_FATAL) ? r : 0; + return FLAGS_SET(p->mode, MNT_FATAL) ? r : 0; } if (r > 0) return 0; /* Skip securityfs in a container */ - if (!(p->mode & MNT_IN_CONTAINER) && detect_container() > 0) + if (!FLAGS_SET(p->mode, MNT_IN_CONTAINER) && detect_container() > 0) return 0; /* The access mode here doesn't really matter too much, since @@ -202,44 +195,37 @@ static int mount_one(const MountPoint *p, bool relabel) { p->type, strna(p->options)); - if (FLAGS_SET(p->mode, MNT_FOLLOW_SYMLINK)) - r = mount_follow_verbose(priority, p->what, p->where, p->type, p->flags, p->options); - else - r = mount_nofollow_verbose(priority, p->what, p->where, p->type, p->flags, p->options); + r = mount_verbose_full(priority, p->what, p->where, p->type, p->flags, p->options, FLAGS_SET(p->mode, MNT_FOLLOW_SYMLINK)); if (r < 0) - return (p->mode & MNT_FATAL) ? r : 0; + return FLAGS_SET(p->mode, MNT_FATAL) ? r : 0; /* Relabel again, since we now mounted something fresh here */ if (relabel) (void) label_fix(p->where, 0); - if (p->mode & MNT_CHECK_WRITABLE) { + if (FLAGS_SET(p->mode, MNT_CHECK_WRITABLE)) if (access(p->where, W_OK) < 0) { r = -errno; (void) umount2(p->where, UMOUNT_NOFOLLOW); (void) rmdir(p->where); - log_full_errno(priority, r, "Mount point %s not writable after mounting, undoing: %m", p->where); - return (p->mode & MNT_FATAL) ? r : 0; + log_full_errno(priority, r, "Mount point '%s' not writable after mounting, undoing: %m", p->where); + return FLAGS_SET(p->mode, MNT_FATAL) ? r : 0; } - } return 1; } static int mount_points_setup(size_t n, bool loaded_policy) { - int ret = 0, r; + int r = 0; assert(n <= ELEMENTSOF(mount_table)); - FOREACH_ARRAY(mp, mount_table, n) { - r = mount_one(mp, loaded_policy); - if (r != 0 && ret >= 0) - ret = r; - } + FOREACH_ARRAY(mp, mount_table, n) + RET_GATHER(r, mount_one(mp, loaded_policy)); - return ret; + return r; } int mount_setup_early(void) { @@ -297,81 +283,6 @@ static int symlink_controller(const char *target, const char *alias) { return 0; } -int mount_cgroup_controllers(void) { - _cleanup_set_free_ Set *controllers = NULL; - int r; - - if (!cg_is_legacy_wanted()) - return 0; - - /* Mount all available cgroup controllers that are built into the kernel. */ - r = cg_kernel_controllers(&controllers); - if (r < 0) - return log_error_errno(r, "Failed to enumerate cgroup controllers: %m"); - - for (;;) { - _cleanup_free_ char *options = NULL, *controller = NULL, *where = NULL; - const char *other_controller; - MountPoint p = { - .what = "cgroup", - .type = "cgroup", - .flags = MS_NOSUID|MS_NOEXEC|MS_NODEV, - .mode = MNT_IN_CONTAINER, - }; - - controller = set_steal_first(controllers); - if (!controller) - break; - - /* Check if we shall mount this together with another controller */ - other_controller = join_with(controller); - if (other_controller) { - _cleanup_free_ char *c = NULL; - - /* Check if the other controller is actually available in the kernel too */ - c = set_remove(controllers, other_controller); - if (c) { - - /* Join the two controllers into one string, and maintain a stable ordering */ - if (strcmp(controller, other_controller) < 0) - options = strjoin(controller, ",", other_controller); - else - options = strjoin(other_controller, ",", controller); - if (!options) - return log_oom(); - } - } - - /* The simple case, where there's only one controller to mount together */ - if (!options) - options = TAKE_PTR(controller); - - where = path_join("/sys/fs/cgroup", options); - if (!where) - return log_oom(); - - p.where = where; - p.options = options; - - r = mount_one(&p, true); - if (r < 0) - return r; - - /* Create symlinks from the individual controller names, in case we have a joined mount */ - if (controller) - (void) symlink_controller(options, controller); - if (other_controller) - (void) symlink_controller(options, other_controller); - } - - /* Now that we mounted everything, let's make the tmpfs the cgroup file systems are mounted into read-only. */ - (void) mount_nofollow("tmpfs", "/sys/fs/cgroup", "tmpfs", - MS_REMOUNT|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_STRICTATIME|MS_RDONLY, - "mode=0755" TMPFS_LIMITS_SYS_FS_CGROUP); - - return 0; -} - #if HAVE_SELINUX || ENABLE_SMACK static int relabel_cb( RecurseDirEvent event, @@ -415,34 +326,6 @@ static int relabel_tree(const char *path) { return r; } -static int relabel_cgroup_filesystems(void) { - int r; - struct statfs st; - - r = cg_all_unified(); - if (r == 0) { - /* Temporarily remount the root cgroup filesystem to give it a proper label. Do this - only when the filesystem has been already populated by a previous instance of systemd - running from initrd. Otherwise don't remount anything and leave the filesystem read-write - for the cgroup filesystems to be mounted inside. */ - if (statfs("/sys/fs/cgroup", &st) < 0) - return log_error_errno(errno, "Failed to determine mount flags for /sys/fs/cgroup: %m"); - - if (st.f_flags & ST_RDONLY) - (void) mount_nofollow(NULL, "/sys/fs/cgroup", NULL, MS_REMOUNT, NULL); - - (void) label_fix("/sys/fs/cgroup", 0); - (void) relabel_tree("/sys/fs/cgroup"); - - if (st.f_flags & ST_RDONLY) - (void) mount_nofollow(NULL, "/sys/fs/cgroup", NULL, MS_REMOUNT|MS_RDONLY, NULL); - - } else if (r < 0) - return log_error_errno(r, "Failed to determine whether we are in all unified mode: %m"); - - return 0; -} - static int relabel_extra(void) { _cleanup_strv_free_ char **files = NULL; int r, c = 0; @@ -533,14 +416,12 @@ int mount_setup(bool loaded_policy, bool leave_propagation) { FOREACH_STRING(i, "/dev", "/dev/shm", "/run") (void) relabel_tree(i); - (void) relabel_cgroup_filesystems(); - n_extra = relabel_extra(); after_relabel = now(CLOCK_MONOTONIC); - log_info("Relabeled /dev, /dev/shm, /run, /sys/fs/cgroup%s in %s.", - n_extra > 0 ? ", additional files" : "", + log_info("Relabeled /dev/, /dev/shm/, /run/%s in %s.", + n_extra > 0 ? ", and additional files" : "", FORMAT_TIMESPAN(after_relabel - before_relabel, 0)); } #endif @@ -589,3 +470,127 @@ int mount_setup(bool loaded_policy, bool leave_propagation) { return 0; } + +static const MountPoint cgroupv1_mount_table[] = { + { "tmpfs", "/sys/fs/cgroup", "tmpfs", "mode=0755" TMPFS_LIMITS_SYS_FS_CGROUP, MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_STRICTATIME, + cg_is_legacy_wanted, MNT_FATAL|MNT_IN_CONTAINER }, + { "cgroup2", "/sys/fs/cgroup/unified", "cgroup2", "nsdelegate", MS_NOSUID|MS_NOEXEC|MS_NODEV, + cg_is_hybrid_wanted, MNT_IN_CONTAINER|MNT_CHECK_WRITABLE }, + { "cgroup2", "/sys/fs/cgroup/unified", "cgroup2", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, + cg_is_hybrid_wanted, MNT_IN_CONTAINER|MNT_CHECK_WRITABLE }, + { "cgroup", "/sys/fs/cgroup/systemd", "cgroup", "none,name=systemd,xattr", MS_NOSUID|MS_NOEXEC|MS_NODEV, + cg_is_legacy_wanted, MNT_IN_CONTAINER }, + { "cgroup", "/sys/fs/cgroup/systemd", "cgroup", "none,name=systemd", MS_NOSUID|MS_NOEXEC|MS_NODEV, + cg_is_legacy_wanted, MNT_FATAL|MNT_IN_CONTAINER }, +}; + +static void relabel_cgroup_legacy_hierarchy(void) { +#if HAVE_SELINUX || ENABLE_SMACK + struct statfs st; + + assert(cg_is_legacy_wanted()); + + /* Temporarily remount the root cgroup filesystem to give it a proper label. Do this + only when the filesystem has been already populated by a previous instance of systemd + running from initrd. Otherwise don't remount anything and leave the filesystem read-write + for the cgroup filesystems to be mounted inside. */ + if (statfs("/sys/fs/cgroup", &st) < 0) + return (void) log_error_errno(errno, "Failed to determine mount flags for /sys/fs/cgroup/: %m"); + + if (st.f_flags & ST_RDONLY) + (void) mount_nofollow(NULL, "/sys/fs/cgroup", NULL, MS_REMOUNT, NULL); + + (void) label_fix("/sys/fs/cgroup", 0); + (void) relabel_tree("/sys/fs/cgroup"); + + if (st.f_flags & ST_RDONLY) + (void) mount_nofollow(NULL, "/sys/fs/cgroup", NULL, MS_REMOUNT|MS_RDONLY, NULL); +#endif +} + +int mount_cgroup_legacy_controllers(bool loaded_policy) { + _cleanup_set_free_ Set *controllers = NULL; + int r; + + if (!cg_is_legacy_wanted()) + return 0; + + if (!cg_is_legacy_force_enabled()) + return -ERFKILL; + + FOREACH_ELEMENT(mp, cgroupv1_mount_table) { + r = mount_one(mp, loaded_policy); + if (r < 0) + return r; + } + + if (loaded_policy) + relabel_cgroup_legacy_hierarchy(); + + /* Mount all available cgroup controllers that are built into the kernel. */ + r = cg_kernel_controllers(&controllers); + if (r < 0) + return log_error_errno(r, "Failed to enumerate cgroup controllers: %m"); + + for (;;) { + _cleanup_free_ char *options = NULL, *controller = NULL, *where = NULL; + const char *other_controller; + MountPoint p = { + .what = "cgroup", + .type = "cgroup", + .flags = MS_NOSUID|MS_NOEXEC|MS_NODEV, + .mode = MNT_IN_CONTAINER, + }; + + controller = set_steal_first(controllers); + if (!controller) + break; + + /* Check if we shall mount this together with another controller */ + other_controller = join_with(controller); + if (other_controller) { + _cleanup_free_ char *c = NULL; + + /* Check if the other controller is actually available in the kernel too */ + c = set_remove(controllers, other_controller); + if (c) { + + /* Join the two controllers into one string, and maintain a stable ordering */ + if (strcmp(controller, other_controller) < 0) + options = strjoin(controller, ",", other_controller); + else + options = strjoin(other_controller, ",", controller); + if (!options) + return log_oom(); + } + } + + /* The simple case, where there's only one controller to mount together */ + if (!options) + options = TAKE_PTR(controller); + + where = path_join("/sys/fs/cgroup", options); + if (!where) + return log_oom(); + + p.where = where; + p.options = options; + + r = mount_one(&p, true); + if (r < 0) + return r; + + /* Create symlinks from the individual controller names, in case we have a joined mount */ + if (controller) + (void) symlink_controller(options, controller); + if (other_controller) + (void) symlink_controller(options, other_controller); + } + + /* Now that we mounted everything, let's make the tmpfs the cgroup file systems are mounted into read-only. */ + (void) mount_nofollow("tmpfs", "/sys/fs/cgroup", "tmpfs", + MS_REMOUNT|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_STRICTATIME|MS_RDONLY, + "mode=0755" TMPFS_LIMITS_SYS_FS_CGROUP); + + return 1; +} diff --git a/src/shared/mount-setup.h b/src/shared/mount-setup.h index 29bd62f..9fc4933 100644 --- a/src/shared/mount-setup.h +++ b/src/shared/mount-setup.h @@ -3,10 +3,10 @@ #include <stdbool.h> +bool mount_point_is_api(const char *path); +bool mount_point_ignore(const char *path); + int mount_setup_early(void); int mount_setup(bool loaded_policy, bool leave_propagation); -int mount_cgroup_controllers(void); - -bool mount_point_is_api(const char *path); -bool mount_point_ignore(const char *path); +int mount_cgroup_legacy_controllers(bool loaded_policy); diff --git a/src/shared/mount-util.c b/src/shared/mount-util.c index 4f2acce..4ddfb9a 100644 --- a/src/shared/mount-util.c +++ b/src/shared/mount-util.c @@ -79,7 +79,9 @@ int umount_recursive_full(const char *prefix, int flags, char **keep) { continue; if (prefix && !path_startswith(path, prefix)) { - log_trace("Not unmounting %s, outside of prefix: %s", path, prefix); + // FIXME: This is extremely noisy, we're probably doing something very wrong + // to trigger this so often, needs more investigation. + // log_trace("Not unmounting %s, outside of prefix: %s", path, prefix); continue; } @@ -347,7 +349,7 @@ int bind_remount_recursive_with_mountinfo( * think autofs, NFS, FUSE, …), but let's generate useful debug messages at * the very least. */ - q = path_is_mount_point(x, NULL, 0); + q = path_is_mount_point(x); if (IN_SET(q, 0, -ENOENT)) { /* Hmm, whaaaa? The mount point is not actually a mount point? Then * it is either obstructed by a later mount or somebody has been @@ -453,14 +455,20 @@ int bind_remount_one_with_mountinfo( return 0; } +int bind_remount_one(const char *path, unsigned long new_flags, unsigned long flags_mask) { + _cleanup_fclose_ FILE *proc_self_mountinfo = NULL; + + proc_self_mountinfo = fopen("/proc/self/mountinfo", "re"); + if (!proc_self_mountinfo) + return log_debug_errno(errno, "Failed to open /proc/self/mountinfo: %m"); + + return bind_remount_one_with_mountinfo(path, new_flags, flags_mask, proc_self_mountinfo); +} + static int mount_switch_root_pivot(int fd_newroot, const char *path) { assert(fd_newroot >= 0); assert(path); - /* Change into the new rootfs. */ - if (fchdir(fd_newroot) < 0) - return log_debug_errno(errno, "Failed to chdir into new rootfs '%s': %m", path); - /* Let the kernel tuck the new root under the old one. */ if (pivot_root(".", ".") < 0) return log_debug_errno(errno, "Failed to pivot root to new rootfs '%s': %m", path); @@ -477,10 +485,6 @@ static int mount_switch_root_move(int fd_newroot, const char *path) { assert(fd_newroot >= 0); assert(path); - /* Change into the new rootfs. */ - if (fchdir(fd_newroot) < 0) - return log_debug_errno(errno, "Failed to chdir into new rootfs '%s': %m", path); - /* Move the new root fs */ if (mount(".", "/", NULL, MS_MOVE, NULL) < 0) return log_debug_errno(errno, "Failed to move new rootfs '%s': %m", path); @@ -494,7 +498,7 @@ static int mount_switch_root_move(int fd_newroot, const char *path) { int mount_switch_root_full(const char *path, unsigned long mount_propagation_flag, bool force_ms_move) { _cleanup_close_ int fd_newroot = -EBADF; - int r; + int r, is_current_root; assert(path); assert(mount_propagation_flag_is_valid(mount_propagation_flag)); @@ -503,19 +507,31 @@ int mount_switch_root_full(const char *path, unsigned long mount_propagation_fla if (fd_newroot < 0) return log_debug_errno(errno, "Failed to open new rootfs '%s': %m", path); - if (!force_ms_move) { - r = mount_switch_root_pivot(fd_newroot, path); - if (r < 0) { - log_debug_errno(r, "Failed to pivot into new rootfs '%s', will try to use MS_MOVE instead: %m", path); - force_ms_move = true; + is_current_root = path_is_root_at(fd_newroot, NULL); + if (is_current_root < 0) + return log_debug_errno(is_current_root, "Failed to determine if target dir is our root already: %m"); + + /* Change into the new rootfs. */ + if (fchdir(fd_newroot) < 0) + return log_debug_errno(errno, "Failed to chdir into new rootfs '%s': %m", path); + + /* Make this a NOP if we are supposed to switch to our current root fs. After all, both pivot_root() + * and MS_MOVE don't like that. */ + if (!is_current_root) { + if (!force_ms_move) { + r = mount_switch_root_pivot(fd_newroot, path); + if (r < 0) { + log_debug_errno(r, "Failed to pivot into new rootfs '%s', will try to use MS_MOVE instead: %m", path); + force_ms_move = true; + } + } + if (force_ms_move) { + /* Failed to pivot_root() fallback to MS_MOVE. For example, this may happen if the rootfs is + * an initramfs in which case pivot_root() isn't supported. */ + r = mount_switch_root_move(fd_newroot, path); + if (r < 0) + return log_debug_errno(r, "Failed to switch to new rootfs '%s' with MS_MOVE: %m", path); } - } - if (force_ms_move) { - /* Failed to pivot_root() fallback to MS_MOVE. For example, this may happen if the rootfs is - * an initramfs in which case pivot_root() isn't supported. */ - r = mount_switch_root_move(fd_newroot, path); - if (r < 0) - return log_debug_errno(r, "Failed to switch to new rootfs '%s' with MS_MOVE: %m", path); } /* Finally, let's establish the requested propagation flags. */ @@ -817,8 +833,8 @@ int mount_option_mangle( if (!(ent->mask & MNT_INVERT)) mount_flags |= ent->id; - else if (mount_flags & ent->id) - mount_flags ^= ent->id; + else + mount_flags &= ~ent->id; break; } @@ -1096,7 +1112,7 @@ static int mount_in_namespace( if (!pidref_is_set(target)) return -ESRCH; - r = namespace_open(target->pid, &pidns_fd, &mntns_fd, NULL, NULL, &root_fd); + r = namespace_open(target->pid, &pidns_fd, &mntns_fd, /* ret_netns_fd = */ NULL, /* ret_userns_fd = */ NULL, &root_fd); if (r < 0) return log_debug_errno(r, "Failed to retrieve FDs of the target process' namespace: %m"); @@ -1200,7 +1216,9 @@ static int mount_in_namespace( (void) mkdir_parents(dest, 0755); if (img) { - DissectImageFlags f = DISSECT_IMAGE_TRY_ATOMIC_MOUNT_EXCHANGE; + DissectImageFlags f = + DISSECT_IMAGE_TRY_ATOMIC_MOUNT_EXCHANGE | + DISSECT_IMAGE_ALLOW_USERSPACE_VERITY; if (make_file_or_directory) f |= DISSECT_IMAGE_MKDIR; @@ -1279,7 +1297,7 @@ int make_mount_point(const char *path) { /* If 'path' is already a mount point, does nothing and returns 0. If it is not it makes it one, and returns 1. */ - r = path_is_mount_point(path, NULL, 0); + r = path_is_mount_point(path); if (r < 0) return log_debug_errno(r, "Failed to determine whether '%s' is a mount point: %m", path); if (r > 0) @@ -1310,7 +1328,7 @@ int fd_make_mount_point(int fd) { return 1; } -int make_userns(uid_t uid_shift, uid_t uid_range, uid_t owner, RemountIdmapping idmapping) { +int make_userns(uid_t uid_shift, uid_t uid_range, uid_t source_owner, uid_t dest_owner, RemountIdmapping idmapping) { _cleanup_close_ int userns_fd = -EBADF; _cleanup_free_ char *line = NULL; @@ -1345,8 +1363,20 @@ int make_userns(uid_t uid_shift, uid_t uid_range, uid_t owner, RemountIdmapping if (idmapping == REMOUNT_IDMAPPING_HOST_OWNER) { /* Remap the owner of the bind mounted directory to the root user within the container. This * way every file written by root within the container to the bind-mounted directory will - * be owned by the original user. All other user will remain unmapped. */ - if (asprintf(&line, UID_FMT " " UID_FMT " " UID_FMT "\n", owner, uid_shift, 1u) < 0) + * be owned by the original user from the host. All other users will remain unmapped. */ + if (asprintf(&line, UID_FMT " " UID_FMT " " UID_FMT "\n", source_owner, uid_shift, 1u) < 0) + return log_oom_debug(); + } + + if (idmapping == REMOUNT_IDMAPPING_HOST_OWNER_TO_TARGET_OWNER) { + /* Remap the owner of the bind mounted directory to the owner of the target directory + * within the container. This way every file written by target directory owner within the + * container to the bind-mounted directory will be owned by the original host user. + * All other users will remain unmapped. */ + if (asprintf( + &line, + UID_FMT " " UID_FMT " " UID_FMT "\n", + source_owner, dest_owner, 1u) < 0) return log_oom_debug(); } @@ -1366,7 +1396,7 @@ int remount_idmap_fd( assert(userns_fd >= 0); - /* This remounts all specified paths with the specified userns as idmap. It will do so in in the + /* This remounts all specified paths with the specified userns as idmap. It will do so in the * order specified in the strv: the expectation is that the top-level directories are at the * beginning, and nested directories in the right, so that the tree can be built correctly from left * to right. */ @@ -1420,10 +1450,10 @@ int remount_idmap_fd( return 0; } -int remount_idmap(char **p, uid_t uid_shift, uid_t uid_range, uid_t owner, RemountIdmapping idmapping) { +int remount_idmap(char **p, uid_t uid_shift, uid_t uid_range, uid_t source_owner, uid_t dest_owner,RemountIdmapping idmapping) { _cleanup_close_ int userns_fd = -EBADF; - userns_fd = make_userns(uid_shift, uid_range, owner, idmapping); + userns_fd = make_userns(uid_shift, uid_range, source_owner, dest_owner, idmapping); if (userns_fd < 0) return userns_fd; @@ -1586,7 +1616,7 @@ int bind_mount_submounts( if (!t) return -ENOMEM; - r = path_is_mount_point(t, NULL, 0); + r = path_is_mount_point(t); if (r < 0) { log_debug_errno(r, "Failed to detect if '%s' already is a mount point, ignoring: %m", t); continue; diff --git a/src/shared/mount-util.h b/src/shared/mount-util.h index ef31104..26d96b2 100644 --- a/src/shared/mount-util.h +++ b/src/shared/mount-util.h @@ -26,6 +26,7 @@ static inline int bind_remount_recursive(const char *prefix, unsigned long new_f } int bind_remount_one_with_mountinfo(const char *path, unsigned long new_flags, unsigned long flags_mask, FILE *proc_self_mountinfo); +int bind_remount_one(const char *path, unsigned long new_flags, unsigned long flags_mask); int mount_switch_root_full(const char *path, unsigned long mount_propagation_flag, bool force_ms_move); static inline int mount_switch_root(const char *path, unsigned long mount_propagation_flag) { @@ -116,16 +117,19 @@ typedef enum RemountIdmapping { * certain security implications defaults to off, and requires explicit opt-in. */ REMOUNT_IDMAPPING_HOST_ROOT, /* Define a mapping from root user within the container to the owner of the bind mounted directory. - * This ensure no root-owned files will be written in a bind-mounted directory owned by a different + * This ensures no root-owned files will be written in a bind-mounted directory owned by a different * user. No other users are mapped. */ REMOUNT_IDMAPPING_HOST_OWNER, + /* Define a mapping from bind-target owner within the container to the host owner of the bind mounted + * directory. No other users are mapped. */ + REMOUNT_IDMAPPING_HOST_OWNER_TO_TARGET_OWNER, _REMOUNT_IDMAPPING_MAX, _REMOUNT_IDMAPPING_INVALID = -EINVAL, } RemountIdmapping; -int make_userns(uid_t uid_shift, uid_t uid_range, uid_t owner, RemountIdmapping idmapping); +int make_userns(uid_t uid_shift, uid_t uid_range, uid_t host_owner, uid_t dest_owner, RemountIdmapping idmapping); int remount_idmap_fd(char **p, int userns_fd); -int remount_idmap(char **p, uid_t uid_shift, uid_t uid_range, uid_t owner, RemountIdmapping idmapping); +int remount_idmap(char **p, uid_t uid_shift, uid_t uid_range, uid_t host_owner, uid_t dest_owner, RemountIdmapping idmapping); int bind_mount_submounts( const char *source, diff --git a/src/shared/netif-naming-scheme.c b/src/shared/netif-naming-scheme.c index fbaf5c5..2955b6e 100644 --- a/src/shared/netif-naming-scheme.c +++ b/src/shared/netif-naming-scheme.c @@ -1,6 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "sd-device.h" + #include "alloc-util.h" +#include "device-private.h" #include "netif-naming-scheme.h" #include "proc-cmdline.h" #include "string-util.h" @@ -50,7 +53,7 @@ const NamingScheme* naming_scheme(void) { return cache; /* Acquire setting from the kernel command line */ - (void) proc_cmdline_get_key("net.naming-scheme", 0, &buffer); + (void) proc_cmdline_get_key("net.naming_scheme", 0, &buffer); /* Also acquire it from an env var */ e = getenv("NET_NAMING_SCHEME"); @@ -101,3 +104,81 @@ static const char* const alternative_names_policy_table[_NAMEPOLICY_MAX] = { }; DEFINE_STRING_TABLE_LOOKUP(alternative_names_policy, NamePolicy); + +static int naming_sysattr_allowed_by_default(sd_device *dev) { + int r; + + assert(dev); + + r = device_get_property_bool(dev, "ID_NET_NAME_ALLOW"); + if (r == -ENOENT) + return true; + + return r; +} + +static int naming_sysattr_allowed(sd_device *dev, const char *sysattr) { + char *sysattr_property; + int r; + + assert(dev); + assert(sysattr); + + sysattr_property = strjoina("ID_NET_NAME_ALLOW_", sysattr); + ascii_strupper(sysattr_property); + + r = device_get_property_bool(dev, sysattr_property); + if (r == -ENOENT) + /* If ID_NET_NAME_ALLOW is not set or set to 1 default is to allow */ + return naming_sysattr_allowed_by_default(dev); + + return r; +} + +int device_get_sysattr_int_filtered(sd_device *device, const char *sysattr, int *ret_value) { + int r; + + r = naming_sysattr_allowed(device, sysattr); + if (r < 0) + return r; + if (r == 0) + return -ENOENT; + + return device_get_sysattr_int(device, sysattr, ret_value); +} + +int device_get_sysattr_unsigned_filtered(sd_device *device, const char *sysattr, unsigned *ret_value) { + int r; + + r = naming_sysattr_allowed(device, sysattr); + if (r < 0) + return r; + if (r == 0) + return -ENOENT; + + return device_get_sysattr_unsigned(device, sysattr, ret_value); +} + +int device_get_sysattr_bool_filtered(sd_device *device, const char *sysattr) { + int r; + + r = naming_sysattr_allowed(device, sysattr); + if (r < 0) + return r; + if (r == 0) + return -ENOENT; + + return device_get_sysattr_bool(device, sysattr); +} + +int device_get_sysattr_value_filtered(sd_device *device, const char *sysattr, const char **ret_value) { + int r; + + r = naming_sysattr_allowed(device, sysattr); + if (r < 0) + return r; + if (r == 0) + return -ENOENT; + + return sd_device_get_sysattr_value(device, sysattr, ret_value); +} diff --git a/src/shared/netif-naming-scheme.h b/src/shared/netif-naming-scheme.h index 3f7be08..62afdc5 100644 --- a/src/shared/netif-naming-scheme.h +++ b/src/shared/netif-naming-scheme.h @@ -3,6 +3,8 @@ #include <stdbool.h> +#include "sd-device.h" + #include "macro.h" /* So here's the deal: net_id is supposed to be an exercise in providing stable names for network devices. However, we @@ -95,3 +97,8 @@ NamePolicy name_policy_from_string(const char *p) _pure_; const char *alternative_names_policy_to_string(NamePolicy p) _const_; NamePolicy alternative_names_policy_from_string(const char *p) _pure_; + +int device_get_sysattr_int_filtered(sd_device *device, const char *sysattr, int *ret_value); +int device_get_sysattr_unsigned_filtered(sd_device *device, const char *sysattr, unsigned *ret_value); +int device_get_sysattr_bool_filtered(sd_device *device, const char *sysattr); +int device_get_sysattr_value_filtered(sd_device *device, const char *sysattr, const char **ret_value); diff --git a/src/shared/netif-sriov.c b/src/shared/netif-sriov.c index 7559b0d..20e175e 100644 --- a/src/shared/netif-sriov.c +++ b/src/shared/netif-sriov.c @@ -84,7 +84,7 @@ void sr_iov_hash_func(const SRIOV *sr_iov, struct siphash *state) { assert(sr_iov); assert(state); - siphash24_compress(&sr_iov->vf, sizeof(sr_iov->vf), state); + siphash24_compress_typesafe(sr_iov->vf, state); } int sr_iov_compare_func(const SRIOV *s1, const SRIOV *s2) { diff --git a/src/shared/netif-util.c b/src/shared/netif-util.c index f56c564..8adc2c8 100644 --- a/src/shared/netif-util.c +++ b/src/shared/netif-util.c @@ -5,13 +5,17 @@ #include "arphrd-util.h" #include "device-util.h" +#include "hexdecoct.h" #include "log-link.h" #include "memory-util.h" +#include "netif-naming-scheme.h" #include "netif-util.h" #include "siphash24.h" #include "sparse-endian.h" #include "strv.h" +#define SHORTEN_IFNAME_HASH_KEY SD_ID128_MAKE(e1,90,a4,04,a8,ef,4b,51,8c,cc,c3,3a,9f,11,fc,a2) + bool netif_has_carrier(uint8_t operstate, unsigned flags) { /* see Documentation/networking/operstates.txt in the kernel sources */ @@ -32,14 +36,8 @@ int net_get_type_string(sd_device *device, uint16_t iftype, char **ret) { if (device && sd_device_get_devtype(device, &t) >= 0 && - !isempty(t)) { - p = strdup(t); - if (!p) - return -ENOMEM; - - *ret = p; - return 0; - } + !isempty(t)) + return strdup_to(ret, t); t = arphrd_to_name(iftype); if (!t) @@ -204,3 +202,79 @@ int net_verify_hardware_address( return 0; } + +int net_generate_mac( + const char *machine_name, + struct ether_addr *mac, + sd_id128_t hash_key, + uint64_t idx) { + + uint64_t result; + size_t l, sz; + uint8_t *v, *i; + int r; + + l = strlen(machine_name); + sz = sizeof(sd_id128_t) + l; + if (idx > 0) + sz += sizeof(idx); + + v = newa(uint8_t, sz); + + /* fetch some persistent data unique to the host */ + r = sd_id128_get_machine((sd_id128_t*) v); + if (r < 0) + return r; + + /* combine with some data unique (on this host) to this + * container instance */ + i = mempcpy(v + sizeof(sd_id128_t), machine_name, l); + if (idx > 0) { + idx = htole64(idx); + memcpy(i, &idx, sizeof(idx)); + } + + /* Let's hash the host machine ID plus the container name. We + * use a fixed, but originally randomly created hash key here. */ + result = htole64(siphash24(v, sz, hash_key.bytes)); + + assert_cc(ETH_ALEN <= sizeof(result)); + memcpy(mac->ether_addr_octet, &result, ETH_ALEN); + + ether_addr_mark_random(mac); + + return 0; +} + +int net_shorten_ifname(char *ifname, bool check_naming_scheme) { + char new_ifname[IFNAMSIZ]; + + assert(ifname); + + if (strlen(ifname) < IFNAMSIZ) /* Name is short enough */ + return 0; + + if (!check_naming_scheme || naming_scheme_has(NAMING_NSPAWN_LONG_HASH)) { + uint64_t h; + + /* Calculate 64-bit hash value */ + h = siphash24(ifname, strlen(ifname), SHORTEN_IFNAME_HASH_KEY.bytes); + + /* Set the final four bytes (i.e. 32-bit) to the lower 24bit of the hash, encoded in url-safe base64 */ + memcpy(new_ifname, ifname, IFNAMSIZ - 5); + new_ifname[IFNAMSIZ - 5] = urlsafe_base64char(h >> 18); + new_ifname[IFNAMSIZ - 4] = urlsafe_base64char(h >> 12); + new_ifname[IFNAMSIZ - 3] = urlsafe_base64char(h >> 6); + new_ifname[IFNAMSIZ - 2] = urlsafe_base64char(h); + } else + /* On old nspawn versions we just truncated the name, provide compatibility */ + memcpy(new_ifname, ifname, IFNAMSIZ-1); + + new_ifname[IFNAMSIZ - 1] = 0; + + /* Log the incident to make it more discoverable */ + log_warning("Network interface name '%s' has been changed to '%s' to fit length constraints.", ifname, new_ifname); + + strcpy(ifname, new_ifname); + return 1; +} diff --git a/src/shared/netif-util.h b/src/shared/netif-util.h index fb6a27c..02c531e 100644 --- a/src/shared/netif-util.h +++ b/src/shared/netif-util.h @@ -20,3 +20,9 @@ int net_verify_hardware_address( uint16_t iftype, const struct hw_addr_data *ib_hw_addr, struct hw_addr_data *new_hw_addr); +int net_generate_mac( + const char *machine_name, + struct ether_addr *mac, + sd_id128_t hash_key, + uint64_t idx); +int net_shorten_ifname(char *ifname, bool check_naming_scheme); diff --git a/src/shared/nscd-flush.c b/src/shared/nscd-flush.c index 6df18d7..3d47ae3 100644 --- a/src/shared/nscd-flush.c +++ b/src/shared/nscd-flush.c @@ -93,7 +93,7 @@ static int nscd_flush_cache_one(const char *database, usec_t end) { ssize_t m; if (has_read >= sizeof(resp)) - return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Response from nscd longer than expected: %m"); + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Response from nscd longer than expected."); m = recv(fd, (uint8_t*) &resp + has_read, sizeof(resp) - has_read, 0); if (m < 0) { diff --git a/src/shared/nsresource.c b/src/shared/nsresource.c new file mode 100644 index 0000000..bc0a352 --- /dev/null +++ b/src/shared/nsresource.c @@ -0,0 +1,330 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <sys/prctl.h> + +#include "fd-util.h" +#include "format-util.h" +#include "missing_sched.h" +#include "namespace-util.h" +#include "nsresource.h" +#include "process-util.h" +#include "varlink.h" + +static int make_pid_name(char **ret) { + char comm[TASK_COMM_LEN]; + + assert(ret); + + if (prctl(PR_GET_NAME, comm) < 0) + return -errno; + + /* So the namespace name should be 16 chars at max (because we want that it is usable in usernames, + * which have a limit of 31 chars effectively, and the nsresourced service wants to prefix/suffix + * some bits). But it also should be unique if we are called multiple times in a row. Hence we take + * the "comm" name (which is 15 chars), and suffix it with the UID, possibly overriding the end. */ + assert_cc(TASK_COMM_LEN == 15 + 1); + + char spid[DECIMAL_STR_MAX(pid_t)]; + xsprintf(spid, PID_FMT, getpid_cached()); + + assert(strlen(spid) <= 16); + strshorten(comm, 16 - strlen(spid)); + + _cleanup_free_ char *s = strjoin(comm, spid); + if (!s) + return -ENOMEM; + + *ret = TAKE_PTR(s); + return 0; +} + +int nsresource_allocate_userns(const char *name, uint64_t size) { + _cleanup_(varlink_unrefp) Varlink *vl = NULL; + _cleanup_close_ int userns_fd = -EBADF; + _cleanup_free_ char *_name = NULL; + const char *error_id; + int r, userns_fd_idx; + + /* Allocate a new dynamic user namespace via the userdb registry logic */ + + if (!name) { + r = make_pid_name(&_name); + if (r < 0) + return r; + + name = _name; + } + + if (size <= 0 || size > UINT64_C(0x100000000)) /* Note: the server actually only allows allocating 1 or 64K right now */ + return -EINVAL; + + r = varlink_connect_address(&vl, "/run/systemd/io.systemd.NamespaceResource"); + if (r < 0) + return log_debug_errno(r, "Failed to connect to namespace resource manager: %m"); + + r = varlink_set_allow_fd_passing_output(vl, true); + if (r < 0) + return log_debug_errno(r, "Failed to enable varlink fd passing for write: %m"); + + userns_fd = userns_acquire_empty(); + if (userns_fd < 0) + return log_debug_errno(userns_fd, "Failed to acquire empty user namespace: %m"); + + userns_fd_idx = varlink_push_dup_fd(vl, userns_fd); + if (userns_fd_idx < 0) + return log_debug_errno(userns_fd_idx, "Failed to push userns fd into varlink connection: %m"); + + JsonVariant *reply = NULL; + r = varlink_callb(vl, + "io.systemd.NamespaceResource.AllocateUserRange", + &reply, + &error_id, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("name", JSON_BUILD_STRING(name)), + JSON_BUILD_PAIR("size", JSON_BUILD_UNSIGNED(size)), + JSON_BUILD_PAIR("userNamespaceFileDescriptor", JSON_BUILD_UNSIGNED(userns_fd_idx)))); + if (r < 0) + return log_debug_errno(r, "Failed to call AllocateUserRange() varlink call: %m"); + if (error_id) + return log_debug_errno(varlink_error_to_errno(error_id, reply), "Failed to allocate user namespace with %" PRIu64 " users: %s", size, error_id); + + return TAKE_FD(userns_fd); +} + +int nsresource_register_userns(const char *name, int userns_fd) { + _cleanup_(varlink_unrefp) Varlink *vl = NULL; + _cleanup_close_ int _userns_fd = -EBADF; + _cleanup_free_ char *_name = NULL; + const char *error_id; + int r, userns_fd_idx; + + /* Register the specified user namespace with userbd. */ + + if (!name) { + r = make_pid_name(&_name); + if (r < 0) + return r; + + name = _name; + } + + if (userns_fd < 0) { + _userns_fd = namespace_open_by_type(NAMESPACE_USER); + if (_userns_fd < 0) + return -errno; + + userns_fd = _userns_fd; + } + + r = varlink_connect_address(&vl, "/run/systemd/io.systemd.NamespaceResource"); + if (r < 0) + return log_debug_errno(r, "Failed to connect to namespace resource manager: %m"); + + r = varlink_set_allow_fd_passing_output(vl, true); + if (r < 0) + return log_debug_errno(r, "Failed to enable varlink fd passing for write: %m"); + + userns_fd_idx = varlink_push_dup_fd(vl, userns_fd); + if (userns_fd_idx < 0) + return log_debug_errno(userns_fd_idx, "Failed to push userns fd into varlink connection: %m"); + + JsonVariant *reply = NULL; + r = varlink_callb(vl, + "io.systemd.NamespaceResource.RegisterUserNamespace", + &reply, + &error_id, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("name", JSON_BUILD_STRING(name)), + JSON_BUILD_PAIR("userNamespaceFileDescriptor", JSON_BUILD_UNSIGNED(userns_fd_idx)))); + if (r < 0) + return log_debug_errno(r, "Failed to call RegisterUserNamespace() varlink call: %m"); + if (error_id) + return log_debug_errno(varlink_error_to_errno(error_id, reply), "Failed to register user namespace: %s", error_id); + + return 0; +} + +int nsresource_add_mount(int userns_fd, int mount_fd) { + _cleanup_(varlink_unrefp) Varlink *vl = NULL; + _cleanup_close_ int _userns_fd = -EBADF; + int r, userns_fd_idx, mount_fd_idx; + const char *error_id; + + assert(mount_fd >= 0); + + if (userns_fd < 0) { + _userns_fd = namespace_open_by_type(NAMESPACE_USER); + if (_userns_fd < 0) + return _userns_fd; + + userns_fd = _userns_fd; + } + + r = varlink_connect_address(&vl, "/run/systemd/io.systemd.NamespaceResource"); + if (r < 0) + return log_error_errno(r, "Failed to connect to namespace resource manager: %m"); + + r = varlink_set_allow_fd_passing_output(vl, true); + if (r < 0) + return log_error_errno(r, "Failed to enable varlink fd passing for write: %m"); + + userns_fd_idx = varlink_push_dup_fd(vl, userns_fd); + if (userns_fd_idx < 0) + return log_error_errno(userns_fd_idx, "Failed to push userns fd into varlink connection: %m"); + + mount_fd_idx = varlink_push_dup_fd(vl, mount_fd); + if (mount_fd_idx < 0) + return log_error_errno(mount_fd_idx, "Failed to push mount fd into varlink connection: %m"); + + JsonVariant *reply = NULL; + r = varlink_callb(vl, + "io.systemd.NamespaceResource.AddMountToUserNamespace", + &reply, + &error_id, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("userNamespaceFileDescriptor", JSON_BUILD_UNSIGNED(userns_fd_idx)), + JSON_BUILD_PAIR("mountFileDescriptor", JSON_BUILD_UNSIGNED(mount_fd_idx)))); + if (r < 0) + return log_error_errno(r, "Failed to call AddMountToUserNamespace() varlink call: %m"); + if (streq_ptr(error_id, "io.systemd.NamespaceResource.UserNamespaceNotRegistered")) { + log_notice("User namespace has not been allocated via namespace resource registry, not adding mount to registration."); + return 0; + } + if (error_id) + return log_error_errno(varlink_error_to_errno(error_id, reply), "Failed to mount image: %s", error_id); + + return 1; +} + +int nsresource_add_cgroup(int userns_fd, int cgroup_fd) { + _cleanup_(varlink_unrefp) Varlink *vl = NULL; + _cleanup_close_ int _userns_fd = -EBADF; + int r, userns_fd_idx, cgroup_fd_idx; + const char *error_id; + + assert(cgroup_fd >= 0); + + if (userns_fd < 0) { + _userns_fd = namespace_open_by_type(NAMESPACE_USER); + if (_userns_fd < 0) + return -errno; + + userns_fd = _userns_fd; + } + + r = varlink_connect_address(&vl, "/run/systemd/io.systemd.NamespaceResource"); + if (r < 0) + return log_debug_errno(r, "Failed to connect to namespace resource manager: %m"); + + r = varlink_set_allow_fd_passing_output(vl, true); + if (r < 0) + return log_debug_errno(r, "Failed to enable varlink fd passing for write: %m"); + + userns_fd_idx = varlink_push_dup_fd(vl, userns_fd); + if (userns_fd_idx < 0) + return log_debug_errno(userns_fd_idx, "Failed to push userns fd into varlink connection: %m"); + + cgroup_fd_idx = varlink_push_dup_fd(vl, cgroup_fd); + if (cgroup_fd_idx < 0) + return log_debug_errno(userns_fd_idx, "Failed to push cgroup fd into varlink connection: %m"); + + JsonVariant *reply = NULL; + r = varlink_callb(vl, + "io.systemd.NamespaceResource.AddControlGroupToUserNamespace", + &reply, + &error_id, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("userNamespaceFileDescriptor", JSON_BUILD_UNSIGNED(userns_fd_idx)), + JSON_BUILD_PAIR("controlGroupFileDescriptor", JSON_BUILD_UNSIGNED(cgroup_fd_idx)))); + if (r < 0) + return log_debug_errno(r, "Failed to call AddControlGroupToUserNamespace() varlink call: %m"); + if (streq_ptr(error_id, "io.systemd.NamespaceResource.UserNamespaceNotRegistered")) { + log_notice("User namespace has not been allocated via namespace resource registry, not adding cgroup to registration."); + return 0; + } + if (error_id) + return log_debug_errno(varlink_error_to_errno(error_id, reply), "Failed to add cgroup to user namespace: %s", error_id); + + return 1; +} + +int nsresource_add_netif( + int userns_fd, + int netns_fd, + const char *namespace_ifname, + char **ret_host_ifname, + char **ret_namespace_ifname) { + + _cleanup_close_ int _userns_fd = -EBADF, _netns_fd = -EBADF; + _cleanup_(varlink_unrefp) Varlink *vl = NULL; + int r, userns_fd_idx, netns_fd_idx; + const char *error_id; + + if (userns_fd < 0) { + _userns_fd = namespace_open_by_type(NAMESPACE_USER); + if (_userns_fd < 0) + return -errno; + + userns_fd = _userns_fd; + } + + if (netns_fd < 0) { + _netns_fd = namespace_open_by_type(NAMESPACE_NET); + if (_netns_fd < 0) + return -errno; + + netns_fd = _netns_fd; + } + + r = varlink_connect_address(&vl, "/run/systemd/io.systemd.NamespaceResource"); + if (r < 0) + return log_debug_errno(r, "Failed to connect to namespace resource manager: %m"); + + r = varlink_set_allow_fd_passing_output(vl, true); + if (r < 0) + return log_debug_errno(r, "Failed to enable varlink fd passing for write: %m"); + + userns_fd_idx = varlink_push_dup_fd(vl, userns_fd); + if (userns_fd_idx < 0) + return log_debug_errno(userns_fd_idx, "Failed to push userns fd into varlink connection: %m"); + + netns_fd_idx = varlink_push_dup_fd(vl, netns_fd); + if (netns_fd_idx < 0) + return log_debug_errno(netns_fd_idx, "Failed to push netns fd into varlink connection: %m"); + + JsonVariant *reply = NULL; + r = varlink_callb(vl, + "io.systemd.NamespaceResource.AddNetworkToUserNamespace", + &reply, + &error_id, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("userNamespaceFileDescriptor", JSON_BUILD_UNSIGNED(userns_fd_idx)), + JSON_BUILD_PAIR("networkNamespaceFileDescriptor", JSON_BUILD_UNSIGNED(netns_fd_idx)), + JSON_BUILD_PAIR("mode", JSON_BUILD_CONST_STRING("veth")), + JSON_BUILD_PAIR_CONDITION(namespace_ifname, "namespaceInterfaceName", JSON_BUILD_STRING(namespace_ifname)))); + if (r < 0) + return log_debug_errno(r, "Failed to call AddNetworkToUserNamespace() varlink call: %m"); + if (streq_ptr(error_id, "io.systemd.NamespaceResource.UserNamespaceNotRegistered")) { + log_notice("User namespace has not been allocated via namespace resource registry, not adding network to registration."); + return 0; + } + if (error_id) + return log_debug_errno(varlink_error_to_errno(error_id, reply), "Failed to add network to user namespace: %s", error_id); + + _cleanup_free_ char *host_interface_name = NULL, *namespace_interface_name = NULL; + r = json_dispatch( + reply, + (const JsonDispatch[]) { + { "hostInterfaceName", JSON_VARIANT_STRING, json_dispatch_string, PTR_TO_SIZE(&host_interface_name) }, + { "namespaceInterfaceName", JSON_VARIANT_STRING, json_dispatch_string, PTR_TO_SIZE(&namespace_interface_name) }, + }, + JSON_ALLOW_EXTENSIONS, + /* userdata= */ NULL); + + if (ret_host_ifname) + *ret_host_ifname = TAKE_PTR(host_interface_name); + if (ret_namespace_ifname) + *ret_namespace_ifname = TAKE_PTR(namespace_interface_name); + + return 1; +} diff --git a/src/shared/nsresource.h b/src/shared/nsresource.h new file mode 100644 index 0000000..6b807b3 --- /dev/null +++ b/src/shared/nsresource.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include <inttypes.h> + +int nsresource_allocate_userns(const char *name, uint64_t size); +int nsresource_register_userns(const char *name, int userns_fd); +int nsresource_add_mount(int userns_fd, int mount_fd); +int nsresource_add_cgroup(int userns_fd, int cgroup_fd); +int nsresource_add_netif(int userns_fd, int netns_fd, const char *namespace_ifname, char **ret_host_ifname, char **ret_namespace_ifname); diff --git a/src/shared/open-file.c b/src/shared/open-file.c index 7d7a8a9..a9c2a11 100644 --- a/src/shared/open-file.c +++ b/src/shared/open-file.c @@ -1,4 +1,3 @@ - /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include <fcntl.h> @@ -23,7 +22,7 @@ int open_file_parse(const char *v, OpenFile **ret) { if (!of) return -ENOMEM; - r = extract_many_words(&v, ":", EXTRACT_DONT_COALESCE_SEPARATORS|EXTRACT_CUNESCAPE, &of->path, &of->fdname, &options, NULL); + r = extract_many_words(&v, ":", EXTRACT_DONT_COALESCE_SEPARATORS|EXTRACT_CUNESCAPE, &of->path, &of->fdname, &options); if (r < 0) return r; if (r == 0) @@ -122,19 +121,14 @@ int open_file_to_string(const OpenFile *of, char **ret) { return 0; } -OpenFile *open_file_free(OpenFile *of) { +OpenFile* open_file_free(OpenFile *of) { if (!of) return NULL; free(of->path); free(of->fdname); - return mfree(of); -} -void open_file_free_many(OpenFile **head) { - assert(head); - - LIST_CLEAR(open_files, *head, open_file_free); + return mfree(of); } static const char * const open_file_flags_table[_OPENFILE_MAX] = { diff --git a/src/shared/open-file.h b/src/shared/open-file.h index bb63ec8..4999c96 100644 --- a/src/shared/open-file.h +++ b/src/shared/open-file.h @@ -1,8 +1,8 @@ - /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once #include "list.h" +#include "macro.h" typedef enum OpenFileFlag { OPENFILE_READ_ONLY = 1 << 0, @@ -27,10 +27,12 @@ int open_file_validate(const OpenFile *of); int open_file_to_string(const OpenFile *of, char **ret); -OpenFile *open_file_free(OpenFile *of); +OpenFile* open_file_free(OpenFile *of); DEFINE_TRIVIAL_CLEANUP_FUNC(OpenFile*, open_file_free); -void open_file_free_many(OpenFile **head); +static inline void open_file_free_many(OpenFile **head) { + LIST_CLEAR(open_files, *ASSERT_PTR(head), open_file_free); +} -const char *open_file_flags_to_string(OpenFileFlag t) _const_; +const char* open_file_flags_to_string(OpenFileFlag t) _const_; OpenFileFlag open_file_flags_from_string(const char *t) _pure_; diff --git a/src/shared/openssl-util.c b/src/shared/openssl-util.c index b0a5563..2ab89c7 100644 --- a/src/shared/openssl-util.c +++ b/src/shared/openssl-util.c @@ -1,14 +1,28 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include <endian.h> + #include "alloc-util.h" #include "fd-util.h" #include "hexdecoct.h" +#include "memory-util.h" #include "openssl-util.h" +#include "random-util.h" #include "string-util.h" #if HAVE_OPENSSL -/* For each error in the the OpenSSL thread error queue, log the provided message and the OpenSSL error - * string. If there are no errors in the OpenSSL thread queue, this logs the message with "No openssl +# include <openssl/rsa.h> +# include <openssl/ec.h> + +# if !defined(OPENSSL_NO_ENGINE) && !defined(OPENSSL_NO_DEPRECATED_3_0) +# include <openssl/engine.h> +DISABLE_WARNING_DEPRECATED_DECLARATIONS; +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(ENGINE*, ENGINE_free, NULL); +REENABLE_WARNING; +# endif + +/* For each error in the OpenSSL thread error queue, log the provided message and the OpenSSL error + * string. If there are no errors in the OpenSSL thread queue, this logs the message with "No OpenSSL * errors." This logs at level debug. Returns -EIO (or -ENOMEM). */ #define log_openssl_errors(fmt, ...) _log_openssl_errors(UNIQ, fmt, ##__VA_ARGS__) #define _log_openssl_errors(u, fmt, ...) \ @@ -523,7 +537,6 @@ int rsa_encrypt_bytes( *ret_encrypt_key = TAKE_PTR(b); *ret_encrypt_key_size = l; - return 0; } @@ -624,48 +637,55 @@ int rsa_pkey_to_suitable_key_size( return 0; } -/* Generate RSA public key from provided "n" and "e" values. Note that if "e" is a number (e.g. uint32_t), it - * must be provided here big-endian, e.g. wrap it with htobe32(). */ +/* Generate RSA public key from provided "n" and "e" values. Numbers "n" and "e" must be provided here + * in big-endian format, e.g. wrap it with htobe32() for uint32_t. */ int rsa_pkey_from_n_e(const void *n, size_t n_size, const void *e, size_t e_size, EVP_PKEY **ret) { _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; assert(n); + assert(n_size != 0); assert(e); + assert(e_size != 0); assert(ret); - _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); +#if OPENSSL_VERSION_MAJOR >= 3 + _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name(NULL, "RSA", NULL); if (!ctx) return log_openssl_errors("Failed to create new EVP_PKEY_CTX"); - _cleanup_(BN_freep) BIGNUM *bn_n = BN_bin2bn(n, n_size, NULL); - if (!bn_n) - return log_openssl_errors("Failed to create BIGNUM for RSA n"); - - _cleanup_(BN_freep) BIGNUM *bn_e = BN_bin2bn(e, e_size, NULL); - if (!bn_e) - return log_openssl_errors("Failed to create BIGNUM for RSA e"); - -#if OPENSSL_VERSION_MAJOR >= 3 if (EVP_PKEY_fromdata_init(ctx) <= 0) return log_openssl_errors("Failed to initialize EVP_PKEY_CTX"); - _cleanup_(OSSL_PARAM_BLD_freep) OSSL_PARAM_BLD *bld = OSSL_PARAM_BLD_new(); - if (!bld) - return log_openssl_errors("Failed to create new OSSL_PARAM_BLD"); + OSSL_PARAM params[3]; - if (!OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_N, bn_n)) - return log_openssl_errors("Failed to set RSA OSSL_PKEY_PARAM_RSA_N"); +#if __BYTE_ORDER == __BIG_ENDIAN + params[0] = OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_RSA_N, (void*)n, n_size); + params[1] = OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_RSA_E, (void*)e, e_size); +#else + _cleanup_free_ void *native_n = memdup_reverse(n, n_size); + if (!native_n) + return log_oom_debug(); - if (!OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_E, bn_e)) - return log_openssl_errors("Failed to set RSA OSSL_PKEY_PARAM_RSA_E"); + _cleanup_free_ void *native_e = memdup_reverse(e, e_size); + if (!native_e) + return log_oom_debug(); - _cleanup_(OSSL_PARAM_freep) OSSL_PARAM *params = OSSL_PARAM_BLD_to_param(bld); - if (!params) - return log_openssl_errors("Failed to build RSA OSSL_PARAM"); + params[0] = OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_RSA_N, native_n, n_size); + params[1] = OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_RSA_E, native_e, e_size); +#endif + params[2] = OSSL_PARAM_construct_end(); if (EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_PUBLIC_KEY, params) <= 0) return log_openssl_errors("Failed to create RSA EVP_PKEY"); #else + _cleanup_(BN_freep) BIGNUM *bn_n = BN_bin2bn(n, n_size, NULL); + if (!bn_n) + return log_openssl_errors("Failed to create BIGNUM for RSA n"); + + _cleanup_(BN_freep) BIGNUM *bn_e = BN_bin2bn(e, e_size, NULL); + if (!bn_e) + return log_openssl_errors("Failed to create BIGNUM for RSA e"); + _cleanup_(RSA_freep) RSA *rsa_key = RSA_new(); if (!rsa_key) return log_openssl_errors("Failed to create new RSA"); @@ -989,7 +1009,7 @@ int ecc_ecdh(const EVP_PKEY *private_pkey, if (EVP_PKEY_derive(ctx, NULL, &shared_secret_size) <= 0) return log_openssl_errors("Failed to get ECC shared secret size"); - _cleanup_free_ void *shared_secret = malloc(shared_secret_size); + _cleanup_(erase_and_freep) void *shared_secret = malloc(shared_secret_size); if (!shared_secret) return log_oom_debug(); @@ -1128,6 +1148,258 @@ int string_hashsum( return 0; } # endif + +static int ecc_pkey_generate_volume_keys( + EVP_PKEY *pkey, + void **ret_decrypted_key, + size_t *ret_decrypted_key_size, + void **ret_saved_key, + size_t *ret_saved_key_size) { + + _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey_new = NULL; + _cleanup_(erase_and_freep) void *decrypted_key = NULL; + _cleanup_free_ unsigned char *saved_key = NULL; + size_t decrypted_key_size, saved_key_size; + int nid = NID_undef; + int r; + +#if OPENSSL_VERSION_MAJOR >= 3 + _cleanup_free_ char *curve_name = NULL; + size_t len = 0; + + if (EVP_PKEY_get_group_name(pkey, NULL, 0, &len) != 1 || len == 0) + return log_openssl_errors("Failed to determine PKEY group name length"); + + len++; + curve_name = new(char, len); + if (!curve_name) + return log_oom_debug(); + + if (EVP_PKEY_get_group_name(pkey, curve_name, len, &len) != 1) + return log_openssl_errors("Failed to get PKEY group name"); + + nid = OBJ_sn2nid(curve_name); +#else + EC_KEY *ec_key = EVP_PKEY_get0_EC_KEY(pkey); + if (!ec_key) + return log_openssl_errors("PKEY doesn't have EC_KEY associated"); + + if (EC_KEY_check_key(ec_key) != 1) + return log_openssl_errors("EC_KEY associated with PKEY is not valid"); + + nid = EC_GROUP_get_curve_name(EC_KEY_get0_group(ec_key)); +#endif + + r = ecc_pkey_new(nid, &pkey_new); + if (r < 0) + return log_debug_errno(r, "Failed to generate a new EC keypair: %m"); + + r = ecc_ecdh(pkey_new, pkey, &decrypted_key, &decrypted_key_size); + if (r < 0) + return log_debug_errno(r, "Failed to derive shared secret: %m"); + +#if OPENSSL_VERSION_MAJOR >= 3 + /* EVP_PKEY_get1_encoded_public_key() always returns uncompressed format of EC points. + See https://github.com/openssl/openssl/discussions/22835 */ + saved_key_size = EVP_PKEY_get1_encoded_public_key(pkey_new, &saved_key); + if (saved_key_size == 0) + return log_openssl_errors("Failed to convert the generated public key to SEC1 format"); +#else + EC_KEY *ec_key_new = EVP_PKEY_get0_EC_KEY(pkey_new); + if (!ec_key_new) + return log_openssl_errors("The generated key doesn't have associated EC_KEY"); + + if (EC_KEY_check_key(ec_key_new) != 1) + return log_openssl_errors("EC_KEY associated with the generated key is not valid"); + + saved_key_size = EC_POINT_point2oct(EC_KEY_get0_group(ec_key_new), + EC_KEY_get0_public_key(ec_key_new), + POINT_CONVERSION_UNCOMPRESSED, + NULL, 0, NULL); + if (saved_key_size == 0) + return log_openssl_errors("Failed to determine size of the generated public key"); + + saved_key = malloc(saved_key_size); + if (!saved_key) + return log_oom_debug(); + + saved_key_size = EC_POINT_point2oct(EC_KEY_get0_group(ec_key_new), + EC_KEY_get0_public_key(ec_key_new), + POINT_CONVERSION_UNCOMPRESSED, + saved_key, saved_key_size, NULL); + if (saved_key_size == 0) + return log_openssl_errors("Failed to convert the generated public key to SEC1 format"); +#endif + + *ret_decrypted_key = TAKE_PTR(decrypted_key); + *ret_decrypted_key_size = decrypted_key_size; + *ret_saved_key = TAKE_PTR(saved_key); + *ret_saved_key_size = saved_key_size; + return 0; +} + +static int rsa_pkey_generate_volume_keys( + EVP_PKEY *pkey, + void **ret_decrypted_key, + size_t *ret_decrypted_key_size, + void **ret_saved_key, + size_t *ret_saved_key_size) { + + _cleanup_(erase_and_freep) void *decrypted_key = NULL; + _cleanup_free_ void *saved_key = NULL; + size_t decrypted_key_size, saved_key_size; + int r; + + r = rsa_pkey_to_suitable_key_size(pkey, &decrypted_key_size); + if (r < 0) + return log_debug_errno(r, "Failed to determine RSA public key size."); + + log_debug("Generating %zu bytes random key.", decrypted_key_size); + + decrypted_key = malloc(decrypted_key_size); + if (!decrypted_key) + return log_oom_debug(); + + r = crypto_random_bytes(decrypted_key, decrypted_key_size); + if (r < 0) + return log_debug_errno(r, "Failed to generate random key: %m"); + + r = rsa_encrypt_bytes(pkey, decrypted_key, decrypted_key_size, &saved_key, &saved_key_size); + if (r < 0) + return log_debug_errno(r, "Failed to encrypt random key: %m"); + + *ret_decrypted_key = TAKE_PTR(decrypted_key); + *ret_decrypted_key_size = decrypted_key_size; + *ret_saved_key = TAKE_PTR(saved_key); + *ret_saved_key_size = saved_key_size; + return 0; +} + +int pkey_generate_volume_keys( + EVP_PKEY *pkey, + void **ret_decrypted_key, + size_t *ret_decrypted_key_size, + void **ret_saved_key, + size_t *ret_saved_key_size) { + + assert(pkey); + assert(ret_decrypted_key); + assert(ret_decrypted_key_size); + assert(ret_saved_key); + assert(ret_saved_key_size); + +#if OPENSSL_VERSION_MAJOR >= 3 + int type = EVP_PKEY_get_base_id(pkey); +#else + int type = EVP_PKEY_base_id(pkey); +#endif + switch (type) { + + case EVP_PKEY_RSA: + return rsa_pkey_generate_volume_keys(pkey, ret_decrypted_key, ret_decrypted_key_size, ret_saved_key, ret_saved_key_size); + + case EVP_PKEY_EC: + return ecc_pkey_generate_volume_keys(pkey, ret_decrypted_key, ret_decrypted_key_size, ret_saved_key, ret_saved_key_size); + + case NID_undef: + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to determine a type of public key."); + + default: + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unsupported public key type: %s", OBJ_nid2sn(type)); + } +} + +static int load_key_from_provider(const char *provider, const char *private_key_uri, EVP_PKEY **ret) { + + assert(provider); + assert(private_key_uri); + assert(ret); + +#if OPENSSL_VERSION_MAJOR >= 3 + /* Load the provider so that this can work without any custom written configuration in /etc/. + * Also load the 'default' as that seems to be the recommendation. */ + if (!OSSL_PROVIDER_try_load(/* ctx= */ NULL, provider, /* retain_fallbacks= */ true)) + return log_openssl_errors("Failed to load OpenSSL provider '%s'", provider); + if (!OSSL_PROVIDER_try_load(/* ctx= */ NULL, "default", /* retain_fallbacks= */ true)) + return log_openssl_errors("Failed to load OpenSSL provider 'default'"); + + _cleanup_(OSSL_STORE_closep) OSSL_STORE_CTX *store = OSSL_STORE_open( + private_key_uri, + /* ui_method= */ NULL, + /* ui_data= */ NULL, + /* post_process= */ NULL, + /* post_process_data= */ NULL); + if (!store) + return log_openssl_errors("Failed to open OpenSSL store via '%s'", private_key_uri); + + _cleanup_(OSSL_STORE_INFO_freep) OSSL_STORE_INFO *info = OSSL_STORE_load(store); + if (!info) + return log_openssl_errors("Failed to load OpenSSL store via '%s'", private_key_uri); + + _cleanup_(EVP_PKEY_freep) EVP_PKEY *private_key = OSSL_STORE_INFO_get1_PKEY(info); + if (!private_key) + return log_openssl_errors("Failed to load private key via '%s'", private_key_uri); + + *ret = TAKE_PTR(private_key); + + return 0; +#else + return -EOPNOTSUPP; +#endif +} + +static int load_key_from_engine(const char *engine, const char *private_key_uri, EVP_PKEY **ret) { + + assert(engine); + assert(private_key_uri); + assert(ret); + +#if !defined(OPENSSL_NO_ENGINE) && !defined(OPENSSL_NO_DEPRECATED_3_0) + DISABLE_WARNING_DEPRECATED_DECLARATIONS; + _cleanup_(ENGINE_freep) ENGINE *e = ENGINE_by_id(engine); + if (!e) + return log_openssl_errors("Failed to load signing engine '%s'", engine); + + if (ENGINE_init(e) == 0) + return log_openssl_errors("Failed to initialize signing engine '%s'", engine); + + _cleanup_(EVP_PKEY_freep) EVP_PKEY *private_key = ENGINE_load_private_key( + e, + private_key_uri, + /* ui_method= */ NULL, + /* callback_data= */ NULL); + if (!private_key) + return log_openssl_errors("Failed to load private key from '%s'", private_key_uri); + REENABLE_WARNING; + + *ret = TAKE_PTR(private_key); + + return 0; +#else + return -EOPNOTSUPP; +#endif +} + +int openssl_load_key_from_token( + KeySourceType private_key_source_type, + const char *private_key_source, + const char *private_key, + EVP_PKEY **ret) { + + assert(IN_SET(private_key_source_type, OPENSSL_KEY_SOURCE_ENGINE, OPENSSL_KEY_SOURCE_PROVIDER)); + assert(private_key_source); + assert(private_key); + + switch (private_key_source_type) { + + case OPENSSL_KEY_SOURCE_ENGINE: + return load_key_from_engine(private_key_source, private_key, ret); + case OPENSSL_KEY_SOURCE_PROVIDER: + return load_key_from_provider(private_key_source, private_key, ret); + default: + assert_not_reached(); + } +} #endif int x509_fingerprint(X509 *cert, uint8_t buffer[static SHA256_DIGEST_SIZE]) { @@ -1144,6 +1416,37 @@ int x509_fingerprint(X509 *cert, uint8_t buffer[static SHA256_DIGEST_SIZE]) { sha256_direct(der, dersz, buffer); return 0; #else - return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL is not supported, cannot calculate X509 fingerprint: %m"); + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL is not supported, cannot calculate X509 fingerprint."); #endif } + +int parse_openssl_key_source_argument( + const char *argument, + char **private_key_source, + KeySourceType *private_key_source_type) { + + KeySourceType type; + const char *e = NULL; + int r; + + assert(argument); + assert(private_key_source); + assert(private_key_source_type); + + if (streq(argument, "file")) + type = OPENSSL_KEY_SOURCE_FILE; + else if ((e = startswith(argument, "engine:"))) + type = OPENSSL_KEY_SOURCE_ENGINE; + else if ((e = startswith(argument, "provider:"))) + type = OPENSSL_KEY_SOURCE_PROVIDER; + else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid private key source '%s'", argument); + + r = free_and_strdup_warn(private_key_source, e); + if (r < 0) + return r; + + *private_key_source_type = type; + + return 0; +} diff --git a/src/shared/openssl-util.h b/src/shared/openssl-util.h index e3f34a8..1a89fcc 100644 --- a/src/shared/openssl-util.h +++ b/src/shared/openssl-util.h @@ -5,6 +5,16 @@ #include "macro.h" #include "sha256.h" +typedef enum KeySourceType { + OPENSSL_KEY_SOURCE_FILE, + OPENSSL_KEY_SOURCE_ENGINE, + OPENSSL_KEY_SOURCE_PROVIDER, + _OPENSSL_KEY_SOURCE_MAX, + _OPENSSL_KEY_SOURCE_INVALID = -EINVAL, +} KeySourceType; + +int parse_openssl_key_source_argument(const char *argument, char **private_key_source, KeySourceType *private_key_source_type); + #define X509_FINGERPRINT_SIZE SHA256_DIGEST_SIZE #if HAVE_OPENSSL @@ -25,6 +35,8 @@ # include <openssl/core_names.h> # include <openssl/kdf.h> # include <openssl/param_build.h> +# include <openssl/provider.h> +# include <openssl/store.h> # endif DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_MACRO(void*, OPENSSL_free, NULL); @@ -40,6 +52,8 @@ DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(PKCS7*, PKCS7_free, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(SSL*, SSL_free, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(BIO*, BIO_free, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_MD_CTX*, EVP_MD_CTX_free, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(ASN1_OCTET_STRING*, ASN1_OCTET_STRING_free, NULL); + #if OPENSSL_VERSION_MAJOR >= 3 DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_CIPHER*, EVP_CIPHER_free, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_KDF*, EVP_KDF_free, NULL); @@ -49,6 +63,8 @@ DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_MAC_CTX*, EVP_MAC_CTX_free, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_MD*, EVP_MD_free, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(OSSL_PARAM*, OSSL_PARAM_free, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(OSSL_PARAM_BLD*, OSSL_PARAM_BLD_free, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(OSSL_STORE_CTX*, OSSL_STORE_close, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(OSSL_STORE_INFO*, OSSL_STORE_INFO_free, NULL); #else DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EC_KEY*, EC_KEY_free, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(HMAC_CTX*, HMAC_CTX_free, NULL); @@ -108,10 +124,14 @@ int ecc_pkey_new(int curve_id, EVP_PKEY **ret); int ecc_ecdh(const EVP_PKEY *private_pkey, const EVP_PKEY *peer_pkey, void **ret_shared_secret, size_t *ret_shared_secret_size); +int pkey_generate_volume_keys(EVP_PKEY *pkey, void **ret_decrypted_key, size_t *ret_decrypted_key_size, void **ret_saved_key, size_t *ret_saved_key_size); + int pubkey_fingerprint(EVP_PKEY *pk, const EVP_MD *md, void **ret, size_t *ret_size); int digest_and_sign(const EVP_MD *md, EVP_PKEY *privkey, const void *data, size_t size, void **ret, size_t *ret_size); +int openssl_load_key_from_token(KeySourceType private_key_source_type, const char *private_key_source, const char *private_key, EVP_PKEY **ret); + #else typedef struct X509 X509; @@ -127,6 +147,15 @@ static inline void *EVP_PKEY_free(EVP_PKEY *p) { return NULL; } +static inline int openssl_load_key_from_token( + KeySourceType private_key_source_type, + const char *private_key_source, + const char *private_key, + EVP_PKEY **ret) { + + return -EOPNOTSUPP; +} + #endif DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(X509*, X509_free, NULL); diff --git a/src/shared/pager.c b/src/shared/pager.c index 19deefa..9b8ae76 100644 --- a/src/shared/pager.c +++ b/src/shared/pager.c @@ -175,7 +175,7 @@ void pager_open(PagerFlags flags) { * pager. If they didn't, use secure mode when under euid is changed. If $SYSTEMD_PAGERSECURE * wasn't explicitly set, and we autodetect the need for secure mode, only use the pager we * know to be good. */ - int use_secure_mode = getenv_bool_secure("SYSTEMD_PAGERSECURE"); + int use_secure_mode = secure_getenv_bool("SYSTEMD_PAGERSECURE"); bool trust_pager = use_secure_mode >= 0; if (use_secure_mode == -ENXIO) { uid_t uid; diff --git a/src/shared/pam-util.c b/src/shared/pam-util.c index f5814ef..3cbe431 100644 --- a/src/shared/pam-util.c +++ b/src/shared/pam-util.c @@ -14,6 +14,14 @@ #include "stdio-util.h" #include "string-util.h" +void pam_log_setup(void) { + /* Make sure we don't leak the syslog fd we open by opening/closing the fd each time. */ + log_set_open_when_needed(true); + + /* pam logs to syslog so let's make our generic logging functions do the same thing. */ + log_set_target(LOG_TARGET_SYSLOG); +} + int pam_syslog_errno(pam_handle_t *handle, int level, int error, const char *format, ...) { va_list ap; @@ -96,7 +104,9 @@ static void pam_bus_data_destroy(pam_handle_t *handle, void *data, int error_sta if (FLAGS_SET(error_status, PAM_DATA_SILENT) && d->bus && bus_origin_changed(d->bus)) /* Please adjust test/units/end.sh when updating the log message. */ - pam_syslog(handle, LOG_DEBUG, "Attempted to close sd-bus after fork whose connection is opened before the fork, this should not happen."); + pam_syslog(handle, LOG_DEBUG, + "Warning: cannot close sd-bus connection (%s) after fork when it was opened before the fork.", + strna(d->cache_id)); pam_bus_data_free(data); } @@ -177,6 +187,8 @@ int pam_acquire_bus_connection( if (r != PAM_SUCCESS) return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to set PAM bus data: @PAMERR@"); + pam_syslog(handle, LOG_DEBUG, "New sd-bus connection (%s) opened.", d->cache_id); + success: *ret_bus = sd_bus_ref(d->bus); @@ -205,7 +217,99 @@ int pam_release_bus_connection(pam_handle_t *handle, const char *module_name) { return PAM_SUCCESS; } +int pam_get_bus_data( + pam_handle_t *handle, + const char *module_name, + PamBusData **ret) { + + PamBusData *d = NULL; + _cleanup_free_ char *cache_id = NULL; + int r; + + assert(handle); + assert(module_name); + assert(ret); + + cache_id = pam_make_bus_cache_id(module_name); + if (!cache_id) + return pam_log_oom(handle); + + /* We cache the bus connection so that we can share it between the session and the authentication hooks */ + r = pam_get_data(handle, cache_id, (const void**) &d); + if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA)) + return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get bus connection: @PAMERR@"); + + *ret = d; + return PAM_SUCCESS; +} + void pam_cleanup_free(pam_handle_t *handle, void *data, int error_status) { /* A generic destructor for pam_set_data() that just frees the specified data */ free(data); } + +int pam_get_item_many_internal(pam_handle_t *handle, ...) { + va_list ap; + int r; + + va_start(ap, handle); + for (;;) { + int item_type = va_arg(ap, int); + + if (item_type <= 0) { + r = PAM_SUCCESS; + break; + } + + const void **value = ASSERT_PTR(va_arg(ap, const void **)); + + r = pam_get_item(handle, item_type, value); + if (!IN_SET(r, PAM_BAD_ITEM, PAM_SUCCESS)) + break; + } + va_end(ap); + + return r; +} + +int pam_prompt_graceful(pam_handle_t *handle, int style, char **ret_response, const char *fmt, ...) { + va_list args; + int r; + + assert(handle); + assert(fmt); + + /* This is just like pam_prompt(), but does not noisily (i.e. beyond LOG_DEBUG) log on its own, but leaves that to the caller */ + + _cleanup_free_ char *msg = NULL; + va_start(args, fmt); + r = vasprintf(&msg, fmt, args); + va_end(args); + if (r < 0) + return PAM_BUF_ERR; + + const struct pam_conv *conv = NULL; + r = pam_get_item(handle, PAM_CONV, (const void**) &conv); + if (!IN_SET(r, PAM_SUCCESS, PAM_BAD_ITEM)) + return pam_syslog_pam_error(handle, LOG_DEBUG, r, "Failed to get conversation function structure: @PAMERR@"); + if (!conv || !conv->conv) { + pam_syslog(handle, LOG_DEBUG, "No conversation function."); + return PAM_SYSTEM_ERR; + } + + struct pam_message message = { + .msg_style = style, + .msg = msg, + }; + const struct pam_message *pmessage = &message; + _cleanup_free_ struct pam_response *response = NULL; + r = conv->conv(1, &pmessage, &response, conv->appdata_ptr); + _cleanup_(erase_and_freep) char *rr = response ? response->resp : NULL; /* make sure string is freed + erased */ + if (r != PAM_SUCCESS) + return pam_syslog_pam_error(handle, LOG_DEBUG, r, "Conversation function failed: @PAMERR@"); + + if (ret_response) + *ret_response = TAKE_PTR(rr); + + return PAM_SUCCESS; +} diff --git a/src/shared/pam-util.h b/src/shared/pam-util.h index 5a05fb7..d627eb7 100644 --- a/src/shared/pam-util.h +++ b/src/shared/pam-util.h @@ -5,6 +5,8 @@ #include "sd-bus.h" +void pam_log_setup(void); + int pam_syslog_errno(pam_handle_t *handle, int level, int error, const char *format, ...) _printf_(4,5); int pam_syslog_pam_error(pam_handle_t *handle, int level, int error, const char *format, ...) _printf_(4,5); @@ -37,5 +39,12 @@ void pam_bus_data_disconnectp(PamBusData **d); * helps avoid a clash in the internal data structures of sd-bus. It will be used as key for cache items. */ int pam_acquire_bus_connection(pam_handle_t *handle, const char *module_name, sd_bus **ret_bus, PamBusData **ret_bus_data); int pam_release_bus_connection(pam_handle_t *handle, const char *module_name); +int pam_get_bus_data(pam_handle_t *handle, const char *module_name, PamBusData **ret); void pam_cleanup_free(pam_handle_t *handle, void *data, int error_status); + +int pam_get_item_many_internal(pam_handle_t *handle, ...); + +#define pam_get_item_many(handle, ...) pam_get_item_many_internal(handle, __VA_ARGS__, -1) + +int pam_prompt_graceful(pam_handle_t *handle, int style, char **ret_response, const char *fmt, ...) _printf_(4,5); diff --git a/src/shared/parse-helpers.c b/src/shared/parse-helpers.c index 9664b9c..ca6842d 100644 --- a/src/shared/parse-helpers.c +++ b/src/shared/parse-helpers.c @@ -4,6 +4,7 @@ #include "extract-word.h" #include "ip-protocol-list.h" #include "log.h" +#include "mountpoint-util.h" #include "parse-helpers.h" #include "parse-util.h" #include "path-util.h" @@ -11,47 +12,56 @@ int path_simplify_and_warn( char *path, - unsigned flag, + PathSimplifyWarnFlags flags, const char *unit, const char *filename, unsigned line, const char *lvalue) { - bool fatal = flag & PATH_CHECK_FATAL; + bool fatal = flags & PATH_CHECK_FATAL; + int level = fatal ? LOG_ERR : LOG_WARNING; - assert(!FLAGS_SET(flag, PATH_CHECK_ABSOLUTE | PATH_CHECK_RELATIVE)); + assert(path); + assert(!FLAGS_SET(flags, PATH_CHECK_ABSOLUTE | PATH_CHECK_RELATIVE)); + assert(lvalue); if (!utf8_is_valid(path)) return log_syntax_invalid_utf8(unit, LOG_ERR, filename, line, path); - if (flag & (PATH_CHECK_ABSOLUTE | PATH_CHECK_RELATIVE)) { + if (flags & (PATH_CHECK_ABSOLUTE | PATH_CHECK_RELATIVE)) { bool absolute; absolute = path_is_absolute(path); - if (!absolute && (flag & PATH_CHECK_ABSOLUTE)) - return log_syntax(unit, LOG_ERR, filename, line, SYNTHETIC_ERRNO(EINVAL), + if (!absolute && (flags & PATH_CHECK_ABSOLUTE)) + return log_syntax(unit, level, filename, line, SYNTHETIC_ERRNO(EINVAL), "%s= path is not absolute%s: %s", lvalue, fatal ? "" : ", ignoring", path); - if (absolute && (flag & PATH_CHECK_RELATIVE)) - return log_syntax(unit, LOG_ERR, filename, line, SYNTHETIC_ERRNO(EINVAL), + if (absolute && (flags & PATH_CHECK_RELATIVE)) + return log_syntax(unit, level, filename, line, SYNTHETIC_ERRNO(EINVAL), "%s= path is absolute%s: %s", lvalue, fatal ? "" : ", ignoring", path); } - path_simplify_full(path, flag & PATH_KEEP_TRAILING_SLASH ? PATH_SIMPLIFY_KEEP_TRAILING_SLASH : 0); + path_simplify_full(path, flags & PATH_KEEP_TRAILING_SLASH ? PATH_SIMPLIFY_KEEP_TRAILING_SLASH : 0); if (!path_is_valid(path)) - return log_syntax(unit, LOG_ERR, filename, line, SYNTHETIC_ERRNO(EINVAL), + return log_syntax(unit, level, filename, line, SYNTHETIC_ERRNO(EINVAL), "%s= path has invalid length (%zu bytes)%s.", lvalue, strlen(path), fatal ? "" : ", ignoring"); if (!path_is_normalized(path)) - return log_syntax(unit, LOG_ERR, filename, line, SYNTHETIC_ERRNO(EINVAL), + return log_syntax(unit, level, filename, line, SYNTHETIC_ERRNO(EINVAL), "%s= path is not normalized%s: %s", lvalue, fatal ? "" : ", ignoring", path); + if (FLAGS_SET(flags, PATH_CHECK_NON_API_VFS) && path_below_api_vfs(path)) + return log_syntax(unit, level, filename, line, SYNTHETIC_ERRNO(EINVAL), + "%s= path is below API VFS%s: %s", + lvalue, fatal ? ", refusing" : ", ignoring", + path); + return 0; } @@ -102,6 +112,8 @@ static int parse_ip_ports_token( uint16_t *nr_ports, uint16_t *port_min) { + int r; + assert(token); assert(nr_ports); assert(port_min); @@ -110,7 +122,7 @@ static int parse_ip_ports_token( *nr_ports = *port_min = 0; else { uint16_t mn = 0, mx = 0; - int r = parse_ip_port_range(token, &mn, &mx); + r = parse_ip_port_range(token, &mn, &mx, /* allow_zero = */ true); if (r < 0) return r; @@ -194,6 +206,7 @@ int parse_socket_bind_item( *ip_protocol = proto; *nr_ports = nr; *port_min = mn; + return 0; } diff --git a/src/shared/parse-helpers.h b/src/shared/parse-helpers.h index 3e4ad3c..6d1034b 100644 --- a/src/shared/parse-helpers.h +++ b/src/shared/parse-helpers.h @@ -3,27 +3,28 @@ #include <stdint.h> -enum { - PATH_CHECK_FATAL = 1 << 0, /* If not set, then error message is appended with 'ignoring'. */ - PATH_CHECK_ABSOLUTE = 1 << 1, - PATH_CHECK_RELATIVE = 1 << 2, +typedef enum PathSimplifyWarnFlags { + PATH_CHECK_FATAL = 1 << 0, /* If not set, then error message is appended with 'ignoring'. */ + PATH_CHECK_ABSOLUTE = 1 << 1, + PATH_CHECK_RELATIVE = 1 << 2, PATH_KEEP_TRAILING_SLASH = 1 << 3, -}; + PATH_CHECK_NON_API_VFS = 1 << 4, +} PathSimplifyWarnFlags; int path_simplify_and_warn( char *path, - unsigned flag, + PathSimplifyWarnFlags flags, const char *unit, const char *filename, unsigned line, const char *lvalue); int parse_socket_bind_item( - const char *str, - int *address_family, - int *ip_protocol, - uint16_t *nr_ports, - uint16_t *port_min); + const char *str, + int *address_family, + int *ip_protocol, + uint16_t *nr_ports, + uint16_t *port_min); int config_parse_path_or_ignore( const char *unit, diff --git a/src/shared/password-quality-util-passwdqc.c b/src/shared/password-quality-util-passwdqc.c index adfc14d..764b772 100644 --- a/src/shared/password-quality-util-passwdqc.c +++ b/src/shared/password-quality-util-passwdqc.c @@ -12,14 +12,19 @@ static void *passwdqc_dl = NULL; -void (*sym_passwdqc_params_reset)(passwdqc_params_t *params); -int (*sym_passwdqc_params_load)(passwdqc_params_t *params, char **reason, const char *pathname); -int (*sym_passwdqc_params_parse)(passwdqc_params_t *params, char **reason, int argc, const char *const *argv); -void (*sym_passwdqc_params_free)(passwdqc_params_t *params); -const char *(*sym_passwdqc_check)(const passwdqc_params_qc_t *params, const char *newpass, const char *oldpass, const struct passwd *pw); -char *(*sym_passwdqc_random)(const passwdqc_params_qc_t *params); +DLSYM_FUNCTION(passwdqc_params_reset); +DLSYM_FUNCTION(passwdqc_params_load); +DLSYM_FUNCTION(passwdqc_params_parse); +DLSYM_FUNCTION(passwdqc_params_free); +DLSYM_FUNCTION(passwdqc_check); +DLSYM_FUNCTION(passwdqc_random); int dlopen_passwdqc(void) { + ELF_NOTE_DLOPEN("passwdqc", + "Support for password quality checks", + ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + "libpasswdqc.so.1"); + return dlopen_many_sym_or_warn( &passwdqc_dl, "libpasswdqc.so.1", LOG_DEBUG, DLSYM_ARG(passwdqc_params_reset), diff --git a/src/shared/password-quality-util-passwdqc.h b/src/shared/password-quality-util-passwdqc.h index 0d528d2..9911888 100644 --- a/src/shared/password-quality-util-passwdqc.h +++ b/src/shared/password-quality-util-passwdqc.h @@ -6,12 +6,14 @@ #if HAVE_PASSWDQC #include <passwdqc.h> -extern void (*sym_passwdqc_params_reset)(passwdqc_params_t *params); -extern int (*sym_passwdqc_params_load)(passwdqc_params_t *params, char **reason, const char *pathname); -extern int (*sym_passwdqc_params_parse)(passwdqc_params_t *params, char **reason, int argc, const char *const *argv); -extern void (*sym_passwdqc_params_free)(passwdqc_params_t *params); -extern const char *(*sym_passwdqc_check)(const passwdqc_params_qc_t *params, const char *newpass, const char *oldpass, const struct passwd *pw); -extern char *(*sym_passwdqc_random)(const passwdqc_params_qc_t *params); +#include "dlfcn-util.h" + +DLSYM_PROTOTYPE(passwdqc_params_reset); +DLSYM_PROTOTYPE(passwdqc_params_load); +DLSYM_PROTOTYPE(passwdqc_params_parse); +DLSYM_PROTOTYPE(passwdqc_params_free); +DLSYM_PROTOTYPE(passwdqc_check); +DLSYM_PROTOTYPE(passwdqc_random); int dlopen_passwdqc(void); diff --git a/src/shared/password-quality-util-pwquality.c b/src/shared/password-quality-util-pwquality.c index 80f7d58..7456469 100644 --- a/src/shared/password-quality-util-pwquality.c +++ b/src/shared/password-quality-util-pwquality.c @@ -14,16 +14,21 @@ static void *pwquality_dl = NULL; -int (*sym_pwquality_check)(pwquality_settings_t *pwq, const char *password, const char *oldpassword, const char *user, void **auxerror); -pwquality_settings_t *(*sym_pwquality_default_settings)(void); -void (*sym_pwquality_free_settings)(pwquality_settings_t *pwq); -int (*sym_pwquality_generate)(pwquality_settings_t *pwq, int entropy_bits, char **password); -int (*sym_pwquality_get_str_value)(pwquality_settings_t *pwq, int setting, const char **value); -int (*sym_pwquality_read_config)(pwquality_settings_t *pwq, const char *cfgfile, void **auxerror); -int (*sym_pwquality_set_int_value)(pwquality_settings_t *pwq, int setting, int value); -const char* (*sym_pwquality_strerror)(char *buf, size_t len, int errcode, void *auxerror); +DLSYM_FUNCTION(pwquality_check); +DLSYM_FUNCTION(pwquality_default_settings); +DLSYM_FUNCTION(pwquality_free_settings); +DLSYM_FUNCTION(pwquality_generate); +DLSYM_FUNCTION(pwquality_get_str_value); +DLSYM_FUNCTION(pwquality_read_config); +DLSYM_FUNCTION(pwquality_set_int_value); +DLSYM_FUNCTION(pwquality_strerror); int dlopen_pwquality(void) { + ELF_NOTE_DLOPEN("pwquality", + "Support for password quality checks", + ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + "libpwquality.so.1"); + return dlopen_many_sym_or_warn( &pwquality_dl, "libpwquality.so.1", LOG_DEBUG, DLSYM_ARG(pwquality_check), @@ -101,7 +106,6 @@ int suggest_passwords(void) { _cleanup_strv_free_erase_ char **suggestions = NULL; _cleanup_(erase_and_freep) char *joined = NULL; char buf[PWQ_MAX_ERROR_MESSAGE_LEN]; - size_t i; int r; r = pwq_allocate_context(&pwq); @@ -115,7 +119,7 @@ int suggest_passwords(void) { if (!suggestions) return log_oom(); - for (i = 0; i < N_SUGGESTIONS; i++) { + for (size_t i = 0; i < N_SUGGESTIONS; i++) { r = sym_pwquality_generate(pwq, 64, suggestions + i); if (r < 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to generate password, ignoring: %s", @@ -145,13 +149,10 @@ int check_password_quality(const char *password, const char *old, const char *us r = sym_pwquality_check(pwq, password, old, username, &auxerror); if (r < 0) { if (ret_error) { - _cleanup_free_ char *e = NULL; - - e = strdup(sym_pwquality_strerror(buf, sizeof(buf), r, auxerror)); - if (!e) - return -ENOMEM; - - *ret_error = TAKE_PTR(e); + r = strdup_to(ret_error, + sym_pwquality_strerror(buf, sizeof(buf), r, auxerror)); + if (r < 0) + return r; } return 0; /* all bad */ diff --git a/src/shared/password-quality-util-pwquality.h b/src/shared/password-quality-util-pwquality.h index a420b0d..4c2517b 100644 --- a/src/shared/password-quality-util-pwquality.h +++ b/src/shared/password-quality-util-pwquality.h @@ -8,14 +8,16 @@ #include <sys/types.h> #include <pwquality.h> -extern int (*sym_pwquality_check)(pwquality_settings_t *pwq, const char *password, const char *oldpassword, const char *user, void **auxerror); -extern pwquality_settings_t *(*sym_pwquality_default_settings)(void); -extern void (*sym_pwquality_free_settings)(pwquality_settings_t *pwq); -extern int (*sym_pwquality_generate)(pwquality_settings_t *pwq, int entropy_bits, char **password); -extern int (*sym_pwquality_get_str_value)(pwquality_settings_t *pwq, int setting, const char **value); -extern int (*sym_pwquality_read_config)(pwquality_settings_t *pwq, const char *cfgfile, void **auxerror); -extern int (*sym_pwquality_set_int_value)(pwquality_settings_t *pwq, int setting, int value); -extern const char* (*sym_pwquality_strerror)(char *buf, size_t len, int errcode, void *auxerror); +#include "dlfcn-util.h" + +DLSYM_PROTOTYPE(pwquality_check); +DLSYM_PROTOTYPE(pwquality_default_settings); +DLSYM_PROTOTYPE(pwquality_free_settings); +DLSYM_PROTOTYPE(pwquality_generate); +DLSYM_PROTOTYPE(pwquality_get_str_value); +DLSYM_PROTOTYPE(pwquality_read_config); +DLSYM_PROTOTYPE(pwquality_set_int_value); +DLSYM_PROTOTYPE(pwquality_strerror); int dlopen_pwquality(void); diff --git a/src/shared/pcre2-util.c b/src/shared/pcre2-util.c index 578b02d..7deb64f 100644 --- a/src/shared/pcre2-util.c +++ b/src/shared/pcre2-util.c @@ -7,13 +7,13 @@ #if HAVE_PCRE2 static void *pcre2_dl = NULL; -pcre2_match_data* (*sym_pcre2_match_data_create)(uint32_t, pcre2_general_context *); -void (*sym_pcre2_match_data_free)(pcre2_match_data *); -void (*sym_pcre2_code_free)(pcre2_code *); -pcre2_code* (*sym_pcre2_compile)(PCRE2_SPTR, PCRE2_SIZE, uint32_t, int *, PCRE2_SIZE *, pcre2_compile_context *); -int (*sym_pcre2_get_error_message)(int, PCRE2_UCHAR *, PCRE2_SIZE); -int (*sym_pcre2_match)(const pcre2_code *, PCRE2_SPTR, PCRE2_SIZE, PCRE2_SIZE, uint32_t, pcre2_match_data *, pcre2_match_context *); -PCRE2_SIZE* (*sym_pcre2_get_ovector_pointer)(pcre2_match_data *); +DLSYM_FUNCTION(pcre2_match_data_create); +DLSYM_FUNCTION(pcre2_match_data_free); +DLSYM_FUNCTION(pcre2_code_free); +DLSYM_FUNCTION(pcre2_compile); +DLSYM_FUNCTION(pcre2_get_error_message); +DLSYM_FUNCTION(pcre2_match); +DLSYM_FUNCTION(pcre2_get_ovector_pointer); DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR( pcre2_code_hash_ops_free, @@ -27,6 +27,11 @@ const struct hash_ops pcre2_code_hash_ops_free = {}; int dlopen_pcre2(void) { #if HAVE_PCRE2 + ELF_NOTE_DLOPEN("pcre2", + "Support for regular expressions", + ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + "libpcre2-8.so.0"); + /* So here's something weird: PCRE2 actually renames the symbols exported by the library via C * macros, so that the exported symbols carry a suffix "_8" but when used from C the suffix is * gone. In the argument list below we ignore this mangling. Surprisingly (at least to me), we diff --git a/src/shared/pcre2-util.h b/src/shared/pcre2-util.h index f1e744d..27e2b02 100644 --- a/src/shared/pcre2-util.h +++ b/src/shared/pcre2-util.h @@ -6,16 +6,18 @@ #if HAVE_PCRE2 +#include "dlfcn-util.h" + #define PCRE2_CODE_UNIT_WIDTH 8 #include <pcre2.h> -extern pcre2_match_data* (*sym_pcre2_match_data_create)(uint32_t, pcre2_general_context *); -extern void (*sym_pcre2_match_data_free)(pcre2_match_data *); -extern void (*sym_pcre2_code_free)(pcre2_code *); -extern pcre2_code* (*sym_pcre2_compile)(PCRE2_SPTR, PCRE2_SIZE, uint32_t, int *, PCRE2_SIZE *, pcre2_compile_context *); -extern int (*sym_pcre2_get_error_message)(int, PCRE2_UCHAR *, PCRE2_SIZE); -extern int (*sym_pcre2_match)(const pcre2_code *, PCRE2_SPTR, PCRE2_SIZE, PCRE2_SIZE, uint32_t, pcre2_match_data *, pcre2_match_context *); -extern PCRE2_SIZE* (*sym_pcre2_get_ovector_pointer)(pcre2_match_data *); +DLSYM_PROTOTYPE(pcre2_match_data_create); +DLSYM_PROTOTYPE(pcre2_match_data_free); +DLSYM_PROTOTYPE(pcre2_code_free); +DLSYM_PROTOTYPE(pcre2_compile); +DLSYM_PROTOTYPE(pcre2_get_error_message); +DLSYM_PROTOTYPE(pcre2_match); +DLSYM_PROTOTYPE(pcre2_get_ovector_pointer); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(pcre2_match_data*, sym_pcre2_match_data_free, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(pcre2_code*, sym_pcre2_code_free, NULL); diff --git a/src/shared/pcrextend-util.c b/src/shared/pcrextend-util.c index fa066a4..5ec9a06 100644 --- a/src/shared/pcrextend-util.c +++ b/src/shared/pcrextend-util.c @@ -101,7 +101,7 @@ int pcrextend_file_system_word(const char *path, char **ret_word, char **ret_nor if (r < 0) return log_error_errno(r, "Failed to determine if path '%s' is mount point: %m", normalized_path); if (r == 0) - return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR), "Specified path '%s' is not a mount point, refusing: %m", normalized_path); + return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR), "Specified path '%s' is not a mount point, refusing.", normalized_path); normalized_escaped = xescape(normalized_path, ":"); /* Avoid ambiguity around ":" */ if (!normalized_escaped) diff --git a/src/shared/pe-binary.c b/src/shared/pe-binary.c index 4c05323..997e0e4 100644 --- a/src/shared/pe-binary.c +++ b/src/shared/pe-binary.c @@ -234,8 +234,9 @@ bool pe_is_uki(const PeHeader *pe_header, const IMAGE_SECTION_HEADER *sections) if (le16toh(pe_header->optional.Subsystem) != IMAGE_SUBSYSTEM_EFI_APPLICATION) return false; + /* Note that the UKI spec only requires .linux, but we are stricter here, and require .osrel too, + * since for sd-boot it just doesn't make sense to not have that. */ return pe_header_find_section(pe_header, sections, ".osrel") && - pe_header_find_section(pe_header, sections, ".linux") && - pe_header_find_section(pe_header, sections, ".initrd"); + pe_header_find_section(pe_header, sections, ".linux"); } diff --git a/src/shared/pkcs11-util.c b/src/shared/pkcs11-util.c index 6e88dc3..b5cd9a3 100644 --- a/src/shared/pkcs11-util.c +++ b/src/shared/pkcs11-util.c @@ -43,22 +43,29 @@ bool pkcs11_uri_valid(const char *uri) { static void *p11kit_dl = NULL; -char *(*sym_p11_kit_module_get_name)(CK_FUNCTION_LIST *module); -void (*sym_p11_kit_modules_finalize_and_release)(CK_FUNCTION_LIST **modules); -CK_FUNCTION_LIST **(*sym_p11_kit_modules_load_and_initialize)(int flags); -const char *(*sym_p11_kit_strerror)(CK_RV rv); -int (*sym_p11_kit_uri_format)(P11KitUri *uri, P11KitUriType uri_type, char **string); -void (*sym_p11_kit_uri_free)(P11KitUri *uri); -CK_ATTRIBUTE_PTR (*sym_p11_kit_uri_get_attributes)(P11KitUri *uri, CK_ULONG *n_attrs); -CK_INFO_PTR (*sym_p11_kit_uri_get_module_info)(P11KitUri *uri); -CK_SLOT_INFO_PTR (*sym_p11_kit_uri_get_slot_info)(P11KitUri *uri); -CK_TOKEN_INFO_PTR (*sym_p11_kit_uri_get_token_info)(P11KitUri *uri); -int (*sym_p11_kit_uri_match_token_info)(const P11KitUri *uri, const CK_TOKEN_INFO *token_info); -const char *(*sym_p11_kit_uri_message)(int code); -P11KitUri *(*sym_p11_kit_uri_new)(void); -int (*sym_p11_kit_uri_parse)(const char *string, P11KitUriType uri_type, P11KitUri *uri); +DLSYM_FUNCTION(p11_kit_module_get_name); +DLSYM_FUNCTION(p11_kit_modules_finalize_and_release); +DLSYM_FUNCTION(p11_kit_modules_load_and_initialize); +DLSYM_FUNCTION(p11_kit_strerror); +DLSYM_FUNCTION(p11_kit_uri_format); +DLSYM_FUNCTION(p11_kit_uri_free); +DLSYM_FUNCTION(p11_kit_uri_get_attributes); +DLSYM_FUNCTION(p11_kit_uri_get_attribute); +DLSYM_FUNCTION(p11_kit_uri_set_attribute); +DLSYM_FUNCTION(p11_kit_uri_get_module_info); +DLSYM_FUNCTION(p11_kit_uri_get_slot_info); +DLSYM_FUNCTION(p11_kit_uri_get_token_info); +DLSYM_FUNCTION(p11_kit_uri_match_token_info); +DLSYM_FUNCTION(p11_kit_uri_message); +DLSYM_FUNCTION(p11_kit_uri_new); +DLSYM_FUNCTION(p11_kit_uri_parse); int dlopen_p11kit(void) { + ELF_NOTE_DLOPEN("p11-kit", + "Support for PKCS11 hardware tokens", + ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + "libp11-kit.so.0"); + return dlopen_many_sym_or_warn( &p11kit_dl, "libp11-kit.so.0", LOG_DEBUG, @@ -69,6 +76,8 @@ int dlopen_p11kit(void) { DLSYM_ARG(p11_kit_uri_format), DLSYM_ARG(p11_kit_uri_free), DLSYM_ARG(p11_kit_uri_get_attributes), + DLSYM_ARG(p11_kit_uri_get_attribute), + DLSYM_ARG(p11_kit_uri_set_attribute), DLSYM_ARG(p11_kit_uri_get_module_info), DLSYM_ARG(p11_kit_uri_get_slot_info), DLSYM_ARG(p11_kit_uri_get_token_info), @@ -287,12 +296,11 @@ int pkcs11_token_login( CK_SLOT_ID slotid, const CK_TOKEN_INFO *token_info, const char *friendly_name, - const char *icon_name, - const char *key_name, - const char *credential_name, + const char *askpw_icon, + const char *askpw_keyring, + const char *askpw_credential, usec_t until, - AskPasswordFlags ask_password_flags, - bool headless, + AskPasswordFlags askpw_flags, char **ret_used_pin) { _cleanup_free_ char *token_uri_string = NULL, *token_uri_escaped = NULL, *id = NULL, *token_label = NULL; @@ -347,7 +355,7 @@ int pkcs11_token_login( if (!passwords) return log_oom(); - } else if (headless) + } else if (FLAGS_SET(askpw_flags, ASK_PASSWORD_HEADLESS)) return log_error_errno(SYNTHETIC_ERRNO(ENOPKG), "PIN querying disabled via 'headless' option. Use the 'PIN' environment variable."); else { _cleanup_free_ char *text = NULL; @@ -371,8 +379,16 @@ int pkcs11_token_login( if (r < 0) return log_oom(); + AskPasswordRequest req = { + .message = text, + .icon = askpw_icon, + .id = id, + .keyring = askpw_keyring, + .credential = askpw_credential, + }; + /* We never cache PINs, simply because it's fatal if we use wrong PINs, since usually there are only 3 tries */ - r = ask_password_auto(text, icon_name, id, key_name, credential_name, until, ask_password_flags, &passwords); + r = ask_password_auto(&req, until, askpw_flags, &passwords); if (r < 0) return log_error_errno(r, "Failed to query PIN for security token '%s': %m", token_label); } @@ -523,13 +539,294 @@ int pkcs11_token_find_x509_certificate( } #if HAVE_OPENSSL +static int read_public_key_info( + CK_FUNCTION_LIST *m, + CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE object, + EVP_PKEY **ret_pkey) { + + CK_ATTRIBUTE attribute = { CKA_PUBLIC_KEY_INFO, NULL_PTR, 0 }; + _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; + CK_RV rv; + + rv = m->C_GetAttributeValue(session, object, &attribute, 1); + if (rv != CKR_OK) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "Failed to get size of CKA_PUBLIC_KEY_INFO: %s", sym_p11_kit_strerror(rv)); + + if (attribute.ulValueLen == 0) + return log_debug_errno(SYNTHETIC_ERRNO(ENOENT), "CKA_PUBLIC_KEY_INFO is empty"); + + _cleanup_free_ void *buffer = malloc(attribute.ulValueLen); + if (!buffer) + return log_oom_debug(); + + attribute.pValue = buffer; + + rv = m->C_GetAttributeValue(session, object, &attribute, 1); + if (rv != CKR_OK) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), + "Failed to read CKA_PUBLIC_KEY_INFO: %s", sym_p11_kit_strerror(rv)); + + const unsigned char *value = attribute.pValue; + pkey = d2i_PUBKEY(NULL, &value, attribute.ulValueLen); + if (!pkey) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Failed to parse CKA_PUBLIC_KEY_INFO"); + + *ret_pkey = TAKE_PTR(pkey); + return 0; +} + +int pkcs11_token_read_public_key( + CK_FUNCTION_LIST *m, + CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE object, + EVP_PKEY **ret_pkey) { + + _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; + CK_RV rv; + int r; + + r = read_public_key_info(m, session, object, &pkey); + if (r >= 0) { + *ret_pkey = TAKE_PTR(pkey); + return 0; + } + + CK_KEY_TYPE key_type; + CK_ATTRIBUTE attribute = { CKA_KEY_TYPE, &key_type, sizeof(key_type) }; + + rv = m->C_GetAttributeValue(session, object, &attribute, 1); + if (rv != CKR_OK) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), + "Failed to get CKA_KEY_TYPE of a public key: %s", sym_p11_kit_strerror(rv)); + + switch (key_type) { + case CKK_RSA: { + CK_ATTRIBUTE rsa_attributes[] = { + { CKA_MODULUS, NULL_PTR, 0 }, + { CKA_PUBLIC_EXPONENT, NULL_PTR, 0 }, + }; + + rv = m->C_GetAttributeValue(session, object, rsa_attributes, ELEMENTSOF(rsa_attributes)); + if (rv != CKR_OK) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), + "Failed to get size of attributes of an RSA public key: %s", sym_p11_kit_strerror(rv)); + + if (rsa_attributes[0].ulValueLen == 0) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "An RSA public key has empty CKA_MODULUS."); + + _cleanup_free_ void *modulus = malloc(rsa_attributes[0].ulValueLen); + if (!modulus) + return log_oom_debug(); + + rsa_attributes[0].pValue = modulus; + + if (rsa_attributes[1].ulValueLen == 0) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "An RSA public key has empty CKA_PUBLIC_EXPONENT."); + + _cleanup_free_ void *public_exponent = malloc(rsa_attributes[1].ulValueLen); + if (!public_exponent) + return log_oom_debug(); + + rsa_attributes[1].pValue = public_exponent; + + rv = m->C_GetAttributeValue(session, object, rsa_attributes, ELEMENTSOF(rsa_attributes)); + if (rv != CKR_OK) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), + "Failed to get attributes of an RSA public key: %s", sym_p11_kit_strerror(rv)); + + size_t n_size = rsa_attributes[0].ulValueLen, e_size = rsa_attributes[1].ulValueLen; + r = rsa_pkey_from_n_e(rsa_attributes[0].pValue, n_size, rsa_attributes[1].pValue, e_size, &pkey); + if (r < 0) + return log_debug_errno(r, "Failed to create an EVP_PKEY from RSA parameters."); + + break; + } + case CKK_EC: { + CK_ATTRIBUTE ec_attributes[] = { + { CKA_EC_PARAMS, NULL_PTR, 0 }, + { CKA_EC_POINT, NULL_PTR, 0 }, + }; + + rv = m->C_GetAttributeValue(session, object, ec_attributes, ELEMENTSOF(ec_attributes)); + if (rv != CKR_OK) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), + "Failed to get size of attributes of an EC public key: %s", sym_p11_kit_strerror(rv)); + + if (ec_attributes[0].ulValueLen == 0) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "An EC public key has empty CKA_EC_PARAMS."); + + _cleanup_free_ void *ec_group = malloc(ec_attributes[0].ulValueLen); + if (!ec_group) + return log_oom_debug(); + + ec_attributes[0].pValue = ec_group; + + if (ec_attributes[1].ulValueLen == 0) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "An EC public key has empty CKA_EC_POINT."); + + _cleanup_free_ void *ec_point = malloc(ec_attributes[1].ulValueLen); + if (!ec_point) + return log_oom_debug(); + + ec_attributes[1].pValue = ec_point; + + rv = m->C_GetAttributeValue(session, object, ec_attributes, ELEMENTSOF(ec_attributes)); + if (rv != CKR_OK) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), + "Failed to get attributes of an EC public key: %s", sym_p11_kit_strerror(rv)); + + _cleanup_(EC_GROUP_freep) EC_GROUP *group = NULL; + _cleanup_(ASN1_OCTET_STRING_freep) ASN1_OCTET_STRING *os = NULL; + + const unsigned char *ec_params_value = ec_attributes[0].pValue; + group = d2i_ECPKParameters(NULL, &ec_params_value, ec_attributes[0].ulValueLen); + if (!group) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Unable to decode CKA_EC_PARAMS."); + + const unsigned char *ec_point_value = ec_attributes[1].pValue; + os = d2i_ASN1_OCTET_STRING(NULL, &ec_point_value, ec_attributes[1].ulValueLen); + if (!os) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Unable to decode CKA_EC_POINT."); + +#if OPENSSL_VERSION_MAJOR >= 3 + _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL); + if (!ctx) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to create an EVP_PKEY_CTX for EC."); + + if (EVP_PKEY_fromdata_init(ctx) != 1) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to init an EVP_PKEY_CTX for EC."); + + OSSL_PARAM ec_params[8] = { + OSSL_PARAM_octet_string(OSSL_PKEY_PARAM_PUB_KEY, os->data, os->length) + }; + + _cleanup_free_ void *order = NULL, *p = NULL, *a = NULL, *b = NULL, *generator = NULL; + size_t order_size, p_size, a_size, b_size, generator_size; + + int nid = EC_GROUP_get_curve_name(group); + if (nid != NID_undef) { + const char* name = OSSL_EC_curve_nid2name(nid); + ec_params[1] = OSSL_PARAM_construct_utf8_string(OSSL_PKEY_PARAM_GROUP_NAME, (char*)name, strlen(name)); + ec_params[2] = OSSL_PARAM_construct_end(); + } else { + const char *field_type = EC_GROUP_get_field_type(group) == NID_X9_62_prime_field ? + "prime-field" : "characteristic-two-field"; + + const BIGNUM *bn_order = EC_GROUP_get0_order(group); + + _cleanup_(BN_CTX_freep) BN_CTX *bnctx = BN_CTX_new(); + if (!bnctx) + return log_oom_debug(); + + _cleanup_(BN_freep) BIGNUM *bn_p = BN_new(); + if (!bn_p) + return log_oom_debug(); + + _cleanup_(BN_freep) BIGNUM *bn_a = BN_new(); + if (!bn_a) + return log_oom_debug(); + + _cleanup_(BN_freep) BIGNUM *bn_b = BN_new(); + if (!bn_b) + return log_oom_debug(); + + if (EC_GROUP_get_curve(group, bn_p, bn_a, bn_b, bnctx) != 1) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to extract EC parameters from EC_GROUP."); + + order_size = BN_num_bytes(bn_order); + p_size = BN_num_bytes(bn_p); + a_size = BN_num_bytes(bn_a); + b_size = BN_num_bytes(bn_b); + + order = malloc(order_size); + if (!order) + return log_oom_debug(); + + p = malloc(p_size); + if (!p) + return log_oom_debug(); + + a = malloc(a_size); + if (!a) + return log_oom_debug(); + + b = malloc(b_size); + if (!b) + return log_oom_debug(); + + if (BN_bn2nativepad(bn_order, order, order_size) <= 0 || + BN_bn2nativepad(bn_p, p, p_size) <= 0 || + BN_bn2nativepad(bn_a, a, a_size) <= 0 || + BN_bn2nativepad(bn_b, b, b_size) <= 0 ) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to store EC parameters in native byte order."); + + const EC_POINT *point_gen = EC_GROUP_get0_generator(group); + generator_size = EC_POINT_point2oct(group, point_gen, POINT_CONVERSION_UNCOMPRESSED, NULL, 0, bnctx); + if (generator_size == 0) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to determine size of a EC generator."); + + generator = malloc(generator_size); + if (!generator) + return log_oom_debug(); + + generator_size = EC_POINT_point2oct(group, point_gen, POINT_CONVERSION_UNCOMPRESSED, generator, generator_size, bnctx); + if (generator_size == 0) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to convert a EC generator to octet string."); + + ec_params[1] = OSSL_PARAM_construct_utf8_string(OSSL_PKEY_PARAM_EC_FIELD_TYPE, (char*)field_type, strlen(field_type)); + ec_params[2] = OSSL_PARAM_construct_octet_string(OSSL_PKEY_PARAM_EC_GENERATOR, generator, generator_size); + ec_params[3] = OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_EC_ORDER, order, order_size); + ec_params[4] = OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_EC_P, p, p_size); + ec_params[5] = OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_EC_A, a, a_size); + ec_params[6] = OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_EC_B, b, b_size); + ec_params[7] = OSSL_PARAM_construct_end(); + } + + if (EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_PUBLIC_KEY, ec_params) != 1) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to create EVP_PKEY from EC parameters."); +#else + _cleanup_(EC_POINT_freep) EC_POINT *point = EC_POINT_new(group); + if (!point) + return log_oom_debug(); + + if (EC_POINT_oct2point(group, point, os->data, os->length, NULL) != 1) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Unable to decode CKA_EC_POINT."); + + _cleanup_(EC_KEY_freep) EC_KEY *ec_key = EC_KEY_new(); + if (!ec_key) + return log_oom_debug(); + + if (EC_KEY_set_group(ec_key, group) != 1) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set group for EC_KEY."); + + if (EC_KEY_set_public_key(ec_key, point) != 1) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set public key for EC_KEY."); + + pkey = EVP_PKEY_new(); + if (!pkey) + return log_oom_debug(); + + if (EVP_PKEY_set1_EC_KEY(pkey, ec_key) != 1) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to assign EC_KEY to EVP_PKEY."); +#endif + break; + } + default: + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unsupported type of public key: %lu", key_type); + } + + *ret_pkey = TAKE_PTR(pkey); + return 0; +} + int pkcs11_token_read_x509_certificate( CK_FUNCTION_LIST *m, CK_SESSION_HANDLE session, CK_OBJECT_HANDLE object, X509 **ret_cert) { - _cleanup_free_ void *buffer = NULL; _cleanup_free_ char *t = NULL; CK_ATTRIBUTE attribute = { .type = CKA_VALUE @@ -537,7 +834,6 @@ int pkcs11_token_read_x509_certificate( CK_RV rv; _cleanup_(X509_freep) X509 *x509 = NULL; X509_NAME *name = NULL; - const unsigned char *p; int r; r = dlopen_p11kit(); @@ -546,32 +842,32 @@ int pkcs11_token_read_x509_certificate( rv = m->C_GetAttributeValue(session, object, &attribute, 1); if (rv != CKR_OK) - return log_error_errno(SYNTHETIC_ERRNO(EIO), + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to read X.509 certificate size off token: %s", sym_p11_kit_strerror(rv)); - buffer = malloc(attribute.ulValueLen); + _cleanup_free_ void *buffer = malloc(attribute.ulValueLen); if (!buffer) - return log_oom(); + return log_oom_debug(); attribute.pValue = buffer; rv = m->C_GetAttributeValue(session, object, &attribute, 1); if (rv != CKR_OK) - return log_error_errno(SYNTHETIC_ERRNO(EIO), + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to read X.509 certificate data off token: %s", sym_p11_kit_strerror(rv)); - p = attribute.pValue; + const unsigned char *p = attribute.pValue; x509 = d2i_X509(NULL, &p, attribute.ulValueLen); if (!x509) - return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Failed parse X.509 certificate."); + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Failed to parse X.509 certificate."); name = X509_get_subject_name(x509); if (!name) - return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Failed to acquire X.509 subject name."); + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Failed to acquire X.509 subject name."); t = X509_NAME_oneline(name, NULL, 0); if (!t) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to format X.509 subject name as string."); + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to format X.509 subject name as string."); log_debug("Using X.509 certificate issued for '%s'.", t); @@ -586,143 +882,429 @@ int pkcs11_token_find_private_key( P11KitUri *search_uri, CK_OBJECT_HANDLE *ret_object) { - bool found_decrypt = false, found_class = false, found_key_type = false; + bool found_class = false; _cleanup_free_ CK_ATTRIBUTE *attributes_buffer = NULL; - CK_ULONG n_attributes, a, n_objects; - CK_ATTRIBUTE *attributes = NULL; - CK_OBJECT_HANDLE objects[2]; - CK_RV rv, rv2; - int r; + CK_KEY_TYPE key_type; + CK_BBOOL decrypt_value, derive_value; + CK_ATTRIBUTE optional_attributes[] = { + { CKA_KEY_TYPE, &key_type, sizeof(key_type) }, + { CKA_DECRYPT, &decrypt_value, sizeof(decrypt_value) }, + { CKA_DERIVE, &derive_value, sizeof(derive_value) }, + }; + uint8_t n_private_keys = 0; + CK_OBJECT_HANDLE private_key = CK_INVALID_HANDLE; + CK_RV rv; assert(m); assert(search_uri); assert(ret_object); - r = dlopen_p11kit(); - if (r < 0) - return r; - - attributes = sym_p11_kit_uri_get_attributes(search_uri, &n_attributes); - for (a = 0; a < n_attributes; a++) { + CK_ULONG n_attributes; + CK_ATTRIBUTE *attributes = sym_p11_kit_uri_get_attributes(search_uri, &n_attributes); + for (CK_ULONG i = 0; i < n_attributes; i++) { /* We use the URI's included match attributes, but make them more strict. This allows users * to specify a token URL instead of an object URL and the right thing should happen if * there's only one suitable key on the token. */ - switch (attributes[a].type) { - + switch (attributes[i].type) { case CKA_CLASS: { - CK_OBJECT_CLASS c; - - if (attributes[a].ulValueLen != sizeof(c)) + if (attributes[i].ulValueLen != sizeof(CK_OBJECT_CLASS)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid PKCS#11 CKA_CLASS attribute size."); - memcpy(&c, attributes[a].pValue, sizeof(c)); - if (c != CKO_PRIVATE_KEY) + CK_OBJECT_CLASS *class = (CK_OBJECT_CLASS*) attributes[i].pValue; + if (*class != CKO_PRIVATE_KEY) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Selected PKCS#11 object is not a private key, refusing."); found_class = true; break; - } + }} + } - case CKA_DECRYPT: { - CK_BBOOL b; + if (!found_class) { + /* Hmm, let's slightly extend the attribute list we search for */ + static const CK_OBJECT_CLASS required_class = CKO_PRIVATE_KEY; - if (attributes[a].ulValueLen != sizeof(b)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid PKCS#11 CKA_DECRYPT attribute size."); + attributes_buffer = new(CK_ATTRIBUTE, n_attributes + 1); + if (!attributes_buffer) + return log_oom(); - memcpy(&b, attributes[a].pValue, sizeof(b)); - if (!b) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Selected PKCS#11 object is not suitable for decryption, refusing."); + memcpy(attributes_buffer, attributes, sizeof(CK_ATTRIBUTE) * n_attributes); + + attributes_buffer[n_attributes++] = (CK_ATTRIBUTE) { + .type = CKA_CLASS, + .pValue = (CK_OBJECT_CLASS*) &required_class, + .ulValueLen = sizeof(required_class), + }; + + attributes = attributes_buffer; + } - found_decrypt = true; + rv = m->C_FindObjectsInit(session, attributes, n_attributes); + if (rv != CKR_OK) + return log_error_errno(SYNTHETIC_ERRNO(EIO), + "Failed to initialize object find call: %s", sym_p11_kit_strerror(rv)); + + for (;;) { + CK_ULONG b; + CK_OBJECT_HANDLE candidate; + rv = m->C_FindObjects(session, &candidate, 1, &b); + if (rv != CKR_OK) + return log_error_errno(SYNTHETIC_ERRNO(EIO), + "Failed to find objects: %s", sym_p11_kit_strerror(rv)); + + if (b == 0) break; - } - case CKA_KEY_TYPE: { - CK_KEY_TYPE t; + optional_attributes[0].ulValueLen = sizeof(key_type); + optional_attributes[1].ulValueLen = sizeof(decrypt_value); + optional_attributes[2].ulValueLen = sizeof(derive_value); - if (attributes[a].ulValueLen != sizeof(t)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid PKCS#11 CKA_KEY_TYPE attribute size."); + rv = m->C_GetAttributeValue(session, candidate, optional_attributes, ELEMENTSOF(optional_attributes)); + if (!IN_SET(rv, CKR_OK, CKR_ATTRIBUTE_TYPE_INVALID)) + return log_error_errno(SYNTHETIC_ERRNO(EIO), + "Failed to get attributes of a found private key: %s", sym_p11_kit_strerror(rv)); - memcpy(&t, attributes[a].pValue, sizeof(t)); - if (t != CKK_RSA) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Selected PKCS#11 object is not an RSA key, refusing."); + if (optional_attributes[0].ulValueLen == CK_UNAVAILABLE_INFORMATION) { + log_debug("A found private key does not have CKA_KEY_TYPE, rejecting the key."); + continue; + } + + if (key_type == CKK_RSA) + if (optional_attributes[1].ulValueLen == CK_UNAVAILABLE_INFORMATION || decrypt_value == CK_FALSE) { + log_debug("A found private RSA key can't decrypt, rejecting the key."); + continue; + } - found_key_type = true; + if (key_type == CKK_EC) + if (optional_attributes[2].ulValueLen == CK_UNAVAILABLE_INFORMATION || derive_value == CK_FALSE) { + log_debug("A found private EC key can't derive, rejecting the key."); + continue; + } + + n_private_keys++; + if (n_private_keys > 1) break; - }} + private_key = candidate; } - if (!found_decrypt || !found_class || !found_key_type) { - /* Hmm, let's slightly extend the attribute list we search for */ + rv = m->C_FindObjectsFinal(session); + if (rv != CKR_OK) + return log_error_errno(SYNTHETIC_ERRNO(EIO), + "Failed to finalize object find call: %s", sym_p11_kit_strerror(rv)); - attributes_buffer = new(CK_ATTRIBUTE, n_attributes + !found_decrypt + !found_class + !found_key_type); - if (!attributes_buffer) + if (n_private_keys == 0) + return log_error_errno(SYNTHETIC_ERRNO(ENOENT), + "Failed to find selected private key suitable for decryption or derivation on token."); + + if (n_private_keys > 1) + return log_error_errno(SYNTHETIC_ERRNO(ENOTUNIQ), + "Configured private key URI matches multiple keys, refusing."); + + *ret_object = private_key; + return 0; +} + +static const char* object_class_to_string(CK_OBJECT_CLASS class) { + switch (class) { + case CKO_CERTIFICATE: + return "CKO_CERTIFICATE"; + case CKO_PUBLIC_KEY: + return "CKO_PUBLIC_KEY"; + case CKO_PRIVATE_KEY: + return "CKO_PRIVATE_KEY"; + case CKO_SECRET_KEY: + return "CKO_SECRET_KEY"; + default: + return NULL; + } +} + +/* Returns an object with the given class and the same CKA_ID or CKA_LABEL as prototype */ +int pkcs11_token_find_related_object( + CK_FUNCTION_LIST *m, + CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE prototype, + CK_OBJECT_CLASS class, + CK_OBJECT_HANDLE *ret_object ) { + + _cleanup_free_ void *buffer = NULL; + CK_ATTRIBUTE attributes[] = { + { CKA_ID, NULL_PTR, 0 }, + { CKA_LABEL, NULL_PTR, 0 } + }; + CK_OBJECT_CLASS search_class = class; + CK_ATTRIBUTE search_attributes[2] = { + { CKA_CLASS, &search_class, sizeof(search_class) } + }; + CK_ULONG n_objects; + CK_OBJECT_HANDLE objects[2]; + CK_RV rv; + + rv = m->C_GetAttributeValue(session, prototype, attributes, ELEMENTSOF(attributes)); + if (!IN_SET(rv, CKR_OK, CKR_ATTRIBUTE_TYPE_INVALID)) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to retrieve length of attributes: %s", sym_p11_kit_strerror(rv)); + + if (attributes[0].ulValueLen != CK_UNAVAILABLE_INFORMATION) { + buffer = malloc(attributes[0].ulValueLen); + if (!buffer) return log_oom(); - memcpy(attributes_buffer, attributes, sizeof(CK_ATTRIBUTE) * n_attributes); + attributes[0].pValue = buffer; + rv = m->C_GetAttributeValue(session, prototype, &attributes[0], 1); + if (rv != CKR_OK) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), + "Failed to retrieve CKA_ID: %s", sym_p11_kit_strerror(rv)); - if (!found_decrypt) { - static const CK_BBOOL yes = true; + search_attributes[1] = attributes[0]; - attributes_buffer[n_attributes++] = (CK_ATTRIBUTE) { - .type = CKA_DECRYPT, - .pValue = (CK_BBOOL*) &yes, - .ulValueLen = sizeof(yes), - }; - } + } else if (attributes[1].ulValueLen != CK_UNAVAILABLE_INFORMATION) { + buffer = malloc(attributes[1].ulValueLen); + if (!buffer) + return log_oom(); - if (!found_class) { - static const CK_OBJECT_CLASS class = CKO_PRIVATE_KEY; + attributes[1].pValue = buffer; + rv = m->C_GetAttributeValue(session, prototype, &attributes[1], 1); + if (rv != CKR_OK) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), + "Failed to retrieve CKA_LABEL: %s", sym_p11_kit_strerror(rv)); - attributes_buffer[n_attributes++] = (CK_ATTRIBUTE) { - .type = CKA_CLASS, - .pValue = (CK_OBJECT_CLASS*) &class, - .ulValueLen = sizeof(class), - }; - } + search_attributes[1] = attributes[1]; - if (!found_key_type) { - static const CK_KEY_TYPE type = CKK_RSA; + } else + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "The prototype does not have CKA_ID or CKA_LABEL"); - attributes_buffer[n_attributes++] = (CK_ATTRIBUTE) { - .type = CKA_KEY_TYPE, - .pValue = (CK_KEY_TYPE*) &type, - .ulValueLen = sizeof(type), - }; - } + rv = m->C_FindObjectsInit(session, search_attributes, 2); + if (rv != CKR_OK) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), + "Failed to initialize object find call: %s", sym_p11_kit_strerror(rv)); - attributes = attributes_buffer; + rv = m->C_FindObjects(session, objects, 2, &n_objects); + if (rv != CKR_OK) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), + "Failed to find objects: %s", sym_p11_kit_strerror(rv)); + + rv = m->C_FindObjectsFinal(session); + if (rv != CKR_OK) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), + "Failed to finalize object find call: %s", sym_p11_kit_strerror(rv)); + + if (n_objects == 0) + return log_debug_errno(SYNTHETIC_ERRNO(ENOENT), + "Failed to find a related object with class %s", object_class_to_string(class)); + + if (n_objects > 1) + log_warning("Found multiple related objects with class %s, using the first object.", + object_class_to_string(class)); + + *ret_object = objects[0]; + return 0; +} + +#if HAVE_OPENSSL +static int ecc_convert_to_compressed( + CK_FUNCTION_LIST *m, + CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE object, + const void *uncompressed_point, + size_t uncompressed_point_size, + void **ret_compressed_point, + size_t *ret_compressed_point_size) { + + _cleanup_free_ void *ec_params_buffer = NULL; + CK_ATTRIBUTE ec_params_attr = { CKA_EC_PARAMS, NULL_PTR, 0 }; + CK_RV rv; + int r; + + rv = m->C_GetAttributeValue(session, object, &ec_params_attr, 1); + if (!IN_SET(rv, CKR_OK, CKR_ATTRIBUTE_TYPE_INVALID)) + return log_error_errno(SYNTHETIC_ERRNO(EIO), + "Failed to retrieve length of CKA_EC_PARAMS: %s", sym_p11_kit_strerror(rv)); + + if (ec_params_attr.ulValueLen != CK_UNAVAILABLE_INFORMATION) { + ec_params_buffer = malloc(ec_params_attr.ulValueLen); + if (!ec_params_buffer) + return log_oom(); + + ec_params_attr.pValue = ec_params_buffer; + rv = m->C_GetAttributeValue(session, object, &ec_params_attr, 1); + if (rv != CKR_OK) + return log_error_errno(SYNTHETIC_ERRNO(EIO), + "Failed to retrieve CKA_EC_PARAMS from a private key: %s", sym_p11_kit_strerror(rv)); + } else { + CK_OBJECT_HANDLE public_key; + r = pkcs11_token_find_related_object(m, session, object, CKO_PUBLIC_KEY, &public_key); + if (r < 0) + return log_error_errno(r, "Failed to find a public key for compressing a EC point"); + + ec_params_attr.ulValueLen = 0; + rv = m->C_GetAttributeValue(session, public_key, &ec_params_attr, 1); + if (!IN_SET(rv, CKR_OK, CKR_ATTRIBUTE_TYPE_INVALID)) + return log_error_errno(SYNTHETIC_ERRNO(EIO), + "Failed to retrieve length of CKA_EC_PARAMS: %s", sym_p11_kit_strerror(rv)); + + if (ec_params_attr.ulValueLen == CK_UNAVAILABLE_INFORMATION) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "The public key does not have CKA_EC_PARAMS"); + + ec_params_buffer = malloc(ec_params_attr.ulValueLen); + if (!ec_params_buffer) + return log_oom(); + + ec_params_attr.pValue = ec_params_buffer; + rv = m->C_GetAttributeValue(session, public_key, &ec_params_attr, 1); + if (rv != CKR_OK) + return log_error_errno(SYNTHETIC_ERRNO(EIO), + "Failed to retrieve CKA_EC_PARAMS from a public key: %s", sym_p11_kit_strerror(rv)); } - rv = m->C_FindObjectsInit(session, attributes, n_attributes); + _cleanup_(EC_GROUP_freep) EC_GROUP *group = NULL; + _cleanup_(EC_POINT_freep) EC_POINT *point = NULL; + _cleanup_(BN_CTX_freep) BN_CTX *bnctx = NULL; + _cleanup_free_ void *compressed_point = NULL; + size_t compressed_point_size; + + const unsigned char *ec_params_value = ec_params_attr.pValue; + group = d2i_ECPKParameters(NULL, &ec_params_value, ec_params_attr.ulValueLen); + if (!group) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unable to decode CKA_EC_PARAMS"); + + point = EC_POINT_new(group); + if (!point) + return log_oom(); + + bnctx = BN_CTX_new(); + if (!bnctx) + return log_oom(); + + if (EC_POINT_oct2point(group, point, uncompressed_point, uncompressed_point_size, bnctx) != 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unable to decode an uncompressed EC point"); + + compressed_point_size = EC_POINT_point2oct(group, point, POINT_CONVERSION_COMPRESSED, NULL, 0, bnctx); + if (compressed_point_size == 0) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to determine size of a compressed EC point"); + + compressed_point = malloc(compressed_point_size); + if (!compressed_point) + return log_oom(); + + compressed_point_size = EC_POINT_point2oct(group, point, POINT_CONVERSION_COMPRESSED, compressed_point, compressed_point_size, bnctx); + if (compressed_point_size == 0) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to convert a EC point to compressed format"); + + *ret_compressed_point = TAKE_PTR(compressed_point); + *ret_compressed_point_size = compressed_point_size; + return 0; +} +#endif + +/* Since EC keys doesn't support encryption directly, we use ECDH protocol to derive shared secret here. + * We use PKCS#11 C_DeriveKey function to derive a shared secret with a private key stored in the token and + * a public key saved on enrollment. */ +static int pkcs11_token_decrypt_data_ecc( + CK_FUNCTION_LIST *m, + CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE object, + const void *encrypted_data, + size_t encrypted_data_size, + void **ret_decrypted_data, + size_t *ret_decrypted_data_size) { + + static const CK_BBOOL yes = CK_TRUE, no = CK_FALSE; + static const CK_OBJECT_CLASS shared_secret_class = CKO_SECRET_KEY; + static const CK_KEY_TYPE shared_secret_type = CKK_GENERIC_SECRET; + static const CK_ATTRIBUTE shared_secret_template[] = { + { CKA_TOKEN, (void*) &no, sizeof(no) }, + { CKA_CLASS, (void*) &shared_secret_class, sizeof(shared_secret_class) }, + { CKA_KEY_TYPE, (void*) &shared_secret_type, sizeof(shared_secret_type) }, + { CKA_SENSITIVE, (void*) &no, sizeof(no) }, + { CKA_EXTRACTABLE, (void*) &yes, sizeof(yes) } + }; + CK_ECDH1_DERIVE_PARAMS params = { + .kdf = CKD_NULL, + .pPublicData = (void*) encrypted_data, + .ulPublicDataLen = encrypted_data_size + }; + CK_MECHANISM mechanism = { + .mechanism = CKM_ECDH1_DERIVE, + .pParameter = ¶ms, + .ulParameterLen = sizeof(params) + }; + CK_OBJECT_HANDLE shared_secret_handle; + CK_SESSION_INFO session_info; + CK_MECHANISM_INFO mechanism_info; + CK_RV rv, rv2; +#if HAVE_OPENSSL + _cleanup_free_ void *compressed_point = NULL; + int r; +#endif + + rv = m->C_GetSessionInfo(session, &session_info); if (rv != CKR_OK) return log_error_errno(SYNTHETIC_ERRNO(EIO), - "Failed to initialize object find call: %s", sym_p11_kit_strerror(rv)); + "Failed to get information about the PKCS#11 session: %s", sym_p11_kit_strerror(rv)); - rv = m->C_FindObjects(session, objects, ELEMENTSOF(objects), &n_objects); - rv2 = m->C_FindObjectsFinal(session); + rv = m->C_GetMechanismInfo(session_info.slotID, CKM_ECDH1_DERIVE, &mechanism_info); if (rv != CKR_OK) return log_error_errno(SYNTHETIC_ERRNO(EIO), - "Failed to find objects: %s", sym_p11_kit_strerror(rv)); + "Failed to get information about CKM_ECDH1_DERIVE: %s", sym_p11_kit_strerror(rv)); + + if (!(mechanism_info.flags & CKF_EC_UNCOMPRESS)) { + if (mechanism_info.flags & CKF_EC_COMPRESS) { +#if HAVE_OPENSSL + log_debug("CKM_ECDH1_DERIVE accepts compressed EC points only, trying to convert."); + size_t compressed_point_size = 0; /* Explicit initialization to appease gcc */ + r = ecc_convert_to_compressed(m, session, object, encrypted_data, encrypted_data_size, &compressed_point, &compressed_point_size); + if (r < 0) + return r; + + params.pPublicData = compressed_point; + params.ulPublicDataLen = compressed_point_size; +#else + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "CKM_ECDH1_DERIVE does not support uncompressed format of EC points"); +#endif + } else + log_debug("Both CKF_EC_UNCOMPRESS and CKF_EC_COMPRESS are false for CKM_ECDH1_DERIVE, ignoring."); + } + + rv = m->C_DeriveKey(session, &mechanism, object, (CK_ATTRIBUTE*) shared_secret_template, ELEMENTSOF(shared_secret_template), &shared_secret_handle); + if (rv != CKR_OK) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to derive a shared secret: %s", sym_p11_kit_strerror(rv)); + + CK_ATTRIBUTE shared_secret_attr = { CKA_VALUE, NULL_PTR, 0}; + + rv = m->C_GetAttributeValue(session, shared_secret_handle, &shared_secret_attr, 1); + if (rv != CKR_OK) { + rv2 = m->C_DestroyObject(session, shared_secret_handle); + if (rv2 != CKR_OK) + log_warning("Failed to destroy a shared secret, ignoring: %s", sym_p11_kit_strerror(rv2)); + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to retrieve shared secret length: %s", sym_p11_kit_strerror(rv)); + } + + shared_secret_attr.pValue = malloc(shared_secret_attr.ulValueLen); + if (!shared_secret_attr.pValue) + return log_oom(); + + rv = m->C_GetAttributeValue(session, shared_secret_handle, &shared_secret_attr, 1); + rv2 = m->C_DestroyObject(session, shared_secret_handle); if (rv2 != CKR_OK) - return log_error_errno(SYNTHETIC_ERRNO(EIO), - "Failed to finalize object find call: %s", sym_p11_kit_strerror(rv)); - if (n_objects == 0) - return log_error_errno(SYNTHETIC_ERRNO(ENOENT), - "Failed to find selected private key suitable for decryption on token."); - if (n_objects > 1) - return log_error_errno(SYNTHETIC_ERRNO(ENOTUNIQ), - "Configured private key URI matches multiple keys, refusing."); + log_warning("Failed to destroy a shared secret, ignoring: %s", sym_p11_kit_strerror(rv2)); - *ret_object = objects[0]; + if (rv != CKR_OK) { + erase_and_free(shared_secret_attr.pValue); + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to retrieve a shared secret: %s", sym_p11_kit_strerror(rv)); + } + + log_info("Successfully derived key with security token."); + + *ret_decrypted_data = shared_secret_attr.pValue; + *ret_decrypted_data_size = shared_secret_attr.ulValueLen; return 0; } -int pkcs11_token_decrypt_data( +static int pkcs11_token_decrypt_data_rsa( CK_FUNCTION_LIST *m, CK_SESSION_HANDLE session, CK_OBJECT_HANDLE object, @@ -737,17 +1319,6 @@ int pkcs11_token_decrypt_data( _cleanup_(erase_and_freep) CK_BYTE *dbuffer = NULL; CK_ULONG dbuffer_size = 0; CK_RV rv; - int r; - - assert(m); - assert(encrypted_data); - assert(encrypted_data_size > 0); - assert(ret_decrypted_data); - assert(ret_decrypted_data_size); - - r = dlopen_p11kit(); - if (r < 0) - return r; rv = m->C_DecryptInit(session, (CK_MECHANISM*) &mechanism, object); if (rv != CKR_OK) @@ -780,6 +1351,42 @@ int pkcs11_token_decrypt_data( return 0; } +int pkcs11_token_decrypt_data( + CK_FUNCTION_LIST *m, + CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE object, + const void *encrypted_data, + size_t encrypted_data_size, + void **ret_decrypted_data, + size_t *ret_decrypted_data_size) { + + CK_KEY_TYPE key_type; + CK_ATTRIBUTE key_type_template = { CKA_KEY_TYPE, &key_type, sizeof(key_type) }; + CK_RV rv; + + assert(m); + assert(encrypted_data); + assert(encrypted_data_size > 0); + assert(ret_decrypted_data); + assert(ret_decrypted_data_size); + + rv = m->C_GetAttributeValue(session, object, &key_type_template, 1); + if (rv != CKR_OK) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to retrieve private key type"); + + switch (key_type) { + + case CKK_RSA: + return pkcs11_token_decrypt_data_rsa(m, session, object, encrypted_data, encrypted_data_size, ret_decrypted_data, ret_decrypted_data_size); + + case CKK_EC: + return pkcs11_token_decrypt_data_ecc(m, session, object, encrypted_data, encrypted_data_size, ret_decrypted_data, ret_decrypted_data_size); + + default: + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unsupported private key type: %lu", key_type); + } +} + int pkcs11_token_acquire_rng( CK_FUNCTION_LIST *m, CK_SESSION_HANDLE session) { @@ -1055,20 +1662,19 @@ int pkcs11_find_token( } #if HAVE_OPENSSL -struct pkcs11_acquire_certificate_callback_data { +struct pkcs11_acquire_public_key_callback_data { char *pin_used; - X509 *cert; - const char *askpw_friendly_name, *askpw_icon_name; + EVP_PKEY *pkey; + const char *askpw_friendly_name, *askpw_icon, *askpw_credential; AskPasswordFlags askpw_flags; - bool headless; }; -static void pkcs11_acquire_certificate_callback_data_release(struct pkcs11_acquire_certificate_callback_data *data) { +static void pkcs11_acquire_public_key_callback_data_release(struct pkcs11_acquire_public_key_callback_data *data) { erase_and_free(data->pin_used); - X509_free(data->cert); + EVP_PKEY_free(data->pkey); } -static int pkcs11_acquire_certificate_callback( +static int pkcs11_acquire_public_key_callback( CK_FUNCTION_LIST *m, CK_SESSION_HANDLE session, CK_SLOT_ID slot_id, @@ -1078,8 +1684,16 @@ static int pkcs11_acquire_certificate_callback( void *userdata) { _cleanup_(erase_and_freep) char *pin_used = NULL; - struct pkcs11_acquire_certificate_callback_data *data = ASSERT_PTR(userdata); - CK_OBJECT_HANDLE object; + _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; + CK_OBJECT_CLASS class; + CK_CERTIFICATE_TYPE type; + CK_ATTRIBUTE candidate_attributes[] = { + { CKA_CLASS, &class, sizeof(class) }, + { CKA_CERTIFICATE_TYPE, &type, sizeof(type) }, + }; + CK_OBJECT_HANDLE candidate, public_key = CK_INVALID_HANDLE, certificate = CK_INVALID_HANDLE; + uint8_t n_public_keys = 0, n_certificates = 0; + CK_RV rv; int r; assert(m); @@ -1087,6 +1701,8 @@ static int pkcs11_acquire_certificate_callback( assert(token_info); assert(uri); + struct pkcs11_acquire_public_key_callback_data *data = ASSERT_PTR(userdata); + /* Called for every token matching our URI */ r = pkcs11_token_login( @@ -1095,50 +1711,154 @@ static int pkcs11_acquire_certificate_callback( slot_id, token_info, data->askpw_friendly_name, - data->askpw_icon_name, - "pkcs11-pin", + data->askpw_icon, "pkcs11-pin", + data->askpw_credential, UINT64_MAX, data->askpw_flags, - data->headless, &pin_used); if (r < 0) return r; - r = pkcs11_token_find_x509_certificate(m, session, uri, &object); - if (r < 0) - return r; + CK_ULONG n_attributes; + CK_ATTRIBUTE *attributes = sym_p11_kit_uri_get_attributes(uri, &n_attributes); + for (CK_ULONG i = 0; i < n_attributes; i++) { + switch (attributes[i].type) { + case CKA_CLASS: { + CK_OBJECT_CLASS requested_class = *((CK_OBJECT_CLASS*) attributes[i].pValue); + if (!IN_SET(requested_class, CKO_PUBLIC_KEY, CKO_CERTIFICATE)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Selected PKCS#11 object is not a public key or certificate, refusing."); + break; + } - r = pkcs11_token_read_x509_certificate(m, session, object, &data->cert); - if (r < 0) - return r; + case CKA_CERTIFICATE_TYPE: { + CK_CERTIFICATE_TYPE requested_type = *((CK_CERTIFICATE_TYPE*) attributes[i].pValue); + if (requested_type != CKC_X_509) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Selected PKCS#11 object is not an X.509 certificate, refusing."); + break; + }} + } + + rv = m->C_FindObjectsInit(session, attributes, n_attributes); + if (rv != CKR_OK) + return log_error_errno(SYNTHETIC_ERRNO(EIO), + "Failed to initialize object find call: %s", sym_p11_kit_strerror(rv)); + + for (;;) { + CK_ULONG n; + rv = m->C_FindObjects(session, &candidate, 1, &n); + if (rv != CKR_OK) + return log_error_errno(SYNTHETIC_ERRNO(EIO), + "Failed to find objects: %s", sym_p11_kit_strerror(rv)); + + if (n == 0) + break; + + candidate_attributes[0].ulValueLen = sizeof(class); + candidate_attributes[1].ulValueLen = sizeof(type); + rv = m->C_GetAttributeValue(session, candidate, candidate_attributes, ELEMENTSOF(candidate_attributes)); + if (!IN_SET(rv, CKR_OK, CKR_ATTRIBUTE_TYPE_INVALID)) + return log_error_errno(SYNTHETIC_ERRNO(EIO), + "Failed to get attributes of a selected candidate: %s", sym_p11_kit_strerror(rv)); + + if (candidate_attributes[0].ulValueLen == CK_UNAVAILABLE_INFORMATION) { + log_debug("Failed to get CKA_CLASS of a selected candidate"); + continue; + } + + if (class == CKO_PUBLIC_KEY) { + n_public_keys++; + if (n_public_keys > 1) + break; + public_key = candidate; + continue; + } + + if (class == CKO_CERTIFICATE) { + if (candidate_attributes[1].ulValueLen == CK_UNAVAILABLE_INFORMATION) { + log_debug("Failed to get CKA_CERTIFICATE_TYPE of a selected candidate"); + continue; + } + if (type != CKC_X_509) + continue; + n_certificates++; + if (n_certificates > 1) + break; + certificate = candidate; + continue; + } + } + + rv = m->C_FindObjectsFinal(session); + if (rv != CKR_OK) + return log_error_errno(SYNTHETIC_ERRNO(EIO), + "Failed to finalize object find call: %s", sym_p11_kit_strerror(rv)); + + if (n_public_keys == 0 && n_certificates == 0) + return log_error_errno(SYNTHETIC_ERRNO(ENOENT), + "Failed to find selected public key or X.509 certificate."); + + if (n_public_keys > 1) + return log_error_errno(SYNTHETIC_ERRNO(ENOTUNIQ), + "Provided URI matches multiple public keys, refusing."); + if (n_certificates > 1) + return log_error_errno(SYNTHETIC_ERRNO(ENOTUNIQ), + "Provided URI matches multiple certificates, refusing."); + + if (n_public_keys != 0) { + r = pkcs11_token_read_public_key(m, session, public_key, &pkey); + if (r >= 0) + goto success; + } + + if (n_certificates == 0) + return log_error_errno(r, "Failed to read a found public key."); + + { + _cleanup_(X509_freep) X509 *cert = NULL; + + r = pkcs11_token_read_x509_certificate(m, session, certificate, &cert); + if (r < 0) + return log_error_errno(r, "Failed to read a found X.509 certificate."); + + pkey = X509_get_pubkey(cert); + if (!pkey) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to extract public key from X.509 certificate."); + } +success: /* Let's read some random data off the token and write it to the kernel pool before we generate our * random key from it. This way we can claim the quality of the RNG is at least as good as the * kernel's and the token's pool */ (void) pkcs11_token_acquire_rng(m, session); data->pin_used = TAKE_PTR(pin_used); - return 1; + data->pkey = TAKE_PTR(pkey); + return 0; } -int pkcs11_acquire_certificate( +int pkcs11_acquire_public_key( const char *uri, const char *askpw_friendly_name, - const char *askpw_icon_name, - X509 **ret_cert, + const char *askpw_icon, + const char *askpw_credential, + AskPasswordFlags askpw_flags, + EVP_PKEY **ret_pkey, char **ret_pin_used) { - _cleanup_(pkcs11_acquire_certificate_callback_data_release) struct pkcs11_acquire_certificate_callback_data data = { + _cleanup_(pkcs11_acquire_public_key_callback_data_release) struct pkcs11_acquire_public_key_callback_data data = { .askpw_friendly_name = askpw_friendly_name, - .askpw_icon_name = askpw_icon_name, + .askpw_icon = askpw_icon, + .askpw_credential = askpw_credential, + .askpw_flags = askpw_flags, }; int r; assert(uri); - assert(ret_cert); + assert(ret_pkey); - r = pkcs11_find_token(uri, pkcs11_acquire_certificate_callback, &data); + r = pkcs11_find_token(uri, pkcs11_acquire_public_key_callback, &data); if (r == -EAGAIN) /* pkcs11_find_token() doesn't log about this error, but all others */ return log_error_errno(SYNTHETIC_ERRNO(ENXIO), "Specified PKCS#11 token with URI '%s' not found.", @@ -1146,11 +1866,9 @@ int pkcs11_acquire_certificate( if (r < 0) return r; - *ret_cert = TAKE_PTR(data.cert); - + *ret_pkey = TAKE_PTR(data.pkey); if (ret_pin_used) *ret_pin_used = TAKE_PTR(data.pin_used); - return 0; } #endif @@ -1229,7 +1947,7 @@ int pkcs11_list_tokens(void) { if (r < 0 && r != -EAGAIN) return r; - if (table_get_rows(t) <= 1) { + if (table_isempty(t)) { log_info("No suitable PKCS#11 tokens found."); return 0; } @@ -1338,10 +2056,9 @@ int pkcs11_crypt_device_callback( data->friendly_name, "drive-harddisk", "pkcs11-pin", - "cryptsetup.pkcs11-pin", + data->askpw_credential, data->until, data->askpw_flags, - data->headless, NULL); if (r < 0) return r; diff --git a/src/shared/pkcs11-util.h b/src/shared/pkcs11-util.h index 5bc23c1..23ab823 100644 --- a/src/shared/pkcs11-util.h +++ b/src/shared/pkcs11-util.h @@ -1,6 +1,10 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#if HAVE_OPENSSL +# include <openssl/evp.h> +# include <openssl/x509.h> +#endif #include <stdbool.h> #if HAVE_P11KIT @@ -9,28 +13,29 @@ #endif #include "ask-password-api.h" +#include "dlfcn-util.h" #include "macro.h" -#include "openssl-util.h" #include "time-util.h" bool pkcs11_uri_valid(const char *uri); #if HAVE_P11KIT - -extern char *(*sym_p11_kit_module_get_name)(CK_FUNCTION_LIST *module); -extern void (*sym_p11_kit_modules_finalize_and_release)(CK_FUNCTION_LIST **modules); -extern CK_FUNCTION_LIST **(*sym_p11_kit_modules_load_and_initialize)(int flags); -extern const char *(*sym_p11_kit_strerror)(CK_RV rv); -extern int (*sym_p11_kit_uri_format)(P11KitUri *uri, P11KitUriType uri_type, char **string); -extern void (*sym_p11_kit_uri_free)(P11KitUri *uri); -extern CK_ATTRIBUTE_PTR (*sym_p11_kit_uri_get_attributes)(P11KitUri *uri, CK_ULONG *n_attrs); -extern CK_INFO_PTR (*sym_p11_kit_uri_get_module_info)(P11KitUri *uri); -extern CK_SLOT_INFO_PTR (*sym_p11_kit_uri_get_slot_info)(P11KitUri *uri); -extern CK_TOKEN_INFO_PTR (*sym_p11_kit_uri_get_token_info)(P11KitUri *uri); -extern int (*sym_p11_kit_uri_match_token_info)(const P11KitUri *uri, const CK_TOKEN_INFO *token_info); -extern const char *(*sym_p11_kit_uri_message)(int code); -extern P11KitUri *(*sym_p11_kit_uri_new)(void); -extern int (*sym_p11_kit_uri_parse)(const char *string, P11KitUriType uri_type, P11KitUri *uri); +DLSYM_PROTOTYPE(p11_kit_module_get_name); +DLSYM_PROTOTYPE(p11_kit_modules_finalize_and_release); +DLSYM_PROTOTYPE(p11_kit_modules_load_and_initialize); +DLSYM_PROTOTYPE(p11_kit_strerror); +DLSYM_PROTOTYPE(p11_kit_uri_format); +DLSYM_PROTOTYPE(p11_kit_uri_free); +DLSYM_PROTOTYPE(p11_kit_uri_get_attributes); +DLSYM_PROTOTYPE(p11_kit_uri_get_attribute); +DLSYM_PROTOTYPE(p11_kit_uri_set_attribute); +DLSYM_PROTOTYPE(p11_kit_uri_get_module_info); +DLSYM_PROTOTYPE(p11_kit_uri_get_slot_info); +DLSYM_PROTOTYPE(p11_kit_uri_get_token_info); +DLSYM_PROTOTYPE(p11_kit_uri_match_token_info); +DLSYM_PROTOTYPE(p11_kit_uri_message); +DLSYM_PROTOTYPE(p11_kit_uri_new); +DLSYM_PROTOTYPE(p11_kit_uri_parse); int uri_from_string(const char *p, P11KitUri **ret); @@ -48,10 +53,12 @@ char *pkcs11_token_manufacturer_id(const CK_TOKEN_INFO *token_info); char *pkcs11_token_model(const CK_TOKEN_INFO *token_info); int pkcs11_token_login_by_pin(CK_FUNCTION_LIST *m, CK_SESSION_HANDLE session, const CK_TOKEN_INFO *token_info, const char *token_label, const void *pin, size_t pin_size); -int pkcs11_token_login(CK_FUNCTION_LIST *m, CK_SESSION_HANDLE session, CK_SLOT_ID slotid, const CK_TOKEN_INFO *token_info, const char *friendly_name, const char *icon_name, const char *key_name, const char *credential_name, usec_t until, AskPasswordFlags ask_password_flags, bool headless, char **ret_used_pin); +int pkcs11_token_login(CK_FUNCTION_LIST *m, CK_SESSION_HANDLE session, CK_SLOT_ID slotid, const CK_TOKEN_INFO *token_info, const char *friendly_name, const char *icon_name, const char *key_name, const char *credential_name, usec_t until, AskPasswordFlags ask_password_flags, char **ret_used_pin); +int pkcs11_token_find_related_object(CK_FUNCTION_LIST *m, CK_SESSION_HANDLE session, CK_OBJECT_HANDLE prototype, CK_OBJECT_CLASS class, CK_OBJECT_HANDLE *ret_object); int pkcs11_token_find_x509_certificate(CK_FUNCTION_LIST *m, CK_SESSION_HANDLE session, P11KitUri *search_uri, CK_OBJECT_HANDLE *ret_object); #if HAVE_OPENSSL +int pkcs11_token_read_public_key(CK_FUNCTION_LIST *m, CK_SESSION_HANDLE session, CK_OBJECT_HANDLE object, EVP_PKEY **ret_pkey); int pkcs11_token_read_x509_certificate(CK_FUNCTION_LIST *m, CK_SESSION_HANDLE session, CK_OBJECT_HANDLE object, X509 **ret_cert); #endif @@ -64,7 +71,7 @@ typedef int (*pkcs11_find_token_callback_t)(CK_FUNCTION_LIST *m, CK_SESSION_HAND int pkcs11_find_token(const char *pkcs11_uri, pkcs11_find_token_callback_t callback, void *userdata); #if HAVE_OPENSSL -int pkcs11_acquire_certificate(const char *uri, const char *askpw_friendly_name, const char *askpw_icon_name, X509 **ret_cert, char **ret_pin_used); +int pkcs11_acquire_public_key(const char *uri, const char *askpw_friendly_name, const char *askpw_icon, const char *askpw_credential, AskPasswordFlags askpw_flags, EVP_PKEY **ret_pkey, char **ret_pin_used); #endif typedef struct { @@ -75,7 +82,7 @@ typedef struct { void *decrypted_key; size_t decrypted_key_size; bool free_encrypted_key; - bool headless; + const char *askpw_credential; AskPasswordFlags askpw_flags; } pkcs11_crypt_device_callback_data; @@ -103,7 +110,7 @@ static inline int dlopen_p11kit(void) { typedef struct { const char *friendly_name; usec_t until; - bool headless; + const char *askpw_credential; AskPasswordFlags askpw_flags; } systemd_pkcs11_plugin_params; diff --git a/src/shared/pretty-print.c b/src/shared/pretty-print.c index 2833063..c75f74a 100644 --- a/src/shared/pretty-print.c +++ b/src/shared/pretty-print.c @@ -5,6 +5,7 @@ #include <stdio.h> #include "alloc-util.h" +#include "color-util.h" #include "conf-files.h" #include "constants.h" #include "env-util.h" @@ -140,16 +141,8 @@ int terminal_urlify_path(const char *path, const char *text, char **ret) { if (isempty(text)) text = path; - if (!urlify_enabled()) { - char *n; - - n = strdup(text); - if (!n) - return -ENOMEM; - - *ret = n; - return 0; - } + if (!urlify_enabled()) + return strdup_to(ret, text); r = file_url_from_path(path, &url); if (r < 0) @@ -213,7 +206,7 @@ static int cat_file(const char *filename, bool newline, CatFlags flags) { break; LineType line_type = classify_line_type(line, flags); - if (flags & CAT_TLDR) { + if (FLAGS_SET(flags, CAT_TLDR)) { if (line_type == LINE_SECTION) { /* The start of a section, let's not print it yet. */ free_and_replace(section, line); @@ -236,6 +229,28 @@ static int cat_file(const char *filename, bool newline, CatFlags flags) { } } + /* Highlight the left side (directive) of a Foo=bar assignment */ + if (FLAGS_SET(flags, CAT_FORMAT_HAS_SECTIONS) && line_type == LINE_NORMAL) { + const char *p = strchr(line, '='); + if (p) { + _cleanup_free_ char *highlighted = NULL, *directive = NULL; + + directive = strndup(line, p - line); + if (!directive) + return log_oom(); + + highlighted = strjoin(ansi_highlight_green(), + directive, + "=", + ansi_normal(), + p + 1); + if (!highlighted) + return log_oom(); + + free_and_replace(line, highlighted); + } + } + printf("%s%s%s\n", line_type == LINE_SECTION ? ansi_highlight_cyan() : line_type == LINE_COMMENT ? ansi_highlight_grey() : @@ -271,14 +286,12 @@ void print_separator(void) { * one line filled with spaces with ANSI underline set, followed by a second (empty) line. */ if (underline_enabled()) { - size_t i, c; - - c = columns(); + size_t c = columns(); flockfile(stdout); fputs_unlocked(ANSI_UNDERLINE, stdout); - for (i = 0; i < c; i++) + for (size_t i = 0; i < c; i++) fputc_unlocked(' ', stdout); fputs_unlocked(ANSI_NORMAL "\n\n", stdout); @@ -287,7 +300,7 @@ void print_separator(void) { fputs("\n\n", stdout); } -static int guess_type(const char **name, char ***prefixes, bool *is_collection, const char **extension) { +static int guess_type(const char **name, char ***ret_prefixes, bool *ret_is_collection, const char **ret_extension) { /* Try to figure out if name is like tmpfiles.d/ or systemd/system-presets/, * i.e. a collection of directories without a main config file. * Incidentally, all those formats don't use sections. So we return a single @@ -295,11 +308,10 @@ static int guess_type(const char **name, char ***prefixes, bool *is_collection, */ _cleanup_free_ char *n = NULL; - bool usr = false, run = false, coll = false; + bool run = false, coll = false; const char *ext = ".conf"; /* This is static so that the array doesn't get deallocated when we exit the function */ static const char* const std_prefixes[] = { CONF_PATHS(""), NULL }; - static const char* const usr_prefixes[] = { CONF_PATHS_USR(""), NULL }; static const char* const run_prefixes[] = { "/run/", NULL }; if (path_equal(*name, "environment.d")) @@ -311,50 +323,33 @@ static int guess_type(const char **name, char ***prefixes, bool *is_collection, if (!n) return log_oom(); - /* All systemd-style config files should support the /usr-/etc-/run split and - * dropins. Let's add a blanket rule that allows us to support them without keeping - * an explicit list. */ - if (path_startswith(n, "systemd") && endswith(n, ".conf")) - usr = true; - delete_trailing_chars(n, "/"); + /* We assume systemd-style config files support the /usr-/run-/etc split and dropins. */ + if (endswith(n, ".d")) coll = true; - if (path_equal(n, "environment")) - usr = true; - if (path_equal(n, "udev/hwdb.d")) ext = ".hwdb"; - - if (path_equal(n, "udev/rules.d")) + else if (path_equal(n, "udev/rules.d")) ext = ".rules"; - - if (path_equal(n, "kernel/install.d")) + else if (path_equal(n, "kernel/install.d")) ext = ".install"; - - if (path_equal(n, "systemd/ntp-units.d")) { + else if (path_equal(n, "systemd/ntp-units.d")) { coll = true; ext = ".list"; - } - - if (path_equal(n, "systemd/relabel-extra.d")) { + } else if (path_equal(n, "systemd/relabel-extra.d")) { coll = run = true; ext = ".relabel"; - } - - if (PATH_IN_SET(n, "systemd/system-preset", "systemd/user-preset")) { + } else if (PATH_IN_SET(n, "systemd/system-preset", "systemd/user-preset")) { coll = true; ext = ".preset"; } - if (path_equal(n, "systemd/user-preset")) - usr = true; - - *prefixes = (char**) (usr ? usr_prefixes : run ? run_prefixes : std_prefixes); - *is_collection = coll; - *extension = ext; + *ret_prefixes = (char**) (run ? run_prefixes : std_prefixes); + *ret_is_collection = coll; + *ret_extension = ext; return 0; } @@ -419,3 +414,116 @@ int conf_files_cat(const char *root, const char *name, CatFlags flags) { return cat_files(path, files, flags); } + +int terminal_tint_color(double hue, char **ret) { + double red, green, blue; + int r; + + assert(ret); + + r = get_default_background_color(&red, &green, &blue); + if (r < 0) + return log_debug_errno(r, "Unable to get terminal background color: %m"); + + double s, v; + rgb_to_hsv(red, green, blue, /* h= */ NULL, &s, &v); + + if (v > 50) /* If the background is bright, then pull down saturation */ + s = 25; + else /* otherwise pump it up */ + s = 75; + + v = MAX(20, v); /* Make sure we don't hide the color in black */ + + uint8_t r8, g8, b8; + hsv_to_rgb(hue, s, v, &r8, &g8, &b8); + + if (asprintf(ret, "48;2;%u;%u;%u", r8, g8, b8) < 0) + return -ENOMEM; + + return 0; +} + +bool shall_tint_background(void) { + static int cache = -1; + + if (cache >= 0) + return cache; + + cache = getenv_bool("SYSTEMD_TINT_BACKGROUND"); + if (cache == -ENXIO) + return (cache = true); + if (cache < 0) + log_debug_errno(cache, "Failed to parse $SYSTEMD_TINT_BACKGROUND, leaving background tinting enabled: %m"); + + return cache != 0; +} + +void draw_progress_bar(const char *prefix, double percentage) { + + fputc('\r', stderr); + if (prefix) + fputs(prefix, stderr); + + if (!terminal_is_dumb()) { + size_t cols = columns(); + size_t prefix_length = strlen_ptr(prefix); + size_t length = cols > prefix_length + 6 ? cols - prefix_length - 6 : 0; + + if (length > 5 && percentage >= 0.0 && percentage <= 100.0) { + size_t p = (size_t) (length * percentage / 100.0); + bool separator_done = false; + + fputs(ansi_highlight_green(), stderr); + + for (size_t i = 0; i < length; i++) { + + if (i <= p) { + if (get_color_mode() == COLOR_24BIT) { + uint8_t r8, g8, b8; + double z = i == 0 ? 0 : (((double) i / p) * 100); + hsv_to_rgb(145 /* green */, z, 33 + z*2/3, &r8, &g8, &b8); + fprintf(stderr, "\x1B[38;2;%u;%u;%um", r8, g8, b8); + } + + fputs(special_glyph(SPECIAL_GLYPH_HORIZONTAL_FAT), stderr); + } else if (i+1 < length && !separator_done) { + fputs(ansi_normal(), stderr); + fputc(' ', stderr); + separator_done = true; + fputs(ansi_grey(), stderr); + } else + fputs(special_glyph(SPECIAL_GLYPH_HORIZONTAL_DOTTED), stderr); + } + + fputs(ansi_normal(), stderr); + fputc(' ', stderr); + } + } + + fprintf(stderr, + "%s%3.0f%%%s", + ansi_highlight(), + percentage, + ansi_normal()); + + if (!terminal_is_dumb()) + fputs(ANSI_ERASE_TO_END_OF_LINE, stderr); + + fputc('\r', stderr); + fflush(stderr); +} + +void clear_progress_bar(const char *prefix) { + + fputc('\r', stderr); + + if (terminal_is_dumb()) + fputs(strrepa(" ", strlen_ptr(prefix) + 4), /* 4: %3.0f%% */ + stderr); + else + fputs(ANSI_ERASE_TO_END_OF_LINE, stderr); + + fputc('\r', stderr); + fflush(stderr); +} diff --git a/src/shared/pretty-print.h b/src/shared/pretty-print.h index c17e976..c166054 100644 --- a/src/shared/pretty-print.h +++ b/src/shared/pretty-print.h @@ -47,3 +47,10 @@ static inline const char *green_check_mark_internal(char buffer[static GREEN_CHE #define GREEN_CHECK_MARK() green_check_mark_internal((char[GREEN_CHECK_MARK_MAX]) {}) #define COLOR_MARK_BOOL(b) ((b) ? GREEN_CHECK_MARK() : RED_CROSS_MARK()) + +int terminal_tint_color(double hue, char **ret); + +bool shall_tint_background(void); + +void draw_progress_bar(const char *prefix, double percentage); +void clear_progress_bar(const char *prefix); diff --git a/src/shared/ptyfwd.c b/src/shared/ptyfwd.c index 195e603..998ce96 100644 --- a/src/shared/ptyfwd.c +++ b/src/shared/ptyfwd.c @@ -18,13 +18,28 @@ #include "alloc-util.h" #include "errno-util.h" +#include "extract-word.h" #include "fd-util.h" +#include "io-util.h" #include "log.h" #include "macro.h" #include "ptyfwd.h" +#include "stat-util.h" +#include "strv.h" #include "terminal-util.h" #include "time-util.h" +typedef enum AnsiColorState { + ANSI_COLOR_STATE_TEXT, + ANSI_COLOR_STATE_ESC, + ANSI_COLOR_STATE_CSI_SEQUENCE, + ANSI_COLOR_STATE_OSC_SEQUENCE, + ANSI_COLOR_STATE_NEWLINE, + ANSI_COLOR_STATE_CARRIAGE_RETURN, + _ANSI_COLOR_STATE_MAX, + _ANSI_COLOR_STATE_INVALID = -EINVAL, +} AnsiColorState; + struct PTYForward { sd_event *event; @@ -65,7 +80,8 @@ struct PTYForward { bool last_char_set:1; char last_char; - char in_buffer[LINE_MAX], out_buffer[LINE_MAX]; + char in_buffer[LINE_MAX], *out_buffer; + size_t out_buffer_size; size_t in_buffer_full, out_buffer_full; usec_t escape_timestamp; @@ -73,6 +89,14 @@ struct PTYForward { PTYForwardHandler handler; void *userdata; + + char *background_color; + AnsiColorState ansi_color_state; + char *csi_sequence; + char *osc_sequence; + + char *title; /* Window title to show by default */ + char *title_prefix; /* If terminal client overrides window title, prefix this string */ }; #define ESCAPE_USEC (1*USEC_PER_SEC) @@ -95,6 +119,14 @@ static void pty_forward_disconnect(PTYForward *f) { /* STDIN/STDOUT should not be non-blocking normally, so let's reset it */ (void) fd_nonblock(f->output_fd, false); + + if (colors_enabled()) { + (void) loop_write(f->output_fd, ANSI_NORMAL ANSI_ERASE_TO_END_OF_SCREEN, SIZE_MAX); + + if (f->title) + (void) loop_write(f->output_fd, ANSI_WINDOW_TITLE_POP, SIZE_MAX); + } + if (f->close_output_fd) f->output_fd = safe_close(f->output_fd); } @@ -109,6 +141,15 @@ static void pty_forward_disconnect(PTYForward *f) { } f->saved_stdout = f->saved_stdin = false; + + f->out_buffer = mfree(f->out_buffer); + f->out_buffer_size = 0; + f->out_buffer_full = 0; + f->in_buffer_full = 0; + + f->csi_sequence = mfree(f->csi_sequence); + f->osc_sequence = mfree(f->osc_sequence); + f->ansi_color_state = _ANSI_COLOR_STATE_INVALID; } static int pty_forward_done(PTYForward *f, int rcode) { @@ -142,7 +183,7 @@ static bool look_for_escape(PTYForward *f, const char *buffer, size_t n) { if (*p == 0x1D) { usec_t nw = now(CLOCK_MONOTONIC); - if (f->escape_counter == 0 || nw > f->escape_timestamp + ESCAPE_USEC) { + if (f->escape_counter == 0 || nw > f->escape_timestamp + ESCAPE_USEC) { f->escape_timestamp = nw; f->escape_counter = 1; } else { @@ -196,11 +237,321 @@ static bool drained(PTYForward *f) { return true; } -static int shovel(PTYForward *f) { +static char *background_color_sequence(PTYForward *f) { + assert(f); + assert(f->background_color); + + return strjoin("\x1B[", f->background_color, "m"); +} + +static int insert_string(PTYForward *f, size_t offset, const char *s) { + assert(f); + assert(offset <= f->out_buffer_full); + assert(s); + + size_t l = strlen(s); + assert(l <= INT_MAX); /* Make sure we can still return this */ + + void *p = realloc(f->out_buffer, MAX(f->out_buffer_full + l, (size_t) LINE_MAX)); + if (!p) + return -ENOMEM; + + f->out_buffer = p; + f->out_buffer_size = MALLOC_SIZEOF_SAFE(f->out_buffer); + + memmove(f->out_buffer + offset + l, f->out_buffer + offset, f->out_buffer_full - offset); + memcpy(f->out_buffer + offset, s, l); + f->out_buffer_full += l; + + return (int) l; +} + +static int insert_background_color(PTYForward *f, size_t offset) { + _cleanup_free_ char *s = NULL; + + assert(f); + + if (!f->background_color) + return 0; + + s = background_color_sequence(f); + if (!s) + return -ENOMEM; + + return insert_string(f, offset, s); +} + +static int is_csi_background_reset_sequence(const char *seq) { + enum { + COLOR_TOKEN_NO, + COLOR_TOKEN_START, + COLOR_TOKEN_8BIT, + COLOR_TOKEN_24BIT_R, + COLOR_TOKEN_24BIT_G, + COLOR_TOKEN_24BIT_B, + } token_state = COLOR_TOKEN_NO; + + bool b = false; + int r; + + assert(seq); + + /* This parses CSI "m" sequences, and determines if they reset the background color. If so returns + * 1. This can then be used to insert another sequence that sets the color to the desired + * replacement. */ + + for (;;) { + _cleanup_free_ char *token = NULL; + + r = extract_first_word(&seq, &token, ";", EXTRACT_RELAX|EXTRACT_DONT_COALESCE_SEPARATORS|EXTRACT_RETAIN_ESCAPE); + if (r < 0) + return r; + if (r == 0) + break; + + switch (token_state) { + + case COLOR_TOKEN_NO: + + if (STR_IN_SET(token, "", "0", "00", "49")) + b = true; /* These tokens set the background back to normal */ + else if (STR_IN_SET(token, "40", "41", "42", "43", "44", "45", "46", "47", "48")) + b = false; /* And these tokens set them to something other than normal */ + + if (STR_IN_SET(token, "38", "48", "58")) + token_state = COLOR_TOKEN_START; /* These tokens mean an 8bit or 24bit color will follow */ + break; + + case COLOR_TOKEN_START: + + if (STR_IN_SET(token, "5", "05")) + token_state = COLOR_TOKEN_8BIT; /* 8bit color */ + else if (STR_IN_SET(token, "2", "02")) + token_state = COLOR_TOKEN_24BIT_R; /* 24bit color */ + else + token_state = COLOR_TOKEN_NO; /* something weird? */ + break; + + case COLOR_TOKEN_24BIT_R: + token_state = COLOR_TOKEN_24BIT_G; + break; + + case COLOR_TOKEN_24BIT_G: + token_state = COLOR_TOKEN_24BIT_B; + break; + + case COLOR_TOKEN_8BIT: + case COLOR_TOKEN_24BIT_B: + token_state = COLOR_TOKEN_NO; + break; + } + } + + return b; +} + +static int insert_background_fix(PTYForward *f, size_t offset) { + assert(f); + + if (!f->background_color) + return 0; + + if (!is_csi_background_reset_sequence(strempty(f->csi_sequence))) + return 0; + + _cleanup_free_ char *s = NULL; + s = strjoin(";", f->background_color); + if (!s) + return -ENOMEM; + + return insert_string(f, offset, s); +} + +static int insert_window_title_fix(PTYForward *f, size_t offset) { + assert(f); + + if (!f->title_prefix) + return 0; + + if (!f->osc_sequence) + return 0; + + const char *t = startswith(f->osc_sequence, "0;"); /* Set window title OSC sequence*/ + if (!t) + return 0; + + _cleanup_free_ char *joined = strjoin("\x1b]0;", f->title_prefix, t, "\a"); + if (!joined) + return -ENOMEM; + + return insert_string(f, offset, joined); +} + +static int pty_forward_ansi_process(PTYForward *f, size_t offset) { + int r; + + assert(f); + assert(offset <= f->out_buffer_full); + + if (!f->background_color && !f->title_prefix) + return 0; + + if (FLAGS_SET(f->flags, PTY_FORWARD_DUMB_TERMINAL)) + return 0; + + for (size_t i = offset; i < f->out_buffer_full; i++) { + char c = f->out_buffer[i]; + + switch (f->ansi_color_state) { + + case ANSI_COLOR_STATE_TEXT: + break; + + case ANSI_COLOR_STATE_NEWLINE: + case ANSI_COLOR_STATE_CARRIAGE_RETURN: + /* Immediately after a newline (ASCII 10) or carriage return (ASCII 13) insert an + * ANSI sequence set the background color back. */ + + r = insert_background_color(f, i); + if (r < 0) + return r; + + i += r; + break; + + case ANSI_COLOR_STATE_ESC: + + if (c == '[') { + f->ansi_color_state = ANSI_COLOR_STATE_CSI_SEQUENCE; + continue; + } else if (c == ']') { + f->ansi_color_state = ANSI_COLOR_STATE_OSC_SEQUENCE; + continue; + } + break; + + case ANSI_COLOR_STATE_CSI_SEQUENCE: + + if (c >= 0x20 && c <= 0x3F) { + /* If this is a "parameter" or "intermediary" byte (i.e. ranges 0x20…0x2F and + * 0x30…0x3F) then we are still in the CSI sequence */ + + if (strlen_ptr(f->csi_sequence) >= 64) { + /* Safety check: lets not accept unbounded CSI sequences */ + + f->csi_sequence = mfree(f->csi_sequence); + break; + } else if (!strextend(&f->csi_sequence, CHAR_TO_STR(c))) + return -ENOMEM; + } else { + /* Otherwise, the CSI sequence is over */ + + if (c == 'm') { + /* This is an "SGR" (Select Graphic Rendition) sequence. Patch in our background color. */ + r = insert_background_fix(f, i); + if (r < 0) + return r; + + i += r; + } + + f->csi_sequence = mfree(f->csi_sequence); + f->ansi_color_state = ANSI_COLOR_STATE_TEXT; + } + continue; + + case ANSI_COLOR_STATE_OSC_SEQUENCE: + + if ((uint8_t) c >= ' ') { + if (strlen_ptr(f->osc_sequence) >= 64) { + /* Safety check: lets not accept unbounded OSC sequences */ + f->osc_sequence = mfree(f->osc_sequence); + break; + } else if (!strextend(&f->osc_sequence, CHAR_TO_STR(c))) + return -ENOMEM; + } else { + /* Otherwise, the OSC sequence is over + * + * There are two allowed ways to end an OSC sequence: + * BEL '\x07' + * String Terminator (ST): <Esc>\ - "\x1b\x5c" + * since we cannot lookahead to see if the Esc is followed by a \ + * we cut a corner here and assume it will be \. */ + + if (IN_SET(c, '\x07', '\x1b')) { + r = insert_window_title_fix(f, i+1); + if (r < 0) + return r; + + i += r; + } + + f->osc_sequence = mfree(f->osc_sequence); + f->ansi_color_state = ANSI_COLOR_STATE_TEXT; + } + continue; + + default: + assert_not_reached(); + } + + if (c == '\n') + f->ansi_color_state = ANSI_COLOR_STATE_NEWLINE; + else if (c == '\r') + f->ansi_color_state = ANSI_COLOR_STATE_CARRIAGE_RETURN; + else if (c == 0x1B) /* ESC */ + f->ansi_color_state = ANSI_COLOR_STATE_ESC; + else + f->ansi_color_state = ANSI_COLOR_STATE_TEXT; + } + + return 0; +} + +static int do_shovel(PTYForward *f) { ssize_t k; + int r; assert(f); + if (f->out_buffer_size == 0 && !FLAGS_SET(f->flags, PTY_FORWARD_DUMB_TERMINAL)) { + /* If the output hasn't been allocated yet, we are at the beginning of the first + * shovelling. Hence, possibly send some initial ANSI sequences. But do so only if we are + * talking to an actual TTY. */ + + if (f->background_color) { + /* Erase the first line when we start */ + f->out_buffer = background_color_sequence(f); + if (!f->out_buffer) + return log_oom(); + + if (!strextend(&f->out_buffer, ANSI_ERASE_TO_END_OF_LINE)) + return log_oom(); + } + + if (f->title) { + if (!strextend(&f->out_buffer, + ANSI_WINDOW_TITLE_PUSH + "\x1b]2;", f->title, "\a")) + return log_oom(); + } + + if (f->out_buffer) { + f->out_buffer_full = strlen(f->out_buffer); + f->out_buffer_size = MALLOC_SIZEOF_SAFE(f->out_buffer); + } + } + + if (f->out_buffer_size < LINE_MAX) { + /* Make sure we always have room for at least one "line" */ + void *p = realloc(f->out_buffer, LINE_MAX); + if (!p) + return log_oom(); + + f->out_buffer = p; + f->out_buffer_size = MALLOC_SIZEOF_SAFE(p); + } + while ((f->stdin_readable && f->in_buffer_full <= 0) || (f->master_writable && f->in_buffer_full > 0) || (f->master_readable && f->out_buffer_full <= 0) || @@ -218,21 +569,19 @@ static int shovel(PTYForward *f) { f->stdin_hangup = true; f->stdin_event_source = sd_event_source_unref(f->stdin_event_source); - } else { - log_error_errno(errno, "read(): %m"); - return pty_forward_done(f, -errno); - } + } else + return log_error_errno(errno, "Failed to read from pty input fd: %m"); } else if (k == 0) { /* EOF on stdin */ f->stdin_readable = false; f->stdin_hangup = true; f->stdin_event_source = sd_event_source_unref(f->stdin_event_source); - } else { + } else { /* Check if ^] has been pressed three times within one second. If we get this we quite * immediately. */ if (look_for_escape(f, f->in_buffer + f->in_buffer_full, k)) - return pty_forward_done(f, -ECANCELED); + return -ECANCELED; f->in_buffer_full += (size_t) k; } @@ -250,10 +599,8 @@ static int shovel(PTYForward *f) { f->master_hangup = true; f->master_event_source = sd_event_source_unref(f->master_event_source); - } else { - log_error_errno(errno, "write(): %m"); - return pty_forward_done(f, -errno); - } + } else + return log_error_errno(errno, "write(): %m"); } else { assert(f->in_buffer_full >= (size_t) k); memmove(f->in_buffer, f->in_buffer + k, f->in_buffer_full - k); @@ -261,17 +608,14 @@ static int shovel(PTYForward *f) { } } - if (f->master_readable && f->out_buffer_full < LINE_MAX) { + if (f->master_readable && f->out_buffer_full < MIN(f->out_buffer_size, (size_t) LINE_MAX)) { - k = read(f->master, f->out_buffer + f->out_buffer_full, LINE_MAX - f->out_buffer_full); + k = read(f->master, f->out_buffer + f->out_buffer_full, f->out_buffer_size - f->out_buffer_full); if (k < 0) { - /* Note that EIO on the master device - * might be caused by vhangup() or - * temporary closing of everything on - * the other side, we treat it like - * EAGAIN here and try again, unless - * ignore_vhangup is off. */ + /* Note that EIO on the master device might be caused by vhangup() or + * temporary closing of everything on the other side, we treat it like EAGAIN + * here and try again, unless ignore_vhangup is off. */ if (errno == EAGAIN || (errno == EIO && ignore_vhangup(f))) f->master_readable = false; @@ -280,13 +624,16 @@ static int shovel(PTYForward *f) { f->master_hangup = true; f->master_event_source = sd_event_source_unref(f->master_event_source); - } else { - log_error_errno(errno, "read(): %m"); - return pty_forward_done(f, -errno); - } - } else { + } else + return log_error_errno(errno, "Failed to read from pty master fd: %m"); + } else { f->read_from_master = true; + size_t scan_index = f->out_buffer_full; f->out_buffer_full += (size_t) k; + + r = pty_forward_ansi_process(f, scan_index); + if (r < 0) + return log_error_errno(r, "Failed to scan for ANSI sequences: %m"); } } @@ -301,10 +648,8 @@ static int shovel(PTYForward *f) { f->stdout_writable = false; f->stdout_hangup = true; f->stdout_event_source = sd_event_source_unref(f->stdout_event_source); - } else { - log_error_errno(errno, "write(): %m"); - return pty_forward_done(f, -errno); - } + } else + return log_error_errno(errno, "Failed to write to pty output fd: %m"); } else { @@ -337,6 +682,18 @@ static int shovel(PTYForward *f) { return 0; } +static int shovel(PTYForward *f) { + int r; + + assert(f); + + r = do_shovel(f); + if (r < 0) + return pty_forward_done(f, r); + + return r; +} + static int on_master_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) { PTYForward *f = ASSERT_PTR(userdata); @@ -406,11 +763,14 @@ int pty_forward_new( struct winsize ws; int r; + assert(master >= 0); + assert(ret); + f = new(PTYForward, 1); if (!f) return -ENOMEM; - *f = (struct PTYForward) { + *f = (PTYForward) { .flags = flags, .master = -EBADF, .input_fd = -EBADF, @@ -430,13 +790,17 @@ int pty_forward_new( else { /* If we shall be invoked in interactive mode, let's switch on non-blocking mode, so that we * never end up staving one direction while we block on the other. However, let's be careful - * here and not turn on O_NONBLOCK for stdin/stdout directly, but of re-opened copies of + * here and not turn on O_NONBLOCK for stdin/stdout directly, but of reopened copies of * them. This has two advantages: when we are killed abruptly the stdin/stdout fds won't be * left in O_NONBLOCK state for the next process using them. In addition, if some process * running in the background wants to continue writing to our stdout it can do so without - * being confused by O_NONBLOCK. */ + * being confused by O_NONBLOCK. + * We keep O_APPEND (if present) on the output FD and (try to) keep current file position on + * both input and output FD (principle of least surprise). + */ - f->input_fd = fd_reopen(STDIN_FILENO, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NONBLOCK); + f->input_fd = fd_reopen_propagate_append_and_position( + STDIN_FILENO, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NONBLOCK); if (f->input_fd < 0) { /* Handle failures gracefully, after all certain fd types cannot be reopened * (sockets, …) */ @@ -450,7 +814,8 @@ int pty_forward_new( } else f->close_input_fd = true; - f->output_fd = fd_reopen(STDOUT_FILENO, O_WRONLY|O_CLOEXEC|O_NOCTTY|O_NONBLOCK); + f->output_fd = fd_reopen_propagate_append_and_position( + STDOUT_FILENO, O_WRONLY|O_CLOEXEC|O_NOCTTY|O_NONBLOCK); if (f->output_fd < 0) { log_debug_errno(f->output_fd, "Failed to reopen stdout, using original fd: %m"); @@ -469,6 +834,10 @@ int pty_forward_new( f->master = master; + /* Disable color/window title setting unless we talk to a good TTY */ + if (!isatty_safe(f->output_fd) || get_color_mode() == COLOR_OFF) + f->flags |= PTY_FORWARD_DUMB_TERMINAL; + if (ioctl(f->output_fd, TIOCGWINSZ, &ws) < 0) /* If we can't get the resolution from the output fd, then use our internal, regular width/height, * i.e. something derived from $COLUMNS and $LINES if set. */ @@ -479,9 +848,16 @@ int pty_forward_new( (void) ioctl(master, TIOCSWINSZ, &ws); - if (!(flags & PTY_FORWARD_READ_ONLY)) { + if (!FLAGS_SET(flags, PTY_FORWARD_READ_ONLY)) { + bool same; + assert(f->input_fd >= 0); + r = fd_inode_same(f->input_fd, f->output_fd); + if (r < 0) + return r; + same = r > 0; + if (tcgetattr(f->input_fd, &f->saved_stdin_attr) >= 0) { struct termios raw_stdin_attr; @@ -489,11 +865,14 @@ int pty_forward_new( raw_stdin_attr = f->saved_stdin_attr; cfmakeraw(&raw_stdin_attr); - raw_stdin_attr.c_oflag = f->saved_stdin_attr.c_oflag; - tcsetattr(f->input_fd, TCSANOW, &raw_stdin_attr); + + if (!same) + raw_stdin_attr.c_oflag = f->saved_stdin_attr.c_oflag; + + (void) tcsetattr(f->input_fd, TCSANOW, &raw_stdin_attr); } - if (tcgetattr(f->output_fd, &f->saved_stdout_attr) >= 0) { + if (!same && tcgetattr(f->output_fd, &f->saved_stdout_attr) >= 0) { struct termios raw_stdout_attr; f->saved_stdout = true; @@ -502,7 +881,7 @@ int pty_forward_new( cfmakeraw(&raw_stdout_attr); raw_stdout_attr.c_iflag = f->saved_stdout_attr.c_iflag; raw_stdout_attr.c_lflag = f->saved_stdout_attr.c_lflag; - tcsetattr(f->output_fd, TCSANOW, &raw_stdout_attr); + (void) tcsetattr(f->output_fd, TCSANOW, &raw_stdout_attr); } r = sd_event_add_io(f->event, &f->stdin_event_source, f->input_fd, EPOLLIN|EPOLLET, on_stdin_event, f); @@ -540,7 +919,14 @@ int pty_forward_new( } PTYForward *pty_forward_free(PTYForward *f) { + if (!f) + return NULL; + pty_forward_disconnect(f); + free(f->background_color); + free(f->title); + free(f->title_prefix); + return mfree(f); } @@ -560,15 +946,14 @@ int pty_forward_set_ignore_vhangup(PTYForward *f, bool b) { assert(f); - if (!!(f->flags & PTY_FORWARD_IGNORE_VHANGUP) == b) + if (FLAGS_SET(f->flags, PTY_FORWARD_IGNORE_VHANGUP) == b) return 0; SET_FLAG(f->flags, PTY_FORWARD_IGNORE_VHANGUP, b); if (!ignore_vhangup(f)) { - /* We shall now react to vhangup()s? Let's check - * immediately if we might be in one */ + /* We shall now react to vhangup()s? Let's check immediately if we might be in one. */ f->master_readable = true; r = shovel(f); @@ -582,7 +967,7 @@ int pty_forward_set_ignore_vhangup(PTYForward *f, bool b) { bool pty_forward_get_ignore_vhangup(PTYForward *f) { assert(f); - return !!(f->flags & PTY_FORWARD_IGNORE_VHANGUP); + return FLAGS_SET(f->flags, PTY_FORWARD_IGNORE_VHANGUP); } bool pty_forward_is_done(PTYForward *f) { @@ -614,6 +999,7 @@ bool pty_forward_drain(PTYForward *f) { int pty_forward_set_priority(PTYForward *f, int64_t priority) { int r; + assert(f); if (f->stdin_event_source) { @@ -675,3 +1061,45 @@ int pty_forward_set_width_height(PTYForward *f, unsigned width, unsigned height) return 0; } + +int pty_forward_set_background_color(PTYForward *f, const char *color) { + assert(f); + + return free_and_strdup(&f->background_color, color); +} + +int pty_forward_set_title(PTYForward *f, const char *title) { + assert(f); + + /* Refuse accepting a title when we already started shoveling */ + if (f->out_buffer_size > 0) + return -EBUSY; + + return free_and_strdup(&f->title, title); +} + +int pty_forward_set_titlef(PTYForward *f, const char *format, ...) { + _cleanup_free_ char *title = NULL; + va_list ap; + int r; + + assert(f); + assert(format); + + if (f->out_buffer_size > 0) + return -EBUSY; + + va_start(ap, format); + r = vasprintf(&title, format, ap); + va_end(ap); + if (r < 0) + return -ENOMEM; + + return free_and_replace(f->title, title); +} + +int pty_forward_set_title_prefix(PTYForward *f, const char *title_prefix) { + assert(f); + + return free_and_strdup(&f->title_prefix, title_prefix); +} diff --git a/src/shared/ptyfwd.h b/src/shared/ptyfwd.h index f0ae6e9..248646d 100644 --- a/src/shared/ptyfwd.h +++ b/src/shared/ptyfwd.h @@ -10,13 +10,17 @@ typedef struct PTYForward PTYForward; typedef enum PTYForwardFlags { - PTY_FORWARD_READ_ONLY = 1, + /* Only output to STDOUT, never try to read from STDIN */ + PTY_FORWARD_READ_ONLY = 1 << 0, /* Continue reading after hangup? */ - PTY_FORWARD_IGNORE_VHANGUP = 2, + PTY_FORWARD_IGNORE_VHANGUP = 1 << 1, /* Continue reading after hangup but only if we never read anything else? */ - PTY_FORWARD_IGNORE_INITIAL_VHANGUP = 4, + PTY_FORWARD_IGNORE_INITIAL_VHANGUP = 1 << 2, + + /* Don't tint the background, or set window title */ + PTY_FORWARD_DUMB_TERMINAL = 1 << 3, } PTYForwardFlags; typedef int (*PTYForwardHandler)(PTYForward *f, int rcode, void *userdata); @@ -39,4 +43,11 @@ int pty_forward_set_priority(PTYForward *f, int64_t priority); int pty_forward_set_width_height(PTYForward *f, unsigned width, unsigned height); +int pty_forward_set_background_color(PTYForward *f, const char *color); + +int pty_forward_set_title(PTYForward *f, const char *title); +int pty_forward_set_titlef(PTYForward *f, const char *format, ...) _printf_(2,3); + +int pty_forward_set_title_prefix(PTYForward *f, const char *prefix); + DEFINE_TRIVIAL_CLEANUP_FUNC(PTYForward*, pty_forward_free); diff --git a/src/shared/qrcode-util.c b/src/shared/qrcode-util.c index b0dd90a..e62a5a8 100644 --- a/src/shared/qrcode-util.c +++ b/src/shared/qrcode-util.c @@ -18,12 +18,17 @@ static void *qrcode_dl = NULL; -static QRcode* (*sym_QRcode_encodeString)(const char *string, int version, QRecLevel level, QRencodeMode hint, int casesensitive) = NULL; -static void (*sym_QRcode_free)(QRcode *qrcode) = NULL; +static DLSYM_FUNCTION(QRcode_encodeString); +static DLSYM_FUNCTION(QRcode_free); int dlopen_qrencode(void) { int r; + ELF_NOTE_DLOPEN("qrencode", + "Support for generating QR codes", + ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + "libqrencode.so.4", "libqrencode.so.3"); + FOREACH_STRING(s, "libqrencode.so.4", "libqrencode.so.3") { r = dlopen_many_sym_or_warn( &qrcode_dl, s, LOG_DEBUG, diff --git a/src/shared/reboot-util.c b/src/shared/reboot-util.c index 62ff697..b1c1aa7 100644 --- a/src/shared/reboot-util.c +++ b/src/shared/reboot-util.c @@ -23,8 +23,15 @@ #include "reboot-util.h" #include "string-util.h" #include "umask-util.h" +#include "utf8.h" #include "virt.h" +bool reboot_parameter_is_valid(const char *parameter) { + assert(parameter); + + return ascii_is_valid(parameter) && strlen(parameter) <= NAME_MAX; +} + int update_reboot_parameter_and_warn(const char *parameter, bool keep) { int r; @@ -42,6 +49,9 @@ int update_reboot_parameter_and_warn(const char *parameter, bool keep) { return 0; } + if (!reboot_parameter_is_valid(parameter)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid reboot parameter '%s'.", parameter); + WITH_UMASK(0022) { r = write_string_file("/run/systemd/reboot-param", parameter, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC); diff --git a/src/shared/reboot-util.h b/src/shared/reboot-util.h index ccd15c7..2ac478f 100644 --- a/src/shared/reboot-util.h +++ b/src/shared/reboot-util.h @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +bool reboot_parameter_is_valid(const char *parameter); int update_reboot_parameter_and_warn(const char *parameter, bool keep); typedef enum RebootFlags { diff --git a/src/shared/resize-fs.c b/src/shared/resize-fs.c index 178aefa..b1c4a49 100644 --- a/src/shared/resize-fs.c +++ b/src/shared/resize-fs.c @@ -61,7 +61,7 @@ int resize_fs(int fd, uint64_t sz, uint64_t *ret_size) { if (ret_size) *ret_size = sz; - } else if (is_fs_type(&sfs, XFS_SB_MAGIC)) { + } else if (is_fs_type(&sfs, XFS_SUPER_MAGIC)) { xfs_fsop_geom_t geo; xfs_growfs_data_t d; @@ -95,7 +95,7 @@ uint64_t minimal_size_by_fs_magic(statfs_f_type_t magic) { case (statfs_f_type_t) EXT4_SUPER_MAGIC: return EXT4_MINIMAL_SIZE; - case (statfs_f_type_t) XFS_SB_MAGIC: + case (statfs_f_type_t) XFS_SUPER_MAGIC: return XFS_MINIMAL_SIZE; case (statfs_f_type_t) BTRFS_SUPER_MAGIC: diff --git a/src/shared/rm-rf.c b/src/shared/rm-rf.c index 4664215..767db48 100644 --- a/src/shared/rm-rf.c +++ b/src/shared/rm-rf.c @@ -326,7 +326,7 @@ static int rm_rf_children_impl( dirname = mfree(dirname); /* And now let's back out one level up */ - n_todo --; + n_todo--; d = TAKE_PTR(todos[n_todo].dir); dirname = TAKE_PTR(todos[n_todo].dirname); old_mode = todos[n_todo].old_mode; diff --git a/src/shared/seccomp-util.c b/src/shared/seccomp-util.c index 00a8ced..2469e24 100644 --- a/src/shared/seccomp-util.c +++ b/src/shared/seccomp-util.c @@ -298,7 +298,7 @@ bool is_seccomp_available(void) { if (cached_enabled < 0) { int b; - b = getenv_bool_secure("SYSTEMD_SECCOMP"); + b = secure_getenv_bool("SYSTEMD_SECCOMP"); if (b != 0) { if (b < 0 && b != -ENXIO) /* ENXIO: env var unset */ log_debug_errno(b, "Failed to parse $SYSTEMD_SECCOMP value, ignoring."); diff --git a/src/shared/selinux-util.c b/src/shared/selinux-util.c index 2fef29c..d2b1a3e 100644 --- a/src/shared/selinux-util.c +++ b/src/shared/selinux-util.c @@ -39,8 +39,6 @@ typedef enum Initialized { LAZY_INITIALIZED, } Initialized; -static int mac_selinux_reload(int seqno); - static int cached_use = -1; static Initialized initialized = UNINITIALIZED; static int last_policyload = 0; @@ -214,6 +212,16 @@ int mac_selinux_init_lazy(void) { return 0; } +#if HAVE_SELINUX +static int mac_selinux_reload(int seqno) { + log_debug("SELinux reload %d", seqno); + + (void) open_label_db(); + + return 0; +} +#endif + void mac_selinux_maybe_reload(void) { #if HAVE_SELINUX int policyload; @@ -257,16 +265,6 @@ void mac_selinux_finish(void) { } #if HAVE_SELINUX -static int mac_selinux_reload(int seqno) { - log_debug("SELinux reload %d", seqno); - - (void) open_label_db(); - - return 0; -} -#endif - -#if HAVE_SELINUX static int selinux_fix_fd( int fd, const char *label_path, diff --git a/src/shared/serialize.c b/src/shared/serialize.c index 344b102..735caf4 100644 --- a/src/shared/serialize.c +++ b/src/shared/serialize.c @@ -485,7 +485,7 @@ int deserialize_pidref(FDSet *fds, const char *value, PidRef *ret) { _cleanup_free_ char *fdstr = NULL, *pidstr = NULL; _cleanup_close_ int fd = -EBADF; - r = extract_many_words(&e, ":", /* flags = */ 0, &fdstr, &pidstr, NULL); + r = extract_many_words(&e, ":", /* flags = */ 0, &fdstr, &pidstr); if (r < 0) return log_debug_errno(r, "Failed to deserialize pidref '%s': %m", e); if (r == 0) diff --git a/src/shared/service-util.c b/src/shared/service-util.c index b0585ba..7d46ee7 100644 --- a/src/shared/service-util.c +++ b/src/shared/service-util.c @@ -17,19 +17,22 @@ static int help(const char *program_path, const char *service, const char *descr if (r < 0) return log_oom(); - printf("%s [OPTIONS...]\n\n" - "%s%s%s\n\n" - "This program takes no positional arguments.\n\n" - "%sOptions%s:\n" + printf("%1$s [OPTIONS...]\n" + "\n%5$s%7$s%6$s\n" + "\nThis program takes no positional arguments.\n" + "\n%3$sOptions:%4$s\n" " -h --help Show this help\n" " --version Show package version\n" - " --bus-introspect=PATH Write D-Bus XML introspection data\n" - "\nSee the %s for details.\n" - , program_path - , ansi_highlight(), description, ansi_normal() - , ansi_underline(), ansi_normal() - , link - ); + "%8$s" + "\nSee the %2$s for details.\n", + program_path, + link, + ansi_underline(), + ansi_normal(), + ansi_highlight(), + ansi_normal(), + description, + bus_introspect ? " --bus-introspect=PATH Write D-Bus XML introspection data\n" : ""); return 0; /* No further action */ } diff --git a/src/shared/sleep-config.c b/src/shared/sleep-config.c index 7282111..3d4d331 100644 --- a/src/shared/sleep-config.c +++ b/src/shared/sleep-config.c @@ -54,6 +54,8 @@ SleepConfig* sleep_config_free(SleepConfig *sc) { strv_free(sc->modes[i]); } + strv_free(sc->mem_modes); + return mfree(sc); } @@ -69,8 +71,8 @@ static int config_parse_sleep_mode( void *data, void *userdata) { - _cleanup_strv_free_ char **modes = NULL; char ***sv = ASSERT_PTR(data); + _cleanup_strv_free_ char **modes = NULL; int r; assert(filename); @@ -87,7 +89,7 @@ static int config_parse_sleep_mode( return log_oom(); } - return free_and_replace(*sv, modes); + return strv_free_and_replace(*sv, modes); } static void sleep_config_validate_state_and_mode(SleepConfig *sc) { @@ -140,14 +142,19 @@ int parse_sleep_config(SleepConfig **ret) { { "Sleep", "HybridSleepState", config_parse_warn_compat, DISABLED_LEGACY, NULL }, { "Sleep", "HybridSleepMode", config_parse_warn_compat, DISABLED_LEGACY, NULL }, + { "Sleep", "MemorySleepMode", config_parse_sleep_mode, 0, &sc->mem_modes }, + { "Sleep", "HibernateDelaySec", config_parse_sec, 0, &sc->hibernate_delay_usec }, { "Sleep", "SuspendEstimationSec", config_parse_sec, 0, &sc->suspend_estimation_usec }, {} }; - (void) config_parse_config_file("sleep.conf", "Sleep\0", - config_item_table_lookup, items, - CONFIG_PARSE_WARN, NULL); + (void) config_parse_standard_file_with_dropins( + "systemd/sleep.conf", + "Sleep\0", + config_item_table_lookup, items, + CONFIG_PARSE_WARN, + /* userdata= */ NULL); /* use default values unless set */ sc->allow[SLEEP_SUSPEND] = allow_suspend != 0; @@ -180,7 +187,7 @@ int parse_sleep_config(SleepConfig **ret) { return 0; } -int sleep_state_supported(char **states) { +int sleep_state_supported(char * const *states) { _cleanup_free_ char *supported_sysfs = NULL; const char *found; int r; @@ -210,22 +217,24 @@ int sleep_state_supported(char **states) { return false; } -int sleep_mode_supported(char **modes) { +int sleep_mode_supported(const char *path, char * const *modes) { _cleanup_free_ char *supported_sysfs = NULL; int r; + assert(path); + /* Unlike state, kernel has its own default choice if not configured */ if (strv_isempty(modes)) { - log_debug("No sleep mode configured, using kernel default."); + log_debug("No sleep mode configured, using kernel default for %s.", path); return true; } - if (access("/sys/power/disk", W_OK) < 0) - return log_debug_errno(errno, "/sys/power/disk is not writable: %m"); + if (access(path, W_OK) < 0) + return log_debug_errno(errno, "%s is not writable: %m", path); - r = read_one_line_file("/sys/power/disk", &supported_sysfs); + r = read_one_line_file(path, &supported_sysfs); if (r < 0) - return log_debug_errno(r, "Failed to read /sys/power/disk: %m"); + return log_debug_errno(r, "Failed to read %s: %m", path); for (const char *p = supported_sysfs;;) { _cleanup_free_ char *word = NULL; @@ -234,7 +243,7 @@ int sleep_mode_supported(char **modes) { r = extract_first_word(&p, &word, NULL, 0); if (r < 0) - return log_debug_errno(r, "Failed to parse /sys/power/disk: %m"); + return log_debug_errno(r, "Failed to parse %s: %m", path); if (r == 0) break; @@ -247,14 +256,15 @@ int sleep_mode_supported(char **modes) { } if (strv_contains(modes, mode)) { - log_debug("Disk sleep mode '%s' is supported by kernel.", mode); + log_debug("Sleep mode '%s' is supported by kernel (%s).", mode, path); return true; } } if (DEBUG_LOGGING) { _cleanup_free_ char *joined = strv_join(modes, " "); - log_debug("None of the configured hibernation power modes are supported by kernel: %s", strnull(joined)); + log_debug("None of the configured modes are supported by kernel (%s): %s", + path, strnull(joined)); } return false; } @@ -284,7 +294,7 @@ static int s2h_supported(const SleepConfig *sleep_config, SleepSupport *ret_supp return false; } - FOREACH_ARRAY(i, operations, ELEMENTSOF(operations)) { + FOREACH_ELEMENT(i, operations) { r = sleep_supported_internal(sleep_config, *i, /* check_allowed = */ false, &support); if (r < 0) return r; @@ -338,8 +348,18 @@ static int sleep_supported_internal( return false; } - if (sleep_operation_is_hibernation(operation)) { - r = sleep_mode_supported(sleep_config->modes[operation]); + if (SLEEP_NEEDS_MEM_SLEEP(sleep_config, operation)) { + r = sleep_mode_supported("/sys/power/mem_sleep", sleep_config->mem_modes); + if (r < 0) + return r; + if (r == 0) { + *ret_support = SLEEP_STATE_OR_MODE_NOT_SUPPORTED; + return false; + } + } + + if (SLEEP_OPERATION_IS_HIBERNATION(operation)) { + r = sleep_mode_supported("/sys/power/disk", sleep_config->modes[operation]); if (r < 0) return r; if (r == 0) { @@ -348,16 +368,28 @@ static int sleep_supported_internal( } r = hibernation_is_safe(); - if (r == -ENOTRECOVERABLE) { + switch (r) { + + case -ENOTRECOVERABLE: *ret_support = SLEEP_RESUME_NOT_SUPPORTED; return false; - } - if (r == -ENOSPC) { + + case -ESTALE: + *ret_support = SLEEP_RESUME_DEVICE_MISSING; + return false; + + case -ENOMEDIUM: + *ret_support = SLEEP_RESUME_MISCONFIGURED; + return false; + + case -ENOSPC: *ret_support = SLEEP_NOT_ENOUGH_SWAP_SPACE; return false; + + default: + if (r < 0) + return r; } - if (r < 0) - return r; } else assert(!sleep_config->modes[operation]); diff --git a/src/shared/sleep-config.h b/src/shared/sleep-config.h index bc5aeb9..b59bce8 100644 --- a/src/shared/sleep-config.h +++ b/src/shared/sleep-config.h @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include "strv.h" #include "time-util.h" typedef enum SleepOperation { @@ -20,7 +21,7 @@ typedef enum SleepOperation { const char* sleep_operation_to_string(SleepOperation s) _const_; SleepOperation sleep_operation_from_string(const char *s) _pure_; -static inline bool sleep_operation_is_hibernation(SleepOperation operation) { +static inline bool SLEEP_OPERATION_IS_HIBERNATION(SleepOperation operation) { return IN_SET(operation, SLEEP_HIBERNATE, SLEEP_HYBRID_SLEEP); } @@ -28,7 +29,8 @@ typedef struct SleepConfig { bool allow[_SLEEP_OPERATION_MAX]; char **states[_SLEEP_OPERATION_CONFIG_MAX]; - char **modes[_SLEEP_OPERATION_CONFIG_MAX]; /* Power mode after writing hibernation image */ + char **modes[_SLEEP_OPERATION_CONFIG_MAX]; /* Power mode after writing hibernation image (/sys/power/disk) */ + char **mem_modes; /* /sys/power/mem_sleep */ usec_t hibernate_delay_usec; usec_t suspend_estimation_usec; @@ -39,12 +41,26 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(SleepConfig*, sleep_config_free); int parse_sleep_config(SleepConfig **sleep_config); +static inline bool SLEEP_NEEDS_MEM_SLEEP(const SleepConfig *sc, SleepOperation operation) { + assert(sc); + assert(operation >= 0 && operation < _SLEEP_OPERATION_CONFIG_MAX); + + /* As per https://docs.kernel.org/admin-guide/pm/sleep-states.html#basic-sysfs-interfaces-for-system-suspend-and-hibernation, + * /sys/power/mem_sleep is honored if /sys/power/state is set to "mem" (common for suspend) + * or /sys/power/disk is set to "suspend" (hybrid-sleep). */ + + return strv_contains(sc->states[operation], "mem") || + strv_contains(sc->modes[operation], "suspend"); +} + typedef enum SleepSupport { SLEEP_SUPPORTED, SLEEP_DISABLED, /* Disabled in SleepConfig.allow */ SLEEP_NOT_CONFIGURED, /* SleepConfig.states is not configured */ SLEEP_STATE_OR_MODE_NOT_SUPPORTED, /* SleepConfig.states/modes are not supported by kernel */ SLEEP_RESUME_NOT_SUPPORTED, + SLEEP_RESUME_DEVICE_MISSING, /* resume= is specified, but the device cannot be found in /proc/swaps */ + SLEEP_RESUME_MISCONFIGURED, /* resume= is not set yet resume_offset= is configured */ SLEEP_NOT_ENOUGH_SWAP_SPACE, SLEEP_ALARM_NOT_SUPPORTED, /* CLOCK_BOOTTIME_ALARM is unsupported by kernel (only used by s2h) */ } SleepSupport; @@ -55,5 +71,5 @@ static inline int sleep_supported(SleepOperation operation) { } /* Only for test-sleep-config */ -int sleep_state_supported(char **states); -int sleep_mode_supported(char **modes); +int sleep_state_supported(char * const *states); +int sleep_mode_supported(const char *path, char * const *modes); diff --git a/src/shared/socket-netlink.c b/src/shared/socket-netlink.c index 0ba5762..a126d5d 100644 --- a/src/shared/socket-netlink.c +++ b/src/shared/socket-netlink.c @@ -1,15 +1,19 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* Make sure the net/if.h header is included before any linux/ one */ +#include <net/if.h> #include <arpa/inet.h> #include <errno.h> -#include <net/if.h> +#include <linux/net_namespace.h> #include <string.h> #include "alloc-util.h" #include "errno-util.h" #include "extract-word.h" +#include "fd-util.h" #include "log.h" #include "memory-util.h" +#include "namespace-util.h" #include "netlink-util.h" #include "parse-util.h" #include "socket-netlink.h" @@ -407,3 +411,69 @@ const char *in_addr_full_to_string(struct in_addr_full *a) { return a->cached_server_string; } + +int netns_get_nsid(int netnsfd, uint32_t *ret) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL; + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + _cleanup_close_ int _netns_fd = -EBADF; + int r; + + if (netnsfd < 0) { + r = namespace_open( + 0, + /* ret_pidns_fd = */ NULL, + /* ret_mntns_fd = */ NULL, + &_netns_fd, + /* ret_userns_fd = */ NULL, + /* ret_root_fd = */ NULL); + if (r < 0) + return r; + + netnsfd = _netns_fd; + } + + r = sd_netlink_open(&rtnl); + if (r < 0) + return r; + + r = sd_rtnl_message_new_nsid(rtnl, &req, RTM_GETNSID); + if (r < 0) + return r; + + r = sd_netlink_message_append_s32(req, NETNSA_FD, netnsfd); + if (r < 0) + return r; + + r = sd_netlink_call(rtnl, req, 0, &reply); + if (r < 0) + return r; + + for (sd_netlink_message *m = reply; m; m = sd_netlink_message_next(m)) { + uint16_t type; + + r = sd_netlink_message_get_errno(m); + if (r < 0) + return r; + + r = sd_netlink_message_get_type(m, &type); + if (r < 0) + return r; + if (type != RTM_NEWNSID) + continue; + + uint32_t u; + r = sd_netlink_message_read_u32(m, NETNSA_NSID, &u); + if (r < 0) + return r; + + if (u == UINT32_MAX) /* no NSID assigned yet */ + return -ENODATA; + + if (ret) + *ret = u; + + return 0; + } + + return -ENXIO; +} diff --git a/src/shared/socket-netlink.h b/src/shared/socket-netlink.h index 6256a83..2c06fbe 100644 --- a/src/shared/socket-netlink.h +++ b/src/shared/socket-netlink.h @@ -42,3 +42,5 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(struct in_addr_full*, in_addr_full_free); int in_addr_full_new(int family, const union in_addr_union *a, uint16_t port, int ifindex, const char *server_name, struct in_addr_full **ret); int in_addr_full_new_from_string(const char *s, struct in_addr_full **ret); const char *in_addr_full_to_string(struct in_addr_full *a); + +int netns_get_nsid(int netnsfd, uint32_t *ret); diff --git a/src/shared/specifier.c b/src/shared/specifier.c index e5a1f94..f6739f2 100644 --- a/src/shared/specifier.c +++ b/src/shared/specifier.c @@ -78,8 +78,7 @@ int specifier_printf(const char *text, size_t max_length, const Specifier table[ if (!GREEDY_REALLOC(result, j + k + l + 1)) return -ENOMEM; - memcpy(result + j, w, k); - t = result + j + k; + t = mempcpy(result + j, w, k); } else if (strchr(POSSIBLE_SPECIFIERS, *f)) /* Oops, an unknown specifier. */ return -EBADSLT; @@ -112,18 +111,7 @@ int specifier_printf(const char *text, size_t max_length, const Specifier table[ /* Generic handler for simple string replacements */ int specifier_string(char specifier, const void *data, const char *root, const void *userdata, char **ret) { - char *n = NULL; - - assert(ret); - - if (!isempty(data)) { - n = strdup(data); - if (!n) - return -ENOMEM; - } - - *ret = n; - return 0; + return strdup_to(ASSERT_PTR(ret), empty_to_null(data)); } int specifier_real_path(char specifier, const void *data, const char *root, const void *userdata, char **ret) { @@ -250,32 +238,18 @@ int specifier_pretty_hostname(char specifier, const void *data, const char *root int specifier_kernel_release(char specifier, const void *data, const char *root, const void *userdata, char **ret) { struct utsname uts; - char *n; assert(ret); if (uname(&uts) < 0) return -errno; - n = strdup(uts.release); - if (!n) - return -ENOMEM; - - *ret = n; - return 0; + return strdup_to(ret, uts.release); } int specifier_architecture(char specifier, const void *data, const char *root, const void *userdata, char **ret) { - char *t; - - assert(ret); - - t = strdup(architecture_to_string(uname_architecture())); - if (!t) - return -ENOMEM; - - *ret = t; - return 0; + return strdup_to(ASSERT_PTR(ret), + architecture_to_string(uname_architecture())); } /* Note: fields in /etc/os-release might quite possibly be missing, even if everything is entirely valid @@ -421,7 +395,6 @@ int specifier_user_shell(char specifier, const void *data, const char *root, con int specifier_tmp_dir(char specifier, const void *data, const char *root, const void *userdata, char **ret) { const char *p; - char *copy; int r; assert(ret); @@ -433,17 +406,12 @@ int specifier_tmp_dir(char specifier, const void *data, const char *root, const if (r < 0) return r; } - copy = strdup(p); - if (!copy) - return -ENOMEM; - *ret = copy; - return 0; + return strdup_to(ret, p); } int specifier_var_tmp_dir(char specifier, const void *data, const char *root, const void *userdata, char **ret) { const char *p; - char *copy; int r; assert(ret); @@ -455,12 +423,8 @@ int specifier_var_tmp_dir(char specifier, const void *data, const char *root, co if (r < 0) return r; } - copy = strdup(p); - if (!copy) - return -ENOMEM; - *ret = copy; - return 0; + return strdup_to(ret, p); } int specifier_escape_strv(char **l, char ***ret) { diff --git a/src/shared/switch-root.c b/src/shared/switch-root.c index b620156..e64b6f6 100644 --- a/src/shared/switch-root.c +++ b/src/shared/switch-root.c @@ -30,8 +30,8 @@ int switch_root(const char *new_root, const char *old_root_after, /* path below the new root, where to place the old root after the transition; may be NULL to unmount it */ SwitchRootFlags flags) { - /* Stuff mounted below /run/ we don't save on soft reboot, as it might have lost its relevance, i.e. - * credentials, removable media and such, we rather want that the new boot mounts this fresh. But on + /* Stuff mounted below /run/ we don't save on soft reboot, as it might have lost its relevance, + * e.g. removable media and such. We rather want that the new boot mounts this fresh. But on * the switch from initrd we do use MS_REC, as it is expected that mounts set up in /run/ are * maintained. */ static const struct { @@ -39,13 +39,12 @@ int switch_root(const char *new_root, unsigned long mount_flags; /* Flags to apply if SWITCH_ROOT_RECURSIVE_RUN is unset */ unsigned long mount_flags_recursive_run; /* Flags to apply if SWITCH_ROOT_RECURSIVE_RUN is set (0 if shall be skipped) */ } transfer_table[] = { - { "/dev", MS_BIND|MS_REC, MS_BIND|MS_REC }, /* Recursive, because we want to save the original /dev/shm/ + /dev/pts/ and similar */ - { "/sys", MS_BIND|MS_REC, MS_BIND|MS_REC }, /* Similar, we want to retain various API VFS, or the cgroupv1 /sys/fs/cgroup/ tree */ - { "/proc", MS_BIND|MS_REC, MS_BIND|MS_REC }, /* Similar */ - { "/run", MS_BIND, MS_BIND|MS_REC }, /* Recursive except on soft reboot, see above */ - { SYSTEM_CREDENTIALS_DIRECTORY, MS_BIND, 0 /* skip! */ }, /* Credentials passed into the system should survive */ - { ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY, MS_BIND, 0 /* skip! */ }, /* Similar */ - { "/run/host", MS_BIND|MS_REC, 0 /* skip! */ }, /* Host supplied hierarchy should also survive */ + { "/dev", MS_BIND|MS_REC, MS_BIND|MS_REC }, /* Recursive, because we want to save the original /dev/shm/ + /dev/pts/ and similar */ + { "/sys", MS_BIND|MS_REC, MS_BIND|MS_REC }, /* Similar, we want to retain various API VFS, or the cgroupv1 /sys/fs/cgroup/ tree */ + { "/proc", MS_BIND|MS_REC, MS_BIND|MS_REC }, /* Similar */ + { "/run", MS_BIND, MS_BIND|MS_REC }, /* Recursive except on soft reboot, see above */ + { "/run/credentials", MS_BIND|MS_REC, 0 /* skip! */ }, /* Credential mounts should survive */ + { "/run/host", MS_BIND|MS_REC, 0 /* skip! */ }, /* Host supplied hierarchy should also survive */ }; _cleanup_close_ int old_root_fd = -EBADF, new_root_fd = -EBADF; @@ -63,11 +62,11 @@ int switch_root(const char *new_root, if (new_root_fd < 0) return log_error_errno(errno, "Failed to open target directory '%s': %m", new_root); - r = inode_same_at(old_root_fd, "", new_root_fd, "", AT_EMPTY_PATH); + r = fds_are_same_mount(old_root_fd, new_root_fd); if (r < 0) - return log_error_errno(r, "Failed to determine if old and new root directory are the same: %m"); + return log_error_errno(r, "Failed to check if old and new root directory/mount are the same: %m"); if (r > 0) { - log_debug("Skipping switch root, as old and new root directory are the same."); + log_debug("Skipping switch root, as old and new root directories/mounts are the same."); return 0; } @@ -126,7 +125,7 @@ int switch_root(const char *new_root, * and switch_root() nevertheless. */ (void) base_filesystem_create_fd(new_root_fd, new_root, UID_INVALID, GID_INVALID); - FOREACH_ARRAY(transfer, transfer_table, ELEMENTSOF(transfer_table)) { + FOREACH_ELEMENT(transfer, transfer_table) { _cleanup_free_ char *chased = NULL; unsigned long mount_flags; @@ -144,7 +143,7 @@ int switch_root(const char *new_root, return log_error_errno(r, "Failed to resolve %s/%s: %m", new_root, transfer->path); /* Let's see if it is a mount point already. */ - r = path_is_mount_point(chased, NULL, 0); + r = path_is_mount_point(chased); if (r < 0) return log_error_errno(r, "Failed to determine whether %s is a mount point: %m", chased); if (r > 0) /* If it is already mounted, then do nothing */ diff --git a/src/shared/tests.c b/src/shared/tests.c index 3882a18..9169513 100644 --- a/src/shared/tests.c +++ b/src/shared/tests.c @@ -21,6 +21,7 @@ #include "fd-util.h" #include "fs-util.h" #include "log.h" +#include "mountpoint-util.h" #include "namespace-util.h" #include "path-util.h" #include "process-util.h" @@ -113,9 +114,9 @@ bool slow_tests_enabled(void) { } void test_setup_logging(int level) { + log_set_assert_return_is_critical(true); log_set_max_level(level); - log_parse_environment(); - log_open(); + log_setup(); } int write_tmpfile(char *pattern, const char *contents) { @@ -188,12 +189,16 @@ static int allocate_scope(void) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - _cleanup_free_ char *scope = NULL; + _cleanup_free_ char *scope = NULL, *cgroup_root = NULL; const char *object; int r; /* Let's try to run this test in a scope of its own, with delegation turned on, so that PID 1 doesn't * interfere with our cgroup management. */ + if (cg_pid_get_path(NULL, 0, &cgroup_root) >= 0 && cg_is_delegated(cgroup_root) && stderr_is_journal()) { + log_debug("Already running as a unit with delegated cgroup, not allocating a cgroup subroot."); + return 0; + } r = sd_bus_default_system(&bus); if (r < 0) @@ -249,7 +254,7 @@ static int allocate_scope(void) { if (r < 0) return bus_log_parse_error(r); - r = bus_wait_for_jobs_one(w, object, false, NULL); + r = bus_wait_for_jobs_one(w, object, BUS_WAIT_JOBS_LOG_ERROR, NULL); if (r < 0) return r; @@ -266,7 +271,7 @@ static int enter_cgroup(char **ret_cgroup, bool enter_subroot) { log_warning_errno(r, "Couldn't allocate a scope unit for this test, proceeding without."); r = cg_pid_get_path(NULL, 0, &cgroup_root); - if (r == -ENOMEDIUM) + if (IN_SET(r, -ENOMEDIUM, -ENOENT)) return log_warning_errno(r, "cg_pid_get_path(NULL, 0, ...) failed: %m"); assert(r >= 0); diff --git a/src/shared/tests.h b/src/shared/tests.h index d76cf2e..09fdfd6 100644 --- a/src/shared/tests.h +++ b/src/shared/tests.h @@ -2,14 +2,41 @@ #pragma once #include <stdbool.h> +#include <sys/prctl.h> +#include <unistd.h> #include "sd-daemon.h" #include "argv-util.h" +#include "errno-util.h" #include "macro.h" +#include "process-util.h" +#include "rlimit-util.h" +#include "signal-util.h" #include "static-destruct.h" #include "strv.h" +static inline void log_set_assert_return_is_criticalp(bool *p) { + log_set_assert_return_is_critical(*p); +} + +#define _SAVE_ASSERT_RETURN_IS_CRITICAL(b) \ + _unused_ _cleanup_(log_set_assert_return_is_criticalp) bool b = \ + log_get_assert_return_is_critical() + +#define SAVE_ASSERT_RETURN_IS_CRITICAL \ + _SAVE_ASSERT_RETURN_IS_CRITICAL(UNIQ_T(saved, UNIQ)) + +#define ASSERT_RETURN_IS_CRITICAL(b, expr) \ + ({ \ + SAVE_ASSERT_RETURN_IS_CRITICAL; \ + log_set_assert_return_is_critical(b); \ + expr; \ + }) + +#define ASSERT_RETURN_EXPECTED(expr) ASSERT_RETURN_IS_CRITICAL(false, expr) +#define ASSERT_RETURN_EXPECTED_SE(expr) ASSERT_RETURN_EXPECTED(assert_se(expr)); + static inline bool manager_errno_skip_test(int r) { return IN_SET(abs(r), EPERM, @@ -58,7 +85,7 @@ bool can_memlock(void); #define DEFINE_HEX_PTR(name, hex) \ _cleanup_free_ void *name = NULL; \ size_t name##_len = 0; \ - assert_se(unhexmem(hex, strlen_ptr(hex), &name, &name##_len) >= 0); + assert_se(unhexmem_full(hex, strlen_ptr(hex), false, &name, &name##_len) >= 0); #define TEST_REQ_RUNNING_SYSTEMD(x) \ if (sd_booted() > 0) { \ @@ -179,3 +206,216 @@ static inline int run_test_table(void) { DEFINE_TEST_MAIN_FULL(log_level, intro, NULL) #define DEFINE_TEST_MAIN(log_level) \ DEFINE_TEST_MAIN_FULL(log_level, NULL, NULL) + +#define ASSERT_OK(expr) \ + ({ \ + typeof(expr) _result = (expr); \ + if (_result < 0) { \ + log_error_errno(_result, "%s:%i: Assertion failed: expected \"%s\" to succeed but got the following error: %m", \ + PROJECT_FILE, __LINE__, #expr); \ + abort(); \ + } \ + }) + +#define ASSERT_OK_ERRNO(expr) \ + ({ \ + typeof(expr) _result = (expr); \ + if (_result < 0) { \ + log_error_errno(errno, "%s:%i: Assertion failed: expected \"%s\" to succeed but got the following error: %m", \ + PROJECT_FILE, __LINE__, #expr); \ + abort(); \ + } \ + }) + +#define ASSERT_ERROR(expr1, expr2) \ + ({ \ + int _expr1 = (expr1); \ + int _expr2 = (expr2); \ + if (_expr1 >= 0) { \ + log_error("%s:%i: Assertion failed: expected \"%s\" to fail with error \"%s\", but it succeeded", \ + PROJECT_FILE, __LINE__, #expr1, STRERROR(_expr2)); \ + abort(); \ + } else if (-_expr1 != _expr2) { \ + log_error_errno(_expr1, "%s:%i: Assertion failed: expected \"%s\" to fail with error \"%s\", but got the following error: %m", \ + PROJECT_FILE, __LINE__, #expr1, STRERROR(_expr2)); \ + abort(); \ + } \ + }) + +#define ASSERT_ERROR_ERRNO(expr1, expr2) \ + ({ \ + int _expr1 = (expr1); \ + int _expr2 = (expr2); \ + if (_expr1 >= 0) { \ + log_error("%s:%i: Assertion failed: expected \"%s\" to fail with error \"%s\", but it succeeded", \ + PROJECT_FILE, __LINE__, #expr1, STRERROR(_expr2)); \ + abort(); \ + } else if (errno != _expr2) { \ + log_error_errno(errno, "%s:%i: Assertion failed: expected \"%s\" to fail with error \"%s\", but got the following error: %m", \ + PROJECT_FILE, __LINE__, #expr1, STRERROR(errno)); \ + abort(); \ + } \ + }) + +#define ASSERT_TRUE(expr) \ + ({ \ + if (!(expr)) { \ + log_error("%s:%i: Assertion failed: expected \"%s\" to be true", \ + PROJECT_FILE, __LINE__, #expr); \ + abort(); \ + } \ + }) + +#define ASSERT_FALSE(expr) \ + ({ \ + if ((expr)) { \ + log_error("%s:%i: Assertion failed: expected \"%s\" to be false", \ + PROJECT_FILE, __LINE__, #expr); \ + abort(); \ + } \ + }) + +#define ASSERT_NULL(expr) \ + ({ \ + typeof(expr) _result = (expr); \ + if (_result != NULL) { \ + log_error("%s:%i: Assertion failed: expected \"%s\" to be NULL, but \"%p\" != NULL", \ + PROJECT_FILE, __LINE__, #expr, _result); \ + abort(); \ + } \ + }) + +#define ASSERT_NOT_NULL(expr) \ + ({ \ + if ((expr) == NULL) { \ + log_error("%s:%i: Assertion failed: expected \"%s\" to be not NULL", \ + PROJECT_FILE, __LINE__, #expr); \ + abort(); \ + } \ + }) + +#define ASSERT_STREQ(expr1, expr2) \ + ({ \ + const char *_expr1 = (expr1), *_expr2 = (expr2); \ + if (!streq_ptr(_expr1, _expr2)) { \ + log_error("%s:%i: Assertion failed: expected \"%s == %s\", but \"%s != %s\"", \ + PROJECT_FILE, __LINE__, #expr1, #expr2, strnull(_expr1), strnull(_expr2)); \ + abort(); \ + } \ + }) + +/* DECIMAL_STR_FMT() uses _Generic which cannot be used in string concatenation so we have to format the + * input into strings first and then format those into the final assertion message. */ + +#define ASSERT_EQ(expr1, expr2) \ + ({ \ + typeof(expr1) _expr1 = (expr1); \ + typeof(expr2) _expr2 = (expr2); \ + if (_expr1 != _expr2) { \ + char _sexpr1[DECIMAL_STR_MAX(typeof(expr1))]; \ + char _sexpr2[DECIMAL_STR_MAX(typeof(expr2))]; \ + xsprintf(_sexpr1, DECIMAL_STR_FMT(_expr1), _expr1); \ + xsprintf(_sexpr2, DECIMAL_STR_FMT(_expr2), _expr2); \ + log_error("%s:%i: Assertion failed: expected \"%s == %s\", but \"%s != %s\"", \ + PROJECT_FILE, __LINE__, #expr1, #expr2, _sexpr1, _sexpr2); \ + abort(); \ + } \ + }) + +#define ASSERT_GE(expr1, expr2) \ + ({ \ + typeof(expr1) _expr1 = (expr1); \ + typeof(expr2) _expr2 = (expr2); \ + if (_expr1 < _expr2) { \ + char _sexpr1[DECIMAL_STR_MAX(typeof(expr1))]; \ + char _sexpr2[DECIMAL_STR_MAX(typeof(expr2))]; \ + xsprintf(_sexpr1, DECIMAL_STR_FMT(_expr1), _expr1); \ + xsprintf(_sexpr2, DECIMAL_STR_FMT(_expr2), _expr2); \ + log_error("%s:%i: Assertion failed: expected \"%s >= %s\", but \"%s < %s\"", \ + PROJECT_FILE, __LINE__, #expr1, #expr2, _sexpr1, _sexpr2); \ + abort(); \ + } \ + }) + +#define ASSERT_LE(expr1, expr2) \ + ({ \ + typeof(expr1) _expr1 = (expr1); \ + typeof(expr2) _expr2 = (expr2); \ + if (_expr1 > _expr2) { \ + char _sexpr1[DECIMAL_STR_MAX(typeof(expr1))]; \ + char _sexpr2[DECIMAL_STR_MAX(typeof(expr2))]; \ + xsprintf(_sexpr1, DECIMAL_STR_FMT(_expr1), _expr1); \ + xsprintf(_sexpr2, DECIMAL_STR_FMT(_expr2), _expr2); \ + log_error("%s:%i: Assertion failed: expected \"%s <= %s\", but \"%s > %s\"", \ + PROJECT_FILE, __LINE__, #expr1, #expr2, _sexpr1, _sexpr2); \ + abort(); \ + } \ + }) + +#define ASSERT_NE(expr1, expr2) \ + ({ \ + typeof(expr1) _expr1 = (expr1); \ + typeof(expr2) _expr2 = (expr2); \ + if (_expr1 == _expr2) { \ + char _sexpr1[DECIMAL_STR_MAX(typeof(expr1))]; \ + char _sexpr2[DECIMAL_STR_MAX(typeof(expr2))]; \ + xsprintf(_sexpr1, DECIMAL_STR_FMT(_expr1), _expr1); \ + xsprintf(_sexpr2, DECIMAL_STR_FMT(_expr2), _expr2); \ + log_error("%s:%i: Assertion failed: expected \"%s != %s\", but \"%s == %s\"", \ + PROJECT_FILE, __LINE__, #expr1, #expr2, _sexpr1, _sexpr2); \ + abort(); \ + } \ + }) + +#define ASSERT_GT(expr1, expr2) \ + ({ \ + typeof(expr1) _expr1 = (expr1); \ + typeof(expr2) _expr2 = (expr2); \ + if (!(_expr1 > _expr2)) { \ + char _sexpr1[DECIMAL_STR_MAX(typeof(expr1))]; \ + char _sexpr2[DECIMAL_STR_MAX(typeof(expr2))]; \ + xsprintf(_sexpr1, DECIMAL_STR_FMT(_expr1), _expr1); \ + xsprintf(_sexpr2, DECIMAL_STR_FMT(_expr2), _expr2); \ + log_error("%s:%i: Assertion failed: expected \"%s > %s\", but \"%s <= %s\"", \ + PROJECT_FILE, __LINE__, #expr1, #expr2, _sexpr1, _sexpr2); \ + abort(); \ + } \ + }) + +#define ASSERT_LT(expr1, expr2) \ + ({ \ + typeof(expr1) _expr1 = (expr1); \ + typeof(expr2) _expr2 = (expr2); \ + if (!(_expr1 < _expr2)) { \ + char _sexpr1[DECIMAL_STR_MAX(typeof(expr1))]; \ + char _sexpr2[DECIMAL_STR_MAX(typeof(expr2))]; \ + xsprintf(_sexpr1, DECIMAL_STR_FMT(_expr1), _expr1); \ + xsprintf(_sexpr2, DECIMAL_STR_FMT(_expr2), _expr2); \ + log_error("%s:%i: Assertion failed: expected \"%s < %s\", but \"%s >= %s\"", \ + PROJECT_FILE, __LINE__, #expr1, #expr2, _sexpr1, _sexpr2); \ + abort(); \ + } \ + }) + +#define ASSERT_SIGNAL(expr, signal) \ + ({ \ + ASSERT_TRUE(SIGNAL_VALID(signal)); \ + siginfo_t _siginfo = {}; \ + int _pid = fork(); \ + ASSERT_OK(_pid); \ + if (_pid == 0) { \ + /* Speed things up by never even attempting to generate a coredump */ \ + (void) prctl(PR_SET_DUMPABLE, 0); \ + /* But still set an rlimit just in case */ \ + (void) setrlimit(RLIMIT_CORE, &RLIMIT_MAKE_CONST(0)); \ + expr; \ + _exit(EXIT_SUCCESS); \ + } \ + (void) wait_for_terminate(_pid, &_siginfo); \ + if (_siginfo.si_status != signal) { \ + log_error("%s:%i: Assertion failed: \"%s\" died with signal %s, but %s was expected", \ + PROJECT_FILE, __LINE__, #expr, signal_to_string(_siginfo.si_status), \ + signal_to_string(signal)); \ + abort(); \ + } \ + }) diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index c7e0b24..87ce53c 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -4,6 +4,7 @@ #include "alloc-util.h" #include "constants.h" +#include "creds-util.h" #include "cryptsetup-util.h" #include "dirent-util.h" #include "dlfcn-util.h" @@ -25,8 +26,10 @@ #include "nulstr-util.h" #include "parse-util.h" #include "random-util.h" +#include "recurse-dir.h" #include "sha256.h" #include "sort-util.h" +#include "sparse-endian.h" #include "stat-util.h" #include "string-table.h" #include "sync-util.h" @@ -34,76 +37,87 @@ #include "tpm2-util.h" #include "virt.h" +#if HAVE_OPENSSL +# include <openssl/hmac.h> +#endif + #if HAVE_TPM2 static void *libtss2_esys_dl = NULL; static void *libtss2_rc_dl = NULL; static void *libtss2_mu_dl = NULL; -static TSS2_RC (*sym_Esys_Create)(ESYS_CONTEXT *esysContext, ESYS_TR parentHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_SENSITIVE_CREATE *inSensitive, const TPM2B_PUBLIC *inPublic, const TPM2B_DATA *outsideInfo, const TPML_PCR_SELECTION *creationPCR, TPM2B_PRIVATE **outPrivate, TPM2B_PUBLIC **outPublic, TPM2B_CREATION_DATA **creationData, TPM2B_DIGEST **creationHash, TPMT_TK_CREATION **creationTicket) = NULL; -static TSS2_RC (*sym_Esys_CreateLoaded)(ESYS_CONTEXT *esysContext, ESYS_TR parentHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_SENSITIVE_CREATE *inSensitive, const TPM2B_TEMPLATE *inPublic, ESYS_TR *objectHandle, TPM2B_PRIVATE **outPrivate, TPM2B_PUBLIC **outPublic) = NULL; -static TSS2_RC (*sym_Esys_CreatePrimary)(ESYS_CONTEXT *esysContext, ESYS_TR primaryHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_SENSITIVE_CREATE *inSensitive, const TPM2B_PUBLIC *inPublic, const TPM2B_DATA *outsideInfo, const TPML_PCR_SELECTION *creationPCR, ESYS_TR *objectHandle, TPM2B_PUBLIC **outPublic, TPM2B_CREATION_DATA **creationData, TPM2B_DIGEST **creationHash, TPMT_TK_CREATION **creationTicket) = NULL; -static TSS2_RC (*sym_Esys_EvictControl)(ESYS_CONTEXT *esysContext, ESYS_TR auth, ESYS_TR objectHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPMI_DH_PERSISTENT persistentHandle, ESYS_TR *newObjectHandle) = NULL; -static void (*sym_Esys_Finalize)(ESYS_CONTEXT **context) = NULL; -static TSS2_RC (*sym_Esys_FlushContext)(ESYS_CONTEXT *esysContext, ESYS_TR flushHandle) = NULL; -static void (*sym_Esys_Free)(void *ptr) = NULL; -static TSS2_RC (*sym_Esys_GetCapability)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2_CAP capability, UINT32 property, UINT32 propertyCount, TPMI_YES_NO *moreData, TPMS_CAPABILITY_DATA **capabilityData) = NULL; -static TSS2_RC (*sym_Esys_GetRandom)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, UINT16 bytesRequested, TPM2B_DIGEST **randomBytes) = NULL; -static TSS2_RC (*sym_Esys_Import)(ESYS_CONTEXT *esysContext, ESYS_TR parentHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_DATA *encryptionKey, const TPM2B_PUBLIC *objectPublic, const TPM2B_PRIVATE *duplicate, const TPM2B_ENCRYPTED_SECRET *inSymSeed, const TPMT_SYM_DEF_OBJECT *symmetricAlg, TPM2B_PRIVATE **outPrivate) = NULL; -static TSS2_RC (*sym_Esys_Initialize)(ESYS_CONTEXT **esys_context, TSS2_TCTI_CONTEXT *tcti, TSS2_ABI_VERSION *abiVersion) = NULL; -static TSS2_RC (*sym_Esys_Load)(ESYS_CONTEXT *esysContext, ESYS_TR parentHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_PRIVATE *inPrivate, const TPM2B_PUBLIC *inPublic, ESYS_TR *objectHandle) = NULL; -static TSS2_RC (*sym_Esys_LoadExternal)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_SENSITIVE *inPrivate, const TPM2B_PUBLIC *inPublic, ESYS_TR hierarchy, ESYS_TR *objectHandle) = NULL; -static TSS2_RC (*sym_Esys_NV_DefineSpace)(ESYS_CONTEXT *esysContext, ESYS_TR authHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_AUTH *auth, const TPM2B_NV_PUBLIC *publicInfo, ESYS_TR *nvHandle); -static TSS2_RC (*sym_Esys_NV_UndefineSpace)(ESYS_CONTEXT *esysContext, ESYS_TR authHandle, ESYS_TR nvIndex, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3); -static TSS2_RC (*sym_Esys_NV_Write)(ESYS_CONTEXT *esysContext, ESYS_TR authHandle, ESYS_TR nvIndex, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_MAX_NV_BUFFER *data, UINT16 offset); -static TSS2_RC (*sym_Esys_PCR_Extend)(ESYS_CONTEXT *esysContext, ESYS_TR pcrHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPML_DIGEST_VALUES *digests) = NULL; -static TSS2_RC (*sym_Esys_PCR_Read)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1,ESYS_TR shandle2, ESYS_TR shandle3, const TPML_PCR_SELECTION *pcrSelectionIn, UINT32 *pcrUpdateCounter, TPML_PCR_SELECTION **pcrSelectionOut, TPML_DIGEST **pcrValues) = NULL; -static TSS2_RC (*sym_Esys_PolicyAuthValue)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3) = NULL; -static TSS2_RC (*sym_Esys_PolicyAuthorize)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_DIGEST *approvedPolicy, const TPM2B_NONCE *policyRef, const TPM2B_NAME *keySign, const TPMT_TK_VERIFIED *checkTicket) = NULL; -static TSS2_RC (*sym_Esys_PolicyAuthorizeNV)(ESYS_CONTEXT *esysContext, ESYS_TR authHandle, ESYS_TR nvIndex, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3); -static TSS2_RC (*sym_Esys_PolicyGetDigest)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2B_DIGEST **policyDigest) = NULL; -static TSS2_RC (*sym_Esys_PolicyOR)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPML_DIGEST *pHashList) = NULL; -static TSS2_RC (*sym_Esys_PolicyPCR)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_DIGEST *pcrDigest, const TPML_PCR_SELECTION *pcrs) = NULL; -static TSS2_RC (*sym_Esys_ReadPublic)(ESYS_CONTEXT *esysContext, ESYS_TR objectHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2B_PUBLIC **outPublic, TPM2B_NAME **name, TPM2B_NAME **qualifiedName) = NULL; -static TSS2_RC (*sym_Esys_StartAuthSession)(ESYS_CONTEXT *esysContext, ESYS_TR tpmKey, ESYS_TR bind, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_NONCE *nonceCaller, TPM2_SE sessionType, const TPMT_SYM_DEF *symmetric, TPMI_ALG_HASH authHash, ESYS_TR *sessionHandle) = NULL; -static TSS2_RC (*sym_Esys_Startup)(ESYS_CONTEXT *esysContext, TPM2_SU startupType) = NULL; -static TSS2_RC (*sym_Esys_TestParms)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPMT_PUBLIC_PARMS *parameters) = NULL; -static TSS2_RC (*sym_Esys_TR_Close)(ESYS_CONTEXT *esys_context, ESYS_TR *rsrc_handle) = NULL; -static TSS2_RC (*sym_Esys_TR_Deserialize)(ESYS_CONTEXT *esys_context, uint8_t const *buffer, size_t buffer_size, ESYS_TR *esys_handle) = NULL; -static TSS2_RC (*sym_Esys_TR_FromTPMPublic)(ESYS_CONTEXT *esysContext, TPM2_HANDLE tpm_handle, ESYS_TR optionalSession1, ESYS_TR optionalSession2, ESYS_TR optionalSession3, ESYS_TR *object) = NULL; -static TSS2_RC (*sym_Esys_TR_GetName)(ESYS_CONTEXT *esysContext, ESYS_TR handle, TPM2B_NAME **name) = NULL; -static TSS2_RC (*sym_Esys_TR_GetTpmHandle)(ESYS_CONTEXT *esys_context, ESYS_TR esys_handle, TPM2_HANDLE *tpm_handle) = NULL; -static TSS2_RC (*sym_Esys_TR_Serialize)(ESYS_CONTEXT *esys_context, ESYS_TR object, uint8_t **buffer, size_t *buffer_size) = NULL; -static TSS2_RC (*sym_Esys_TR_SetAuth)(ESYS_CONTEXT *esysContext, ESYS_TR handle, TPM2B_AUTH const *authValue) = NULL; -static TSS2_RC (*sym_Esys_TRSess_GetAttributes)(ESYS_CONTEXT *esysContext, ESYS_TR session, TPMA_SESSION *flags) = NULL; -static TSS2_RC (*sym_Esys_TRSess_SetAttributes)(ESYS_CONTEXT *esysContext, ESYS_TR session, TPMA_SESSION flags, TPMA_SESSION mask) = NULL; -static TSS2_RC (*sym_Esys_Unseal)(ESYS_CONTEXT *esysContext, ESYS_TR itemHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2B_SENSITIVE_DATA **outData) = NULL; -static TSS2_RC (*sym_Esys_VerifySignature)(ESYS_CONTEXT *esysContext, ESYS_TR keyHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_DIGEST *digest, const TPMT_SIGNATURE *signature, TPMT_TK_VERIFIED **validation) = NULL; - -static TSS2_RC (*sym_Tss2_MU_TPM2_CC_Marshal)(TPM2_CC src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; -static TSS2_RC (*sym_Tss2_MU_TPM2_HANDLE_Marshal)(TPM2_HANDLE src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; -static TSS2_RC (*sym_Tss2_MU_TPM2B_DIGEST_Marshal)(TPM2B_DIGEST const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; -static TSS2_RC (*sym_Tss2_MU_TPM2B_ENCRYPTED_SECRET_Marshal)(TPM2B_ENCRYPTED_SECRET const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; -static TSS2_RC (*sym_Tss2_MU_TPM2B_ENCRYPTED_SECRET_Unmarshal)(uint8_t const buffer[], size_t buffer_size, size_t *offset, TPM2B_ENCRYPTED_SECRET *dest) = NULL; -static TSS2_RC (*sym_Tss2_MU_TPM2B_NAME_Marshal)(TPM2B_NAME const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; -static TSS2_RC (*sym_Tss2_MU_TPM2B_PRIVATE_Marshal)(TPM2B_PRIVATE const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; -static TSS2_RC (*sym_Tss2_MU_TPM2B_PRIVATE_Unmarshal)(uint8_t const buffer[], size_t buffer_size, size_t *offset, TPM2B_PRIVATE *dest) = NULL; -static TSS2_RC (*sym_Tss2_MU_TPM2B_PUBLIC_Marshal)(TPM2B_PUBLIC const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; -static TSS2_RC (*sym_Tss2_MU_TPM2B_PUBLIC_Unmarshal)(uint8_t const buffer[], size_t buffer_size, size_t *offset, TPM2B_PUBLIC *dest) = NULL; -static TSS2_RC (*sym_Tss2_MU_TPM2B_SENSITIVE_Marshal)(TPM2B_SENSITIVE const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; -static TSS2_RC (*sym_Tss2_MU_TPML_PCR_SELECTION_Marshal)(TPML_PCR_SELECTION const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; -static TSS2_RC (*sym_Tss2_MU_TPMS_NV_PUBLIC_Marshal)(TPMS_NV_PUBLIC const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; -static TSS2_RC (*sym_Tss2_MU_TPM2B_NV_PUBLIC_Marshal)(TPM2B_NV_PUBLIC const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; -static TSS2_RC (*sym_Tss2_MU_TPM2B_NV_PUBLIC_Unmarshal)(uint8_t const buffer[], size_t buffer_size, size_t *offset, TPM2B_NV_PUBLIC *dest) = NULL; -static TSS2_RC (*sym_Tss2_MU_TPMS_ECC_POINT_Marshal)(TPMS_ECC_POINT const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; -static TSS2_RC (*sym_Tss2_MU_TPMT_HA_Marshal)(TPMT_HA const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; -static TSS2_RC (*sym_Tss2_MU_TPMT_PUBLIC_Marshal)(TPMT_PUBLIC const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; -static TSS2_RC (*sym_Tss2_MU_UINT32_Marshal)(UINT32 src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; - -static const char* (*sym_Tss2_RC_Decode)(TSS2_RC rc) = NULL; +static DLSYM_FUNCTION(Esys_Create); +static DLSYM_FUNCTION(Esys_CreateLoaded); +static DLSYM_FUNCTION(Esys_CreatePrimary); +static DLSYM_FUNCTION(Esys_EvictControl); +static DLSYM_FUNCTION(Esys_Finalize); +static DLSYM_FUNCTION(Esys_FlushContext); +static DLSYM_FUNCTION(Esys_Free); +static DLSYM_FUNCTION(Esys_GetCapability); +static DLSYM_FUNCTION(Esys_GetRandom); +static DLSYM_FUNCTION(Esys_Import); +static DLSYM_FUNCTION(Esys_Initialize); +static DLSYM_FUNCTION(Esys_Load); +static DLSYM_FUNCTION(Esys_LoadExternal); +static DLSYM_FUNCTION(Esys_NV_DefineSpace); +static DLSYM_FUNCTION(Esys_NV_UndefineSpace); +static DLSYM_FUNCTION(Esys_NV_Write); +static DLSYM_FUNCTION(Esys_PCR_Extend); +static DLSYM_FUNCTION(Esys_PCR_Read); +static DLSYM_FUNCTION(Esys_PolicyAuthValue); +static DLSYM_FUNCTION(Esys_PolicyAuthorize); +static DLSYM_FUNCTION(Esys_PolicyAuthorizeNV); +static DLSYM_FUNCTION(Esys_PolicyGetDigest); +static DLSYM_FUNCTION(Esys_PolicyOR); +static DLSYM_FUNCTION(Esys_PolicyPCR); +static DLSYM_FUNCTION(Esys_PolicySigned); +static DLSYM_FUNCTION(Esys_ReadPublic); +static DLSYM_FUNCTION(Esys_StartAuthSession); +static DLSYM_FUNCTION(Esys_Startup); +static DLSYM_FUNCTION(Esys_TestParms); +static DLSYM_FUNCTION(Esys_TR_Close); +static DLSYM_FUNCTION(Esys_TR_Deserialize); +static DLSYM_FUNCTION(Esys_TR_FromTPMPublic); +static DLSYM_FUNCTION(Esys_TR_GetName); +static DLSYM_FUNCTION(Esys_TR_GetTpmHandle); +static DLSYM_FUNCTION(Esys_TR_Serialize); +static DLSYM_FUNCTION(Esys_TR_SetAuth); +static DLSYM_FUNCTION(Esys_TRSess_GetAttributes); +static DLSYM_FUNCTION(Esys_TRSess_GetNonceTPM); +static DLSYM_FUNCTION(Esys_TRSess_SetAttributes); +static DLSYM_FUNCTION(Esys_Unseal); +static DLSYM_FUNCTION(Esys_VerifySignature); + +static DLSYM_FUNCTION(Tss2_MU_TPM2_CC_Marshal); +static DLSYM_FUNCTION(Tss2_MU_TPM2_HANDLE_Marshal); +static DLSYM_FUNCTION(Tss2_MU_TPM2B_DIGEST_Marshal); +static DLSYM_FUNCTION(Tss2_MU_TPM2B_ENCRYPTED_SECRET_Marshal); +static DLSYM_FUNCTION(Tss2_MU_TPM2B_ENCRYPTED_SECRET_Unmarshal); +static DLSYM_FUNCTION(Tss2_MU_TPM2B_NAME_Marshal); +static DLSYM_FUNCTION(Tss2_MU_TPM2B_PRIVATE_Marshal); +static DLSYM_FUNCTION(Tss2_MU_TPM2B_PRIVATE_Unmarshal); +static DLSYM_FUNCTION(Tss2_MU_TPM2B_PUBLIC_Marshal); +static DLSYM_FUNCTION(Tss2_MU_TPM2B_PUBLIC_Unmarshal); +static DLSYM_FUNCTION(Tss2_MU_TPM2B_SENSITIVE_Marshal); +static DLSYM_FUNCTION(Tss2_MU_TPML_PCR_SELECTION_Marshal); +static DLSYM_FUNCTION(Tss2_MU_TPMS_NV_PUBLIC_Marshal); +static DLSYM_FUNCTION(Tss2_MU_TPM2B_NV_PUBLIC_Marshal); +static DLSYM_FUNCTION(Tss2_MU_TPM2B_NV_PUBLIC_Unmarshal); +static DLSYM_FUNCTION(Tss2_MU_TPMS_ECC_POINT_Marshal); +static DLSYM_FUNCTION(Tss2_MU_TPMT_HA_Marshal); +static DLSYM_FUNCTION(Tss2_MU_TPMT_PUBLIC_Marshal); +static DLSYM_FUNCTION(Tss2_MU_UINT32_Marshal); + +static DLSYM_FUNCTION(Tss2_RC_Decode); int dlopen_tpm2(void) { int r; + ELF_NOTE_DLOPEN("tpm", + "Support for TPM", + ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + "libtss2-esys.so.0"); + r = dlopen_many_sym_or_warn( &libtss2_esys_dl, "libtss2-esys.so.0", LOG_DEBUG, DLSYM_ARG(Esys_Create), @@ -130,6 +144,7 @@ int dlopen_tpm2(void) { DLSYM_ARG(Esys_PolicyGetDigest), DLSYM_ARG(Esys_PolicyOR), DLSYM_ARG(Esys_PolicyPCR), + DLSYM_ARG(Esys_PolicySigned), DLSYM_ARG(Esys_ReadPublic), DLSYM_ARG(Esys_StartAuthSession), DLSYM_ARG(Esys_Startup), @@ -141,6 +156,7 @@ int dlopen_tpm2(void) { DLSYM_ARG(Esys_TR_Serialize), DLSYM_ARG(Esys_TR_SetAuth), DLSYM_ARG(Esys_TRSess_GetAttributes), + DLSYM_ARG(Esys_TRSess_GetNonceTPM), DLSYM_ARG(Esys_TRSess_SetAttributes), DLSYM_ARG(Esys_Unseal), DLSYM_ARG(Esys_VerifySignature)); @@ -153,12 +169,22 @@ int dlopen_tpm2(void) { if (r < 0) log_debug("libtss2-esys too old, does not include Esys_TR_GetTpmHandle."); + ELF_NOTE_DLOPEN("tpm", + "Support for TPM", + ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + "libtss2-rc.so.0"); + r = dlopen_many_sym_or_warn( &libtss2_rc_dl, "libtss2-rc.so.0", LOG_DEBUG, DLSYM_ARG(Tss2_RC_Decode)); if (r < 0) return r; + ELF_NOTE_DLOPEN("tpm", + "Support for TPM", + ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + "libtss2-mu.so.0"); + return dlopen_many_sym_or_warn( &libtss2_mu_dl, "libtss2-mu.so.0", LOG_DEBUG, DLSYM_ARG(Tss2_MU_TPM2_CC_Marshal), @@ -1895,7 +1921,7 @@ int tpm2_pcr_value_from_string(const char *arg, Tpm2PCRValue *ret_pcr_value) { _cleanup_free_ void *buf = NULL; size_t buf_size = 0; - r = unhexmem(p, SIZE_MAX, &buf, &buf_size); + r = unhexmem(p, &buf, &buf_size); if (r < 0) return log_debug_errno(r, "Invalid pcr hash value '%s': %m", p); @@ -2084,7 +2110,7 @@ int tpm2_create_primary( session ? session->esys_handle : ESYS_TR_PASSWORD, ESYS_TR_NONE, ESYS_TR_NONE, - sensitive ? sensitive : &(TPM2B_SENSITIVE_CREATE) {}, + sensitive ?: &(TPM2B_SENSITIVE_CREATE) {}, template, /* outsideInfo= */ NULL, &(TPML_PCR_SELECTION) {}, @@ -2254,9 +2280,9 @@ static int tpm2_load_external( #if HAVE_TSS2_ESYS3 /* tpm2-tss >= 3.0.0 requires a ESYS_TR_RH_* constant specifying the requested * hierarchy, older versions need TPM2_RH_* instead. */ - ESYS_TR_RH_OWNER, + private ? ESYS_TR_RH_NULL : ESYS_TR_RH_OWNER, #else - TPM2_RH_OWNER, + private ? TPM2_RH_NULL : TPM2_RH_OWNER, #endif &handle->esys_handle); if (rc != TSS2_RC_SUCCESS) @@ -3074,7 +3100,7 @@ static void tpm2_trim_auth_value(TPM2B_AUTH *auth) { log_debug("authValue ends in 0, trimming as required by the TPM2 specification Part 1 section 'HMAC Computation' authValue Note 2."); } -int tpm2_get_pin_auth(TPMI_ALG_HASH hash, const char *pin, TPM2B_AUTH *ret_auth) { +int tpm2_auth_value_from_pin(TPMI_ALG_HASH hash, const char *pin, TPM2B_AUTH *ret_auth) { TPM2B_AUTH auth = {}; int r; @@ -3121,7 +3147,7 @@ int tpm2_set_auth(Tpm2Context *c, const Tpm2Handle *handle, const char *pin) { CLEANUP_ERASE(auth); - r = tpm2_get_pin_auth(TPM2_ALG_SHA256, pin, &auth); + r = tpm2_auth_value_from_pin(TPM2_ALG_SHA256, pin, &auth); if (r < 0) return r; @@ -3408,7 +3434,7 @@ int tpm2_calculate_pubkey_name(const TPMT_PUBLIC *public, TPM2B_NAME *ret_name) * * The handle must reference a key already present in the TPM. It may be either a public key only, or a * public/private keypair. */ -static int tpm2_get_name( +int tpm2_get_name( Tpm2Context *c, const Tpm2Handle *handle, TPM2B_NAME **ret_name) { @@ -3546,6 +3572,150 @@ int tpm2_policy_auth_value( return tpm2_get_policy_digest(c, session, ret_policy_digest); } +/* Extend 'digest' with the PolicySigned calculated hash. */ +int tpm2_calculate_policy_signed(TPM2B_DIGEST *digest, const TPM2B_NAME *name) { + TPM2_CC command = TPM2_CC_PolicySigned; + TSS2_RC rc; + int r; + + assert(digest); + assert(digest->size == SHA256_DIGEST_SIZE); + assert(name); + + r = dlopen_tpm2(); + if (r < 0) + return log_debug_errno(r, "TPM2 support not installed: %m"); + + uint8_t buf[sizeof(command)]; + size_t offset = 0; + + rc = sym_Tss2_MU_TPM2_CC_Marshal(command, buf, sizeof(buf), &offset); + if (rc != TSS2_RC_SUCCESS) + return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to marshal PolicySigned command: %s", sym_Tss2_RC_Decode(rc)); + + if (offset != sizeof(command)) + return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Offset 0x%zx wrong after marshalling PolicySigned command", offset); + + struct iovec data[] = { + IOVEC_MAKE(buf, offset), + IOVEC_MAKE(name->name, name->size), + }; + + r = tpm2_digest_many(TPM2_ALG_SHA256, digest, data, ELEMENTSOF(data), /* extend= */ true); + if (r < 0) + return r; + + const TPM2B_NONCE policyRef = {}; /* For now, we do not make use of the policyRef stuff */ + + r = tpm2_digest_buffer(TPM2_ALG_SHA256, digest, policyRef.buffer, policyRef.size, /* extend= */ true); + if (r < 0) + return r; + + tpm2_log_debug_digest(digest, "PolicySigned calculated digest"); + + return 0; +} + +int tpm2_policy_signed_hmac_sha256( + Tpm2Context *c, + const Tpm2Handle *session, + const Tpm2Handle *hmac_key_handle, + const struct iovec *hmac_key, + TPM2B_DIGEST **ret_policy_digest) { + +#if HAVE_OPENSSL + TSS2_RC rc; + int r; + + assert(c); + assert(session); + assert(hmac_key_handle); + assert(iovec_is_set(hmac_key)); + + /* This sends a TPM2_PolicySigned command to the tpm. As signature key we use an HMAC-SHA256 key + * specified in the hmac_key parameter. The secret key must be loaded into the TPM already and + * referenced in hmac_key_handle. */ + + log_debug("Submitting PolicySigned policy for HMAC-SHA256."); + + /* Acquire the nonce from the TPM that we shall sign */ + _cleanup_(Esys_Freep) TPM2B_NONCE *nonce = NULL; + rc = sym_Esys_TRSess_GetNonceTPM( + c->esys_context, + session->esys_handle, + &nonce); + if (rc != TSS2_RC_SUCCESS) + return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to determine NoneTPM of auth session: %s", + sym_Tss2_RC_Decode(rc)); + + be32_t expiration = htobe64(0); + const TPM2B_DIGEST cpHashA = {}; /* For now, we do not make use of the cpHashA stuff */ + const TPM2B_NONCE policyRef = {}; /* ditto, we do not bother with policyRef */ + + /* Put together the data to sign, as per TPM2 Spec Part 3, 23.3.1 */ + struct iovec data_to_sign[] = { + IOVEC_MAKE(nonce->buffer, nonce->size), + IOVEC_MAKE(&expiration, sizeof(expiration)), + IOVEC_MAKE(cpHashA.buffer, cpHashA.size), + IOVEC_MAKE(policyRef.buffer, policyRef.size), + }; + + /* Now calculate the digest of the data we put together */ + TPM2B_DIGEST digest_to_sign; + r = tpm2_digest_many(TPM2_ALG_SHA256, &digest_to_sign, data_to_sign, ELEMENTSOF(data_to_sign), /* extend= */ false); + if (r < 0) + return r; + + unsigned char hmac_signature[SHA256_DIGEST_SIZE]; + unsigned hmac_signature_size = sizeof(hmac_signature); + + /* And sign this with our key */ + if (!HMAC(EVP_sha256(), + hmac_key->iov_base, + hmac_key->iov_len, + digest_to_sign.buffer, + digest_to_sign.size, + hmac_signature, + &hmac_signature_size)) + return -ENOTRECOVERABLE; + + /* Now bring the signature into a format that the TPM understands */ + TPMT_SIGNATURE sig = { + .sigAlg = TPM2_ALG_HMAC, + .signature.hmac.hashAlg = TPM2_ALG_SHA256, + }; + assert(hmac_signature_size == sizeof(sig.signature.hmac.digest.sha256)); + memcpy(sig.signature.hmac.digest.sha256, hmac_signature, hmac_signature_size); + + /* And submit the whole shebang to the TPM */ + rc = sym_Esys_PolicySigned( + c->esys_context, + hmac_key_handle->esys_handle, + session->esys_handle, + /* shandle1= */ ESYS_TR_NONE, + /* shandle2= */ ESYS_TR_NONE, + /* shandle3= */ ESYS_TR_NONE, + nonce, + &cpHashA, + &policyRef, + expiration, + &sig, + /* timeout= */ NULL, + /* policyTicket= */ NULL); + if (rc != TSS2_RC_SUCCESS) + return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to add PolicySigned policy to TPM: %s", + sym_Tss2_RC_Decode(rc)); + + return tpm2_get_policy_digest(c, session, ret_policy_digest); +#else /* HAVE_OPENSSL */ + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL support is disabled."); +#endif +} + int tpm2_calculate_policy_authorize_nv( const TPM2B_NV_PUBLIC *public_info, TPM2B_DIGEST *digest) { @@ -4151,7 +4321,7 @@ static const struct { static int tpm2_ecc_curve_from_openssl_curve_id(int openssl_ecc_curve_id, TPM2_ECC_CURVE *ret) { assert(ret); - FOREACH_ARRAY(t, tpm2_openssl_ecc_curve_table, ELEMENTSOF(tpm2_openssl_ecc_curve_table)) + FOREACH_ELEMENT(t, tpm2_openssl_ecc_curve_table) if (t->openssl_ecc_curve_id == openssl_ecc_curve_id) { *ret = t->tpm2_ecc_curve_id; return 0; @@ -4164,7 +4334,7 @@ static int tpm2_ecc_curve_from_openssl_curve_id(int openssl_ecc_curve_id, TPM2_E static int tpm2_ecc_curve_to_openssl_curve_id(TPM2_ECC_CURVE tpm2_ecc_curve_id, int *ret) { assert(ret); - FOREACH_ARRAY(t, tpm2_openssl_ecc_curve_table, ELEMENTSOF(tpm2_openssl_ecc_curve_table)) + FOREACH_ELEMENT(t, tpm2_openssl_ecc_curve_table) if (t->tpm2_ecc_curve_id == tpm2_ecc_curve_id) { *ret = t->openssl_ecc_curve_id; return 0; @@ -4801,7 +4971,7 @@ static int tpm2_calculate_seal_private( TPM2B_AUTH auth = {}; if (pin) { - r = tpm2_get_pin_auth(parent->publicArea.nameAlg, pin, &auth); + r = tpm2_auth_value_from_pin(parent->publicArea.nameAlg, pin, &auth); if (r < 0) return r; } @@ -5035,7 +5205,7 @@ static int tpm2_calculate_seal_ecc_seed( size_t bits = (size_t) r * 8; _cleanup_free_ void *seed = NULL; - size_t seed_size; + size_t seed_size = 0; /* Explicit initialization to appease gcc */ r = tpm2_kdfe(parent->publicArea.nameAlg, shared_secret, shared_secret_size, @@ -5072,7 +5242,7 @@ static int tpm2_calculate_seal_seed( log_debug("Calculating encrypted seed for sealed object."); _cleanup_free_ void *seed = NULL, *encrypted_seed = NULL; - size_t seed_size, encrypted_seed_size; + size_t seed_size = 0, encrypted_seed_size = 0; /* Explicit initialization to appease gcc */ if (parent->publicArea.type == TPM2_ALG_RSA) r = tpm2_calculate_seal_rsa_seed(parent, &seed, &seed_size, &encrypted_seed, &encrypted_seed_size); else if (parent->publicArea.type == TPM2_ALG_ECC) @@ -5095,28 +5265,22 @@ int tpm2_calculate_seal( TPM2_HANDLE parent_handle, const TPM2B_PUBLIC *parent_public, const TPMA_OBJECT *attributes, - const void *secret, - size_t secret_size, + const struct iovec *secret, const TPM2B_DIGEST *policy, const char *pin, - void **ret_secret, - size_t *ret_secret_size, - void **ret_blob, - size_t *ret_blob_size, - void **ret_serialized_parent, - size_t *ret_serialized_parent_size) { + struct iovec *ret_secret, + struct iovec *ret_blob, + struct iovec *ret_serialized_parent) { #if HAVE_OPENSSL int r; assert(parent_public); - assert(secret || secret_size == 0); + assert(iovec_is_valid(secret)); assert(secret || ret_secret); assert(!(secret && ret_secret)); /* Either provide a secret, or we create one, but not both */ assert(ret_blob); - assert(ret_blob_size); assert(ret_serialized_parent); - assert(ret_serialized_parent_size); log_debug("Calculating sealed object."); @@ -5137,27 +5301,27 @@ int tpm2_calculate_seal( parent_handle); } - _cleanup_(erase_and_freep) void *generated_secret = NULL; + _cleanup_(iovec_done_erase) struct iovec generated_secret = {}; if (!secret) { /* No secret provided, generate a random secret. We use SHA256 digest length, though it can * be up to TPM2_MAX_SEALED_DATA. The secret length is not limited to the nameAlg hash * size. */ - secret_size = TPM2_SHA256_DIGEST_SIZE; - generated_secret = malloc(secret_size); - if (!generated_secret) + generated_secret.iov_len = TPM2_SHA256_DIGEST_SIZE; + generated_secret.iov_base = malloc(generated_secret.iov_len); + if (!generated_secret.iov_base) return log_oom_debug(); - r = crypto_random_bytes(generated_secret, secret_size); + r = crypto_random_bytes(generated_secret.iov_base, generated_secret.iov_len); if (r < 0) return log_debug_errno(r, "Failed to generate secret key: %m"); - secret = generated_secret; + secret = &generated_secret; } - if (secret_size > TPM2_MAX_SEALED_DATA) + if (secret->iov_len > TPM2_MAX_SEALED_DATA) return log_debug_errno(SYNTHETIC_ERRNO(EOVERFLOW), "Secret size %zu too large, limit is %d bytes.", - secret_size, TPM2_MAX_SEALED_DATA); + secret->iov_len, TPM2_MAX_SEALED_DATA); TPM2B_DIGEST random_seed; TPM2B_ENCRYPTED_SECRET seed; @@ -5166,7 +5330,7 @@ int tpm2_calculate_seal( return r; TPM2B_PUBLIC public; - r = tpm2_calculate_seal_public(parent_public, attributes, policy, &random_seed, secret, secret_size, &public); + r = tpm2_calculate_seal_public(parent_public, attributes, policy, &random_seed, secret->iov_base, secret->iov_len, &public); if (r < 0) return r; @@ -5176,13 +5340,12 @@ int tpm2_calculate_seal( return r; TPM2B_PRIVATE private; - r = tpm2_calculate_seal_private(parent_public, &name, pin, &random_seed, secret, secret_size, &private); + r = tpm2_calculate_seal_private(parent_public, &name, pin, &random_seed, secret->iov_base, secret->iov_len, &private); if (r < 0) return r; - _cleanup_free_ void *blob = NULL; - size_t blob_size; - r = tpm2_marshal_blob(&public, &private, &seed, &blob, &blob_size); + _cleanup_(iovec_done) struct iovec blob = {}; + r = tpm2_marshal_blob(&public, &private, &seed, &blob.iov_base, &blob.iov_len); if (r < 0) return log_debug_errno(r, "Could not create sealed blob: %m"); @@ -5191,25 +5354,20 @@ int tpm2_calculate_seal( if (r < 0) return r; - _cleanup_free_ void *serialized_parent = NULL; - size_t serialized_parent_size; + _cleanup_(iovec_done) struct iovec serialized_parent = {}; r = tpm2_calculate_serialize( parent_handle, &parent_name, parent_public, - &serialized_parent, - &serialized_parent_size); + &serialized_parent.iov_base, + &serialized_parent.iov_len); if (r < 0) return r; if (ret_secret) - *ret_secret = TAKE_PTR(generated_secret); - if (ret_secret_size) - *ret_secret_size = secret_size; - *ret_blob = TAKE_PTR(blob); - *ret_blob_size = blob_size; - *ret_serialized_parent = TAKE_PTR(serialized_parent); - *ret_serialized_parent_size = serialized_parent_size; + *ret_secret = TAKE_STRUCT(generated_secret); + *ret_blob = TAKE_STRUCT(blob); + *ret_serialized_parent = TAKE_STRUCT(serialized_parent); return 0; #else /* HAVE_OPENSSL */ @@ -5221,21 +5379,16 @@ int tpm2_seal(Tpm2Context *c, uint32_t seal_key_handle, const TPM2B_DIGEST *policy, const char *pin, - void **ret_secret, - size_t *ret_secret_size, - void **ret_blob, - size_t *ret_blob_size, + struct iovec *ret_secret, + struct iovec *ret_blob, uint16_t *ret_primary_alg, - void **ret_srk_buf, - size_t *ret_srk_buf_size) { + struct iovec *ret_srk) { uint16_t primary_alg = 0; int r; assert(ret_secret); - assert(ret_secret_size); assert(ret_blob); - assert(ret_blob_size); /* So here's what we do here: we connect to the TPM2 chip. It persistently contains a "seed" key that * is randomized when the TPM2 is first initialized or reset and remains stable across boots. We @@ -5255,13 +5408,22 @@ int tpm2_seal(Tpm2Context *c, usec_t start = now(CLOCK_MONOTONIC); + TPMA_OBJECT hmac_attributes = + TPMA_OBJECT_FIXEDTPM | + TPMA_OBJECT_FIXEDPARENT; + + /* If protected by PIN, a user-selected low-entropy password, enable DA protection. + Without a PIN, the key's left protected only by a PCR policy, which does not benefit + from DA protection. */ + hmac_attributes |= pin ? 0 : TPMA_OBJECT_NODA; + /* We use a keyed hash object (i.e. HMAC) to store the secret key we want to use for unlocking the * LUKS2 volume with. We don't ever use for HMAC/keyed hash operations however, we just use it * because it's a key type that is universally supported and suitable for symmetric binary blobs. */ TPMT_PUBLIC hmac_template = { .type = TPM2_ALG_KEYEDHASH, .nameAlg = TPM2_ALG_SHA256, - .objectAttributes = TPMA_OBJECT_FIXEDTPM | TPMA_OBJECT_FIXEDPARENT, + .objectAttributes = hmac_attributes, .parameters.keyedHashDetail.scheme.scheme = TPM2_ALG_NULL, .unique.keyedHash.size = SHA256_DIGEST_SIZE, .authPolicy = policy ? *policy : TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE), @@ -5274,7 +5436,7 @@ int tpm2_seal(Tpm2Context *c, CLEANUP_ERASE(hmac_sensitive); if (pin) { - r = tpm2_get_pin_auth(TPM2_ALG_SHA256, pin, &hmac_sensitive.userAuth); + r = tpm2_auth_value_from_pin(TPM2_ALG_SHA256, pin, &hmac_sensitive.userAuth); if (r < 0) return r; } @@ -5290,7 +5452,7 @@ int tpm2_seal(Tpm2Context *c, return log_debug_errno(r, "Failed to generate secret key: %m"); _cleanup_(tpm2_handle_freep) Tpm2Handle *primary_handle = NULL; - if (ret_srk_buf) { + if (ret_srk) { _cleanup_(Esys_Freep) TPM2B_PUBLIC *primary_public = NULL; if (IN_SET(seal_key_handle, 0, TPM2_SRK_HANDLE)) { @@ -5328,7 +5490,7 @@ int tpm2_seal(Tpm2Context *c, if (seal_key_handle != 0) log_debug("Using primary alg sealing, but seal key handle also provided; ignoring seal key handle."); - /* TODO: force all callers to provide ret_srk_buf, so we can stop sealing with the legacy templates. */ + /* TODO: force all callers to provide ret_srk, so we can stop sealing with the legacy templates. */ primary_alg = TPM2_ALG_ECC; TPM2B_PUBLIC template = { @@ -5372,47 +5534,46 @@ int tpm2_seal(Tpm2Context *c, if (r < 0) return r; - _cleanup_(erase_and_freep) void *secret = NULL; - secret = memdup(hmac_sensitive.data.buffer, hmac_sensitive.data.size); - if (!secret) + _cleanup_(iovec_done_erase) struct iovec secret = {}; + secret.iov_base = memdup(hmac_sensitive.data.buffer, hmac_sensitive.data.size); + if (!secret.iov_base) return log_oom_debug(); + secret.iov_len = hmac_sensitive.data.size; log_debug("Marshalling private and public part of HMAC key."); - _cleanup_free_ void *blob = NULL; - size_t blob_size = 0; - r = tpm2_marshal_blob(public, private, /* seed= */ NULL, &blob, &blob_size); + _cleanup_(iovec_done) struct iovec blob = {}; + r = tpm2_marshal_blob(public, private, /* seed= */ NULL, &blob.iov_base, &blob.iov_len); if (r < 0) return log_debug_errno(r, "Could not create sealed blob: %m"); if (DEBUG_LOGGING) log_debug("Completed TPM2 key sealing in %s.", FORMAT_TIMESPAN(now(CLOCK_MONOTONIC) - start, 1)); - _cleanup_free_ void *srk_buf = NULL; - size_t srk_buf_size = 0; - if (ret_srk_buf) { + if (ret_srk) { + _cleanup_(iovec_done) struct iovec srk = {}; _cleanup_(Esys_Freep) void *tmp = NULL; - r = tpm2_serialize(c, primary_handle, &tmp, &srk_buf_size); + size_t tmp_size; + + r = tpm2_serialize(c, primary_handle, &tmp, &tmp_size); if (r < 0) return r; /* * make a copy since we don't want the caller to understand that * ESYS allocated the pointer. It would make tracking what deallocator - * to use for srk_buf in which context a PITA. + * to use for srk in which context a PITA. */ - srk_buf = memdup(tmp, srk_buf_size); - if (!srk_buf) + srk.iov_base = memdup(tmp, tmp_size); + if (!srk.iov_base) return log_oom_debug(); + srk.iov_len = tmp_size; - *ret_srk_buf = TAKE_PTR(srk_buf); - *ret_srk_buf_size = srk_buf_size; + *ret_srk = TAKE_STRUCT(srk); } - *ret_secret = TAKE_PTR(secret); - *ret_secret_size = hmac_sensitive.data.size; - *ret_blob = TAKE_PTR(blob); - *ret_blob_size = blob_size; + *ret_secret = TAKE_STRUCT(secret); + *ret_blob = TAKE_STRUCT(blob); if (ret_primary_alg) *ret_primary_alg = primary_alg; @@ -5425,31 +5586,24 @@ int tpm2_seal(Tpm2Context *c, int tpm2_unseal(Tpm2Context *c, uint32_t hash_pcr_mask, uint16_t pcr_bank, - const void *pubkey, - size_t pubkey_size, + const struct iovec *pubkey, uint32_t pubkey_pcr_mask, JsonVariant *signature, const char *pin, const Tpm2PCRLockPolicy *pcrlock_policy, uint16_t primary_alg, - const void *blob, - size_t blob_size, - const void *known_policy_hash, - size_t known_policy_hash_size, - const void *srk_buf, - size_t srk_buf_size, - void **ret_secret, - size_t *ret_secret_size) { + const struct iovec *blob, + const struct iovec *known_policy_hash, + const struct iovec *srk, + struct iovec *ret_secret) { TSS2_RC rc; int r; - assert(blob); - assert(blob_size > 0); - assert(known_policy_hash_size == 0 || known_policy_hash); - assert(pubkey_size == 0 || pubkey); + assert(iovec_is_set(blob)); + assert(iovec_is_valid(known_policy_hash)); + assert(iovec_is_valid(pubkey)); assert(ret_secret); - assert(ret_secret_size); assert(TPM2_PCR_MASK_VALID(hash_pcr_mask)); assert(TPM2_PCR_MASK_VALID(pubkey_pcr_mask)); @@ -5467,7 +5621,7 @@ int tpm2_unseal(Tpm2Context *c, TPM2B_PUBLIC public; TPM2B_PRIVATE private; TPM2B_ENCRYPTED_SECRET seed = {}; - r = tpm2_unmarshal_blob(blob, blob_size, &public, &private, &seed); + r = tpm2_unmarshal_blob(blob->iov_base, blob->iov_len, &public, &private, &seed); if (r < 0) return log_debug_errno(r, "Could not extract parts from blob: %m"); @@ -5480,8 +5634,8 @@ int tpm2_unseal(Tpm2Context *c, } _cleanup_(tpm2_handle_freep) Tpm2Handle *primary_handle = NULL; - if (srk_buf) { - r = tpm2_deserialize(c, srk_buf, srk_buf_size, &primary_handle); + if (iovec_is_set(srk)) { + r = tpm2_deserialize(c, srk->iov_base, srk->iov_len, &primary_handle); if (r < 0) return r; } else if (primary_alg != 0) { @@ -5537,14 +5691,13 @@ int tpm2_unseal(Tpm2Context *c, return r; TPM2B_PUBLIC pubkey_tpm2b; - _cleanup_free_ void *fp = NULL; - size_t fp_size = 0; - if (pubkey) { - r = tpm2_tpm2b_public_from_pem(pubkey, pubkey_size, &pubkey_tpm2b); + _cleanup_(iovec_done) struct iovec fp = {}; + if (iovec_is_set(pubkey)) { + r = tpm2_tpm2b_public_from_pem(pubkey->iov_base, pubkey->iov_len, &pubkey_tpm2b); if (r < 0) return log_debug_errno(r, "Could not create TPMT_PUBLIC: %m"); - r = tpm2_tpm2b_public_to_fingerprint(&pubkey_tpm2b, &fp, &fp_size); + r = tpm2_tpm2b_public_to_fingerprint(&pubkey_tpm2b, &fp.iov_base, &fp.iov_len); if (r < 0) return log_debug_errno(r, "Could not get key fingerprint: %m"); } @@ -5582,8 +5735,8 @@ int tpm2_unseal(Tpm2Context *c, policy_session, hash_pcr_mask, pcr_bank, - pubkey ? &pubkey_tpm2b : NULL, - fp, fp_size, + iovec_is_set(pubkey) ? &pubkey_tpm2b : NULL, + fp.iov_base, fp.iov_len, pubkey_pcr_mask, signature, !!pin, @@ -5594,11 +5747,12 @@ int tpm2_unseal(Tpm2Context *c, /* If we know the policy hash to expect, and it doesn't match, we can shortcut things here, and not * wait until the TPM2 tells us to go away. */ - if (known_policy_hash_size > 0 && - memcmp_nn(policy_digest->buffer, policy_digest->size, known_policy_hash, known_policy_hash_size) != 0) { - + if (iovec_is_set(known_policy_hash) && memcmp_nn(policy_digest->buffer, + policy_digest->size, + known_policy_hash->iov_base, + known_policy_hash->iov_len) != 0) { #if HAVE_OPENSSL - if (pubkey_size > 0 && + if (iovec_is_set(pubkey) && pubkey_tpm2b.publicArea.type == TPM2_ALG_RSA && pubkey_tpm2b.publicArea.parameters.rsaDetail.exponent == TPM2_RSA_DEFAULT_EXPONENT) { /* Due to bug #30546, if using RSA pubkey with the default exponent, we may @@ -5630,23 +5784,24 @@ int tpm2_unseal(Tpm2Context *c, log_debug("A PCR value changed during the TPM2 policy session, restarting HMAC key unsealing (%u tries left).", i); } - _cleanup_(erase_and_freep) char *secret = NULL; - secret = memdup(unsealed->buffer, unsealed->size); + _cleanup_(iovec_done_erase) struct iovec secret = {}; + secret.iov_base = memdup(unsealed->buffer, unsealed->size); explicit_bzero_safe(unsealed->buffer, unsealed->size); - if (!secret) + if (!secret.iov_base) return log_oom_debug(); + secret.iov_len = unsealed->size; if (DEBUG_LOGGING) log_debug("Completed TPM2 key unsealing in %s.", FORMAT_TIMESPAN(now(CLOCK_MONOTONIC) - start, 1)); - *ret_secret = TAKE_PTR(secret); - *ret_secret_size = unsealed->size; + *ret_secret = TAKE_STRUCT(secret); return 0; } static TPM2_HANDLE generate_random_nv_index(void) { - return TPM2_NV_INDEX_FIRST + (TPM2_HANDLE) random_u64_range(TPM2_NV_INDEX_LAST - TPM2_NV_INDEX_FIRST + 1); + return TPM2_NV_INDEX_UNASSIGNED_FIRST + + (TPM2_HANDLE) random_u64_range(TPM2_NV_INDEX_UNASSIGNED_LAST - TPM2_NV_INDEX_UNASSIGNED_FIRST + 1); } int tpm2_define_policy_nv_index( @@ -5654,8 +5809,6 @@ int tpm2_define_policy_nv_index( const Tpm2Handle *session, TPM2_HANDLE requested_nv_index, const TPM2B_DIGEST *write_policy, - const char *pin, - const TPM2B_AUTH *auth, TPM2_HANDLE *ret_nv_index, Tpm2Handle **ret_nv_handle, TPM2B_NV_PUBLIC *ret_nv_public) { @@ -5665,7 +5818,10 @@ int tpm2_define_policy_nv_index( int r; assert(c); - assert(pin || auth); + + /* Allocates an nvindex to store a policy for use in PolicyAuthorizeNV in. This is where pcrlock then + * stores its predicted PCR policies in. If 'requested_nv_index' will try to allocate the specified + * nvindex, otherwise will find a free one, and use that. */ r = tpm2_handle_new(c, &new_handle); if (r < 0) @@ -5673,17 +5829,6 @@ int tpm2_define_policy_nv_index( new_handle->flush = false; /* This is a persistent NV index, don't flush hence */ - TPM2B_AUTH _auth = {}; - CLEANUP_ERASE(_auth); - - if (!auth) { - r = tpm2_get_pin_auth(TPM2_ALG_SHA256, pin, &_auth); - if (r < 0) - return r; - - auth = &_auth; - } - for (unsigned try = 0; try < 25U; try++) { TPM2_HANDLE nv_index; @@ -5711,7 +5856,7 @@ int tpm2_define_policy_nv_index( /* shandle1= */ session ? session->esys_handle : ESYS_TR_PASSWORD, /* shandle2= */ ESYS_TR_NONE, /* shandle3= */ ESYS_TR_NONE, - auth, + /* auth= */ NULL, &public_info, &new_handle->esys_handle); @@ -5937,10 +6082,7 @@ int tpm2_unseal_data( "Failed to unseal data: %s", sym_Tss2_RC_Decode(rc)); _cleanup_(iovec_done) struct iovec d = {}; - d = (struct iovec) { - .iov_base = memdup(unsealed->buffer, unsealed->size), - .iov_len = unsealed->size, - }; + d = IOVEC_MAKE(memdup(unsealed->buffer, unsealed->size), unsealed->size); explicit_bzero_safe(unsealed->buffer, unsealed->size); @@ -6011,7 +6153,7 @@ int tpm2_list_devices(void) { } } - if (table_get_rows(t) <= 1) { + if (table_isempty(t)) { log_info("No suitable TPM2 devices found."); return 0; } @@ -6839,6 +6981,43 @@ int tpm2_pcrlock_search_file(const char *path, FILE **ret_file, char **ret_path) return 0; } +int tpm2_pcrlock_policy_from_json( + JsonVariant *v, + Tpm2PCRLockPolicy *ret_policy) { + + /* We use a type check of _JSON_VARIANT_TYPE_INVALID for the integer fields to allow + * json_dispatch_uint32() to parse strings as integers to work around the integer type weakness of + * JSON's design. */ + JsonDispatch policy_dispatch[] = { + { "pcrBank", JSON_VARIANT_STRING, json_dispatch_tpm2_algorithm, offsetof(Tpm2PCRLockPolicy, algorithm), JSON_MANDATORY }, + { "pcrValues", JSON_VARIANT_ARRAY, json_dispatch_variant, offsetof(Tpm2PCRLockPolicy, prediction_json), JSON_MANDATORY }, + { "nvIndex", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint32, offsetof(Tpm2PCRLockPolicy, nv_index), JSON_MANDATORY }, + { "nvHandle", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(Tpm2PCRLockPolicy, nv_handle), JSON_MANDATORY }, + { "nvPublic", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(Tpm2PCRLockPolicy, nv_public), JSON_MANDATORY }, + { "srkHandle", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(Tpm2PCRLockPolicy, srk_handle), JSON_MANDATORY }, + { "pinPublic", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(Tpm2PCRLockPolicy, pin_public), JSON_MANDATORY }, + { "pinPrivate", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(Tpm2PCRLockPolicy, pin_private), JSON_MANDATORY }, + {} + }; + + _cleanup_(tpm2_pcrlock_policy_done) Tpm2PCRLockPolicy policy = {}; + int r; + + assert(v); + assert(ret_policy); + + r = json_dispatch(v, policy_dispatch, JSON_LOG, &policy); + if (r < 0) + return r; + + r = tpm2_pcr_prediction_from_json(&policy.prediction, policy.algorithm, policy.prediction_json); + if (r < 0) + return r; + + *ret_policy = TAKE_STRUCT(policy); + return 1; +} + int tpm2_pcrlock_policy_load( const char *path, Tpm2PCRLockPolicy *ret_policy) { @@ -6855,41 +7034,141 @@ int tpm2_pcrlock_policy_load( if (r < 0) return log_error_errno(r, "Failed to load TPM2 pcrlock policy file: %m"); - _cleanup_(json_variant_unrefp) JsonVariant *configuration_json = NULL; + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; r = json_parse_file( f, discovered_path, /* flags = */ 0, - &configuration_json, + &v, /* ret_line= */ NULL, /* ret_column= */ NULL); if (r < 0) return log_error_errno(r, "Failed to parse existing pcrlock policy file '%s': %m", discovered_path); - JsonDispatch policy_dispatch[] = { - { "pcrBank", JSON_VARIANT_STRING, json_dispatch_tpm2_algorithm, offsetof(Tpm2PCRLockPolicy, algorithm), JSON_MANDATORY }, - { "pcrValues", JSON_VARIANT_ARRAY, json_dispatch_variant, offsetof(Tpm2PCRLockPolicy, prediction_json), JSON_MANDATORY }, - { "nvIndex", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint32, offsetof(Tpm2PCRLockPolicy, nv_index), JSON_MANDATORY }, - { "nvHandle", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(Tpm2PCRLockPolicy, nv_handle), JSON_MANDATORY }, - { "nvPublic", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(Tpm2PCRLockPolicy, nv_public), JSON_MANDATORY }, - { "srkHandle", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(Tpm2PCRLockPolicy, srk_handle), JSON_MANDATORY }, - { "pinPublic", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(Tpm2PCRLockPolicy, pin_public), JSON_MANDATORY }, - { "pinPrivate", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(Tpm2PCRLockPolicy, pin_private), JSON_MANDATORY }, - {} - }; + return tpm2_pcrlock_policy_from_json(v, ret_policy); +} - _cleanup_(tpm2_pcrlock_policy_done) Tpm2PCRLockPolicy policy = {}; +static int pcrlock_policy_load_credential( + const char *name, + const struct iovec *data, + Tpm2PCRLockPolicy *ret) { + + _cleanup_free_ char *c = NULL; + int r; + + assert(name); - r = json_dispatch(configuration_json, policy_dispatch, JSON_LOG, &policy); + c = strdup(name); + if (!c) + return log_oom(); + + ascii_strlower(c); /* Lowercase, to match what we did at encryption time */ + + _cleanup_(iovec_done) struct iovec decoded = {}; + r = decrypt_credential_and_warn( + c, + now(CLOCK_REALTIME), + /* tpm2_device= */ NULL, + /* tpm2_signature_path= */ NULL, + UID_INVALID, + data, + CREDENTIAL_ALLOW_NULL, + &decoded); if (r < 0) return r; - r = tpm2_pcr_prediction_from_json(&policy.prediction, policy.algorithm, policy.prediction_json); + if (memchr(decoded.iov_base, 0, decoded.iov_len)) + return log_error_errno(r, "Credential '%s' contains embedded NUL byte, refusing.", name); + + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + r = json_parse(decoded.iov_base, + /* flags= */ 0, + &v, + /* ret_line= */ NULL, + /* ret_column= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to parse pcrlock policy: %m"); + + r = tpm2_pcrlock_policy_from_json(v, ret); if (r < 0) return r; - *ret_policy = TAKE_STRUCT(policy); - return 1; + return 0; +} + +int tpm2_pcrlock_policy_from_credentials( + const struct iovec *srk, + const struct iovec *nv, + Tpm2PCRLockPolicy *ret) { + + _cleanup_close_ int dfd = -EBADF; + int r; + + /* During boot we'll not have access to the pcrlock.json file in /var/. In order to support + * pcrlock-bound root file systems we'll store a copy of the JSON data, wrapped in an (plaintext) + * credential in the ESP or XBOOTLDR partition. There might be multiple of those however (because of + * multi-boot), hence we use the SRK and NV data from the LUKS2 header as search key, and parse all + * such JSON policies until we find a matching one. */ + + const char *cp = secure_getenv("SYSTEMD_ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY") ?: ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY; + + dfd = open(cp, O_CLOEXEC|O_DIRECTORY); + if (dfd < 0) { + if (errno == ENOENT) { + log_debug("No encrypted system credentials passed."); + return 0; + } + + return log_error_errno(errno, "Failed to open system credentials directory."); + } + + _cleanup_free_ DirectoryEntries *de = NULL; + r = readdir_all(dfd, RECURSE_DIR_IGNORE_DOT, &de); + if (r < 0) + return log_error_errno(r, "Failed to enumerate system credentials: %m"); + + FOREACH_ARRAY(i, de->entries, de->n_entries) { + _cleanup_(iovec_done) struct iovec data = {}; + struct dirent *d = *i; + + if (!startswith_no_case(d->d_name, "pcrlock.")) /* VFAT is case-insensitive, hence don't be too strict here */ + continue; + + r = read_full_file_full( + dfd, d->d_name, + /* offset= */ UINT64_MAX, + /* size= */ CREDENTIAL_ENCRYPTED_SIZE_MAX, + READ_FULL_FILE_UNBASE64|READ_FULL_FILE_FAIL_WHEN_LARGER, + /* bind_name= */ NULL, + (char**) &data.iov_base, + &data.iov_len); + if (r == -ENOENT) + continue; + if (r < 0) { + log_warning_errno(r, "Failed to read credentials file %s/%s, skipping: %m", ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY, d->d_name); + continue; + } + + _cleanup_(tpm2_pcrlock_policy_done) Tpm2PCRLockPolicy loaded_policy = {}; + r = pcrlock_policy_load_credential( + d->d_name, + &data, + &loaded_policy); + if (r < 0) { + log_warning_errno(r, "Loading of pcrlock policy from credential '%s/%s' failed, skipping.", ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY, d->d_name); + continue; + } + + if ((!srk || iovec_memcmp(srk, &loaded_policy.srk_handle) == 0) && + (!nv || iovec_memcmp(nv, &loaded_policy.nv_handle) == 0)) { + *ret = TAKE_STRUCT(loaded_policy); + return 1; + } + } + + log_info("No pcrlock policy found among system credentials."); + *ret = (Tpm2PCRLockPolicy) {}; + return 0; } int tpm2_load_public_key_file(const char *path, TPM2B_PUBLIC *ret) { @@ -6929,6 +7208,75 @@ int tpm2_load_public_key_file(const char *path, TPM2B_PUBLIC *ret) { *ret = device_key_public; return 0; } + +int tpm2_hmac_key_from_pin(Tpm2Context *c, const Tpm2Handle *session, const TPM2B_AUTH *pin, Tpm2Handle **ret) { + int r; + + assert(c); + assert(pin); + assert(ret); + + log_debug("Converting PIN into TPM2 HMAC-SHA256 object."); + + /* Load the PIN (which we have stored in the "auth" TPM2B_AUTH) into the TPM as an HMAC key so that + * we can use it in a TPM2_PolicySigned() to write to the nvindex. For that we'll prep a pair of + * TPM2B_PUBLIC and TPM2B_SENSITIVE that defines an HMAC-SHA256 keyed hash function, and initialize + * it based on the provided PIN data. */ + + TPM2B_PUBLIC auth_hmac_public = { + .publicArea = { + .type = TPM2_ALG_KEYEDHASH, + .nameAlg = TPM2_ALG_SHA256, + .objectAttributes = TPMA_OBJECT_SIGN_ENCRYPT, + .parameters.keyedHashDetail.scheme = { + .scheme = TPM2_ALG_HMAC, + .details.hmac.hashAlg = TPM2_ALG_SHA256, + }, + .unique.keyedHash.size = SHA256_DIGEST_SIZE, + }, + }; + + TPM2B_SENSITIVE auth_hmac_private = { + .sensitiveArea = { + .sensitiveType = TPM2_ALG_KEYEDHASH, + .sensitive.bits.size = pin->size, + .seedValue.size = SHA256_DIGEST_SIZE, + }, + }; + + /* Copy in the key data */ + memcpy_safe(auth_hmac_private.sensitiveArea.sensitive.bits.buffer, pin->buffer, pin->size); + + /* NB: We initialize the seed of the TPMT_SENSITIVE structure to all zeroes, since we want a stable + * "name" of the PIN object */ + + /* Now calculate the "unique" field for the public area, based on the sensitive data, according to + * the algorithm in the TPM2 spec, part 1, Section 27.5.3.2 */ + struct iovec sensitive_data[] = { + IOVEC_MAKE(auth_hmac_private.sensitiveArea.seedValue.buffer, auth_hmac_private.sensitiveArea.seedValue.size), + IOVEC_MAKE(auth_hmac_private.sensitiveArea.sensitive.bits.buffer, auth_hmac_private.sensitiveArea.sensitive.bits.size), + }; + r = tpm2_digest_many( + auth_hmac_public.publicArea.nameAlg, + &auth_hmac_public.publicArea.unique.keyedHash, + sensitive_data, + ELEMENTSOF(sensitive_data), + /* extend= */ false); + if (r < 0) + return r; + + /* And now load the public/private parts into the TPM and get a handle back */ + r = tpm2_load_external( + c, + session, + &auth_hmac_public, + &auth_hmac_private, + ret); + if (r < 0) + return log_error_errno(r, "Failed to load PIN into TPM2: %m"); + + return 0; +} #endif char *tpm2_pcr_mask_to_string(uint32_t mask) { @@ -7002,18 +7350,14 @@ int tpm2_make_luks2_json( int keyslot, uint32_t hash_pcr_mask, uint16_t pcr_bank, - const void *pubkey, - size_t pubkey_size, + const struct iovec *pubkey, uint32_t pubkey_pcr_mask, uint16_t primary_alg, - const void *blob, - size_t blob_size, - const void *policy_hash, - size_t policy_hash_size, - const void *salt, - size_t salt_size, - const void *srk_buf, - size_t srk_buf_size, + const struct iovec *blob, + const struct iovec *policy_hash, + const struct iovec *salt, + const struct iovec *srk, + const struct iovec *pcrlock_nv, TPM2Flags flags, JsonVariant **ret) { @@ -7021,9 +7365,9 @@ int tpm2_make_luks2_json( _cleanup_free_ char *keyslot_as_string = NULL; int r; - assert(blob || blob_size == 0); - assert(policy_hash || policy_hash_size == 0); - assert(pubkey || pubkey_size == 0); + assert(iovec_is_valid(pubkey)); + assert(iovec_is_valid(blob)); + assert(iovec_is_valid(policy_hash)); if (asprintf(&keyslot_as_string, "%i", keyslot) < 0) return -ENOMEM; @@ -7046,17 +7390,18 @@ int tpm2_make_luks2_json( JSON_BUILD_OBJECT( JSON_BUILD_PAIR("type", JSON_BUILD_CONST_STRING("systemd-tpm2")), JSON_BUILD_PAIR("keyslots", JSON_BUILD_ARRAY(JSON_BUILD_STRING(keyslot_as_string))), - JSON_BUILD_PAIR("tpm2-blob", JSON_BUILD_BASE64(blob, blob_size)), + JSON_BUILD_PAIR("tpm2-blob", JSON_BUILD_IOVEC_BASE64(blob)), JSON_BUILD_PAIR("tpm2-pcrs", JSON_BUILD_VARIANT(hmj)), - JSON_BUILD_PAIR_CONDITION(!!tpm2_hash_alg_to_string(pcr_bank), "tpm2-pcr-bank", JSON_BUILD_STRING(tpm2_hash_alg_to_string(pcr_bank))), - JSON_BUILD_PAIR_CONDITION(!!tpm2_asym_alg_to_string(primary_alg), "tpm2-primary-alg", JSON_BUILD_STRING(tpm2_asym_alg_to_string(primary_alg))), - JSON_BUILD_PAIR("tpm2-policy-hash", JSON_BUILD_HEX(policy_hash, policy_hash_size)), - JSON_BUILD_PAIR("tpm2-pin", JSON_BUILD_BOOLEAN(flags & TPM2_FLAGS_USE_PIN)), - JSON_BUILD_PAIR("tpm2_pcrlock", JSON_BUILD_BOOLEAN(flags & TPM2_FLAGS_USE_PCRLOCK)), + JSON_BUILD_PAIR_CONDITION(pcr_bank != 0 && tpm2_hash_alg_to_string(pcr_bank), "tpm2-pcr-bank", JSON_BUILD_STRING(tpm2_hash_alg_to_string(pcr_bank))), + JSON_BUILD_PAIR_CONDITION(primary_alg != 0 && tpm2_asym_alg_to_string(primary_alg), "tpm2-primary-alg", JSON_BUILD_STRING(tpm2_asym_alg_to_string(primary_alg))), + JSON_BUILD_PAIR("tpm2-policy-hash", JSON_BUILD_IOVEC_HEX(policy_hash)), + JSON_BUILD_PAIR_CONDITION(FLAGS_SET(flags, TPM2_FLAGS_USE_PIN), "tpm2-pin", JSON_BUILD_BOOLEAN(true)), + JSON_BUILD_PAIR_CONDITION(FLAGS_SET(flags, TPM2_FLAGS_USE_PCRLOCK), "tpm2_pcrlock", JSON_BUILD_BOOLEAN(true)), JSON_BUILD_PAIR_CONDITION(pubkey_pcr_mask != 0, "tpm2_pubkey_pcrs", JSON_BUILD_VARIANT(pkmj)), - JSON_BUILD_PAIR_CONDITION(pubkey_pcr_mask != 0, "tpm2_pubkey", JSON_BUILD_BASE64(pubkey, pubkey_size)), - JSON_BUILD_PAIR_CONDITION(salt, "tpm2_salt", JSON_BUILD_BASE64(salt, salt_size)), - JSON_BUILD_PAIR_CONDITION(srk_buf, "tpm2_srk", JSON_BUILD_BASE64(srk_buf, srk_buf_size)))); + JSON_BUILD_PAIR_CONDITION(iovec_is_set(pubkey), "tpm2_pubkey", JSON_BUILD_IOVEC_BASE64(pubkey)), + JSON_BUILD_PAIR_CONDITION(iovec_is_set(salt), "tpm2_salt", JSON_BUILD_IOVEC_BASE64(salt)), + JSON_BUILD_PAIR_CONDITION(iovec_is_set(srk), "tpm2_srk", JSON_BUILD_IOVEC_BASE64(srk)), + JSON_BUILD_PAIR_CONDITION(iovec_is_set(pcrlock_nv), "tpm2_pcrlock_nv", JSON_BUILD_IOVEC_BASE64(pcrlock_nv)))); if (r < 0) return r; @@ -7071,22 +7416,17 @@ int tpm2_parse_luks2_json( int *ret_keyslot, uint32_t *ret_hash_pcr_mask, uint16_t *ret_pcr_bank, - void **ret_pubkey, - size_t *ret_pubkey_size, + struct iovec *ret_pubkey, uint32_t *ret_pubkey_pcr_mask, uint16_t *ret_primary_alg, - void **ret_blob, - size_t *ret_blob_size, - void **ret_policy_hash, - size_t *ret_policy_hash_size, - void **ret_salt, - size_t *ret_salt_size, - void **ret_srk_buf, - size_t *ret_srk_buf_size, + struct iovec *ret_blob, + struct iovec *ret_policy_hash, + struct iovec *ret_salt, + struct iovec *ret_srk, + struct iovec *ret_pcrlock_nv, TPM2Flags *ret_flags) { - _cleanup_free_ void *blob = NULL, *policy_hash = NULL, *pubkey = NULL, *salt = NULL, *srk_buf = NULL; - size_t blob_size = 0, policy_hash_size = 0, pubkey_size = 0, salt_size = 0, srk_buf_size = 0; + _cleanup_(iovec_done) struct iovec blob = {}, policy_hash = {}, pubkey = {}, salt = {}, srk = {}, pcrlock_nv = {}; uint32_t hash_pcr_mask = 0, pubkey_pcr_mask = 0; uint16_t primary_alg = TPM2_ALG_ECC; /* ECC was the only supported algorithm in systemd < 250, use that as implied default, for compatibility */ uint16_t pcr_bank = UINT16_MAX; /* default: pick automatically */ @@ -7151,7 +7491,7 @@ int tpm2_parse_luks2_json( if (!w) return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "TPM2 token data lacks 'tpm2-blob' field."); - r = json_variant_unbase64(w, &blob, &blob_size); + r = json_variant_unbase64_iovec(w, &blob); if (r < 0) return log_debug_errno(r, "Invalid base64 data in 'tpm2-blob' field."); @@ -7159,7 +7499,7 @@ int tpm2_parse_luks2_json( if (!w) return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "TPM2 token data lacks 'tpm2-policy-hash' field."); - r = json_variant_unhex(w, &policy_hash, &policy_hash_size); + r = json_variant_unhex_iovec(w, &policy_hash); if (r < 0) return log_debug_errno(r, "Invalid base64 data in 'tpm2-policy-hash' field."); @@ -7181,7 +7521,7 @@ int tpm2_parse_luks2_json( w = json_variant_by_key(v, "tpm2_salt"); if (w) { - r = json_variant_unbase64(w, &salt, &salt_size); + r = json_variant_unbase64_iovec(w, &salt); if (r < 0) return log_debug_errno(r, "Invalid base64 data in 'tpm2_salt' field."); } @@ -7195,7 +7535,7 @@ int tpm2_parse_luks2_json( w = json_variant_by_key(v, "tpm2_pubkey"); if (w) { - r = json_variant_unbase64(w, &pubkey, &pubkey_size); + r = json_variant_unbase64_iovec(w, &pubkey); if (r < 0) return log_debug_errno(r, "Failed to decode PCR public key."); } else if (pubkey_pcr_mask != 0) @@ -7203,11 +7543,18 @@ int tpm2_parse_luks2_json( w = json_variant_by_key(v, "tpm2_srk"); if (w) { - r = json_variant_unbase64(w, &srk_buf, &srk_buf_size); + r = json_variant_unbase64_iovec(w, &srk); if (r < 0) return log_debug_errno(r, "Invalid base64 data in 'tpm2_srk' field."); } + w = json_variant_by_key(v, "tpm2_pcrlock_nv"); + if (w) { + r = json_variant_unbase64_iovec(w, &pcrlock_nv); + if (r < 0) + return log_debug_errno(r, "Invalid base64 data in 'tpm2_pcrlock_nv' field."); + } + if (ret_keyslot) *ret_keyslot = keyslot; if (ret_hash_pcr_mask) @@ -7215,32 +7562,23 @@ int tpm2_parse_luks2_json( if (ret_pcr_bank) *ret_pcr_bank = pcr_bank; if (ret_pubkey) - *ret_pubkey = TAKE_PTR(pubkey); - if (ret_pubkey_size) - *ret_pubkey_size = pubkey_size; + *ret_pubkey = TAKE_STRUCT(pubkey); if (ret_pubkey_pcr_mask) *ret_pubkey_pcr_mask = pubkey_pcr_mask; if (ret_primary_alg) *ret_primary_alg = primary_alg; if (ret_blob) - *ret_blob = TAKE_PTR(blob); - if (ret_blob_size) - *ret_blob_size = blob_size; + *ret_blob = TAKE_STRUCT(blob); if (ret_policy_hash) - *ret_policy_hash = TAKE_PTR(policy_hash); - if (ret_policy_hash_size) - *ret_policy_hash_size = policy_hash_size; + *ret_policy_hash = TAKE_STRUCT(policy_hash); if (ret_salt) - *ret_salt = TAKE_PTR(salt); - if (ret_salt_size) - *ret_salt_size = salt_size; + *ret_salt = TAKE_STRUCT(salt); + if (ret_srk) + *ret_srk = TAKE_STRUCT(srk); + if (ret_pcrlock_nv) + *ret_pcrlock_nv = TAKE_STRUCT(pcrlock_nv); if (ret_flags) *ret_flags = flags; - if (ret_srk_buf) - *ret_srk_buf = TAKE_PTR(srk_buf); - if (ret_srk_buf_size) - *ret_srk_buf_size = srk_buf_size; - return 0; } @@ -7553,7 +7891,7 @@ int tpm2_load_pcr_signature(const char *path, JsonVariant **ret) { /* Tries to load a JSON PCR signature file. Takes an absolute path, a simple file name or NULL. In * the latter two cases searches in /etc/, /usr/lib/, /run/, as usual. */ - search = strv_split_nulstr(CONF_PATHS_NULSTR("systemd")); + search = strv_new(CONF_PATHS("systemd")); if (!search) return log_oom_debug(); @@ -7618,7 +7956,7 @@ int tpm2_util_pbkdf2_hmac_sha256(const void *pass, size_t saltlen, uint8_t ret_key[static SHA256_DIGEST_SIZE]) { - uint8_t _cleanup_(erase_and_freep) *buffer = NULL; + _cleanup_(erase_and_freep) uint8_t *buffer = NULL; uint8_t u[SHA256_DIGEST_SIZE]; /* To keep this simple, since derived KeyLen (dkLen in docs) diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h index 911a3c7..ed306d4 100644 --- a/src/shared/tpm2-util.h +++ b/src/shared/tpm2-util.h @@ -132,6 +132,7 @@ int tpm2_marshal_nv_public(const TPM2B_NV_PUBLIC *nv_public, void **ret, size_t int tpm2_unmarshal_nv_public(const void *data, size_t size, TPM2B_NV_PUBLIC *ret_nv_public); int tpm2_marshal_blob(const TPM2B_PUBLIC *public, const TPM2B_PRIVATE *private, const TPM2B_ENCRYPTED_SECRET *seed, void **ret_blob, size_t *ret_blob_size); int tpm2_unmarshal_blob(const void *blob, size_t blob_size, TPM2B_PUBLIC *ret_public, TPM2B_PRIVATE *ret_private, TPM2B_ENCRYPTED_SECRET *ret_seed); +int tpm2_get_name(Tpm2Context *c, const Tpm2Handle *handle, TPM2B_NAME **ret_name); bool tpm2_supports_alg(Tpm2Context *c, TPM2_ALG_ID alg); bool tpm2_supports_command(Tpm2Context *c, TPM2_CC command); @@ -246,8 +247,10 @@ typedef struct Tpm2PCRLockPolicy { } Tpm2PCRLockPolicy; void tpm2_pcrlock_policy_done(Tpm2PCRLockPolicy *data); +int tpm2_pcrlock_policy_from_json(JsonVariant *v, Tpm2PCRLockPolicy *ret_policy); int tpm2_pcrlock_search_file(const char *path, FILE **ret_file, char **ret_path); int tpm2_pcrlock_policy_load(const char *path, Tpm2PCRLockPolicy *ret_policy); +int tpm2_pcrlock_policy_from_credentials(const struct iovec *srk, const struct iovec *nv, Tpm2PCRLockPolicy *ret); int tpm2_index_to_handle(Tpm2Context *c, TPM2_HANDLE index, const Tpm2Handle *session, TPM2B_PUBLIC **ret_public, TPM2B_NAME **ret_name, TPM2B_NAME **ret_qname, Tpm2Handle **ret_handle); int tpm2_index_from_handle(Tpm2Context *c, const Tpm2Handle *handle, TPM2_HANDLE *ret_index); @@ -255,7 +258,7 @@ int tpm2_index_from_handle(Tpm2Context *c, const Tpm2Handle *handle, TPM2_HANDLE int tpm2_pcr_read(Tpm2Context *c, const TPML_PCR_SELECTION *pcr_selection, Tpm2PCRValue **ret_pcr_values, size_t *ret_n_pcr_values); int tpm2_pcr_read_missing_values(Tpm2Context *c, Tpm2PCRValue *pcr_values, size_t n_pcr_values); -int tpm2_get_pin_auth(TPMI_ALG_HASH hash, const char *pin, TPM2B_AUTH *ret_auth); +int tpm2_auth_value_from_pin(TPMI_ALG_HASH hash, const char *pin, TPM2B_AUTH *ret_auth); int tpm2_set_auth(Tpm2Context *c, const Tpm2Handle *handle, const char *pin); int tpm2_set_auth_binary(Tpm2Context *c, const Tpm2Handle *handle, const TPM2B_AUTH *auth); @@ -266,6 +269,7 @@ int tpm2_policy_authorize_nv(Tpm2Context *c, const Tpm2Handle *session, const Tp int tpm2_policy_pcr(Tpm2Context *c, const Tpm2Handle *session, const TPML_PCR_SELECTION *pcr_selection, TPM2B_DIGEST **ret_policy_digest); int tpm2_policy_or(Tpm2Context *c, const Tpm2Handle *session, const TPM2B_DIGEST *branches, size_t n_branches, TPM2B_DIGEST **ret_policy_digest); int tpm2_policy_super_pcr(Tpm2Context *c, const Tpm2Handle *session, const Tpm2PCRPrediction *prediction, uint16_t algorithm); +int tpm2_policy_signed_hmac_sha256(Tpm2Context *c, const Tpm2Handle *session, const Tpm2Handle *hmac_key_handle, const struct iovec *hmac_key, TPM2B_DIGEST **ret_policy_digest); int tpm2_calculate_pubkey_name(const TPMT_PUBLIC *public, TPM2B_NAME *ret_name); int tpm2_calculate_nv_index_name(const TPMS_NV_PUBLIC *nvpublic, TPM2B_NAME *ret_name); @@ -276,9 +280,10 @@ int tpm2_calculate_policy_authorize_nv(const TPM2B_NV_PUBLIC *public, TPM2B_DIGE int tpm2_calculate_policy_pcr(const Tpm2PCRValue *pcr_values, size_t n_pcr_values, TPM2B_DIGEST *digest); int tpm2_calculate_policy_or(const TPM2B_DIGEST *branches, size_t n_branches, TPM2B_DIGEST *digest); int tpm2_calculate_policy_super_pcr(Tpm2PCRPrediction *prediction, uint16_t algorithm, TPM2B_DIGEST *pcr_policy); +int tpm2_calculate_policy_signed(TPM2B_DIGEST *digest, const TPM2B_NAME *name); int tpm2_calculate_serialize(TPM2_HANDLE handle, const TPM2B_NAME *name, const TPM2B_PUBLIC *public, void **ret_serialized, size_t *ret_serialized_size); int tpm2_calculate_sealing_policy(const Tpm2PCRValue *pcr_values, size_t n_pcr_values, const TPM2B_PUBLIC *public, bool use_pin, const Tpm2PCRLockPolicy *policy, TPM2B_DIGEST *digest); -int tpm2_calculate_seal(TPM2_HANDLE parent_handle, const TPM2B_PUBLIC *parent_public, const TPMA_OBJECT *attributes, const void *secret, size_t secret_size, const TPM2B_DIGEST *policy, const char *pin, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, void **ret_serialized_parent, size_t *ret_serialized_parent_size); +int tpm2_calculate_seal(TPM2_HANDLE parent_handle, const TPM2B_PUBLIC *parent_public, const TPMA_OBJECT *attributes, const struct iovec *secret, const TPM2B_DIGEST *policy, const char *pin, struct iovec *ret_secret, struct iovec *ret_blob, struct iovec *ret_serialized_parent); int tpm2_get_srk_template(TPMI_ALG_PUBLIC alg, TPMT_PUBLIC *ret_template); int tpm2_get_best_srk_template(Tpm2Context *c, TPMT_PUBLIC *ret_template); @@ -286,8 +291,8 @@ int tpm2_get_best_srk_template(Tpm2Context *c, TPMT_PUBLIC *ret_template); int tpm2_get_srk(Tpm2Context *c, const Tpm2Handle *session, TPM2B_PUBLIC **ret_public, TPM2B_NAME **ret_name, TPM2B_NAME **ret_qname, Tpm2Handle **ret_handle); int tpm2_get_or_create_srk(Tpm2Context *c, const Tpm2Handle *session, TPM2B_PUBLIC **ret_public, TPM2B_NAME **ret_name, TPM2B_NAME **ret_qname, Tpm2Handle **ret_handle); -int tpm2_seal(Tpm2Context *c, uint32_t seal_key_handle, const TPM2B_DIGEST *policy, const char *pin, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, uint16_t *ret_primary_alg, void **ret_srk_buf, size_t *ret_srk_buf_size); -int tpm2_unseal(Tpm2Context *c, uint32_t hash_pcr_mask, uint16_t pcr_bank, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, JsonVariant *signature, const char *pin, const Tpm2PCRLockPolicy *pcrlock_policy, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, const void *srk_buf, size_t srk_buf_size, void **ret_secret, size_t *ret_secret_size); +int tpm2_seal(Tpm2Context *c, uint32_t seal_key_handle, const TPM2B_DIGEST *policy, const char *pin, struct iovec *ret_secret, struct iovec *ret_blob, uint16_t *ret_primary_alg, struct iovec *ret_srk); +int tpm2_unseal(Tpm2Context *c, uint32_t hash_pcr_mask, uint16_t pcr_bank, const struct iovec *pubkey, uint32_t pubkey_pcr_mask, JsonVariant *signature, const char *pin, const Tpm2PCRLockPolicy *pcrlock_policy, uint16_t primary_alg, const struct iovec *blob, const struct iovec *policy_hash, const struct iovec *srk, struct iovec *ret_secret); #if HAVE_OPENSSL int tpm2_tpm2b_public_to_openssl_pkey(const TPM2B_PUBLIC *public, EVP_PKEY **ret); @@ -297,7 +302,7 @@ int tpm2_tpm2b_public_from_openssl_pkey(const EVP_PKEY *pkey, TPM2B_PUBLIC *ret) int tpm2_tpm2b_public_from_pem(const void *pem, size_t pem_size, TPM2B_PUBLIC *ret); int tpm2_tpm2b_public_to_fingerprint(const TPM2B_PUBLIC *public, void **ret_fingerprint, size_t *ret_fingerprint_size); -int tpm2_define_policy_nv_index(Tpm2Context *c, const Tpm2Handle *session, TPM2_HANDLE requested_nv_index, const TPM2B_DIGEST *write_policy, const char *pin, const TPM2B_AUTH *auth, TPM2_HANDLE *ret_nv_index, Tpm2Handle **ret_nv_handle, TPM2B_NV_PUBLIC *ret_nv_public); +int tpm2_define_policy_nv_index(Tpm2Context *c, const Tpm2Handle *session, TPM2_HANDLE requested_nv_index, const TPM2B_DIGEST *write_policy, TPM2_HANDLE *ret_nv_index, Tpm2Handle **ret_nv_handle, TPM2B_NV_PUBLIC *ret_nv_public); int tpm2_write_policy_nv_index(Tpm2Context *c, const Tpm2Handle *policy_session, TPM2_HANDLE nv_index, const Tpm2Handle *nv_handle, const TPM2B_DIGEST *policy_digest); int tpm2_undefine_policy_nv_index(Tpm2Context *c, const Tpm2Handle *session, TPM2_HANDLE nv_index, const Tpm2Handle *nv_handle); @@ -309,6 +314,8 @@ int tpm2_deserialize(Tpm2Context *c, const void *serialized, size_t serialized_s int tpm2_load_public_key_file(const char *path, TPM2B_PUBLIC *ret); +int tpm2_hmac_key_from_pin(Tpm2Context *c, const Tpm2Handle *session, const TPM2B_AUTH *pin, Tpm2Handle **ret); + /* The tpm2-tss library has many structs that are simply a combination of an array (or object) and * size. These macros allow easily initializing or assigning instances of such structs from an existing * buffer/object and size, while also checking the size for safety with the struct buffer/object size. If the @@ -384,8 +391,8 @@ int tpm2_find_device_auto(char **ret); int tpm2_make_pcr_json_array(uint32_t pcr_mask, JsonVariant **ret); int tpm2_parse_pcr_json_array(JsonVariant *v, uint32_t *ret); -int tpm2_make_luks2_json(int keyslot, uint32_t hash_pcr_mask, uint16_t pcr_bank, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, const void *salt, size_t salt_size, const void *srk_buf, size_t srk_buf_size, TPM2Flags flags, JsonVariant **ret); -int tpm2_parse_luks2_json(JsonVariant *v, int *ret_keyslot, uint32_t *ret_hash_pcr_mask, uint16_t *ret_pcr_bank, void **ret_pubkey, size_t *ret_pubkey_size, uint32_t *ret_pubkey_pcr_mask, uint16_t *ret_primary_alg, void **ret_blob, size_t *ret_blob_size, void **ret_policy_hash, size_t *ret_policy_hash_size, void **ret_salt, size_t *ret_salt_size, void **ret_srk_buf, size_t *ret_srk_buf_size, TPM2Flags *ret_flags); +int tpm2_make_luks2_json(int keyslot, uint32_t hash_pcr_mask, uint16_t pcr_bank, const struct iovec *pubkey, uint32_t pubkey_pcr_mask, uint16_t primary_alg, const struct iovec *blob, const struct iovec *policy_hash, const struct iovec *salt, const struct iovec *srk, const struct iovec *pcrlock_nv, TPM2Flags flags, JsonVariant **ret); +int tpm2_parse_luks2_json(JsonVariant *v, int *ret_keyslot, uint32_t *ret_hash_pcr_mask, uint16_t *ret_pcr_bank, struct iovec *ret_pubkey, uint32_t *ret_pubkey_pcr_mask, uint16_t *ret_primary_alg, struct iovec *ret_blob, struct iovec *ret_policy_hash, struct iovec *ret_salt, struct iovec *ret_srk, struct iovec *pcrlock_nv, TPM2Flags *ret_flags); /* Default to PCR 7 only */ #define TPM2_PCR_INDEX_DEFAULT UINT32_C(7) @@ -477,3 +484,14 @@ enum { int tpm2_pcr_index_from_string(const char *s) _pure_; const char *tpm2_pcr_index_to_string(int pcr) _const_; + +/* The first and last NV index handle that is not registered to any company, as per TCG's "Registry of + * Reserved TPM 2.0 Handles and Localities", section 2.2.2. */ +#define TPM2_NV_INDEX_UNASSIGNED_FIRST UINT32_C(0x01800000) +#define TPM2_NV_INDEX_UNASSIGNED_LAST UINT32_C(0x01BFFFFF) + +#if HAVE_TPM2 +/* Verify that the above is indeed a subset of the general NV Index range */ +assert_cc(TPM2_NV_INDEX_UNASSIGNED_FIRST >= TPM2_NV_INDEX_FIRST); +assert_cc(TPM2_NV_INDEX_UNASSIGNED_LAST <= TPM2_NV_INDEX_LAST); +#endif diff --git a/src/shared/udev-util.c b/src/shared/udev-util.c index cf28ba8..15996ca 100644 --- a/src/shared/udev-util.c +++ b/src/shared/udev-util.c @@ -14,6 +14,7 @@ #include "id128-util.h" #include "log.h" #include "macro.h" +#include "missing_threads.h" #include "parse-util.h" #include "path-util.h" #include "signal-util.h" @@ -22,43 +23,35 @@ #include "udev-util.h" #include "utf8.h" -int udev_set_max_log_level(char *str) { - size_t n; +int udev_parse_config_full(const ConfigTableItem config_table[]) { + int r; - /* This may modify input string. */ + assert(config_table); - if (isempty(str)) + r = config_parse_standard_file_with_dropins( + "udev/udev.conf", + /* sections = */ NULL, + config_item_table_lookup, config_table, + CONFIG_PARSE_WARN, + /* userdata = */ NULL); + if (r == -ENOENT) return 0; - - /* unquote */ - n = strlen(str); - if (n >= 2 && - ((str[0] == '"' && str[n - 1] == '"') || - (str[0] == '\'' && str[n - 1] == '\''))) { - str[n - 1] = '\0'; - str++; - } - - /* we set the udev log level here explicitly, this is supposed - * to regulate the code in libudev/ and udev/. */ - return log_set_max_level_from_string(str); + return r; } int udev_parse_config(void) { - _cleanup_free_ char *log_val = NULL; - int r; + int r, log_val = -1; + const ConfigTableItem config_table[] = { + { NULL, "udev_log", config_parse_log_level, 0, &log_val }, + {} + }; - r = parse_env_file(NULL, "/etc/udev/udev.conf", - "udev_log", &log_val); - if (r == -ENOENT) - return 0; + r = udev_parse_config_full(config_table); if (r < 0) return r; - r = udev_set_max_log_level(log_val); - if (r < 0) - log_syntax(NULL, LOG_WARNING, "/etc/udev/udev.conf", 0, r, - "Failed to set udev log level '%s', ignoring: %m", log_val); + if (log_val >= 0) + log_set_max_level(log_val); return 0; } @@ -146,7 +139,7 @@ static int device_wait_for_initialization_internal( } if (device) { - if (sd_device_get_is_initialized(device) > 0) { + if (device_is_processed(device) > 0) { if (ret) *ret = sd_device_ref(device); return 0; @@ -209,7 +202,7 @@ static int device_wait_for_initialization_internal( if (r < 0 && !ERRNO_IS_DEVICE_ABSENT(r)) return log_error_errno(r, "Failed to create sd-device object from %s: %m", devlink); } - if (device && sd_device_get_is_initialized(device) > 0) { + if (device && device_is_processed(device) > 0) { if (ret) *ret = sd_device_ref(device); return 0; @@ -237,13 +230,34 @@ int device_is_renaming(sd_device *dev) { assert(dev); - r = sd_device_get_property_value(dev, "ID_RENAMING", NULL); + r = device_get_property_bool(dev, "ID_RENAMING"); if (r == -ENOENT) - return false; + return false; /* defaults to false */ + + return r; +} + +int device_is_processed(sd_device *dev) { + int r; + + assert(dev); + + /* sd_device_get_is_initialized() only checks if the udev database file exists. However, even if the + * database file exist, systemd-udevd may be still processing the device, e.g. when the udev rules + * for the device have RUN tokens. See issue #30056. Hence, to check if the device is really + * processed by systemd-udevd, we also need to read ID_PROCESSING property. */ + + r = sd_device_get_is_initialized(dev); + if (r <= 0) + return r; + + r = device_get_property_bool(dev, "ID_PROCESSING"); + if (r == -ENOENT) + return true; /* If the property does not exist, then it means that the device is processed. */ if (r < 0) return r; - return true; + return !r; } bool device_for_action(sd_device *dev, sd_device_action_t a) { @@ -369,18 +383,22 @@ int udev_queue_is_empty(void) { (errno == ENOENT ? true : -errno) : false; } -bool udev_available(void) { - static int cache = -1; +static int cached_udev_availability = -1; +void reset_cached_udev_availability(void) { + cached_udev_availability = -1; +} + +bool udev_available(void) { /* The service systemd-udevd is started only when /sys is read write. * See systemd-udevd.service: ConditionPathIsReadWrite=/sys * Also, our container interface (http://systemd.io/CONTAINER_INTERFACE/) states that /sys must * be mounted in read-only mode in containers. */ - if (cache >= 0) - return cache; + if (cached_udev_availability >= 0) + return cached_udev_availability; - return (cache = (path_is_read_only_fs("/sys/") <= 0)); + return (cached_udev_availability = (path_is_read_only_fs("/sys/") <= 0)); } int device_get_vendor_string(sd_device *device, const char **ret) { diff --git a/src/shared/udev-util.h b/src/shared/udev-util.h index 651d335..c21c4c1 100644 --- a/src/shared/udev-util.h +++ b/src/shared/udev-util.h @@ -3,15 +3,17 @@ #include "sd-device.h" +#include "conf-parser.h" #include "hashmap.h" #include "time-util.h" -int udev_set_max_log_level(char *str); +int udev_parse_config_full(const ConfigTableItem config_table[]); int udev_parse_config(void); int device_wait_for_initialization(sd_device *device, const char *subsystem, usec_t timeout_usec, sd_device **ret); int device_wait_for_devlink(const char *path, const char *subsystem, usec_t timeout_usec, sd_device **ret); int device_is_renaming(sd_device *dev); +int device_is_processed(sd_device *dev); bool device_for_action(sd_device *dev, sd_device_action_t action); @@ -22,6 +24,7 @@ size_t udev_replace_chars(char *str, const char *allow); int udev_queue_is_empty(void); +void reset_cached_udev_availability(void); bool udev_available(void); int device_get_vendor_string(sd_device *device, const char **ret); diff --git a/src/shared/user-record-nss.c b/src/shared/user-record-nss.c index 414a493..ffb5721 100644 --- a/src/shared/user-record-nss.c +++ b/src/shared/user-record-nss.c @@ -208,39 +208,17 @@ int nss_user_record_by_name( bool with_shadow, UserRecord **ret) { - _cleanup_free_ char *buf = NULL, *sbuf = NULL; - struct passwd pwd, *result; + _cleanup_free_ char *sbuf = NULL; + _cleanup_free_ struct passwd *result = NULL; bool incomplete = false; - size_t buflen = 4096; struct spwd spwd, *sresult = NULL; int r; assert(name); - for (;;) { - buf = malloc(buflen); - if (!buf) - return -ENOMEM; - - r = getpwnam_r(name, &pwd, buf, buflen, &result); - if (r == 0) { - if (!result) - return -ESRCH; - - break; - } - - if (r < 0) - return log_debug_errno(SYNTHETIC_ERRNO(EIO), "getpwnam_r() returned a negative value"); - if (r != ERANGE) - return -r; - - if (buflen > SIZE_MAX / 2) - return -ERANGE; - - buflen *= 2; - buf = mfree(buf); - } + r = getpwnam_malloc(name, &result); + if (r < 0) + return r; if (with_shadow) { r = nss_spwd_for_passwd(result, &spwd, &sbuf); @@ -266,36 +244,15 @@ int nss_user_record_by_uid( bool with_shadow, UserRecord **ret) { - _cleanup_free_ char *buf = NULL, *sbuf = NULL; - struct passwd pwd, *result; + _cleanup_free_ char *sbuf = NULL; + _cleanup_free_ struct passwd *result = NULL; bool incomplete = false; - size_t buflen = 4096; struct spwd spwd, *sresult = NULL; int r; - for (;;) { - buf = malloc(buflen); - if (!buf) - return -ENOMEM; - - r = getpwuid_r(uid, &pwd, buf, buflen, &result); - if (r == 0) { - if (!result) - return -ESRCH; - - break; - } - if (r < 0) - return log_debug_errno(SYNTHETIC_ERRNO(EIO), "getpwuid_r() returned a negative value"); - if (r != ERANGE) - return -r; - - if (buflen > SIZE_MAX / 2) - return -ERANGE; - - buflen *= 2; - buf = mfree(buf); - } + r = getpwuid_malloc(uid, &result); + if (r < 0) + return r; if (with_shadow) { r = nss_spwd_for_passwd(result, &spwd, &sbuf); @@ -422,38 +379,17 @@ int nss_group_record_by_name( bool with_shadow, GroupRecord **ret) { - _cleanup_free_ char *buf = NULL, *sbuf = NULL; - struct group grp, *result; + _cleanup_free_ char *sbuf = NULL; + _cleanup_free_ struct group *result = NULL; bool incomplete = false; - size_t buflen = 4096; struct sgrp sgrp, *sresult = NULL; int r; assert(name); - for (;;) { - buf = malloc(buflen); - if (!buf) - return -ENOMEM; - - r = getgrnam_r(name, &grp, buf, buflen, &result); - if (r == 0) { - if (!result) - return -ESRCH; - - break; - } - - if (r < 0) - return log_debug_errno(SYNTHETIC_ERRNO(EIO), "getgrnam_r() returned a negative value"); - if (r != ERANGE) - return -r; - if (buflen > SIZE_MAX / 2) - return -ERANGE; - - buflen *= 2; - buf = mfree(buf); - } + r = getgrnam_malloc(name, &result); + if (r < 0) + return r; if (with_shadow) { r = nss_sgrp_for_group(result, &sgrp, &sbuf); @@ -479,35 +415,15 @@ int nss_group_record_by_gid( bool with_shadow, GroupRecord **ret) { - _cleanup_free_ char *buf = NULL, *sbuf = NULL; - struct group grp, *result; + _cleanup_free_ char *sbuf = NULL; + _cleanup_free_ struct group *result = NULL; bool incomplete = false; - size_t buflen = 4096; struct sgrp sgrp, *sresult = NULL; int r; - for (;;) { - buf = malloc(buflen); - if (!buf) - return -ENOMEM; - - r = getgrgid_r(gid, &grp, buf, buflen, &result); - if (r == 0) { - if (!result) - return -ESRCH; - break; - } - - if (r < 0) - return log_debug_errno(SYNTHETIC_ERRNO(EIO), "getgrgid_r() returned a negative value"); - if (r != ERANGE) - return -r; - if (buflen > SIZE_MAX / 2) - return -ERANGE; - - buflen *= 2; - buf = mfree(buf); - } + r = getgrgid_malloc(gid, &result); + if (r < 0) + return r; if (with_shadow) { r = nss_sgrp_for_group(result, &sgrp, &sbuf); diff --git a/src/shared/user-record-show.c b/src/shared/user-record-show.c index 28fa7a8..23a4153 100644 --- a/src/shared/user-record-show.c +++ b/src/shared/user-record-show.c @@ -3,8 +3,14 @@ #include "cap-list.h" #include "format-util.h" #include "fs-util.h" +#include "glyph-util.h" +#include "hashmap.h" +#include "hexdecoct.h" +#include "path-util.h" +#include "pretty-print.h" #include "process-util.h" #include "rlimit-util.h" +#include "sha256.h" #include "strv.h" #include "terminal-util.h" #include "user-record-show.h" @@ -23,6 +29,7 @@ const char *user_record_state_color(const char *state) { } void user_record_show(UserRecord *hr, bool show_full_group_info) { + _cleanup_strv_free_ char **langs = NULL; const char *hd, *ip, *shell; UserStorage storage; usec_t t; @@ -203,8 +210,45 @@ void user_record_show(UserRecord *hr, bool show_full_group_info) { printf(" Real Name: %s\n", hr->real_name); hd = user_record_home_directory(hr); - if (hd) - printf(" Directory: %s\n", hd); + if (hd) { + printf(" Directory: %s", hd); + + if (hr->fallback_home_directory && hr->use_fallback) + printf(" %s(fallback)%s", ansi_highlight_yellow(), ansi_normal()); + + printf("\n"); + } + + if (hr->blob_directory) { + _cleanup_free_ char **filenames = NULL; + size_t n_filenames = 0; + + r = hashmap_dump_keys_sorted(hr->blob_manifest, (void***) &filenames, &n_filenames); + if (r < 0) { + errno = -r; + printf(" Blob Dir.: %s (can't iterate: %m)\n", hr->blob_directory); + } else + printf(" Blob Dir.: %s\n", hr->blob_directory); + + for (size_t i = 0; i < n_filenames; i++) { + _cleanup_free_ char *path = NULL, *link = NULL, *hash = NULL; + const char *filename = filenames[i]; + const uint8_t *hash_bytes = hashmap_get(hr->blob_manifest, filename); + bool last = i == n_filenames - 1; + + path = path_join(hr->blob_directory, filename); + if (path) + (void) terminal_urlify_path(path, filename, &link); + hash = hexmem(hash_bytes, SHA256_DIGEST_SIZE); + + printf(" %s %s %s(%s)%s\n", + special_glyph(last ? SPECIAL_GLYPH_TREE_RIGHT : SPECIAL_GLYPH_TREE_BRANCH), + link ?: filename, + ansi_grey(), + hash ?: "can't display hash", + ansi_normal()); + } + } storage = user_record_storage(hr); if (storage >= 0) /* Let's be political, and clarify which storage we like, and which we don't. About CIFS we don't complain. */ @@ -222,8 +266,14 @@ void user_record_show(UserRecord *hr, bool show_full_group_info) { printf(" Removable: %s\n", yes_no(b)); shell = user_record_shell(hr); - if (shell) - printf(" Shell: %s\n", shell); + if (shell) { + printf(" Shell: %s", shell); + + if (hr->fallback_shell && hr->use_fallback) + printf(" %s(fallback)%s", ansi_highlight_yellow(), ansi_normal()); + + printf("\n"); + } if (hr->email_address) printf(" Email: %s\n", hr->email_address); @@ -237,15 +287,15 @@ void user_record_show(UserRecord *hr, bool show_full_group_info) { if (hr->time_zone) printf(" Time Zone: %s\n", hr->time_zone); - if (hr->preferred_language) - printf(" Language: %s\n", hr->preferred_language); - - if (!strv_isempty(hr->environment)) - STRV_FOREACH(i, hr->environment) { - printf(i == hr->environment ? - " Environment: %s\n" : - " %s\n", *i); - } + r = user_record_languages(hr, &langs); + if (r < 0) { + errno = -r; + printf(" Languages: (can't acquire: %m)\n"); + } else if (!strv_isempty(langs)) { + STRV_FOREACH(i, langs) + printf(i == langs ? " Languages: %s" : ", %s", *i); + printf("\n"); + } if (hr->locked >= 0) printf(" Locked: %s\n", yes_no(hr->locked)); @@ -525,6 +575,11 @@ void user_record_show(UserRecord *hr, bool show_full_group_info) { if (hr->auto_login >= 0) printf("Autom. Login: %s\n", yes_no(hr->auto_login)); + if (hr->preferred_session_launcher) + printf("Sess. Launch: %s\n", hr->preferred_session_launcher); + if (hr->preferred_session_type) + printf("Session Type: %s\n", hr->preferred_session_type); + if (hr->kill_processes >= 0) printf(" Kill Proc.: %s\n", yes_no(hr->kill_processes)); diff --git a/src/shared/user-record.c b/src/shared/user-record.c index 3fe3e80..ec084de 100644 --- a/src/shared/user-record.c +++ b/src/shared/user-record.c @@ -10,15 +10,18 @@ #include "glyph-util.h" #include "hexdecoct.h" #include "hostname-util.h" +#include "locale-util.h" #include "memory-util.h" #include "path-util.h" #include "pkcs11-util.h" #include "rlimit-util.h" +#include "sha256.h" #include "string-table.h" #include "strv.h" -#include "uid-alloc-range.h" +#include "uid-classification.h" #include "user-record.h" #include "user-util.h" +#include "utf8.h" #define DEFAULT_RATELIMIT_BURST 30 #define DEFAULT_RATELIMIT_INTERVAL_USEC (1*USEC_PER_MINUTE) @@ -141,11 +144,15 @@ static UserRecord* user_record_free(UserRecord *h) { free(h->location); free(h->icon_name); + free(h->blob_directory); + hashmap_free(h->blob_manifest); + free(h->shell); strv_free(h->environment); free(h->time_zone); free(h->preferred_language); + strv_free(h->additional_languages); rlimit_free_all(h->rlimits); free(h->skeleton_directory); @@ -165,6 +172,9 @@ static UserRecord* user_record_free(UserRecord *h) { free(h->home_directory); free(h->home_directory_auto); + free(h->fallback_shell); + free(h->fallback_home_directory); + strv_free(h->member_of); strv_free(h->capability_bounding_set); strv_free(h->capability_ambient_set); @@ -179,6 +189,9 @@ static UserRecord* user_record_free(UserRecord *h) { free(h->state); free(h->service); + free(h->preferred_session_type); + free(h->preferred_session_launcher); + strv_free(h->pkcs11_token_uri); for (size_t i = 0; i < h->n_pkcs11_encrypted_key; i++) pkcs11_encrypted_key_done(h->pkcs11_encrypted_key + i); @@ -535,44 +548,65 @@ static int json_dispatch_environment(const char *name, JsonVariant *variant, Jso return strv_free_and_replace(*l, n); } -int json_dispatch_user_disposition(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { - UserDisposition *disposition = userdata, k; +static int json_dispatch_locale(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { + char **s = userdata; + const char *n; + int r; if (json_variant_is_null(variant)) { - *disposition = _USER_DISPOSITION_INVALID; + *s = mfree(*s); return 0; } if (!json_variant_is_string(variant)) return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name)); - k = user_disposition_from_string(json_variant_string(variant)); - if (k < 0) - return json_log(variant, flags, k, "Disposition type '%s' not known.", json_variant_string(variant)); + n = json_variant_string(variant); + + if (!locale_is_valid(n)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid locale.", strna(name)); + + r = free_and_strdup(s, n); + if (r < 0) + return json_log(variant, flags, r, "Failed to allocate string: %m"); - *disposition = k; return 0; } -static int json_dispatch_storage(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { - UserStorage *storage = userdata, k; +static int json_dispatch_locales(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { + _cleanup_strv_free_ char **n = NULL; + char ***l = userdata; + const char *locale; + JsonVariant *e; + int r; if (json_variant_is_null(variant)) { - *storage = _USER_STORAGE_INVALID; + *l = strv_free(*l); return 0; } - if (!json_variant_is_string(variant)) - return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name)); + if (!json_variant_is_array(variant)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of strings.", strna(name)); - k = user_storage_from_string(json_variant_string(variant)); - if (k < 0) - return json_log(variant, flags, k, "Storage type '%s' not known.", json_variant_string(variant)); + JSON_VARIANT_ARRAY_FOREACH(e, variant) { + if (!json_variant_is_string(e)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of strings.", strna(name)); - *storage = k; - return 0; + locale = json_variant_string(e); + if (!locale_is_valid(locale)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of valid locales.", strna(name)); + + r = strv_extend(&n, locale); + if (r < 0) + return json_log_oom(variant, flags); + } + + return strv_free_and_replace(*l, n); } +JSON_DISPATCH_ENUM_DEFINE(json_dispatch_user_disposition, UserDisposition, user_disposition_from_string); +static JSON_DISPATCH_ENUM_DEFINE(json_dispatch_user_storage, UserStorage, user_storage_from_string); + static int json_dispatch_tasks_or_memory_max(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { uint64_t *limit = userdata, k; @@ -746,7 +780,7 @@ static int dispatch_pkcs11_key_data(const char *name, JsonVariant *variant, Json if (!json_variant_is_string(variant)) return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name)); - r = unbase64mem(json_variant_string(variant), SIZE_MAX, &b, &l); + r = unbase64mem(json_variant_string(variant), &b, &l); if (r < 0) return json_log(variant, flags, r, "Failed to decode encrypted PKCS#11 key: %m"); @@ -813,7 +847,7 @@ static int dispatch_fido2_hmac_credential(const char *name, JsonVariant *variant if (!json_variant_is_string(variant)) return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name)); - r = unbase64mem(json_variant_string(variant), SIZE_MAX, &b, &l); + r = unbase64mem(json_variant_string(variant), &b, &l); if (r < 0) return json_log(variant, flags, r, "Failed to decode FIDO2 credential ID: %m"); @@ -843,7 +877,7 @@ static int dispatch_fido2_hmac_credential_array(const char *name, JsonVariant *v if (!array) return log_oom(); - r = unbase64mem(json_variant_string(e), SIZE_MAX, &b, &l); + r = unbase64mem(json_variant_string(e), &b, &l); if (r < 0) return json_log(variant, flags, r, "Failed to decode FIDO2 credential ID: %m"); @@ -873,7 +907,7 @@ static int dispatch_fido2_hmac_salt_value(const char *name, JsonVariant *variant if (!json_variant_is_string(variant)) return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name)); - r = unbase64mem(json_variant_string(variant), SIZE_MAX, &b, &l); + r = unbase64mem(json_variant_string(variant), &b, &l); if (r < 0) return json_log(variant, flags, r, "Failed to decode FIDO2 salt: %m"); @@ -1048,6 +1082,7 @@ static int dispatch_privileged(const char *name, JsonVariant *variant, JsonDispa static int dispatch_binding(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { static const JsonDispatch binding_dispatch_table[] = { + { "blobDirectory", JSON_VARIANT_STRING, json_dispatch_path, offsetof(UserRecord, blob_directory), 0 }, { "imagePath", JSON_VARIANT_STRING, json_dispatch_image_path, offsetof(UserRecord, image_path), 0 }, { "homeDirectory", JSON_VARIANT_STRING, json_dispatch_home_directory, offsetof(UserRecord, home_directory), 0 }, { "partitionUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, partition_uuid), 0 }, @@ -1055,7 +1090,7 @@ static int dispatch_binding(const char *name, JsonVariant *variant, JsonDispatch { "fileSystemUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, file_system_uuid), 0 }, { "uid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(UserRecord, uid), 0 }, { "gid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(UserRecord, gid), 0 }, - { "storage", JSON_VARIANT_STRING, json_dispatch_storage, offsetof(UserRecord, storage), 0 }, + { "storage", JSON_VARIANT_STRING, json_dispatch_user_storage, offsetof(UserRecord, storage), 0 }, { "fileSystemType", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, file_system_type), JSON_SAFE }, { "luksCipher", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_cipher), JSON_SAFE }, { "luksCipherMode", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_cipher_mode), JSON_SAFE }, @@ -1084,6 +1119,52 @@ static int dispatch_binding(const char *name, JsonVariant *variant, JsonDispatch return json_dispatch(m, binding_dispatch_table, flags, userdata); } +static int dispatch_blob_manifest(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { + _cleanup_hashmap_free_ Hashmap *manifest = NULL; + Hashmap **ret = ASSERT_PTR(userdata); + JsonVariant *value; + const char *key; + int r; + + if (!variant) + return 0; + + if (!json_variant_is_object(variant)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an object.", strna(name)); + + JSON_VARIANT_OBJECT_FOREACH(key, value, variant) { + _cleanup_free_ char *filename = NULL; + _cleanup_free_ uint8_t *hash = NULL; + + if (!json_variant_is_string(value)) + return json_log(value, flags, SYNTHETIC_ERRNO(EINVAL), "Blob entry '%s' has invalid hash.", key); + + if (!suitable_blob_filename(key)) + return json_log(value, flags, SYNTHETIC_ERRNO(EINVAL), "Blob entry '%s' has invalid filename.", key); + + filename = strdup(key); + if (!filename) + return json_log_oom(value, flags); + + hash = malloc(SHA256_DIGEST_SIZE); + if (!hash) + return json_log_oom(value, flags); + + r = parse_sha256(json_variant_string(value), hash); + if (r < 0) + return json_log(value, flags, r, "Blob entry '%s' has invalid hash: %s", filename, json_variant_string(value)); + + r = hashmap_ensure_put(&manifest, &path_hash_ops_free_free, filename, hash); + if (r < 0) + return json_log(value, flags, r, "Failed to insert blob manifest entry '%s': %m", filename); + TAKE_PTR(filename); /* Ownership transfers to hashmap */ + TAKE_PTR(hash); + } + + hashmap_free_and_replace(*ret, manifest); + return 0; +} + int per_machine_id_match(JsonVariant *ids, JsonDispatchFlags flags) { sd_id128_t mid; int r; @@ -1168,24 +1249,54 @@ int per_machine_hostname_match(JsonVariant *hns, JsonDispatchFlags flags) { return false; } +int per_machine_match(JsonVariant *entry, JsonDispatchFlags flags) { + JsonVariant *m; + int r; + + assert(json_variant_is_object(entry)); + + m = json_variant_by_key(entry, "matchMachineId"); + if (m) { + r = per_machine_id_match(m, flags); + if (r < 0) + return r; + if (r > 0) + return true; + } + + m = json_variant_by_key(entry, "matchHostname"); + if (m) { + r = per_machine_hostname_match(m, flags); + if (r < 0) + return r; + if (r > 0) + return true; + } + + return false; +} + static int dispatch_per_machine(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { static const JsonDispatch per_machine_dispatch_table[] = { { "matchMachineId", _JSON_VARIANT_TYPE_INVALID, NULL, 0, 0 }, { "matchHostname", _JSON_VARIANT_TYPE_INVALID, NULL, 0, 0 }, + { "blobDirectory", JSON_VARIANT_STRING, json_dispatch_path, offsetof(UserRecord, blob_directory), 0 }, + { "blobManifest", JSON_VARIANT_OBJECT, dispatch_blob_manifest, offsetof(UserRecord, blob_manifest), 0 }, { "iconName", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, icon_name), JSON_SAFE }, { "location", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, location), 0 }, { "shell", JSON_VARIANT_STRING, json_dispatch_filename_or_path, offsetof(UserRecord, shell), 0 }, { "umask", JSON_VARIANT_UNSIGNED, json_dispatch_umask, offsetof(UserRecord, umask), 0 }, { "environment", JSON_VARIANT_ARRAY, json_dispatch_environment, offsetof(UserRecord, environment), 0 }, { "timeZone", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, time_zone), JSON_SAFE }, - { "preferredLanguage", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, preferred_language), JSON_SAFE }, + { "preferredLanguage", JSON_VARIANT_STRING, json_dispatch_locale, offsetof(UserRecord, preferred_language), 0 }, + { "additionalLanguages", JSON_VARIANT_ARRAY, json_dispatch_locales, offsetof(UserRecord, additional_languages), 0 }, { "niceLevel", _JSON_VARIANT_TYPE_INVALID, json_dispatch_nice, offsetof(UserRecord, nice_level), 0 }, { "resourceLimits", _JSON_VARIANT_TYPE_INVALID, json_dispatch_rlimits, offsetof(UserRecord, rlimits), 0 }, { "locked", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, locked), 0 }, { "notBeforeUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, not_before_usec), 0 }, { "notAfterUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, not_after_usec), 0 }, - { "storage", JSON_VARIANT_STRING, json_dispatch_storage, offsetof(UserRecord, storage), 0 }, + { "storage", JSON_VARIANT_STRING, json_dispatch_user_storage, offsetof(UserRecord, storage), 0 }, { "diskSize", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_size), 0 }, { "diskSizeRelative", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_size_relative), 0 }, { "skeletonDirectory", JSON_VARIANT_STRING, json_dispatch_path, offsetof(UserRecord, skeleton_directory), 0 }, @@ -1232,6 +1343,8 @@ static int dispatch_per_machine(const char *name, JsonVariant *variant, JsonDisp { "rateLimitBurst", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, ratelimit_burst), 0 }, { "enforcePasswordPolicy", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, enforce_password_policy), 0 }, { "autoLogin", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, auto_login), 0 }, + { "preferredSessionType", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, preferred_session_type), JSON_SAFE }, + { "preferredSessionLauncher", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, preferred_session_launcher), JSON_SAFE }, { "stopDelayUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, stop_delay_usec), 0 }, { "killProcesses", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, kill_processes), 0 }, { "passwordChangeMinUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, password_change_min_usec), 0 }, @@ -1254,33 +1367,13 @@ static int dispatch_per_machine(const char *name, JsonVariant *variant, JsonDisp return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array.", strna(name)); JSON_VARIANT_ARRAY_FOREACH(e, variant) { - bool matching = false; - JsonVariant *m; - if (!json_variant_is_object(e)) return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of objects.", strna(name)); - m = json_variant_by_key(e, "matchMachineId"); - if (m) { - r = per_machine_id_match(m, flags); - if (r < 0) - return r; - - matching = r > 0; - } - - if (!matching) { - m = json_variant_by_key(e, "matchHostname"); - if (m) { - r = per_machine_hostname_match(m, flags); - if (r < 0) - return r; - - matching = r > 0; - } - } - - if (!matching) + r = per_machine_match(e, flags); + if (r < 0) + return r; + if (r == 0) continue; r = json_dispatch(e, per_machine_dispatch_table, flags, userdata); @@ -1294,23 +1387,26 @@ static int dispatch_per_machine(const char *name, JsonVariant *variant, JsonDisp static int dispatch_status(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { static const JsonDispatch status_dispatch_table[] = { - { "diskUsage", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_usage), 0 }, - { "diskFree", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_free), 0 }, - { "diskSize", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_size), 0 }, - { "diskCeiling", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_ceiling), 0 }, - { "diskFloor", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_floor), 0 }, - { "state", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, state), JSON_SAFE }, - { "service", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, service), JSON_SAFE }, - { "signedLocally", _JSON_VARIANT_TYPE_INVALID, json_dispatch_tristate, offsetof(UserRecord, signed_locally), 0 }, - { "goodAuthenticationCounter", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, good_authentication_counter), 0 }, - { "badAuthenticationCounter", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, bad_authentication_counter), 0 }, - { "lastGoodAuthenticationUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, last_good_authentication_usec), 0 }, - { "lastBadAuthenticationUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, last_bad_authentication_usec), 0 }, - { "rateLimitBeginUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, ratelimit_begin_usec), 0 }, - { "rateLimitCount", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, ratelimit_count), 0 }, - { "removable", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(UserRecord, removable), 0 }, - { "accessMode", JSON_VARIANT_UNSIGNED, json_dispatch_access_mode, offsetof(UserRecord, access_mode), 0 }, - { "fileSystemType", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, file_system_type), JSON_SAFE }, + { "diskUsage", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_usage), 0 }, + { "diskFree", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_free), 0 }, + { "diskSize", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_size), 0 }, + { "diskCeiling", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_ceiling), 0 }, + { "diskFloor", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_floor), 0 }, + { "state", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, state), JSON_SAFE }, + { "service", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, service), JSON_SAFE }, + { "signedLocally", _JSON_VARIANT_TYPE_INVALID, json_dispatch_tristate, offsetof(UserRecord, signed_locally), 0 }, + { "goodAuthenticationCounter", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, good_authentication_counter), 0 }, + { "badAuthenticationCounter", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, bad_authentication_counter), 0 }, + { "lastGoodAuthenticationUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, last_good_authentication_usec), 0 }, + { "lastBadAuthenticationUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, last_bad_authentication_usec), 0 }, + { "rateLimitBeginUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, ratelimit_begin_usec), 0 }, + { "rateLimitCount", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, ratelimit_count), 0 }, + { "removable", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, removable), 0 }, + { "accessMode", JSON_VARIANT_UNSIGNED, json_dispatch_access_mode, offsetof(UserRecord, access_mode), 0 }, + { "fileSystemType", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, file_system_type), JSON_SAFE }, + { "fallbackShell", JSON_VARIANT_STRING, json_dispatch_filename_or_path, offsetof(UserRecord, fallback_shell), 0 }, + { "fallbackHomeDirectory", JSON_VARIANT_STRING, json_dispatch_home_directory, offsetof(UserRecord, fallback_home_directory), 0 }, + { "useFallback", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(UserRecord, use_fallback), 0 }, {}, }; @@ -1523,6 +1619,8 @@ int user_record_load(UserRecord *h, JsonVariant *v, UserRecordLoadFlags load_fla static const JsonDispatch user_dispatch_table[] = { { "userName", JSON_VARIANT_STRING, json_dispatch_user_group_name, offsetof(UserRecord, user_name), JSON_RELAX}, { "realm", JSON_VARIANT_STRING, json_dispatch_realm, offsetof(UserRecord, realm), 0 }, + { "blobDirectory", JSON_VARIANT_STRING, json_dispatch_path, offsetof(UserRecord, blob_directory), 0 }, + { "blobManifest", JSON_VARIANT_OBJECT, dispatch_blob_manifest, offsetof(UserRecord, blob_manifest), 0 }, { "realName", JSON_VARIANT_STRING, json_dispatch_gecos, offsetof(UserRecord, real_name), 0 }, { "emailAddress", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, email_address), JSON_SAFE }, { "iconName", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, icon_name), JSON_SAFE }, @@ -1534,13 +1632,14 @@ int user_record_load(UserRecord *h, JsonVariant *v, UserRecordLoadFlags load_fla { "umask", JSON_VARIANT_UNSIGNED, json_dispatch_umask, offsetof(UserRecord, umask), 0 }, { "environment", JSON_VARIANT_ARRAY, json_dispatch_environment, offsetof(UserRecord, environment), 0 }, { "timeZone", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, time_zone), JSON_SAFE }, - { "preferredLanguage", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, preferred_language), JSON_SAFE }, + { "preferredLanguage", JSON_VARIANT_STRING, json_dispatch_locale, offsetof(UserRecord, preferred_language), 0 }, + { "additionalLanguages", JSON_VARIANT_ARRAY, json_dispatch_locales, offsetof(UserRecord, additional_languages), 0 }, { "niceLevel", _JSON_VARIANT_TYPE_INVALID, json_dispatch_nice, offsetof(UserRecord, nice_level), 0 }, { "resourceLimits", _JSON_VARIANT_TYPE_INVALID, json_dispatch_rlimits, offsetof(UserRecord, rlimits), 0 }, { "locked", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, locked), 0 }, { "notBeforeUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, not_before_usec), 0 }, { "notAfterUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, not_after_usec), 0 }, - { "storage", JSON_VARIANT_STRING, json_dispatch_storage, offsetof(UserRecord, storage), 0 }, + { "storage", JSON_VARIANT_STRING, json_dispatch_user_storage, offsetof(UserRecord, storage), 0 }, { "diskSize", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_size), 0 }, { "diskSizeRelative", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_size_relative), 0 }, { "skeletonDirectory", JSON_VARIANT_STRING, json_dispatch_path, offsetof(UserRecord, skeleton_directory), 0 }, @@ -1589,6 +1688,8 @@ int user_record_load(UserRecord *h, JsonVariant *v, UserRecordLoadFlags load_fla { "rateLimitBurst", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, ratelimit_burst), 0 }, { "enforcePasswordPolicy", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, enforce_password_policy), 0 }, { "autoLogin", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, auto_login), 0 }, + { "preferredSessionType", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, preferred_session_type), JSON_SAFE }, + { "preferredSessionLauncher", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, preferred_session_launcher), JSON_SAFE }, { "stopDelayUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, stop_delay_usec), 0 }, { "killProcesses", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, kill_processes), 0 }, { "passwordChangeMinUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, password_change_min_usec), 0 }, @@ -1625,7 +1726,7 @@ int user_record_load(UserRecord *h, JsonVariant *v, UserRecordLoadFlags load_fla if (r < 0) return r; - r = json_dispatch(h->json, user_dispatch_table, json_flags, h); + r = json_dispatch(h->json, user_dispatch_table, json_flags | JSON_ALLOW_EXTENSIONS, h); if (r < 0) return r; @@ -1720,7 +1821,7 @@ mode_t user_record_access_mode(UserRecord *h) { return h->access_mode != MODE_INVALID ? h->access_mode : 0700; } -const char* user_record_home_directory(UserRecord *h) { +static const char *user_record_home_directory_real(UserRecord *h) { assert(h); if (h->home_directory) @@ -1735,6 +1836,15 @@ const char* user_record_home_directory(UserRecord *h) { return "/"; } +const char* user_record_home_directory(UserRecord *h) { + assert(h); + + if (h->use_fallback && h->fallback_home_directory) + return h->fallback_home_directory; + + return user_record_home_directory_real(h); +} + const char *user_record_image_path(UserRecord *h) { assert(h); @@ -1743,7 +1853,9 @@ const char *user_record_image_path(UserRecord *h) { if (h->image_path_auto) return h->image_path_auto; - return IN_SET(user_record_storage(h), USER_CLASSIC, USER_DIRECTORY, USER_SUBVOLUME, USER_FSCRYPT) ? user_record_home_directory(h) : NULL; + /* For some storage types the image is the home directory itself. (But let's ignore the fallback logic for it) */ + return IN_SET(user_record_storage(h), USER_CLASSIC, USER_DIRECTORY, USER_SUBVOLUME, USER_FSCRYPT) ? + user_record_home_directory_real(h) : NULL; } const char *user_record_cifs_user_name(UserRecord *h) { @@ -1760,7 +1872,7 @@ unsigned long user_record_mount_flags(UserRecord *h) { (h->nodev ? MS_NODEV : 0); } -const char *user_record_shell(UserRecord *h) { +static const char *user_record_shell_real(UserRecord *h) { assert(h); if (h->shell) @@ -1775,6 +1887,21 @@ const char *user_record_shell(UserRecord *h) { return NOLOGIN; } +const char *user_record_shell(UserRecord *h) { + const char *shell; + + assert(h); + + shell = user_record_shell_real(h); + + /* Return fallback shall if we are told so — except if the primary shell is already a nologin shell, + * then let's not risk anything. */ + if (h->use_fallback && h->fallback_shell) + return is_nologin_shell(shell) ? NOLOGIN : h->fallback_shell; + + return shell; +} + const char *user_record_real_name(UserRecord *h) { assert(h); @@ -2062,6 +2189,27 @@ uint64_t user_record_capability_ambient_set(UserRecord *h) { return parse_caps_strv(h->capability_ambient_set) & user_record_capability_bounding_set(h); } +int user_record_languages(UserRecord *h, char ***ret) { + _cleanup_strv_free_ char **l = NULL; + int r; + + assert(h); + assert(ret); + + if (h->preferred_language) { + l = strv_new(h->preferred_language); + if (!l) + return -ENOMEM; + } + + r = strv_extend_strv(&l, h->additional_languages, /* filter_duplicates= */ true); + if (r < 0) + return r; + + *ret = TAKE_PTR(l); + return 0; +} + uint64_t user_record_ratelimit_next_try(UserRecord *h) { assert(h); @@ -2288,6 +2436,13 @@ int user_record_test_password_change_required(UserRecord *h) { return change_permitted ? 0 : -EROFS; } +int suitable_blob_filename(const char *name) { + /* Enforces filename requirements as described in docs/USER_RECORD_BULK_DIRS.md */ + return filename_is_valid(name) && + in_charset(name, URI_UNRESERVED) && + name[0] != '.'; +} + static const char* const user_storage_table[_USER_STORAGE_MAX] = { [USER_CLASSIC] = "classic", [USER_LUKS] = "luks", diff --git a/src/shared/user-record.h b/src/shared/user-record.h index 298dc24..cca112f 100644 --- a/src/shared/user-record.h +++ b/src/shared/user-record.h @@ -6,6 +6,7 @@ #include "sd-id128.h" +#include "hashmap.h" #include "json.h" #include "missing_resource.h" #include "time-util.h" @@ -243,6 +244,9 @@ typedef struct UserRecord { char *icon_name; char *location; + char *blob_directory; + Hashmap *blob_manifest; + UserDisposition disposition; uint64_t last_change_usec; uint64_t last_password_change_usec; @@ -252,6 +256,7 @@ typedef struct UserRecord { char **environment; char *time_zone; char *preferred_language; + char **additional_languages; int nice_level; struct rlimit *rlimits[_RLIMIT_MAX]; @@ -292,6 +297,10 @@ typedef struct UserRecord { char *home_directory; char *home_directory_auto; /* when none is set explicitly, this is where we place the implicit home directory */ + /* fallback shell and home dir */ + char *fallback_shell; + char *fallback_home_directory; + uid_t uid; gid_t gid; @@ -321,6 +330,8 @@ typedef struct UserRecord { uint64_t disk_ceiling; uint64_t disk_floor; + bool use_fallback; /* if true → use fallback_shell + fallback_home_directory instead of the regular ones */ + char *state; char *service; int signed_locally; @@ -340,6 +351,9 @@ typedef struct UserRecord { int auto_login; int drop_caches; + char *preferred_session_type; + char *preferred_session_launcher; + uint64_t stop_delay_usec; /* How long to leave systemd --user around on log-out */ int kill_processes; /* Whether to kill user processes forcibly on log-out */ @@ -415,6 +429,7 @@ AutoResizeMode user_record_auto_resize_mode(UserRecord *h); uint64_t user_record_rebalance_weight(UserRecord *h); uint64_t user_record_capability_bounding_set(UserRecord *h); uint64_t user_record_capability_ambient_set(UserRecord *h); +int user_record_languages(UserRecord *h, char ***ret); int user_record_build_image_path(UserStorage storage, const char *user_name_and_realm, char **ret); @@ -438,8 +453,12 @@ int json_dispatch_user_disposition(const char *name, JsonVariant *variant, JsonD int per_machine_id_match(JsonVariant *ids, JsonDispatchFlags flags); int per_machine_hostname_match(JsonVariant *hns, JsonDispatchFlags flags); +int per_machine_match(JsonVariant *entry, JsonDispatchFlags flags); int user_group_record_mangle(JsonVariant *v, UserRecordLoadFlags load_flags, JsonVariant **ret_variant, UserRecordMask *ret_mask); +#define BLOB_DIR_MAX_SIZE (UINT64_C(64) * U64_MB) +int suitable_blob_filename(const char *name); + const char* user_storage_to_string(UserStorage t) _const_; UserStorage user_storage_from_string(const char *s) _pure_; diff --git a/src/shared/userdb.c b/src/shared/userdb.c index f60d48a..75dece3 100644 --- a/src/shared/userdb.c +++ b/src/shared/userdb.c @@ -199,7 +199,7 @@ static int userdb_on_query_reply( assert_se(!iterator->found_user); - r = json_dispatch(parameters, dispatch_table, 0, &user_data); + r = json_dispatch(parameters, dispatch_table, JSON_ALLOW_EXTENSIONS, &user_data); if (r < 0) goto finish; @@ -256,7 +256,7 @@ static int userdb_on_query_reply( assert_se(!iterator->found_group); - r = json_dispatch(parameters, dispatch_table, 0, &group_data); + r = json_dispatch(parameters, dispatch_table, JSON_ALLOW_EXTENSIONS, &group_data); if (r < 0) goto finish; @@ -309,7 +309,7 @@ static int userdb_on_query_reply( assert(!iterator->found_user_name); assert(!iterator->found_group_name); - r = json_dispatch(parameters, dispatch_table, 0, &membership_data); + r = json_dispatch(parameters, dispatch_table, JSON_ALLOW_EXTENSIONS, &membership_data); if (r < 0) goto finish; @@ -1455,7 +1455,9 @@ int userdb_block_nss_systemd(int b) { return 0; } - call = (int (*)(bool b)) dlsym(dl, "_nss_systemd_block"); + log_debug("Loaded '%s' via dlopen()", LIBDIR "/libnss_systemd.so.2"); + + call = dlsym(dl, "_nss_systemd_block"); if (!call) /* If the file is installed but lacks the symbol we expect, things are weird, let's complain */ return log_debug_errno(SYNTHETIC_ERRNO(ELIBBAD), diff --git a/src/shared/varlink-idl.c b/src/shared/varlink-idl.c index 655324c..6748343 100644 --- a/src/shared/varlink-idl.c +++ b/src/shared/varlink-idl.c @@ -17,6 +17,9 @@ enum { _COLOR_MAX, }; +#define varlink_idl_log(error, format, ...) log_debug_errno(error, "Varlink-IDL: " format, ##__VA_ARGS__) +#define varlink_idl_log_full(level, error, format, ...) log_full_errno(level, error, "Varlink-IDL: " format, ##__VA_ARGS__) + static int varlink_idl_format_all_fields(FILE *f, const VarlinkSymbol *symbol, VarlinkFieldDirection direction, const char *indent, const char *const colors[static _COLOR_MAX]); static int varlink_idl_format_enum_values( @@ -401,7 +404,7 @@ static int varlink_interface_realloc(VarlinkInterface **interface, size_t n_symb assert(interface); - n_symbols ++; /* Space for trailing NULL end marker symbol */ + n_symbols++; /* Space for trailing NULL end marker symbol */ /* Overflow check */ if (n_symbols > (SIZE_MAX - offsetof(VarlinkInterface, symbols)) / sizeof(VarlinkSymbol*)) @@ -420,7 +423,7 @@ static int varlink_symbol_realloc(VarlinkSymbol **symbol, size_t n_fields) { assert(symbol); - n_fields ++; /* Space for trailing end marker field */ + n_fields++; /* Space for trailing end marker field */ /* Overflow check */ if (n_fields > (SIZE_MAX - offsetof(VarlinkSymbol, fields)) / sizeof(VarlinkField)) @@ -512,7 +515,7 @@ static int varlink_idl_subparse_token( l = token_match(*p, allowed_delimiters, allowed_chars); if (l == 0) - return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Couldn't find token of allowed chars '%s' or allowed delimiters '%s'.", strempty(allowed_chars), strempty(allowed_delimiters)); + return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "Couldn't find token of allowed chars '%s' or allowed delimiters '%s'.", strempty(allowed_chars), strempty(allowed_delimiters)); } t = strndup(*p, l); @@ -662,7 +665,7 @@ static int varlink_idl_subparse_field_type( if (r < 0) return r; if (!token) - return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column); + return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column); field->named_type = TAKE_PTR(token); field->field_type = VARLINK_NAMED_TYPE; @@ -704,7 +707,7 @@ static int varlink_idl_subparse_struct_or_enum( assert(n_fields); if (depth > DEPTH_MAX) - return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Maximum nesting depth reached (%u).", *line, *column, DEPTH_MAX); + return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Maximum nesting depth reached (%u).", *line, *column, DEPTH_MAX); while (state != STATE_DONE) { _cleanup_free_ char *token = NULL; @@ -723,9 +726,9 @@ static int varlink_idl_subparse_struct_or_enum( case STATE_OPEN: if (!token) - return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column); + return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column); if (!streq(token, "(")) - return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s'.", *line, *column, token); + return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s'.", *line, *column, token); state = STATE_NAME; allowed_delimiters = ")"; @@ -736,7 +739,7 @@ static int varlink_idl_subparse_struct_or_enum( assert(!field_name); if (!token) - return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column); + return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column); if (streq(token, ")")) state = STATE_DONE; else { @@ -752,7 +755,7 @@ static int varlink_idl_subparse_struct_or_enum( assert(field_name); if (!token) - return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column); + return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column); if (streq(token, ":")) { VarlinkField *field; @@ -760,7 +763,7 @@ static int varlink_idl_subparse_struct_or_enum( if ((*symbol)->symbol_type < 0) (*symbol)->symbol_type = VARLINK_STRUCT_TYPE; if ((*symbol)->symbol_type == VARLINK_ENUM_TYPE) - return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Enum with struct fields, refusing.", *line, *column); + return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Enum with struct fields, refusing.", *line, *column); r = varlink_symbol_realloc(symbol, *n_fields + 1); if (r < 0) @@ -787,7 +790,7 @@ static int varlink_idl_subparse_struct_or_enum( if ((*symbol)->symbol_type < 0) (*symbol)->symbol_type = VARLINK_ENUM_TYPE; if ((*symbol)->symbol_type != VARLINK_ENUM_TYPE) - return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Struct with enum fields, refusing.", *line, *column); + return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Struct with enum fields, refusing.", *line, *column); r = varlink_symbol_realloc(symbol, *n_fields + 1); if (r < 0) @@ -808,7 +811,7 @@ static int varlink_idl_subparse_struct_or_enum( state = STATE_DONE; } } else - return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s'.", *line, *column, token); + return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s'.", *line, *column, token); break; @@ -816,7 +819,7 @@ static int varlink_idl_subparse_struct_or_enum( assert(!field_name); if (!token) - return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column); + return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column); if (streq(token, ",")) { state = STATE_NAME; allowed_delimiters = NULL; @@ -824,7 +827,7 @@ static int varlink_idl_subparse_struct_or_enum( } else if (streq(token, ")")) state = STATE_DONE; else - return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s'.", *line, *column, token); + return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s'.", *line, *column, token); break; default: @@ -835,7 +838,7 @@ static int varlink_idl_subparse_struct_or_enum( /* If we don't know the type of the symbol by now it was an empty () which doesn't allow us to * determine if we look at an enum or a struct */ if ((*symbol)->symbol_type < 0) - return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Ambiguous empty () enum/struct is not permitted.", *line, *column); + return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Ambiguous empty () enum/struct is not permitted.", *line, *column); return 0; } @@ -854,14 +857,14 @@ static int varlink_idl_resolve_symbol_types(VarlinkInterface *interface, Varlink continue; if (!field->named_type) - return log_debug_errno(SYNTHETIC_ERRNO(ENETUNREACH), "Named type field lacking a type name."); + return varlink_idl_log(SYNTHETIC_ERRNO(ENETUNREACH), "Named type field lacking a type name."); found = varlink_idl_find_symbol(interface, _VARLINK_SYMBOL_TYPE_INVALID, field->named_type); if (!found) - return log_debug_errno(SYNTHETIC_ERRNO(ENETUNREACH), "Failed to find type '%s'.", field->named_type); + return varlink_idl_log(SYNTHETIC_ERRNO(ENETUNREACH), "Failed to find type '%s'.", field->named_type); if (!IN_SET(found->symbol_type, VARLINK_STRUCT_TYPE, VARLINK_ENUM_TYPE)) - return log_debug_errno(SYNTHETIC_ERRNO(ENETUNREACH), "Symbol '%s' is referenced as type but is not a type.", field->named_type); + return varlink_idl_log(SYNTHETIC_ERRNO(ENETUNREACH), "Symbol '%s' is referenced as type but is not a type.", field->named_type); field->symbol = found; } @@ -932,7 +935,7 @@ int varlink_idl_parse( case STATE_PRE_INTERFACE: if (!token) - return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column); + return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column); if (streq(token, "#")) { r = varlink_idl_subparse_comment(&text, line, column); if (r < 0) @@ -942,7 +945,7 @@ int varlink_idl_parse( allowed_delimiters = NULL; allowed_chars = VALID_CHARS_INTERFACE_NAME; } else - return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s'.", *line, *column, token); + return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s'.", *line, *column, token); break; case STATE_INTERFACE: @@ -950,7 +953,7 @@ int varlink_idl_parse( assert(n_symbols == 0); if (!token) - return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column); + return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column); r = varlink_interface_realloc(&interface, n_symbols); if (r < 0) @@ -982,7 +985,7 @@ int varlink_idl_parse( state = STATE_ERROR; allowed_chars = VALID_CHARS_IDENTIFIER; } else - return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s'.", *line, *column, token); + return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s'.", *line, *column, token); break; @@ -991,7 +994,7 @@ int varlink_idl_parse( n_fields = 0; if (!token) - return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column); + return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column); r = varlink_symbol_realloc(&symbol, n_fields); if (r < 0) @@ -1012,10 +1015,10 @@ int varlink_idl_parse( assert(symbol); if (!token) - return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column); + return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column); if (!streq(token, "->")) - return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s'.", *line, *column, token); + return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s'.", *line, *column, token); r = varlink_idl_subparse_struct_or_enum(&text, line, column, &symbol, &n_fields, VARLINK_OUTPUT, 0); if (r < 0) @@ -1036,7 +1039,7 @@ int varlink_idl_parse( n_fields = 0; if (!token) - return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column); + return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column); r = varlink_symbol_realloc(&symbol, n_fields); if (r < 0) @@ -1064,7 +1067,7 @@ int varlink_idl_parse( n_fields = 0; if (!token) - return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column); + return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column); r = varlink_symbol_realloc(&symbol, n_fields); if (r < 0) @@ -1213,48 +1216,48 @@ static int varlink_idl_field_consistent( symbol_name = symbol->name ?: "<anonymous>"; if (field->field_type <= 0 || field->field_type >= _VARLINK_FIELD_TYPE_MAX) - return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Field type for '%s' in symbol '%s' is not valid, refusing.", field->name, symbol_name); + return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Field type for '%s' in symbol '%s' is not valid, refusing.", field->name, symbol_name); if (field->field_type == VARLINK_ENUM_VALUE) { if (symbol->symbol_type != VARLINK_ENUM_TYPE) - return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Enum field type for '%s' in non-enum symbol '%s', refusing.", field->name, symbol_name); + return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Enum field type for '%s' in non-enum symbol '%s', refusing.", field->name, symbol_name); if (field->field_flags != 0) - return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Enum field '%s' in symbol '%s' has non-zero flags set, refusing.", field->name, symbol_name); + return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Enum field '%s' in symbol '%s' has non-zero flags set, refusing.", field->name, symbol_name); } else { if (symbol->symbol_type == VARLINK_ENUM_TYPE) - return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Non-enum field type for '%s' in enum symbol '%s', refusing.", field->name, symbol_name); + return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Non-enum field type for '%s' in enum symbol '%s', refusing.", field->name, symbol_name); if (!IN_SET(field->field_flags & ~VARLINK_NULLABLE, 0, VARLINK_ARRAY, VARLINK_MAP)) - return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Flags of field '%s' in symbol '%s' is invalid, refusing.", field->name, symbol_name); + return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Flags of field '%s' in symbol '%s' is invalid, refusing.", field->name, symbol_name); } if (symbol->symbol_type != VARLINK_METHOD) { if (field->field_direction != VARLINK_REGULAR) - return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Direction of '%s' in non-method symbol '%s' not regular, refusing.", field->name, symbol_name); + return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Direction of '%s' in non-method symbol '%s' not regular, refusing.", field->name, symbol_name); } else { if (!IN_SET(field->field_direction, VARLINK_INPUT, VARLINK_OUTPUT)) - return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Direction of '%s' in method symbol '%s' is not input or output, refusing.", field->name, symbol_name); + return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Direction of '%s' in method symbol '%s' is not input or output, refusing.", field->name, symbol_name); } if (field->symbol) { if (!IN_SET(field->field_type, VARLINK_STRUCT, VARLINK_ENUM, VARLINK_NAMED_TYPE)) - return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Target symbol for field '%s' in symbol '%s' defined for elemental field, refusing.", field->name, symbol_name); + return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Target symbol for field '%s' in symbol '%s' defined for elemental field, refusing.", field->name, symbol_name); if (field->field_type == VARLINK_NAMED_TYPE) { const VarlinkSymbol *found; if (!field->symbol->name || !field->named_type || !streq(field->symbol->name, field->named_type)) - return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Resolved symbol name and named type of field '%s' in symbol '%s' do do not match, refusing.", field->name, symbol_name); + return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Resolved symbol name and named type of field '%s' in symbol '%s' do not match, refusing.", field->name, symbol_name); /* If this is a named type, then check if it's properly part of the interface */ found = varlink_idl_find_symbol(interface, _VARLINK_SYMBOL_TYPE_INVALID, field->symbol->name); if (!found) - return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Resolved symbol of named type of field '%s' in symbol '%s' is not part of the interface, refusing.", field->name, symbol_name); + return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Resolved symbol of named type of field '%s' in symbol '%s' is not part of the interface, refusing.", field->name, symbol_name); if (!IN_SET(found->symbol_type, VARLINK_ENUM_TYPE, VARLINK_STRUCT_TYPE)) - return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Resolved symbol of named type of field '%s' in symbol '%s' is not a type, refusing.", field->name, symbol_name); + return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Resolved symbol of named type of field '%s' in symbol '%s' is not a type, refusing.", field->name, symbol_name); } else { /* If this is an anonymous type, then we recursively check if it's consistent, since * it's not part of the interface, and hence we won't validate it from there. */ @@ -1266,18 +1269,18 @@ static int varlink_idl_field_consistent( } else { if (IN_SET(field->field_type, VARLINK_STRUCT, VARLINK_ENUM, VARLINK_NAMED_TYPE)) - return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "No target symbol for field '%s' in symbol '%s' defined for elemental field, refusing.", field->name, symbol_name); + return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "No target symbol for field '%s' in symbol '%s' defined for elemental field, refusing.", field->name, symbol_name); if (field->named_type) - return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Unresolved symbol in field '%s' in symbol '%s', refusing.", field->name, symbol_name); + return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Unresolved symbol in field '%s' in symbol '%s', refusing.", field->name, symbol_name); } if (field->named_type) { if (field->field_type != VARLINK_NAMED_TYPE) - return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Named type set for field '%s' in symbol '%s' but not a named type field, refusing.", field->name, symbol_name); + return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Named type set for field '%s' in symbol '%s' but not a named type field, refusing.", field->name, symbol_name); } else { if (field->field_type == VARLINK_NAMED_TYPE) - return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "No named type set for field '%s' in symbol '%s' but field is a named type field, refusing.", field->name, symbol_name); + return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "No named type set for field '%s' in symbol '%s' but field is a named type field, refusing.", field->name, symbol_name); } return 0; @@ -1304,19 +1307,19 @@ static int varlink_idl_symbol_consistent( symbol_name = symbol->name ?: "<anonymous>"; if (symbol->symbol_type < 0 || symbol->symbol_type >= _VARLINK_SYMBOL_TYPE_MAX) - return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Symbol type for '%s' is not valid, refusing.", symbol_name); + return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Symbol type for '%s' is not valid, refusing.", symbol_name); if (IN_SET(symbol->symbol_type, VARLINK_STRUCT_TYPE, VARLINK_ENUM_TYPE) && varlink_symbol_is_empty(symbol)) - return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Symbol '%s' is empty, refusing.", symbol_name); + return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Symbol '%s' is empty, refusing.", symbol_name); for (const VarlinkField *field = symbol->fields; field->field_type != _VARLINK_FIELD_TYPE_END_MARKER; field++) { Set **name_set = field->field_direction == VARLINK_OUTPUT ? &output_set : &input_set; /* for the method case we need two separate sets, otherwise we use the same */ if (!varlink_idl_field_name_is_valid(field->name)) - return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Field name '%s' in symbol '%s' not valid, refusing.", field->name, symbol_name); + return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Field name '%s' in symbol '%s' not valid, refusing.", field->name, symbol_name); if (set_contains(*name_set, field->name)) - return log_full_errno(level, SYNTHETIC_ERRNO(ENOTUNIQ), "Field '%s' defined twice in symbol '%s', refusing.", field->name, symbol_name); + return varlink_idl_log_full(level, SYNTHETIC_ERRNO(ENOTUNIQ), "Field '%s' defined twice in symbol '%s', refusing.", field->name, symbol_name); if (set_ensure_put(name_set, &string_hash_ops, field->name) < 0) return log_oom(); @@ -1336,15 +1339,15 @@ int varlink_idl_consistent(const VarlinkInterface *interface, int level) { assert(interface); if (!varlink_idl_interface_name_is_valid(interface->name)) - return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Interface name '%s' is not valid, refusing.", interface->name); + return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Interface name '%s' is not valid, refusing.", interface->name); for (const VarlinkSymbol *const *symbol = interface->symbols; *symbol; symbol++) { if (!varlink_idl_symbol_name_is_valid((*symbol)->name)) - return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Symbol name '%s' is not valid, refusing.", strempty((*symbol)->name)); + return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Symbol name '%s' is not valid, refusing.", strempty((*symbol)->name)); if (set_contains(name_set, (*symbol)->name)) - return log_full_errno(level, SYNTHETIC_ERRNO(ENOTUNIQ), "Symbol '%s' defined twice in interface, refusing.", (*symbol)->name); + return varlink_idl_log_full(level, SYNTHETIC_ERRNO(ENOTUNIQ), "Symbol '%s' defined twice in interface, refusing.", (*symbol)->name); if (set_ensure_put(&name_set, &string_hash_ops, (*symbol)->name) < 0) return log_oom(); @@ -1371,31 +1374,32 @@ static int varlink_idl_validate_field_element_type(const VarlinkField *field, Js case VARLINK_BOOL: if (!json_variant_is_boolean(v)) - return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be a bool, but it is not, refusing.", strna(field->name)); + return varlink_idl_log(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be a bool, but it is not, refusing.", strna(field->name)); break; case VARLINK_INT: - if (!json_variant_is_integer(v) && !json_variant_is_unsigned(v)) - return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be an int, but it is not, refusing.", strna(field->name)); + /* Allow strings here too, since integers with > 53 bits are often passed in as strings */ + if (!json_variant_is_integer(v) && !json_variant_is_unsigned(v) && !json_variant_is_string(v)) + return varlink_idl_log(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be an int, but it is not, refusing.", strna(field->name)); break; case VARLINK_FLOAT: if (!json_variant_is_number(v)) - return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be a float, but it is not, refusing.", strna(field->name)); + return varlink_idl_log(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be a float, but it is not, refusing.", strna(field->name)); break; case VARLINK_STRING: if (!json_variant_is_string(v)) - return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be a string, but it is not, refusing.", strna(field->name)); + return varlink_idl_log(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be a string, but it is not, refusing.", strna(field->name)); break; case VARLINK_OBJECT: if (!json_variant_is_object(v)) - return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be an object, but it is not, refusing.", strna(field->name)); + return varlink_idl_log(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be an object, but it is not, refusing.", strna(field->name)); break; @@ -1414,13 +1418,13 @@ static int varlink_idl_validate_field(const VarlinkField *field, JsonVariant *v) if (!v || json_variant_is_null(v)) { if (!FLAGS_SET(field->field_flags, VARLINK_NULLABLE)) - return log_debug_errno(SYNTHETIC_ERRNO(ENOANO), "Mandatory field '%s' is null or missing on object, refusing.", strna(field->name)); + return varlink_idl_log(SYNTHETIC_ERRNO(ENOANO), "Mandatory field '%s' is null or missing on object, refusing.", strna(field->name)); } else if (FLAGS_SET(field->field_flags, VARLINK_ARRAY)) { JsonVariant *i; if (!json_variant_is_array(v)) - return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be an array, but it is not, refusing.", strna(field->name)); + return varlink_idl_log(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be an array, but it is not, refusing.", strna(field->name)); JSON_VARIANT_ARRAY_FOREACH(i, v) { r = varlink_idl_validate_field_element_type(field, i); @@ -1433,7 +1437,7 @@ static int varlink_idl_validate_field(const VarlinkField *field, JsonVariant *v) JsonVariant *e; if (!json_variant_is_object(v)) - return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be an object, but it is not, refusing.", strna(field->name)); + return varlink_idl_log(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be an object, but it is not, refusing.", strna(field->name)); JSON_VARIANT_OBJECT_FOREACH(k, e, v) { r = varlink_idl_validate_field_element_type(field, e); @@ -1458,7 +1462,7 @@ static int varlink_idl_validate_symbol(const VarlinkSymbol *symbol, JsonVariant if (!v) { if (bad_field) *bad_field = NULL; - return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Null object passed, refusing."); + return varlink_idl_log(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Null object passed, refusing."); } switch (symbol->symbol_type) { @@ -1470,7 +1474,7 @@ static int varlink_idl_validate_symbol(const VarlinkSymbol *symbol, JsonVariant if (!json_variant_is_string(v)) { if (bad_field) *bad_field = symbol->name; - return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Passed non-string to enum field '%s', refusing.", strna(symbol->name)); + return varlink_idl_log(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Passed non-string to enum field '%s', refusing.", strna(symbol->name)); } assert_se(s = json_variant_string(v)); @@ -1488,7 +1492,7 @@ static int varlink_idl_validate_symbol(const VarlinkSymbol *symbol, JsonVariant if (!found) { if (bad_field) *bad_field = s; - return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Passed unrecognized string '%s' to enum field '%s', refusing.", s, strna(symbol->name)); + return varlink_idl_log(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Passed unrecognized string '%s' to enum field '%s', refusing.", s, strna(symbol->name)); } break; @@ -1500,7 +1504,7 @@ static int varlink_idl_validate_symbol(const VarlinkSymbol *symbol, JsonVariant if (!json_variant_is_object(v)) { if (bad_field) *bad_field = symbol->name; - return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Passed non-object to field '%s', refusing.", strna(symbol->name)); + return varlink_idl_log(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Passed non-object to field '%s', refusing.", strna(symbol->name)); } for (const VarlinkField *field = symbol->fields; field->field_type != _VARLINK_FIELD_TYPE_END_MARKER; field++) { @@ -1522,7 +1526,7 @@ static int varlink_idl_validate_symbol(const VarlinkSymbol *symbol, JsonVariant if (!varlink_idl_find_field(symbol, name)) { if (bad_field) *bad_field = name; - return log_debug_errno(SYNTHETIC_ERRNO(EBUSY), "Field '%s' not defined for object, refusing.", name); + return varlink_idl_log(SYNTHETIC_ERRNO(EBUSY), "Field '%s' not defined for object, refusing.", name); } } diff --git a/src/shared/varlink-io.systemd.BootControl.c b/src/shared/varlink-io.systemd.BootControl.c new file mode 100644 index 0000000..500e072 --- /dev/null +++ b/src/shared/varlink-io.systemd.BootControl.c @@ -0,0 +1,59 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "varlink-io.systemd.BootControl.h" + +static VARLINK_DEFINE_ENUM_TYPE( + BootEntryType, + VARLINK_DEFINE_ENUM_VALUE(type1), + VARLINK_DEFINE_ENUM_VALUE(type2), + VARLINK_DEFINE_ENUM_VALUE(loader), + VARLINK_DEFINE_ENUM_VALUE(auto)); + +static VARLINK_DEFINE_STRUCT_TYPE( + BootEntry, + VARLINK_DEFINE_FIELD_BY_TYPE(type, BootEntryType, 0), + VARLINK_DEFINE_FIELD(id, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(path, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(root, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(title, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(showTitle, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(sortKey, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(version, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(machineId, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(architecture, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(options, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(linux, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(efi, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(initrd, VARLINK_STRING, VARLINK_NULLABLE|VARLINK_ARRAY), + VARLINK_DEFINE_FIELD(devicetree, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(devicetreeOverlay, VARLINK_STRING, VARLINK_NULLABLE|VARLINK_ARRAY), + VARLINK_DEFINE_FIELD(isReported, VARLINK_BOOL, 0), + VARLINK_DEFINE_FIELD(triesLeft, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(triesDone, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(isDefault, VARLINK_BOOL, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(isSelected, VARLINK_BOOL, VARLINK_NULLABLE)); + +static VARLINK_DEFINE_METHOD( + ListBootEntries, + VARLINK_DEFINE_OUTPUT_BY_TYPE(entry, BootEntry, VARLINK_NULLABLE)); + +static VARLINK_DEFINE_METHOD( + SetRebootToFirmware, + VARLINK_DEFINE_INPUT(state, VARLINK_BOOL, 0)); + +static VARLINK_DEFINE_METHOD( + GetRebootToFirmware, + VARLINK_DEFINE_OUTPUT(state, VARLINK_BOOL, 0)); + +static VARLINK_DEFINE_ERROR( + RebootToFirmwareNotSupported); + +VARLINK_DEFINE_INTERFACE( + io_systemd_BootControl, + "io.systemd.BootControl", + &vl_type_BootEntryType, + &vl_type_BootEntry, + &vl_method_ListBootEntries, + &vl_method_SetRebootToFirmware, + &vl_method_GetRebootToFirmware, + &vl_error_RebootToFirmwareNotSupported); diff --git a/src/shared/varlink-io.systemd.BootControl.h b/src/shared/varlink-io.systemd.BootControl.h new file mode 100644 index 0000000..fa72b70 --- /dev/null +++ b/src/shared/varlink-io.systemd.BootControl.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "varlink-idl.h" + +extern const VarlinkInterface vl_interface_io_systemd_BootControl; diff --git a/src/shared/varlink-io.systemd.Credentials.c b/src/shared/varlink-io.systemd.Credentials.c new file mode 100644 index 0000000..03db0b3 --- /dev/null +++ b/src/shared/varlink-io.systemd.Credentials.c @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "varlink-io.systemd.Credentials.h" + +static VARLINK_DEFINE_METHOD( + Encrypt, + VARLINK_DEFINE_INPUT(name, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(text, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(data, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(timestamp, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(notAfter, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(scope, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(uid, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(allowInteractiveAuthentication, VARLINK_BOOL, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(blob, VARLINK_STRING, 0)); + +static VARLINK_DEFINE_METHOD( + Decrypt, + VARLINK_DEFINE_INPUT(name, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(blob, VARLINK_STRING, 0), + VARLINK_DEFINE_INPUT(timestamp, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(scope, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(uid, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(allowInteractiveAuthentication, VARLINK_BOOL, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(data, VARLINK_STRING, 0)); + +static VARLINK_DEFINE_ERROR(BadFormat); +static VARLINK_DEFINE_ERROR(NameMismatch); +static VARLINK_DEFINE_ERROR(TimeMismatch); +static VARLINK_DEFINE_ERROR(NoSuchUser); +static VARLINK_DEFINE_ERROR(BadScope); + +VARLINK_DEFINE_INTERFACE( + io_systemd_Credentials, + "io.systemd.Credentials", + &vl_method_Encrypt, + &vl_method_Decrypt, + &vl_error_BadFormat, + &vl_error_NameMismatch, + &vl_error_TimeMismatch, + &vl_error_NoSuchUser, + &vl_error_BadScope); diff --git a/src/shared/varlink-io.systemd.Credentials.h b/src/shared/varlink-io.systemd.Credentials.h new file mode 100644 index 0000000..c0ecc3d --- /dev/null +++ b/src/shared/varlink-io.systemd.Credentials.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "varlink-idl.h" + +extern const VarlinkInterface vl_interface_io_systemd_Credentials; diff --git a/src/shared/varlink-io.systemd.Hostname.c b/src/shared/varlink-io.systemd.Hostname.c new file mode 100644 index 0000000..a6c6aec --- /dev/null +++ b/src/shared/varlink-io.systemd.Hostname.c @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "varlink-io.systemd.Credentials.h" + +static VARLINK_DEFINE_METHOD( + Describe, + VARLINK_DEFINE_OUTPUT(Hostname, VARLINK_STRING, 0), + VARLINK_DEFINE_OUTPUT(StaticHostname, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(PrettyHostname, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(DefaultHostname, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(HostnameSource, VARLINK_STRING, 0), + VARLINK_DEFINE_OUTPUT(IconName, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(Chassis, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(Deployment, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(Location, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(KernelName, VARLINK_STRING, 0), + VARLINK_DEFINE_OUTPUT(KernelRelease, VARLINK_STRING, 0), + VARLINK_DEFINE_OUTPUT(KernelVersion, VARLINK_STRING, 0), + VARLINK_DEFINE_OUTPUT(OperatingSystemPrettyName, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(OperatingSystemCPEName, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(OperatingSystemHomeURL, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(OperatingSystemSupportEnd, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(OperatingSystemReleaseData, VARLINK_STRING, VARLINK_NULLABLE|VARLINK_ARRAY), + VARLINK_DEFINE_OUTPUT(MachineInformationData, VARLINK_STRING, VARLINK_NULLABLE|VARLINK_ARRAY), + VARLINK_DEFINE_OUTPUT(HardwareVendor, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(HardwareModel, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(HardwareSerial, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(FirmwareVersion, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(FirmwareVendor, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(FirmwareDate, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(MachineID, VARLINK_STRING, 0), + VARLINK_DEFINE_OUTPUT(BootID, VARLINK_STRING, 0), + VARLINK_DEFINE_OUTPUT(ProductUUID, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(VSockCID, VARLINK_INT, VARLINK_NULLABLE)); + +VARLINK_DEFINE_INTERFACE( + io_systemd_Hostname, + "io.systemd.Hostname", + &vl_method_Describe); diff --git a/src/shared/varlink-io.systemd.Hostname.h b/src/shared/varlink-io.systemd.Hostname.h new file mode 100644 index 0000000..29bb20e --- /dev/null +++ b/src/shared/varlink-io.systemd.Hostname.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "varlink-idl.h" + +extern const VarlinkInterface vl_interface_io_systemd_Hostname; diff --git a/src/shared/varlink-io.systemd.Machine.c b/src/shared/varlink-io.systemd.Machine.c new file mode 100644 index 0000000..2d25a34 --- /dev/null +++ b/src/shared/varlink-io.systemd.Machine.c @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "varlink-idl.h" +#include "varlink-io.systemd.Machine.h" + +static VARLINK_DEFINE_METHOD( + Register, + VARLINK_DEFINE_INPUT(name, VARLINK_STRING, 0), + VARLINK_DEFINE_INPUT(id, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(service, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(class, VARLINK_STRING, 0), + VARLINK_DEFINE_INPUT(leader, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(rootDirectory, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(ifIndices, VARLINK_INT, VARLINK_ARRAY|VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(vSockCid, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(sshAddress, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(sshPrivateKeyPath, VARLINK_STRING, VARLINK_NULLABLE)); + +static VARLINK_DEFINE_ERROR(MachineExists); + +VARLINK_DEFINE_INTERFACE( + io_systemd_Machine, + "io.systemd.Machine", + &vl_method_Register, + &vl_error_MachineExists); diff --git a/src/shared/varlink-io.systemd.Machine.h b/src/shared/varlink-io.systemd.Machine.h new file mode 100644 index 0000000..c9fc85f --- /dev/null +++ b/src/shared/varlink-io.systemd.Machine.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "varlink-idl.h" + +extern const VarlinkInterface vl_interface_io_systemd_Machine; diff --git a/src/shared/varlink-io.systemd.MountFileSystem.c b/src/shared/varlink-io.systemd.MountFileSystem.c new file mode 100644 index 0000000..4a33578 --- /dev/null +++ b/src/shared/varlink-io.systemd.MountFileSystem.c @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "varlink-io.systemd.MountFileSystem.h" + +static VARLINK_DEFINE_ENUM_TYPE( + PartitionDesignator, + VARLINK_DEFINE_ENUM_VALUE(root), + VARLINK_DEFINE_ENUM_VALUE(usr), + VARLINK_DEFINE_ENUM_VALUE(home), + VARLINK_DEFINE_ENUM_VALUE(srv), + VARLINK_DEFINE_ENUM_VALUE(esp), + VARLINK_DEFINE_ENUM_VALUE(xbootldr), + VARLINK_DEFINE_ENUM_VALUE(swap), + VARLINK_DEFINE_ENUM_VALUE(root_verity), + VARLINK_DEFINE_ENUM_VALUE(usr_verity), + VARLINK_DEFINE_ENUM_VALUE(root_verity_sig), + VARLINK_DEFINE_ENUM_VALUE(usr_verity_sig), + VARLINK_DEFINE_ENUM_VALUE(tmp), + VARLINK_DEFINE_ENUM_VALUE(var)); + +static VARLINK_DEFINE_STRUCT_TYPE( + PartitionInfo, + VARLINK_DEFINE_FIELD(designator, VARLINK_STRING, 0), + VARLINK_DEFINE_FIELD(writable, VARLINK_BOOL, 0), + VARLINK_DEFINE_FIELD(growFileSystem, VARLINK_BOOL, 0), + VARLINK_DEFINE_FIELD(partitionNumber, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(architecture, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(partitionUuid, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(fileSystemType, VARLINK_STRING, 0), + VARLINK_DEFINE_FIELD(partitionLabel, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(size, VARLINK_INT, 0), + VARLINK_DEFINE_FIELD(offset, VARLINK_INT, 0), + VARLINK_DEFINE_FIELD(mountFileDescriptor, VARLINK_INT, 0)); + +static VARLINK_DEFINE_METHOD( + MountImage, + VARLINK_DEFINE_INPUT(imageFileDescriptor, VARLINK_INT, 0), + VARLINK_DEFINE_INPUT(userNamespaceFileDescriptor, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(readOnly, VARLINK_BOOL, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(growFileSystems, VARLINK_BOOL, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(password, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(imagePolicy, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(allowInteractiveAuthentication, VARLINK_BOOL, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT_BY_TYPE(partitions, PartitionInfo, VARLINK_ARRAY), + VARLINK_DEFINE_OUTPUT(imagePolicy, VARLINK_STRING, 0), + VARLINK_DEFINE_OUTPUT(imageSize, VARLINK_INT, 0), + VARLINK_DEFINE_OUTPUT(sectorSize, VARLINK_INT, 0), + VARLINK_DEFINE_OUTPUT(imageName, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(imageUuid, VARLINK_STRING, VARLINK_NULLABLE)); + +static VARLINK_DEFINE_ERROR(IncompatibleImage); +static VARLINK_DEFINE_ERROR(MultipleRootPartitionsFound); +static VARLINK_DEFINE_ERROR(RootPartitionNotFound); +static VARLINK_DEFINE_ERROR(DeniedByImagePolicy); +static VARLINK_DEFINE_ERROR(KeyNotFound); +static VARLINK_DEFINE_ERROR(VerityFailure); + +VARLINK_DEFINE_INTERFACE( + io_systemd_MountFileSystem, + "io.systemd.MountFileSystem", + &vl_type_PartitionDesignator, + &vl_type_PartitionInfo, + &vl_method_MountImage, + &vl_error_IncompatibleImage, + &vl_error_MultipleRootPartitionsFound, + &vl_error_RootPartitionNotFound, + &vl_error_DeniedByImagePolicy, + &vl_error_KeyNotFound, + &vl_error_VerityFailure); diff --git a/src/shared/varlink-io.systemd.MountFileSystem.h b/src/shared/varlink-io.systemd.MountFileSystem.h new file mode 100644 index 0000000..dc75957 --- /dev/null +++ b/src/shared/varlink-io.systemd.MountFileSystem.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "varlink-idl.h" + +extern const VarlinkInterface vl_interface_io_systemd_MountFileSystem; diff --git a/src/shared/varlink-io.systemd.NamespaceResource.c b/src/shared/varlink-io.systemd.NamespaceResource.c new file mode 100644 index 0000000..e98c6c6 --- /dev/null +++ b/src/shared/varlink-io.systemd.NamespaceResource.c @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "varlink-io.systemd.NamespaceResource.h" + +static VARLINK_DEFINE_METHOD( + AllocateUserRange, + VARLINK_DEFINE_INPUT(name, VARLINK_STRING, 0), + VARLINK_DEFINE_INPUT(size, VARLINK_INT, 0), + VARLINK_DEFINE_INPUT(target, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(userNamespaceFileDescriptor, VARLINK_INT, 0)); + +static VARLINK_DEFINE_METHOD( + RegisterUserNamespace, + VARLINK_DEFINE_INPUT(name, VARLINK_STRING, 0), + VARLINK_DEFINE_INPUT(userNamespaceFileDescriptor, VARLINK_INT, 0)); + +static VARLINK_DEFINE_METHOD( + AddMountToUserNamespace, + VARLINK_DEFINE_INPUT(userNamespaceFileDescriptor, VARLINK_INT, 0), + VARLINK_DEFINE_INPUT(mountFileDescriptor, VARLINK_INT, 0)); + +static VARLINK_DEFINE_METHOD( + AddControlGroupToUserNamespace, + VARLINK_DEFINE_INPUT(userNamespaceFileDescriptor, VARLINK_INT, 0), + VARLINK_DEFINE_INPUT(controlGroupFileDescriptor, VARLINK_INT, 0)); + +static VARLINK_DEFINE_METHOD( + AddNetworkToUserNamespace, + VARLINK_DEFINE_INPUT(userNamespaceFileDescriptor, VARLINK_INT, 0), + VARLINK_DEFINE_INPUT(networkNamespaceFileDescriptor, VARLINK_INT, 0), + VARLINK_DEFINE_INPUT(namespaceInterfaceName, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(mode, VARLINK_STRING, 0), + VARLINK_DEFINE_OUTPUT(hostInterfaceName, VARLINK_STRING, 0), + VARLINK_DEFINE_OUTPUT(namespaceInterfaceName, VARLINK_STRING, 0)); + +static VARLINK_DEFINE_ERROR(UserNamespaceInterfaceNotSupported); +static VARLINK_DEFINE_ERROR(NameExists); +static VARLINK_DEFINE_ERROR(UserNamespaceExists); +static VARLINK_DEFINE_ERROR(DynamicRangeUnavailable); +static VARLINK_DEFINE_ERROR(NoDynamicRange); +static VARLINK_DEFINE_ERROR(UserNamespaceNotRegistered); +static VARLINK_DEFINE_ERROR(UserNamespaceWithoutUserRange); +static VARLINK_DEFINE_ERROR(TooManyControlGroups); +static VARLINK_DEFINE_ERROR(ControlGroupAlreadyAdded); + +VARLINK_DEFINE_INTERFACE( + io_systemd_NamespaceResource, + "io.systemd.NamespaceResource", + &vl_method_AllocateUserRange, + &vl_method_RegisterUserNamespace, + &vl_method_AddMountToUserNamespace, + &vl_method_AddControlGroupToUserNamespace, + &vl_method_AddNetworkToUserNamespace, + &vl_error_UserNamespaceInterfaceNotSupported, + &vl_error_NameExists, + &vl_error_UserNamespaceExists, + &vl_error_DynamicRangeUnavailable, + &vl_error_NoDynamicRange, + &vl_error_UserNamespaceNotRegistered, + &vl_error_UserNamespaceWithoutUserRange, + &vl_error_TooManyControlGroups, + &vl_error_ControlGroupAlreadyAdded); diff --git a/src/shared/varlink-io.systemd.NamespaceResource.h b/src/shared/varlink-io.systemd.NamespaceResource.h new file mode 100644 index 0000000..443cb97 --- /dev/null +++ b/src/shared/varlink-io.systemd.NamespaceResource.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "varlink-idl.h" + +extern const VarlinkInterface vl_interface_io_systemd_NamespaceResource; diff --git a/src/shared/varlink-io.systemd.Network.c b/src/shared/varlink-io.systemd.Network.c new file mode 100644 index 0000000..394cc33 --- /dev/null +++ b/src/shared/varlink-io.systemd.Network.c @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "varlink-io.systemd.Network.h" + +static VARLINK_DEFINE_METHOD( + GetStates, + VARLINK_DEFINE_OUTPUT(AddressState, VARLINK_STRING, 0), + VARLINK_DEFINE_OUTPUT(IPv4AddressState, VARLINK_STRING, 0), + VARLINK_DEFINE_OUTPUT(IPv6AddressState, VARLINK_STRING, 0), + VARLINK_DEFINE_OUTPUT(CarrierState, VARLINK_STRING, 0), + VARLINK_DEFINE_OUTPUT(OnlineState, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(OperationalState, VARLINK_STRING, 0)); + +static VARLINK_DEFINE_METHOD( + GetNamespaceId, + VARLINK_DEFINE_OUTPUT(NamespaceId, VARLINK_INT, 0), + VARLINK_DEFINE_OUTPUT(NamespaceNSID, VARLINK_INT, VARLINK_NULLABLE)); + +static VARLINK_DEFINE_STRUCT_TYPE( + LLDPNeighbor, + VARLINK_DEFINE_FIELD(ChassisID, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(RawChassisID, VARLINK_INT, VARLINK_ARRAY), + VARLINK_DEFINE_FIELD(PortID, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(RawPortID, VARLINK_INT, VARLINK_ARRAY), + VARLINK_DEFINE_FIELD(PortDescription, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(SystemName, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(SystemDescription, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(EnabledCapabilities, VARLINK_INT, VARLINK_NULLABLE)); + +static VARLINK_DEFINE_STRUCT_TYPE( + LLDPNeighborsByInterface, + VARLINK_DEFINE_FIELD(InterfaceIndex, VARLINK_INT, 0), + VARLINK_DEFINE_FIELD(InterfaceName, VARLINK_STRING, 0), + VARLINK_DEFINE_FIELD(InterfaceAlternativeNames, VARLINK_STRING, VARLINK_ARRAY|VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD_BY_TYPE(Neighbors, LLDPNeighbor, VARLINK_ARRAY)); + +static VARLINK_DEFINE_METHOD( + GetLLDPNeighbors, + VARLINK_DEFINE_INPUT(InterfaceIndex, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(InterfaceName, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT_BY_TYPE(Neighbors, LLDPNeighborsByInterface, VARLINK_ARRAY)); + +static VARLINK_DEFINE_METHOD( + SetPersistentStorage, + VARLINK_DEFINE_INPUT(Ready, VARLINK_BOOL, 0)); + +static VARLINK_DEFINE_ERROR(StorageReadOnly); + +VARLINK_DEFINE_INTERFACE( + io_systemd_Network, + "io.systemd.Network", + &vl_method_GetStates, + &vl_method_GetNamespaceId, + &vl_method_GetLLDPNeighbors, + &vl_method_SetPersistentStorage, + &vl_type_LLDPNeighbor, + &vl_type_LLDPNeighborsByInterface, + &vl_error_StorageReadOnly); diff --git a/src/shared/varlink-io.systemd.Network.h b/src/shared/varlink-io.systemd.Network.h new file mode 100644 index 0000000..12d532a --- /dev/null +++ b/src/shared/varlink-io.systemd.Network.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "varlink-idl.h" + +extern const VarlinkInterface vl_interface_io_systemd_Network; diff --git a/src/shared/varlink-io.systemd.PCRLock.c b/src/shared/varlink-io.systemd.PCRLock.c new file mode 100644 index 0000000..22c6532 --- /dev/null +++ b/src/shared/varlink-io.systemd.PCRLock.c @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "varlink-io.systemd.PCRLock.h" + +static VARLINK_DEFINE_METHOD( + ReadEventLog, + VARLINK_DEFINE_OUTPUT(record, VARLINK_OBJECT, 0)); + +static VARLINK_DEFINE_METHOD( + MakePolicy, + VARLINK_DEFINE_INPUT(force, VARLINK_BOOL, VARLINK_NULLABLE)); + +static VARLINK_DEFINE_METHOD( + RemovePolicy); + +static VARLINK_DEFINE_ERROR( + NoChange); + +VARLINK_DEFINE_INTERFACE( + io_systemd_PCRLock, + "io.systemd.PCRLock", + &vl_method_ReadEventLog, + &vl_method_MakePolicy, + &vl_method_RemovePolicy, + &vl_error_NoChange); diff --git a/src/shared/varlink-io.systemd.PCRLock.h b/src/shared/varlink-io.systemd.PCRLock.h new file mode 100644 index 0000000..687f09e --- /dev/null +++ b/src/shared/varlink-io.systemd.PCRLock.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "varlink-idl.h" + +extern const VarlinkInterface vl_interface_io_systemd_PCRLock; diff --git a/src/shared/varlink-io.systemd.Resolve.Monitor.c b/src/shared/varlink-io.systemd.Resolve.Monitor.c index d95b613..ba928fa 100644 --- a/src/shared/varlink-io.systemd.Resolve.Monitor.c +++ b/src/shared/varlink-io.systemd.Resolve.Monitor.c @@ -2,96 +2,44 @@ #include "varlink-io.systemd.Resolve.Monitor.h" -VARLINK_DEFINE_STRUCT_TYPE( - ResourceKey, - VARLINK_DEFINE_FIELD(class, VARLINK_INT, 0), - VARLINK_DEFINE_FIELD(type, VARLINK_INT, 0), - VARLINK_DEFINE_FIELD(name, VARLINK_STRING, 0)); - -VARLINK_DEFINE_STRUCT_TYPE( - ResourceRecord, - VARLINK_DEFINE_FIELD_BY_TYPE(key, ResourceKey, 0), - VARLINK_DEFINE_FIELD(priority, VARLINK_INT, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(weight, VARLINK_INT, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(port, VARLINK_INT, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(name, VARLINK_STRING, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(cpu, VARLINK_STRING, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(os, VARLINK_STRING, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(items, VARLINK_STRING, VARLINK_NULLABLE|VARLINK_ARRAY), - VARLINK_DEFINE_FIELD(address, VARLINK_INT, VARLINK_NULLABLE|VARLINK_ARRAY), - VARLINK_DEFINE_FIELD(mname, VARLINK_STRING, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(rname, VARLINK_STRING, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(serial, VARLINK_INT, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(refresh, VARLINK_INT, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(expire, VARLINK_INT, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(minimum, VARLINK_INT, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(exchange, VARLINK_STRING, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(version, VARLINK_INT, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(size, VARLINK_INT, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(horiz_pre, VARLINK_INT, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(vert_pre, VARLINK_INT, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(latitude, VARLINK_INT, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(longitude, VARLINK_INT, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(altitude, VARLINK_INT, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(keyTag, VARLINK_INT, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(algorithm, VARLINK_INT, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(digestType, VARLINK_INT, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(digest, VARLINK_STRING, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(fptype, VARLINK_INT, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(fingerprint, VARLINK_STRING, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(flags, VARLINK_INT, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(protocol, VARLINK_INT, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(dnskey, VARLINK_STRING, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(signer, VARLINK_STRING, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(typeCovered, VARLINK_INT, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(labels, VARLINK_INT, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(originalTtl, VARLINK_INT, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(expiration, VARLINK_INT, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(inception, VARLINK_INT, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(signature, VARLINK_STRING, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(nextDomain, VARLINK_STRING, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(types, VARLINK_INT, VARLINK_NULLABLE|VARLINK_ARRAY), - VARLINK_DEFINE_FIELD(iterations, VARLINK_INT, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(salt, VARLINK_STRING, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(hash, VARLINK_STRING, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(certUsage, VARLINK_INT, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(selector, VARLINK_INT, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(matchingType, VARLINK_INT, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(data, VARLINK_STRING, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(tag, VARLINK_STRING, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(value, VARLINK_STRING, VARLINK_NULLABLE)); - -VARLINK_DEFINE_STRUCT_TYPE( +/* We want to reuse the ResourceKey and ResourceRecord structures from the io.systemd.Resolve interface, + * hence import them here. */ +#include "varlink-io.systemd.Resolve.h" + +static VARLINK_DEFINE_STRUCT_TYPE( ResourceRecordArray, VARLINK_DEFINE_FIELD_BY_TYPE(rr, ResourceRecord, VARLINK_NULLABLE), VARLINK_DEFINE_FIELD(raw, VARLINK_STRING, 0)); -VARLINK_DEFINE_STRUCT_TYPE( +static VARLINK_DEFINE_STRUCT_TYPE( Answer, VARLINK_DEFINE_FIELD_BY_TYPE(rr, ResourceRecord, VARLINK_NULLABLE), VARLINK_DEFINE_FIELD(raw, VARLINK_STRING, 0), VARLINK_DEFINE_FIELD(ifindex, VARLINK_INT, VARLINK_NULLABLE)); -VARLINK_DEFINE_METHOD( +static VARLINK_DEFINE_METHOD( SubscribeQueryResults, /* First reply */ VARLINK_DEFINE_OUTPUT(ready, VARLINK_BOOL, VARLINK_NULLABLE), /* Subsequent replies */ VARLINK_DEFINE_OUTPUT(state, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(result, VARLINK_STRING, VARLINK_NULLABLE), VARLINK_DEFINE_OUTPUT(rcode, VARLINK_INT, VARLINK_NULLABLE), VARLINK_DEFINE_OUTPUT(errno, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(extendedDNSErrorCode, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(extendedDNSErrorMessage, VARLINK_STRING, VARLINK_NULLABLE), VARLINK_DEFINE_OUTPUT_BY_TYPE(question, ResourceKey, VARLINK_NULLABLE|VARLINK_ARRAY), VARLINK_DEFINE_OUTPUT_BY_TYPE(collectedQuestions, ResourceKey, VARLINK_NULLABLE|VARLINK_ARRAY), VARLINK_DEFINE_OUTPUT_BY_TYPE(answer, Answer, VARLINK_NULLABLE|VARLINK_ARRAY)); -VARLINK_DEFINE_STRUCT_TYPE( +static VARLINK_DEFINE_STRUCT_TYPE( CacheEntry, VARLINK_DEFINE_FIELD_BY_TYPE(key, ResourceKey, 0), VARLINK_DEFINE_FIELD_BY_TYPE(rrs, ResourceRecordArray, VARLINK_NULLABLE|VARLINK_ARRAY), VARLINK_DEFINE_FIELD(type, VARLINK_STRING, VARLINK_NULLABLE), VARLINK_DEFINE_FIELD(until, VARLINK_INT, 0)); -VARLINK_DEFINE_STRUCT_TYPE( +static VARLINK_DEFINE_STRUCT_TYPE( ScopeCache, VARLINK_DEFINE_FIELD(protocol, VARLINK_STRING, 0), VARLINK_DEFINE_FIELD(family, VARLINK_INT, VARLINK_NULLABLE), @@ -99,11 +47,11 @@ VARLINK_DEFINE_STRUCT_TYPE( VARLINK_DEFINE_FIELD(ifname, VARLINK_STRING, VARLINK_NULLABLE), VARLINK_DEFINE_FIELD_BY_TYPE(cache, CacheEntry, VARLINK_ARRAY)); -VARLINK_DEFINE_METHOD( +static VARLINK_DEFINE_METHOD( DumpCache, VARLINK_DEFINE_OUTPUT_BY_TYPE(dump, ScopeCache, VARLINK_ARRAY)); -VARLINK_DEFINE_STRUCT_TYPE( +static VARLINK_DEFINE_STRUCT_TYPE( ServerState, VARLINK_DEFINE_FIELD(Server, VARLINK_STRING, 0), VARLINK_DEFINE_FIELD(Type, VARLINK_STRING, 0), @@ -122,11 +70,11 @@ VARLINK_DEFINE_STRUCT_TYPE( VARLINK_DEFINE_FIELD(PacketInvalid, VARLINK_BOOL, 0), VARLINK_DEFINE_FIELD(PacketDoOff, VARLINK_BOOL, 0)); -VARLINK_DEFINE_METHOD( +static VARLINK_DEFINE_METHOD( DumpServerState, VARLINK_DEFINE_OUTPUT_BY_TYPE(dump, ServerState, VARLINK_ARRAY)); -VARLINK_DEFINE_STRUCT_TYPE( +static VARLINK_DEFINE_STRUCT_TYPE( TransactionStatistics, VARLINK_DEFINE_FIELD(currentTransactions, VARLINK_INT, 0), VARLINK_DEFINE_FIELD(totalTransactions, VARLINK_INT, 0), @@ -135,26 +83,26 @@ VARLINK_DEFINE_STRUCT_TYPE( VARLINK_DEFINE_FIELD(totalFailedResponses, VARLINK_INT, 0), VARLINK_DEFINE_FIELD(totalFailedResponsesServedStale, VARLINK_INT, 0)); -VARLINK_DEFINE_STRUCT_TYPE( +static VARLINK_DEFINE_STRUCT_TYPE( CacheStatistics, VARLINK_DEFINE_FIELD(size, VARLINK_INT, 0), VARLINK_DEFINE_FIELD(hits, VARLINK_INT, 0), VARLINK_DEFINE_FIELD(misses, VARLINK_INT, 0)); -VARLINK_DEFINE_STRUCT_TYPE( +static VARLINK_DEFINE_STRUCT_TYPE( DnssecStatistics, VARLINK_DEFINE_FIELD(secure, VARLINK_INT, 0), VARLINK_DEFINE_FIELD(insecure, VARLINK_INT, 0), VARLINK_DEFINE_FIELD(bogus, VARLINK_INT, 0), VARLINK_DEFINE_FIELD(indeterminate, VARLINK_INT, 0)); -VARLINK_DEFINE_METHOD( +static VARLINK_DEFINE_METHOD( DumpStatistics, VARLINK_DEFINE_OUTPUT_BY_TYPE(transactions, TransactionStatistics, 0), VARLINK_DEFINE_OUTPUT_BY_TYPE(cache, CacheStatistics, 0), VARLINK_DEFINE_OUTPUT_BY_TYPE(dnssec, DnssecStatistics, 0)); -VARLINK_DEFINE_METHOD(ResetStatistics); +static VARLINK_DEFINE_METHOD(ResetStatistics); VARLINK_DEFINE_INTERFACE( io_systemd_Resolve_Monitor, diff --git a/src/shared/varlink-io.systemd.Resolve.c b/src/shared/varlink-io.systemd.Resolve.c index 0d8ad28..71f4dc8 100644 --- a/src/shared/varlink-io.systemd.Resolve.c +++ b/src/shared/varlink-io.systemd.Resolve.c @@ -2,6 +2,73 @@ #include "varlink-io.systemd.Resolve.h" +VARLINK_DEFINE_STRUCT_TYPE( + ResourceKey, + VARLINK_DEFINE_FIELD(class, VARLINK_INT, 0), + VARLINK_DEFINE_FIELD(type, VARLINK_INT, 0), + VARLINK_DEFINE_FIELD(name, VARLINK_STRING, 0)); + +VARLINK_DEFINE_STRUCT_TYPE( + ResourceRecord, + VARLINK_DEFINE_FIELD_BY_TYPE(key, ResourceKey, 0), + VARLINK_DEFINE_FIELD(priority, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(weight, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(port, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(name, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(cpu, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(os, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(items, VARLINK_STRING, VARLINK_NULLABLE|VARLINK_ARRAY), + VARLINK_DEFINE_FIELD(address, VARLINK_INT, VARLINK_NULLABLE|VARLINK_ARRAY), + VARLINK_DEFINE_FIELD(mname, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(rname, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(serial, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(refresh, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(expire, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(minimum, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(exchange, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(version, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(size, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(horiz_pre, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(vert_pre, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(latitude, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(longitude, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(altitude, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(keyTag, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(algorithm, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(digestType, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(digest, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(fptype, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(fingerprint, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(flags, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(protocol, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(dnskey, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(signer, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(typeCovered, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(labels, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(originalTtl, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(expiration, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(inception, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(signature, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(nextDomain, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(types, VARLINK_INT, VARLINK_NULLABLE|VARLINK_ARRAY), + VARLINK_DEFINE_FIELD(iterations, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(salt, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(hash, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(certUsage, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(selector, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(matchingType, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(data, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(tag, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(value, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(target, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(params, VARLINK_STRING, VARLINK_NULLABLE|VARLINK_ARRAY), + VARLINK_DEFINE_FIELD(order, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(preference, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(naptrFlags, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(services, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(regexp, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(replacement, VARLINK_STRING, VARLINK_NULLABLE)); + static VARLINK_DEFINE_STRUCT_TYPE( ResolvedAddress, VARLINK_DEFINE_FIELD(ifindex, VARLINK_INT, VARLINK_NULLABLE), @@ -32,6 +99,50 @@ static VARLINK_DEFINE_METHOD( VARLINK_DEFINE_OUTPUT_BY_TYPE(names, ResolvedName, VARLINK_ARRAY), VARLINK_DEFINE_OUTPUT(flags, VARLINK_INT, 0)); +static VARLINK_DEFINE_STRUCT_TYPE( + ResolvedService, + VARLINK_DEFINE_FIELD(priority, VARLINK_INT, 0), + VARLINK_DEFINE_FIELD(weight, VARLINK_INT, 0), + VARLINK_DEFINE_FIELD(port, VARLINK_INT, 0), + VARLINK_DEFINE_FIELD(hostname, VARLINK_STRING, 0), + VARLINK_DEFINE_FIELD(canonicalName, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD_BY_TYPE(addresses, ResolvedAddress, VARLINK_ARRAY|VARLINK_NULLABLE)); + +static VARLINK_DEFINE_STRUCT_TYPE( + ResolvedCanonical, + VARLINK_DEFINE_FIELD(name, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(type, VARLINK_STRING, 0), + VARLINK_DEFINE_FIELD(domain, VARLINK_STRING, 0)); + +static VARLINK_DEFINE_METHOD( + ResolveService, + VARLINK_DEFINE_INPUT(name, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(type, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(domain, VARLINK_STRING, 0), + VARLINK_DEFINE_INPUT(ifindex, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(family, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(flags, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT_BY_TYPE(services, ResolvedService, VARLINK_ARRAY), + VARLINK_DEFINE_OUTPUT(txt, VARLINK_STRING, VARLINK_ARRAY|VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT_BY_TYPE(canonical, ResolvedCanonical, 0), + VARLINK_DEFINE_OUTPUT(flags, VARLINK_INT, 0)); + +static VARLINK_DEFINE_STRUCT_TYPE( + ResolvedRecord, + VARLINK_DEFINE_FIELD(ifindex, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD_BY_TYPE(rr, ResourceRecord, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(raw, VARLINK_STRING, 0)); + +static VARLINK_DEFINE_METHOD( + ResolveRecord, + VARLINK_DEFINE_INPUT(ifindex, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(name, VARLINK_STRING, 0), + VARLINK_DEFINE_INPUT(class, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(type, VARLINK_INT, 0), + VARLINK_DEFINE_INPUT(flags, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT_BY_TYPE(rrs, ResolvedRecord, VARLINK_ARRAY), + VARLINK_DEFINE_OUTPUT(flags, VARLINK_INT, 0)); + static VARLINK_DEFINE_ERROR(NoNameServers); static VARLINK_DEFINE_ERROR(NoSuchResourceRecord); static VARLINK_DEFINE_ERROR(QueryTimedOut); @@ -40,7 +151,9 @@ static VARLINK_DEFINE_ERROR(InvalidReply); static VARLINK_DEFINE_ERROR(QueryAborted); static VARLINK_DEFINE_ERROR( DNSSECValidationFailed, - VARLINK_DEFINE_FIELD(result, VARLINK_STRING, 0)); + VARLINK_DEFINE_FIELD(result, VARLINK_STRING, 0), + VARLINK_DEFINE_FIELD(extendedDNSErrorCode, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(extendedDNSErrorMessage, VARLINK_STRING, VARLINK_NULLABLE)); static VARLINK_DEFINE_ERROR(NoTrustAnchor); static VARLINK_DEFINE_ERROR(ResourceRecordTypeUnsupported); static VARLINK_DEFINE_ERROR(NetworkDown); @@ -48,17 +161,29 @@ static VARLINK_DEFINE_ERROR(NoSource); static VARLINK_DEFINE_ERROR(StubLoop); static VARLINK_DEFINE_ERROR( DNSError, - VARLINK_DEFINE_FIELD(rcode, VARLINK_INT, 0)); + VARLINK_DEFINE_FIELD(rcode, VARLINK_INT, 0), + VARLINK_DEFINE_FIELD(extendedDNSErrorCode, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(extendedDNSErrorMessage, VARLINK_STRING, VARLINK_NULLABLE)); static VARLINK_DEFINE_ERROR(CNAMELoop); static VARLINK_DEFINE_ERROR(BadAddressSize); +static VARLINK_DEFINE_ERROR(ResourceRecordTypeInvalidForQuery); +static VARLINK_DEFINE_ERROR(ZoneTransfersNotPermitted); +static VARLINK_DEFINE_ERROR(ResourceRecordTypeObsolete); VARLINK_DEFINE_INTERFACE( io_systemd_Resolve, "io.systemd.Resolve", &vl_method_ResolveHostname, &vl_method_ResolveAddress, + &vl_method_ResolveService, + &vl_method_ResolveRecord, &vl_type_ResolvedAddress, &vl_type_ResolvedName, + &vl_type_ResolvedService, + &vl_type_ResolvedCanonical, + &vl_type_ResourceKey, + &vl_type_ResourceRecord, + &vl_type_ResolvedRecord, &vl_error_NoNameServers, &vl_error_NoSuchResourceRecord, &vl_error_QueryTimedOut, @@ -73,4 +198,7 @@ VARLINK_DEFINE_INTERFACE( &vl_error_StubLoop, &vl_error_DNSError, &vl_error_CNAMELoop, - &vl_error_BadAddressSize); + &vl_error_BadAddressSize, + &vl_error_ResourceRecordTypeInvalidForQuery, + &vl_error_ZoneTransfersNotPermitted, + &vl_error_ResourceRecordTypeObsolete); diff --git a/src/shared/varlink-io.systemd.Resolve.h b/src/shared/varlink-io.systemd.Resolve.h index 5c7ed39..48a9241 100644 --- a/src/shared/varlink-io.systemd.Resolve.h +++ b/src/shared/varlink-io.systemd.Resolve.h @@ -3,4 +3,7 @@ #include "varlink-idl.h" +extern const VarlinkSymbol vl_type_ResourceKey; +extern const VarlinkSymbol vl_type_ResourceRecord; + extern const VarlinkInterface vl_interface_io_systemd_Resolve; diff --git a/src/shared/varlink.c b/src/shared/varlink.c index 749b644..034e72b 100644 --- a/src/shared/varlink.c +++ b/src/shared/varlink.c @@ -37,6 +37,7 @@ #define VARLINK_DEFAULT_TIMEOUT_USEC (45U*USEC_PER_SEC) #define VARLINK_BUFFER_MAX (16U*1024U*1024U) #define VARLINK_READ_SIZE (64U*1024U) +#define VARLINK_COLLECT_MAX 1024U typedef enum VarlinkState { /* Client side states */ @@ -45,6 +46,8 @@ typedef enum VarlinkState { VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING, VARLINK_CALLED, + VARLINK_COLLECTING, + VARLINK_COLLECTING_REPLY, VARLINK_PROCESSING_REPLY, /* Server side states */ @@ -79,6 +82,8 @@ typedef enum VarlinkState { VARLINK_AWAITING_REPLY_MORE, \ VARLINK_CALLING, \ VARLINK_CALLED, \ + VARLINK_COLLECTING, \ + VARLINK_COLLECTING_REPLY, \ VARLINK_PROCESSING_REPLY, \ VARLINK_IDLE_SERVER, \ VARLINK_PROCESSING_METHOD, \ @@ -169,8 +174,11 @@ struct Varlink { VarlinkReply reply_callback; JsonVariant *current; + JsonVariant *current_collected; + VarlinkReplyFlags current_reply_flags; VarlinkSymbol *current_method; + int peer_pidfd; struct ucred ucred; bool ucred_acquired:1; @@ -183,6 +191,7 @@ struct Varlink { bool allow_fd_passing_output:1; bool output_buffer_sensitive:1; /* whether to erase the output buffer after writing it to the socket */ + bool input_sensitive:1; /* Whether incoming messages might be sensitive */ int af; /* address family if socket; AF_UNSPEC if not socket; negative if not known */ @@ -241,18 +250,14 @@ struct VarlinkServer { bool exit_on_idle; }; -typedef struct VarlinkCollectContext { - JsonVariant *parameters; - const char *error_id; - VarlinkReplyFlags flags; -} VarlinkCollectContext ; - static const char* const varlink_state_table[_VARLINK_STATE_MAX] = { [VARLINK_IDLE_CLIENT] = "idle-client", [VARLINK_AWAITING_REPLY] = "awaiting-reply", [VARLINK_AWAITING_REPLY_MORE] = "awaiting-reply-more", [VARLINK_CALLING] = "calling", [VARLINK_CALLED] = "called", + [VARLINK_COLLECTING] = "collecting", + [VARLINK_COLLECTING_REPLY] = "collecting-reply", [VARLINK_PROCESSING_REPLY] = "processing-reply", [VARLINK_IDLE_SERVER] = "idle-server", [VARLINK_PROCESSING_METHOD] = "processing-method", @@ -361,6 +366,8 @@ static int varlink_new(Varlink **ret) { .timeout = VARLINK_DEFAULT_TIMEOUT_USEC, .af = -1, + + .peer_pidfd = -EBADF, }; *ret = v; @@ -447,6 +454,10 @@ int varlink_connect_exec(Varlink **ret, const char *_command, char **_argv) { if (socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0, pair) < 0) return log_debug_errno(errno, "Failed to allocate AF_UNIX socket pair: %m"); + r = fd_nonblock(pair[1], false); + if (r < 0) + return log_debug_errno(r, "Failed to disable O_NONBLOCK for varlink socket: %m"); + r = safe_fork_full( "(sd-vlexec)", /* stdio_fds= */ NULL, @@ -504,39 +515,120 @@ int varlink_connect_exec(Varlink **ret, const char *_command, char **_argv) { return 0; } +static int varlink_connect_ssh(Varlink **ret, const char *where) { + _cleanup_close_pair_ int pair[2] = EBADF_PAIR; + _cleanup_(sigkill_waitp) pid_t pid = 0; + int r; + + assert_return(ret, -EINVAL); + assert_return(where, -EINVAL); + + /* Connects to an SSH server via OpenSSH 9.4's -W switch to connect to a remote AF_UNIX socket. For + * now we do not expose this function directly, but only via varlink_connect_url(). */ + + const char *ssh = secure_getenv("SYSTEMD_SSH") ?: "ssh"; + if (!path_is_valid(ssh)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "SSH path is not valid, refusing: %s", ssh); + + const char *e = strchr(where, ':'); + if (!e) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "SSH specification lacks a : separator between host and path, refusing: %s", where); + + _cleanup_free_ char *h = strndup(where, e - where); + if (!h) + return log_oom_debug(); + + _cleanup_free_ char *c = strdup(e + 1); + if (!c) + return log_oom_debug(); + + if (!path_is_absolute(c)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Remote AF_UNIX socket path is not absolute, refusing: %s", c); + + _cleanup_free_ char *p = NULL; + r = path_simplify_alloc(c, &p); + if (r < 0) + return r; + + if (!path_is_normalized(p)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Specified path is not normalized, refusing: %s", p); + + log_debug("Forking off SSH child process '%s -W %s %s'.", ssh, p, h); + + if (socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0, pair) < 0) + return log_debug_errno(errno, "Failed to allocate AF_UNIX socket pair: %m"); + + r = safe_fork_full( + "(sd-vlssh)", + /* stdio_fds= */ (int[]) { pair[1], pair[1], STDERR_FILENO }, + /* except_fds= */ NULL, + /* n_except_fds= */ 0, + FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_REOPEN_LOG|FORK_LOG|FORK_RLIMIT_NOFILE_SAFE|FORK_REARRANGE_STDIO, + &pid); + if (r < 0) + return log_debug_errno(r, "Failed to spawn process: %m"); + if (r == 0) { + /* Child */ + + execlp(ssh, "ssh", "-W", p, h, NULL); + log_debug_errno(errno, "Failed to invoke %s: %m", ssh); + _exit(EXIT_FAILURE); + } + + pair[1] = safe_close(pair[1]); + + Varlink *v; + r = varlink_new(&v); + if (r < 0) + return log_debug_errno(r, "Failed to create varlink object: %m"); + + v->fd = TAKE_FD(pair[0]); + v->af = AF_UNIX; + v->exec_pid = TAKE_PID(pid); + varlink_set_state(v, VARLINK_IDLE_CLIENT); + + *ret = v; + return 0; +} + int varlink_connect_url(Varlink **ret, const char *url) { _cleanup_free_ char *c = NULL; const char *p; - bool exec; + enum { + SCHEME_UNIX, + SCHEME_EXEC, + SCHEME_SSH, + } scheme; int r; assert_return(ret, -EINVAL); assert_return(url, -EINVAL); - // FIXME: Add support for vsock:, ssh-exec:, ssh-unix: URL schemes here. (The latter with OpenSSH - // 9.4's -W switch for referencing remote AF_UNIX sockets.) + // FIXME: Maybe add support for vsock: and ssh-exec: URL schemes here. - /* The Varlink URL scheme is a bit underdefined. We support only the unix: transport for now, plus an - * exec: transport we made up ourselves. Strictly speaking this shouldn't even be called URL, since - * it has nothing to do with Internet URLs by RFC. */ + /* The Varlink URL scheme is a bit underdefined. We support only the spec-defined unix: transport for + * now, plus exec:, ssh: transports we made up ourselves. Strictly speaking this shouldn't even be + * called "URL", since it has nothing to do with Internet URLs by RFC. */ p = startswith(url, "unix:"); if (p) - exec = false; - else { - p = startswith(url, "exec:"); - if (!p) - return log_debug_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "URL scheme not supported."); - - exec = true; - } + scheme = SCHEME_UNIX; + else if ((p = startswith(url, "exec:"))) + scheme = SCHEME_EXEC; + else if ((p = startswith(url, "ssh:"))) + scheme = SCHEME_SSH; + else + return log_debug_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "URL scheme not supported."); /* The varlink.org reference C library supports more than just file system paths. We might want to * support that one day too. For now simply refuse that. */ if (p[strcspn(p, ";?#")] != '\0') return log_debug_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "URL parameterization with ';', '?', '#' not supported."); - if (exec || p[0] != '@') { /* no validity checks for abstract namespace */ + if (scheme == SCHEME_SSH) + return varlink_connect_ssh(ret, p); + + if (scheme == SCHEME_EXEC || p[0] != '@') { /* no path validity checks for abstract namespace sockets */ if (!path_is_absolute(p)) return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Specified path not absolute, refusing."); @@ -549,7 +641,7 @@ int varlink_connect_url(Varlink **ret, const char *url) { return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Specified path is not normalized, refusing."); } - if (exec) + if (scheme == SCHEME_EXEC) return varlink_connect_exec(ret, c, NULL); return varlink_connect_address(ret, c ?: p); @@ -599,7 +691,9 @@ static void varlink_clear_current(Varlink *v) { /* Clears the currently processed incoming message */ v->current = json_variant_unref(v->current); + v->current_collected = json_variant_unref(v->current_collected); v->current_method = NULL; + v->current_reply_flags = 0; close_many(v->input_fds, v->n_input_fds); v->input_fds = mfree(v->input_fds); @@ -615,7 +709,7 @@ static void varlink_clear(Varlink *v) { varlink_clear_current(v); - v->input_buffer = mfree(v->input_buffer); + v->input_buffer = v->input_sensitive ? erase_and_free(v->input_buffer) : mfree(v->input_buffer); v->output_buffer = v->output_buffer_sensitive ? erase_and_free(v->output_buffer) : mfree(v->output_buffer); v->input_control_buffer = mfree(v->input_control_buffer); @@ -638,6 +732,8 @@ static void varlink_clear(Varlink *v) { sigterm_wait(v->exec_pid); v->exec_pid = 0; } + + v->peer_pidfd = safe_close(v->peer_pidfd); } static Varlink* varlink_destroy(Varlink *v) { @@ -680,7 +776,7 @@ static int varlink_test_disconnect(Varlink *v) { goto disconnect; /* If we are waiting for incoming data but the read side is shut down, disconnect. */ - if (IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING, VARLINK_IDLE_SERVER) && v->read_disconnected) + if (IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING, VARLINK_COLLECTING, VARLINK_IDLE_SERVER) && v->read_disconnected) goto disconnect; /* Similar, if are a client that hasn't written anything yet but the write side is dead, also @@ -803,7 +899,7 @@ static int varlink_read(Varlink *v) { assert(v); - if (!IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING, VARLINK_IDLE_SERVER)) + if (!IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING, VARLINK_COLLECTING, VARLINK_IDLE_SERVER)) return 0; if (v->connecting) /* read() on a socket while we are in connect() will fail with EINVAL, hence exit early here */ return 0; @@ -932,7 +1028,8 @@ static int varlink_read(Varlink *v) { } static int varlink_parse_message(Varlink *v) { - const char *e, *begin; + const char *e; + char *begin; size_t sz; int r; @@ -956,11 +1053,9 @@ static int varlink_parse_message(Varlink *v) { sz = e - begin + 1; - varlink_log(v, "New incoming message: %s", begin); /* FIXME: should we output the whole message here before validation? - * This may produce a non-printable journal entry if the message - * is invalid. We may also expose privileged information. */ - r = json_parse(begin, 0, &v->current, NULL, NULL); + if (v->input_sensitive) + explicit_bzero_safe(begin, sz); if (r < 0) { /* If we encounter a parse failure flush all data. We cannot possibly recover from this, * hence drop all buffered data now. */ @@ -968,6 +1063,24 @@ static int varlink_parse_message(Varlink *v) { return varlink_log_errno(v, r, "Failed to parse JSON: %m"); } + if (v->input_sensitive) { + /* Mark the parameters subfield as sensitive right-away, if that's requested */ + JsonVariant *parameters = json_variant_by_key(v->current, "parameters"); + if (parameters) + json_variant_sensitive(parameters); + } + + if (DEBUG_LOGGING) { + _cleanup_(erase_and_freep) char *censored_text = NULL; + + /* Suppress sensitive fields in the debug output */ + r = json_variant_format(v->current, /* flags= */ JSON_FORMAT_CENSOR_SENSITIVE, &censored_text); + if (r < 0) + return r; + + varlink_log(v, "Received message: %s", censored_text); + } + v->input_buffer_size -= sz; if (v->input_buffer_size == 0) @@ -982,7 +1095,7 @@ static int varlink_parse_message(Varlink *v) { static int varlink_test_timeout(Varlink *v) { assert(v); - if (!IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING)) + if (!IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING, VARLINK_COLLECTING)) return 0; if (v->timeout == USEC_INFINITY) return 0; @@ -1006,7 +1119,7 @@ static int varlink_dispatch_local_error(Varlink *v, const char *error) { r = v->reply_callback(v, NULL, error, VARLINK_REPLY_ERROR|VARLINK_REPLY_LOCAL, v->userdata); if (r < 0) - log_debug_errno(r, "Reply callback returned error, ignoring: %m"); + varlink_log_errno(v, r, "Reply callback returned error, ignoring: %m"); return 1; } @@ -1072,7 +1185,7 @@ static int varlink_dispatch_reply(Varlink *v) { assert(v); - if (!IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING)) + if (!IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING, VARLINK_COLLECTING)) return 0; if (!v->current) return 0; @@ -1115,7 +1228,7 @@ static int varlink_dispatch_reply(Varlink *v) { } /* Replies with 'continue' set are only OK if we set 'more' when the method call was initiated */ - if (v->state != VARLINK_AWAITING_REPLY_MORE && FLAGS_SET(flags, VARLINK_REPLY_CONTINUES)) + if (!IN_SET(v->state, VARLINK_AWAITING_REPLY_MORE, VARLINK_COLLECTING) && FLAGS_SET(flags, VARLINK_REPLY_CONTINUES)) goto invalid; /* An error is final */ @@ -1126,19 +1239,20 @@ static int varlink_dispatch_reply(Varlink *v) { if (r < 0) goto invalid; + v->current_reply_flags = flags; + if (IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE)) { varlink_set_state(v, VARLINK_PROCESSING_REPLY); if (v->reply_callback) { r = v->reply_callback(v, parameters, error, flags, v->userdata); if (r < 0) - log_debug_errno(r, "Reply callback returned error, ignoring: %m"); + varlink_log_errno(v, r, "Reply callback returned error, ignoring: %m"); } varlink_clear_current(v); if (v->state == VARLINK_PROCESSING_REPLY) { - assert(v->n_pending > 0); if (!FLAGS_SET(flags, VARLINK_REPLY_CONTINUES)) @@ -1148,7 +1262,9 @@ static int varlink_dispatch_reply(Varlink *v) { FLAGS_SET(flags, VARLINK_REPLY_CONTINUES) ? VARLINK_AWAITING_REPLY_MORE : v->n_pending == 0 ? VARLINK_IDLE_CLIENT : VARLINK_AWAITING_REPLY); } - } else { + } else if (v->state == VARLINK_COLLECTING) + varlink_set_state(v, VARLINK_COLLECTING_REPLY); + else { assert(v->state == VARLINK_CALLING); varlink_set_state(v, VARLINK_CALLED); } @@ -1176,9 +1292,7 @@ static int generic_method_get_info( assert(link); if (json_variant_elements(parameters) != 0) - return varlink_errorb(link, VARLINK_ERROR_INVALID_PARAMETER, - JSON_BUILD_OBJECT( - JSON_BUILD_PAIR_VARIANT("parameter", json_variant_by_index(parameters, 0)))); + return varlink_error_invalid_parameter(link, parameters); product = strjoin("systemd (", program_invocation_short_name, ")"); if (!product) @@ -1196,7 +1310,7 @@ static int generic_method_get_info( return varlink_replyb(link, JSON_BUILD_OBJECT( JSON_BUILD_PAIR_STRING("vendor", "The systemd Project"), JSON_BUILD_PAIR_STRING("product", product), - JSON_BUILD_PAIR_STRING("version", STRINGIFY(PROJECT_VERSION) " (" GIT_VERSION ")"), + JSON_BUILD_PAIR_STRING("version", PROJECT_VERSION_FULL " (" GIT_VERSION ")"), JSON_BUILD_PAIR_STRING("url", "https://systemd.io/"), JSON_BUILD_PAIR_STRV("interfaces", interfaces))); } @@ -1327,16 +1441,18 @@ static int varlink_dispatch_method(Varlink *v) { v->current_method = hashmap_get(v->server->symbols, method); if (!v->current_method) - log_debug("No interface description defined for method '%s', not validating.", method); + varlink_log(v, "No interface description defined for method '%s', not validating.", method); else { const char *bad_field; r = varlink_idl_validate_method_call(v->current_method, parameters, &bad_field); if (r < 0) { - log_debug_errno(r, "Parameters for method %s() didn't pass validation on field '%s': %m", method, strna(bad_field)); + /* Please adjust test/units/end.sh when updating the log message. */ + varlink_log_errno(v, r, "Parameters for method %s() didn't pass validation on field '%s': %m", + method, strna(bad_field)); - if (!FLAGS_SET(flags, VARLINK_METHOD_ONEWAY)) { - r = varlink_errorb(v, VARLINK_ERROR_INVALID_PARAMETER, JSON_BUILD_OBJECT(JSON_BUILD_PAIR_STRING("parameter", bad_field))); + if (IN_SET(v->state, VARLINK_PROCESSING_METHOD, VARLINK_PROCESSING_METHOD_MORE)) { + r = varlink_error_invalid_parameter_name(v, bad_field); if (r < 0) return r; } @@ -1347,24 +1463,21 @@ static int varlink_dispatch_method(Varlink *v) { if (!invalid) { r = callback(v, parameters, flags, v->userdata); if (r < 0) { - log_debug_errno(r, "Callback for %s returned error: %m", method); + varlink_log_errno(v, r, "Callback for %s returned error: %m", method); /* We got an error back from the callback. Propagate it to the client if the method call remains unanswered. */ - if (v->state == VARLINK_PROCESSED_METHOD) - r = 0; /* already processed */ - else if (!FLAGS_SET(flags, VARLINK_METHOD_ONEWAY)) { + if (IN_SET(v->state, VARLINK_PROCESSING_METHOD, VARLINK_PROCESSING_METHOD_MORE)) { r = varlink_error_errno(v, r); if (r < 0) return r; } } } - } else if (!FLAGS_SET(flags, VARLINK_METHOD_ONEWAY)) { + } else if (IN_SET(v->state, VARLINK_PROCESSING_METHOD, VARLINK_PROCESSING_METHOD_MORE)) { r = varlink_errorb(v, VARLINK_ERROR_METHOD_NOT_FOUND, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("method", JSON_BUILD_STRING(method)))); if (r < 0) return r; - } else - r = 0; + } switch (v->state) { @@ -1386,7 +1499,7 @@ static int varlink_dispatch_method(Varlink *v) { assert_not_reached(); } - return r; + return 1; invalid: r = -EINVAL; @@ -1482,6 +1595,48 @@ finish: return r; } +int varlink_dispatch_again(Varlink *v) { + int r; + + assert_return(v, -EINVAL); + + /* If a method call handler could not process the method call just yet (for example because it needed + * some Polkit authentication first), then it can leave the call unanswered, do its thing, and then + * ask to be dispatched a second time, via this call. It will then be called again, for the same + * message */ + + if (v->state == VARLINK_DISCONNECTED) + return varlink_log_errno(v, SYNTHETIC_ERRNO(ENOTCONN), "Not connected."); + if (v->state != VARLINK_PENDING_METHOD) + return varlink_log_errno(v, SYNTHETIC_ERRNO(EBUSY), "Connection has no pending method."); + + varlink_set_state(v, VARLINK_IDLE_SERVER); + + r = sd_event_source_set_enabled(v->defer_event_source, SD_EVENT_ON); + if (r < 0) + return varlink_log_errno(v, r, "Failed to enable deferred event source: %m"); + + return 0; +} + +int varlink_get_current_parameters(Varlink *v, JsonVariant **ret) { + JsonVariant *p; + + assert_return(v, -EINVAL); + + if (!v->current) + return -ENODATA; + + p = json_variant_by_key(v->current, "parameters"); + if (!p) + return -ENODATA; + + if (ret) + *ret = json_variant_ref(p); + + return 0; +} + static void handle_revents(Varlink *v, int revents) { assert(v); @@ -1587,7 +1742,7 @@ int varlink_get_events(Varlink *v) { return EPOLLOUT; if (!v->read_disconnected && - IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING, VARLINK_IDLE_SERVER) && + IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING, VARLINK_COLLECTING, VARLINK_IDLE_SERVER) && !v->current && v->input_buffer_unscanned <= 0) ret |= EPOLLIN; @@ -1605,7 +1760,7 @@ int varlink_get_timeout(Varlink *v, usec_t *ret) { if (v->state == VARLINK_DISCONNECTED) return varlink_log_errno(v, SYNTHETIC_ERRNO(ENOTCONN), "Not connected."); - if (IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING) && + if (IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING, VARLINK_COLLECTING) && v->timeout != USEC_INFINITY) { if (ret) *ret = usec_add(v->timestamp, v->timeout); @@ -1726,51 +1881,60 @@ Varlink* varlink_flush_close_unref(Varlink *v) { static int varlink_format_json(Varlink *v, JsonVariant *m) { _cleanup_(erase_and_freep) char *text = NULL; - int r; + int sz, r; assert(v); assert(m); - r = json_variant_format(m, 0, &text); - if (r < 0) - return r; - assert(text[r] == '\0'); + sz = json_variant_format(m, /* flags= */ 0, &text); + if (sz < 0) + return sz; + assert(text[sz] == '\0'); - if (v->output_buffer_size + r + 1 > VARLINK_BUFFER_MAX) + if (v->output_buffer_size + sz + 1 > VARLINK_BUFFER_MAX) return -ENOBUFS; - varlink_log(v, "Sending message: %s", text); + if (DEBUG_LOGGING) { + _cleanup_(erase_and_freep) char *censored_text = NULL; + + /* Suppress sensitive fields in the debug output */ + r = json_variant_format(m, /* flags= */ JSON_FORMAT_CENSOR_SENSITIVE, &censored_text); + if (r < 0) + return r; + + varlink_log(v, "Sending message: %s", censored_text); + } if (v->output_buffer_size == 0) { free_and_replace(v->output_buffer, text); - v->output_buffer_size = r + 1; + v->output_buffer_size = sz + 1; v->output_buffer_index = 0; } else if (v->output_buffer_index == 0) { - if (!GREEDY_REALLOC(v->output_buffer, v->output_buffer_size + r + 1)) + if (!GREEDY_REALLOC(v->output_buffer, v->output_buffer_size + sz + 1)) return -ENOMEM; - memcpy(v->output_buffer + v->output_buffer_size, text, r + 1); - v->output_buffer_size += r + 1; + memcpy(v->output_buffer + v->output_buffer_size, text, sz + 1); + v->output_buffer_size += sz + 1; } else { char *n; - const size_t new_size = v->output_buffer_size + r + 1; + const size_t new_size = v->output_buffer_size + sz + 1; n = new(char, new_size); if (!n) return -ENOMEM; - memcpy(mempcpy(n, v->output_buffer + v->output_buffer_index, v->output_buffer_size), text, r + 1); + memcpy(mempcpy(n, v->output_buffer + v->output_buffer_index, v->output_buffer_size), text, sz + 1); free_and_replace(v->output_buffer, n); v->output_buffer_size = new_size; v->output_buffer_index = 0; } - if (json_variant_is_sensitive(m)) + if (json_variant_is_sensitive_recursive(m)) v->output_buffer_sensitive = true; /* Propagate sensitive flag */ else text = mfree(text); /* No point in the erase_and_free() destructor declared above */ @@ -1999,7 +2163,7 @@ int varlink_observeb(Varlink *v, const char *method, ...) { return varlink_observe(v, method, parameters); } -int varlink_call( +int varlink_call_full( Varlink *v, const char *method, JsonVariant *parameters, @@ -2043,7 +2207,6 @@ int varlink_call( v->timestamp = now(CLOCK_MONOTONIC); while (v->state == VARLINK_CALLING) { - r = varlink_process(v); if (r < 0) return r; @@ -2057,21 +2220,29 @@ int varlink_call( switch (v->state) { - case VARLINK_CALLED: + case VARLINK_CALLED: { assert(v->current); varlink_set_state(v, VARLINK_IDLE_CLIENT); assert(v->n_pending == 1); v->n_pending--; + JsonVariant *e = json_variant_by_key(v->current, "error"), + *p = json_variant_by_key(v->current, "parameters"); + + /* If caller doesn't ask for the error string, then let's return an error code in case of failure */ + if (!ret_error_id && e) + return varlink_error_to_errno(json_variant_string(e), p); + if (ret_parameters) - *ret_parameters = json_variant_by_key(v->current, "parameters"); + *ret_parameters = p; if (ret_error_id) - *ret_error_id = json_variant_string(json_variant_by_key(v->current, "error")); + *ret_error_id = e ? json_variant_string(e) : NULL; if (ret_flags) - *ret_flags = 0; + *ret_flags = v->current_reply_flags; return 1; + } case VARLINK_PENDING_DISCONNECT: case VARLINK_DISCONNECTED: @@ -2085,63 +2256,76 @@ int varlink_call( } } -int varlink_callb( +int varlink_callb_ap( Varlink *v, const char *method, JsonVariant **ret_parameters, const char **ret_error_id, - VarlinkReplyFlags *ret_flags, ...) { + VarlinkReplyFlags *ret_flags, + va_list ap) { _cleanup_(json_variant_unrefp) JsonVariant *parameters = NULL; - va_list ap; int r; assert_return(v, -EINVAL); + assert_return(method, -EINVAL); - va_start(ap, ret_flags); r = json_buildv(¶meters, ap); - va_end(ap); - if (r < 0) return varlink_log_errno(v, r, "Failed to build json message: %m"); - return varlink_call(v, method, parameters, ret_parameters, ret_error_id, ret_flags); + return varlink_call_full(v, method, parameters, ret_parameters, ret_error_id, ret_flags); } -static void varlink_collect_context_free(VarlinkCollectContext *cc) { - assert(cc); +int varlink_call_and_log( + Varlink *v, + const char *method, + JsonVariant *parameters, + JsonVariant **ret_parameters) { + + JsonVariant *reply = NULL; + const char *error_id = NULL; + int r; + + assert_return(v, -EINVAL); + assert_return(method, -EINVAL); + + r = varlink_call(v, method, parameters, &reply, &error_id); + if (r < 0) + return log_error_errno(r, "Failed to issue %s() varlink call: %m", method); + if (error_id) + return log_error_errno(varlink_error_to_errno(error_id, reply), + "Failed to issue %s() varlink call: %s", method, error_id); - json_variant_unref(cc->parameters); - free((char *)cc->error_id); + if (ret_parameters) + *ret_parameters = TAKE_PTR(reply); + + return 0; } -static int collect_callback( +int varlink_callb_and_log( Varlink *v, - JsonVariant *parameters, - const char *error_id, - VarlinkReplyFlags flags, - void *userdata) { + const char *method, + JsonVariant **ret_parameters, + ...) { - VarlinkCollectContext *context = ASSERT_PTR(userdata); + _cleanup_(json_variant_unrefp) JsonVariant *parameters = NULL; + va_list ap; int r; - assert(v); - - context->flags = flags; - /* If we hit an error, we will drop all collected replies and just return the error_id and flags in varlink_collect() */ - if (error_id) { - context->error_id = error_id; - return 0; - } + assert_return(v, -EINVAL); + assert_return(method, -EINVAL); - r = json_variant_append_array(&context->parameters, parameters); + va_start(ap, ret_parameters); + r = json_buildv(¶meters, ap); + va_end(ap); if (r < 0) - return varlink_log_errno(v, r, "Failed to append JSON object to array: %m"); + return log_error_errno(r, "Failed to build JSON message: %m"); - return 1; + return varlink_call_and_log(v, method, parameters, ret_parameters); } -int varlink_collect( +int varlink_collect_full( Varlink *v, const char *method, JsonVariant *parameters, @@ -2149,7 +2333,7 @@ int varlink_collect( const char **ret_error_id, VarlinkReplyFlags *ret_flags) { - _cleanup_(varlink_collect_context_free) VarlinkCollectContext context = {}; + _cleanup_(json_variant_unrefp) JsonVariant *m = NULL, *collected = NULL; int r; assert_return(v, -EINVAL); @@ -2166,61 +2350,105 @@ int varlink_collect( * that we can assign a new reply shortly. */ varlink_clear_current(v); - r = varlink_bind_reply(v, collect_callback); + r = varlink_sanitize_parameters(¶meters); if (r < 0) - return varlink_log_errno(v, r, "Failed to bind collect callback"); + return varlink_log_errno(v, r, "Failed to sanitize parameters: %m"); - varlink_set_userdata(v, &context); - r = varlink_observe(v, method, parameters); + r = json_build(&m, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("method", JSON_BUILD_STRING(method)), + JSON_BUILD_PAIR("parameters", JSON_BUILD_VARIANT(parameters)), + JSON_BUILD_PAIR("more", JSON_BUILD_BOOLEAN(true)))); if (r < 0) - return varlink_log_errno(v, r, "Failed to collect varlink method: %m"); + return varlink_log_errno(v, r, "Failed to build json message: %m"); - while (v->state == VARLINK_AWAITING_REPLY_MORE) { + r = varlink_enqueue_json(v, m); + if (r < 0) + return varlink_log_errno(v, r, "Failed to enqueue json message: %m"); - r = varlink_process(v); - if (r < 0) - return r; + varlink_set_state(v, VARLINK_COLLECTING); + v->n_pending++; + v->timestamp = now(CLOCK_MONOTONIC); - /* If we get an error from any of the replies, return immediately with just the error_id and flags*/ - if (context.error_id) { - if (ret_error_id) - *ret_error_id = TAKE_PTR(context.error_id); - if (ret_flags) - *ret_flags = context.flags; - return 0; + for (;;) { + while (v->state == VARLINK_COLLECTING) { + r = varlink_process(v); + if (r < 0) + return r; + if (r > 0) + continue; + + r = varlink_wait(v, USEC_INFINITY); + if (r < 0) + return r; } - if (r > 0) - continue; + switch (v->state) { - r = varlink_wait(v, USEC_INFINITY); - if (r < 0) - return r; - } + case VARLINK_COLLECTING_REPLY: { + assert(v->current); - switch (v->state) { + JsonVariant *e = json_variant_by_key(v->current, "error"), + *p = json_variant_by_key(v->current, "parameters"); - case VARLINK_IDLE_CLIENT: - break; + /* Unless there is more to collect we reset state to idle */ + if (!FLAGS_SET(v->current_reply_flags, VARLINK_REPLY_CONTINUES)) { + varlink_set_state(v, VARLINK_IDLE_CLIENT); + assert(v->n_pending == 1); + v->n_pending--; + } - case VARLINK_PENDING_DISCONNECT: - case VARLINK_DISCONNECTED: - return varlink_log_errno(v, SYNTHETIC_ERRNO(ECONNRESET), "Connection was closed."); + if (e) { + if (!ret_error_id) + return varlink_error_to_errno(json_variant_string(e), p); - case VARLINK_PENDING_TIMEOUT: - return varlink_log_errno(v, SYNTHETIC_ERRNO(ETIME), "Connection timed out."); + if (ret_parameters) + *ret_parameters = p; + if (ret_error_id) + *ret_error_id = json_variant_string(e); + if (ret_flags) + *ret_flags = v->current_reply_flags; - default: - assert_not_reached(); - } + return 1; + } - if (ret_parameters) - *ret_parameters = TAKE_PTR(context.parameters); - if (ret_error_id) - *ret_error_id = TAKE_PTR(context.error_id); - if (ret_flags) - *ret_flags = context.flags; - return 1; + if (json_variant_elements(collected) >= VARLINK_COLLECT_MAX) + return varlink_log_errno(v, SYNTHETIC_ERRNO(E2BIG), "Number of reply messages grew too large (%zu) while collecting.", json_variant_elements(collected)); + + r = json_variant_append_array(&collected, p); + if (r < 0) + return varlink_log_errno(v, r, "Failed to append JSON object to array: %m"); + + if (FLAGS_SET(v->current_reply_flags, VARLINK_REPLY_CONTINUES)) { + /* There's more to collect, continue */ + varlink_clear_current(v); + varlink_set_state(v, VARLINK_COLLECTING); + continue; + } + + if (ret_parameters) + /* Install the collection array in the connection object, so that we can hand + * out a pointer to it without passing over ownership, to make it work more + * alike regular method call replies */ + *ret_parameters = v->current_collected = TAKE_PTR(collected); + if (ret_error_id) + *ret_error_id = NULL; + if (ret_flags) + *ret_flags = v->current_reply_flags; + + return 1; + } + + case VARLINK_PENDING_DISCONNECT: + case VARLINK_DISCONNECTED: + return varlink_log_errno(v, SYNTHETIC_ERRNO(ECONNRESET), "Connection was closed."); + + case VARLINK_PENDING_TIMEOUT: + return varlink_log_errno(v, SYNTHETIC_ERRNO(ETIME), "Connection timed out."); + + default: + assert_not_reached(); + } + } } int varlink_collectb( @@ -2228,7 +2456,7 @@ int varlink_collectb( const char *method, JsonVariant **ret_parameters, const char **ret_error_id, - VarlinkReplyFlags *ret_flags, ...) { + ...) { _cleanup_(json_variant_unrefp) JsonVariant *parameters = NULL; va_list ap; @@ -2236,14 +2464,14 @@ int varlink_collectb( assert_return(v, -EINVAL); - va_start(ap, ret_flags); + va_start(ap, ret_error_id); r = json_buildv(¶meters, ap); va_end(ap); if (r < 0) return varlink_log_errno(v, r, "Failed to build json message: %m"); - return varlink_collect(v, method, parameters, ret_parameters, ret_error_id, ret_flags); + return varlink_collect_full(v, method, parameters, ret_parameters, ret_error_id, NULL); } int varlink_reply(Varlink *v, JsonVariant *parameters) { @@ -2272,7 +2500,9 @@ int varlink_reply(Varlink *v, JsonVariant *parameters) { r = varlink_idl_validate_method_reply(v->current_method, parameters, &bad_field); if (r < 0) - log_debug_errno(r, "Return parameters for method reply %s() didn't pass validation on field '%s', ignoring: %m", v->current_method->name, strna(bad_field)); + /* Please adjust test/units/end.sh when updating the log message. */ + varlink_log_errno(v, r, "Return parameters for method reply %s() didn't pass validation on field '%s', ignoring: %m", + v->current_method->name, strna(bad_field)); } r = varlink_enqueue_json(v, m); @@ -2343,13 +2573,15 @@ int varlink_error(Varlink *v, const char *error_id, JsonVariant *parameters) { VarlinkSymbol *symbol = hashmap_get(v->server->symbols, error_id); if (!symbol) - log_debug("No interface description defined for error '%s', not validating.", error_id); + varlink_log(v, "No interface description defined for error '%s', not validating.", error_id); else { const char *bad_field = NULL; r = varlink_idl_validate_error(symbol, parameters, &bad_field); if (r < 0) - log_debug_errno(r, "Parameters for error %s didn't pass validation on field '%s', ignoring: %m", error_id, strna(bad_field)); + /* Please adjust test/units/end.sh when updating the log message. */ + varlink_log_errno(v, r, "Parameters for error %s didn't pass validation on field '%s', ignoring: %m", + error_id, strna(bad_field)); } r = varlink_enqueue_json(v, m); @@ -2425,6 +2657,13 @@ int varlink_error_invalid_parameter(Varlink *v, JsonVariant *parameters) { return -EINVAL; } +int varlink_error_invalid_parameter_name(Varlink *v, const char *name) { + return varlink_errorb( + v, + VARLINK_ERROR_INVALID_PARAMETER, + JSON_BUILD_OBJECT(JSON_BUILD_PAIR("parameter", JSON_BUILD_STRING(name)))); +} + int varlink_error_errno(Varlink *v, int error) { return varlink_errorb( v, @@ -2464,7 +2703,9 @@ int varlink_notify(Varlink *v, JsonVariant *parameters) { r = varlink_idl_validate_method_reply(v->current_method, parameters, &bad_field); if (r < 0) - log_debug_errno(r, "Return parameters for method reply %s() didn't pass validation on field '%s', ignoring: %m", v->current_method->name, strna(bad_field)); + /* Please adjust test/units/end.sh when updating the log message. */ + varlink_log_errno(v, r, "Return parameters for method reply %s() didn't pass validation on field '%s', ignoring: %m", + v->current_method->name, strna(bad_field)); } r = varlink_enqueue_json(v, m); @@ -2504,8 +2745,7 @@ int varlink_dispatch(Varlink *v, JsonVariant *parameters, const JsonDispatch tab r = json_dispatch_full(parameters, table, /* bad= */ NULL, /* flags= */ 0, userdata, &bad_field); if (r < 0) { if (bad_field) - return varlink_errorb(v, VARLINK_ERROR_INVALID_PARAMETER, - JSON_BUILD_OBJECT(JSON_BUILD_PAIR("parameter", JSON_BUILD_STRING(bad_field)))); + return varlink_error_invalid_parameter_name(v, bad_field); return r; } @@ -2567,12 +2807,29 @@ int varlink_get_peer_uid(Varlink *v, uid_t *ret) { return varlink_log_errno(v, r, "Failed to acquire credentials: %m"); if (!uid_is_valid(v->ucred.uid)) - return varlink_log_errno(v, SYNTHETIC_ERRNO(ENODATA), "Peer uid is invalid."); + return varlink_log_errno(v, SYNTHETIC_ERRNO(ENODATA), "Peer UID is invalid."); *ret = v->ucred.uid; return 0; } +int varlink_get_peer_gid(Varlink *v, gid_t *ret) { + int r; + + assert_return(v, -EINVAL); + assert_return(ret, -EINVAL); + + r = varlink_acquire_ucred(v); + if (r < 0) + return varlink_log_errno(v, r, "Failed to acquire credentials: %m"); + + if (!gid_is_valid(v->ucred.gid)) + return varlink_log_errno(v, SYNTHETIC_ERRNO(ENODATA), "Peer GID is invalid."); + + *ret = v->ucred.gid; + return 0; +} + int varlink_get_peer_pid(Varlink *v, pid_t *ret) { int r; @@ -2590,6 +2847,54 @@ int varlink_get_peer_pid(Varlink *v, pid_t *ret) { return 0; } +static int varlink_acquire_pidfd(Varlink *v) { + assert(v); + + if (v->peer_pidfd >= 0) + return 0; + + v->peer_pidfd = getpeerpidfd(v->fd); + if (v->peer_pidfd < 0) + return v->peer_pidfd; + + return 0; +} + +int varlink_get_peer_pidref(Varlink *v, PidRef *ret) { + int r; + + assert_return(v, -EINVAL); + assert_return(ret, -EINVAL); + + /* Returns r > 0 if we acquired the pidref via SO_PEERPIDFD (i.e. if we can use it for + * authentication). Returns == 0 if we didn't, and the pidref should not be used for + * authentication. */ + + r = varlink_acquire_pidfd(v); + if (r < 0 && !ERRNO_IS_NEG_NOT_SUPPORTED(r)) + return r; + + if (v->peer_pidfd < 0) { + pid_t pid; + + r = varlink_get_peer_pid(v, &pid); + if (r < 0) + return r; + + r = pidref_set_pid(ret, pid); + if (r < 0) + return r; + + return 0; /* didn't get pidfd securely */ + } + + r = pidref_set_pidfd(ret, v->peer_pidfd); + if (r < 0) + return r; + + return 1; /* got pidfd securely */ +} + int varlink_set_relative_timeout(Varlink *v, usec_t timeout) { assert_return(v, -EINVAL); assert_return(timeout > 0, -EINVAL); @@ -2788,7 +3093,7 @@ int varlink_push_fd(Varlink *v, int fd) { return i; } -int varlink_dup_fd(Varlink *v, int fd) { +int varlink_push_dup_fd(Varlink *v, int fd) { _cleanup_close_ int dp = -1; int r; @@ -2836,6 +3141,16 @@ int varlink_peek_fd(Varlink *v, size_t i) { return v->input_fds[i]; } +int varlink_peek_dup_fd(Varlink *v, size_t i) { + int fd; + + fd = varlink_peek_fd(v, i); + if (fd < 0) + return fd; + + return RET_NERRNO(fcntl(fd, F_DUPFD_CLOEXEC, 3)); +} + int varlink_take_fd(Varlink *v, size_t i) { assert_return(v, -EINVAL); @@ -2855,6 +3170,16 @@ int varlink_take_fd(Varlink *v, size_t i) { static int verify_unix_socket(Varlink *v) { assert(v); + /* Returns: + * • 0 if this is an AF_UNIX socket + * • -ENOTSOCK if this is not a socket at all + * • -ENOMEDIUM if this is a socket, but not an AF_UNIX socket + * + * Reminder: + * • v->af is < 0 if we haven't checked what kind of address family the thing is yet. + * • v->af == AF_UNSPEC if we checked but it's not a socket + * • otherwise: v->af contains the address family we determined */ + if (v->af < 0) { struct stat st; @@ -2870,7 +3195,8 @@ static int verify_unix_socket(Varlink *v) { return v->af; } - return v->af == AF_UNIX ? 0 : -ENOMEDIUM; + return v->af == AF_UNIX ? 0 : + v->af == AF_UNSPEC ? -ENOTSOCK : -ENOMEDIUM; } int varlink_set_allow_fd_passing_input(Varlink *v, bool b) { @@ -2915,6 +3241,13 @@ int varlink_set_allow_fd_passing_output(Varlink *v, bool b) { return 0; } +int varlink_set_input_sensitive(Varlink *v) { + assert_return(v, -EINVAL); + + v->input_sensitive = true; + return 0; +} + int varlink_server_new(VarlinkServer **ret, VarlinkServerFlags flags) { _cleanup_(varlink_server_unrefp) VarlinkServer *s = NULL; int r; @@ -3021,9 +3354,11 @@ static int count_connection(VarlinkServer *server, const struct ucred *ucred) { server->n_connections++; if (FLAGS_SET(server->flags, VARLINK_SERVER_ACCOUNT_UID)) { + assert(uid_is_valid(ucred->uid)); + r = hashmap_ensure_allocated(&server->by_uid, NULL); if (r < 0) - return log_debug_errno(r, "Failed to allocate UID hash table: %m"); + return varlink_server_log_errno(server, r, "Failed to allocate UID hash table: %m"); c = PTR_TO_UINT(hashmap_get(server->by_uid, UID_TO_PTR(ucred->uid))); @@ -3032,7 +3367,7 @@ static int count_connection(VarlinkServer *server, const struct ucred *ucred) { r = hashmap_replace(server->by_uid, UID_TO_PTR(ucred->uid), UINT_TO_PTR(c + 1)); if (r < 0) - return log_debug_errno(r, "Failed to increment counter in UID hash table: %m"); + return varlink_server_log_errno(server, r, "Failed to increment counter in UID hash table: %m"); } return 0; @@ -3080,7 +3415,7 @@ int varlink_server_add_connection(VarlinkServer *server, int fd, Varlink **ret) } _cleanup_free_ char *desc = NULL; - if (asprintf(&desc, "%s-%i", server->description ?: "varlink", v->fd) >= 0) + if (asprintf(&desc, "%s-%i", varlink_server_description(server), v->fd) >= 0) v->description = TAKE_PTR(desc); /* Link up the server and the connection, and take reference in both directions. Note that the @@ -3141,6 +3476,9 @@ static int connect_callback(sd_event_source *source, int fd, uint32_t revents, v TAKE_FD(cfd); + if (FLAGS_SET(ss->server->flags, VARLINK_SERVER_INPUT_SENSITIVE)) + varlink_set_input_sensitive(v); + if (ss->server->connect_callback) { r = ss->server->connect_callback(ss->server, v, ss->server->userdata); if (r < 0) { @@ -3293,6 +3631,14 @@ int varlink_server_listen_auto(VarlinkServer *s) { n++; } + /* For debug purposes let's listen on an explicitly specified address */ + const char *e = secure_getenv("SYSTEMD_VARLINK_LISTEN"); + if (e) { + r = varlink_server_listen_address(s, e, FLAGS_SET(s->flags, VARLINK_SERVER_ROOT_ONLY) ? 0600 : 0666); + if (r < 0) + return r; + } + return n; } @@ -3436,7 +3782,7 @@ int varlink_server_detach_event(VarlinkServer *s) { LIST_FOREACH(sockets, ss, s->sockets) ss->event_source = sd_event_source_disable_unref(ss->event_source); - sd_event_unref(s->event); + s->event = sd_event_unref(s->event); return 0; } @@ -3472,7 +3818,7 @@ int varlink_server_bind_method(VarlinkServer *s, const char *method, VarlinkMeth if (varlink_symbol_in_interface(method, "org.varlink.service") || varlink_symbol_in_interface(method, "io.systemd")) - return log_debug_errno(SYNTHETIC_ERRNO(EEXIST), "Cannot bind server to '%s'.", method); + return varlink_server_log_errno(s, SYNTHETIC_ERRNO(EEXIST), "Cannot bind server to '%s'.", method); m = strdup(method); if (!m) @@ -3482,7 +3828,7 @@ int varlink_server_bind_method(VarlinkServer *s, const char *method, VarlinkMeth if (r == -ENOMEM) return log_oom_debug(); if (r < 0) - return log_debug_errno(r, "Failed to register callback: %m"); + return varlink_server_log_errno(s, r, "Failed to register callback: %m"); if (r > 0) TAKE_PTR(m); @@ -3519,7 +3865,7 @@ int varlink_server_bind_connect(VarlinkServer *s, VarlinkConnect callback) { assert_return(s, -EINVAL); if (callback && s->connect_callback && callback != s->connect_callback) - return log_debug_errno(SYNTHETIC_ERRNO(EBUSY), "A different callback was already set."); + return varlink_server_log_errno(s, SYNTHETIC_ERRNO(EBUSY), "A different callback was already set."); s->connect_callback = callback; return 0; @@ -3529,7 +3875,7 @@ int varlink_server_bind_disconnect(VarlinkServer *s, VarlinkDisconnect callback) assert_return(s, -EINVAL); if (callback && s->disconnect_callback && callback != s->disconnect_callback) - return log_debug_errno(SYNTHETIC_ERRNO(EBUSY), "A different callback was already set."); + return varlink_server_log_errno(s, SYNTHETIC_ERRNO(EBUSY), "A different callback was already set."); s->disconnect_callback = callback; return 0; @@ -3543,7 +3889,7 @@ int varlink_server_add_interface(VarlinkServer *s, const VarlinkInterface *inter assert_return(interface->name, -EINVAL); if (hashmap_contains(s->interfaces, interface->name)) - return log_debug_errno(SYNTHETIC_ERRNO(EEXIST), "Duplicate registration of interface '%s'.", interface->name); + return varlink_server_log_errno(s, SYNTHETIC_ERRNO(EEXIST), "Duplicate registration of interface '%s'.", interface->name); r = hashmap_ensure_put(&s->interfaces, &string_hash_ops, interface->name, (void*) interface); if (r < 0) @@ -3696,11 +4042,11 @@ int varlink_server_deserialize_one(VarlinkServer *s, const char *value, FDSet *f return log_oom_debug(); if (v[n] != ' ') - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + return varlink_server_log_errno(s, SYNTHETIC_ERRNO(EINVAL), "Failed to deserialize VarlinkServerSocket: %s: %m", value); v = startswith(v + n + 1, "varlink-server-socket-fd="); if (!v) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + return varlink_server_log_errno(s, SYNTHETIC_ERRNO(EINVAL), "Failed to deserialize VarlinkServerSocket fd %s: %m", value); n = strcspn(v, " "); @@ -3708,9 +4054,9 @@ int varlink_server_deserialize_one(VarlinkServer *s, const char *value, FDSet *f fd = parse_fd(buf); if (fd < 0) - return log_debug_errno(fd, "Unable to parse VarlinkServerSocket varlink-server-socket-fd=%s: %m", buf); + return varlink_server_log_errno(s, fd, "Unable to parse VarlinkServerSocket varlink-server-socket-fd=%s: %m", buf); if (!fdset_contains(fds, fd)) - return log_debug_errno(SYNTHETIC_ERRNO(EBADF), + return varlink_server_log_errno(s, SYNTHETIC_ERRNO(EBADF), "VarlinkServerSocket varlink-server-socket-fd= has unknown fd %d: %m", fd); ss = new(VarlinkServerSocket, 1); @@ -3725,7 +4071,7 @@ int varlink_server_deserialize_one(VarlinkServer *s, const char *value, FDSet *f r = varlink_server_add_socket_event_source(s, ss, SD_EVENT_PRIORITY_NORMAL); if (r < 0) - return log_debug_errno(r, "Failed to add VarlinkServerSocket event source to the event loop: %m"); + return varlink_server_log_errno(s, r, "Failed to add VarlinkServerSocket event source to the event loop: %m"); LIST_PREPEND(sockets, s->sockets, TAKE_PTR(ss)); return 0; @@ -3738,6 +4084,10 @@ int varlink_invocation(VarlinkInvocationFlags flags) { /* Returns true if this is a "pure" varlink server invocation, i.e. with one fd passed. */ + const char *e = secure_getenv("SYSTEMD_VARLINK_LISTEN"); /* Permit a manual override for testing purposes */ + if (e) + return true; + r = sd_listen_fds_with_names(/* unset_environment= */ false, &names); if (r < 0) return r; @@ -3765,3 +4115,42 @@ int varlink_invocation(VarlinkInvocationFlags flags) { return true; } + +int varlink_error_to_errno(const char *error, JsonVariant *parameters) { + static const struct { + const char *error; + int value; + } table[] = { + { VARLINK_ERROR_DISCONNECTED, -ECONNRESET }, + { VARLINK_ERROR_TIMEOUT, -ETIMEDOUT }, + { VARLINK_ERROR_PROTOCOL, -EPROTO }, + { VARLINK_ERROR_INTERFACE_NOT_FOUND, -EADDRNOTAVAIL }, + { VARLINK_ERROR_METHOD_NOT_FOUND, -ENXIO }, + { VARLINK_ERROR_METHOD_NOT_IMPLEMENTED, -ENOTTY }, + { VARLINK_ERROR_INVALID_PARAMETER, -EINVAL }, + { VARLINK_ERROR_PERMISSION_DENIED, -EACCES }, + { VARLINK_ERROR_EXPECTED_MORE, -EBADE }, + }; + + if (!error) + return 0; + + FOREACH_ELEMENT(t, table) + if (streq(error, t->error)) + return t->value; + + if (streq(error, VARLINK_ERROR_SYSTEM) && parameters) { + JsonVariant *e; + + e = json_variant_by_key(parameters, "errno"); + if (json_variant_is_integer(e)) { + int64_t i; + + i = json_variant_integer(e); + if (i > 0 && i < ERRNO_MAX) + return -i; + } + } + + return -EBADR; /* Catch-all */ +} diff --git a/src/shared/varlink.h b/src/shared/varlink.h index 6ec708a..f6dda63 100644 --- a/src/shared/varlink.h +++ b/src/shared/varlink.h @@ -4,6 +4,7 @@ #include "sd-event.h" #include "json.h" +#include "pidref.h" #include "time-util.h" #include "varlink-idl.h" @@ -46,7 +47,8 @@ typedef enum VarlinkServerFlags { VARLINK_SERVER_MYSELF_ONLY = 1 << 1, /* Only accessible by our own UID */ VARLINK_SERVER_ACCOUNT_UID = 1 << 2, /* Do per user accounting */ VARLINK_SERVER_INHERIT_USERDATA = 1 << 3, /* Initialize Varlink connection userdata from VarlinkServer userdata */ - _VARLINK_SERVER_FLAGS_ALL = (1 << 4) - 1, + VARLINK_SERVER_INPUT_SENSITIVE = 1 << 4, /* Automatically mark al connection input as sensitive */ + _VARLINK_SERVER_FLAGS_ALL = (1 << 5) - 1, } VarlinkServerFlags; typedef int (*VarlinkMethod)(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata); @@ -86,12 +88,39 @@ int varlink_send(Varlink *v, const char *method, JsonVariant *parameters); int varlink_sendb(Varlink *v, const char *method, ...); /* Send method call and wait for reply */ -int varlink_call(Varlink *v, const char *method, JsonVariant *parameters, JsonVariant **ret_parameters, const char **ret_error_id, VarlinkReplyFlags *ret_flags); -int varlink_callb(Varlink *v, const char *method, JsonVariant **ret_parameters, const char **ret_error_id, VarlinkReplyFlags *ret_flags, ...); +int varlink_call_full(Varlink *v, const char *method, JsonVariant *parameters, JsonVariant **ret_parameters, const char **ret_error_id, VarlinkReplyFlags *ret_flags); +static inline int varlink_call(Varlink *v, const char *method, JsonVariant *parameters, JsonVariant **ret_parameters, const char **ret_error_id) { + return varlink_call_full(v, method, parameters, ret_parameters, ret_error_id, NULL); +} +int varlink_call_and_log(Varlink *v, const char *method, JsonVariant *parameters, JsonVariant **ret_parameters); + +int varlink_callb_ap(Varlink *v, const char *method, JsonVariant **ret_parameters, const char **ret_error_id, VarlinkReplyFlags *ret_flags, va_list ap); +static inline int varlink_callb_full(Varlink *v, const char *method, JsonVariant **ret_parameters, const char **ret_error_id, VarlinkReplyFlags *ret_flags, ...) { + va_list ap; + int r; + + va_start(ap, ret_flags); + r = varlink_callb_ap(v, method, ret_parameters, ret_error_id, ret_flags, ap); + va_end(ap); + return r; +} +static inline int varlink_callb(Varlink *v, const char *method, JsonVariant **ret_parameters, const char **ret_error_id, ...) { + va_list ap; + int r; + + va_start(ap, ret_error_id); + r = varlink_callb_ap(v, method, ret_parameters, ret_error_id, NULL, ap); + va_end(ap); + return r; +} +int varlink_callb_and_log(Varlink *v, const char *method, JsonVariant **ret_parameters, ...); /* Send method call and begin collecting all 'more' replies into an array, finishing when a final reply is sent */ -int varlink_collect(Varlink *v, const char *method, JsonVariant *parameters, JsonVariant **ret_parameters, const char **ret_error_id, VarlinkReplyFlags *ret_flags); -int varlink_collectb(Varlink *v, const char *method, JsonVariant **ret_parameters, const char **ret_error_id, VarlinkReplyFlags *ret_flags, ...); +int varlink_collect_full(Varlink *v, const char *method, JsonVariant *parameters, JsonVariant **ret_parameters, const char **ret_error_id, VarlinkReplyFlags *ret_flags); +static inline int varlink_collect(Varlink *v, const char *method, JsonVariant *parameters, JsonVariant **ret_parameters, const char **ret_error_id) { + return varlink_collect_full(v, method, parameters, ret_parameters, ret_error_id, NULL); +} +int varlink_collectb(Varlink *v, const char *method, JsonVariant **ret_parameters, const char **ret_error_id, ...); /* Enqueue method call, expect a reply, which is eventually delivered to the reply callback */ int varlink_invoke(Varlink *v, const char *method, JsonVariant *parameters); @@ -109,22 +138,30 @@ int varlink_replyb(Varlink *v, ...); int varlink_error(Varlink *v, const char *error_id, JsonVariant *parameters); int varlink_errorb(Varlink *v, const char *error_id, ...); int varlink_error_invalid_parameter(Varlink *v, JsonVariant *parameters); +int varlink_error_invalid_parameter_name(Varlink *v, const char *name); int varlink_error_errno(Varlink *v, int error); /* Enqueue a "more" reply */ int varlink_notify(Varlink *v, JsonVariant *parameters); int varlink_notifyb(Varlink *v, ...); +/* Ask for the current message to be dispatched again */ +int varlink_dispatch_again(Varlink *v); + +/* Get the currently processed incoming message */ +int varlink_get_current_parameters(Varlink *v, JsonVariant **ret); + /* Parsing incoming data via json_dispatch() and generate a nice error on parse errors */ int varlink_dispatch(Varlink *v, JsonVariant *parameters, const JsonDispatch table[], void *userdata); /* Write outgoing fds into the socket (to be associated with the next enqueued message) */ int varlink_push_fd(Varlink *v, int fd); -int varlink_dup_fd(Varlink *v, int fd); +int varlink_push_dup_fd(Varlink *v, int fd); int varlink_reset_fds(Varlink *v); /* Read incoming fds from the socket (associated with the currently handled message) */ int varlink_peek_fd(Varlink *v, size_t i); +int varlink_peek_dup_fd(Varlink *v, size_t i); int varlink_take_fd(Varlink *v, size_t i); int varlink_set_allow_fd_passing_input(Varlink *v, bool b); @@ -137,7 +174,9 @@ void* varlink_set_userdata(Varlink *v, void *userdata); void* varlink_get_userdata(Varlink *v); int varlink_get_peer_uid(Varlink *v, uid_t *ret); +int varlink_get_peer_gid(Varlink *v, gid_t *ret); int varlink_get_peer_pid(Varlink *v, pid_t *ret); +int varlink_get_peer_pidref(Varlink *v, PidRef *ret); int varlink_set_relative_timeout(Varlink *v, usec_t usec); @@ -145,6 +184,9 @@ VarlinkServer* varlink_get_server(Varlink *v); int varlink_set_description(Varlink *v, const char *d); +/* Automatically mark the parameters part of incoming messages as security sensitive */ +int varlink_set_input_sensitive(Varlink *v); + /* Create a varlink server */ int varlink_server_new(VarlinkServer **ret, VarlinkServerFlags flags); VarlinkServer *varlink_server_ref(VarlinkServer *s); @@ -200,6 +242,8 @@ typedef enum VarlinkInvocationFlags { int varlink_invocation(VarlinkInvocationFlags flags); +int varlink_error_to_errno(const char *error, JsonVariant *parameters); + DEFINE_TRIVIAL_CLEANUP_FUNC(Varlink *, varlink_unref); DEFINE_TRIVIAL_CLEANUP_FUNC(Varlink *, varlink_close_unref); DEFINE_TRIVIAL_CLEANUP_FUNC(Varlink *, varlink_flush_close_unref); @@ -213,12 +257,13 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(VarlinkServer *, varlink_server_unref); /* This one we invented, and use for generically propagating system errors (errno) to clients */ #define VARLINK_ERROR_SYSTEM "io.systemd.System" +/* This one we invented and is a weaker version of "org.varlink.service.PermissionDenied", and indicates that if user would allow interactive auth, we might allow access */ +#define VARLINK_ERROR_INTERACTIVE_AUTHENTICATION_REQUIRED "io.systemd.InteractiveAuthenticationRequired" + /* These are errors defined in the Varlink spec */ #define VARLINK_ERROR_INTERFACE_NOT_FOUND "org.varlink.service.InterfaceNotFound" #define VARLINK_ERROR_METHOD_NOT_FOUND "org.varlink.service.MethodNotFound" #define VARLINK_ERROR_METHOD_NOT_IMPLEMENTED "org.varlink.service.MethodNotImplemented" #define VARLINK_ERROR_INVALID_PARAMETER "org.varlink.service.InvalidParameter" - -/* These are errors we came up with and squatted the namespace with */ #define VARLINK_ERROR_PERMISSION_DENIED "org.varlink.service.PermissionDenied" #define VARLINK_ERROR_EXPECTED_MORE "org.varlink.service.ExpectedMore" diff --git a/src/shared/verbs.c b/src/shared/verbs.c index a38591d..7e7ac2a 100644 --- a/src/shared/verbs.c +++ b/src/shared/verbs.c @@ -177,4 +177,4 @@ int dispatch_verb(int argc, char *argv[], const Verb verbs[], void *userdata) { return verb->dispatch(1, STRV_MAKE(verb->verb), userdata); return verb->dispatch(left, argv, userdata); - } +} diff --git a/src/shared/vpick.c b/src/shared/vpick.c new file mode 100644 index 0000000..4720e74 --- /dev/null +++ b/src/shared/vpick.c @@ -0,0 +1,699 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <sys/stat.h> + +#include "architecture.h" +#include "chase.h" +#include "fd-util.h" +#include "fs-util.h" +#include "parse-util.h" +#include "path-util.h" +#include "recurse-dir.h" +#include "vpick.h" + +void pick_result_done(PickResult *p) { + assert(p); + + free(p->path); + safe_close(p->fd); + free(p->version); + + *p = PICK_RESULT_NULL; +} + +static int format_fname( + const PickFilter *filter, + PickFlags flags, + char **ret) { + + _cleanup_free_ char *fn = NULL; + int r; + + assert(filter); + assert(ret); + + if (FLAGS_SET(flags, PICK_TRIES) || !filter->version) /* Underspecified? */ + return -ENOEXEC; + if (strv_length(filter->suffix) > 1) /* suffix is not deterministic? */ + return -ENOEXEC; + + /* The format for names we match goes like this: + * + * <basename><suffix> + * or: + * <basename>_<version><suffix> + * or: + * <basename>_<version>_<architecture><suffix> + * or: + * <basename>_<architecture><suffix> + * + * (Note that basename can be empty, in which case the leading "_" is suppressed) + * + * Examples: foo.raw, foo_1.3-7.raw, foo_1.3-7_x86-64.raw, foo_x86-64.raw + * + * Why use "_" as separator here? Primarily because it is not used by Semver 2.0. In RPM it is used + * for "unsortable" versions, i.e. doesn't show up in "sortable" versions, which we matter for this + * usecase here. In Debian the underscore is not allowed (and it uses it itself for separating + * fields). + * + * This is very close to Debian's way to name packages, but allows arbitrary suffixes, and makes the + * architecture field redundant. + * + * Compare with RPM's "NEVRA" concept. Here we have "BVAS" (basename, version, architecture, suffix). + */ + + if (filter->basename) { + fn = strdup(filter->basename); + if (!fn) + return -ENOMEM; + } + + if (filter->version) { + if (isempty(fn)) { + r = free_and_strdup(&fn, filter->version); + if (r < 0) + return r; + } else if (!strextend(&fn, "_", filter->version)) + return -ENOMEM; + } + + if (FLAGS_SET(flags, PICK_ARCHITECTURE) && filter->architecture >= 0) { + const char *as = ASSERT_PTR(architecture_to_string(filter->architecture)); + if (isempty(fn)) { + r = free_and_strdup(&fn, as); + if (r < 0) + return r; + } else if (!strextend(&fn, "_", as)) + return -ENOMEM; + } + + if (!strv_isempty(filter->suffix)) + if (!strextend(&fn, filter->suffix[0])) + return -ENOMEM; + + if (!filename_is_valid(fn)) + return -EINVAL; + + *ret = TAKE_PTR(fn); + return 0; +} + +static int errno_from_mode(uint32_t type_mask, mode_t found) { + /* Returns the most appropriate error code if we are lookging for an inode of type of those in the + * 'type_mask' but found 'found' instead. + * + * type_mask is a mask of 1U << DT_REG, 1U << DT_DIR, … flags, while found is a S_IFREG, S_IFDIR, … + * mode value. */ + + if (type_mask == 0) /* type doesn't matter */ + return 0; + + if (FLAGS_SET(type_mask, UINT32_C(1) << IFTODT(found))) + return 0; + + if (type_mask == (UINT32_C(1) << DT_BLK)) + return -ENOTBLK; + if (type_mask == (UINT32_C(1) << DT_DIR)) + return -ENOTDIR; + if (type_mask == (UINT32_C(1) << DT_SOCK)) + return -ENOTSOCK; + + if (S_ISLNK(found)) + return -ELOOP; + if (S_ISDIR(found)) + return -EISDIR; + + return -EBADF; +} + +static int pin_choice( + const char *toplevel_path, + int toplevel_fd, + const char *inode_path, + int _inode_fd, /* we always take ownership of the fd, even on failure */ + unsigned tries_left, + unsigned tries_done, + const PickFilter *filter, + PickFlags flags, + PickResult *ret) { + + _cleanup_close_ int inode_fd = TAKE_FD(_inode_fd); + _cleanup_free_ char *resolved_path = NULL; + int r; + + assert(toplevel_fd >= 0 || toplevel_fd == AT_FDCWD); + assert(inode_path); + assert(filter); + assert(ret); + + if (inode_fd < 0 || FLAGS_SET(flags, PICK_RESOLVE)) { + r = chaseat(toplevel_fd, + inode_path, + CHASE_AT_RESOLVE_IN_ROOT, + FLAGS_SET(flags, PICK_RESOLVE) ? &resolved_path : 0, + inode_fd < 0 ? &inode_fd : NULL); + if (r < 0) + return r; + + if (resolved_path) + inode_path = resolved_path; + } + + struct stat st; + if (fstat(inode_fd, &st) < 0) + return log_debug_errno(errno, "Failed to stat discovered inode '%s': %m", prefix_roota(toplevel_path, inode_path)); + + if (filter->type_mask != 0 && + !FLAGS_SET(filter->type_mask, UINT32_C(1) << IFTODT(st.st_mode))) + return log_debug_errno( + SYNTHETIC_ERRNO(errno_from_mode(filter->type_mask, st.st_mode)), + "Inode '%s' has wrong type, found '%s'.", + prefix_roota(toplevel_path, inode_path), + inode_type_to_string(st.st_mode)); + + _cleanup_(pick_result_done) PickResult result = { + .fd = TAKE_FD(inode_fd), + .st = st, + .architecture = filter->architecture, + .tries_left = tries_left, + .tries_done = tries_done, + }; + + result.path = strdup(inode_path); + if (!result.path) + return log_oom_debug(); + + r = strdup_to(&result.version, filter->version); + if (r < 0) + return r; + + *ret = TAKE_PICK_RESULT(result); + return 1; +} + +static int parse_tries(const char *s, unsigned *ret_tries_left, unsigned *ret_tries_done) { + unsigned left, done; + size_t n; + + assert(s); + assert(ret_tries_left); + assert(ret_tries_done); + + if (s[0] != '+') + goto nomatch; + + s++; + + n = strspn(s, DIGITS); + if (n == 0) + goto nomatch; + + if (s[n] == 0) { + if (safe_atou(s, &left) < 0) + goto nomatch; + + done = 0; + } else if (s[n] == '-') { + _cleanup_free_ char *c = NULL; + + c = strndup(s, n); + if (!c) + return -ENOMEM; + + if (safe_atou(c, &left) < 0) + goto nomatch; + + s += n + 1; + + if (!in_charset(s, DIGITS)) + goto nomatch; + + if (safe_atou(s, &done) < 0) + goto nomatch; + } else + goto nomatch; + + *ret_tries_left = left; + *ret_tries_done = done; + return 1; + +nomatch: + *ret_tries_left = *ret_tries_done = UINT_MAX; + return 0; +} + +static int make_choice( + const char *toplevel_path, + int toplevel_fd, + const char *inode_path, + int _inode_fd, /* we always take ownership of the fd, even on failure */ + const PickFilter *filter, + PickFlags flags, + PickResult *ret) { + + static const Architecture local_architectures[] = { + /* In order of preference */ + native_architecture(), +#ifdef ARCHITECTURE_SECONDARY + ARCHITECTURE_SECONDARY, +#endif + _ARCHITECTURE_INVALID, /* accept any arch, as last resort */ + }; + + _cleanup_free_ DirectoryEntries *de = NULL; + _cleanup_free_ char *best_version = NULL, *best_filename = NULL, *p = NULL, *j = NULL; + _cleanup_close_ int dir_fd = -EBADF, object_fd = -EBADF, inode_fd = TAKE_FD(_inode_fd); + const Architecture *architectures; + unsigned best_tries_left = UINT_MAX, best_tries_done = UINT_MAX; + size_t n_architectures, best_architecture_index = SIZE_MAX; + int r; + + assert(toplevel_fd >= 0 || toplevel_fd == AT_FDCWD); + assert(inode_path); + assert(filter); + assert(ret); + + if (inode_fd < 0) { + r = chaseat(toplevel_fd, inode_path, CHASE_AT_RESOLVE_IN_ROOT, NULL, &inode_fd); + if (r < 0) + return r; + } + + /* Maybe the filter is fully specified? Then we can generate the file name directly */ + r = format_fname(filter, flags, &j); + if (r >= 0) { + _cleanup_free_ char *object_path = NULL; + + /* Yay! This worked! */ + p = path_join(inode_path, j); + if (!p) + return log_oom_debug(); + + r = chaseat(toplevel_fd, p, CHASE_AT_RESOLVE_IN_ROOT, &object_path, &object_fd); + if (r == -ENOENT) { + *ret = PICK_RESULT_NULL; + return 0; + } + if (r < 0) + return log_debug_errno(r, "Failed to open '%s': %m", prefix_roota(toplevel_path, p)); + + return pin_choice( + toplevel_path, + toplevel_fd, + FLAGS_SET(flags, PICK_RESOLVE) ? object_path : p, + TAKE_FD(object_fd), /* unconditionally pass ownership of the fd */ + /* tries_left= */ UINT_MAX, + /* tries_done= */ UINT_MAX, + filter, + flags & ~PICK_RESOLVE, + ret); + + } else if (r != -ENOEXEC) + return log_debug_errno(r, "Failed to format file name: %m"); + + /* Underspecified, so we do our enumeration dance */ + + /* Convert O_PATH to a regular directory fd */ + dir_fd = fd_reopen(inode_fd, O_DIRECTORY|O_RDONLY|O_CLOEXEC); + if (dir_fd < 0) + return log_debug_errno(dir_fd, "Failed to reopen '%s' as directory: %m", prefix_roota(toplevel_path, inode_path)); + + r = readdir_all(dir_fd, 0, &de); + if (r < 0) + return log_debug_errno(r, "Failed to read directory '%s': %m", prefix_roota(toplevel_path, inode_path)); + + if (filter->architecture < 0) { + architectures = local_architectures; + n_architectures = ELEMENTSOF(local_architectures); + } else { + architectures = &filter->architecture; + n_architectures = 1; + } + + FOREACH_ARRAY(entry, de->entries, de->n_entries) { + unsigned found_tries_done = UINT_MAX, found_tries_left = UINT_MAX; + _cleanup_free_ char *dname = NULL; + size_t found_architecture_index = SIZE_MAX; + const char *e; + + dname = strdup((*entry)->d_name); + if (!dname) + return log_oom_debug(); + + if (!isempty(filter->basename)) { + e = startswith(dname, filter->basename); + if (!e) + continue; + + if (e[0] != '_') + continue; + + e++; + } else + e = dname; + + if (!strv_isempty(filter->suffix)) { + char *sfx = endswith_strv(e, filter->suffix); + if (!sfx) + continue; + + *sfx = 0; + } + + if (FLAGS_SET(flags, PICK_TRIES)) { + char *plus = strrchr(e, '+'); + if (plus) { + r = parse_tries(plus, &found_tries_left, &found_tries_done); + if (r < 0) + return r; + if (r > 0) /* Found and parsed, now chop off */ + *plus = 0; + } + } + + if (FLAGS_SET(flags, PICK_ARCHITECTURE)) { + char *underscore = strrchr(e, '_'); + Architecture a; + + a = underscore ? architecture_from_string(underscore + 1) : _ARCHITECTURE_INVALID; + + for (size_t i = 0; i < n_architectures; i++) + if (architectures[i] == a) { + found_architecture_index = i; + break; + } + + if (found_architecture_index == SIZE_MAX) { /* No matching arch found */ + log_debug("Found entry with architecture '%s' which is not what we are looking for, ignoring entry.", a < 0 ? "any" : architecture_to_string(a)); + continue; + } + + /* Chop off architecture from string */ + if (underscore) + *underscore = 0; + } + + if (!version_is_valid(e)) { + log_debug("Version string '%s' of entry '%s' is invalid, ignoring entry.", e, (*entry)->d_name); + continue; + } + + if (filter->version && !streq(filter->version, e)) { + log_debug("Found entry with version string '%s', but was looking for '%s', ignoring entry.", e, filter->version); + continue; + } + + if (best_filename) { /* Already found one matching entry? Then figure out the better one */ + int d = 0; + + /* First, prefer entries with tries left over those without */ + if (FLAGS_SET(flags, PICK_TRIES)) + d = CMP(found_tries_left != 0, best_tries_left != 0); + + /* Second, prefer newer versions */ + if (d == 0) + d = strverscmp_improved(e, best_version); + + /* Third, prefer native architectures over secondary architectures */ + if (d == 0 && + FLAGS_SET(flags, PICK_ARCHITECTURE) && + found_architecture_index != SIZE_MAX && best_architecture_index != SIZE_MAX) + d = -CMP(found_architecture_index, best_architecture_index); + + /* Fourth, prefer entries with more tries left */ + if (FLAGS_SET(flags, PICK_TRIES)) { + if (d == 0) + d = CMP(found_tries_left, best_tries_left); + + /* Fifth, prefer entries with fewer attempts done so far */ + if (d == 0) + d = -CMP(found_tries_done, best_tries_done); + } + + /* Last, just compare the filenames as strings */ + if (d == 0) + d = strcmp((*entry)->d_name, best_filename); + + if (d < 0) { + log_debug("Found entry '%s' but previously found entry '%s' matches better, hence skipping entry.", (*entry)->d_name, best_filename); + continue; + } + } + + r = free_and_strdup_warn(&best_version, e); + if (r < 0) + return r; + + r = free_and_strdup_warn(&best_filename, (*entry)->d_name); + if (r < 0) + return r; + + best_architecture_index = found_architecture_index; + best_tries_left = found_tries_left; + best_tries_done = found_tries_done; + } + + if (!best_filename) { /* Everything was good, but we didn't find any suitable entry */ + *ret = PICK_RESULT_NULL; + return 0; + } + + p = path_join(inode_path, best_filename); + if (!p) + return log_oom_debug(); + + object_fd = openat(dir_fd, best_filename, O_CLOEXEC|O_PATH); + if (object_fd < 0) + return log_debug_errno(errno, "Failed to open '%s': %m", prefix_roota(toplevel_path, p)); + + return pin_choice( + toplevel_path, + toplevel_fd, + p, + TAKE_FD(object_fd), + best_tries_left, + best_tries_done, + &(const PickFilter) { + .type_mask = filter->type_mask, + .basename = filter->basename, + .version = empty_to_null(best_version), + .architecture = best_architecture_index != SIZE_MAX ? architectures[best_architecture_index] : _ARCHITECTURE_INVALID, + .suffix = filter->suffix, + }, + flags, + ret); +} + +int path_pick( + const char *toplevel_path, + int toplevel_fd, + const char *path, + const PickFilter *filter, + PickFlags flags, + PickResult *ret) { + + _cleanup_free_ char *filter_bname = NULL, *dir = NULL, *parent = NULL, *fname = NULL; + char * const *filter_suffix_strv = NULL; + const char *filter_suffix = NULL, *enumeration_path; + uint32_t filter_type_mask; + int r; + + assert(toplevel_fd >= 0 || toplevel_fd == AT_FDCWD); + assert(path); + assert(filter); + assert(ret); + + /* Given a path, resolve .v/ subdir logic (if used!), and returns the choice made. This supports + * three ways to be called: + * + * • with a path referring a directory of any name, and filter→basename *explicitly* specified, in + * which case we'll use a pattern "<filter→basename>_*<filter→suffix>" on the directory's files. + * + * • with no filter→basename explicitly specified and a path referring to a directory named in format + * "<somestring><filter→suffix>.v" . In this case the filter basename to search for inside the dir + * is derived from the directory name. Example: "/foo/bar/baz.suffix.v" → we'll search for + * "/foo/bar/baz.suffix.v/baz_*.suffix". + * + * • with a path whose penultimate component ends in ".v/". In this case the final component of the + * path refers to the pattern. Example: "/foo/bar/baz.v/waldo__.suffix" → we'll search for + * "/foo/bar/baz.v/waldo_*.suffix". + */ + + /* Explicit basename specified, then shortcut things and do .v mode regardless of the path name. */ + if (filter->basename) + return make_choice( + toplevel_path, + toplevel_fd, + path, + /* inode_fd= */ -EBADF, + filter, + flags, + ret); + + r = path_extract_filename(path, &fname); + if (r < 0) { + if (r != -EADDRNOTAVAIL) /* root dir or "." */ + return r; + + /* If there's no path element we can derive a pattern off, the don't */ + goto bypass; + } + + /* Remember if the path ends in a dash suffix */ + bool slash_suffix = r == O_DIRECTORY; + + const char *e = endswith(fname, ".v"); + if (e) { + /* So a path in the form /foo/bar/baz.v is specified. In this case our search basename is + * "baz", possibly with a suffix chopped off if there's one specified. */ + filter_bname = strndup(fname, e - fname); + if (!filter_bname) + return -ENOMEM; + + /* Chop off suffix, if specified */ + char *f = endswith_strv(filter_bname, filter->suffix); + if (f) + *f = 0; + + filter_suffix_strv = filter->suffix; + filter_type_mask = filter->type_mask; + + enumeration_path = path; + } else { + /* The path does not end in '.v', hence see if the last element is a pattern. */ + + char *wildcard = strrstr(fname, "___"); + if (!wildcard) + goto bypass; /* Not a pattern, then bypass */ + + /* We found the '___' wildcard, hence everything after it is our filter suffix, and + * everything before is our filter basename */ + *wildcard = 0; + filter_suffix = empty_to_null(wildcard + 3); + + filter_bname = TAKE_PTR(fname); + + r = path_extract_directory(path, &dir); + if (r < 0) { + if (!IN_SET(r, -EDESTADDRREQ, -EADDRNOTAVAIL)) /* only filename specified (no dir), or root or "." */ + return r; + + goto bypass; /* No dir extractable, can't check if parent ends in ".v" */ + } + + r = path_extract_filename(dir, &parent); + if (r < 0) { + if (r != -EADDRNOTAVAIL) /* root dir or "." */ + return r; + + goto bypass; /* Cannot extract fname from parent dir, can't check if it ends in ".v" */ + } + + e = endswith(parent, ".v"); + if (!e) + goto bypass; /* Doesn't end in ".v", shortcut */ + + filter_type_mask = filter->type_mask; + if (slash_suffix) { + /* If the pattern is suffixed by a / then we are looking for directories apparently. */ + if (filter_type_mask != 0 && !FLAGS_SET(filter_type_mask, UINT32_C(1) << DT_DIR)) + return log_debug_errno(SYNTHETIC_ERRNO(errno_from_mode(filter_type_mask, S_IFDIR)), + "Specified pattern ends in '/', but not looking for directories, refusing."); + filter_type_mask = UINT32_C(1) << DT_DIR; + } + + enumeration_path = dir; + } + + return make_choice( + toplevel_path, + toplevel_fd, + enumeration_path, + /* inode_fd= */ -EBADF, + &(const PickFilter) { + .type_mask = filter_type_mask, + .basename = filter_bname, + .version = filter->version, + .architecture = filter->architecture, + .suffix = filter_suffix_strv ?: STRV_MAKE(filter_suffix), + }, + flags, + ret); + +bypass: + /* Don't make any choice, but just use the passed path literally */ + return pin_choice( + toplevel_path, + toplevel_fd, + path, + /* inode_fd= */ -EBADF, + /* tries_left= */ UINT_MAX, + /* tries_done= */ UINT_MAX, + filter, + flags, + ret); +} + +int path_pick_update_warn( + char **path, + const PickFilter *filter, + PickFlags flags, + PickResult *ret_result) { + + _cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL; + int r; + + assert(path); + assert(*path); + assert(filter); + + /* This updates the first argument if needed! */ + + r = path_pick(/* toplevel_path= */ NULL, + /* toplevel_fd= */ AT_FDCWD, + *path, + filter, + flags, + &result); + if (r == -ENOENT) { + log_debug("Path '%s' doesn't exist, leaving as is.", *path); + + if (ret_result) + *ret_result = PICK_RESULT_NULL; + return 0; + } + if (r < 0) + return log_error_errno(r, "Failed to pick version on path '%s': %m", *path); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "No matching entries in versioned directory '%s' found.", *path); + + log_debug("Resolved versioned directory pattern '%s' to file '%s' as version '%s'.", result.path, *path, strna(result.version)); + + if (ret_result) { + r = free_and_strdup_warn(path, result.path); + if (r < 0) + return r; + + *ret_result = TAKE_PICK_RESULT(result); + } else + free_and_replace(*path, result.path); + + return 1; +} + +const PickFilter pick_filter_image_raw = { + .type_mask = (UINT32_C(1) << DT_REG) | (UINT32_C(1) << DT_BLK), + .architecture = _ARCHITECTURE_INVALID, + .suffix = STRV_MAKE(".raw"), +}; + +const PickFilter pick_filter_image_dir = { + .type_mask = UINT32_C(1) << DT_DIR, + .architecture = _ARCHITECTURE_INVALID, +}; + +const PickFilter pick_filter_image_any = { + .type_mask = (UINT32_C(1) << DT_REG) | (UINT32_C(1) << DT_BLK) | (UINT32_C(1) << DT_DIR), + .architecture = _ARCHITECTURE_INVALID, + .suffix = STRV_MAKE(".raw", ""), +}; diff --git a/src/shared/vpick.h b/src/shared/vpick.h new file mode 100644 index 0000000..38251c8 --- /dev/null +++ b/src/shared/vpick.h @@ -0,0 +1,61 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include <sys/types.h> + +#include "architecture.h" + +typedef enum PickFlags { + PICK_ARCHITECTURE = 1 << 0, /* Look for an architecture suffix */ + PICK_TRIES = 1 << 1, /* Look for tries left/tries done counters */ + PICK_RESOLVE = 1 << 2, /* Return the fully resolved (chased) path, rather than the path to the entry */ +} PickFlags; + +typedef struct PickFilter { + uint32_t type_mask; /* A mask of 1U << DT_REG, 1U << DT_DIR, … */ + const char *basename; /* Can be overridden by search pattern */ + const char *version; + Architecture architecture; + char * const *suffix; /* Can be overridden by search pattern */ +} PickFilter; + +typedef struct PickResult { + char *path; + int fd; /* O_PATH */ + struct stat st; + char *version; + Architecture architecture; + unsigned tries_left; + unsigned tries_done; +} PickResult; + +#define PICK_RESULT_NULL \ + (const PickResult) { \ + .fd = -EBADF, \ + .st.st_mode = MODE_INVALID, \ + .architecture = _ARCHITECTURE_INVALID, \ + .tries_left = UINT_MAX, \ + .tries_done = UINT_MAX, \ + } + +#define TAKE_PICK_RESULT(pick) TAKE_GENERIC(pick, PickResult, PICK_RESULT_NULL) + +void pick_result_done(PickResult *p); + +int path_pick( + const char *toplevel_path, + int toplevel_fd, + const char *path, + const PickFilter *filter, + PickFlags flags, + PickResult *ret); + +int path_pick_update_warn( + char **path, + const PickFilter *filter, + PickFlags flags, + PickResult *ret); + +extern const PickFilter pick_filter_image_raw; +extern const PickFilter pick_filter_image_dir; +extern const PickFilter pick_filter_image_any; diff --git a/src/shared/wall.c b/src/shared/wall.c index d5900ef..c5d6439 100644 --- a/src/shared/wall.c +++ b/src/shared/wall.c @@ -30,8 +30,9 @@ static int write_to_terminal(const char *tty, const char *message) { fd = open(tty, O_WRONLY|O_NONBLOCK|O_NOCTTY|O_CLOEXEC); if (fd < 0) return -errno; - if (!isatty(fd)) - return -ENOTTY; + + if (!isatty_safe(fd)) + return -errno; return loop_write_full(fd, message, SIZE_MAX, TIMEOUT_USEC); } diff --git a/src/shared/watchdog.c b/src/shared/watchdog.c index 99ccefb..810c5b5 100644 --- a/src/shared/watchdog.c +++ b/src/shared/watchdog.c @@ -41,7 +41,7 @@ static int saturated_usec_to_sec(usec_t val) { return MIN(t, (usec_t) WATCHDOG_TIMEOUT_MAX_SEC); /* Saturate to watchdog max */ } -static int get_watchdog_sysfs_path(const char *filename, char **ret_path) { +static int watchdog_get_sysfs_path(const char *filename, char **ret_path) { struct stat st; if (watchdog_fd < 0) @@ -59,11 +59,11 @@ static int get_watchdog_sysfs_path(const char *filename, char **ret_path) { return 0; } -static int get_pretimeout_governor(char **ret_gov) { +static int watchdog_get_pretimeout_governor(char **ret_gov) { _cleanup_free_ char *sys_fn = NULL; int r; - r = get_watchdog_sysfs_path("pretimeout_governor", &sys_fn); + r = watchdog_get_sysfs_path("pretimeout_governor", &sys_fn); if (r < 0) return r; @@ -78,14 +78,14 @@ static int get_pretimeout_governor(char **ret_gov) { return 0; } -static int set_pretimeout_governor(const char *governor) { +static int watchdog_set_pretimeout_governor(const char *governor) { _cleanup_free_ char *sys_fn = NULL; int r; if (isempty(governor)) return 0; /* Nothing to do */ - r = get_watchdog_sysfs_path("pretimeout_governor", &sys_fn); + r = watchdog_get_sysfs_path("pretimeout_governor", &sys_fn); if (r < 0) return r; @@ -205,7 +205,7 @@ static int watchdog_ping_now(void) { return 0; } -static int update_pretimeout(void) { +static int watchdog_update_pretimeout(void) { _cleanup_free_ char *governor = NULL; int r, t_sec, pt_sec; @@ -223,9 +223,9 @@ static int update_pretimeout(void) { watchdog_supports_pretimeout = false; /* Update the pretimeout governor as well */ - (void) set_pretimeout_governor(watchdog_pretimeout_governor); + (void) watchdog_set_pretimeout_governor(watchdog_pretimeout_governor); - r = get_pretimeout_governor(&governor); + r = watchdog_get_pretimeout_governor(&governor); if (r < 0) return log_warning_errno(r, "Watchdog: failed to read pretimeout governor: %m"); if (isempty(governor)) @@ -259,7 +259,7 @@ static int update_pretimeout(void) { return r; } -static int update_timeout(void) { +static int watchdog_update_timeout(void) { int r; usec_t previous_timeout; @@ -296,7 +296,7 @@ static int update_timeout(void) { * changed as well by the driver or the kernel so we need to update the * pretimeout now. Or if the watchdog is being configured for the first * time, we want to configure the pretimeout before it is enabled. */ - (void) update_pretimeout(); + (void) watchdog_update_pretimeout(); r = watchdog_set_enable(true); if (r < 0) @@ -307,7 +307,7 @@ static int update_timeout(void) { return watchdog_ping_now(); } -static int open_watchdog(void) { +static int watchdog_open(void) { struct watchdog_info ident; char **try_order; int r; @@ -338,7 +338,9 @@ static int open_watchdog(void) { } if (watchdog_fd < 0) - return log_debug_errno(SYNTHETIC_ERRNO(ENOENT), "Failed to open watchdog device %s: %m", watchdog_device ?: "auto"); + return log_debug_errno(SYNTHETIC_ERRNO(ENOENT), "Failed to open watchdog device %s.", watchdog_device ?: "auto"); + + watchdog_last_ping = USEC_INFINITY; if (ioctl(watchdog_fd, WDIOC_GETSUPPORT, &ident) < 0) log_debug_errno(errno, "Hardware watchdog %s does not support WDIOC_GETSUPPORT ioctl, ignoring: %m", watchdog_device); @@ -348,7 +350,7 @@ static int open_watchdog(void) { ident.firmware_version, watchdog_device); - r = update_timeout(); + r = watchdog_update_timeout(); if (r < 0) goto close_and_fail; @@ -396,9 +398,9 @@ int watchdog_setup(usec_t timeout) { watchdog_timeout = timeout; if (watchdog_fd < 0) - return open_watchdog(); + return watchdog_open(); - r = update_timeout(); + r = watchdog_update_timeout(); if (r < 0) watchdog_timeout = previous_timeout; @@ -415,17 +417,17 @@ int watchdog_setup_pretimeout(usec_t timeout) { * even if it fails to update the timeout. */ watchdog_pretimeout = timeout; - return update_pretimeout(); + return watchdog_update_pretimeout(); } int watchdog_setup_pretimeout_governor(const char *governor) { if (free_and_strdup(&watchdog_pretimeout_governor, governor) < 0) return -ENOMEM; - return set_pretimeout_governor(watchdog_pretimeout_governor); + return watchdog_set_pretimeout_governor(watchdog_pretimeout_governor); } -static usec_t calc_timeout(void) { +static usec_t watchdog_calc_timeout(void) { /* Calculate the effective timeout which accounts for the watchdog * pretimeout if configured and supported. */ if (watchdog_supports_pretimeout && timestamp_is_set(watchdog_pretimeout) && watchdog_timeout >= watchdog_pretimeout) @@ -435,7 +437,7 @@ static usec_t calc_timeout(void) { } usec_t watchdog_runtime_wait(void) { - usec_t timeout = calc_timeout(); + usec_t timeout = watchdog_calc_timeout(); if (!timestamp_is_set(timeout)) return USEC_INFINITY; @@ -458,10 +460,10 @@ int watchdog_ping(void) { if (watchdog_fd < 0) /* open_watchdog() will automatically ping the device for us if necessary */ - return open_watchdog(); + return watchdog_open(); ntime = now(CLOCK_BOOTTIME); - timeout = calc_timeout(); + timeout = watchdog_calc_timeout(); /* Never ping earlier than watchdog_timeout/4 and try to ping * by watchdog_timeout/2 plus scheduling latencies at the latest */ diff --git a/src/shared/wifi-util.c b/src/shared/wifi-util.c index d4e6dca..052f560 100644 --- a/src/shared/wifi-util.c +++ b/src/shared/wifi-util.c @@ -55,7 +55,7 @@ int wifi_get_interface(sd_netlink *genl, int ifindex, enum nl80211_iftype *ret_i if (r < 0) return log_debug_errno(r, "Failed to get NL80211_ATTR_IFTYPE attribute: %m"); - r = sd_netlink_message_read_data_suffix0(reply, NL80211_ATTR_SSID, &len, (void**) &ssid); + r = sd_netlink_message_read_data(reply, NL80211_ATTR_SSID, &len, (void**) &ssid); if (r < 0 && r != -ENODATA) return log_debug_errno(r, "Failed to get NL80211_ATTR_SSID attribute: %m"); if (r >= 0) { |