diff options
Diffstat (limited to 'src')
127 files changed, 1293 insertions, 677 deletions
diff --git a/src/analyze/analyze-srk.c b/src/analyze/analyze-srk.c index 0e24b41..6faf2c2 100644 --- a/src/analyze/analyze-srk.c +++ b/src/analyze/analyze-srk.c @@ -11,9 +11,9 @@ int verb_srk(int argc, char *argv[], void *userdata) { _cleanup_(Esys_Freep) TPM2B_PUBLIC *public = NULL; int r; - r = tpm2_context_new(/* device= */ NULL, &c); + r = tpm2_context_new_or_warn(/* device= */ NULL, &c); if (r < 0) - return log_error_errno(r, "Failed to create TPM2 context: %m"); + return r; r = tpm2_get_srk( c, diff --git a/src/backlight/backlight.c b/src/backlight/backlight.c index 5ac9f90..b2032ad 100644 --- a/src/backlight/backlight.c +++ b/src/backlight/backlight.c @@ -55,6 +55,10 @@ static int has_multiple_graphics_cards(void) { if (r < 0) return r; + r = sd_device_enumerator_allow_uninitialized(e); + if (r < 0) + return r; + r = sd_device_enumerator_add_match_subsystem(e, "pci", /* match = */ true); if (r < 0) return r; diff --git a/src/basic/chase.c b/src/basic/chase.c index 26bc2d6..9f5477e 100644 --- a/src/basic/chase.c +++ b/src/basic/chase.c @@ -374,11 +374,11 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int return r; if (FLAGS_SET(flags, CHASE_MKDIR_0755) && !isempty(todo)) { - child = xopenat(fd, - first, - O_DIRECTORY|O_CREAT|O_EXCL|O_NOFOLLOW|O_CLOEXEC, - /* xopen_flags = */ 0, - 0755); + child = xopenat_full(fd, + first, + O_DIRECTORY|O_CREAT|O_EXCL|O_NOFOLLOW|O_CLOEXEC, + /* xopen_flags = */ 0, + 0755); if (child < 0) return child; } else if (FLAGS_SET(flags, CHASE_PARENT) && isempty(todo)) { @@ -760,10 +760,10 @@ int chase_and_open(const char *path, const char *root, ChaseFlags chase_flags, i if (empty_or_root(root) && !ret_path && (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0) /* Shortcut this call if none of the special features of this call are requested */ - return xopenat(AT_FDCWD, path, - open_flags | (FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0), - /* xopen_flags = */ 0, - mode); + return xopenat_full(AT_FDCWD, path, + open_flags | (FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0), + /* xopen_flags = */ 0, + mode); r = chase(path, root, CHASE_PARENT|chase_flags, &p, &path_fd); if (r < 0) @@ -777,7 +777,7 @@ int chase_and_open(const char *path, const char *root, ChaseFlags chase_flags, i return r; } - r = xopenat(path_fd, strempty(fname), open_flags|O_NOFOLLOW, /* xopen_flags = */ 0, mode); + r = xopenat_full(path_fd, strempty(fname), open_flags|O_NOFOLLOW, /* xopen_flags = */ 0, mode); if (r < 0) return r; @@ -964,10 +964,10 @@ int chase_and_openat(int dir_fd, const char *path, ChaseFlags chase_flags, int o if (dir_fd == AT_FDCWD && !ret_path && (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0) /* Shortcut this call if none of the special features of this call are requested */ - return xopenat(dir_fd, path, - open_flags | (FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0), - /* xopen_flags = */ 0, - mode); + return xopenat_full(dir_fd, path, + open_flags | (FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0), + /* xopen_flags = */ 0, + mode); r = chaseat(dir_fd, path, chase_flags|CHASE_PARENT, &p, &path_fd); if (r < 0) @@ -979,7 +979,7 @@ int chase_and_openat(int dir_fd, const char *path, ChaseFlags chase_flags, int o return r; } - r = xopenat(path_fd, strempty(fname), open_flags|O_NOFOLLOW, /* xopen_flags = */ 0, mode); + r = xopenat_full(path_fd, strempty(fname), open_flags|O_NOFOLLOW, /* xopen_flags = */ 0, mode); if (r < 0) return r; diff --git a/src/basic/chattr-util.c b/src/basic/chattr-util.c index fe8b9ab..d76be5c 100644 --- a/src/basic/chattr-util.c +++ b/src/basic/chattr-util.c @@ -29,7 +29,7 @@ int chattr_full( assert(dir_fd >= 0 || dir_fd == AT_FDCWD); - fd = xopenat(dir_fd, path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, /* xopen_flags = */ 0, /* mode = */ 0); + fd = xopenat(dir_fd, path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); if (fd < 0) return fd; diff --git a/src/basic/env-util.c b/src/basic/env-util.c index d3bf733..a97651d 100644 --- a/src/basic/env-util.c +++ b/src/basic/env-util.c @@ -983,8 +983,8 @@ int putenv_dup(const char *assignment, bool override) { } int setenv_systemd_exec_pid(bool update_only) { - char str[DECIMAL_STR_MAX(pid_t)]; const char *e; + int r; /* Update $SYSTEMD_EXEC_PID=pid except when '*' is set for the variable. */ @@ -995,10 +995,9 @@ int setenv_systemd_exec_pid(bool update_only) { if (streq_ptr(e, "*")) return 0; - xsprintf(str, PID_FMT, getpid_cached()); - - if (setenv("SYSTEMD_EXEC_PID", str, 1) < 0) - return -errno; + r = setenvf("SYSTEMD_EXEC_PID", /* overwrite= */ 1, PID_FMT, getpid_cached()); + if (r < 0) + return r; return 1; } @@ -1093,3 +1092,25 @@ int set_full_environment(char **env) { return 0; } + +int setenvf(const char *name, bool overwrite, const char *valuef, ...) { + _cleanup_free_ char *value = NULL; + va_list ap; + int r; + + assert(name); + + if (!valuef) + return RET_NERRNO(unsetenv(name)); + + va_start(ap, valuef); + DISABLE_WARNING_FORMAT_NONLITERAL; + r = vasprintf(&value, valuef, ap); + REENABLE_WARNING; + va_end(ap); + + if (r < 0) + return -ENOMEM; + + return RET_NERRNO(setenv(name, value, overwrite)); +} diff --git a/src/basic/env-util.h b/src/basic/env-util.h index f7fb1e9..34cf1f9 100644 --- a/src/basic/env-util.h +++ b/src/basic/env-util.h @@ -79,3 +79,5 @@ int getenv_path_list(const char *name, char ***ret_paths); int getenv_steal_erase(const char *name, char **ret); int set_full_environment(char **env); + +int setenvf(const char *name, bool overwrite, const char *valuef, ...) _printf_(3,4); diff --git a/src/basic/filesystems-gperf.gperf b/src/basic/filesystems-gperf.gperf index e8c5357..1cd66b5 100644 --- a/src/basic/filesystems-gperf.gperf +++ b/src/basic/filesystems-gperf.gperf @@ -91,6 +91,7 @@ ocfs2, {OCFS2_SUPER_MAGIC} openpromfs, {OPENPROM_SUPER_MAGIC} orangefs, {ORANGEFS_DEVREQ_MAGIC} overlay, {OVERLAYFS_SUPER_MAGIC} +pidfs, {PID_FS_MAGIC} pipefs, {PIPEFS_MAGIC} ppc-cmm, {PPC_CMM_MAGIC} proc, {PROC_SUPER_MAGIC} diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c index 9ba9268..5bc7d2f 100644 --- a/src/basic/fs-util.c +++ b/src/basic/fs-util.c @@ -1054,7 +1054,7 @@ int open_mkdir_at(int dirfd, const char *path, int flags, mode_t mode) { path = fname; } - fd = xopenat(dirfd, path, flags|O_CREAT|O_DIRECTORY|O_NOFOLLOW, /* xopen_flags = */ 0, mode); + fd = xopenat_full(dirfd, path, flags|O_CREAT|O_DIRECTORY|O_NOFOLLOW, /* xopen_flags = */ 0, mode); if (IN_SET(fd, -ELOOP, -ENOTDIR)) return -EEXIST; if (fd < 0) @@ -1110,7 +1110,7 @@ int openat_report_new(int dirfd, const char *pathname, int flags, mode_t mode, b } } -int xopenat(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_flags, mode_t mode) { +int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_flags, mode_t mode) { _cleanup_close_ int fd = -EBADF; bool made = false; int r; @@ -1191,7 +1191,7 @@ int xopenat(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_flags return TAKE_FD(fd); } -int xopenat_lock( +int xopenat_lock_full( int dir_fd, const char *path, int open_flags, @@ -1214,7 +1214,7 @@ int xopenat_lock( for (;;) { struct stat st; - fd = xopenat(dir_fd, path, open_flags, xopen_flags, mode); + fd = xopenat_full(dir_fd, path, open_flags, xopen_flags, mode); if (fd < 0) return fd; diff --git a/src/basic/fs-util.h b/src/basic/fs-util.h index 1023ab7..6a1e2e7 100644 --- a/src/basic/fs-util.h +++ b/src/basic/fs-util.h @@ -137,6 +137,12 @@ typedef enum XOpenFlags { XO_SUBVOLUME = 1 << 1, } XOpenFlags; -int xopenat(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_flags, mode_t mode); +int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_flags, mode_t mode); +static inline int xopenat(int dir_fd, const char *path, int open_flags) { + return xopenat_full(dir_fd, path, open_flags, 0, 0); +} -int xopenat_lock(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_flags, mode_t mode, LockType locktype, int operation); +int xopenat_lock_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_flags, mode_t mode, LockType locktype, int operation); +static inline int xopenat_lock(int dir_fd, const char *path, int open_flags, LockType locktype, int operation) { + return xopenat_lock_full(dir_fd, path, open_flags, 0, 0, locktype, operation); +} diff --git a/src/basic/hashmap.h b/src/basic/hashmap.h index 233f1d7..d0ebdf5 100644 --- a/src/basic/hashmap.h +++ b/src/basic/hashmap.h @@ -39,8 +39,8 @@ typedef struct IteratedCache IteratedCache; /* Caches the iterated order of on * by hashmap users, so the definition has to be here. Do not use its fields * directly. */ typedef struct { - unsigned idx; /* index of an entry to be iterated next */ const void *next_key; /* expected value of that entry's key pointer */ + unsigned idx; /* index of an entry to be iterated next */ #if ENABLE_DEBUG_HASHMAP unsigned put_count; /* hashmap's put_count recorded at start of iteration */ unsigned rem_count; /* hashmap's rem_count in previous iteration */ diff --git a/src/basic/lock-util.c b/src/basic/lock-util.c index 047fd01..7bffe85 100644 --- a/src/basic/lock-util.c +++ b/src/basic/lock-util.c @@ -40,13 +40,13 @@ int make_lock_file_at(int dir_fd, const char *p, int operation, LockFile *ret) { if (!t) return -ENOMEM; - fd = xopenat_lock(dfd, - p, - O_CREAT|O_RDWR|O_NOFOLLOW|O_CLOEXEC|O_NOCTTY, - /* xopen_flags = */ 0, - 0600, - LOCK_UNPOSIX, - operation); + fd = xopenat_lock_full(dfd, + p, + O_CREAT|O_RDWR|O_NOFOLLOW|O_CLOEXEC|O_NOCTTY, + /* xopen_flags = */ 0, + 0600, + LOCK_UNPOSIX, + operation); if (fd < 0) return fd == -EAGAIN ? -EBUSY : fd; diff --git a/src/basic/log.c b/src/basic/log.c index 1470611..7a44300 100644 --- a/src/basic/log.c +++ b/src/basic/log.c @@ -427,6 +427,8 @@ static int write_to_console( const char *func, const char *buffer) { + static int dumb = -1; + char location[256], header_time[FORMAT_TIMESTAMP_MAX], prefix[1 + DECIMAL_STR_MAX(int) + 2], @@ -438,6 +440,9 @@ static int write_to_console( if (console_fd < 0) return 0; + if (dumb < 0) + dumb = getenv_terminal_is_dumb(); + if (log_target == LOG_TARGET_CONSOLE_PREFIXED) { xsprintf(prefix, "<%i>", level); iovec[n++] = IOVEC_MAKE_STRING(prefix); @@ -481,8 +486,9 @@ static int write_to_console( /* When writing to a TTY we output an extra '\r' (i.e. CR) first, to generate CRNL rather than just * NL. This is a robustness thing in case the TTY is currently in raw mode (specifically: has the * ONLCR flag off). We want that subsequent output definitely starts at the beginning of the line - * again, after all. If the TTY is not in raw mode the extra CR should not hurt. */ - iovec[n++] = IOVEC_MAKE_STRING(check_console_fd_is_tty() ? "\r\n" : "\n"); + * again, after all. If the TTY is not in raw mode the extra CR should not hurt. If we're writing to + * a dumb terminal, only write NL as CRNL might be interpreted as a double newline. */ + iovec[n++] = IOVEC_MAKE_STRING(check_console_fd_is_tty() && !dumb ? "\r\n" : "\n"); if (writev(console_fd, iovec, n) < 0) { diff --git a/src/basic/meson.build b/src/basic/meson.build index d7450d8..111253e 100644 --- a/src/basic/meson.build +++ b/src/basic/meson.build @@ -235,7 +235,7 @@ filesystem_includes = ['linux/magic.h', check_filesystems = find_program('check-filesystems.sh') r = run_command([check_filesystems, cpp, files('filesystems-gperf.gperf')] + filesystem_includes, check: false) if r.returncode() != 0 - error('Unknown filesystems defined in kernel headers:\n\n' + r.stdout()) + warning('Unknown filesystems defined in kernel headers:\n\n' + r.stdout()) endif filesystems_gperf_h = custom_target( diff --git a/src/basic/missing_magic.h b/src/basic/missing_magic.h index 27a33ad..82d71c8 100644 --- a/src/basic/missing_magic.h +++ b/src/basic/missing_magic.h @@ -128,6 +128,11 @@ #define DEVMEM_MAGIC 0x454d444d #endif +/* cb12fd8e0dabb9a1c8aef55a6a41e2c255fcdf4b (6.8) */ +#ifndef PID_FS_MAGIC +#define PID_FS_MAGIC 0x50494446 +#endif + /* Not in mainline but included in Ubuntu */ #ifndef SHIFTFS_MAGIC #define SHIFTFS_MAGIC 0x6a656a62 diff --git a/src/basic/os-util.c b/src/basic/os-util.c index dbd067f..985d89b 100644 --- a/src/basic/os-util.c +++ b/src/basic/os-util.c @@ -61,6 +61,39 @@ bool image_name_is_valid(const char *s) { return true; } +int path_extract_image_name(const char *path, char **ret) { + _cleanup_free_ char *fn = NULL; + int r; + + assert(path); + assert(ret); + + /* Extract last component from path, without any "/" suffixes. */ + r = path_extract_filename(path, &fn); + if (r < 0) + return r; + + if (r != O_DIRECTORY) { + /* Chop off any image suffixes we recognize (unless we already know this must refer to some dir */ + FOREACH_STRING(suffix, ".sysext.raw", ".confext.raw", ".raw") { + char *m = endswith(fn, suffix); + if (m) { + *m = 0; + break; + } + } + } + + /* Truncate the version/counting suffixes */ + fn[strcspn(fn, "_+")] = 0; + + if (!image_name_is_valid(fn)) + return -EINVAL; + + *ret = TAKE_PTR(fn); + return 0; +} + int path_is_extension_tree(ImageClass image_class, const char *path, const char *extension, bool relax_extension_release_check) { int r; @@ -230,9 +263,25 @@ int open_extension_release_at( continue; } - if (!relax_extension_release_check && - extension_release_strict_xattr_value(fd, dir_path, de->d_name) != 0) - continue; + if (!relax_extension_release_check) { + _cleanup_free_ char *base_image_name = NULL, *base_extension = NULL; + + r = path_extract_image_name(image_name, &base_image_name); + if (r < 0) { + log_debug_errno(r, "Failed to extract image name from %s/%s, ignoring: %m", dir_path, de->d_name); + continue; + } + + r = path_extract_image_name(extension, &base_extension); + if (r < 0) { + log_debug_errno(r, "Failed to extract image name from %s, ignoring: %m", extension); + continue; + } + + if (!streq(base_image_name, base_extension) && + extension_release_strict_xattr_value(fd, dir_path, image_name) != 0) + continue; + } /* We already found what we were looking for, but there's another candidate? We treat this as * an error, as we want to enforce that there are no ambiguities in case we are in the diff --git a/src/basic/os-util.h b/src/basic/os-util.h index 7cee3dd..f6a12a3 100644 --- a/src/basic/os-util.h +++ b/src/basic/os-util.h @@ -25,6 +25,7 @@ ImageClass image_class_from_string(const char *s) _pure_; * in accordance with the OS extension specification, rather than for /usr/lib/ or /etc/os-release. */ bool image_name_is_valid(const char *s) _pure_; +int path_extract_image_name(const char *path, char **ret); int path_is_extension_tree(ImageClass image_class, const char *path, const char *extension, bool relax_extension_release_check); static inline int path_is_os_tree(const char *path) { diff --git a/src/basic/stat-util.c b/src/basic/stat-util.c index c54374b..581370d 100644 --- a/src/basic/stat-util.c +++ b/src/basic/stat-util.c @@ -262,11 +262,31 @@ int path_is_network_fs(const char *path) { return is_network_fs(&s); } +int stat_verify_linked(const struct stat *st) { + assert(st); + + if (st->st_nlink <= 0) + return -EIDRM; /* recognizable error. */ + + return 0; +} + +int fd_verify_linked(int fd) { + struct stat st; + + assert(fd >= 0); + + if (fstat(fd, &st) < 0) + return -errno; + + return stat_verify_linked(&st); +} + int stat_verify_regular(const struct stat *st) { assert(st); - /* Checks whether the specified stat() structure refers to a regular file. If not returns an appropriate error - * code. */ + /* Checks whether the specified stat() structure refers to a regular file. If not returns an + * appropriate error code. */ if (S_ISDIR(st->st_mode)) return -EISDIR; @@ -470,7 +490,7 @@ int xstatfsat(int dir_fd, const char *path, struct statfs *ret) { assert(dir_fd >= 0 || dir_fd == AT_FDCWD); assert(ret); - fd = xopenat(dir_fd, path, O_PATH|O_CLOEXEC|O_NOCTTY, /* xopen_flags = */ 0, /* mode = */ 0); + fd = xopenat(dir_fd, path, O_PATH|O_CLOEXEC|O_NOCTTY); if (fd < 0) return fd; diff --git a/src/basic/stat-util.h b/src/basic/stat-util.h index ae0aaf8..3501406 100644 --- a/src/basic/stat-util.h +++ b/src/basic/stat-util.h @@ -71,6 +71,9 @@ int path_is_network_fs(const char *path); */ #define F_TYPE_EQUAL(a, b) (a == (typeof(a)) b) +int stat_verify_linked(const struct stat *st); +int fd_verify_linked(int fd); + int stat_verify_regular(const struct stat *st); int fd_verify_regular(int fd); int verify_regular_at(int dir_fd, const char *path, bool follow); diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index 3355b74..530ef9a 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -1300,7 +1300,7 @@ static bool on_dev_null(void) { return cached_on_dev_null; } -static bool getenv_terminal_is_dumb(void) { +bool getenv_terminal_is_dumb(void) { const char *e; e = getenv("TERM"); diff --git a/src/basic/terminal-util.h b/src/basic/terminal-util.h index 2a7d48b..b1d7aee 100644 --- a/src/basic/terminal-util.h +++ b/src/basic/terminal-util.h @@ -160,6 +160,7 @@ void columns_lines_cache_reset(int _unused_ signum); void reset_terminal_feature_caches(void); bool on_tty(void); +bool getenv_terminal_is_dumb(void); bool terminal_is_dumb(void); ColorMode get_color_mode(void); bool underline_enabled(void); @@ -186,7 +187,7 @@ static inline bool colors_enabled(void) { } static inline const char *ansi_underline(void) { - return underline_enabled() ? ANSI_UNDERLINE : ANSI_NORMAL; + return underline_enabled() ? ANSI_UNDERLINE : ""; } #define DEFINE_ANSI_FUNC_UNDERLINE(name, NAME) \ diff --git a/src/basic/virt.c b/src/basic/virt.c index 93ccfaa..88357a9 100644 --- a/src/basic/virt.c +++ b/src/basic/virt.c @@ -178,6 +178,7 @@ static Virtualization detect_vm_dmi_vendor(void) { { "VMW", VIRTUALIZATION_VMWARE }, { "innotek GmbH", VIRTUALIZATION_ORACLE }, { "VirtualBox", VIRTUALIZATION_ORACLE }, + { "Oracle Corporation", VIRTUALIZATION_ORACLE }, /* Detect VirtualBox on some proprietary systems via the board_vendor */ { "Xen", VIRTUALIZATION_XEN }, { "Bochs", VIRTUALIZATION_BOCHS }, { "Parallels", VIRTUALIZATION_PARALLELS }, diff --git a/src/boot/efi/boot.c b/src/boot/efi/boot.c index 5c0f0ab..a3d5607 100644 --- a/src/boot/efi/boot.c +++ b/src/boot/efi/boot.c @@ -2250,9 +2250,9 @@ static EFI_STATUS initrd_prepare( assert(ret_initrd_size); if (entry->type != LOADER_LINUX || !entry->initrd) { - ret_options = NULL; - ret_initrd = NULL; - ret_initrd_size = 0; + *ret_options = NULL; + *ret_initrd = NULL; + *ret_initrd_size = 0; return EFI_SUCCESS; } diff --git a/src/boot/efi/cpio.c b/src/boot/efi/cpio.c index 5b90e17..c4f803c 100644 --- a/src/boot/efi/cpio.c +++ b/src/boot/efi/cpio.c @@ -65,7 +65,7 @@ static EFI_STATUS pack_cpio_one( char *a; assert(fname); - assert(contents_size || contents_size == 0); + assert(contents || contents_size == 0); assert(target_dir_prefix); assert(inode_counter); assert(cpio_buffer); diff --git a/src/boot/efi/meson.build b/src/boot/efi/meson.build index c95132e..43727ef 100644 --- a/src/boot/efi/meson.build +++ b/src/boot/efi/meson.build @@ -404,6 +404,11 @@ foreach efi_elf_binary : efi_elf_binaries if name == 'addon@0@.efi.stub'.format(efi_arch) efi_addon = exe.full_path() endif + + test('check-alignment-@0@'.format(name), + check_efi_alignment_py, + args : exe.full_path(), + suite : 'efi') endforeach alias_target('systemd-boot', boot_targets) diff --git a/src/boot/efi/stub.c b/src/boot/efi/stub.c index 7ef3e76..0d9df7e 100644 --- a/src/boot/efi/stub.c +++ b/src/boot/efi/stub.c @@ -540,6 +540,10 @@ static EFI_STATUS run(EFI_HANDLE image) { CLEANUP_ARRAY(dt_filenames_addons_global, n_dts_addons_global, dt_filenames_free); CLEANUP_ARRAY(dt_filenames_addons_uki, n_dts_addons_uki, dt_filenames_free); + if (szs[UNIFIED_SECTION_UNAME] > 0) + uname = xstrndup8((char *)loaded_image->ImageBase + addrs[UNIFIED_SECTION_UNAME], + szs[UNIFIED_SECTION_UNAME]); + /* Now that we have the UKI sections loaded, also load global first and then local (per-UKI) * addons. The data is loaded at once, and then used later. */ err = load_addons( @@ -614,10 +618,6 @@ static EFI_STATUS run(EFI_HANDLE image) { /* Show splash screen as early as possible */ graphics_splash((const uint8_t*) loaded_image->ImageBase + addrs[UNIFIED_SECTION_SPLASH], szs[UNIFIED_SECTION_SPLASH]); - if (szs[UNIFIED_SECTION_UNAME] > 0) - uname = xstrndup8((char *)loaded_image->ImageBase + addrs[UNIFIED_SECTION_UNAME], - szs[UNIFIED_SECTION_UNAME]); - if (use_load_options(image, loaded_image, szs[UNIFIED_SECTION_CMDLINE] > 0, &cmdline)) { /* Let's measure the passed kernel command line into the TPM. Note that this possibly * duplicates what we already did in the boot menu, if that was already used. However, since diff --git a/src/busctl/busctl.c b/src/busctl/busctl.c index 39d22f2..01cb896 100644 --- a/src/busctl/busctl.c +++ b/src/busctl/busctl.c @@ -2021,6 +2021,15 @@ static int call(int argc, char **argv, void *userdata) { if (r < 0) return r; + if (!service_name_is_valid(argv[1])) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid service name: %s", argv[1]); + if (!object_path_is_valid(argv[2])) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid object path: %s", argv[2]); + if (!interface_name_is_valid(argv[3])) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid interface name: %s", argv[3]); + if (!member_name_is_valid(argv[4])) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid member name: %s", argv[4]); + r = sd_bus_message_new_method_call(bus, &m, argv[1], argv[2], argv[3], argv[4]); if (r < 0) return bus_log_create_error(r); diff --git a/src/core/bpf-socket-bind.c b/src/core/bpf-socket-bind.c index 9f290ab..88ab487 100644 --- a/src/core/bpf-socket-bind.c +++ b/src/core/bpf-socket-bind.c @@ -32,6 +32,15 @@ static int update_rules_map( assert(map_fd >= 0); + if (!head) { + static const struct socket_bind_rule val = { + .address_family = SOCKET_BIND_RULE_AF_MATCH_NOTHING, + }; + + if (sym_bpf_map_update_elem(map_fd, &i, &val, BPF_ANY) != 0) + return -errno; + } + LIST_FOREACH(socket_bind_items, item, head) { struct socket_bind_rule val = { .address_family = (uint32_t) item->address_family, diff --git a/src/core/bpf/socket_bind/socket-bind-api.bpf.h b/src/core/bpf/socket_bind/socket-bind-api.bpf.h index 277b9bb..4fe08f1 100644 --- a/src/core/bpf/socket_bind/socket-bind-api.bpf.h +++ b/src/core/bpf/socket_bind/socket-bind-api.bpf.h @@ -7,13 +7,17 @@ */ #include <linux/types.h> +#include <stdint.h> /* * Bind rule is matched with socket fields accessible to cgroup/bind{4,6} hook * through bpf_sock_addr struct. - * 'address_family' is expected to be one of AF_UNSPEC, AF_INET or AF_INET6. + * 'address_family' is expected to be one of AF_UNSPEC, AF_INET, AF_INET6 or the + * magic SOCKET_BIND_RULE_AF_MATCH_NOTHING. * Matching by family is bypassed for rules with AF_UNSPEC set, which makes the * rest of a rule applicable for both IPv4 and IPv6 addresses. + * If SOCKET_BIND_RULE_AF_MATCH_NOTHING is set the rule fails unconditionally + * and other checks are skipped. * If matching by family is either successful or bypassed, a rule and a socket * are matched by ip protocol. * If 'protocol' is 0, matching is bypassed. @@ -49,3 +53,4 @@ struct socket_bind_rule { }; #define SOCKET_BIND_MAX_RULES 128 +#define SOCKET_BIND_RULE_AF_MATCH_NOTHING UINT32_MAX diff --git a/src/core/bpf/socket_bind/socket-bind.bpf.c b/src/core/bpf/socket_bind/socket-bind.bpf.c index b7972a8..da9f9d1 100644 --- a/src/core/bpf/socket_bind/socket-bind.bpf.c +++ b/src/core/bpf/socket_bind/socket-bind.bpf.c @@ -55,6 +55,9 @@ static __always_inline bool match( __u32 protocol, __u16 port, const struct socket_bind_rule *r) { + if (r->address_family == SOCKET_BIND_RULE_AF_MATCH_NOTHING) + return false; + return match_af(address_family, r) && match_protocol(protocol, r) && match_user_port(port, r); diff --git a/src/core/dynamic-user.c b/src/core/dynamic-user.c index 12724c6..2bf9094 100644 --- a/src/core/dynamic-user.c +++ b/src/core/dynamic-user.c @@ -337,8 +337,10 @@ static int dynamic_user_pop(DynamicUser *d, uid_t *ret_uid, int *ret_lock_fd) { * the lock on the socket taken. */ k = receive_one_fd_iov(d->storage_socket[0], &iov, 1, MSG_DONTWAIT, &lock_fd); - if (k < 0) + if (k < 0) { + assert(errno_is_valid(-k)); return (int) k; + } *ret_uid = uid; *ret_lock_fd = lock_fd; diff --git a/src/core/exec-invoke.c b/src/core/exec-invoke.c index 28d6142..8e6de15 100644 --- a/src/core/exec-invoke.c +++ b/src/core/exec-invoke.c @@ -3459,7 +3459,7 @@ static int close_remaining_fds( const int *fds, size_t n_fds) { size_t n_dont_close = 0; - int dont_close[n_fds + 14]; + int dont_close[n_fds + 15]; assert(params); @@ -3495,6 +3495,8 @@ static int close_remaining_fds( if (params->user_lookup_fd >= 0) dont_close[n_dont_close++] = params->user_lookup_fd; + assert(n_dont_close <= ELEMENTSOF(dont_close)); + return close_all_fds(dont_close, n_dont_close); } diff --git a/src/core/main.c b/src/core/main.c index 3f71cc0..1ed968d 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -627,7 +627,7 @@ static int parse_config_file(void) { { "Manager", "CPUAffinity", config_parse_cpu_affinity2, 0, &arg_cpu_affinity }, { "Manager", "NUMAPolicy", config_parse_numa_policy, 0, &arg_numa_policy.type }, { "Manager", "NUMAMask", config_parse_numa_mask, 0, &arg_numa_policy }, - { "Manager", "JoinControllers", config_parse_warn_compat, DISABLED_CONFIGURATION, NULL }, + { "Manager", "JoinControllers", config_parse_warn_compat, DISABLED_LEGACY, NULL }, { "Manager", "RuntimeWatchdogSec", config_parse_watchdog_sec, 0, &arg_runtime_watchdog }, { "Manager", "RuntimeWatchdogPreSec", config_parse_watchdog_sec, 0, &arg_pretimeout_watchdog }, { "Manager", "RebootWatchdogSec", config_parse_watchdog_sec, 0, &arg_reboot_watchdog }, diff --git a/src/core/manager-serialize.c b/src/core/manager-serialize.c index e9d567a..1ac2636 100644 --- a/src/core/manager-serialize.c +++ b/src/core/manager-serialize.c @@ -153,6 +153,7 @@ int manager_serialize( } (void) serialize_ratelimit(f, "dump-ratelimit", &m->dump_ratelimit); + (void) serialize_ratelimit(f, "reload-ratelimit", &m->reload_ratelimit); bus_track_serialize(m->subscribed, f, "subscribed"); @@ -515,6 +516,8 @@ int manager_deserialize(Manager *m, FILE *f, FDSet *fds) { (void) varlink_server_deserialize_one(m->varlink_server, val, fds); } else if ((val = startswith(l, "dump-ratelimit="))) deserialize_ratelimit(&m->dump_ratelimit, "dump-ratelimit", val); + else if ((val = startswith(l, "reload-ratelimit="))) + deserialize_ratelimit(&m->reload_ratelimit, "reload-ratelimit", val); else { ManagerTimestamp q; diff --git a/src/core/mount.c b/src/core/mount.c index ded322d..3c4971c 100644 --- a/src/core/mount.c +++ b/src/core/mount.c @@ -55,7 +55,7 @@ static const UnitActiveState state_translation_table[_MOUNT_STATE_MAX] = { static int mount_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata); static int mount_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata); -static void mount_enter_dead(Mount *m, MountResult f); +static void mount_enter_dead(Mount *m, MountResult f, bool flush_result); static void mount_enter_mounted(Mount *m, MountResult f); static void mount_cycle_clear(Mount *m); static int mount_process_proc_self_mountinfo(Manager *m); @@ -846,7 +846,7 @@ static void mount_catchup(Unit *u) { break; case MOUNT_MOUNTED: assert(!pidref_is_set(&m->control_pid)); - mount_enter_dead(m, MOUNT_SUCCESS); + mount_enter_dead(m, MOUNT_SUCCESS, /* flush_result = */ false); break; default: break; @@ -952,10 +952,10 @@ static int mount_spawn(Mount *m, ExecCommand *c, PidRef *ret_pid) { return 0; } -static void mount_enter_dead(Mount *m, MountResult f) { +static void mount_enter_dead(Mount *m, MountResult f, bool flush_result) { assert(m); - if (m->result == MOUNT_SUCCESS) + if (m->result == MOUNT_SUCCESS || flush_result) m->result = f; unit_log_result(UNIT(m), m->result == MOUNT_SUCCESS, mount_result_to_string(m->result)); @@ -983,17 +983,20 @@ static void mount_enter_mounted(Mount *m, MountResult f) { mount_set_state(m, MOUNT_MOUNTED); } -static void mount_enter_dead_or_mounted(Mount *m, MountResult f) { +static void mount_enter_dead_or_mounted(Mount *m, MountResult f, bool flush_result) { assert(m); - /* Enter DEAD or MOUNTED state, depending on what the kernel currently says about the mount point. We use this - * whenever we executed an operation, so that our internal state reflects what the kernel says again, after all - * ultimately we just mirror the kernel's internal state on this. */ + /* Enter DEAD or MOUNTED state, depending on what the kernel currently says about the mount point. + * We use this whenever we executed an operation, so that our internal state reflects what + * the kernel says again, after all ultimately we just mirror the kernel's internal state on this. + * + * Note that flush_result only applies to mount_enter_dead(), since that's when the result gets + * turned into unit end state. */ if (m->from_proc_self_mountinfo) mount_enter_mounted(m, f); else - mount_enter_dead(m, f); + mount_enter_dead(m, f, flush_result); } static int state_to_kill_operation(MountState state) { @@ -1049,12 +1052,12 @@ static void mount_enter_signal(Mount *m, MountState state, MountResult f) { else if (state == MOUNT_UNMOUNTING_SIGTERM && m->kill_context.send_sigkill) mount_enter_signal(m, MOUNT_UNMOUNTING_SIGKILL, MOUNT_SUCCESS); else - mount_enter_dead_or_mounted(m, MOUNT_SUCCESS); + mount_enter_dead_or_mounted(m, MOUNT_SUCCESS, /* flush_result = */ false); return; fail: - mount_enter_dead_or_mounted(m, MOUNT_FAILURE_RESOURCES); + mount_enter_dead_or_mounted(m, MOUNT_FAILURE_RESOURCES, /* flush_result = */ false); } static int mount_set_umount_command(Mount *m, ExecCommand *c) { @@ -1116,7 +1119,7 @@ static void mount_enter_unmounting(Mount *m) { return; fail: - mount_enter_dead_or_mounted(m, MOUNT_FAILURE_RESOURCES); + mount_enter_dead_or_mounted(m, MOUNT_FAILURE_RESOURCES, /* flush_result = */ false); } static int mount_set_mount_command(Mount *m, ExecCommand *c, const MountParameters *p) { @@ -1232,7 +1235,7 @@ static void mount_enter_mounting(Mount *m) { return; fail: - mount_enter_dead_or_mounted(m, MOUNT_FAILURE_RESOURCES); + mount_enter_dead_or_mounted(m, MOUNT_FAILURE_RESOURCES, /* flush_result = */ false); } static void mount_set_reload_result(Mount *m, MountResult result) { @@ -1298,7 +1301,7 @@ static void mount_enter_remounting(Mount *m) { fail: mount_set_reload_result(m, MOUNT_FAILURE_RESOURCES); - mount_enter_dead_or_mounted(m, MOUNT_SUCCESS); + mount_enter_dead_or_mounted(m, MOUNT_SUCCESS, /* flush_result = */ false); } static void mount_cycle_clear(Mount *m) { @@ -1472,8 +1475,8 @@ static int mount_deserialize_item(Unit *u, const char *key, const char *value, F } else if (streq(key, "control-pid")) { - pidref_done(&m->control_pid); - (void) deserialize_pidref(fds, value, &m->control_pid); + if (!pidref_is_set(&m->control_pid)) + (void) deserialize_pidref(fds, value, &m->control_pid); } else if (streq(key, "control-command")) { MountExecCommand id; @@ -1555,7 +1558,8 @@ static void mount_sigchld_event(Unit *u, pid_t pid, int code, int status) { if (IN_SET(m->state, MOUNT_REMOUNTING, MOUNT_REMOUNTING_SIGKILL, MOUNT_REMOUNTING_SIGTERM)) mount_set_reload_result(m, f); - else if (m->result == MOUNT_SUCCESS) + else if (m->result == MOUNT_SUCCESS && !IN_SET(m->state, MOUNT_MOUNTING, MOUNT_UNMOUNTING)) + /* MOUNT_MOUNTING and MOUNT_UNMOUNTING states need to be patched, see below. */ m->result = f; if (m->control_command) { @@ -1578,15 +1582,15 @@ static void mount_sigchld_event(Unit *u, pid_t pid, int code, int status) { switch (m->state) { case MOUNT_MOUNTING: - /* Our mount point has not appeared in mountinfo. Something went wrong. */ + /* Our mount point has not appeared in mountinfo. Something went wrong. */ if (f == MOUNT_SUCCESS) { - /* Either /bin/mount has an unexpected definition of success, - * or someone raced us and we lost. */ + /* Either /bin/mount has an unexpected definition of success, or someone raced us + * and we lost. */ log_unit_warning(UNIT(m), "Mount process finished, but there is no mount."); f = MOUNT_FAILURE_PROTOCOL; } - mount_enter_dead(m, f); + mount_enter_dead(m, f, /* flush_result = */ false); break; case MOUNT_MOUNTING_DONE: @@ -1596,13 +1600,11 @@ static void mount_sigchld_event(Unit *u, pid_t pid, int code, int status) { case MOUNT_REMOUNTING: case MOUNT_REMOUNTING_SIGTERM: case MOUNT_REMOUNTING_SIGKILL: - mount_enter_dead_or_mounted(m, MOUNT_SUCCESS); + mount_enter_dead_or_mounted(m, MOUNT_SUCCESS, /* flush_result = */ false); break; case MOUNT_UNMOUNTING: - if (f == MOUNT_SUCCESS && m->from_proc_self_mountinfo) { - /* Still a mount point? If so, let's try again. Most likely there were multiple mount points * stacked on top of each other. We might exceed the timeout specified by the user overall, * but we will stop as soon as any one umount times out. */ @@ -1613,23 +1615,33 @@ static void mount_sigchld_event(Unit *u, pid_t pid, int code, int status) { mount_enter_unmounting(m); } else { log_unit_warning(u, "Mount still present after %u attempts to unmount, giving up.", m->n_retry_umount); - mount_enter_mounted(m, f); + mount_enter_mounted(m, MOUNT_FAILURE_PROTOCOL); } + } else if (f == MOUNT_FAILURE_EXIT_CODE && !m->from_proc_self_mountinfo) { + /* Hmm, umount process spawned by us failed, but the mount disappeared anyway? + * Maybe someone else is trying to unmount at the same time. */ + log_unit_notice(u, "Mount disappeared even though umount process failed, continuing."); + mount_enter_dead(m, MOUNT_SUCCESS, /* flush_result = */ true); } else - mount_enter_dead_or_mounted(m, f); + /* At this point, either the unmount succeeded or unexpected error occurred. We usually + * remember the first error in 'result', but here let's update that forcibly, since + * there could previous failed attempts yet we only care about the most recent + * attempt. IOW, if we eventually managed to unmount the stuff, don't enter failed + * end state. */ + mount_enter_dead_or_mounted(m, f, /* flush_result = */ true); break; - case MOUNT_UNMOUNTING_SIGKILL: case MOUNT_UNMOUNTING_SIGTERM: - mount_enter_dead_or_mounted(m, f); + case MOUNT_UNMOUNTING_SIGKILL: + mount_enter_dead_or_mounted(m, f, /* flush_result = */ false); break; case MOUNT_CLEANING: if (m->clean_result == MOUNT_SUCCESS) m->clean_result = f; - mount_enter_dead(m, MOUNT_SUCCESS); + mount_enter_dead(m, MOUNT_SUCCESS, /* flush_result = */ false); break; default: @@ -1668,7 +1680,7 @@ static int mount_dispatch_timer(sd_event_source *source, usec_t usec, void *user mount_enter_signal(m, MOUNT_REMOUNTING_SIGKILL, MOUNT_SUCCESS); } else { log_unit_warning(UNIT(m), "Remounting timed out. Skipping SIGKILL. Ignoring."); - mount_enter_dead_or_mounted(m, MOUNT_SUCCESS); + mount_enter_dead_or_mounted(m, MOUNT_SUCCESS, /* flush_result = */ false); } break; @@ -1676,7 +1688,7 @@ static int mount_dispatch_timer(sd_event_source *source, usec_t usec, void *user mount_set_reload_result(m, MOUNT_FAILURE_TIMEOUT); log_unit_warning(UNIT(m), "Mount process still around after SIGKILL. Ignoring."); - mount_enter_dead_or_mounted(m, MOUNT_SUCCESS); + mount_enter_dead_or_mounted(m, MOUNT_SUCCESS, /* flush_result = */ false); break; case MOUNT_UNMOUNTING: @@ -1690,13 +1702,13 @@ static int mount_dispatch_timer(sd_event_source *source, usec_t usec, void *user mount_enter_signal(m, MOUNT_UNMOUNTING_SIGKILL, MOUNT_FAILURE_TIMEOUT); } else { log_unit_warning(UNIT(m), "Mount process timed out. Skipping SIGKILL. Ignoring."); - mount_enter_dead_or_mounted(m, MOUNT_FAILURE_TIMEOUT); + mount_enter_dead_or_mounted(m, MOUNT_FAILURE_TIMEOUT, /* flush_result = */ false); } break; case MOUNT_UNMOUNTING_SIGKILL: log_unit_warning(UNIT(m), "Mount process still around after SIGKILL. Ignoring."); - mount_enter_dead_or_mounted(m, MOUNT_FAILURE_TIMEOUT); + mount_enter_dead_or_mounted(m, MOUNT_FAILURE_TIMEOUT, /* flush_result = */ false); break; case MOUNT_CLEANING: @@ -2157,8 +2169,11 @@ static int mount_process_proc_self_mountinfo(Manager *m) { switch (mount->state) { case MOUNT_MOUNTED: - /* This has just been unmounted by somebody else, follow the state change. */ - mount_enter_dead(mount, MOUNT_SUCCESS); + /* This has just been unmounted by somebody else, follow the state change. + * Also explicitly override the result (see the comment in mount_sigchld_event()), + * but more aggressively here since the state change is extrinsic. */ + mount_cycle_clear(mount); + mount_enter_dead(mount, MOUNT_SUCCESS, /* flush_result = */ true); break; case MOUNT_MOUNTING_DONE: @@ -2166,7 +2181,7 @@ static int mount_process_proc_self_mountinfo(Manager *m) { * then remove it because of an internal error. E.g., fuse.sshfs seems * to do that when the connection fails. See #17617. To handle such the * case, let's once set the state back to mounting. Then, the unit can - * correctly enter the failed state later in mount_sigchld(). */ + * correctly enter the failed state later in mount_sigchld_event(). */ mount_set_state(mount, MOUNT_MOUNTING); break; @@ -2330,7 +2345,7 @@ static int mount_can_start(Unit *u) { r = unit_test_start_limit(u); if (r < 0) { - mount_enter_dead(m, MOUNT_FAILURE_START_LIMIT_HIT); + mount_enter_dead(m, MOUNT_FAILURE_START_LIMIT_HIT, /* flush_result = */ false); return r; } diff --git a/src/core/scope.c b/src/core/scope.c index e4c27da..2841280 100644 --- a/src/core/scope.c +++ b/src/core/scope.c @@ -586,6 +586,8 @@ static int scope_deserialize_item(Unit *u, const char *key, const char *value, F } else if (streq(key, "pids")) { _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + /* We don't check if we already received the pid before here because unit_watch_pidref() + * does this check internally and discards the new pidref if we already received it before. */ if (deserialize_pidref(fds, value, &pidref) >= 0) { r = unit_watch_pidref(u, &pidref, /* exclusive= */ false); if (r < 0) diff --git a/src/core/service.c b/src/core/service.c index 060ac08..ffe92d2 100644 --- a/src/core/service.c +++ b/src/core/service.c @@ -3174,14 +3174,14 @@ static int service_deserialize_item(Unit *u, const char *key, const char *value, s->reload_result = f; } else if (streq(key, "control-pid")) { - pidref_done(&s->control_pid); - (void) deserialize_pidref(fds, value, &s->control_pid); + if (!pidref_is_set(&s->control_pid)) + (void) deserialize_pidref(fds, value, &s->control_pid); } else if (streq(key, "main-pid")) { _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; - if (deserialize_pidref(fds, value, &pidref) >= 0) + if (!pidref_is_set(&s->main_pid) && deserialize_pidref(fds, value, &pidref) >= 0) (void) service_set_main_pidref(s, &pidref); } else if (streq(key, "main-pid-known")) { @@ -3589,8 +3589,10 @@ static void service_notify_cgroup_empty_event(Unit *u) { break; } - if (s->exit_type == SERVICE_EXIT_CGROUP && main_pid_good(s) <= 0) - service_enter_start_post(s); + if (s->exit_type == SERVICE_EXIT_CGROUP && main_pid_good(s) <= 0) { + service_enter_stop_post(s, SERVICE_SUCCESS); + break; + } _fallthrough_; case SERVICE_START_POST: @@ -3861,11 +3863,13 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) { default: assert_not_reached(); } - } else if (s->exit_type == SERVICE_EXIT_CGROUP && s->state == SERVICE_START) + } else if (s->exit_type == SERVICE_EXIT_CGROUP && s->state == SERVICE_START && + !IN_SET(s->type, SERVICE_NOTIFY, SERVICE_NOTIFY_RELOAD, SERVICE_DBUS)) /* If a main process exits very quickly, this function might be executed * before service_dispatch_exec_io(). Since this function disabled IO events * to monitor the main process above, we need to update the state here too. - * Let's consider the process is successfully launched and exited. */ + * Let's consider the process is successfully launched and exited, but + * only when we're not expecting a readiness notification or dbus name. */ service_enter_start_post(s); } diff --git a/src/core/show-status.c b/src/core/show-status.c index 606237e..5b003ba 100644 --- a/src/core/show-status.c +++ b/src/core/show-status.c @@ -38,6 +38,8 @@ int parse_show_status(const char *v, ShowStatus *ret) { int status_vprintf(const char *status, ShowStatusFlags flags, const char *format, va_list ap) { static const char status_indent[] = " "; /* "[" STATUS "] " */ + static int dumb = -1; + _cleanup_free_ char *s = NULL; _cleanup_close_ int fd = -EBADF; struct iovec iovec[7] = {}; @@ -46,6 +48,9 @@ int status_vprintf(const char *status, ShowStatusFlags flags, const char *format assert(format); + if (dumb < 0) + dumb = getenv_terminal_is_dumb(); + /* This is independent of logging, as status messages are * optional and go exclusively to the console. */ @@ -61,7 +66,7 @@ int status_vprintf(const char *status, ShowStatusFlags flags, const char *format if (fd < 0) return fd; - if (FLAGS_SET(flags, SHOW_STATUS_ELLIPSIZE)) { + if (FLAGS_SET(flags, SHOW_STATUS_ELLIPSIZE) && !dumb) { char *e; size_t emax, sl; int c; @@ -81,7 +86,7 @@ int status_vprintf(const char *status, ShowStatusFlags flags, const char *format free_and_replace(s, e); } - if (prev_ephemeral) + if (prev_ephemeral && !dumb) iovec[n++] = IOVEC_MAKE_STRING(ANSI_REVERSE_LINEFEED "\r" ANSI_ERASE_TO_END_OF_LINE); if (status) { @@ -94,9 +99,11 @@ int status_vprintf(const char *status, ShowStatusFlags flags, const char *format } iovec[n++] = IOVEC_MAKE_STRING(s); - iovec[n++] = IOVEC_MAKE_STRING("\r\n"); /* use CRNL instead of just NL, to be robust towards TTYs in raw mode */ + /* use CRNL instead of just NL, to be robust towards TTYs in raw mode. If we're writing to a dumb + * terminal, use NL as CRNL might be interpreted as a double newline. */ + iovec[n++] = IOVEC_MAKE_STRING(dumb ? "\n" : "\r\n"); - if (prev_ephemeral && !FLAGS_SET(flags, SHOW_STATUS_EPHEMERAL)) + if (prev_ephemeral && !FLAGS_SET(flags, SHOW_STATUS_EPHEMERAL) && !dumb) iovec[n++] = IOVEC_MAKE_STRING(ANSI_ERASE_TO_END_OF_LINE); prev_ephemeral = FLAGS_SET(flags, SHOW_STATUS_EPHEMERAL); diff --git a/src/core/socket.c b/src/core/socket.c index 388be62..9adae16 100644 --- a/src/core/socket.c +++ b/src/core/socket.c @@ -2634,8 +2634,9 @@ static int socket_deserialize_item(Unit *u, const char *key, const char *value, else s->n_refused += k; } else if (streq(key, "control-pid")) { - pidref_done(&s->control_pid); - (void) deserialize_pidref(fds, value, &s->control_pid); + + if (!pidref_is_set(&s->control_pid)) + (void) deserialize_pidref(fds, value, &s->control_pid); } else if (streq(key, "control-command")) { SocketExecCommand id; diff --git a/src/core/swap.c b/src/core/swap.c index 488b171..682c2b9 100644 --- a/src/core/swap.c +++ b/src/core/swap.c @@ -989,8 +989,8 @@ static int swap_deserialize_item(Unit *u, const char *key, const char *value, FD s->result = f; } else if (streq(key, "control-pid")) { - pidref_done(&s->control_pid); - (void) deserialize_pidref(fds, value, &s->control_pid); + if (!pidref_is_set(&s->control_pid)) + (void) deserialize_pidref(fds, value, &s->control_pid); } else if (streq(key, "control-command")) { SwapExecCommand id; diff --git a/src/cryptenroll/cryptenroll-tpm2.c b/src/cryptenroll/cryptenroll-tpm2.c index 653ad44..2d93e13 100644 --- a/src/cryptenroll/cryptenroll-tpm2.c +++ b/src/cryptenroll/cryptenroll-tpm2.c @@ -239,9 +239,9 @@ int enroll_tpm2(struct crypt_device *cd, return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Must provide all PCR values when using TPM2 device key."); } else { - r = tpm2_context_new(device, &tpm2_context); + r = tpm2_context_new_or_warn(device, &tpm2_context); if (r < 0) - return log_error_errno(r, "Failed to create TPM2 context: %m"); + return r; if (!tpm2_pcr_values_has_all_values(hash_pcr_values, n_hash_pcr_values)) { r = tpm2_pcr_read_missing_values(tpm2_context, hash_pcr_values, n_hash_pcr_values); diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c index 72be5cc..846679f 100644 --- a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c +++ b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c @@ -85,9 +85,9 @@ int acquire_luks2_key( } _cleanup_(tpm2_context_unrefp) Tpm2Context *tpm2_context = NULL; - r = tpm2_context_new(device, &tpm2_context); + r = tpm2_context_new_or_warn(device, &tpm2_context); if (r < 0) - return log_error_errno(r, "Failed to create TPM2 context: %m"); + return r; r = tpm2_unseal(tpm2_context, hash_pcr_mask, diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h index d84e5a3..8408bab 100644 --- a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h +++ b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h @@ -14,8 +14,8 @@ int acquire_luks2_key( size_t pubkey_size, uint32_t pubkey_pcr_mask, const char *signature_path, - const char *pcrlock_path, const char *pin, + const char *pcrlock_path, uint16_t primary_alg, const void *key_data, size_t key_data_size, diff --git a/src/cryptsetup/cryptsetup-tpm2.c b/src/cryptsetup/cryptsetup-tpm2.c index f59d5f9..e7a38d4 100644 --- a/src/cryptsetup/cryptsetup-tpm2.c +++ b/src/cryptsetup/cryptsetup-tpm2.c @@ -139,9 +139,9 @@ int acquire_tpm2_key( } _cleanup_(tpm2_context_unrefp) Tpm2Context *tpm2_context = NULL; - r = tpm2_context_new(device, &tpm2_context); + r = tpm2_context_new_or_warn(device, &tpm2_context); if (r < 0) - return log_error_errno(r, "Failed to create TPM2 context: %m"); + return r; if (!(flags & TPM2_FLAGS_USE_PIN)) { r = tpm2_unseal(tpm2_context, diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c index b56b51a..1822beb 100644 --- a/src/cryptsetup/cryptsetup.c +++ b/src/cryptsetup/cryptsetup.c @@ -846,9 +846,9 @@ static int measure_volume_key( #if HAVE_TPM2 _cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL; - r = tpm2_context_new(arg_tpm2_device, &c); + r = tpm2_context_new_or_warn(arg_tpm2_device, &c); if (r < 0) - return log_error_errno(r, "Failed to create TPM2 context: %m"); + return r; _cleanup_strv_free_ char **l = NULL; if (strv_isempty(arg_tpm2_measure_banks)) { diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index 92432b6..c858e6a 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -85,7 +85,7 @@ static bool arg_rmdir = false; static bool arg_in_memory = false; static char **arg_argv = NULL; static char *arg_loop_ref = NULL; -static ImagePolicy* arg_image_policy = NULL; +static ImagePolicy *arg_image_policy = NULL; static bool arg_mtree_hash = true; STATIC_DESTRUCTOR_REGISTER(arg_image, freep); @@ -94,6 +94,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_path, freep); STATIC_DESTRUCTOR_REGISTER(arg_verity_settings, verity_settings_done); STATIC_DESTRUCTOR_REGISTER(arg_argv, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_loop_ref, freep); +STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); static int help(void) { _cleanup_free_ char *link = NULL; diff --git a/src/home/homed-manager.c b/src/home/homed-manager.c index c452531..b8bef53 100644 --- a/src/home/homed-manager.c +++ b/src/home/homed-manager.c @@ -1040,7 +1040,7 @@ static int manager_bind_varlink(Manager *m) { assert(!m->userdb_service); r = path_extract_filename(socket_path, &m->userdb_service); if (r < 0) - return log_error_errno(r, "Failed to extra filename from socket path '%s': %m", socket_path); + return log_error_errno(r, "Failed to extract filename from socket path '%s': %m", socket_path); /* Avoid recursion */ if (setenv("SYSTEMD_BYPASS_USERDB", m->userdb_service, 1) < 0) diff --git a/src/home/homework-cifs.c b/src/home/homework-cifs.c index 19f1cd5..5d87131 100644 --- a/src/home/homework-cifs.c +++ b/src/home/homework-cifs.c @@ -5,6 +5,7 @@ #include <linux/fs.h> #endif +#include "data-fd-util.h" #include "dirent-util.h" #include "fd-util.h" #include "fileio.h" @@ -24,7 +25,7 @@ int home_setup_cifs( HomeSetupFlags flags, HomeSetup *setup) { - _cleanup_free_ char *chost = NULL, *cservice = NULL, *cdir = NULL, *chost_and_service = NULL, *j = NULL; + _cleanup_free_ char *chost = NULL, *cservice = NULL, *cdir = NULL, *chost_and_service = NULL, *j = NULL, *options = NULL; int r; assert(h); @@ -53,49 +54,50 @@ int home_setup_cifs( if (!chost_and_service) return log_oom(); + if (asprintf(&options, "user=%s,uid=" UID_FMT ",forceuid,gid=" GID_FMT ",forcegid,file_mode=0%3o,dir_mode=0%3o", + user_record_cifs_user_name(h), h->uid, user_record_gid(h), user_record_access_mode(h), + user_record_access_mode(h)) < 0) + return log_oom(); + + if (h->cifs_domain) + if (strextendf_with_separator(&options, ",", "domain=%s", h->cifs_domain) < 0) + return log_oom(); + + if (h->cifs_extra_mount_options) + if (!strextend_with_separator(&options, ",", h->cifs_extra_mount_options)) + return log_oom(); + r = home_unshare_and_mkdir(); if (r < 0) return r; STRV_FOREACH(pw, h->password) { - _cleanup_(unlink_and_freep) char *p = NULL; - _cleanup_free_ char *options = NULL; - _cleanup_fclose_ FILE *f = NULL; + _cleanup_close_ int passwd_fd = -EBADF; pid_t mount_pid; int exit_status; - r = fopen_temporary_child(NULL, &f, &p); - if (r < 0) - return log_error_errno(r, "Failed to create temporary credentials file: %m"); - - fprintf(f, - "username=%s\n" - "password=%s\n", - user_record_cifs_user_name(h), - *pw); - - if (h->cifs_domain) - fprintf(f, "domain=%s\n", h->cifs_domain); - - r = fflush_and_check(f); - if (r < 0) - return log_error_errno(r, "Failed to write temporary credentials file: %m"); - - f = safe_fclose(f); - - if (asprintf(&options, "credentials=%s,uid=" UID_FMT ",forceuid,gid=" GID_FMT ",forcegid,file_mode=0%3o,dir_mode=0%3o", - p, h->uid, user_record_gid(h), user_record_access_mode(h), user_record_access_mode(h)) < 0) - return log_oom(); - - if (h->cifs_extra_mount_options) - if (!strextend_with_separator(&options, ",", h->cifs_extra_mount_options)) - return log_oom(); + passwd_fd = acquire_data_fd(*pw, strlen(*pw), /* flags= */ 0); + if (passwd_fd < 0) + return log_error_errno(passwd_fd, "Failed to create data FD for password: %m"); r = safe_fork("(mount)", FORK_RESET_SIGNALS|FORK_RLIMIT_NOFILE_SAFE|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_STDOUT_TO_STDERR, &mount_pid); if (r < 0) return r; if (r == 0) { /* Child */ + + r = fd_cloexec(passwd_fd, false); + if (r < 0) { + log_error_errno(r, "Failed to disable CLOEXEC on password FD: %m"); + _exit(EXIT_FAILURE); + } + + r = setenvf("PASSWD_FD", /* overwrite= */ true, "%d", passwd_fd); + if (r < 0) { + log_error_errno(errno, "Failed to set $PASSWD_FD: %m"); + _exit(EXIT_FAILURE); + } + execl("/bin/mount", "/bin/mount", "-n", "-t", "cifs", chost_and_service, HOME_RUNTIME_WORK_DIR, "-o", options, NULL); diff --git a/src/journal-remote/journal-gatewayd.c b/src/journal-remote/journal-gatewayd.c index 0919471..aaa52c0 100644 --- a/src/journal-remote/journal-gatewayd.c +++ b/src/journal-remote/journal-gatewayd.c @@ -47,6 +47,7 @@ static char **arg_file = NULL; STATIC_DESTRUCTOR_REGISTER(arg_key_pem, erase_and_freep); STATIC_DESTRUCTOR_REGISTER(arg_cert_pem, freep); STATIC_DESTRUCTOR_REGISTER(arg_trust_pem, freep); +STATIC_DESTRUCTOR_REGISTER(arg_file, strv_freep); typedef struct RequestMeta { sd_journal *journal; diff --git a/src/journal-remote/journal-remote-main.c b/src/journal-remote/journal-remote-main.c index da0f20d..294719b 100644 --- a/src/journal-remote/journal-remote-main.c +++ b/src/journal-remote/journal-remote-main.c @@ -535,24 +535,6 @@ static int dispatch_http_event(sd_event_source *event, ********************************************************************** **********************************************************************/ -static int setup_signals(RemoteServer *s) { - int r; - - assert(s); - - assert_se(sigprocmask_many(SIG_SETMASK, NULL, SIGINT, SIGTERM, -1) >= 0); - - r = sd_event_add_signal(s->events, &s->sigterm_event, SIGTERM, NULL, s); - if (r < 0) - return r; - - r = sd_event_add_signal(s->events, &s->sigint_event, SIGINT, NULL, s); - if (r < 0) - return r; - - return 0; -} - static int setup_raw_socket(RemoteServer *s, const char *address) { int fd; @@ -580,9 +562,9 @@ static int create_remoteserver( if (r < 0) return r; - r = setup_signals(s); + r = sd_event_set_signal_exit(s->events, true); if (r < 0) - return log_error_errno(r, "Failed to set up signals: %m"); + return log_error_errno(r, "Failed to install SIGINT/SIGTERM handlers: %m"); n = sd_listen_fds(true); if (n < 0) diff --git a/src/journal-remote/journal-remote.c b/src/journal-remote/journal-remote.c index 79010d0..9db686d 100644 --- a/src/journal-remote/journal-remote.c +++ b/src/journal-remote/journal-remote.c @@ -376,8 +376,6 @@ void journal_remote_server_destroy(RemoteServer *s) { writer_unref(s->_single_writer); hashmap_free(s->writers); - sd_event_source_unref(s->sigterm_event); - sd_event_source_unref(s->sigint_event); sd_event_source_unref(s->listen_event); sd_event_unref(s->events); @@ -517,7 +515,9 @@ static int accept_connection( switch (socket_address_family(addr)) { case AF_INET: - case AF_INET6: { + case AF_INET6: + case AF_VSOCK: + case AF_UNIX: { _cleanup_free_ char *a = NULL; char *b; diff --git a/src/journal-remote/journal-remote.h b/src/journal-remote/journal-remote.h index 8d73f95..3d64db0 100644 --- a/src/journal-remote/journal-remote.h +++ b/src/journal-remote/journal-remote.h @@ -30,7 +30,7 @@ struct RemoteServer { size_t active; sd_event *events; - sd_event_source *sigterm_event, *sigint_event, *listen_event; + sd_event_source *listen_event; Hashmap *writers; Writer *_single_writer; diff --git a/src/journal-remote/journal-upload.c b/src/journal-remote/journal-upload.c index db74355..d70a049 100644 --- a/src/journal-remote/journal-upload.c +++ b/src/journal-remote/journal-upload.c @@ -59,6 +59,8 @@ static int arg_follow = -1; static const char *arg_save_state = NULL; static usec_t arg_network_timeout_usec = USEC_INFINITY; +STATIC_DESTRUCTOR_REGISTER(arg_file, strv_freep); + static void close_fd_input(Uploader *u); #define SERVER_ANSWER_KEEP 2048 @@ -378,38 +380,6 @@ static int open_file_for_upload(Uploader *u, const char *filename) { return r; } -static int dispatch_sigterm(sd_event_source *event, - const struct signalfd_siginfo *si, - void *userdata) { - Uploader *u = ASSERT_PTR(userdata); - - log_received_signal(LOG_INFO, si); - - close_fd_input(u); - close_journal_input(u); - - sd_event_exit(u->events, 0); - return 0; -} - -static int setup_signals(Uploader *u) { - int r; - - assert(u); - - assert_se(sigprocmask_many(SIG_SETMASK, NULL, SIGINT, SIGTERM, -1) >= 0); - - r = sd_event_add_signal(u->events, &u->sigterm_event, SIGTERM, dispatch_sigterm, u); - if (r < 0) - return r; - - r = sd_event_add_signal(u->events, &u->sigint_event, SIGINT, dispatch_sigterm, u); - if (r < 0) - return r; - - return 0; -} - static int setup_uploader(Uploader *u, const char *url, const char *state_file) { int r; const char *host, *proto = ""; @@ -449,9 +419,9 @@ static int setup_uploader(Uploader *u, const char *url, const char *state_file) if (r < 0) return log_error_errno(r, "sd_event_default failed: %m"); - r = setup_signals(u); + r = sd_event_set_signal_exit(u->events, true); if (r < 0) - return log_error_errno(r, "Failed to set up signals: %m"); + return log_error_errno(r, "Failed to install SIGINT/SIGTERM handlers: %m"); (void) sd_watchdog_enabled(false, &u->watchdog_usec); @@ -475,8 +445,6 @@ static void destroy_uploader(Uploader *u) { close_fd_input(u); close_journal_input(u); - sd_event_source_unref(u->sigterm_event); - sd_event_source_unref(u->sigint_event); sd_event_unref(u->events); } diff --git a/src/journal-remote/journal-upload.h b/src/journal-remote/journal-upload.h index 9ff5a7b..2007864 100644 --- a/src/journal-remote/journal-upload.h +++ b/src/journal-remote/journal-upload.h @@ -25,7 +25,6 @@ typedef enum { typedef struct Uploader { sd_event *events; - sd_event_source *sigint_event, *sigterm_event; char *url; CURL *easy; diff --git a/src/journal/cat.c b/src/journal/cat.c index 609ddba..0325add 100644 --- a/src/journal/cat.c +++ b/src/journal/cat.c @@ -12,6 +12,7 @@ #include "alloc-util.h" #include "build.h" +#include "env-util.h" #include "fd-util.h" #include "format-util.h" #include "main-func.h" @@ -157,7 +158,6 @@ static int run(int argc, char *argv[]) { if (argc <= optind) (void) execl("/bin/cat", "/bin/cat", NULL); else { - _cleanup_free_ char *s = NULL; struct stat st; if (fstat(STDERR_FILENO, &st) < 0) @@ -165,11 +165,9 @@ static int run(int argc, char *argv[]) { "Failed to fstat(%s): %m", FORMAT_PROC_FD_PATH(STDERR_FILENO)); - if (asprintf(&s, DEV_FMT ":" INO_FMT, (dev_t)st.st_dev, st.st_ino) < 0) - return log_oom(); - - if (setenv("JOURNAL_STREAM", s, /* overwrite = */ true) < 0) - return log_error_errno(errno, "Failed to set environment variable JOURNAL_STREAM: %m"); + r = setenvf("JOURNAL_STREAM", /* overwrite = */ true, DEV_FMT ":" INO_FMT, (dev_t) st.st_dev, st.st_ino); + if (r < 0) + return log_error_errno(r, "Failed to set environment variable JOURNAL_STREAM: %m"); (void) execvp(argv[optind], argv + optind); } diff --git a/src/journal/journalctl.c b/src/journal/journalctl.c index 7f3dcd5..45ecc96 100644 --- a/src/journal/journalctl.c +++ b/src/journal/journalctl.c @@ -380,7 +380,7 @@ static int help(void) { " -u --unit=UNIT Show logs from the specified unit\n" " --user-unit=UNIT Show logs from the specified user unit\n" " -t --identifier=STRING Show entries with the specified syslog identifier\n" - " -p --priority=RANGE Show entries with the specified priority\n" + " -p --priority=RANGE Show entries within the specified priority range\n" " --facility=FACILITY... Show entries with the specified facilities\n" " -g --grep=PATTERN Show entries with MESSAGE matching PATTERN\n" " --case-sensitive[=BOOL] Force case sensitive or insensitive matching\n" @@ -1938,6 +1938,7 @@ static int update_cursor(sd_journal *j) { typedef struct Context { sd_journal *journal; + bool has_cursor; bool need_seek; bool since_seeked; bool ellipsized; @@ -1967,11 +1968,11 @@ static int show(Context *c) { break; } - if (arg_until_set && !arg_reverse && (arg_lines < 0 || arg_since_set)) { - /* If --lines= is set, we usually rely on the n_shown to tell us - * when to stop. However, if --since= is set too, we may end up - * having less than --lines= to output. In this case let's also - * check if the entry is in range. */ + if (arg_until_set && !arg_reverse && (arg_lines < 0 || arg_since_set || c->has_cursor)) { + /* If --lines= is set, we usually rely on the n_shown to tell us when to stop. + * However, if --since= or one of the cursor argument is set too, we may end up + * having less than --lines= to output. In this case let's also check if the entry + * is in range. */ usec_t usec; @@ -2572,6 +2573,7 @@ static int run(int argc, char *argv[]) { Context c = { .journal = j, + .has_cursor = cursor, .need_seek = need_seek, .since_seeked = since_seeked, }; diff --git a/src/kernel-install/60-ukify.install.in b/src/kernel-install/60-ukify.install.in index be1e21b..0f7a0db 100755 --- a/src/kernel-install/60-ukify.install.in +++ b/src/kernel-install/60-ukify.install.in @@ -109,6 +109,12 @@ def parse_args(args=None): return opts def we_are_wanted() -> bool: + KERNEL_INSTALL_IMAGE_TYPE = os.getenv('KERNEL_INSTALL_IMAGE_TYPE') + + if KERNEL_INSTALL_IMAGE_TYPE == 'uki': + log('The image being installed is already a UKI, quitting.') + return False + KERNEL_INSTALL_LAYOUT = os.getenv('KERNEL_INSTALL_LAYOUT') if KERNEL_INSTALL_LAYOUT != 'uki': diff --git a/src/kernel-install/90-uki-copy.install b/src/kernel-install/90-uki-copy.install index c66c097..d443c4b 100755 --- a/src/kernel-install/90-uki-copy.install +++ b/src/kernel-install/90-uki-copy.install @@ -26,8 +26,6 @@ KERNEL_VERSION="${2:?}" ENTRY_DIR_ABS="$3" KERNEL_IMAGE="$4" -[ "$KERNEL_INSTALL_LAYOUT" = "uki" ] || exit 0 - ENTRY_TOKEN="$KERNEL_INSTALL_ENTRY_TOKEN" BOOT_ROOT="$KERNEL_INSTALL_BOOT_ROOT" @@ -48,6 +46,8 @@ case "$COMMAND" in ;; esac +[ "$KERNEL_INSTALL_LAYOUT" = "uki" ] || exit 0 + if ! [ -d "$UKI_DIR" ]; then [ "$KERNEL_INSTALL_VERBOSE" -gt 0 ] && echo "creating $UKI_DIR" mkdir -p "$UKI_DIR" diff --git a/src/libsystemd-network/dhcp-option.c b/src/libsystemd-network/dhcp-option.c index 5e216c5..5679091 100644 --- a/src/libsystemd-network/dhcp-option.c +++ b/src/libsystemd-network/dhcp-option.c @@ -10,6 +10,8 @@ #include "alloc-util.h" #include "dhcp-option.h" #include "dhcp-server-internal.h" +#include "dns-domain.h" +#include "hostname-util.h" #include "memory-util.h" #include "ordered-set.h" #include "strv.h" @@ -396,27 +398,56 @@ int dhcp_option_parse(DHCPMessage *message, size_t len, dhcp_option_callback_t c } int dhcp_option_parse_string(const uint8_t *option, size_t len, char **ret) { + _cleanup_free_ char *string = NULL; int r; assert(option); assert(ret); - if (len <= 0) - *ret = mfree(*ret); - else { - char *string; + if (len <= 0) { + *ret = NULL; + return 0; + } - /* - * One trailing NUL byte is OK, we don't mind. See: - * https://github.com/systemd/systemd/issues/1337 - */ - r = make_cstring((const char *) option, len, MAKE_CSTRING_ALLOW_TRAILING_NUL, &string); - if (r < 0) - return r; + /* One trailing NUL byte is OK, we don't mind. See: + * https://github.com/systemd/systemd/issues/1337 */ + r = make_cstring((const char *) option, len, MAKE_CSTRING_ALLOW_TRAILING_NUL, &string); + if (r < 0) + return r; + + if (!string_is_safe(string) || !utf8_is_valid(string)) + return -EINVAL; + + *ret = TAKE_PTR(string); + return 0; +} + +int dhcp_option_parse_hostname(const uint8_t *option, size_t len, char **ret) { + _cleanup_free_ char *hostname = NULL; + int r; - free_and_replace(*ret, string); + assert(option); + assert(ret); + + r = dhcp_option_parse_string(option, len, &hostname); + if (r < 0) + return r; + + if (!hostname) { + *ret = NULL; + return 0; } + if (!hostname_is_valid(hostname, 0)) + return -EINVAL; + + r = dns_name_is_valid(hostname); + if (r < 0) + return r; + if (r == 0) + return -EINVAL; + + *ret = TAKE_PTR(hostname); return 0; } diff --git a/src/libsystemd-network/dhcp-option.h b/src/libsystemd-network/dhcp-option.h index 425f5b5..aaa8f84 100644 --- a/src/libsystemd-network/dhcp-option.h +++ b/src/libsystemd-network/dhcp-option.h @@ -44,3 +44,4 @@ int dhcp_option_parse( char **ret_error_message); int dhcp_option_parse_string(const uint8_t *option, size_t len, char **ret); +int dhcp_option_parse_hostname(const uint8_t *option, size_t len, char **ret); diff --git a/src/libsystemd-network/sd-dhcp-lease.c b/src/libsystemd-network/sd-dhcp-lease.c index 4e3be98..202d75f 100644 --- a/src/libsystemd-network/sd-dhcp-lease.c +++ b/src/libsystemd-network/sd-dhcp-lease.c @@ -833,12 +833,16 @@ int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void break; - case SD_DHCP_OPTION_ROOT_PATH: - r = dhcp_option_parse_string(option, len, &lease->root_path); + case SD_DHCP_OPTION_ROOT_PATH: { + _cleanup_free_ char *p = NULL; + + r = dhcp_option_parse_string(option, len, &p); if (r < 0) log_debug_errno(r, "Failed to parse root path, ignoring: %m"); - break; + free_and_replace(lease->root_path, p); + break; + } case SD_DHCP_OPTION_RENEWAL_TIME: r = lease_parse_be32_seconds(option, len, /* max_as_infinity = */ true, &lease->t1); if (r < 0) diff --git a/src/libsystemd-network/sd-dhcp-server.c b/src/libsystemd-network/sd-dhcp-server.c index fcc5b74..b87e4d6 100644 --- a/src/libsystemd-network/sd-dhcp-server.c +++ b/src/libsystemd-network/sd-dhcp-server.c @@ -808,14 +808,16 @@ static int parse_request(uint8_t code, uint8_t len, const void *option, void *us req->agent_info_option = (uint8_t*)option - 2; break; - case SD_DHCP_OPTION_HOST_NAME: - r = dhcp_option_parse_string(option, len, &req->hostname); - if (r < 0) { - log_debug_errno(r, "Failed to parse hostname, ignoring: %m"); - return 0; - } + case SD_DHCP_OPTION_HOST_NAME: { + _cleanup_free_ char *p = NULL; + r = dhcp_option_parse_hostname(option, len, &p); + if (r < 0) + log_debug_errno(r, "Failed to parse hostname, ignoring: %m"); + else + free_and_replace(req->hostname, p); break; + } case SD_DHCP_OPTION_PARAMETER_REQUEST_LIST: req->parameter_request_list = option; req->parameter_request_list_len = len; diff --git a/src/libsystemd/sd-bus/bus-error.c b/src/libsystemd/sd-bus/bus-error.c index 77b2e1a..f415797 100644 --- a/src/libsystemd/sd-bus/bus-error.c +++ b/src/libsystemd/sd-bus/bus-error.c @@ -277,14 +277,16 @@ _public_ int sd_bus_error_setf(sd_bus_error *e, const char *name, const char *fo va_start(ap, format); r = sd_bus_error_setfv(e, name, format, ap); - assert(!name || r < 0); + if (name) + assert(r < 0); va_end(ap); return r; } r = sd_bus_error_set(e, name, NULL); - assert(!name || r < 0); + if (name) + assert(r < 0); return r; } diff --git a/src/libsystemd/sd-device/device-private.h b/src/libsystemd/sd-device/device-private.h index b903d1a..e8a6d52 100644 --- a/src/libsystemd/sd-device/device-private.h +++ b/src/libsystemd/sd-device/device-private.h @@ -20,7 +20,10 @@ int device_opendir(sd_device *device, const char *subdir, DIR **ret); int device_get_property_bool(sd_device *device, const char *key); int device_get_property_int(sd_device *device, const char *key, int *ret); int device_get_sysattr_int(sd_device *device, const char *sysattr, int *ret_value); -int device_get_sysattr_unsigned(sd_device *device, const char *sysattr, unsigned *ret_value); +int device_get_sysattr_unsigned_full(sd_device *device, const char *sysattr, unsigned base, unsigned *ret_value); +static inline int device_get_sysattr_unsigned(sd_device *device, const char *sysattr, unsigned *ret_value) { + return device_get_sysattr_unsigned_full(device, sysattr, 0, ret_value); +} int device_get_sysattr_bool(sd_device *device, const char *sysattr); int device_get_device_id(sd_device *device, const char **ret); int device_get_devlink_priority(sd_device *device, int *ret); diff --git a/src/libsystemd/sd-device/sd-device.c b/src/libsystemd/sd-device/sd-device.c index 2fbc619..01e66b4 100644 --- a/src/libsystemd/sd-device/sd-device.c +++ b/src/libsystemd/sd-device/sd-device.c @@ -2435,7 +2435,7 @@ int device_get_sysattr_int(sd_device *device, const char *sysattr, int *ret_valu return v > 0; } -int device_get_sysattr_unsigned(sd_device *device, const char *sysattr, unsigned *ret_value) { +int device_get_sysattr_unsigned_full(sd_device *device, const char *sysattr, unsigned base, unsigned *ret_value) { const char *value; int r; @@ -2444,7 +2444,7 @@ int device_get_sysattr_unsigned(sd_device *device, const char *sysattr, unsigned return r; unsigned v; - r = safe_atou(value, &v); + r = safe_atou_full(value, base, &v); if (r < 0) return log_device_debug_errno(device, r, "Failed to parse '%s' attribute: %m", sysattr); diff --git a/src/libsystemd/sd-event/sd-event.c b/src/libsystemd/sd-event/sd-event.c index 288798a..b6899df 100644 --- a/src/libsystemd/sd-event/sd-event.c +++ b/src/libsystemd/sd-event/sd-event.c @@ -2415,7 +2415,7 @@ static int inode_data_realize_watch(sd_event *e, struct inode_data *d) { wd = inotify_add_watch_fd(d->inotify_data->fd, d->fd, combined_mask); if (wd < 0) - return -errno; + return wd; if (d->wd < 0) { r = hashmap_put(d->inotify_data->wd, INT_TO_PTR(wd), d); @@ -2637,7 +2637,7 @@ _public_ int sd_event_source_get_io_fd(sd_event_source *s) { } _public_ int sd_event_source_set_io_fd(sd_event_source *s, int fd) { - int r; + int saved_fd, r; assert_return(s, -EINVAL); assert_return(fd >= 0, -EBADF); @@ -2647,16 +2647,12 @@ _public_ int sd_event_source_set_io_fd(sd_event_source *s, int fd) { if (s->io.fd == fd) return 0; - if (event_source_is_offline(s)) { - s->io.fd = fd; - s->io.registered = false; - } else { - int saved_fd; + saved_fd = s->io.fd; + s->io.fd = fd; - saved_fd = s->io.fd; - assert(s->io.registered); + assert(event_source_is_offline(s) == !s->io.registered); - s->io.fd = fd; + if (s->io.registered) { s->io.registered = false; r = source_io_register(s, s->enabled, s->io.events); @@ -2669,6 +2665,9 @@ _public_ int sd_event_source_set_io_fd(sd_event_source *s, int fd) { (void) epoll_ctl(s->event->epoll_fd, EPOLL_CTL_DEL, saved_fd, NULL); } + if (s->io.owned) + safe_close(saved_fd); + return 0; } diff --git a/src/libsystemd/sd-event/test-event.c b/src/libsystemd/sd-event/test-event.c index 63d3ee7..cc3d84e 100644 --- a/src/libsystemd/sd-event/test-event.c +++ b/src/libsystemd/sd-event/test-event.c @@ -828,6 +828,24 @@ TEST(fork) { assert_se(r >= 0); } +TEST(sd_event_source_set_io_fd) { + _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL; + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + _cleanup_close_pair_ int pfd_a[2] = EBADF_PAIR, pfd_b[2] = EBADF_PAIR; + + assert_se(sd_event_default(&e) >= 0); + + assert_se(pipe2(pfd_a, O_CLOEXEC) >= 0); + assert_se(pipe2(pfd_b, O_CLOEXEC) >= 0); + + assert_se(sd_event_add_io(e, &s, pfd_a[0], EPOLLIN, NULL, INT_TO_PTR(-ENOANO)) >= 0); + assert_se(sd_event_source_set_io_fd_own(s, true) >= 0); + TAKE_FD(pfd_a[0]); + + assert_se(sd_event_source_set_io_fd(s, pfd_b[0]) >= 0); + TAKE_FD(pfd_b[0]); +} + static int hup_callback(sd_event_source *s, int fd, uint32_t revents, void *userdata) { unsigned *c = userdata; diff --git a/src/libsystemd/sd-id128/id128-util.c b/src/libsystemd/sd-id128/id128-util.c index 94bfd70..b9714ee 100644 --- a/src/libsystemd/sd-id128/id128-util.c +++ b/src/libsystemd/sd-id128/id128-util.c @@ -138,7 +138,7 @@ int id128_read_at(int dir_fd, const char *path, Id128Flag f, sd_id128_t *ret) { assert(dir_fd >= 0 || dir_fd == AT_FDCWD); assert(path); - fd = xopenat(dir_fd, path, O_RDONLY|O_CLOEXEC|O_NOCTTY, /* xopen_flags = */ 0, /* mode = */ 0); + fd = xopenat(dir_fd, path, O_RDONLY|O_CLOEXEC|O_NOCTTY); if (fd < 0) return fd; @@ -184,7 +184,7 @@ int id128_write_at(int dir_fd, const char *path, Id128Flag f, sd_id128_t id) { assert(dir_fd >= 0 || dir_fd == AT_FDCWD); assert(path); - fd = xopenat(dir_fd, path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY|O_TRUNC, /* xopen_flags = */ 0, 0444); + fd = xopenat_full(dir_fd, path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY|O_TRUNC, /* xopen_flags = */ 0, 0444); if (fd < 0) return fd; diff --git a/src/libsystemd/sd-journal/journal-file.c b/src/libsystemd/sd-journal/journal-file.c index d2493a0..08cbf86 100644 --- a/src/libsystemd/sd-journal/journal-file.c +++ b/src/libsystemd/sd-journal/journal-file.c @@ -639,7 +639,7 @@ static int journal_file_verify_header(JournalFile *f) { return -ENODATA; if (!VALID_REALTIME(le64toh(f->header->tail_entry_realtime))) return -ENODATA; - if (!VALID_MONOTONIC(le64toh(f->header->tail_entry_realtime))) + if (!VALID_MONOTONIC(le64toh(f->header->tail_entry_monotonic))) return -ENODATA; } else { /* Otherwise, the fields must be zero. */ @@ -650,7 +650,7 @@ static int journal_file_verify_header(JournalFile *f) { return -ENODATA; if (f->header->tail_entry_realtime != 0) return -ENODATA; - if (f->header->tail_entry_realtime != 0) + if (f->header->tail_entry_monotonic != 0) return -ENODATA; } } @@ -736,8 +736,9 @@ int journal_file_fstat(JournalFile *f) { return r; /* Refuse appending to files that are already deleted */ - if (f->last_stat.st_nlink <= 0) - return -EIDRM; + r = stat_verify_linked(&f->last_stat); + if (r < 0) + return r; return 0; } @@ -2532,7 +2533,7 @@ int journal_file_append_entry( ts->realtime); if (!VALID_MONOTONIC(ts->monotonic)) return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), - "Invalid monotomic timestamp %" PRIu64 ", refusing entry.", + "Invalid monotonic timestamp %" PRIu64 ", refusing entry.", ts->monotonic); } else { dual_timestamp_now(&_ts); diff --git a/src/libsystemd/sd-journal/journal-verify.c b/src/libsystemd/sd-journal/journal-verify.c index bdaa01d..b5ce55a 100644 --- a/src/libsystemd/sd-journal/journal-verify.c +++ b/src/libsystemd/sd-journal/journal-verify.c @@ -162,7 +162,7 @@ static int journal_file_object_verify(JournalFile *f, uint64_t offset, Object *o int r; if (le64toh(o->data.entry_offset) == 0) - warning(offset, "Unused data (entry_offset==0)"); + debug(offset, "Unused data (entry_offset==0)"); if ((le64toh(o->data.entry_offset) == 0) ^ (le64toh(o->data.n_entries) == 0)) { error(offset, "Bad n_entries: %"PRIu64, le64toh(o->data.n_entries)); diff --git a/src/libsystemd/sd-journal/sd-journal.c b/src/libsystemd/sd-journal/sd-journal.c index 6b9ff0a..ca1ef0c 100644 --- a/src/libsystemd/sd-journal/sd-journal.c +++ b/src/libsystemd/sd-journal/sd-journal.c @@ -1720,7 +1720,7 @@ static void directory_watch(sd_journal *j, Directory *m, int fd, uint32_t mask) m->wd = inotify_add_watch_fd(j->inotify_fd, fd, mask); if (m->wd < 0) { - log_debug_errno(errno, "Failed to watch journal directory '%s', ignoring: %m", m->path); + log_debug_errno(m->wd, "Failed to watch journal directory '%s', ignoring: %m", m->path); return; } diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c index ec1f2f3..cd2db2d 100644 --- a/src/login/logind-dbus.c +++ b/src/login/logind-dbus.c @@ -2072,7 +2072,7 @@ static int method_do_shutdown_or_sleep( case SLEEP_RESUME_NOT_SUPPORTED: return sd_bus_error_set(error, BUS_ERROR_SLEEP_VERB_NOT_SUPPORTED, - "Not running on EFI and resume= is not set. No available method to resume from hibernation"); + "Not running on EFI and resume= is not set, or noresume is set. No available method to resume from hibernation"); case SLEEP_NOT_ENOUGH_SWAP_SPACE: return sd_bus_error_set(error, BUS_ERROR_SLEEP_VERB_NOT_SUPPORTED, diff --git a/src/network/networkd-state-file.c b/src/network/networkd-state-file.c index 3a95ba8..bba84bb 100644 --- a/src/network/networkd-state-file.c +++ b/src/network/networkd-state-file.c @@ -127,7 +127,7 @@ static int link_put_dns(Link *link, OrderedSet **s) { NDiscRDNSS *a; SET_FOREACH(a, link->ndisc_rdnss) { - r = ordered_set_put_in6_addrv(s, &a->router, 1); + r = ordered_set_put_in6_addrv(s, &a->address, 1); if (r < 0) return r; } @@ -190,7 +190,7 @@ static int link_put_sip(Link *link, OrderedSet **s) { assert(link->network); assert(s); - if (link->dhcp_lease && link->network->dhcp_use_ntp) { + if (link->dhcp_lease && link->network->dhcp_use_sip) { const struct in_addr *addresses; r = sd_dhcp_lease_get_sip(link->dhcp_lease, &addresses); diff --git a/src/network/tc/qdisc.c b/src/network/tc/qdisc.c index f20f410..f9b9437 100644 --- a/src/network/tc/qdisc.c +++ b/src/network/tc/qdisc.c @@ -293,14 +293,20 @@ QDisc* qdisc_drop(QDisc *qdisc) { link = ASSERT_PTR(qdisc->link); + qdisc_mark(qdisc); /* To avoid stack overflow. */ + /* also drop all child classes assigned to the qdisc. */ SET_FOREACH(tclass, link->tclasses) { + if (tclass_is_marked(tclass)) + continue; + if (TC_H_MAJ(tclass->classid) != qdisc->handle) continue; tclass_drop(tclass); } + qdisc_unmark(qdisc); qdisc_enter_removed(qdisc); if (qdisc->state == 0) { diff --git a/src/network/tc/tclass.c b/src/network/tc/tclass.c index 0a5fec0..394e06d 100644 --- a/src/network/tc/tclass.c +++ b/src/network/tc/tclass.c @@ -260,14 +260,20 @@ TClass* tclass_drop(TClass *tclass) { link = ASSERT_PTR(tclass->link); + tclass_mark(tclass); /* To avoid stack overflow. */ + /* Also drop all child qdiscs assigned to the class. */ SET_FOREACH(qdisc, link->qdiscs) { + if (qdisc_is_marked(qdisc)) + continue; + if (qdisc->parent != tclass->classid) continue; qdisc_drop(qdisc); } + tclass_unmark(tclass); tclass_enter_removed(tclass); if (tclass->state == 0) { diff --git a/src/partition/repart.c b/src/partition/repart.c index 5487aaf..4fabe1b 100644 --- a/src/partition/repart.c +++ b/src/partition/repart.c @@ -3839,9 +3839,9 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Must provide all PCR values when using TPM2 device key."); } else { - r = tpm2_context_new(arg_tpm2_device, &tpm2_context); + r = tpm2_context_new_or_warn(arg_tpm2_device, &tpm2_context); if (r < 0) - return log_error_errno(r, "Failed to create TPM2 context: %m"); + return r; if (!tpm2_pcr_values_has_all_values(arg_tpm2_hash_pcr_values, arg_tpm2_n_hash_pcr_values)) { r = tpm2_pcr_read_missing_values(tpm2_context, arg_tpm2_hash_pcr_values, arg_tpm2_n_hash_pcr_values); diff --git a/src/pcrextend/pcrextend.c b/src/pcrextend/pcrextend.c index 1295949..394c258 100644 --- a/src/pcrextend/pcrextend.c +++ b/src/pcrextend/pcrextend.c @@ -199,7 +199,7 @@ static int extend_now(unsigned pcr, const void *data, size_t size, Tpm2Userspace _cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL; int r; - r = tpm2_context_new(arg_tpm2_device, &c); + r = tpm2_context_new_or_warn(arg_tpm2_device, &c); if (r < 0) return r; diff --git a/src/pcrlock/pcrlock.c b/src/pcrlock/pcrlock.c index bdc6bbd..dde4dd9 100644 --- a/src/pcrlock/pcrlock.c +++ b/src/pcrlock/pcrlock.c @@ -1194,7 +1194,7 @@ static int event_log_read_pcrs(EventLog *el) { assert(el); - r = tpm2_context_new(NULL, &tc); + r = tpm2_context_new_or_warn(/* device= */ NULL, &tc); if (r < 0) return r; @@ -4281,9 +4281,9 @@ static int verb_make_policy(int argc, char *argv[], void *userdata) { } _cleanup_(tpm2_context_unrefp) Tpm2Context *tc = NULL; - r = tpm2_context_new(NULL, &tc); + r = tpm2_context_new_or_warn(/* device= */ NULL, &tc); if (r < 0) - return log_error_errno(r, "Failed to allocate TPM2 context: %m"); + return r; if (!tpm2_supports_command(tc, TPM2_CC_PolicyAuthorizeNV)) return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 does not support PolicyAuthorizeNV command, refusing."); @@ -4610,7 +4610,7 @@ static int undefine_policy_nv_index( assert(srk_blob); _cleanup_(tpm2_context_unrefp) Tpm2Context *tc = NULL; - r = tpm2_context_new(NULL, &tc); + r = tpm2_context_new_or_warn(/* device= */ NULL, &tc); if (r < 0) return r; diff --git a/src/portable/portable.c b/src/portable/portable.c index 6054f0f..3b2a379 100644 --- a/src/portable/portable.c +++ b/src/portable/portable.c @@ -1623,9 +1623,8 @@ int portable_attach( return 0; } -static bool marker_matches_images(const char *marker, const char *name_or_path, char **extension_image_paths) { +static bool marker_matches_images(const char *marker, const char *name_or_path, char **extension_image_paths, bool match_all) { _cleanup_strv_free_ char **root_and_extensions = NULL; - const char *a; int r; assert(marker); @@ -1635,7 +1634,9 @@ static bool marker_matches_images(const char *marker, const char *name_or_path, * list of images/paths. We enforce strict 1:1 matching, so that we are sure * we are detaching exactly what was attached. * For each image, starting with the root, we look for a token in the marker, - * and return a negative answer on any non-matching combination. */ + * and return a negative answer on any non-matching combination. + * If a partial match is allowed, then return immediately once it is found, otherwise + * ensure that everything matches. */ root_and_extensions = strv_new(name_or_path); if (!root_and_extensions) @@ -1645,70 +1646,33 @@ static bool marker_matches_images(const char *marker, const char *name_or_path, if (r < 0) return r; - STRV_FOREACH(image_name_or_path, root_and_extensions) { - _cleanup_free_ char *image = NULL; + /* Ensure the number of images passed matches the number of images listed in the marker */ + while (!isempty(marker)) + STRV_FOREACH(image_name_or_path, root_and_extensions) { + _cleanup_free_ char *image = NULL, *base_image = NULL, *base_image_name_or_path = NULL; - r = extract_first_word(&marker, &image, ":", EXTRACT_UNQUOTE|EXTRACT_RETAIN_ESCAPE); - if (r < 0) - return log_debug_errno(r, "Failed to parse marker: %s", marker); - if (r == 0) - return false; - - a = last_path_component(image); - - if (image_name_is_valid(*image_name_or_path)) { - const char *e, *underscore; - - /* We shall match against an image name. In that case let's compare the last component, and optionally - * allow either a suffix of ".raw" or a series of "/". - * But allow matching on a different version of the same image, when a "_" is used as a separator. */ - underscore = strchr(*image_name_or_path, '_'); - if (underscore) { - if (strneq(a, *image_name_or_path, underscore - *image_name_or_path)) - continue; - return false; - } - - e = startswith(a, *image_name_or_path); - if (!e) - return false; - - if(!(e[strspn(e, "/")] == 0 || streq(e, ".raw"))) - return false; - } else { - const char *b, *underscore; - size_t l; - - /* We shall match against a path. Let's ignore any prefix here though, as often there are many ways to - * reach the same file. However, in this mode, let's validate any file suffix. - * But also ensure that we don't fail if both components don't have a '/' at all - * (strcspn returns the full length of the string in that case, which might not - * match as the versions might differ). */ - - l = strcspn(a, "/"); - b = last_path_component(*image_name_or_path); - - if ((a[l] != '/') != !strchr(b, '/')) /* One is a directory, the other is not */ + r = extract_first_word(&marker, &image, ":", EXTRACT_UNQUOTE|EXTRACT_RETAIN_ESCAPE); + if (r < 0) + return log_debug_errno(r, "Failed to parse marker: %s", marker); + if (r == 0) return false; - if (a[l] != 0 && strcspn(b, "/") != l) - return false; + r = path_extract_image_name(image, &base_image); + if (r < 0) + return log_debug_errno(r, "Failed to extract image name from %s, ignoring: %m", image); - underscore = strchr(b, '_'); - if (underscore) - l = underscore - b; - else { /* Either component could be versioned */ - underscore = strchr(a, '_'); - if (underscore) - l = underscore - a; - } + r = path_extract_image_name(*image_name_or_path, &base_image_name_or_path); + if (r < 0) + return log_debug_errno(r, "Failed to extract image name from %s, ignoring: %m", *image_name_or_path); - if (!strneq(a, b, l)) - return false; + if (!streq(base_image, base_image_name_or_path)) { + if (match_all) + return false; + } else if (!match_all) + return true; } - } - return true; + return match_all; } static int test_chroot_dropin( @@ -1763,7 +1727,9 @@ static int test_chroot_dropin( if (!name_or_path) r = true; else - r = marker_matches_images(marker, name_or_path, extension_image_paths); + /* When detaching we want to match exactly on all images, but when inspecting we only need + * to get the state of one component */ + r = marker_matches_images(marker, name_or_path, extension_image_paths, ret_marker != NULL); if (ret_marker) *ret_marker = TAKE_PTR(marker); diff --git a/src/resolve/resolved-bus.c b/src/resolve/resolved-bus.c index 1ef25ac..75ba29c 100644 --- a/src/resolve/resolved-bus.c +++ b/src/resolve/resolved-bus.c @@ -13,6 +13,7 @@ #include "missing_capability.h" #include "resolved-bus.h" #include "resolved-def.h" +#include "resolved-dns-stream.h" #include "resolved-dns-synthesize.h" #include "resolved-dnssd-bus.h" #include "resolved-dnssd.h" @@ -1832,6 +1833,7 @@ static int bus_method_reset_server_features(sd_bus_message *message, void *userd bus_client_log(message, "server feature reset"); + (void) dns_stream_disconnect_all(m); manager_reset_server_features(m); return sd_bus_reply_method_return(message, NULL); @@ -2218,9 +2220,15 @@ static int match_prepare_for_sleep(sd_bus_message *message, void *userdata, sd_b if (b) return 0; - log_debug("Coming back from suspend, verifying all RRs..."); + log_debug("Coming back from suspend, closing all TCP connections..."); + (void) dns_stream_disconnect_all(m); + + log_debug("Coming back from suspend, resetting all probed server features..."); + manager_reset_server_features(m); + log_debug("Coming back from suspend, verifying all RRs..."); manager_verify_all(m); + return 0; } diff --git a/src/resolve/resolved-dns-cache.c b/src/resolve/resolved-dns-cache.c index a9a6492..e90915e 100644 --- a/src/resolve/resolved-dns-cache.c +++ b/src/resolve/resolved-dns-cache.c @@ -531,6 +531,20 @@ static int dns_cache_put_positive( TAKE_PTR(i); return 0; } +/* https://www.iana.org/assignments/special-use-domain-names/special-use-domain-names.xhtml */ +/* https://www.iana.org/assignments/locally-served-dns-zones/locally-served-dns-zones.xhtml#transport-independent */ +static bool dns_special_use_domain_invalid_answer(DnsResourceKey *key, int rcode) { + /* Sometimes we know a domain exists, even if broken nameservers say otherwise. Make sure not to + * cache any answers we know are wrong. */ + + /* RFC9462 § 6.4: resolvers SHOULD respond to queries of any type other than SVCB for + * _dns.resolver.arpa. with NODATA and queries of any type for any domain name under resolver.arpa + * with NODATA. */ + if (dns_name_endswith(dns_resource_key_name(key), "resolver.arpa") > 0 && rcode == DNS_RCODE_NXDOMAIN) + return true; + + return false; +} static int dns_cache_put_negative( DnsCache *c, @@ -561,6 +575,8 @@ static int dns_cache_put_negative( return 0; if (dns_type_is_pseudo(key->type)) return 0; + if (dns_special_use_domain_invalid_answer(key, rcode)) + return 0; if (IN_SET(rcode, DNS_RCODE_SUCCESS, DNS_RCODE_NXDOMAIN)) { if (!soa) diff --git a/src/resolve/resolved-dns-query.c b/src/resolve/resolved-dns-query.c index 7eb6b97..16334c6 100644 --- a/src/resolve/resolved-dns-query.c +++ b/src/resolve/resolved-dns-query.c @@ -57,6 +57,21 @@ static void dns_query_candidate_stop(DnsQueryCandidate *c) { } } +static void dns_query_candidate_abandon(DnsQueryCandidate *c) { + DnsTransaction *t; + + assert(c); + + /* Abandon all the DnsTransactions attached to this query */ + + while ((t = set_steal_first(c->transactions))) { + t->wait_for_answer = true; + set_remove(t->notify_query_candidates, c); + set_remove(t->notify_query_candidates_done, c); + dns_transaction_gc(t); + } +} + static DnsQueryCandidate* dns_query_candidate_unlink(DnsQueryCandidate *c) { assert(c); @@ -354,6 +369,16 @@ static void dns_query_stop(DnsQuery *q) { dns_query_candidate_stop(c); } +static void dns_query_abandon(DnsQuery *q) { + assert(q); + + /* Thankfully transactions have their own timeouts */ + event_source_disable(q->timeout_event_source); + + LIST_FOREACH(candidates_by_query, c, q->candidates) + dns_query_candidate_abandon(c); +} + static void dns_query_unlink_candidates(DnsQuery *q) { assert(q); @@ -588,7 +613,7 @@ void dns_query_complete(DnsQuery *q, DnsTransactionState state) { (void) manager_monitor_send(q->manager, q->state, q->answer_rcode, q->answer_errno, q->question_idna, q->question_utf8, q->question_bypass, q->collected_questions, q->answer); - dns_query_stop(q); + dns_query_abandon(q); if (q->complete) q->complete(q); } diff --git a/src/resolve/resolved-dns-rr.c b/src/resolve/resolved-dns-rr.c index 00f7bea..b280a5a 100644 --- a/src/resolve/resolved-dns-rr.c +++ b/src/resolve/resolved-dns-rr.c @@ -181,6 +181,23 @@ bool dns_resource_key_is_dnssd_ptr(const DnsResourceKey *key) { dns_name_endswith(dns_resource_key_name(key), "_udp.local"); } +bool dns_resource_key_is_dnssd_two_label_ptr(const DnsResourceKey *key) { + assert(key); + + /* Check if this is a PTR resource key used in Service Instance + * Enumeration as described in RFC6763 § 4.1, excluding selective + * service names described in RFC6763 § 7.1. */ + + if (key->type != DNS_TYPE_PTR) + return false; + + const char *name = dns_resource_key_name(key); + if (dns_name_parent(&name) <= 0) + return false; + + return dns_name_equal(name, "_tcp.local") || dns_name_equal(name, "_udp.local"); +} + int dns_resource_key_equal(const DnsResourceKey *a, const DnsResourceKey *b) { int r; diff --git a/src/resolve/resolved-dns-rr.h b/src/resolve/resolved-dns-rr.h index fd15cc3..1a12933 100644 --- a/src/resolve/resolved-dns-rr.h +++ b/src/resolve/resolved-dns-rr.h @@ -305,6 +305,7 @@ DnsResourceKey* dns_resource_key_unref(DnsResourceKey *key); const char* dns_resource_key_name(const DnsResourceKey *key); bool dns_resource_key_is_address(const DnsResourceKey *key); bool dns_resource_key_is_dnssd_ptr(const DnsResourceKey *key); +bool dns_resource_key_is_dnssd_two_label_ptr(const DnsResourceKey *key); int dns_resource_key_equal(const DnsResourceKey *a, const DnsResourceKey *b); int dns_resource_key_match_rr(const DnsResourceKey *key, DnsResourceRecord *rr, const char *search_domain); int dns_resource_key_match_cname_or_dname(const DnsResourceKey *key, const DnsResourceKey *cname, const char *search_domain); diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c index 2e8b3e5..af8e9cd 100644 --- a/src/resolve/resolved-dns-scope.c +++ b/src/resolve/resolved-dns-scope.c @@ -424,7 +424,15 @@ static int dns_scope_socket( return r; } - if (ifindex != 0) { + bool addr_is_nonlocal = s->link && + !manager_find_link_address(s->manager, sa.sa.sa_family, sockaddr_in_addr(&sa.sa)) && + in_addr_is_localhost(sa.sa.sa_family, sockaddr_in_addr(&sa.sa)) == 0; + + if (addr_is_nonlocal && ifindex != 0) { + /* As a special exception we don't use UNICAST_IF if we notice that the specified IP address + * is on the local host. Otherwise, destination addresses on the local host result in + * EHOSTUNREACH, since Linux won't send the packets out of the specified interface, but + * delivers them directly to the local socket. */ r = socket_set_unicast_if(fd, sa.sa.sa_family, ifindex); if (r < 0) return r; @@ -463,19 +471,13 @@ static int dns_scope_socket( else { bool bound = false; - /* Let's temporarily bind the socket to the specified ifindex. The kernel currently takes - * only the SO_BINDTODEVICE/SO_BINDTOINDEX ifindex into account when making routing decisions + /* Let's temporarily bind the socket to the specified ifindex. Older kernels only take + * the SO_BINDTODEVICE/SO_BINDTOINDEX ifindex into account when making routing decisions * in connect() — and not IP_UNICAST_IF. We don't really want any of the other semantics of * SO_BINDTODEVICE/SO_BINDTOINDEX, hence we immediately unbind the socket after the fact * again. - * - * As a special exception we don't do this if we notice that the specified IP address is on - * the local host. SO_BINDTODEVICE in combination with destination addresses on the local - * host result in EHOSTUNREACH, since Linux won't send the packets out of the specified - * interface, but delivers them directly to the local socket. */ - if (s->link && - !manager_find_link_address(s->manager, sa.sa.sa_family, sockaddr_in_addr(&sa.sa)) && - in_addr_is_localhost(sa.sa.sa_family, sockaddr_in_addr(&sa.sa)) == 0) { + */ + if (addr_is_nonlocal) { r = socket_bind_to_ifindex(fd, ifindex); if (r < 0) return r; @@ -589,6 +591,29 @@ static DnsScopeMatch match_subnet_reverse_lookups( return _DNS_SCOPE_MATCH_INVALID; } +/* https://www.iana.org/assignments/special-use-domain-names/special-use-domain-names.xhtml */ +/* https://www.iana.org/assignments/locally-served-dns-zones/locally-served-dns-zones.xhtml */ +static bool dns_refuse_special_use_domain(const char *domain, DnsQuestion *question) { + /* RFC9462 § 6.4: resolvers SHOULD respond to queries of any type other than SVCB for + * _dns.resolver.arpa. with NODATA and queries of any type for any domain name under + * resolver.arpa with NODATA. */ + if (dns_name_equal(domain, "_dns.resolver.arpa") > 0) { + DnsResourceKey *t; + + /* Only SVCB is permitted to _dns.resolver.arpa */ + DNS_QUESTION_FOREACH(t, question) + if (t->type == DNS_TYPE_SVCB) + return false; + + return true; + } + + if (dns_name_endswith(domain, "resolver.arpa") > 0) + return true; + + return false; +} + DnsScopeMatch dns_scope_good_domain( DnsScope *s, DnsQuery *q) { @@ -601,6 +626,7 @@ DnsScopeMatch dns_scope_good_domain( /* This returns the following return values: * * DNS_SCOPE_NO → This scope is not suitable for lookups of this domain, at all + * DNS_SCOPE_LAST_RESORT→ This scope is not suitable, unless we have no alternative * DNS_SCOPE_MAYBE → This scope is suitable, but only if nothing else wants it * DNS_SCOPE_YES_BASE+n → This scope is suitable, and 'n' suffix labels match * @@ -643,6 +669,10 @@ DnsScopeMatch dns_scope_good_domain( if (dns_name_dont_resolve(domain)) return DNS_SCOPE_NO; + /* Avoid asking invalid questions of some special use domains */ + if (dns_refuse_special_use_domain(domain, question)) + return DNS_SCOPE_NO; + /* Never go to network for the _gateway, _outbound, _localdnsstub, _localdnsproxy domain — they're something special, synthesized locally. */ if (is_gateway_hostname(domain) || is_outbound_hostname(domain) || @@ -749,7 +779,7 @@ DnsScopeMatch dns_scope_good_domain( if ((s->family == AF_INET && dns_name_endswith(domain, "in-addr.arpa") > 0) || (s->family == AF_INET6 && dns_name_endswith(domain, "ip6.arpa") > 0)) - return DNS_SCOPE_MAYBE; + return DNS_SCOPE_LAST_RESORT; if ((dns_name_endswith(domain, "local") > 0 && /* only resolve names ending in .local via mDNS */ dns_name_equal(domain, "local") == 0 && /* but not the single-label "local" name itself */ @@ -772,7 +802,7 @@ DnsScopeMatch dns_scope_good_domain( if ((s->family == AF_INET && dns_name_endswith(domain, "in-addr.arpa") > 0) || (s->family == AF_INET6 && dns_name_endswith(domain, "ip6.arpa") > 0)) - return DNS_SCOPE_MAYBE; + return DNS_SCOPE_LAST_RESORT; if ((dns_name_is_single_label(domain) && /* only resolve single label names via LLMNR */ dns_name_equal(domain, "local") == 0 && /* don't resolve "local" with LLMNR, it's the top-level domain of mDNS after all, see above */ @@ -1459,9 +1489,10 @@ int dns_scope_announce(DnsScope *scope, bool goodbye) { continue; } - /* Collect service types for _services._dns-sd._udp.local RRs in a set */ + /* Collect service types for _services._dns-sd._udp.local RRs in a set. Only two-label names + * (not selective names) are considered according to RFC6763 § 9. */ if (!scope->announced && - dns_resource_key_is_dnssd_ptr(z->rr->key)) { + dns_resource_key_is_dnssd_two_label_ptr(z->rr->key)) { if (!set_contains(types, dns_resource_key_name(z->rr->key))) { r = set_ensure_put(&types, &dns_name_hash_ops, dns_resource_key_name(z->rr->key)); if (r < 0) diff --git a/src/resolve/resolved-dns-scope.h b/src/resolve/resolved-dns-scope.h index ca33fd0..b1d1206 100644 --- a/src/resolve/resolved-dns-scope.h +++ b/src/resolve/resolved-dns-scope.h @@ -18,6 +18,7 @@ typedef struct DnsScope DnsScope; typedef enum DnsScopeMatch { DNS_SCOPE_NO, + DNS_SCOPE_LAST_RESORT, DNS_SCOPE_MAYBE, DNS_SCOPE_YES_BASE, /* Add the number of matching labels to this */ DNS_SCOPE_YES_END = DNS_SCOPE_YES_BASE + DNS_N_LABELS_MAX, diff --git a/src/resolve/resolved-dns-stream.c b/src/resolve/resolved-dns-stream.c index ddd1db5..056ba77 100644 --- a/src/resolve/resolved-dns-stream.c +++ b/src/resolve/resolved-dns-stream.c @@ -593,3 +593,44 @@ void dns_stream_detach(DnsStream *s) { dns_server_unref_stream(s->server); } + +DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( + dns_stream_hash_ops, + void, + trivial_hash_func, + trivial_compare_func, + dns_stream_unref); + +int dns_stream_disconnect_all(Manager *m) { + _cleanup_(set_freep) Set *closed = NULL; + int r; + + assert(m); + + /* Terminates all TCP connections (called after system suspend for example, to speed up recovery) */ + + log_info("Closing all remaining TCP connections."); + + bool restart; + do { + restart = false; + + LIST_FOREACH(streams, s, m->dns_streams) { + r = set_ensure_put(&closed, &dns_stream_hash_ops, s); + if (r < 0) + return log_oom(); + if (r > 0) { + /* Haven't seen this one before. Close it. */ + dns_stream_ref(s); + (void) dns_stream_complete(s, ECONNRESET); + + /* This might have a ripple effect, let's hence no look at the list further, + * but scan from the beginning again */ + restart = true; + break; + } + } + } while (restart); + + return 0; +} diff --git a/src/resolve/resolved-dns-stream.h b/src/resolve/resolved-dns-stream.h index ba4a59e..912b9bf 100644 --- a/src/resolve/resolved-dns-stream.h +++ b/src/resolve/resolved-dns-stream.h @@ -126,3 +126,4 @@ static inline bool DNS_STREAM_QUEUED(DnsStream *s) { } void dns_stream_detach(DnsStream *s); +int dns_stream_disconnect_all(Manager *m); diff --git a/src/resolve/resolved-dns-stub.c b/src/resolve/resolved-dns-stub.c index c59e3b7..10b35da 100644 --- a/src/resolve/resolved-dns-stub.c +++ b/src/resolve/resolved-dns-stub.c @@ -837,12 +837,20 @@ static void dns_stub_query_complete(DnsQuery *query) { break; case DNS_TRANSACTION_NO_SERVERS: + /* We're not configured to give answers for this question. Refuse it. */ + (void) dns_stub_send_reply(q, DNS_RCODE_REFUSED); + break; + + case DNS_TRANSACTION_RR_TYPE_UNSUPPORTED: + /* This RR Type is not implemented */ + (void) dns_stub_send_reply(q, DNS_RCODE_NOTIMP); + break; + case DNS_TRANSACTION_INVALID_REPLY: case DNS_TRANSACTION_ERRNO: case DNS_TRANSACTION_ABORTED: case DNS_TRANSACTION_DNSSEC_FAILED: case DNS_TRANSACTION_NO_TRUST_ANCHOR: - case DNS_TRANSACTION_RR_TYPE_UNSUPPORTED: case DNS_TRANSACTION_NETWORK_DOWN: case DNS_TRANSACTION_NO_SOURCE: case DNS_TRANSACTION_STUB_LOOP: diff --git a/src/resolve/resolved-dns-synthesize.c b/src/resolve/resolved-dns-synthesize.c index 5bde29c..6144dc0 100644 --- a/src/resolve/resolved-dns-synthesize.c +++ b/src/resolve/resolved-dns-synthesize.c @@ -463,7 +463,7 @@ int dns_synthesize_answer( name = dns_resource_key_name(key); - if (dns_name_is_root(name)) { + if (dns_name_is_root(name) || dns_name_endswith(name, "resolver.arpa") > 0) { /* Do nothing. */ } else if (dns_name_dont_resolve(name)) { diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c index 8ff5653..ad8b88e 100644 --- a/src/resolve/resolved-dns-transaction.c +++ b/src/resolve/resolved-dns-transaction.c @@ -175,6 +175,9 @@ DnsTransaction* dns_transaction_gc(DnsTransaction *t) { if (t->block_gc > 0) return t; + if (t->wait_for_answer && IN_SET(t->state, DNS_TRANSACTION_PENDING, DNS_TRANSACTION_VALIDATING)) + return t; + if (set_isempty(t->notify_query_candidates) && set_isempty(t->notify_query_candidates_done) && set_isempty(t->notify_zone_items) && @@ -2229,7 +2232,7 @@ static int dns_transaction_add_dnssec_transaction(DnsTransaction *t, DnsResource return 1; } -static int dns_transaction_request_dnssec_rr(DnsTransaction *t, DnsResourceKey *key) { +static int dns_transaction_request_dnssec_rr_full(DnsTransaction *t, DnsResourceKey *key, DnsTransaction **ret) { _cleanup_(dns_answer_unrefp) DnsAnswer *a = NULL; DnsTransaction *aux; int r; @@ -2246,13 +2249,18 @@ static int dns_transaction_request_dnssec_rr(DnsTransaction *t, DnsResourceKey * if (r < 0) return r; + if (ret) + *ret = NULL; return 0; } /* This didn't work, ask for it via the network/cache then. */ r = dns_transaction_add_dnssec_transaction(t, key, &aux); - if (r == -ELOOP) /* This would result in a cyclic dependency */ + if (r == -ELOOP) { /* This would result in a cyclic dependency */ + if (ret) + *ret = NULL; return 0; + } if (r < 0) return r; @@ -2260,11 +2268,19 @@ static int dns_transaction_request_dnssec_rr(DnsTransaction *t, DnsResourceKey * r = dns_transaction_go(aux); if (r < 0) return r; + if (ret) + *ret = aux; } return 1; } +static int dns_transaction_request_dnssec_rr(DnsTransaction *t, DnsResourceKey *key) { + assert(t); + assert(key); + return dns_transaction_request_dnssec_rr_full(t, key, NULL); +} + static int dns_transaction_negative_trust_anchor_lookup(DnsTransaction *t, const char *name) { int r; @@ -2365,6 +2381,8 @@ static bool dns_transaction_dnssec_supported_full(DnsTransaction *t) { int dns_transaction_request_dnssec_keys(DnsTransaction *t) { DnsResourceRecord *rr; + /* Have we already requested a record that would be sufficient to validate an insecure delegation? */ + bool chased_insecure = false; int r; assert(t); @@ -2377,11 +2395,11 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) { * - For RRSIG we get the matching DNSKEY * - For DNSKEY we get the matching DS * - For unsigned SOA/NS we get the matching DS - * - For unsigned CNAME/DNAME/DS we get the parent SOA RR - * - For other unsigned RRs we get the matching SOA RR + * - For unsigned CNAME/DNAME/DS we get the parent DS RR + * - For other unsigned RRs we get the matching DS RR * - For SOA/NS queries with no matching response RR, and no NSEC/NSEC3, the DS RR - * - For DS queries with no matching response RRs, and no NSEC/NSEC3, the parent's SOA RR - * - For other queries with no matching response RRs, and no NSEC/NSEC3, the SOA RR + * - For DS queries with no matching response RRs, and no NSEC/NSEC3, the parent's DS RR + * - For other queries with no matching response RRs, and no NSEC/NSEC3, the DS RR */ if (FLAGS_SET(t->query_flags, SD_RESOLVED_NO_VALIDATE) || t->scope->dnssec_mode == DNSSEC_NO) @@ -2408,6 +2426,7 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) { case DNS_TYPE_RRSIG: { /* For each RRSIG we request the matching DNSKEY */ _cleanup_(dns_resource_key_unrefp) DnsResourceKey *dnskey = NULL; + DnsTransaction *aux; /* If this RRSIG is about a DNSKEY RR and the * signer is the same as the owner, then we @@ -2444,9 +2463,22 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) { log_debug("Requesting DNSKEY to validate transaction %" PRIu16" (%s, RRSIG with key tag: %" PRIu16 ").", t->id, dns_resource_key_name(rr->key), rr->rrsig.key_tag); - r = dns_transaction_request_dnssec_rr(t, dnskey); + r = dns_transaction_request_dnssec_rr_full(t, dnskey, &aux); if (r < 0) return r; + + /* If we are requesting a DNSKEY, we can anticiapte that we will want the matching DS + * in the near future. Let's request it in advance so we don't have to wait in the + * common case. */ + if (aux) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *ds = + dns_resource_key_new(rr->key->class, DNS_TYPE_DS, dns_resource_key_name(dnskey)); + if (!ds) + return -ENOMEM; + r = dns_transaction_request_dnssec_rr(t, ds); + if (r < 0) + return r; + } break; } @@ -2521,6 +2553,7 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) { if (r > 0) continue; + chased_insecure = true; ds = dns_resource_key_new(rr->key->class, DNS_TYPE_DS, dns_resource_key_name(rr->key)); if (!ds) return -ENOMEM; @@ -2537,11 +2570,11 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) { case DNS_TYPE_DS: case DNS_TYPE_CNAME: case DNS_TYPE_DNAME: { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *soa = NULL; + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *ds = NULL; const char *name; /* CNAMEs and DNAMEs cannot be located at a - * zone apex, hence ask for the parent SOA for + * zone apex, hence ask for the parent DS for * unsigned CNAME/DNAME RRs, maybe that's the * apex. But do all that only if this is * actually a response to our original @@ -2575,13 +2608,13 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) { if (r == 0) continue; - soa = dns_resource_key_new(rr->key->class, DNS_TYPE_SOA, name); - if (!soa) + ds = dns_resource_key_new(rr->key->class, DNS_TYPE_DS, name); + if (!ds) return -ENOMEM; - log_debug("Requesting parent SOA to validate transaction %" PRIu16 " (%s, unsigned CNAME/DNAME/DS RRset).", + log_debug("Requesting parent DS to validate transaction %" PRIu16 " (%s, unsigned CNAME/DNAME/DS RRset).", t->id, dns_resource_key_name(rr->key)); - r = dns_transaction_request_dnssec_rr(t, soa); + r = dns_transaction_request_dnssec_rr(t, ds); if (r < 0) return r; @@ -2589,11 +2622,11 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) { } default: { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *soa = NULL; + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *ds = NULL; /* For other unsigned RRsets (including * NSEC/NSEC3!), look for proof the zone is - * unsigned, by requesting the SOA RR of the + * unsigned, by requesting the DS RR of the * zone. However, do so only if they are * directly relevant to our original * question. */ @@ -2610,13 +2643,13 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) { if (r > 0) continue; - soa = dns_resource_key_new(rr->key->class, DNS_TYPE_SOA, dns_resource_key_name(rr->key)); - if (!soa) + ds = dns_resource_key_new(rr->key->class, DNS_TYPE_DS, dns_resource_key_name(rr->key)); + if (!ds) return -ENOMEM; - log_debug("Requesting SOA to validate transaction %" PRIu16 " (%s, unsigned non-SOA/NS RRset <%s>).", + log_debug("Requesting DS to validate transaction %" PRIu16 " (%s, unsigned non-SOA/NS RRset <%s>).", t->id, dns_resource_key_name(rr->key), dns_resource_record_to_string(rr)); - r = dns_transaction_request_dnssec_rr(t, soa); + r = dns_transaction_request_dnssec_rr(t, ds); if (r < 0) return r; break; @@ -2631,49 +2664,38 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) { if (r < 0) return r; if (r > 0) { - const char *name, *signed_status; - uint16_t type = 0; + const char *name = dns_resource_key_name(dns_transaction_key(t)); + bool was_signed = dns_answer_contains_nsec_or_nsec3(t->answer); - name = dns_resource_key_name(dns_transaction_key(t)); - signed_status = dns_answer_contains_nsec_or_nsec3(t->answer) ? "signed" : "unsigned"; - - /* If this was a SOA or NS request, then check if there's a DS RR for the same domain. Note that this - * could also be used as indication that we are not at a zone apex, but in real world setups there are - * too many broken DNS servers (Hello, incapdns.net!) where non-terminal zones return NXDOMAIN even - * though they have further children. If this was a DS request, then it's signed when the parent zone - * is signed, hence ask the parent SOA in that case. If this was any other RR then ask for the SOA RR, - * to see if that is signed. */ - - if (dns_transaction_key(t)->type == DNS_TYPE_DS) { - r = dns_name_parent(&name); - if (r > 0) { - type = DNS_TYPE_SOA; - log_debug("Requesting parent SOA (%s %s) to validate transaction %" PRIu16 " (%s, %s empty DS response).", - special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), name, t->id, - dns_resource_key_name(dns_transaction_key(t)), signed_status); - } else + /* If the response is empty, seek the DS for this name, just in case we're at a zone cut + * already, unless we just requested the DS, in which case we have to ask the parent to make + * progress. + * + * If this was an SOA or NS request, we could also skip to the parent, but in real world + * setups there are too many broken DNS servers (Hello, incapdns.net!) where non-terminal + * zones return NXDOMAIN even though they have further children. */ + + if (chased_insecure || was_signed) + /* In this case we already reqeusted what we need above. */ + name = NULL; + else if (dns_transaction_key(t)->type == DNS_TYPE_DS) + /* If the DS response is empty, we'll walk up the dns labels requesting DS until we + * find a referral to the SOA or hit it anyway and get a positive DS response. */ + if (dns_name_parent(&name) <= 0) name = NULL; - } else if (IN_SET(dns_transaction_key(t)->type, DNS_TYPE_SOA, DNS_TYPE_NS)) { - - type = DNS_TYPE_DS; - log_debug("Requesting DS (%s %s) to validate transaction %" PRIu16 " (%s, %s empty SOA/NS response).", - special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), name, t->id, name, signed_status); - - } else { - type = DNS_TYPE_SOA; - log_debug("Requesting SOA (%s %s) to validate transaction %" PRIu16 " (%s, %s empty non-SOA/NS/DS response).", - special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), name, t->id, name, signed_status); - } - if (name) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *soa = NULL; + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *ds = NULL; + + log_debug("Requesting DS (%s %s) to validate transaction %" PRIu16 " (%s empty response).", + special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), name, t->id, + dns_resource_key_name(dns_transaction_key(t))); - soa = dns_resource_key_new(dns_transaction_key(t)->class, type, name); - if (!soa) + ds = dns_resource_key_new(dns_transaction_key(t)->class, DNS_TYPE_DS, name); + if (!ds) return -ENOMEM; - r = dns_transaction_request_dnssec_rr(t, soa); + r = dns_transaction_request_dnssec_rr(t, ds); if (r < 0) return r; } @@ -2753,7 +2775,6 @@ static int dns_transaction_requires_rrsig(DnsTransaction *t, DnsResourceRecord * DnsTransaction *dt; /* For SOA or NS RRs we look for a matching DS transaction */ - SET_FOREACH(dt, t->dnssec_transactions) { if (dns_transaction_key(dt)->class != rr->key->class) @@ -2761,7 +2782,7 @@ static int dns_transaction_requires_rrsig(DnsTransaction *t, DnsResourceRecord * if (dns_transaction_key(dt)->type != DNS_TYPE_DS) continue; - r = dns_name_equal(dns_resource_key_name(dns_transaction_key(dt)), dns_resource_key_name(rr->key)); + r = dns_name_endswith(dns_resource_key_name(rr->key), dns_resource_key_name(dns_transaction_key(dt))); if (r < 0) return r; if (r == 0) @@ -2790,16 +2811,16 @@ static int dns_transaction_requires_rrsig(DnsTransaction *t, DnsResourceRecord * DnsTransaction *dt; /* - * CNAME/DNAME RRs cannot be located at a zone apex, hence look directly for the parent SOA. + * CNAME/DNAME RRs cannot be located at a zone apex, hence look directly for the parent DS. * - * DS RRs are signed if the parent is signed, hence also look at the parent SOA + * DS RRs are signed if the parent is signed, hence also look at the parent DS */ SET_FOREACH(dt, t->dnssec_transactions) { if (dns_transaction_key(dt)->class != rr->key->class) continue; - if (dns_transaction_key(dt)->type != DNS_TYPE_SOA) + if (dns_transaction_key(dt)->type != DNS_TYPE_DS) continue; if (!parent) { @@ -2817,7 +2838,7 @@ static int dns_transaction_requires_rrsig(DnsTransaction *t, DnsResourceRecord * } } - r = dns_name_equal(dns_resource_key_name(dns_transaction_key(dt)), parent); + r = dns_name_endswith(parent, dns_resource_key_name(dns_transaction_key(dt))); if (r < 0) return r; if (r == 0) @@ -2832,25 +2853,26 @@ static int dns_transaction_requires_rrsig(DnsTransaction *t, DnsResourceRecord * default: { DnsTransaction *dt; - /* Any other kind of RR (including DNSKEY/NSEC/NSEC3). Let's see if our SOA lookup was authenticated */ + /* Any other kind of RR (including DNSKEY/NSEC/NSEC3). Let's see if our DS lookup was authenticated */ SET_FOREACH(dt, t->dnssec_transactions) { - if (dns_transaction_key(dt)->class != rr->key->class) continue; - if (dns_transaction_key(dt)->type != DNS_TYPE_SOA) + if (dns_transaction_key(dt)->type != DNS_TYPE_DS) continue; - r = dns_name_equal(dns_resource_key_name(dns_transaction_key(dt)), dns_resource_key_name(rr->key)); + r = dns_name_endswith(dns_resource_key_name(rr->key), dns_resource_key_name(dns_transaction_key(dt))); if (r < 0) return r; if (r == 0) continue; - /* We found the transaction that was supposed to find the SOA RR for us. It was - * successful, but found no RR for us. This means we are not at a zone cut. In this - * case, we require authentication if the SOA lookup was authenticated too. */ - return FLAGS_SET(dt->answer_query_flags, SD_RESOLVED_AUTHENTICATED); + if (!FLAGS_SET(dt->answer_query_flags, SD_RESOLVED_AUTHENTICATED)) + return false; + + /* We expect this to be signed when the DS record exists, and don't expect it to be + * signed when the DS record is proven not to exist. */ + return dns_answer_match_key(dt->answer, dns_transaction_key(dt), NULL); } return true; @@ -2920,7 +2942,6 @@ static int dns_transaction_requires_nsec(DnsTransaction *t) { char key_str[DNS_RESOURCE_KEY_STRING_MAX]; DnsTransaction *dt; const char *name; - uint16_t type = 0; int r; assert(t); @@ -2955,43 +2976,37 @@ static int dns_transaction_requires_nsec(DnsTransaction *t) { name = dns_resource_key_name(dns_transaction_key(t)); - if (dns_transaction_key(t)->type == DNS_TYPE_DS) { - - /* We got a negative reply for this DS lookup? DS RRs are signed when their parent zone is signed, - * hence check the parent SOA in this case. */ - + if (IN_SET(dns_transaction_key(t)->type, DNS_TYPE_DS, DNS_TYPE_CNAME, DNS_TYPE_DNAME)) { + /* We got a negative reply for this DS/CNAME/DNAME lookup? Check the parent in this case to + * see if this answer should have been signed. */ r = dns_name_parent(&name); if (r < 0) return r; if (r == 0) return true; + } - type = DNS_TYPE_SOA; - - } else if (IN_SET(dns_transaction_key(t)->type, DNS_TYPE_SOA, DNS_TYPE_NS)) - /* We got a negative reply for this SOA/NS lookup? If so, check if there's a DS RR for this */ - type = DNS_TYPE_DS; - else - /* For all other negative replies, check for the SOA lookup */ - type = DNS_TYPE_SOA; - - /* For all other RRs we check the SOA on the same level to see + /* For all other RRs we check the DS on the same level to see * if it's signed. */ SET_FOREACH(dt, t->dnssec_transactions) { - if (dns_transaction_key(dt)->class != dns_transaction_key(t)->class) continue; - if (dns_transaction_key(dt)->type != type) + if (dns_transaction_key(dt)->type != DNS_TYPE_DS) continue; - r = dns_name_equal(dns_resource_key_name(dns_transaction_key(dt)), name); + r = dns_name_endswith(name, dns_resource_key_name(dns_transaction_key(dt))); if (r < 0) return r; if (r == 0) continue; - return FLAGS_SET(dt->answer_query_flags, SD_RESOLVED_AUTHENTICATED); + if (!FLAGS_SET(dt->answer_query_flags, SD_RESOLVED_AUTHENTICATED)) + return false; + + /* We expect this to be signed when the DS record exists, and don't expect it to be signed + * when the DS record is proven not to exist. */ + return dns_answer_match_key(dt->answer, dns_transaction_key(dt), NULL); } /* If in doubt, require NSEC/NSEC3 */ diff --git a/src/resolve/resolved-dns-transaction.h b/src/resolve/resolved-dns-transaction.h index 2fd8720..6be7c5f 100644 --- a/src/resolve/resolved-dns-transaction.h +++ b/src/resolve/resolved-dns-transaction.h @@ -134,6 +134,11 @@ struct DnsTransaction { unsigned block_gc; + /* Set when we're willing to let this transaction live beyond it's usefulness for the original query, + * for caching purposes. This blocks gc while there is still a chance we might still receive an + * answer. */ + bool wait_for_answer; + LIST_FIELDS(DnsTransaction, transactions_by_scope); LIST_FIELDS(DnsTransaction, transactions_by_stream); LIST_FIELDS(DnsTransaction, transactions_by_key); diff --git a/src/resolve/resolved-dns-trust-anchor.c b/src/resolve/resolved-dns-trust-anchor.c index 1703c43..8aea5e1 100644 --- a/src/resolve/resolved-dns-trust-anchor.c +++ b/src/resolve/resolved-dns-trust-anchor.c @@ -165,6 +165,11 @@ static int dns_trust_anchor_add_builtin_negative(DnsTrustAnchor *d) { /* Defined by RFC 8375. The most official choice. */ "home.arpa\0" + /* RFC 9462 doesn't mention DNSSEC, but this domain + * can't really be signed and clients need to validate + * the answer before using it anyway. */ + "resolver.arpa\0" + /* RFC 8880 says because the 'ipv4only.arpa' zone has to * be an insecure delegation, DNSSEC cannot be used to * protect these answers from tampering by malicious diff --git a/src/rpm/macros.systemd.in b/src/rpm/macros.systemd.in index 241e4b9..317e13d 100644 --- a/src/rpm/macros.systemd.in +++ b/src/rpm/macros.systemd.in @@ -13,6 +13,7 @@ %_udevhwdbdir {{UDEV_HWDB_DIR}} %_udevrulesdir {{UDEV_RULES_DIR}} %_journalcatalogdir {{SYSTEMD_CATALOG_DIR}} +%_kernel_install_dir {{KERNEL_INSTALL_DIR}} %_binfmtdir {{BINFMT_DIR}} %_sysctldir {{SYSCTL_DIR}} %_sysusersdir {{SYSUSERS_DIR}} diff --git a/src/shared/base-filesystem.c b/src/shared/base-filesystem.c index 569ef46..a4e2dae 100644 --- a/src/shared/base-filesystem.c +++ b/src/shared/base-filesystem.c @@ -120,13 +120,13 @@ static const BaseFilesystem table[] = { # else # error "Unknown RISC-V ABI" # endif -#elif defined(__s390__) - /* s390-linux-gnu */ #elif defined(__s390x__) { "lib64", 0, "usr/lib/"LIB_ARCH_TUPLE"\0" "usr/lib64\0" "usr/lib\0", "ld-lsb-s390x.so.3" }, # define KNOW_LIB64_DIRS 1 +#elif defined(__s390__) + /* s390-linux-gnu */ #elif defined(__sparc__) #endif /* gcc doesn't allow pragma to be used within constructs, hence log about this separately below */ diff --git a/src/shared/blockdev-util.c b/src/shared/blockdev-util.c index c906aec..7a2dd1c 100644 --- a/src/shared/blockdev-util.c +++ b/src/shared/blockdev-util.c @@ -11,6 +11,7 @@ #include "alloc-util.h" #include "blockdev-util.h" #include "btrfs-util.h" +#include "device-private.h" #include "device-util.h" #include "devnum-util.h" #include "dirent-util.h" @@ -367,24 +368,36 @@ int lock_whole_block_device(dev_t devt, int operation) { } int blockdev_partscan_enabled(int fd) { - _cleanup_free_ char *p = NULL, *buf = NULL; - unsigned long long ull; - struct stat st; - int r; - - /* Checks if partition scanning is correctly enabled on the block device */ + _cleanup_(sd_device_unrefp) sd_device *dev = NULL; + unsigned capability; + int r, ext_range; - if (fstat(fd, &st) < 0) - return -errno; + /* Checks if partition scanning is correctly enabled on the block device. + * + * The 'GENHD_FL_NO_PART_SCAN' flag was introduced by + * https://github.com/torvalds/linux/commit/d27769ec3df1a8de9ca450d2dcd72d1ab259ba32 (v3.2). + * But at that time, the flag is also effectively implied when 'minors' element of 'struct gendisk' + * 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/e81cd5a983bb35dabd38ee472cf3fea1c63e0f23 (v6.3), + * the 'capability' sysfs attribute is deprecated, hence we cannot check the flag from it. + * + * 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. */ - if (!S_ISBLK(st.st_mode)) - return -ENOTBLK; + assert(fd >= 0); - if (asprintf(&p, "/sys/dev/block/%u:%u/capability", major(st.st_rdev), minor(st.st_rdev)) < 0) - return -ENOMEM; + r = block_device_new_from_fd(fd, 0, &dev); + if (r < 0) + return r; - r = read_one_line_file(p, &buf); - if (r == -ENOENT) /* If the capability file doesn't exist then we are most likely looking at a + 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 * partition scanning on for it (we do for its parent, but not for the partition * itself). */ @@ -392,7 +405,13 @@ int blockdev_partscan_enabled(int fd) { if (r < 0) return r; - r = safe_atollu_full(buf, 16, &ull); + if (ext_range <= 1) /* The valus should be always positive, but the kernel uses '%d' for the + * attribute. Let's gracefully handle zero or negative. */ + return false; + + r = device_get_sysattr_unsigned_full(dev, "capability", 16, &capability); + if (r == -ENOENT) + return false; if (r < 0) return r; @@ -400,7 +419,12 @@ int blockdev_partscan_enabled(int fd) { #define GENHD_FL_NO_PART_SCAN (0x0200) #endif - return !FLAGS_SET(ull, GENHD_FL_NO_PART_SCAN); + /* If 0x200 is set, part scanning is definitely off. */ + if (FLAGS_SET(capability, GENHD_FL_NO_PART_SCAN)) + return false; + + /* Otherwise, assume part scanning is on, we have no further checks available. Assume the best. */ + return true; } static int blockdev_is_encrypted(const char *sysfs_path, unsigned depth_left) { diff --git a/src/shared/bpf-dlopen.c b/src/shared/bpf-dlopen.c index 15301ae..f00dbea 100644 --- a/src/shared/bpf-dlopen.c +++ b/src/shared/bpf-dlopen.c @@ -74,18 +74,23 @@ int dlopen_bpf(void) { return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "neither libbpf.so.1 nor libbpf.so.0 are installed: %s", dlerror()); + log_debug("Loaded 'libbpf.so.0' via dlopen()"); + /* symbols deprecated in 1.0 we use as compat */ r = dlsym_many_or_warn( dl, LOG_DEBUG, #if MODERN_LIBBPF /* Don't exist anymore in new libbpf, hence cannot type check them */ DLSYM_ARG_FORCE(bpf_create_map), - DLSYM_ARG_FORCE(bpf_probe_prog_type)); + DLSYM_ARG_FORCE(bpf_probe_prog_type) #else DLSYM_ARG(bpf_create_map), - DLSYM_ARG(bpf_probe_prog_type)); + DLSYM_ARG(bpf_probe_prog_type) #endif + ); } else { + log_debug("Loaded 'libbpf.so.1' via dlopen()"); + /* symbols available from 0.7.0 */ r = dlsym_many_or_warn( dl, LOG_DEBUG, @@ -99,6 +104,8 @@ int dlopen_bpf(void) { #endif ); } + if (r < 0) + return r; r = dlsym_many_or_warn( dl, LOG_DEBUG, diff --git a/src/shared/btrfs-util.c b/src/shared/btrfs-util.c index b3e4b50..2ed6bf2 100644 --- a/src/shared/btrfs-util.c +++ b/src/shared/btrfs-util.c @@ -65,7 +65,7 @@ int btrfs_subvol_set_read_only_at(int dir_fd, const char *path, bool b) { assert(dir_fd >= 0 || dir_fd == AT_FDCWD); - fd = xopenat(dir_fd, path, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY, /* xopen_flags = */ 0, /* mode = */ 0); + fd = xopenat(dir_fd, path, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); if (fd < 0) return fd; @@ -113,7 +113,7 @@ int btrfs_get_block_device_at(int dir_fd, const char *path, dev_t *ret) { assert(path); assert(ret); - fd = xopenat(dir_fd, path, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY, /* xopen_flags = */ 0, /* mode = */ 0); + fd = xopenat(dir_fd, path, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); if (fd < 0) return fd; @@ -1276,8 +1276,6 @@ static int subvol_snapshot_children( if (FLAGS_SET(flags, BTRFS_SNAPSHOT_LOCK_BSD)) { subvolume_fd = xopenat_lock(new_fd, subvolume, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW, - /* xopen_flags = */ 0, - /* mode = */ 0, LOCK_BSD, LOCK_EX); if (subvolume_fd < 0) @@ -1445,7 +1443,7 @@ int btrfs_subvol_snapshot_at_full( assert(dir_fdt >= 0 || dir_fdt == AT_FDCWD); assert(to); - old_fd = xopenat(dir_fdf, from, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY, /* xopen_flags = */ 0, /* mode = */ 0); + old_fd = xopenat(dir_fdf, from, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); if (old_fd < 0) return old_fd; @@ -1482,8 +1480,6 @@ int btrfs_subvol_snapshot_at_full( if (FLAGS_SET(flags, BTRFS_SNAPSHOT_LOCK_BSD)) { subvolume_fd = xopenat_lock(new_fd, subvolume, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW, - /* xopen_flags = */ 0, - /* mode = */ 0, LOCK_BSD, LOCK_EX); if (subvolume_fd < 0) diff --git a/src/shared/copy.c b/src/shared/copy.c index c0e30cd..2b87cba 100644 --- a/src/shared/copy.c +++ b/src/shared/copy.c @@ -208,6 +208,7 @@ int copy_bytes_full( r = reflink_range(fdf, foffset, fdt, toffset, max_bytes == UINT64_MAX ? 0 : max_bytes); /* partial reflink */ if (r >= 0) { off_t t; + int ret; /* This worked, yay! Now — to be fully correct — let's adjust the file pointers */ if (max_bytes == UINT64_MAX) { @@ -226,7 +227,14 @@ int copy_bytes_full( if (t < 0) return -errno; - return 0; /* we copied the whole thing, hence hit EOF, return 0 */ + if (FLAGS_SET(copy_flags, COPY_VERIFY_LINKED)) { + r = fd_verify_linked(fdf); + if (r < 0) + return r; + } + + /* We copied the whole thing, hence hit EOF, return 0. */ + ret = 0; } else { t = lseek(fdf, foffset + max_bytes, SEEK_SET); if (t < 0) @@ -236,8 +244,18 @@ int copy_bytes_full( if (t < 0) return -errno; - return 1; /* we copied only some number of bytes, which worked, but this means we didn't hit EOF, return 1 */ + /* We copied only some number of bytes, which worked, but + * this means we didn't hit EOF, return 1. */ + ret = 1; + } + + if (FLAGS_SET(copy_flags, COPY_VERIFY_LINKED)) { + r = fd_verify_linked(fdf); + if (r < 0) + return r; } + + return ret; } } } @@ -316,7 +334,7 @@ int copy_bytes_full( if (try_cfr) { n = try_copy_file_range(fdf, NULL, fdt, NULL, m, 0u); if (n < 0) { - if (!IN_SET(n, -EINVAL, -ENOSYS, -EXDEV, -EBADF)) + if (!IN_SET(n, -EINVAL, -ENOSYS, -EXDEV, -EBADF, -EOPNOTSUPP)) return n; try_cfr = false; @@ -483,6 +501,12 @@ int copy_bytes_full( copied_something = true; } + if (FLAGS_SET(copy_flags, COPY_VERIFY_LINKED)) { + r = fd_verify_linked(fdf); + if (r < 0) + return r; + } + if (copy_flags & COPY_TRUNCATE) { off_t off = lseek(fdt, 0, SEEK_CUR); if (off < 0) @@ -508,7 +532,6 @@ static int fd_copy_symlink( _cleanup_free_ char *target = NULL; int r; - assert(from); assert(st); assert(to); @@ -526,7 +549,10 @@ static int fd_copy_symlink( mac_selinux_create_file_clear(); if (r < 0) { if (FLAGS_SET(copy_flags, COPY_GRACEFUL_WARN) && (ERRNO_IS_PRIVILEGE(r) || ERRNO_IS_NOT_SUPPORTED(r))) { - log_notice_errno(r, "Failed to copy symlink '%s', ignoring: %m", from); + log_notice_errno(r, "Failed to copy symlink%s%s%s, ignoring: %m", + isempty(from) ? "" : " '", + strempty(from), + isempty(from) ? "" : "'"); return 0; } @@ -757,7 +783,6 @@ static int fd_copy_regular( _cleanup_close_ int fdf = -EBADF, fdt = -EBADF; int r, q; - assert(from); assert(st); assert(to); @@ -767,9 +792,9 @@ static int fd_copy_regular( if (r > 0) /* worked! */ return 0; - fdf = openat(df, from, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); + fdf = xopenat(df, from, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); if (fdf < 0) - return -errno; + return fdf; if (copy_flags & COPY_MAC_CREATE) { r = mac_selinux_create_file_prepare_at(dt, to, S_IFREG); @@ -797,6 +822,12 @@ static int fd_copy_regular( (void) futimens(fdt, (struct timespec[]) { st->st_atim, st->st_mtim }); (void) copy_xattr(fdf, NULL, fdt, NULL, copy_flags); + if (FLAGS_SET(copy_flags, COPY_VERIFY_LINKED)) { + r = fd_verify_linked(fdf); + if (r < 0) + return r; + } + if (copy_flags & COPY_FSYNC) { if (fsync(fdt) < 0) { r = -errno; @@ -830,7 +861,6 @@ static int fd_copy_fifo( HardlinkContext *hardlink_context) { int r; - assert(from); assert(st); assert(to); @@ -849,7 +879,10 @@ static int fd_copy_fifo( if (copy_flags & COPY_MAC_CREATE) mac_selinux_create_file_clear(); if (FLAGS_SET(copy_flags, COPY_GRACEFUL_WARN) && (ERRNO_IS_NEG_PRIVILEGE(r) || ERRNO_IS_NEG_NOT_SUPPORTED(r))) { - log_notice_errno(r, "Failed to copy fifo '%s', ignoring: %m", from); + log_notice_errno(r, "Failed to copy fifo%s%s%s, ignoring: %m", + isempty(from) ? "" : " '", + strempty(from), + isempty(from) ? "" : "'"); return 0; } else if (r < 0) return r; @@ -881,7 +914,6 @@ static int fd_copy_node( HardlinkContext *hardlink_context) { int r; - assert(from); assert(st); assert(to); @@ -900,7 +932,10 @@ static int fd_copy_node( if (copy_flags & COPY_MAC_CREATE) mac_selinux_create_file_clear(); if (FLAGS_SET(copy_flags, COPY_GRACEFUL_WARN) && (ERRNO_IS_NEG_PRIVILEGE(r) || ERRNO_IS_NEG_NOT_SUPPORTED(r))) { - log_notice_errno(r, "Failed to copy node '%s', ignoring: %m", from); + log_notice_errno(r, "Failed to copy node%s%s%s, ignoring: %m", + isempty(from) ? "" : " '", + strempty(from), + isempty(from) ? "" : "'"); return 0; } else if (r < 0) return r; @@ -955,12 +990,9 @@ static int fd_copy_directory( if (depth_left == 0) return -ENAMETOOLONG; - if (from) - fdf = openat(df, from, O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); - else - fdf = fcntl(df, F_DUPFD_CLOEXEC, 3); + fdf = xopenat(df, from, O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); if (fdf < 0) - return -errno; + return fdf; if (!hardlink_context) { /* If recreating hardlinks is requested let's set up a context for that now. */ @@ -984,19 +1016,19 @@ static int fd_copy_directory( exists = r >= 0; - fdt = xopenat_lock(dt, to, - O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW|(exists ? 0 : O_CREAT|O_EXCL), - (copy_flags & COPY_MAC_CREATE ? XO_LABEL : 0)|(set_contains(subvolumes, st) ? XO_SUBVOLUME : 0), - st->st_mode & 07777, - copy_flags & COPY_LOCK_BSD ? LOCK_BSD : LOCK_NONE, - LOCK_EX); + fdt = xopenat_lock_full(dt, to, + O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW|(exists ? 0 : O_CREAT|O_EXCL), + (copy_flags & COPY_MAC_CREATE ? XO_LABEL : 0)|(set_contains(subvolumes, st) ? XO_SUBVOLUME : 0), + st->st_mode & 07777, + copy_flags & COPY_LOCK_BSD ? LOCK_BSD : LOCK_NONE, + LOCK_EX); if (fdt < 0) return fdt; r = 0; if (PTR_TO_INT(hashmap_get(denylist, st)) == DENY_CONTENTS) { - log_debug("%s is in the denylist, not recursing", from); + log_debug("%s is in the denylist, not recursing", from ?: "file to copy"); goto finish; } @@ -1030,7 +1062,8 @@ static int fd_copy_directory( } if (PTR_TO_INT(hashmap_get(denylist, &buf)) == DENY_INODE) { - log_debug("%s/%s is in the denylist, ignoring", from, de->d_name); + log_debug("%s%s%s is in the denylist, ignoring", + strempty(from), isempty(from) ? "" : "/", de->d_name); continue; } @@ -1163,10 +1196,10 @@ static int fd_copy_tree_generic( DenyType t = PTR_TO_INT(hashmap_get(denylist, st)); if (t == DENY_INODE) { - log_debug("%s is in the denylist, ignoring", from); + log_debug("%s is in the denylist, ignoring", from ?: "file to copy"); return 0; } else if (t == DENY_CONTENTS) - log_debug("%s is configured to have its contents excluded, but is not a directory", from); + log_debug("%s is configured to have its contents excluded, but is not a directory", from ?: "file to copy"); r = fd_copy_leaf(df, from, st, dt, to, override_uid, override_gid, copy_flags, hardlink_context, display_path, progress_bytes, userdata); /* We just tried to copy a leaf node of the tree. If it failed because the node already exists *and* the COPY_REPLACE flag has been provided, we should unlink the node and re-copy. */ @@ -1198,11 +1231,10 @@ int copy_tree_at_full( struct stat st; int r; - assert(from); assert(to); assert(!FLAGS_SET(copy_flags, COPY_LOCK_BSD)); - if (fstatat(fdf, from, &st, AT_SYMLINK_NOFOLLOW) < 0) + if (fstatat(fdf, strempty(from), &st, AT_SYMLINK_NOFOLLOW | (isempty(from) ? AT_EMPTY_PATH : 0)) < 0) return -errno; r = fd_copy_tree_generic(fdf, from, &st, fdt, to, st.st_dev, COPY_DEPTH_MAX, override_uid, @@ -1305,13 +1337,12 @@ int copy_file_fd_at_full( int r; assert(dir_fdf >= 0 || dir_fdf == AT_FDCWD); - assert(from); assert(fdt >= 0); assert(!FLAGS_SET(copy_flags, COPY_LOCK_BSD)); - fdf = openat(dir_fdf, from, O_RDONLY|O_CLOEXEC|O_NOCTTY); + fdf = xopenat(dir_fdf, from, O_RDONLY|O_CLOEXEC|O_NOCTTY); if (fdf < 0) - return -errno; + return fdf; r = fd_verify_regular(fdf); if (r < 0) @@ -1332,6 +1363,12 @@ int copy_file_fd_at_full( (void) copy_xattr(fdf, NULL, fdt, NULL, copy_flags); } + if (FLAGS_SET(copy_flags, COPY_VERIFY_LINKED)) { + r = fd_verify_linked(fdf); + if (r < 0) + return r; + } + if (copy_flags & COPY_FSYNC_FULL) { r = fsync_full(fdt); if (r < 0) @@ -1363,12 +1400,11 @@ int copy_file_at_full( assert(dir_fdf >= 0 || dir_fdf == AT_FDCWD); assert(dir_fdt >= 0 || dir_fdt == AT_FDCWD); - assert(from); assert(to); - fdf = openat(dir_fdf, from, O_RDONLY|O_CLOEXEC|O_NOCTTY); + fdf = xopenat(dir_fdf, from, O_RDONLY|O_CLOEXEC|O_NOCTTY); if (fdf < 0) - return -errno; + return fdf; if (fstat(fdf, &st) < 0) return -errno; @@ -1378,11 +1414,11 @@ int copy_file_at_full( return r; WITH_UMASK(0000) { - fdt = xopenat_lock(dir_fdt, to, - flags|O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY, - (copy_flags & COPY_MAC_CREATE ? XO_LABEL : 0), - mode != MODE_INVALID ? mode : st.st_mode, - copy_flags & COPY_LOCK_BSD ? LOCK_BSD : LOCK_NONE, LOCK_EX); + fdt = xopenat_lock_full(dir_fdt, to, + flags|O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY, + (copy_flags & COPY_MAC_CREATE ? XO_LABEL : 0), + mode != MODE_INVALID ? mode : st.st_mode, + copy_flags & COPY_LOCK_BSD ? LOCK_BSD : LOCK_NONE, LOCK_EX); if (fdt < 0) return fdt; } @@ -1403,6 +1439,12 @@ int copy_file_at_full( (void) copy_times(fdf, fdt, copy_flags); (void) copy_xattr(fdf, NULL, fdt, NULL, copy_flags); + if (FLAGS_SET(copy_flags, COPY_VERIFY_LINKED)) { + r = fd_verify_linked(fdf); + if (r < 0) + goto fail; + } + if (chattr_mask != 0) (void) chattr_fd(fdt, chattr_flags, chattr_mask & ~CHATTR_EARLY_FL, NULL); @@ -1451,7 +1493,6 @@ int copy_file_atomic_at_full( _cleanup_close_ int fdt = -EBADF; int r; - assert(from); assert(to); assert(!FLAGS_SET(copy_flags, COPY_LOCK_BSD)); diff --git a/src/shared/copy.h b/src/shared/copy.h index d842edd..b8fb28a 100644 --- a/src/shared/copy.h +++ b/src/shared/copy.h @@ -30,6 +30,7 @@ typedef enum CopyFlags { COPY_GRACEFUL_WARN = 1 << 15, /* Skip copying file types that aren't supported by the target filesystem */ COPY_TRUNCATE = 1 << 16, /* Truncate to current file offset after copying */ COPY_LOCK_BSD = 1 << 17, /* Return a BSD exclusively locked file descriptor referring to the copied image/directory. */ + COPY_VERIFY_LINKED = 1 << 18, /* Check the source file is still linked after copying. */ } CopyFlags; typedef enum DenyType { diff --git a/src/shared/creds-util.c b/src/shared/creds-util.c index 7cc8889..fa8ebe0 100644 --- a/src/shared/creds-util.c +++ b/src/shared/creds-util.c @@ -826,9 +826,9 @@ int encrypt_credential_and_warn( tpm2_pubkey_pcr_mask = 0; _cleanup_(tpm2_context_unrefp) Tpm2Context *tpm2_context = NULL; - r = tpm2_context_new(tpm2_device, &tpm2_context); + r = tpm2_context_new_or_warn(tpm2_device, &tpm2_context); if (r < 0) - return log_error_errno(r, "Failed to create TPM2 context: %m"); + return r; r = tpm2_get_best_pcr_bank(tpm2_context, tpm2_hash_pcr_mask | tpm2_pubkey_pcr_mask, &tpm2_pcr_bank); if (r < 0) diff --git a/src/shared/data-fd-util.h b/src/shared/data-fd-util.h index 4f3d8b8..6d99209 100644 --- a/src/shared/data-fd-util.h +++ b/src/shared/data-fd-util.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#include <inttypes.h> +#include <stddef.h> enum { ACQUIRE_NO_DEV_NULL = 1 << 0, diff --git a/src/shared/dlfcn-util.c b/src/shared/dlfcn-util.c index a321df3..8022f55 100644 --- a/src/shared/dlfcn-util.c +++ b/src/shared/dlfcn-util.c @@ -49,6 +49,8 @@ int dlopen_many_sym_or_warn_sentinel(void **dlp, const char *filename, int log_l 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); diff --git a/src/shared/hibernate-util.c b/src/shared/hibernate-util.c index 0d215e8..c3991cf 100644 --- a/src/shared/hibernate-util.c +++ b/src/shared/hibernate-util.c @@ -23,6 +23,7 @@ #include "log.h" #include "parse-util.h" #include "path-util.h" +#include "proc-cmdline.h" #include "stat-util.h" #include "string-util.h" #include "strv.h" @@ -129,6 +130,13 @@ static int read_resume_config(dev_t *ret_devno, uint64_t *ret_offset) { assert(ret_devno); assert(ret_offset); + r = proc_cmdline_get_key("noresume", /* flags = */ 0, /* ret_value = */ NULL); + if (r < 0) + return log_debug_errno(r, "Failed to check if 'noresume' kernel command line option is set: %m"); + if (r > 0) + return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "'noresume' kernel command line option is set, refusing hibernation device lookup."); + r = read_one_line_file("/sys/power/resume", &devno_str); if (r < 0) return log_debug_errno(r, "Failed to read /sys/power/resume: %m"); diff --git a/src/shared/idn-util.c b/src/shared/idn-util.c index 6f36688..26a9d60 100644 --- a/src/shared/idn-util.c +++ b/src/shared/idn-util.c @@ -50,7 +50,10 @@ int dlopen_idn(void) { if (!dl) return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "libidn support is not installed: %s", dlerror()); - } + log_debug("Loaded 'libidn.so.11' via dlopen()"); + } else + log_debug("Loaded 'libidn.so.12' via dlopen()"); + r = dlsym_many_or_warn( dl, diff --git a/src/shared/install.c b/src/shared/install.c index 0f4dab4..27a421e 100644 --- a/src/shared/install.c +++ b/src/shared/install.c @@ -340,9 +340,12 @@ void install_changes_dump(int r, const char *verb, const InstallChange *changes, assert(verb || r >= 0); for (size_t i = 0; i < n_changes; i++) { - if (changes[i].type < 0) - assert(verb); 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); /* When making changes here, make sure to also change install_error() in dbus-manager.c. */ diff --git a/src/shared/journal-file-util.c b/src/shared/journal-file-util.c index e444a2b..bdceac4 100644 --- a/src/shared/journal-file-util.c +++ b/src/shared/journal-file-util.c @@ -210,11 +210,16 @@ static void journal_file_set_offline_internal(JournalFile *f) { log_debug_errno(r, "Failed to re-enable copy-on-write for %s: %m, rewriting file", f->path); - r = copy_file_atomic_full(FORMAT_PROC_FD_PATH(f->fd), f->path, f->mode, - 0, - FS_NOCOW_FL, - COPY_REPLACE | COPY_FSYNC | COPY_HOLES | COPY_ALL_XATTRS, - NULL, NULL); + /* Here, setting COPY_VERIFY_LINKED flag is crucial. Otherwise, a broken + * journal file may be created, if journal_directory_vacuum() -> + * unlinkat_deallocate() is called in the main thread while this thread is + * copying the file. See issue #24150 and #31222. */ + r = copy_file_atomic_at_full( + f->fd, NULL, AT_FDCWD, f->path, f->mode, + 0, + FS_NOCOW_FL, + COPY_REPLACE | COPY_FSYNC | COPY_HOLES | COPY_ALL_XATTRS | COPY_VERIFY_LINKED, + NULL, NULL); if (r < 0) { log_debug_errno(r, "Failed to rewrite %s: %m", f->path); continue; diff --git a/src/shared/logs-show.c b/src/shared/logs-show.c index a5d0400..0a31be3 100644 --- a/src/shared/logs-show.c +++ b/src/shared/logs-show.c @@ -2088,7 +2088,7 @@ int journal_get_boots(sd_journal *j, BootId **ret_boots, size_t *ret_n_boots) { if (sd_id128_equal(i->id, boot.id)) /* The boot id is already stored, something wrong with the journal files. * Exiting as otherwise this problem would cause an infinite loop. */ - break; + goto finish; if (!GREEDY_REALLOC(boots, n_boots + 1)) return -ENOMEM; @@ -2096,6 +2096,7 @@ int journal_get_boots(sd_journal *j, BootId **ret_boots, size_t *ret_n_boots) { boots[n_boots++] = boot; } + finish: *ret_boots = TAKE_PTR(boots); *ret_n_boots = n_boots; return n_boots > 0; diff --git a/src/shared/loop-util.c b/src/shared/loop-util.c index 5860303..6d55df7 100644 --- a/src/shared/loop-util.c +++ b/src/shared/loop-util.c @@ -702,9 +702,9 @@ int loop_device_make_by_path_at( direct_flags = FLAGS_SET(loop_flags, LO_FLAGS_DIRECT_IO) ? O_DIRECT : 0; rdwr_flags = open_flags >= 0 ? open_flags : O_RDWR; - fd = xopenat(dir_fd, path, basic_flags|direct_flags|rdwr_flags, /* xopen_flags = */ 0, /* mode = */ 0); + fd = xopenat(dir_fd, path, basic_flags|direct_flags|rdwr_flags); if (fd < 0 && direct_flags != 0) /* If we had O_DIRECT on, and things failed with that, let's immediately try again without */ - fd = xopenat(dir_fd, path, basic_flags|rdwr_flags, /* xopen_flags = */ 0, /* mode = */ 0); + fd = xopenat(dir_fd, path, basic_flags|rdwr_flags); else direct = direct_flags != 0; if (fd < 0) { @@ -714,9 +714,9 @@ int loop_device_make_by_path_at( if (open_flags >= 0 || !(ERRNO_IS_PRIVILEGE(r) || r == -EROFS)) return r; - fd = xopenat(dir_fd, path, basic_flags|direct_flags|O_RDONLY, /* xopen_flags = */ 0, /* mode = */ 0); + fd = xopenat(dir_fd, path, basic_flags|direct_flags|O_RDONLY); if (fd < 0 && direct_flags != 0) /* as above */ - fd = xopenat(dir_fd, path, basic_flags|O_RDONLY, /* xopen_flags = */ 0, /* mode = */ 0); + fd = xopenat(dir_fd, path, basic_flags|O_RDONLY); else direct = direct_flags != 0; if (fd < 0) diff --git a/src/shared/open-file.c b/src/shared/open-file.c index 42772bd..7d7a8a9 100644 --- a/src/shared/open-file.c +++ b/src/shared/open-file.c @@ -96,7 +96,7 @@ int open_file_to_string(const OpenFile *of, char **ret) { assert(of); assert(ret); - s = shell_escape(of->path, ":"); + s = xescape(of->path, ":"); if (!s) return -ENOMEM; diff --git a/src/shared/serialize.c b/src/shared/serialize.c index 483cbc7..344b102 100644 --- a/src/shared/serialize.c +++ b/src/shared/serialize.c @@ -180,7 +180,7 @@ int serialize_strv(FILE *f, const char *key, char **l) { } int serialize_pidref(FILE *f, FDSet *fds, const char *key, PidRef *pidref) { - int copy; + int r; assert(f); assert(fds); @@ -188,17 +188,23 @@ int serialize_pidref(FILE *f, FDSet *fds, const char *key, PidRef *pidref) { if (!pidref_is_set(pidref)) return 0; - /* If we have a pidfd we serialize the fd and encode the fd number prefixed by "@" in the - * serialization. Otherwise we serialize the numeric PID as it is. */ + /* We always serialize the pid separately, to keep downgrades mostly working (older versions will + * deserialize the pid and silently fail to deserialize the pidfd). If we also have a pidfd, we + * serialize both the pid and pidfd, so that we can construct the exact same pidref after + * deserialization (this doesn't work with only the pidfd, as we can't retrieve the original pid + * from the pidfd anymore if the process is reaped). */ - if (pidref->fd < 0) - return serialize_item_format(f, key, PID_FMT, pidref->pid); + if (pidref->fd >= 0) { + int copy = fdset_put_dup(fds, pidref->fd); + if (copy < 0) + return log_error_errno(copy, "Failed to add file descriptor to serialization set: %m"); - copy = fdset_put_dup(fds, pidref->fd); - if (copy < 0) - return log_error_errno(copy, "Failed to add file descriptor to serialization set: %m"); + r = serialize_item_format(f, key, "@%i:" PID_FMT, copy, pidref->pid); + if (r < 0) + return r; + } - return serialize_item_format(f, key, "@%i", copy); + return serialize_item_format(f, key, PID_FMT, pidref->pid); } int serialize_ratelimit(FILE *f, const char *key, const RateLimit *rl) { @@ -476,12 +482,39 @@ int deserialize_pidref(FDSet *fds, const char *value, PidRef *ret) { e = startswith(value, "@"); if (e) { - int fd = deserialize_fd(fds, e); + _cleanup_free_ char *fdstr = NULL, *pidstr = NULL; + _cleanup_close_ int fd = -EBADF; + + r = extract_many_words(&e, ":", /* flags = */ 0, &fdstr, &pidstr, NULL); + if (r < 0) + return log_debug_errno(r, "Failed to deserialize pidref '%s': %m", e); + if (r == 0) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot deserialize pidref from empty string."); + + assert(r <= 2); + fd = deserialize_fd(fds, fdstr); if (fd < 0) return fd; - r = pidref_set_pidfd_consume(ret, fd); + /* The serialization format changed after 255.4. In systemd <= 255.4 only pidfd is + * serialized, but that causes problems when reconstructing pidref (see serialize_pidref for + * details). After 255.4 the pid is serialized as well even if we have a pidfd, but we still + * need to support older format as we might be upgrading from a version that still uses the + * old format. */ + if (pidstr) { + pid_t pid; + + r = parse_pid(pidstr, &pid); + if (r < 0) + return log_debug_errno(r, "Failed to parse PID: %s", pidstr); + + *ret = (PidRef) { + .pid = pid, + .fd = TAKE_FD(fd), + }; + } else + r = pidref_set_pidfd_consume(ret, TAKE_FD(fd)); } else { pid_t pid; diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index 30b4f57..c7e0b24 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -664,7 +664,9 @@ int tpm2_context_new(const char *device, Tpm2Context **ret_context) { context->tcti_dl = dlopen(fn, RTLD_NOW); if (!context->tcti_dl) - return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to load %s: %s", fn, dlerror()); + return log_debug_errno(SYNTHETIC_ERRNO(ENOPKG), "Failed to load %s: %s", fn, dlerror()); + + log_debug("Loaded '%s' via dlopen()", fn); func = dlsym(context->tcti_dl, TSS2_TCTI_INFO_SYMBOL); if (!func) @@ -678,7 +680,7 @@ int tpm2_context_new(const char *device, Tpm2Context **ret_context) { log_debug("Loaded TCTI module '%s' (%s) [Version %" PRIu32 "]", info->name, info->description, info->version); - rc = info->init(NULL, &sz, NULL); + rc = info->init(/* context= */ NULL, &sz, /* param= */ NULL); if (rc != TPM2_RC_SUCCESS) return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to initialize TCTI context: %s", sym_Tss2_RC_Decode(rc)); @@ -713,19 +715,37 @@ int tpm2_context_new(const char *device, Tpm2Context **ret_context) { /* We require AES and CFB support for session encryption. */ if (!tpm2_supports_alg(context, TPM2_ALG_AES)) - return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM does not support AES."); + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM does not support AES."); if (!tpm2_supports_alg(context, TPM2_ALG_CFB)) - return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM does not support CFB."); + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM does not support CFB."); if (!tpm2_supports_tpmt_sym_def(context, &SESSION_TEMPLATE_SYM_AES_128_CFB)) - return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM does not support AES-128-CFB."); + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM does not support AES-128-CFB."); *ret_context = TAKE_PTR(context); return 0; } +int tpm2_context_new_or_warn(const char *device, Tpm2Context **ret_context) { + int r; + + assert(ret_context); + + r = tpm2_context_new(device, ret_context); + if (r == -EOPNOTSUPP) + return log_error_errno(r, "TPM device not usable as it does not support the required functionality (AES-128-CFB missing?)."); + if (r == -ENOPKG) + return log_error_errno(r, "TPM TCTI driver not available."); + if (r == -ENOENT) + return log_error_errno(r, "TPM device not found."); + if (r < 0) + return log_error_errno(r, "Failed to create TPM2 context: %m"); + + return 0; +} + static void tpm2_handle_cleanup(ESYS_CONTEXT *esys_context, ESYS_TR esys_handle, bool flush) { TSS2_RC rc; @@ -5540,13 +5560,13 @@ int tpm2_unseal(Tpm2Context *c, if (r < 0) return r; - _cleanup_(tpm2_handle_freep) Tpm2Handle *encryption_session = NULL; - r = tpm2_make_encryption_session(c, primary_handle, hmac_key, &encryption_session); - if (r < 0) - return r; - _cleanup_(Esys_Freep) TPM2B_SENSITIVE_DATA* unsealed = NULL; for (unsigned i = RETRY_UNSEAL_MAX;; i--) { + _cleanup_(tpm2_handle_freep) Tpm2Handle *encryption_session = NULL; + r = tpm2_make_encryption_session(c, primary_handle, hmac_key, &encryption_session); + if (r < 0) + return r; + _cleanup_(tpm2_handle_freep) Tpm2Handle *policy_session = NULL; _cleanup_(Esys_Freep) TPM2B_DIGEST *policy_digest = NULL; r = tpm2_make_policy_session( diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h index 55d7481..911a3c7 100644 --- a/src/shared/tpm2-util.h +++ b/src/shared/tpm2-util.h @@ -72,6 +72,7 @@ typedef struct { } Tpm2Context; int tpm2_context_new(const char *device, Tpm2Context **ret_context); +int tpm2_context_new_or_warn(const char *device, Tpm2Context **ret_context); Tpm2Context *tpm2_context_ref(Tpm2Context *context); Tpm2Context *tpm2_context_unref(Tpm2Context *context); DEFINE_TRIVIAL_CLEANUP_FUNC(Tpm2Context*, tpm2_context_unref); diff --git a/src/shared/verbs.c b/src/shared/verbs.c index a010952..a38591d 100644 --- a/src/shared/verbs.c +++ b/src/shared/verbs.c @@ -13,22 +13,21 @@ #include "verbs.h" #include "virt.h" -/* Wraps running_in_chroot() which is used in various places, but also adds an environment variable check so external - * processes can reliably force this on. - */ +/* Wraps running_in_chroot() which is used in various places, but also adds an environment variable check + * so external processes can reliably force this on. */ bool running_in_chroot_or_offline(void) { int r; - /* Added to support use cases like rpm-ostree, where from %post scripts we only want to execute "preset", but - * not "start"/"restart" for example. + /* Added to support use cases like rpm-ostree, where from %post scripts we only want to execute "preset", + * but not "start"/"restart" for example. * * See docs/ENVIRONMENT.md for docs. */ r = getenv_bool("SYSTEMD_OFFLINE"); - if (r < 0 && r != -ENXIO) - log_debug_errno(r, "Failed to parse $SYSTEMD_OFFLINE: %m"); - else if (r >= 0) + if (r >= 0) return r > 0; + if (r != -ENXIO) + log_debug_errno(r, "Failed to parse $SYSTEMD_OFFLINE, ignoring: %m"); /* We've had this condition check for a long time which basically checks for legacy chroot case like Fedora's * "mock", which is used for package builds. We don't want to try to start systemd services there, since @@ -40,8 +39,7 @@ bool running_in_chroot_or_offline(void) { */ r = running_in_chroot(); if (r < 0) - log_debug_errno(r, "running_in_chroot(): %m"); - + log_debug_errno(r, "Failed to check if we're running in chroot, assuming not: %m"); return r > 0; } @@ -145,6 +143,17 @@ int dispatch_verb(int argc, char *argv[], const Verb verbs[], void *userdata) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown command verb '%s'.", name); } + _cleanup_free_ char *verb_list = NULL; + size_t i; + + for (i = 0; verbs[i].dispatch; i++) + if (!strextend_with_separator(&verb_list, ", ", verbs[i].verb)) + return log_oom(); + + if (i > 2) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Command verb required (one of %s).", verb_list); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Command verb required."); } diff --git a/src/shared/watchdog.c b/src/shared/watchdog.c index 2d79f71..99ccefb 100644 --- a/src/shared/watchdog.c +++ b/src/shared/watchdog.c @@ -95,7 +95,7 @@ static int set_pretimeout_governor(const char *governor) { governor, WRITE_STRING_FILE_DISABLE_BUFFER | WRITE_STRING_FILE_VERIFY_ON_FAILURE | WRITE_STRING_FILE_VERIFY_IGNORE_NEWLINE); if (r < 0) - return log_error_errno(r, "Failed to set pretimeout_governor to '%s': %m", governor); + return log_error_errno(r, "Failed to set watchdog pretimeout_governor to '%s': %m", governor); return r; } @@ -157,7 +157,7 @@ static int watchdog_read_pretimeout(void) { if (ioctl(watchdog_fd, WDIOC_GETPRETIMEOUT, &sec) < 0) { watchdog_pretimeout = 0; - return log_full_errno(ERRNO_IS_NOT_SUPPORTED(errno) ? LOG_DEBUG : LOG_WARNING, errno, "Failed to get pretimeout value, ignoring: %m"); + return log_full_errno(ERRNO_IS_NOT_SUPPORTED(errno) ? LOG_DEBUG : LOG_WARNING, errno, "Failed to get watchdog pretimeout value, ignoring: %m"); } watchdog_pretimeout = sec * USEC_PER_SEC; @@ -181,7 +181,7 @@ static int watchdog_set_pretimeout(void) { return 0; } - return log_error_errno(errno, "Failed to set pretimeout to %s: %m", FORMAT_TIMESPAN(sec, USEC_PER_SEC)); + return log_error_errno(errno, "Failed to set watchdog pretimeout to %s: %m", FORMAT_TIMESPAN(sec, USEC_PER_SEC)); } /* The set ioctl does not return the actual value set so get it now. */ @@ -274,10 +274,10 @@ static int update_timeout(void) { r = watchdog_set_timeout(); if (r < 0) { if (!ERRNO_IS_NOT_SUPPORTED(r)) - return log_error_errno(r, "Failed to set timeout to %s: %m", + return log_error_errno(r, "Failed to set watchdog hardware timeout to %s: %m", FORMAT_TIMESPAN(watchdog_timeout, 0)); - log_info("Modifying watchdog timeout is not supported, reusing the programmed timeout."); + log_info("Modifying watchdog hardware timeout is not supported, reusing the programmed timeout."); watchdog_timeout = USEC_INFINITY; } } @@ -286,8 +286,8 @@ static int update_timeout(void) { r = watchdog_read_timeout(); if (r < 0) { if (!ERRNO_IS_NOT_SUPPORTED(r)) - return log_error_errno(r, "Failed to query watchdog HW timeout: %m"); - log_info("Reading watchdog timeout is not supported, reusing the configured timeout."); + return log_error_errno(r, "Failed to query watchdog hardware timeout: %m"); + log_info("Reading watchdog hardware timeout is not supported, reusing the configured timeout."); watchdog_timeout = previous_timeout; } } @@ -302,7 +302,7 @@ static int update_timeout(void) { if (r < 0) return r; - log_info("Watchdog running with a timeout of %s.", FORMAT_TIMESPAN(watchdog_timeout, 0)); + log_info("Watchdog running with a hardware timeout of %s.", FORMAT_TIMESPAN(watchdog_timeout, 0)); return watchdog_ping_now(); } diff --git a/src/systemctl/systemctl-logind.c b/src/systemctl/systemctl-logind.c index 268e528..7f97325 100644 --- a/src/systemctl/systemctl-logind.c +++ b/src/systemctl/systemctl-logind.c @@ -392,7 +392,7 @@ int logind_show_shutdown(void) { return r; if (isempty(action)) - return log_error_errno(SYNTHETIC_ERRNO(ENODATA), "No scheduled shutdown."); + return log_full_errno(arg_quiet ? LOG_DEBUG : LOG_ERR, SYNTHETIC_ERRNO(ENODATA), "No scheduled shutdown."); if (STR_IN_SET(action, "halt", "poweroff", "exit")) pretty_action = "Shutdown"; diff --git a/src/systemctl/systemctl-show.c b/src/systemctl/systemctl-show.c index e7fabcf..5d1eb49 100644 --- a/src/systemctl/systemctl-show.c +++ b/src/systemctl/systemctl-show.c @@ -2255,7 +2255,7 @@ static int get_unit_dbus_path_by_pid( * sends the numeric PID. */ pidfd = pidfd_open(pid, 0); - if (pidfd < 0 && ERRNO_IS_NOT_SUPPORTED(errno) && !ERRNO_IS_PRIVILEGE(errno)) + if (pidfd < 0 && (ERRNO_IS_NOT_SUPPORTED(errno) || ERRNO_IS_PRIVILEGE(errno))) return get_unit_dbus_path_by_pid_fallback(bus, pid, ret_path, ret_unit); if (pidfd < 0) return log_error_errno(errno, "Failed to open PID %"PRIu32": %m", pid); diff --git a/src/systemd/sd-bus-vtable.h b/src/systemd/sd-bus-vtable.h index 5e80ea8..d06c5c3 100644 --- a/src/systemd/sd-bus-vtable.h +++ b/src/systemd/sd-bus-vtable.h @@ -208,6 +208,7 @@ struct sd_bus_vtable { _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, \ NAME, ...) NAME +#define _SD_VARARGS_FOREACH_EVEN_00(FN) #define _SD_VARARGS_FOREACH_EVEN_01(FN, X) FN(X) #define _SD_VARARGS_FOREACH_EVEN_02(FN, X, Y) FN(X) #define _SD_VARARGS_FOREACH_EVEN_04(FN, X, Y, ...) FN(X) _SD_VARARGS_FOREACH_EVEN_02(FN, __VA_ARGS__) @@ -261,9 +262,11 @@ struct sd_bus_vtable { _SD_VARARGS_FOREACH_EVEN_08, _SD_VARARGS_FOREACH_EVEN_07, \ _SD_VARARGS_FOREACH_EVEN_06, _SD_VARARGS_FOREACH_EVEN_05, \ _SD_VARARGS_FOREACH_EVEN_04, _SD_VARARGS_FOREACH_EVEN_03, \ - _SD_VARARGS_FOREACH_EVEN_02, _SD_VARARGS_FOREACH_EVEN_01) \ + _SD_VARARGS_FOREACH_EVEN_02, _SD_VARARGS_FOREACH_EVEN_01, \ + _SD_VARARGS_FOREACH_EVEN_00) \ (FN, __VA_ARGS__) +#define _SD_VARARGS_FOREACH_ODD_00(FN) #define _SD_VARARGS_FOREACH_ODD_01(FN, X) #define _SD_VARARGS_FOREACH_ODD_02(FN, X, Y) FN(Y) #define _SD_VARARGS_FOREACH_ODD_04(FN, X, Y, ...) FN(Y) _SD_VARARGS_FOREACH_ODD_02(FN, __VA_ARGS__) @@ -317,7 +320,8 @@ struct sd_bus_vtable { _SD_VARARGS_FOREACH_ODD_08, _SD_VARARGS_FOREACH_ODD_07, \ _SD_VARARGS_FOREACH_ODD_06, _SD_VARARGS_FOREACH_ODD_05, \ _SD_VARARGS_FOREACH_ODD_04, _SD_VARARGS_FOREACH_ODD_03, \ - _SD_VARARGS_FOREACH_ODD_02, _SD_VARARGS_FOREACH_ODD_01) \ + _SD_VARARGS_FOREACH_ODD_02, _SD_VARARGS_FOREACH_ODD_01, \ + _SD_VARARGS_FOREACH_ODD_00) \ (FN, __VA_ARGS__) #define SD_BUS_ARGS(...) __VA_ARGS__ diff --git a/src/test/test-btrfs.c b/src/test/test-btrfs.c index 205142e..6dff709 100644 --- a/src/test/test-btrfs.c +++ b/src/test/test-btrfs.c @@ -71,7 +71,7 @@ int main(int argc, char *argv[]) { if (r < 0) log_error_errno(r, "Failed to make snapshot: %m"); if (r >= 0) - assert_se(xopenat_lock(AT_FDCWD, "/xxxtest4", 0, 0, 0, LOCK_BSD, LOCK_EX|LOCK_NB) == -EAGAIN); + assert_se(xopenat_lock(AT_FDCWD, "/xxxtest4", 0, LOCK_BSD, LOCK_EX|LOCK_NB) == -EAGAIN); safe_close(r); diff --git a/src/test/test-copy.c b/src/test/test-copy.c index f3144f0..9674e78 100644 --- a/src/test/test-copy.c +++ b/src/test/test-copy.c @@ -520,13 +520,35 @@ TEST(copy_lock) { assert_se((fd = copy_directory_at(tfd, "abc", tfd, "qed", COPY_LOCK_BSD)) >= 0); assert_se(faccessat(tfd, "qed", F_OK, 0) >= 0); assert_se(faccessat(tfd, "qed/def", F_OK, 0) >= 0); - assert_se(xopenat_lock(tfd, "qed", 0, 0, 0, LOCK_BSD, LOCK_EX|LOCK_NB) == -EAGAIN); + assert_se(xopenat_lock(tfd, "qed", 0, LOCK_BSD, LOCK_EX|LOCK_NB) == -EAGAIN); fd = safe_close(fd); assert_se((fd = copy_file_at(tfd, "abc/def", tfd, "poi", 0, 0644, COPY_LOCK_BSD))); assert_se(read_file_at_and_streq(tfd, "poi", "abc\n")); - assert_se(xopenat_lock(tfd, "poi", 0, 0, 0, LOCK_BSD, LOCK_EX|LOCK_NB) == -EAGAIN); + assert_se(xopenat_lock(tfd, "poi", 0, LOCK_BSD, LOCK_EX|LOCK_NB) == -EAGAIN); fd = safe_close(fd); } +TEST(copy_verify_linked) { + _cleanup_(rm_rf_physical_and_freep) char *t = NULL; + _cleanup_close_ int tfd = -EBADF, fd_1 = -EBADF, fd_2 = -EBADF; + + tfd = mkdtemp_open(NULL, O_PATH, &t); + assert_se(tfd >= 0); + + assert_se(write_string_file_at(tfd, "hoge", "bar bar", WRITE_STRING_FILE_CREATE) >= 0); + + fd_1 = openat(tfd, "hoge", O_CLOEXEC | O_NOCTTY | O_RDONLY); + assert_se(fd_1 >= 0); + fd_2 = openat(tfd, "hoge", O_CLOEXEC | O_NOCTTY | O_RDONLY); + assert_se(fd_2 >= 0); + assert_se(unlinkat(tfd, "hoge", 0) >= 0); + + assert_se(copy_file_at(fd_1, NULL, tfd, "to_1", 0, 0644, 0) >= 0); + assert_se(read_file_at_and_streq(tfd, "to_1", "bar bar\n")); + + assert_se(copy_file_at(fd_2, NULL, tfd, "to_2", O_EXCL, 0644, COPY_VERIFY_LINKED) == -EIDRM); + assert_se(faccessat(tfd, "to_2", F_OK, AT_SYMLINK_NOFOLLOW) < 0 && errno == ENOENT); +} + DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-fs-util.c b/src/test/test-fs-util.c index ef335b4..b32feff 100644 --- a/src/test/test-fs-util.c +++ b/src/test/test-fs-util.c @@ -673,37 +673,37 @@ TEST(openat_report_new) { assert_se(b); } -TEST(xopenat) { +TEST(xopenat_full) { _cleanup_(rm_rf_physical_and_freep) char *t = NULL; _cleanup_close_ int tfd = -EBADF, fd = -EBADF, fd2 = -EBADF; assert_se((tfd = mkdtemp_open(NULL, 0, &t)) >= 0); - /* Test that xopenat() creates directories if O_DIRECTORY is specified. */ + /* Test that xopenat_full() creates directories if O_DIRECTORY is specified. */ - assert_se((fd = xopenat(tfd, "abc", O_DIRECTORY|O_CREAT|O_EXCL|O_CLOEXEC, 0, 0755)) >= 0); + assert_se((fd = xopenat_full(tfd, "abc", O_DIRECTORY|O_CREAT|O_EXCL|O_CLOEXEC, 0, 0755)) >= 0); assert_se((fd_verify_directory(fd) >= 0)); fd = safe_close(fd); - assert_se(xopenat(tfd, "abc", O_DIRECTORY|O_CREAT|O_EXCL|O_CLOEXEC, 0, 0755) == -EEXIST); + assert_se(xopenat_full(tfd, "abc", O_DIRECTORY|O_CREAT|O_EXCL|O_CLOEXEC, 0, 0755) == -EEXIST); - assert_se((fd = xopenat(tfd, "abc", O_DIRECTORY|O_CREAT|O_CLOEXEC, 0, 0755)) >= 0); + assert_se((fd = xopenat_full(tfd, "abc", O_DIRECTORY|O_CREAT|O_CLOEXEC, 0, 0755)) >= 0); assert_se((fd_verify_directory(fd) >= 0)); fd = safe_close(fd); - /* Test that xopenat() creates regular files if O_DIRECTORY is not specified. */ + /* Test that xopenat_full() creates regular files if O_DIRECTORY is not specified. */ - assert_se((fd = xopenat(tfd, "def", O_CREAT|O_EXCL|O_CLOEXEC, 0, 0644)) >= 0); + assert_se((fd = xopenat_full(tfd, "def", O_CREAT|O_EXCL|O_CLOEXEC, 0, 0644)) >= 0); assert_se(fd_verify_regular(fd) >= 0); fd = safe_close(fd); - /* Test that we can reopen an existing fd with xopenat() by specifying an empty path. */ + /* Test that we can reopen an existing fd with xopenat_full() by specifying an empty path. */ - assert_se((fd = xopenat(tfd, "def", O_PATH|O_CLOEXEC, 0, 0)) >= 0); - assert_se((fd2 = xopenat(fd, "", O_RDWR|O_CLOEXEC, 0, 0644)) >= 0); + assert_se((fd = xopenat_full(tfd, "def", O_PATH|O_CLOEXEC, 0, 0)) >= 0); + assert_se((fd2 = xopenat_full(fd, "", O_RDWR|O_CLOEXEC, 0, 0644)) >= 0); } -TEST(xopenat_lock) { +TEST(xopenat_lock_full) { _cleanup_(rm_rf_physical_and_freep) char *t = NULL; _cleanup_close_ int tfd = -EBADF, fd = -EBADF; siginfo_t si; @@ -714,11 +714,11 @@ TEST(xopenat_lock) { * and close the file descriptor and still properly create the directory and acquire the lock in * another process. */ - fd = xopenat_lock(tfd, "abc", O_CREAT|O_DIRECTORY|O_CLOEXEC, 0, 0755, LOCK_BSD, LOCK_EX); + fd = xopenat_lock_full(tfd, "abc", O_CREAT|O_DIRECTORY|O_CLOEXEC, 0, 0755, LOCK_BSD, LOCK_EX); assert_se(fd >= 0); assert_se(faccessat(tfd, "abc", F_OK, 0) >= 0); assert_se(fd_verify_directory(fd) >= 0); - assert_se(xopenat_lock(tfd, "abc", O_DIRECTORY|O_CLOEXEC, 0, 0755, LOCK_BSD, LOCK_EX|LOCK_NB) == -EAGAIN); + assert_se(xopenat_lock_full(tfd, "abc", O_DIRECTORY|O_CLOEXEC, 0, 0755, LOCK_BSD, LOCK_EX|LOCK_NB) == -EAGAIN); pid_t pid = fork(); assert_se(pid >= 0); @@ -726,21 +726,21 @@ TEST(xopenat_lock) { if (pid == 0) { safe_close(fd); - fd = xopenat_lock(tfd, "abc", O_CREAT|O_DIRECTORY|O_CLOEXEC, 0, 0755, LOCK_BSD, LOCK_EX); + fd = xopenat_lock_full(tfd, "abc", O_CREAT|O_DIRECTORY|O_CLOEXEC, 0, 0755, LOCK_BSD, LOCK_EX); assert_se(fd >= 0); assert_se(faccessat(tfd, "abc", F_OK, 0) >= 0); assert_se(fd_verify_directory(fd) >= 0); - assert_se(xopenat_lock(tfd, "abc", O_DIRECTORY|O_CLOEXEC, 0, 0755, LOCK_BSD, LOCK_EX|LOCK_NB) == -EAGAIN); + assert_se(xopenat_lock_full(tfd, "abc", O_DIRECTORY|O_CLOEXEC, 0, 0755, LOCK_BSD, LOCK_EX|LOCK_NB) == -EAGAIN); _exit(EXIT_SUCCESS); } - /* We need to give the child process some time to get past the xopenat() call in xopenat_lock() and - * block in the call to lock_generic() waiting for the lock to become free. We can't modify - * xopenat_lock() to signal an eventfd to let us know when that has happened, so we just sleep for a - * little and assume that's enough time for the child process to get along far enough. It doesn't - * matter if it doesn't get far enough, in that case we just won't trigger the fallback logic in - * xopenat_lock(), but the test will still succeed. */ + /* We need to give the child process some time to get past the xopenat() call in xopenat_lock_full() + * and block in the call to lock_generic() waiting for the lock to become free. We can't modify + * xopenat_lock_full() to signal an eventfd to let us know when that has happened, so we just sleep + * for a little and assume that's enough time for the child process to get along far enough. It + * doesn't matter if it doesn't get far enough, in that case we just won't trigger the fallback logic + * in xopenat_lock_full(), but the test will still succeed. */ assert_se(usleep_safe(20 * USEC_PER_MSEC) >= 0); assert_se(unlinkat(tfd, "abc", AT_REMOVEDIR) >= 0); @@ -749,8 +749,8 @@ TEST(xopenat_lock) { assert_se(wait_for_terminate(pid, &si) >= 0); assert_se(si.si_code == CLD_EXITED); - assert_se(xopenat_lock(tfd, "abc", 0, 0, 0755, LOCK_POSIX, LOCK_EX) == -EBADF); - assert_se(xopenat_lock(tfd, "def", O_DIRECTORY, 0, 0755, LOCK_POSIX, LOCK_EX) == -EBADF); + assert_se(xopenat_lock_full(tfd, "abc", 0, 0, 0755, LOCK_POSIX, LOCK_EX) == -EBADF); + assert_se(xopenat_lock_full(tfd, "def", O_DIRECTORY, 0, 0755, LOCK_POSIX, LOCK_EX) == -EBADF); } static int intro(void) { diff --git a/src/test/test-open-file.c b/src/test/test-open-file.c index 1b938ec..4314d0d 100644 --- a/src/test/test-open-file.c +++ b/src/test/test-open-file.c @@ -172,14 +172,12 @@ TEST(open_file_to_string) { assert_se(streq(s, "/proc/1/ns/mnt::read-only")); s = mfree(s); - assert_se(free_and_strdup(&of->path, "/path:with:colon")); - assert_se(free_and_strdup(&of->fdname, "path:with:colon")); + assert_se(free_and_strdup(&of->path, "/path:with:colon") >= 0); + assert_se(free_and_strdup(&of->fdname, "path:with:colon") >= 0); of->flags = 0; - r = open_file_to_string(of, &s); - - assert_se(r >= 0); - assert_se(streq(s, "/path\\:with\\:colon")); + assert_se(open_file_to_string(of, &s) >= 0); + assert_se(streq(s, "/path\\x3awith\\x3acolon")); } DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-stat-util.c b/src/test/test-stat-util.c index 5aca207..8d7fd5b 100644 --- a/src/test/test-stat-util.c +++ b/src/test/test-stat-util.c @@ -180,6 +180,25 @@ TEST(dir_is_empty) { assert_se(dir_is_empty_at(AT_FDCWD, empty_dir, /* ignore_hidden_or_backup= */ false) > 0); } +TEST(fd_verify_linked) { + _cleanup_(rm_rf_physical_and_freep) char *t = NULL; + _cleanup_close_ int tfd = -EBADF, fd = -EBADF; + _cleanup_free_ char *p = NULL; + + tfd = mkdtemp_open(NULL, O_PATH, &t); + assert_se(tfd >= 0); + + assert_se(p = path_join(t, "hoge")); + assert_se(touch(p) >= 0); + + fd = open(p, O_CLOEXEC | O_PATH); + assert_se(fd >= 0); + + assert_se(fd_verify_linked(fd) >= 0); + assert_se(unlinkat(tfd, "hoge", 0) >= 0); + assert_se(fd_verify_linked(fd) == -EIDRM); +} + static int intro(void) { log_show_color(true); return EXIT_SUCCESS; diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index bc83aab..4919cb7 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -817,7 +817,7 @@ static int dir_cleanup( cutoff_nsec, sub_path, age_by_file, false)) continue; - fd = xopenat(dirfd(d), + fd = xopenat_full(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME|O_NONBLOCK, /* xopen_flags = */ 0, @@ -1173,60 +1173,47 @@ static int parse_acls_from_arg(Item *item) { #if HAVE_ACL static int parse_acl_cond_exec( const char *path, - acl_t access, /* could be empty (NULL) */ - acl_t cond_exec, const struct stat *st, + acl_t cond_exec, + acl_t access, /* could be empty (NULL) */ bool append, acl_t *ret) { - _cleanup_(acl_freep) acl_t parsed = NULL; acl_entry_t entry; acl_permset_t permset; bool has_exec; int r; assert(path); - assert(ret); assert(st); + assert(cond_exec); + assert(ret); - parsed = access ? acl_dup(access) : acl_init(0); - if (!parsed) - return -errno; - - /* Since we substitute 'X' with 'x' in parse_acl(), we just need to copy the entries over - * for directories */ - if (S_ISDIR(st->st_mode)) { - for (r = acl_get_entry(cond_exec, ACL_FIRST_ENTRY, &entry); - r > 0; - r = acl_get_entry(cond_exec, ACL_NEXT_ENTRY, &entry)) { - - acl_entry_t parsed_entry; - - if (acl_create_entry(&parsed, &parsed_entry) < 0) - return -errno; - - if (acl_copy_entry(parsed_entry, entry) < 0) - return -errno; - } - if (r < 0) - return -errno; - - goto finish; - } - - has_exec = st->st_mode & S_IXUSR; - - if (!has_exec && append) { + if (!S_ISDIR(st->st_mode)) { _cleanup_(acl_freep) acl_t old = NULL; old = acl_get_file(path, ACL_TYPE_ACCESS); if (!old) return -errno; + has_exec = false; + for (r = acl_get_entry(old, ACL_FIRST_ENTRY, &entry); r > 0; r = acl_get_entry(old, ACL_NEXT_ENTRY, &entry)) { + acl_tag_t tag; + + if (acl_get_tag_type(entry, &tag) < 0) + return -errno; + + if (tag == ACL_MASK) + continue; + + /* If not appending, skip ACL definitions */ + if (!append && IN_SET(tag, ACL_USER, ACL_GROUP)) + continue; + if (acl_get_permset(entry, &permset) < 0) return -errno; @@ -1240,28 +1227,33 @@ static int parse_acl_cond_exec( } if (r < 0) return -errno; - } - /* Check if we're about to set the execute bit in acl_access */ - if (!has_exec && access) { - for (r = acl_get_entry(access, ACL_FIRST_ENTRY, &entry); - r > 0; - r = acl_get_entry(access, ACL_NEXT_ENTRY, &entry)) { + /* Check if we're about to set the execute bit in acl_access */ + if (!has_exec && access) { + for (r = acl_get_entry(access, ACL_FIRST_ENTRY, &entry); + r > 0; + r = acl_get_entry(access, ACL_NEXT_ENTRY, &entry)) { - if (acl_get_permset(entry, &permset) < 0) - return -errno; + if (acl_get_permset(entry, &permset) < 0) + return -errno; - r = acl_get_perm(permset, ACL_EXECUTE); + r = acl_get_perm(permset, ACL_EXECUTE); + if (r < 0) + return -errno; + if (r > 0) { + has_exec = true; + break; + } + } if (r < 0) return -errno; - if (r > 0) { - has_exec = true; - break; - } } - if (r < 0) - return -errno; - } + } else + has_exec = true; + + _cleanup_(acl_freep) acl_t parsed = access ? acl_dup(access) : acl_init(0); + if (!parsed) + return -errno; for (r = acl_get_entry(cond_exec, ACL_FIRST_ENTRY, &entry); r > 0; @@ -1275,6 +1267,7 @@ static int parse_acl_cond_exec( if (acl_copy_entry(parsed_entry, entry) < 0) return -errno; + /* We substituted 'X' with 'x' in parse_acl(), so drop execute bit here if not applicable. */ if (!has_exec) { if (acl_get_permset(parsed_entry, &permset) < 0) return -errno; @@ -1286,7 +1279,6 @@ static int parse_acl_cond_exec( if (r < 0) return -errno; -finish: if (!append) { /* want_mask = true */ r = calc_acl_mask_if_needed(&parsed); if (r < 0) @@ -1390,10 +1382,9 @@ static int fd_set_acls( } if (item->acl_access_exec) { - r = parse_acl_cond_exec(FORMAT_PROC_FD_PATH(fd), - item->acl_access, + r = parse_acl_cond_exec(FORMAT_PROC_FD_PATH(fd), st, item->acl_access_exec, - st, + item->acl_access, item->append_or_force, &access_with_exec_parsed); if (r < 0) diff --git a/src/tpm2-setup/tpm2-setup.c b/src/tpm2-setup/tpm2-setup.c index 0be7ffc..35628fc 100644 --- a/src/tpm2-setup/tpm2-setup.c +++ b/src/tpm2-setup/tpm2-setup.c @@ -18,6 +18,7 @@ static char *arg_tpm2_device = NULL; static bool arg_early = false; +static bool arg_graceful = false; STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep); @@ -43,6 +44,7 @@ static int help(int argc, char *argv[], void *userdata) { " --tpm2-device=PATH\n" " Pick TPM2 device\n" " --early=BOOL Store SRK public key in /run/ rather than /var/lib/\n" + " --graceful Exit gracefully if no TPM2 device is found\n" "\nSee the %2$s for details.\n", program_invocation_short_name, link, @@ -59,6 +61,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_VERSION = 0x100, ARG_TPM2_DEVICE, ARG_EARLY, + ARG_GRACEFUL, }; static const struct option options[] = { @@ -66,6 +69,7 @@ static int parse_argv(int argc, char *argv[]) { { "version", no_argument, NULL, ARG_VERSION }, { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE }, { "early", required_argument, NULL, ARG_EARLY }, + { "graceful", no_argument, NULL, ARG_GRACEFUL }, {} }; @@ -100,6 +104,10 @@ static int parse_argv(int argc, char *argv[]) { arg_early = r; break; + case ARG_GRACEFUL: + arg_graceful = true; + break; + case '?': return -EINVAL; @@ -204,9 +212,9 @@ static int load_public_key_tpm2(struct public_key_data *ret) { assert(ret); - r = tpm2_context_new(arg_tpm2_device, &c); + r = tpm2_context_new_or_warn(arg_tpm2_device, &c); if (r < 0) - return log_error_errno(r, "Failed to create TPM2 context: %m"); + return r; r = tpm2_get_or_create_srk( c, @@ -247,6 +255,11 @@ static int run(int argc, char *argv[]) { if (r <= 0) return r; + if (arg_graceful && tpm2_support() != TPM2_SUPPORT_FULL) { + log_notice("No complete TPM2 support detected, exiting gracefully."); + return EXIT_SUCCESS; + } + umask(0022); _cleanup_(public_key_data_done) struct public_key_data runtime_key = {}, persistent_key = {}, tpm2_key = {}; diff --git a/src/ukify/ukify.py b/src/ukify/ukify.py index 6abf1b6..b0d0961 100755 --- a/src/ukify/ukify.py +++ b/src/ukify/ukify.py @@ -40,6 +40,7 @@ import subprocess import sys import tempfile import textwrap +import struct from hashlib import sha256 from typing import (Any, Callable, @@ -128,6 +129,45 @@ def try_import(modname, name=None): except ImportError as e: raise ValueError(f'Kernel is compressed with {name or modname}, but module unavailable') from e +def get_zboot_kernel(f): + """Decompress zboot efistub kernel if compressed. Return contents.""" + # See linux/drivers/firmware/efi/libstub/Makefile.zboot + # and linux/drivers/firmware/efi/libstub/zboot-header.S + + # 4 bytes at offset 0x08 contain the starting offset of compressed data + f.seek(8) + _start = f.read(4) + start = struct.unpack('<i', _start)[0] + + # Reading 4 bytes from address 0x0c is the size of compressed data, + # but it needs to be corrected according to the compressed type. + f.seek(0xc) + _sizes = f.read(4) + size = struct.unpack('<i', _sizes)[0] + + # Read 6 bytes from address 0x18, which is a nul-terminated + # string representing the compressed type. + f.seek(0x18) + comp_type = f.read(6) + f.seek(start) + if comp_type.startswith(b'gzip'): + gzip = try_import('gzip') + return gzip.open(f).read(size) + elif comp_type.startswith(b'lz4'): + lz4 = try_import('lz4.frame', 'lz4') + return lz4.frame.decompress(f.read(size)) + elif comp_type.startswith(b'lzma'): + lzma = try_import('lzma') + return lzma.open(f).read(size) + elif comp_type.startswith(b'lzo'): + raise NotImplementedError('lzo decompression not implemented') + elif comp_type.startswith(b'xzkern'): + raise NotImplementedError('xzkern decompression not implemented') + elif comp_type.startswith(b'zstd22'): + zstd = try_import('zstd') + return zstd.uncompress(f.read(size)) + else: + raise NotImplementedError(f'unknown compressed type: {comp_type}') def maybe_decompress(filename): """Decompress file if compressed. Return contents.""" @@ -140,8 +180,14 @@ def maybe_decompress(filename): return f.read() if start.startswith(b'MZ'): - # not compressed aarch64 and riscv64 - return f.read() + f.seek(4) + img_type = f.read(4) + if img_type.startswith(b'zimg'): + # zboot efistub kernel + return get_zboot_kernel(f) + else: + # not compressed aarch64 and riscv64 + return f.read() if start.startswith(b'\x1f\x8b'): gzip = try_import('gzip') @@ -804,14 +850,19 @@ def make_uki(opts): if linux is not None: # Merge the .sbat sections from stub, kernel and parameter, so that revocation can be done on either. - uki.add_section(Section.create('.sbat', merge_sbat([opts.stub, linux], opts.sbat), measure=True)) + input_pes = [opts.stub, linux] + if not opts.sbat: + opts.sbat = ["""sbat,1,SBAT Version,sbat,1,https://github.com/rhboot/shim/blob/main/SBAT.md +uki,1,UKI,uki,1,https://uapi-group.org/specifications/specs/unified_kernel_image/ +"""] else: # Addons don't use the stub so we add SBAT manually + input_pes = [] if not opts.sbat: opts.sbat = ["""sbat,1,SBAT Version,sbat,1,https://github.com/rhboot/shim/blob/main/SBAT.md -uki,1,UKI,uki,1,https://www.freedesktop.org/software/systemd/man/systemd-stub.html +uki-addon,1,UKI Addon,addon,1,https://www.freedesktop.org/software/systemd/man/latest/systemd-stub.html """] - uki.add_section(Section.create('.sbat', merge_sbat([], opts.sbat), measure=False)) + uki.add_section(Section.create('.sbat', merge_sbat(input_pes, opts.sbat), measure=linux is not None)) # PCR measurement and signing diff --git a/src/userdb/userdbctl.c b/src/userdb/userdbctl.c index a7db3fb..238a71d 100644 --- a/src/userdb/userdbctl.c +++ b/src/userdb/userdbctl.c @@ -310,6 +310,9 @@ static int table_add_uid_map( assert(table); assert(add_unavailable); + if (!p) + return 0; + for (size_t i = 0; p && i < p->n_entries; i++) { UidRangeEntry *x = p->entries + i; @@ -534,7 +537,8 @@ static int table_add_gid_boundaries(Table *table, const UidRange *p) { for (size_t i = 0; i < ELEMENTSOF(uid_range_table); i++) { _cleanup_free_ char *name = NULL, *comment = NULL; - if (!uid_range_covers(p, uid_range_table[i].first, uid_range_table[i].last)) + if (!uid_range_covers(p, uid_range_table[i].first, + uid_range_table[i].last - uid_range_table[i].first + 1)) continue; name = strjoin(special_glyph(SPECIAL_GLYPH_ARROW_DOWN), diff --git a/src/userdb/userdbd-manager.c b/src/userdb/userdbd-manager.c index c1dfe47..359c827 100644 --- a/src/userdb/userdbd-manager.c +++ b/src/userdb/userdbd-manager.c @@ -5,6 +5,7 @@ #include "sd-daemon.h" #include "common-signal.h" +#include "env-util.h" #include "fd-util.h" #include "fs-util.h" #include "mkdir.h" @@ -156,7 +157,6 @@ static int start_one_worker(Manager *m) { if (r < 0) return log_error_errno(r, "Failed to fork new worker child: %m"); if (r == 0) { - char pids[DECIMAL_STR_MAX(pid_t)]; /* Child */ if (m->listen_fd == 3) { @@ -174,9 +174,9 @@ static int start_one_worker(Manager *m) { safe_close(m->listen_fd); } - xsprintf(pids, PID_FMT, pid); - if (setenv("LISTEN_PID", pids, 1) < 0) { - log_error_errno(errno, "Failed to set $LISTEN_PID: %m"); + r = setenvf("LISTEN_PID", /* overwrite= */ true, PID_FMT, pid); + if (r < 0) { + log_error_errno(r, "Failed to set $LISTEN_PID: %m"); _exit(EXIT_FAILURE); } |