From c5d554aa666f7d87113a1f93547ce122d5ad4d14 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Sun, 11 Sep 2022 12:17:57 +0200 Subject: [PATCH 01/14] tmpfiles: rebreak some comments --- src/tmpfiles/tmpfiles.c | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index 7e99921db92..7b1e84e8afb 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -1294,9 +1294,8 @@ static int fd_set_attribute(Item *item, int fd, const char *path, const struct s st = &stbuf; } - /* Issuing the file attribute ioctls on device nodes is not - * safe, as that will be delivered to the drivers, not the - * file system containing the device node. */ + /* Issuing the file attribute ioctls on device nodes is not safe, as that will be delivered to the + * drivers, not the file system containing the device node. */ if (!S_ISREG(st->st_mode) && !S_ISDIR(st->st_mode)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Setting file flags is only supported on regular files and directories, cannot set on '%s'.", @@ -1437,17 +1436,14 @@ static int create_file(Item *i, const char *path) { } if (fd < 0) { - /* Even on a read-only filesystem, open(2) returns EEXIST if the - * file already exists. It returns EROFS only if it needs to - * create the file. */ + /* Even on a read-only filesystem, open(2) returns EEXIST if the file already exists. It + * returns EROFS only if it needs to create the file. */ if (fd != -EEXIST) return log_error_errno(fd, "Failed to create file %s: %m", path); - /* Re-open the file. At that point it must exist since open(2) - * failed with EEXIST. We still need to check if the perms/mode - * need to be changed. For read-only filesystems, we let - * fd_set_perms() report the error if the perms need to be - * modified. */ + /* Re-open the file. At that point it must exist since open(2) failed with EEXIST. We still + * need to check if the perms/mode need to be changed. For read-only filesystems, we let + * fd_set_perms() report the error if the perms need to be modified. */ fd = openat(dir_fd, bn, O_NOFOLLOW|O_CLOEXEC|O_PATH, i->mode); if (fd < 0) return log_error_errno(errno, "Failed to re-open file %s: %m", path); @@ -1507,11 +1503,9 @@ static int truncate_file(Item *i, const char *path) { if (fd != -EROFS) return log_error_errno(fd, "Failed to open/create file %s: %m", path); - /* On a read-only filesystem, we don't want to fail if the - * target is already empty and the perms are set. So we still - * proceed with the sanity checks and let the remaining - * operations fail with EROFS if they try to modify the target - * file. */ + /* On a read-only filesystem, we don't want to fail if the target is already empty and the + * perms are set. So we still proceed with the sanity checks and let the remaining operations + * fail with EROFS if they try to modify the target file. */ fd = openat(dir_fd, bn, O_NOFOLLOW|O_CLOEXEC|O_PATH, i->mode); if (fd < 0) { From a9bc518c08d4d3a111b637e348f5a60e5a50025f Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Sun, 11 Sep 2022 12:21:58 +0200 Subject: [PATCH 02/14] tmpfiles: generalize CreationMode and pass it everywhere For some purposes we had CreationMode which indicates whether an inode was created by us, or is pre-existing. Let's generalize that for *all* operations. This is later useful to conditionalize certain operations on that (and makes the codebase more systematic) --- src/tmpfiles/tmpfiles.c | 212 ++++++++++++++++++++++++++-------------- 1 file changed, 140 insertions(+), 72 deletions(-) diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index 7b1e84e8afb..b29614d9a56 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -180,6 +180,14 @@ typedef enum DirectoryType { _DIRECTORY_TYPE_MAX, } DirectoryType; +typedef enum { + CREATION_NORMAL, + CREATION_EXISTING, + CREATION_FORCE, + _CREATION_MODE_MAX, + _CREATION_MODE_INVALID = -EINVAL, +} CreationMode; + static bool arg_cat_config = false; static bool arg_user = false; static OperationMask arg_operation = 0; @@ -205,6 +213,14 @@ STATIC_DESTRUCTOR_REGISTER(arg_exclude_prefixes, freep); STATIC_DESTRUCTOR_REGISTER(arg_root, freep); STATIC_DESTRUCTOR_REGISTER(arg_image, freep); +static const char *const creation_mode_verb_table[_CREATION_MODE_MAX] = { + [CREATION_NORMAL] = "Created", + [CREATION_EXISTING] = "Found existing", + [CREATION_FORCE] = "Created replacement", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(creation_mode_verb, CreationMode); + static int specifier_machine_id_safe(char specifier, const void *data, const char *root, const void *userdata, char **ret) { int r; @@ -844,7 +860,13 @@ static mode_t process_mask_perms(mode_t mode, mode_t current) { return mode; } -static int fd_set_perms(Item *i, int fd, const char *path, const struct stat *st) { +static int fd_set_perms( + Item *i, + int fd, + const char *path, + const struct stat *st, + CreationMode creation) { + struct stat stbuf; mode_t new_mode; bool do_chown; @@ -979,7 +1001,11 @@ static int path_open_safe(const char *path) { return fd; } -static int path_set_perms(Item *i, const char *path) { +static int path_set_perms( + Item *i, + const char *path, + CreationMode creation) { + _cleanup_close_ int fd = -1; assert(i); @@ -989,7 +1015,7 @@ static int path_set_perms(Item *i, const char *path) { if (fd < 0) return fd; - return fd_set_perms(i, fd, path, NULL); + return fd_set_perms(i, fd, path, /* st= */ NULL, creation); } static int parse_xattrs_from_arg(Item *i) { @@ -1028,7 +1054,13 @@ static int parse_xattrs_from_arg(Item *i) { return 0; } -static int fd_set_xattrs(Item *i, int fd, const char *path, const struct stat *st) { +static int fd_set_xattrs( + Item *i, + int fd, + const char *path, + const struct stat *st, + CreationMode creation) { + assert(i); assert(fd >= 0); assert(path); @@ -1042,7 +1074,11 @@ static int fd_set_xattrs(Item *i, int fd, const char *path, const struct stat *s return 0; } -static int path_set_xattrs(Item *i, const char *path) { +static int path_set_xattrs( + Item *i, + const char *path, + CreationMode creation) { + _cleanup_close_ int fd = -1; assert(i); @@ -1052,7 +1088,7 @@ static int path_set_xattrs(Item *i, const char *path) { if (fd < 0) return fd; - return fd_set_xattrs(i, fd, path, NULL); + return fd_set_xattrs(i, fd, path, /* st = */ NULL, creation); } static int parse_acls_from_arg(Item *item) { @@ -1075,7 +1111,13 @@ static int parse_acls_from_arg(Item *item) { } #if HAVE_ACL -static int path_set_acl(const char *path, const char *pretty, acl_type_t type, acl_t acl, bool modify) { +static int path_set_acl( + const char *path, + const char *pretty, + acl_type_t type, + acl_t acl, + bool modify) { + _cleanup_(acl_free_charpp) char *t = NULL; _cleanup_(acl_freep) acl_t dup = NULL; int r; @@ -1124,7 +1166,13 @@ static int path_set_acl(const char *path, const char *pretty, acl_type_t type, a } #endif -static int fd_set_acls(Item *item, int fd, const char *path, const struct stat *st) { +static int fd_set_acls( + Item *item, + int fd, + const char *path, + const struct stat *st, + CreationMode creation) { + int r = 0; #if HAVE_ACL struct stat stbuf; @@ -1174,7 +1222,7 @@ static int fd_set_acls(Item *item, int fd, const char *path, const struct stat * return r; } -static int path_set_acls(Item *item, const char *path) { +static int path_set_acls(Item *item, const char *path, CreationMode creation) { int r = 0; #if HAVE_ACL _cleanup_close_ int fd = -1; @@ -1186,7 +1234,7 @@ static int path_set_acls(Item *item, const char *path) { if (fd < 0) return fd; - r = fd_set_acls(item, fd, path, NULL); + r = fd_set_acls(item, fd, path, /* st= */ NULL, creation); #endif return r; } @@ -1275,7 +1323,13 @@ static int parse_attribute_from_arg(Item *item) { return 0; } -static int fd_set_attribute(Item *item, int fd, const char *path, const struct stat *st) { +static int fd_set_attribute( + Item *item, + int fd, + const char *path, + const struct stat *st, + CreationMode creation) { + _cleanup_close_ int procfs_fd = -1; struct stat stbuf; unsigned f; @@ -1325,7 +1379,7 @@ static int fd_set_attribute(Item *item, int fd, const char *path, const struct s return 0; } -static int path_set_attribute(Item *item, const char *path) { +static int path_set_attribute(Item *item, const char *path, CreationMode creation) { _cleanup_close_ int fd = -1; if (!item->attribute_set || item->attribute_mask == 0) @@ -1335,7 +1389,7 @@ static int path_set_attribute(Item *item, const char *path) { if (fd < 0) return fd; - return fd_set_attribute(item, fd, path, NULL); + return fd_set_attribute(item, fd, path, /* st= */ NULL, creation); } static int write_argument_data(Item *i, int fd, const char *path) { @@ -1359,7 +1413,7 @@ static int write_argument_data(Item *i, int fd, const char *path) { return 0; } -static int write_one_file(Item *i, const char *path) { +static int write_one_file(Item *i, const char *path, CreationMode creation) { _cleanup_close_ int fd = -1, dir_fd = -1; _cleanup_free_ char *bn = NULL; int r; @@ -1402,13 +1456,14 @@ static int write_one_file(Item *i, const char *path) { if (r < 0) return r; - return fd_set_perms(i, fd, path, NULL); + return fd_set_perms(i, fd, path, NULL, creation); } static int create_file(Item *i, const char *path) { _cleanup_close_ int fd = -1, dir_fd = -1; _cleanup_free_ char *bn = NULL; struct stat stbuf, *st = NULL; + CreationMode creation; int r = 0; assert(i); @@ -1457,19 +1512,25 @@ static int create_file(Item *i, const char *path) { path); st = &stbuf; - } else if (item_binary_argument(i)) { - r = write_argument_data(i, fd, path); - if (r < 0) - return r; + creation = CREATION_EXISTING; + } else { + if (item_binary_argument(i)) { + r = write_argument_data(i, fd, path); + if (r < 0) + return r; + } + + creation = CREATION_NORMAL; } - return fd_set_perms(i, fd, path, st); + return fd_set_perms(i, fd, path, st, creation); } static int truncate_file(Item *i, const char *path) { _cleanup_close_ int fd = -1, dir_fd = -1; _cleanup_free_ char *bn = NULL; struct stat stbuf, *st = NULL; + CreationMode creation; bool erofs = false; int r = 0; @@ -1493,10 +1554,16 @@ static int truncate_file(Item *i, const char *path) { if (dir_fd < 0) return dir_fd; - RUN_WITH_UMASK(0000) { - mac_selinux_create_file_prepare(path, S_IFREG); - fd = RET_NERRNO(openat(dir_fd, bn, O_CREAT|O_NOFOLLOW|O_NONBLOCK|O_CLOEXEC|O_WRONLY|O_NOCTTY, i->mode)); - mac_selinux_create_file_clear(); + creation = CREATION_EXISTING; + fd = RET_NERRNO(openat(dir_fd, bn, O_NOFOLLOW|O_NONBLOCK|O_CLOEXEC|O_WRONLY|O_NOCTTY, i->mode)); + if (fd == -ENOENT) { + creation = CREATION_NORMAL; /* Didn't work without O_CREATE, try again with */ + + RUN_WITH_UMASK(0000) { + mac_selinux_create_file_prepare(path, S_IFREG); + fd = RET_NERRNO(openat(dir_fd, bn, O_CREAT|O_NOFOLLOW|O_NONBLOCK|O_CLOEXEC|O_WRONLY|O_NOCTTY, i->mode)); + mac_selinux_create_file_clear(); + } } if (fd < 0) { @@ -1518,6 +1585,7 @@ static int truncate_file(Item *i, const char *path) { } erofs = true; + creation = CREATION_EXISTING; } if (fstat(fd, &stbuf) < 0) @@ -1544,7 +1612,7 @@ static int truncate_file(Item *i, const char *path) { return r; } - return fd_set_perms(i, fd, path, st); + return fd_set_perms(i, fd, path, st, creation); } static int copy_files(Item *i) { @@ -1597,36 +1665,22 @@ static int copy_files(Item *i) { if (fd < 0) return log_error_errno(errno, "Failed to openat(%s): %m", i->path); - return fd_set_perms(i, fd, i->path, NULL); + return fd_set_perms(i, fd, i->path, /* st = */ NULL, _CREATION_MODE_INVALID); } -typedef enum { - CREATION_NORMAL, - CREATION_EXISTING, - CREATION_FORCE, - _CREATION_MODE_MAX, - _CREATION_MODE_INVALID = -EINVAL, -} CreationMode; +static int create_directory_or_subvolume( + const char *path, + mode_t mode, + bool subvol, + CreationMode *ret_creation) { -static const char *const creation_mode_verb_table[_CREATION_MODE_MAX] = { - [CREATION_NORMAL] = "Created", - [CREATION_EXISTING] = "Found existing", - [CREATION_FORCE] = "Created replacement", -}; - -DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(creation_mode_verb, CreationMode); - -static int create_directory_or_subvolume(const char *path, mode_t mode, bool subvol, CreationMode *creation) { _cleanup_free_ char *bn = NULL; _cleanup_close_ int pfd = -1; CreationMode c; - int r; + int r, fd; assert(path); - if (!creation) - creation = &c; - r = path_extract_filename(path, &bn); if (r < 0) return log_error_errno(r, "Failed to extract filename from path '%s': %m", path); @@ -1677,32 +1731,36 @@ static int create_directory_or_subvolume(const char *path, mode_t mode, bool sub return log_warning_errno(SYNTHETIC_ERRNO(EEXIST), "\"%s\" already exists and is not a directory.", path); - *creation = CREATION_EXISTING; + c = CREATION_EXISTING; } else - *creation = CREATION_NORMAL; + c = CREATION_NORMAL; - log_debug("%s directory \"%s\".", creation_mode_verb_to_string(*creation), path); + log_debug("%s directory \"%s\".", creation_mode_verb_to_string(c), path); - r = openat(pfd, bn, O_NOCTTY|O_CLOEXEC|O_DIRECTORY); - if (r < 0) + fd = openat(pfd, bn, O_NOCTTY|O_CLOEXEC|O_DIRECTORY); + if (fd < 0) return log_error_errno(errno, "Failed to open directory '%s': %m", bn); - return r; + if (ret_creation) + *ret_creation = c; + + return fd; } static int create_directory(Item *i, const char *path) { _cleanup_close_ int fd = -1; + CreationMode creation; assert(i); assert(IN_SET(i->type, CREATE_DIRECTORY, TRUNCATE_DIRECTORY)); - fd = create_directory_or_subvolume(path, i->mode, false, NULL); + fd = create_directory_or_subvolume(path, i->mode, /* subvol= */ false, &creation); if (fd == -EEXIST) return 0; if (fd < 0) return fd; - return fd_set_perms(i, fd, path, NULL); + return fd_set_perms(i, fd, path, /* st= */ NULL, creation); } static int create_subvolume(Item *i, const char *path) { @@ -1713,7 +1771,7 @@ static int create_subvolume(Item *i, const char *path) { assert(i); assert(IN_SET(i->type, CREATE_SUBVOLUME, CREATE_SUBVOLUME_NEW_QUOTA, CREATE_SUBVOLUME_INHERIT_QUOTA)); - fd = create_directory_or_subvolume(path, i->mode, true, &creation); + fd = create_directory_or_subvolume(path, i->mode, /* subvol = */ true, &creation); if (fd == -EEXIST) return 0; if (fd < 0) @@ -1736,14 +1794,14 @@ static int create_subvolume(Item *i, const char *path) { log_debug("Quota for subvolume \"%s\" already in place, no change made.", i->path); } - r = fd_set_perms(i, fd, path, NULL); + r = fd_set_perms(i, fd, path, /* st= */ NULL, creation); if (q < 0) /* prefer the quota change error from above */ return q; return r; } -static int empty_directory(Item *i, const char *path) { +static int empty_directory(Item *i, const char *path, CreationMode creation) { int r; assert(i); @@ -1763,7 +1821,7 @@ static int empty_directory(Item *i, const char *path) { return 0; } - return path_set_perms(i, path); + return path_set_perms(i, path, creation); } static int create_device(Item *i, mode_t file_type) { @@ -1841,7 +1899,7 @@ static int create_device(Item *i, mode_t file_type) { if (fd < 0) return log_error_errno(errno, "Failed to openat(%s): %m", i->path); - return fd_set_perms(i, fd, i->path, NULL); + return fd_set_perms(i, fd, i->path, /* st = */ NULL, creation); } static int create_fifo(Item *i, const char *path) { @@ -1901,13 +1959,19 @@ static int create_fifo(Item *i, const char *path) { if (fd < 0) return log_error_errno(errno, "Failed to openat(%s): %m", path); - return fd_set_perms(i, fd, i->path, NULL); + return fd_set_perms(i, fd, i->path, /* st = */ NULL, creation); } -typedef int (*action_t)(Item *i, const char *path); -typedef int (*fdaction_t)(Item *i, int fd, const char *path, const struct stat *st); +typedef int (*action_t)(Item *i, const char *path, CreationMode creation); +typedef int (*fdaction_t)(Item *i, int fd, const char *path, const struct stat *st, CreationMode creation); + +static int item_do( + Item *i, + int fd, + const char *path, + CreationMode creation, + fdaction_t action) { -static int item_do(Item *i, int fd, const char *path, fdaction_t action) { struct stat st; int r = 0, q; @@ -1920,9 +1984,8 @@ static int item_do(Item *i, int fd, const char *path, fdaction_t action) { goto finish; } - /* This returns the first error we run into, but nevertheless - * tries to go on */ - r = action(i, fd, path, &st); + /* This returns the first error we run into, but nevertheless tries to go on */ + r = action(i, fd, path, &st, creation); if (S_ISDIR(st.st_mode)) { _cleanup_closedir_ DIR *d = NULL; @@ -1954,7 +2017,7 @@ static int item_do(Item *i, int fd, const char *path, fdaction_t action) { q = log_oom(); else /* Pass ownership of dirent fd over */ - q = item_do(i, de_fd, de_path, action); + q = item_do(i, de_fd, de_path, CREATION_EXISTING, action); } if (q < 0 && r == 0) @@ -1977,7 +2040,8 @@ static int glob_item(Item *i, action_t action) { return log_error_errno(k, "glob(%s) failed: %m", i->path); STRV_FOREACH(fn, g.gl_pathv) { - k = action(i, *fn); + /* We pass CREATION_EXISTING here, since if we are globbing for it, it always has to exist */ + k = action(i, *fn, CREATION_EXISTING); if (k < 0 && r == 0) r = k; } @@ -2011,7 +2075,7 @@ static int glob_item_recursively(Item *i, fdaction_t action) { continue; } - k = item_do(i, fd, *fn, action); + k = item_do(i, fd, *fn, CREATION_EXISTING, action); if (k < 0 && r == 0) r = k; @@ -2404,7 +2468,7 @@ static int create_item(Item *i) { return 0; } -static int remove_item_instance(Item *i, const char *instance) { +static int remove_item_instance(Item *i, const char *instance, CreationMode creation) { int r; assert(i); @@ -2477,7 +2541,11 @@ static char *age_by_to_string(AgeBy ab, bool is_dir) { return ret; } -static int clean_item_instance(Item *i, const char* instance) { +static int clean_item_instance( + Item *i, + const char* instance, + CreationMode creation) { + _cleanup_closedir_ DIR *d = NULL; STRUCT_STATX_DEFINE(sx); int mountpoint, r; @@ -2562,7 +2630,7 @@ static int clean_item(Item *i) { case TRUNCATE_DIRECTORY: case IGNORE_PATH: case COPY_FILES: - clean_item_instance(i, i->path); + clean_item_instance(i, i->path, CREATION_EXISTING); return 0; case EMPTY_DIRECTORY: case IGNORE_DIRECTORY_PATH: From cc43328c7f603fe25478aa8d8a7848b175c6941b Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Sun, 11 Sep 2022 12:23:23 +0200 Subject: [PATCH 03/14] tmpfiles: allow prefixing uid/gid/mode with ":" to only apply on creation In some cases it is useful to specify the access mode/uid/gid for inodes we create without also enforcing them on existing inodes. Let's add a new flag for that: if the uid/gid/mode specificaitons are prefixed with ":", then they only apply to creation, not otherwise. This is specifically useful for provisioning SSH keys later. Those we'd like to provision like this: d /root :0700 root root - d /root/.ssh :0700 root root - f^ /root/.ssh/authorized_keys - - - - ssh.authorized_keys While /root/ + /root/.ssh/ being owned by root is pretty uncontroversial the access mode of /root/ and /root/.ssh/ might not be. Hence we should only have a default mode defined that is used when we create the dir, but not otherwise. --- src/tmpfiles/tmpfiles.c | 91 +++++++++++++++++++++++++---------------- 1 file changed, 55 insertions(+), 36 deletions(-) diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index b29614d9a56..a168f95c7c3 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -149,6 +149,9 @@ typedef struct Item { bool uid_set:1; bool gid_set:1; bool mode_set:1; + bool uid_only_create:1; + bool gid_only_create:1; + bool mode_only_create:1; bool age_set:1; bool mask_perms:1; bool attribute_set:1; @@ -867,9 +870,11 @@ static int fd_set_perms( const struct stat *st, CreationMode creation) { + bool do_chown, do_chmod; struct stat stbuf; mode_t new_mode; - bool do_chown; + uid_t new_uid; + gid_t new_gid; int r; assert(i); @@ -889,19 +894,20 @@ static int fd_set_perms( return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Refusing to set permissions on hardlinked file %s while the fs.protected_hardlinks sysctl is turned off.", path); + new_uid = i->uid_set && (creation != CREATION_EXISTING || !i->uid_only_create) ? i->uid : st->st_uid; + new_gid = i->gid_set && (creation != CREATION_EXISTING || !i->gid_only_create) ? i->gid : st->st_gid; /* Do we need a chown()? */ - do_chown = - (i->uid_set && i->uid != st->st_uid) || - (i->gid_set && i->gid != st->st_gid); + do_chown = (new_uid != st->st_uid) || (new_gid != st->st_gid); /* Calculate the mode to apply */ - new_mode = i->mode_set ? (i->mask_perms ? - process_mask_perms(i->mode, st->st_mode) : - i->mode) : - (st->st_mode & 07777); + new_mode = i->mode_set && (creation != CREATION_EXISTING || !i->mode_only_create) ? + (i->mask_perms ? process_mask_perms(i->mode, st->st_mode) : i->mode) : + (st->st_mode & 07777); - if (i->mode_set && do_chown) { + do_chmod = ((new_mode ^ st->st_mode) & 07777) != 0; + + if (do_chmod && do_chown) { /* Before we issue the chmod() let's reduce the access mode to the common bits of the old and * the new mode. That way there's no time window where the file exists under the old owner * with more than the old access modes — and not under the new owner with more than the new @@ -924,35 +930,25 @@ static int fd_set_perms( } if (do_chown) { - log_debug("Changing \"%s\" to owner "UID_FMT":"GID_FMT, - path, - i->uid_set ? i->uid : UID_INVALID, - i->gid_set ? i->gid : GID_INVALID); + log_debug("Changing \"%s\" to owner "UID_FMT":"GID_FMT, path, new_uid, new_gid); - if (fchownat(fd, - "", - i->uid_set ? i->uid : UID_INVALID, - i->gid_set ? i->gid : GID_INVALID, + if (fchownat(fd, "", + new_uid != st->st_uid ? new_uid : UID_INVALID, + new_gid != st->st_gid ? new_gid : GID_INVALID, AT_EMPTY_PATH) < 0) return log_error_errno(errno, "fchownat() of %s failed: %m", path); } /* Now, apply the final mode. We do this in two cases: when the user set a mode explicitly, or after a * chown(), since chown()'s mangle the access mode in regards to sgid/suid in some conditions. */ - if (i->mode_set || do_chown) { + if (do_chmod || do_chown) { if (S_ISLNK(st->st_mode)) log_debug("Skipping mode fix for symlink %s.", path); else { - /* Check if the chmod() is unnecessary. Note that if we did a chown() before we always - * chmod() here again, since it might have mangled the bits. */ - if (!do_chown && ((new_mode ^ st->st_mode) & 07777) == 0) - log_debug("\"%s\" matches mode %o already.", path, new_mode); - else { - log_debug("Changing \"%s\" to mode %o.", path, new_mode); - r = fchmod_opath(fd, new_mode); - if (r < 0) - return log_error_errno(r, "fchmod() of %s failed: %m", path); - } + log_debug("Changing \"%s\" to mode %o.", path, new_mode); + r = fchmod_opath(fd, new_mode); + if (r < 0) + return log_error_errno(r, "fchmod() of %s failed: %m", path); } } @@ -2772,12 +2768,15 @@ static bool item_compatible(const Item *a, const Item *b) { a->uid_set == b->uid_set && a->uid == b->uid && + a->uid_only_create == b->uid_only_create && a->gid_set == b->gid_set && a->gid == b->gid && + a->gid_only_create == b->gid_only_create && a->mode_set == b->mode_set && a->mode == b->mode && + a->mode_only_create == b->mode_only_create && a->age_set == b->age_set && a->age == b->age && @@ -3365,32 +3364,52 @@ static int parse_line( } if (!empty_or_dash(user)) { - r = find_uid(user, &i.uid, uid_cache); + const char *u; + + u = startswith(user, ":"); + if (u) + i.uid_only_create = true; + else + u = user; + + r = find_uid(u, &i.uid, uid_cache); if (r < 0) { *invalid_config = true; - return log_syntax(NULL, LOG_ERR, fname, line, r, "Failed to resolve user '%s': %m", user); + return log_syntax(NULL, LOG_ERR, fname, line, r, "Failed to resolve user '%s': %m", u); } i.uid_set = true; } if (!empty_or_dash(group)) { - r = find_gid(group, &i.gid, gid_cache); + const char *g; + + g = startswith(group, ":"); + if (g) + i.gid_only_create = true; + else + g = group; + + r = find_gid(g, &i.gid, gid_cache); if (r < 0) { *invalid_config = true; - return log_syntax(NULL, LOG_ERR, fname, line, r, "Failed to resolve group '%s'.", group); + return log_syntax(NULL, LOG_ERR, fname, line, r, "Failed to resolve group '%s'.", g); } i.gid_set = true; } if (!empty_or_dash(mode)) { - const char *mm = mode; + const char *mm; unsigned m; - if (*mm == '~') { - i.mask_perms = true; - mm++; + for (mm = mode;; mm++) { + if (*mm == '~') + i.mask_perms = true; + else if (*mm == ':') + i.mode_only_create = true; + else + break; } r = parse_mode(mm, &m); From da9dd029a2eec824aac1202bb60737e797d1abca Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 13 Sep 2022 11:33:24 +0100 Subject: [PATCH 04/14] fs-util: replace symlink_atomic() by symlinkat_atomic() --- src/basic/fs-util.c | 11 ++++++----- src/basic/fs-util.h | 4 ++-- src/shared/label.c | 2 +- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c index cbab59b7773..3f23ccab248 100644 --- a/src/basic/fs-util.c +++ b/src/basic/fs-util.c @@ -432,7 +432,7 @@ int symlink_idempotent(const char *from, const char *to, bool make_relative) { return 0; } -int symlink_atomic_full(const char *from, const char *to, bool make_relative) { +int symlinkat_atomic_full(const char *from, int atfd, const char *to, bool make_relative) { _cleanup_free_ char *relpath = NULL, *t = NULL; int r; @@ -451,12 +451,13 @@ int symlink_atomic_full(const char *from, const char *to, bool make_relative) { if (r < 0) return r; - if (symlink(from, t) < 0) + if (symlinkat(from, atfd, t) < 0) return -errno; - if (rename(t, to) < 0) { - unlink_noerrno(t); - return -errno; + r = RET_NERRNO(renameat(atfd, t, atfd, to)); + if (r < 0) { + (void) unlinkat(atfd, t, 0); + return r; } return 0; diff --git a/src/basic/fs-util.h b/src/basic/fs-util.h index f2174af15d7..6be94d913c6 100644 --- a/src/basic/fs-util.h +++ b/src/basic/fs-util.h @@ -58,9 +58,9 @@ static inline int touch(const char *path) { int symlink_idempotent(const char *from, const char *to, bool make_relative); -int symlink_atomic_full(const char *from, const char *to, bool make_relative); +int symlinkat_atomic_full(const char *from, int atfd, const char *to, bool make_relative); static inline int symlink_atomic(const char *from, const char *to) { - return symlink_atomic_full(from, to, false); + return symlinkat_atomic_full(from, AT_FDCWD, to, false); } int mknod_atomic(const char *path, mode_t mode, dev_t dev); int mkfifo_atomic(const char *path, mode_t mode); diff --git a/src/shared/label.c b/src/shared/label.c index d00158a2585..66fcc0a31f5 100644 --- a/src/shared/label.c +++ b/src/shared/label.c @@ -71,7 +71,7 @@ int symlink_atomic_full_label(const char *from, const char *to, bool make_relati if (r < 0) return r; - r = symlink_atomic_full(from, to, make_relative); + r = symlinkat_atomic_full(from, AT_FDCWD, to, make_relative); mac_selinux_create_file_clear(); if (r < 0) From 4f477796f3bfc6926c8bbb07a940afc2a8b29059 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 15 Sep 2022 20:37:52 +0100 Subject: [PATCH 05/14] fs-util: make mkfifo_atomic() just a shortcut for mkfifoat_atomic() --- src/basic/fs-util.c | 35 ++++++----------------------------- src/basic/fs-util.h | 4 +++- 2 files changed, 9 insertions(+), 30 deletions(-) diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c index 3f23ccab248..927bb418af4 100644 --- a/src/basic/fs-util.c +++ b/src/basic/fs-util.c @@ -484,47 +484,24 @@ int mknod_atomic(const char *path, mode_t mode, dev_t dev) { return 0; } -int mkfifo_atomic(const char *path, mode_t mode) { +int mkfifoat_atomic(int atfd, const char *path, mode_t mode) { _cleanup_free_ char *t = NULL; int r; assert(path); + /* We're only interested in the (random) filename. */ r = tempfn_random(path, NULL, &t); if (r < 0) return r; - if (mkfifo(t, mode) < 0) + if (mkfifoat(atfd, t, mode) < 0) return -errno; - if (rename(t, path) < 0) { - unlink_noerrno(t); - return -errno; - } - - return 0; -} - -int mkfifoat_atomic(int dirfd, const char *path, mode_t mode) { - _cleanup_free_ char *t = NULL; - int r; - - assert(path); - - if (path_is_absolute(path)) - return mkfifo_atomic(path, mode); - - /* We're only interested in the (random) filename. */ - r = tempfn_random_child("", NULL, &t); - if (r < 0) + r = RET_NERRNO(renameat(atfd, t, atfd, path)); + if (r < 0) { + (void) unlinkat(atfd, t, 0); return r; - - if (mkfifoat(dirfd, t, mode) < 0) - return -errno; - - if (renameat(dirfd, t, dirfd, path) < 0) { - unlink_noerrno(t); - return -errno; } return 0; diff --git a/src/basic/fs-util.h b/src/basic/fs-util.h index 6be94d913c6..efa1090d2b6 100644 --- a/src/basic/fs-util.h +++ b/src/basic/fs-util.h @@ -63,8 +63,10 @@ static inline int symlink_atomic(const char *from, const char *to) { return symlinkat_atomic_full(from, AT_FDCWD, to, false); } int mknod_atomic(const char *path, mode_t mode, dev_t dev); -int mkfifo_atomic(const char *path, mode_t mode); int mkfifoat_atomic(int dir_fd, const char *path, mode_t mode); +static inline int mkfifo_atomic(const char *path, mode_t mode) { + return mkfifoat_atomic(AT_FDCWD, path, mode); +} int get_files_in_directory(const char *path, char ***list); From 497ca785aa9bb241873cbcfd8c0a6cb12dcacbf9 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 16 Sep 2022 12:45:02 +0100 Subject: [PATCH 06/14] fs-util: add mknodat_atomic() --- src/basic/fs-util.c | 11 ++++++----- src/basic/fs-util.h | 7 ++++++- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c index 927bb418af4..6b757bd5705 100644 --- a/src/basic/fs-util.c +++ b/src/basic/fs-util.c @@ -463,7 +463,7 @@ int symlinkat_atomic_full(const char *from, int atfd, const char *to, bool make_ return 0; } -int mknod_atomic(const char *path, mode_t mode, dev_t dev) { +int mknodat_atomic(int atfd, const char *path, mode_t mode, dev_t dev) { _cleanup_free_ char *t = NULL; int r; @@ -473,12 +473,13 @@ int mknod_atomic(const char *path, mode_t mode, dev_t dev) { if (r < 0) return r; - if (mknod(t, mode, dev) < 0) + if (mknodat(atfd, t, mode, dev) < 0) return -errno; - if (rename(t, path) < 0) { - unlink_noerrno(t); - return -errno; + r = RET_NERRNO(renameat(atfd, t, atfd, path)); + if (r < 0) { + (void) unlinkat(atfd, t, 0); + return r; } return 0; diff --git a/src/basic/fs-util.h b/src/basic/fs-util.h index efa1090d2b6..c4dffc48f3b 100644 --- a/src/basic/fs-util.h +++ b/src/basic/fs-util.h @@ -62,7 +62,12 @@ int symlinkat_atomic_full(const char *from, int atfd, const char *to, bool make_ static inline int symlink_atomic(const char *from, const char *to) { return symlinkat_atomic_full(from, AT_FDCWD, to, false); } -int mknod_atomic(const char *path, mode_t mode, dev_t dev); + +int mknodat_atomic(int atfd, const char *path, mode_t mode, dev_t dev); +static inline int mknod_atomic(const char *path, mode_t mode, dev_t dev) { + return mknodat_atomic(AT_FDCWD, path, mode, dev); +} + int mkfifoat_atomic(int dir_fd, const char *path, mode_t mode); static inline int mkfifo_atomic(const char *path, mode_t mode) { return mkfifoat_atomic(AT_FDCWD, path, mode); From 8f6fb95cd069884f4ce0a24eb20efc821ae3bc5e Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 13 Sep 2022 10:46:23 +0100 Subject: [PATCH 07/14] tmpfiles: whenever creating an inode, immediately O_PATH open it to pin it let's make things a bit less racy: whenever we create an inode, immediately open it via O_PATH, compare type and continue operations with the acquired fd. --- src/tmpfiles/tmpfiles.c | 305 +++++++++++++++++++++++----------------- 1 file changed, 176 insertions(+), 129 deletions(-) diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index a168f95c7c3..a354f3289c3 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -31,6 +31,7 @@ #include "dirent-util.h" #include "dissect-image.h" #include "env-util.h" +#include "errno-util.h" #include "escape.h" #include "fd-util.h" #include "fileio.h" @@ -1510,11 +1511,9 @@ static int create_file(Item *i, const char *path) { st = &stbuf; creation = CREATION_EXISTING; } else { - if (item_binary_argument(i)) { - r = write_argument_data(i, fd, path); - if (r < 0) - return r; - } + r = write_argument_data(i, fd, path); + if (r < 0) + return r; creation = CREATION_NORMAL; } @@ -1614,6 +1613,7 @@ static int truncate_file(Item *i, const char *path) { static int copy_files(Item *i) { _cleanup_close_ int dfd = -1, fd = -1; _cleanup_free_ char *bn = NULL; + struct stat st, a; int r; log_debug("Copying tree \"%s\" to \"%s\".", i->argument, i->path); @@ -1633,46 +1633,40 @@ static int copy_files(Item *i) { i->uid_set ? i->uid : UID_INVALID, i->gid_set ? i->gid : GID_INVALID, COPY_REFLINK | COPY_MERGE_EMPTY | COPY_MAC_CREATE | COPY_HARDLINKS); - if (r < 0) { - struct stat a, b; - - /* If the target already exists on read-only filesystems, trying - * to create the target will not fail with EEXIST but with - * EROFS. */ - if (r == -EROFS && faccessat(dfd, bn, F_OK, AT_SYMLINK_NOFOLLOW) == 0) - r = -EEXIST; - - if (r != -EEXIST) - return log_error_errno(r, "Failed to copy files to %s: %m", i->path); - - if (stat(i->argument, &a) < 0) - return log_error_errno(errno, "stat(%s) failed: %m", i->argument); - - if (fstatat(dfd, bn, &b, AT_SYMLINK_NOFOLLOW) < 0) - return log_error_errno(errno, "stat(%s) failed: %m", i->path); - - if ((a.st_mode ^ b.st_mode) & S_IFMT) { - log_debug("Can't copy to %s, file exists already and is of different type", i->path); - return 0; - } - } fd = openat(dfd, bn, O_NOFOLLOW|O_CLOEXEC|O_PATH); - if (fd < 0) - return log_error_errno(errno, "Failed to openat(%s): %m", i->path); + if (fd < 0) { + if (r < 0) /* Look at original error first */ + return log_error_errno(r, "Failed to copy files to %s: %m", i->path); - return fd_set_perms(i, fd, i->path, /* st = */ NULL, _CREATION_MODE_INVALID); + return log_error_errno(errno, "Failed to openat(%s): %m", i->path); + } + + if (fstat(fd, &st) < 0) + return log_error_errno(errno, "Failed to fstat(%s): %m", i->path); + + if (stat(i->argument, &a) < 0) + return log_error_errno(errno, "Failed to stat(%s): %m", i->argument); + + if (((st.st_mode ^ a.st_mode) & S_IFMT) != 0) { + log_debug("Can't copy to %s, file exists already and is of different type", i->path); + return 0; + } + + return fd_set_perms(i, fd, i->path, &st, _CREATION_MODE_INVALID); } static int create_directory_or_subvolume( const char *path, mode_t mode, bool subvol, + struct stat *ret_st, CreationMode *ret_creation) { _cleanup_free_ char *bn = NULL; _cleanup_close_ int pfd = -1; - CreationMode c; + CreationMode creation; + struct stat st; int r, fd; assert(path); @@ -1692,7 +1686,7 @@ static int create_directory_or_subvolume( log_warning_errno(r, "Cannot parse value of $SYSTEMD_TMPFILES_FORCE_SUBVOL, ignoring."); r = btrfs_is_subvol(empty_to_root(arg_root)) > 0; } - if (!r) + if (r == 0) /* Don't create a subvolume unless the root directory is one, too. We do this under * the assumption that if the root directory is just a plain directory (i.e. very * light-weight), we shouldn't try to split it up into subvolumes (i.e. more @@ -1708,37 +1702,36 @@ static int create_directory_or_subvolume( } else r = 0; - if (!subvol || r == -ENOTTY) + if (!subvol || ERRNO_IS_NOT_SUPPORTED(r)) RUN_WITH_UMASK(0000) r = mkdirat_label(pfd, bn, mode); - if (r < 0) { - int k; + creation = r >= 0 ? CREATION_NORMAL : CREATION_EXISTING; - if (!IN_SET(r, -EEXIST, -EROFS)) + fd = openat(pfd, bn, O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH); + if (fd < 0) { + /* We couldn't open it because it is not actually a directory? */ + if (errno == ENOTDIR) + return log_error_errno(SYNTHETIC_ERRNO(EEXIST), "\"%s\" already exists and is not a directory.", path); + + /* Then look at the original error */ + if (r < 0) return log_error_errno(r, "Failed to create directory or subvolume \"%s\": %m", path); - k = is_dir_full(pfd, bn, /* follow= */ false); - if (k == -ENOENT && r == -EROFS) - return log_error_errno(r, "%s does not exist and cannot be created as the file system is read-only.", path); - if (k < 0) - return log_error_errno(k, "Failed to check if %s exists: %m", path); - if (!k) - return log_warning_errno(SYNTHETIC_ERRNO(EEXIST), - "\"%s\" already exists and is not a directory.", path); + return log_error_errno(errno, "Failed to open directory/subvolume we just created '%s': %m", path); + } - c = CREATION_EXISTING; - } else - c = CREATION_NORMAL; + if (fstat(fd, &st) < 0) + return log_error_errno(errno, "Failed to fstat(%s): %m", path); - log_debug("%s directory \"%s\".", creation_mode_verb_to_string(c), path); + assert(S_ISDIR(st.st_mode)); /* we used O_DIRECTORY above */ - fd = openat(pfd, bn, O_NOCTTY|O_CLOEXEC|O_DIRECTORY); - if (fd < 0) - return log_error_errno(errno, "Failed to open directory '%s': %m", bn); + log_debug("%s directory \"%s\".", creation_mode_verb_to_string(creation), path); + if (ret_st) + *ret_st = st; if (ret_creation) - *ret_creation = c; + *ret_creation = creation; return fd; } @@ -1746,28 +1739,30 @@ static int create_directory_or_subvolume( static int create_directory(Item *i, const char *path) { _cleanup_close_ int fd = -1; CreationMode creation; + struct stat st; assert(i); assert(IN_SET(i->type, CREATE_DIRECTORY, TRUNCATE_DIRECTORY)); - fd = create_directory_or_subvolume(path, i->mode, /* subvol= */ false, &creation); + fd = create_directory_or_subvolume(path, i->mode, /* subvol= */ false, &st, &creation); if (fd == -EEXIST) return 0; if (fd < 0) return fd; - return fd_set_perms(i, fd, path, /* st= */ NULL, creation); + return fd_set_perms(i, fd, path, &st, creation); } static int create_subvolume(Item *i, const char *path) { _cleanup_close_ int fd = -1; CreationMode creation; + struct stat st; int r, q = 0; assert(i); assert(IN_SET(i->type, CREATE_SUBVOLUME, CREATE_SUBVOLUME_NEW_QUOTA, CREATE_SUBVOLUME_INHERIT_QUOTA)); - fd = create_directory_or_subvolume(path, i->mode, /* subvol = */ true, &creation); + fd = create_directory_or_subvolume(path, i->mode, /* subvol = */ true, &st, &creation); if (fd == -EEXIST) return 0; if (fd < 0) @@ -1790,7 +1785,7 @@ static int create_subvolume(Item *i, const char *path) { log_debug("Quota for subvolume \"%s\" already in place, no change made.", i->path); } - r = fd_set_perms(i, fd, path, /* st= */ NULL, creation); + r = fd_set_perms(i, fd, path, &st, creation); if (q < 0) /* prefer the quota change error from above */ return q; @@ -1824,9 +1819,11 @@ static int create_device(Item *i, mode_t file_type) { _cleanup_close_ int dfd = -1, fd = -1; _cleanup_free_ char *bn = NULL; CreationMode creation; + struct stat st; int r; assert(i); + assert(IN_SET(i->type, CREATE_BLOCK_DEVICE|CREATE_CHAR_DEVICE)); assert(IN_SET(file_type, S_IFBLK, S_IFCHR)); r = path_extract_filename(i->path, &bn); @@ -1846,116 +1843,166 @@ static int create_device(Item *i, mode_t file_type) { r = RET_NERRNO(mknodat(dfd, bn, i->mode | file_type, i->major_minor)); mac_selinux_create_file_clear(); } + creation = r >= 0 ? CREATION_NORMAL : CREATION_EXISTING; - if (r < 0) { - struct stat st; + /* Try to open the inode via O_PATH, regardless if we could create it or not. Maybe everything is in + * order anyway and we hence can ignore the error to create the device node */ + fd = openat(dfd, bn, O_NOFOLLOW|O_CLOEXEC|O_PATH); + if (fd < 0) { + /* OK, so opening the inode failed, let's look at the original error then. */ - if (r == -EPERM) { - log_debug_errno(r, - "We lack permissions, possibly because of cgroup configuration; " - "skipping creation of device node %s.", i->path); - return 0; + if (r < 0) { + if (ERRNO_IS_PRIVILEGE(r)) + goto handle_privilege; + + return log_error_errno(r, "Failed to create device node '%s': %m", i->path); } - if (r != -EEXIST) - return log_error_errno(r, "Failed to create device node %s: %m", i->path); + return log_error_errno(errno, "Failed to open device node '%s' we just created: %m", i->path); + } - if (fstatat(dfd, bn, &st, 0) < 0) - return log_error_errno(errno, "stat(%s) failed: %m", i->path); + if (fstat(fd, &st) < 0) + return log_error_errno(errno, "Failed to fstat(%s): %m", i->path); - if ((st.st_mode & S_IFMT) != file_type) { + if (((st.st_mode ^ file_type) & S_IFMT) != 0) { - if (i->append_or_force) { + if (i->append_or_force) { + fd = safe_close(fd); - RUN_WITH_UMASK(0000) { - mac_selinux_create_file_prepare(i->path, file_type); - /* FIXME: need to introduce mknodat_atomic() */ - r = mknod_atomic(i->path, i->mode | file_type, i->major_minor); - mac_selinux_create_file_clear(); - } - - if (r < 0) - return log_error_errno(r, "Failed to create device node \"%s\": %m", i->path); - creation = CREATION_FORCE; - } else { - log_warning("\"%s\" already exists is not a device node.", i->path); - return 0; + RUN_WITH_UMASK(0000) { + mac_selinux_create_file_prepare(i->path, file_type); + r = mknodat_atomic(dfd, bn, i->mode | file_type, i->major_minor); + mac_selinux_create_file_clear(); } - } else - creation = CREATION_EXISTING; - } else - creation = CREATION_NORMAL; + if (ERRNO_IS_PRIVILEGE(r)) + goto handle_privilege; + if (IN_SET(r, -EISDIR, -EEXIST, -ENOTEMPTY)) { + r = rm_rf_child(dfd, bn, REMOVE_PHYSICAL); + if (r < 0) + return log_error_errno(r, "rm -rf %s failed: %m", i->path); + + mac_selinux_create_file_prepare(i->path, file_type); + r = RET_NERRNO(mknodat(dfd, bn, i->mode | file_type, i->major_minor)); + mac_selinux_create_file_clear(); + } + if (r < 0) + return log_error_errno(r, "Failed to create device node '%s': %m", i->path); + + fd = openat(dfd, bn, O_NOFOLLOW|O_CLOEXEC|O_PATH); + if (fd < 0) + return log_error_errno(errno, "Failed to open device node we just created '%s': %m", i->path); + + /* Validate type before change ownership below */ + if (fstat(fd, &st) < 0) + return log_error_errno(errno, "Failed to fstat(%s): %m", i->path); + + if (((st.st_mode ^ file_type) & S_IFMT) != 0) + return log_error_errno(SYNTHETIC_ERRNO(EBADF), "Device node we just created is not a device node, refusing."); + + creation = CREATION_FORCE; + } else { + log_warning("\"%s\" already exists and is not a device node.", i->path); + return 0; + } + } log_debug("%s %s device node \"%s\" %u:%u.", creation_mode_verb_to_string(creation), i->type == CREATE_BLOCK_DEVICE ? "block" : "char", i->path, major(i->mode), minor(i->mode)); - fd = openat(dfd, bn, O_NOFOLLOW|O_CLOEXEC|O_PATH); - if (fd < 0) - return log_error_errno(errno, "Failed to openat(%s): %m", i->path); + return fd_set_perms(i, fd, i->path, &st, creation); - return fd_set_perms(i, fd, i->path, /* st = */ NULL, creation); +handle_privilege: + log_debug_errno(r, + "We lack permissions, possibly because of cgroup configuration; " + "skipping creation of device node '%s'.", i->path); + return 0; } -static int create_fifo(Item *i, const char *path) { +static int create_fifo(Item *i) { _cleanup_close_ int pfd = -1, fd = -1; _cleanup_free_ char *bn = NULL; CreationMode creation; struct stat st; int r; + assert(i); + assert(i->type == CREATE_FIFO); + r = path_extract_filename(i->path, &bn); if (r < 0) - return log_error_errno(r, "Failed to extract filename from path '%s': %m", path); + return log_error_errno(r, "Failed to extract filename from path '%s': %m", i->path); if (r == O_DIRECTORY) - return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Cannot open path '%s' for creating FIFO, is a directory.", path); + return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Cannot open path '%s' for creating FIFO, is a directory.", i->path); - pfd = path_open_parent_safe(path); + pfd = path_open_parent_safe(i->path); if (pfd < 0) return pfd; RUN_WITH_UMASK(0000) { - mac_selinux_create_file_prepare(path, S_IFIFO); + mac_selinux_create_file_prepare(i->path, S_IFIFO); r = RET_NERRNO(mkfifoat(pfd, bn, i->mode)); mac_selinux_create_file_clear(); } - if (r < 0) { - if (r != -EEXIST) - return log_error_errno(r, "Failed to create fifo %s: %m", path); - - if (fstatat(pfd, bn, &st, AT_SYMLINK_NOFOLLOW) < 0) - return log_error_errno(errno, "stat(%s) failed: %m", path); - - if (!S_ISFIFO(st.st_mode)) { - - if (i->append_or_force) { - RUN_WITH_UMASK(0000) { - mac_selinux_create_file_prepare(path, S_IFIFO); - r = mkfifoat_atomic(pfd, bn, i->mode); - mac_selinux_create_file_clear(); - } - - if (r < 0) - return log_error_errno(r, "Failed to create fifo %s: %m", path); - creation = CREATION_FORCE; - } else { - log_warning("\"%s\" already exists and is not a fifo.", path); - return 0; - } - } else - creation = CREATION_EXISTING; - } else - creation = CREATION_NORMAL; - - log_debug("%s fifo \"%s\".", creation_mode_verb_to_string(creation), path); + creation = r >= 0 ? CREATION_NORMAL : CREATION_EXISTING; + /* Open the inode via O_PATH, regardless if we managed to create it or not. Maybe it is is already the FIFO we want */ fd = openat(pfd, bn, O_NOFOLLOW|O_CLOEXEC|O_PATH); - if (fd < 0) - return log_error_errno(errno, "Failed to openat(%s): %m", path); + if (fd < 0) { + if (r < 0) + return log_error_errno(r, "Failed to create FIFO %s: %m", i->path); /* original error! */ - return fd_set_perms(i, fd, i->path, /* st = */ NULL, creation); + return log_error_errno(errno, "Failed to open FIFO we just created %s: %m", i->path); + } + + if (fstat(fd, &st) < 0) + return log_error_errno(errno, "Failed to fstat(%s): %m", i->path); + + if (!S_ISFIFO(st.st_mode)) { + + if (i->append_or_force) { + fd = safe_close(fd); + + RUN_WITH_UMASK(0000) { + mac_selinux_create_file_prepare(i->path, S_IFIFO); + r = mkfifoat_atomic(pfd, bn, i->mode); + mac_selinux_create_file_clear(); + } + if (IN_SET(r, -EISDIR, -EEXIST, -ENOTEMPTY)) { + r = rm_rf_child(pfd, bn, REMOVE_PHYSICAL); + if (r < 0) + return log_error_errno(r, "rm -rf %s failed: %m", i->path); + + mac_selinux_create_file_prepare(i->path, S_IFIFO); + r = RET_NERRNO(mkfifoat(pfd, bn, i->mode)); + mac_selinux_create_file_clear(); + } + if (r < 0) + return log_error_errno(r, "Failed to create FIFO %s: %m", i->path); + + fd = openat(pfd, bn, O_NOFOLLOW|O_CLOEXEC|O_PATH); + if (fd < 0) + return log_error_errno(errno, "Failed to open FIFO we just created '%s': %m", i->path); + + /* Validate type before change ownership below */ + if (fstat(fd, &st) < 0) + return log_error_errno(errno, "Failed to fstat(%s): %m", i->path); + + if (!S_ISFIFO(st.st_mode)) + return log_error_errno(SYNTHETIC_ERRNO(EBADF), "FIFO inode we just created is not a FIFO, refusing."); + + creation = CREATION_FORCE; + } else { + log_warning("\"%s\" already exists and is not a FIFO.", i->path); + return 0; + } + } + + log_debug("%s fifo \"%s\".", creation_mode_verb_to_string(creation), i->path); + + return fd_set_perms(i, fd, i->path, &st, creation); } typedef int (*action_t)(Item *i, const char *path, CreationMode creation); @@ -2337,7 +2384,7 @@ static int create_item(Item *i) { if (r < 0) return r; - r = create_fifo(i, i->path); + r = create_fifo(i); if (r < 0) return r; break; From 9e430ce3d4335541ac54658625483d282d0a1998 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 13 Sep 2022 11:34:18 +0100 Subject: [PATCH 08/14] tmpfiles: move symlink creation into its own function, and modernize MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Let's ensure it also operates based on O_PATH, like fifo/device node/… creation. --- src/tmpfiles/tmpfiles.c | 139 +++++++++++++++++++++++++++------------- 1 file changed, 94 insertions(+), 45 deletions(-) diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index a354f3289c3..8442cce5bc4 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -2005,6 +2005,95 @@ static int create_fifo(Item *i) { return fd_set_perms(i, fd, i->path, &st, creation); } +static int create_symlink(Item *i) { + _cleanup_close_ int pfd = -1, fd = -1; + _cleanup_free_ char *bn = NULL; + CreationMode creation; + struct stat st; + bool good = false; + int r; + + assert(i); + + r = path_extract_filename(i->path, &bn); + if (r < 0) + return log_error_errno(r, "Failed to extract filename from path '%s': %m", i->path); + if (r == O_DIRECTORY) + return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Cannot open path '%s' for creating FIFO, is a directory.", i->path); + + pfd = path_open_parent_safe(i->path); + if (pfd < 0) + return pfd; + + mac_selinux_create_file_prepare(i->path, S_IFLNK); + r = RET_NERRNO(symlinkat(i->argument, pfd, bn)); + mac_selinux_create_file_clear(); + + creation = r >= 0 ? CREATION_NORMAL : CREATION_EXISTING; + + fd = openat(pfd, bn, O_NOFOLLOW|O_CLOEXEC|O_PATH); + if (fd < 0) { + if (r < 0) + return log_error_errno(r, "Failed to create symlink '%s': %m", i->path); /* original error! */ + + return log_error_errno(errno, "Failed to open symlink we just created '%s': %m", i->path); + } + + if (fstat(fd, &st) < 0) + return log_error_errno(errno, "Failed to fstat(%s): %m", i->path); + + if (S_ISLNK(st.st_mode)) { + _cleanup_free_ char *x = NULL; + + r = readlinkat_malloc(fd, "", &x); + if (r < 0) + return log_error_errno(r, "readlinkat(%s) failed: %m", i->path); + + good = streq(x, i->argument); + } else + good = false; + + if (!good) { + if (!i->append_or_force) { + log_debug("\"%s\" is not a symlink or does not point to the correct path.", i->path); + return 0; + } + + fd = safe_close(fd); + + mac_selinux_create_file_prepare(i->path, S_IFLNK); + r = symlinkat_atomic_full(i->argument, pfd, bn, /* make_relative= */ false); + mac_selinux_create_file_clear(); + if (IN_SET(r, -EISDIR, -EEXIST, -ENOTEMPTY)) { + r = rm_rf_child(pfd, bn, REMOVE_PHYSICAL); + if (r < 0) + return log_error_errno(r, "rm -rf %s failed: %m", i->path); + + mac_selinux_create_file_prepare(i->path, S_IFLNK); + r = RET_NERRNO(symlinkat(i->argument, pfd, i->path)); + mac_selinux_create_file_clear(); + } + if (r < 0) + return log_error_errno(r, "symlink(%s, %s) failed: %m", i->argument, i->path); + + fd = openat(pfd, bn, O_NOFOLLOW|O_CLOEXEC|O_PATH); + if (fd < 0) + return log_error_errno(errno, "Failed to open symlink we just created '%s': %m", i->path); + + /* Validate type before change ownership below */ + if (fstat(fd, &st) < 0) + return log_error_errno(errno, "Failed to fstat(%s): %m", i->path); + + if (!S_ISLNK(st.st_mode)) + return log_error_errno(SYNTHETIC_ERRNO(EBADF), "Symlink we just created is not a symlink, refusing."); + + creation = CREATION_FORCE; + } + + log_debug("%s symlink \"%s\".", creation_mode_verb_to_string(creation), i->path); + return fd_set_perms(i, fd, i->path, &st, creation); +} + typedef int (*action_t)(Item *i, const char *path, CreationMode creation); typedef int (*fdaction_t)(Item *i, int fd, const char *path, const struct stat *st, CreationMode creation); @@ -2303,8 +2392,7 @@ static int mkdir_parents_item(Item *i, mode_t child_mode) { } static int create_item(Item *i) { - CreationMode creation; - int r = 0; + int r; assert(i); @@ -2328,7 +2416,6 @@ static int create_item(Item *i) { r = truncate_file(i, i->path); else r = create_file(i, i->path); - if (r < 0) return r; break; @@ -2389,54 +2476,16 @@ static int create_item(Item *i) { return r; break; - case CREATE_SYMLINK: { + case CREATE_SYMLINK: r = mkdir_parents_item(i, S_IFLNK); if (r < 0) return r; - mac_selinux_create_file_prepare(i->path, S_IFLNK); - r = RET_NERRNO(symlink(i->argument, i->path)); - mac_selinux_create_file_clear(); + r = create_symlink(i); + if (r < 0) + return r; - if (r < 0) { - _cleanup_free_ char *x = NULL; - - if (r != -EEXIST) - return log_error_errno(r, "symlink(%s, %s) failed: %m", i->argument, i->path); - - r = readlink_malloc(i->path, &x); - if (r < 0 || !streq(i->argument, x)) { - - if (i->append_or_force) { - mac_selinux_create_file_prepare(i->path, S_IFLNK); - r = symlink_atomic(i->argument, i->path); - mac_selinux_create_file_clear(); - - if (IN_SET(r, -EISDIR, -EEXIST, -ENOTEMPTY)) { - r = rm_rf(i->path, REMOVE_ROOT|REMOVE_PHYSICAL); - if (r < 0) - return log_error_errno(r, "rm -fr %s failed: %m", i->path); - - mac_selinux_create_file_prepare(i->path, S_IFLNK); - r = RET_NERRNO(symlink(i->argument, i->path)); - mac_selinux_create_file_clear(); - } - if (r < 0) - return log_error_errno(r, "symlink(%s, %s) failed: %m", i->argument, i->path); - - creation = CREATION_FORCE; - } else { - log_debug("\"%s\" is not a symlink or does not point to the correct path.", i->path); - return 0; - } - } else - creation = CREATION_EXISTING; - } else - - creation = CREATION_NORMAL; - log_debug("%s symlink \"%s\".", creation_mode_verb_to_string(creation), i->path); break; - } case CREATE_BLOCK_DEVICE: case CREATE_CHAR_DEVICE: From 27f6aa0b7112024c1236957abd909071b06869a8 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 15 Sep 2022 19:35:04 +0100 Subject: [PATCH 09/14] tmpfiles: rework empty_directory() to also use chase_symlinks() --- src/tmpfiles/tmpfiles.c | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index 8442cce5bc4..0031b6ee49d 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -1793,26 +1793,33 @@ static int create_subvolume(Item *i, const char *path) { } static int empty_directory(Item *i, const char *path, CreationMode creation) { + _cleanup_close_ int fd = -1; + struct stat st; int r; assert(i); assert(i->type == EMPTY_DIRECTORY); - r = is_dir(path, false); + r = chase_symlinks(path, arg_root, CHASE_SAFE|CHASE_WARN, NULL, &fd); + if (r == -ENOLINK) /* Unsafe symlink: already covered by CHASE_WARN */ + return fd; if (r == -ENOENT) { - /* Option "e" operates only on existing objects. Do not - * print errors about non-existent files or directories */ - log_debug("Skipping missing directory: %s", path); + /* Option "e" operates only on existing objects. Do not print errors about non-existent files + * or directories */ + log_debug_errno(r, "Skipping missing directory: %s", path); return 0; } if (r < 0) - return log_error_errno(r, "is_dir() failed on path %s: %m", path); - if (r == 0) { - log_warning("\"%s\" already exists and is not a directory.", path); + return log_error_errno(r, "Failed to open directory '%s': %m", path); + + if (fstat(fd, &st) < 0) + return log_error_errno(errno, "Failed to fstat(%s): %m", path); + if (!S_ISDIR(st.st_mode)) { + log_warning("'%s' already exists and is not a directory.", path); return 0; } - return path_set_perms(i, path, creation); + return fd_set_perms(i, fd, path, &st, creation); } static int create_device(Item *i, mode_t file_type) { From 4cebd207d1487e1944fd81bbaf63678dade3ed4e Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 15 Sep 2022 19:35:36 +0100 Subject: [PATCH 10/14] tmpfiles: add lines for provisioning ssh keys for root by default MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With this, I can now easily do: systemd-nspawn --load-credential=ssh.authorized_keys.root:/home/lennart/.ssh/authorized_keys --image=… --boot To boot into an image with my SSH key copied in. Yay! --- tmpfiles.d/provision.conf | 5 +++++ units/systemd-tmpfiles-setup.service | 1 + 2 files changed, 6 insertions(+) diff --git a/tmpfiles.d/provision.conf b/tmpfiles.d/provision.conf index a4d7aaf99b2..3c56f42d585 100644 --- a/tmpfiles.d/provision.conf +++ b/tmpfiles.d/provision.conf @@ -15,3 +15,8 @@ f^ /etc/issue.d/50-provision.conf - - - - login.issue # Provision a /etc/hosts file from credentials. f^ /etc/hosts - - - - network.hosts + +# Provision SSH key for root +d /root :0700 root :root - +d /root/.ssh :0700 root :root - +f^ /root/.ssh/authorized_keys :0600 root :root - ssh.authorized_keys.root diff --git a/units/systemd-tmpfiles-setup.service b/units/systemd-tmpfiles-setup.service index 7e11eb94144..a4204655349 100644 --- a/units/systemd-tmpfiles-setup.service +++ b/units/systemd-tmpfiles-setup.service @@ -27,3 +27,4 @@ LoadCredential=tmpfiles.extra LoadCredential=login.motd LoadCredential=login.issue LoadCredential=network.hosts +LoadCredential=ssh.authorized_keys.root From fdc4b8b1e067d7fded000e0056b3c02a9c7ac5a2 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 16 Sep 2022 14:59:05 +0100 Subject: [PATCH 11/14] man: document new : modified for uid/gid/access mode in tmpfiles.d --- man/tmpfiles.d.xml | 41 +++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/man/tmpfiles.d.xml b/man/tmpfiles.d.xml index 15785d1bf2a..4ede01c82d7 100644 --- a/man/tmpfiles.d.xml +++ b/man/tmpfiles.d.xml @@ -531,27 +531,24 @@ w- /proc/sys/vm/swappiness - - - - 10 Mode - The file access mode to use when creating this file or - directory. If omitted or when set to -, the - default is used: 0755 for directories, 0644 for all other file - objects. For z, Z lines, - if omitted or when set to -, the file access - mode will not be modified. This parameter is ignored for - x, r, - R, L, t, - and a lines. + The file access mode to use when creating this file or directory. If omitted or when set to + -, the default is used: 0755 for directories, 0644 for all other file objects. For + z, Z lines, if omitted or when set to -, the + file access mode will not be modified. This parameter is ignored for x, + r, R, L, t, and + a lines. - Optionally, if prefixed with ~, the - access mode is masked based on the already set access bits for - existing file or directories: if the existing file has all - executable bits unset, all executable bits are removed from the - new access mode, too. Similarly, if all read bits are removed - from the old access mode, they will be removed from the new - access mode too, and if all write bits are removed, they will be - removed from the new access mode too. In addition, the - sticky/SUID/SGID bit is removed unless applied to a - directory. This functionality is particularly useful in - conjunction with Z. + Optionally, if prefixed with ~, the access mode is masked based on the already + set access bits for existing file or directories: if the existing file has all executable bits unset, + all executable bits are removed from the new access mode, too. Similarly, if all read bits are removed + from the old access mode, they will be removed from the new access mode too, and if all write bits are + removed, they will be removed from the new access mode too. In addition, the sticky/SUID/SGID bit is + removed unless applied to a directory. This functionality is particularly useful in conjunction with + Z. + + Optionally, if prefixed with :, the configured access mode is only used when + creating new inodes. If the inode the line refers to already exists, its access mode is left in place + unmodified. @@ -571,6 +568,10 @@ w- /proc/sys/vm/swappiness - - - - 10 url="https://systemd.io/UIDS-GIDS/#notes-on-resolvability-of-user-and-group-names">Notes on Resolvability of User and Group Names for more information on requirements on system user/group definitions. + + Optionally, if prefixed with :, the configured user/group information is only + used when creating new inodes. If the inode the line refers to already exists, its user/group is left + in place unmodified. From aebdd3f3d773f46e929c6c85ad574600cc1d9198 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 16 Sep 2022 15:12:14 +0100 Subject: [PATCH 12/14] test: add test case for new ':' uid/gid/access modifier in tmpfiles.d --- test/units/testsuite-22.14.sh | 37 +++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100755 test/units/testsuite-22.14.sh diff --git a/test/units/testsuite-22.14.sh b/test/units/testsuite-22.14.sh new file mode 100755 index 00000000000..2132de7a323 --- /dev/null +++ b/test/units/testsuite-22.14.sh @@ -0,0 +1,37 @@ +#!/bin/bash +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# Tests for the ":" uid/gid/mode modifier +# +set -eux + +rm -rf /tmp/someinode + +systemd-tmpfiles --create - < Date: Fri, 16 Sep 2022 15:58:00 +0100 Subject: [PATCH 13/14] man: add man page decribing well known system credentials --- man/rules/meson.build | 1 + man/systemd.system-credentials.xml | 192 +++++++++++++++++++++++++++++ 2 files changed, 193 insertions(+) create mode 100644 man/systemd.system-credentials.xml diff --git a/man/rules/meson.build b/man/rules/meson.build index a250326a4d3..2925dadc1e1 100644 --- a/man/rules/meson.build +++ b/man/rules/meson.build @@ -1086,6 +1086,7 @@ manpages = [ ['systemd.special', '7', [], ''], ['systemd.swap', '5', [], ''], ['systemd.syntax', '7', [], ''], + ['systemd.system-credentials', '7', [], ''], ['systemd.target', '5', [], ''], ['systemd.time', '7', [], ''], ['systemd.timer', '5', [], ''], diff --git a/man/systemd.system-credentials.xml b/man/systemd.system-credentials.xml new file mode 100644 index 00000000000..3ec7ae8d4f4 --- /dev/null +++ b/man/systemd.system-credentials.xml @@ -0,0 +1,192 @@ + + + + + + + + systemd.system-credentials + systemd + + + + systemd.system-credentials + 7 + + + + systemd.system-credentials + System Credentials + + + + Description + + System and Service Credentials are data objects + that may be passed into booted systems or system services as they are invoked. They can be acquired from + various external sources, and propagated into the system and from there into system services. Credentials + may optionally be encrypted with a machine-specific key and/or locked to the local TPM2 device, and are + only decrypted when the consuming service is invoked. + + System credentials may be used to provision and configure various aspects of the system. Depending + on the consuming component credentials are only used on initial invocations or are needed for all + invocations. + + Credentials may be used for any kind of data, binary or text, and may carry passwords, secrets, + certificates, cryptographic key material, identity information, configuration, and more. + + + + Well known system credentials + + + + firstboot.keymap + + The console key mapping to set (e.g. de). Read by + systemd-firstboot1, + and only honoured if no console keymap has been configured before. + + + + + firstboot.locale + firstboot.locale-message + + The system locale to set (e.g. de_DE.UTF-8). Read by + systemd-firstboot1, + and only honoured if no locale has been configured before. firstboot.locale sets + LANG, while firstboot.locale-message sets + LC_MESSAGES. + + + + + firstboot.timezone + + The system timezone to set (e.g. Europe/Berlin). Read by + systemd-firstboot1, + and only honoured if no system timezone has been configured before. + + + + + login.issue + + The data of this credential is written to + /etc/issue.d/50-provision.conf, if the file doesn't exist + yet. agetty8 + reads this file and shows its contents at the login prompt of terminal logins. See issue5 for + details. + + Consumed by /usr/lib/tmpfiles.d/provision.conf, see + tmpfiles.d5. + + + + + login.motd + + The data of this credential is written to /etc/motd.d/50-provision.conf, + if the file doesn't exist + yet. pam_motd8 + reads this file and shows its contents as "message of the day" during terminal logins. See + motd5 for + details. + + Consumed by /usr/lib/tmpfiles.d/provision.conf, see + tmpfiles.d5. + + + + + network.hosts + + The data of this credential is written to /etc/hosts, if the file + doesn't exist yet. See hosts5 for + details. + + Consumed by /usr/lib/tmpfiles.d/provision.conf, see + tmpfiles.d5. + + + + + passwd.hashed-password.root + passwd.plaintext-password.root + + May contain the password (either in UNIX hashed format, or in plaintext) for the root users. + Read by both + systemd-firstboot1 + and + systemd-sysusers1, + and only honoured if no root password has been configured before. + + + + + passwd.shell.root + + The path to the shell program (e.g. /bin/bash) for the root user. Read by + both + systemd-firstboot1 + and + systemd-sysusers1, + and only honoured if no root shell has been configured before. + + + + + ssh.authorized_keys.root + + The data of this credential is written to /root/.ssh/authorized_keys, if + the file doesn't exist yet. This allows provisioning SSH access for the system's root user. + + Consumed by /usr/lib/tmpfiles.d/provision.conf, see + tmpfiles.d5. + + + + + sysusers.extra + + Additional + sysusers.d5 + lines to process during boot. + + + + + sysctl.extra + + Additional + sysctl.d5 lines + to process during boot. + + + + + tmpfiles.extra + + Additional + tmpfiles.d5 + lines to process during boot. + + + + + + + + See Also + + systemd1, + kernel-command-line7 + + + + From d1666bde9cf6935352aef01d7147b407dd4f1822 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 16 Sep 2022 16:33:54 +0100 Subject: [PATCH 14/14] update TODO (let's also merge all TODO items about adding creds support to various tools into one item) --- TODO | 96 +++++++++++++++++++++--------------------------------------- 1 file changed, 34 insertions(+), 62 deletions(-) diff --git a/TODO b/TODO index 2bfbbdfa3ba..d6437130bfb 100644 --- a/TODO +++ b/TODO @@ -119,11 +119,9 @@ Deprecations and removals: Features: -* systemd-measure: only require private key to be set when signing. iiuc we can - generate the public key from it anyway. - -* automatically propagate LUKS password credential into cryptsetup from host, - so that one can unlock LUKS via VM hypervisor supplied password. +* automatically propagate LUKS password credential into cryptsetup from host + (i.e. SMBIOS type #11, …), so that one can unlock LUKS via VM hypervisor + supplied password. * add ability to path_is_valid() to classify paths that refer to a dir from those which may refer to anything, and use that in various places to filter @@ -157,9 +155,6 @@ Features: systemd.import_encrypted_creds=foobar.waldo,tmpfiles.extra to protect locked down kernels from credentials generated on the host with a weak kernel -* tmpfiles: currently if we fail to create an inode, we stat it first, and only - then O_PATH open it. Reverse that. - * Add support for extra verity configuration options to systemd-repart (FEC, hash type, etc) @@ -209,8 +204,6 @@ Features: * sd-bus: document that sd_bus_process() only returns messages that non of the filters/handlers installed on the connection took possession of. -* sd-device: add an API for opening a child device, given a device object - * sd-device: add an API for acquiring list of child devices, given a device objects (i.e. all child dirents that dirs or symlinks to dirs) @@ -227,9 +220,6 @@ Features: portabled/… up to udev to watch block devices coming up with the flags set, and use it. -* portabled: read a credential "portable.extra" or so, that takes a list of - file system paths to enable on start. - * sd-boot should look for information what to boot in SMBIOS, too, so that VM managers can tell sd-boot what to boot into and suchlike @@ -268,27 +258,34 @@ Features: this to remove auxiliary files, and never remove them explicitly. Benefit: resources such as initrds/kernels/dtb can be shared between entries. -* networkd/udevd: add a way to define additional .link, .network, .netdev files - via the credentials logic. - -* fstab-generator: allow defining additional fstab-like mounts via - credentials (similar: crypttab-generator, verity-generator, - integrity-generator) - -* getty-generator: allow defining additional getty instances via a credential - -* run-generator: allow defining additional commands to run via a credential - -* resolved: allow defining additional /etc/hosts entries via a credential (it - might make sense to then synthesize a new combined /etc/hosts file in /run - and bind mount it on /etc/hosts for other clients that want to read it. - Similar, allow picking up DNS server IP addresses from credential. - -* repart: allow defining additional partitions via credential - -* tmpfiles: add snippet that provisions /root/.ssh/authorized_keys from credential - -* timesyncd: pick NTP server info from credential +* Process credentials in: + • networkd/udevd: add a way to define additional .link, .network, .netdev files + via the credentials logic. + • fstab-generator: allow defining additional fstab-like mounts via + credentials (similar: crypttab-generator, verity-generator, + integrity-generator) + • getty-generator: allow defining additional getty instances via a credential + • run-generator: allow defining additional commands to run via a credential + • resolved: allow defining additional /etc/hosts entries via a credential (it + might make sense to then synthesize a new combined /etc/hosts file in /run + and bind mount it on /etc/hosts for other clients that want to read it. + Similar, allow picking up DNS server IP addresses from credential. + • repart: allow defining additional partitions via credential + • timesyncd: pick NTP server info from credential + • portabled: read a credential "portable.extra" or so, that takes a list of + file system paths to enable on start. + • make systemd-fstab-generator look for a system credential encoding root= or + usr= + • systemd-homed: when initializing, look for a credential + systemd.homed.register or so with JSON user records to automatically + register if not registered yet. Usecase: deploy a system, and add an + account one can directly log into. + • initialize machine ID from systemd credential picked up from the ESP via + sd-stub, so that machine ID is stable even on systems where unified kernels + are used, and hence kernel cmdline cannot be modified locally + • in gpt-auto-generator: check partition uuids against such uuids supplied via + sd-stub credentials. That way, we can support parallel OS installations with + pre-built kernels. * define a JSON format for units, separating out unit definitions from unit runtime state. Then, expose it: @@ -317,9 +314,6 @@ Features: UEFI firmware (for example, ovmf supports that via qemu cmdline option), and use it to load stuff from the ESP. -* make tmpfiles read lines from creds, so that we can provision SSH host keys - via creds. Similar: sysusers, sysctl, homed - * mount /var/ from initrd, so that we can apply sysext and stuff before the initrd transition. Specifically: 1. There should be a var= kernel cmdline option, matching root= and usr= @@ -352,9 +346,6 @@ Features: comes from, but we can still derive that from the stdin socket its output came from. We apparently don't do that right now. -* make systemd-fstab-generator look for a system credential encoding root= or - usr= - * add ability to set hostname with suffix derived from machine id at boot * ask dracut to generate usr= on the kernel cmdline so that we don't need to @@ -384,10 +375,6 @@ Features: inode first, then connect to /proc/self/fd/XYZ. When binding, create symlink to target dir in /tmp, and bind through it. -* systemd-homed: when initializing, look for a credential sysemd.homed.register - or so with JSON user records to automatically register if not registered yet. - Usecase: deploy a system, and add an account one can directly log into. - * add a proper concept of a "developer" mode, i.e. where cryptographic protections of the root OS are weakened after interactive confirmation, to allow hackers to allow their own stuff. idea: allow entering developer mode @@ -532,14 +519,6 @@ Features: the real kernel. benefit: downloading these stubs would be tiny and quick, hence cheap for enumeration. -* initialize machine ID from systemd credential picked up from the ESP via - sd-stub, so that machine ID is stable even on systems where unified kernels - are used, and hence kernel cmdline cannot be modified locally - -* in gpt-auto-generator: check partition uuids against such uuids supplied via - sd-stub credentials. That way, we can support parallel OS installations with - pre-built kernels. - * sysext: measure all activated sysext into a TPM PCR * maybe add a "syscfg" concept, that is almost entirely identical to "sysext", @@ -615,7 +594,7 @@ Features: * systemd-dissect: show GPT disk UUID in output -* Enable RestricFileSystems= for all our long-running services (similar: +* Enable RestrictFileSystems= for all our long-running services (similar: RestrictNetworkInterfaces=) * Add systemd-analyze security checks for RestrictFileSystems= and @@ -635,9 +614,6 @@ Features: such as masking out /usr/lib/ or so. We should probably refuse if existing inodes are replaced by other types of inodes or so. -* sysext: ensure one can build a sysext that can safely apply to *any* system - (because it contains only static go binaries in /opt/ or so) - * userdb: when synthesizing NSS records, pick "best" password from defined passwords, not just the first. i.e. if there are multiple defined, prefer unlocked over locked and prefer non-empty over empty. @@ -1261,7 +1237,8 @@ Features: "systemd-gdb" for attaching to the start-up of any system service in its natural habitat. -* gpt-auto logic: support encrypted swap, add kernel cmdline option to force it, and honour a gpt bit about it, plus maybe a configuration file +* gpt-auto logic: support encrypted swap, add kernel cmdline option to force + it, and honour a gpt bit about it, plus maybe a configuration file * add a percentage syntax for TimeoutStopSec=, e.g. TimeoutStopSec=150%, and then use that for the setting used in user@.service. It should be understood @@ -1600,11 +1577,6 @@ Features: * mount: turn dependency information from /proc/self/mountinfo into dependency information between systemd units. -* firstboot: allow provisioning of /etc/hosts entries, so that we can via the - credentials logic insert host name to resolve into containers/hosts. Usecase: - fork a container, and make it ping some specific address which is defined by - the host on invocation - * systemd-firstboot: make sure to always use chase_symlinks() before reading/writing files