From fb95c890cf5116e698347c6a7bb3daeeb2d28cf9 Mon Sep 17 00:00:00 2001 From: George McCollister Date: Thu, 21 Feb 2019 18:04:37 -0600 Subject: [PATCH] Make tmpfiles safe In addition to backporting the changesets in #8822, this also backports e04fc13 (test: add tests for systemd-tmpfiles), as well as empty_to_root() from v239. Origin: upstream, https://github.com/systemd/systemd/pull/8822/commits Bug: https://github.com/systemd/systemd/issues/7986 Patch from: systemd_237-3ubuntu10.13.debian CVE-2018-6954_2.patch https://usn.ubuntu.com/3816-1/ states that CVE-2018-6954 doesn't affect Ubuntu 18.10 which uses the same version of systemd as thud (239). CVE: CVE-2018-6954 Upstream-Status: Backport Signed-off-by: George McCollister --- src/basic/btrfs-util.c | 26 +- src/basic/btrfs-util.h | 1 + src/basic/fileio.c | 5 +- src/basic/fs-util.c | 27 +- src/basic/fs-util.h | 2 + src/basic/label.h | 1 + src/basic/mkdir-label.c | 17 + src/basic/mkdir.c | 6 + src/basic/mkdir.h | 1 + src/basic/path-util.c | 5 +- src/basic/path-util.h | 4 + src/basic/selinux-util.c | 84 +++-- src/basic/selinux-util.h | 1 + src/basic/smack-util.c | 119 +++++-- src/basic/smack-util.h | 1 + src/basic/stat-util.c | 11 + src/basic/stat-util.h | 1 + src/test/test-fs-util.c | 25 ++ src/tmpfiles/tmpfiles.c | 902 ++++++++++++++++++++++++++++++++--------------- 19 files changed, 882 insertions(+), 357 deletions(-) diff --git a/src/basic/btrfs-util.c b/src/basic/btrfs-util.c index 19d385ab7c..26b088f52b 100644 --- a/src/basic/btrfs-util.c +++ b/src/basic/btrfs-util.c @@ -150,8 +150,25 @@ int btrfs_is_subvol(const char *path) { return btrfs_is_subvol_fd(fd); } -int btrfs_subvol_make(const char *path) { +int btrfs_subvol_make_fd(int fd, const char *subvolume) { struct btrfs_ioctl_vol_args args = {}; + int r; + + assert(subvolume); + + r = validate_subvolume_name(subvolume); + if (r < 0) + return r; + + strncpy(args.name, subvolume, sizeof(args.name)-1); + + if (ioctl(fd, BTRFS_IOC_SUBVOL_CREATE, &args) < 0) + return -errno; + + return 0; +} + +int btrfs_subvol_make(const char *path) { _cleanup_close_ int fd = -1; const char *subvolume; int r; @@ -166,12 +183,7 @@ int btrfs_subvol_make(const char *path) { if (fd < 0) return fd; - strncpy(args.name, subvolume, sizeof(args.name)-1); - - if (ioctl(fd, BTRFS_IOC_SUBVOL_CREATE, &args) < 0) - return -errno; - - return 0; + return btrfs_subvol_make_fd(fd, subvolume); } int btrfs_subvol_set_read_only_fd(int fd, bool b) { diff --git a/src/basic/btrfs-util.h b/src/basic/btrfs-util.h index 952b3c26da..e92687bc57 100644 --- a/src/basic/btrfs-util.h +++ b/src/basic/btrfs-util.h @@ -84,6 +84,7 @@ int btrfs_resize_loopback_fd(int fd, uint64_t size, bool grow_only); int btrfs_resize_loopback(const char *path, uint64_t size, bool grow_only); int btrfs_subvol_make(const char *path); +int btrfs_subvol_make_fd(int fd, const char *subvolume); int btrfs_subvol_snapshot_fd(int old_fd, const char *new_path, BtrfsSnapshotFlags flags); int btrfs_subvol_snapshot(const char *old_path, const char *new_path, BtrfsSnapshotFlags flags); diff --git a/src/basic/fileio.c b/src/basic/fileio.c index 26d6174664..1c7e23332f 100644 --- a/src/basic/fileio.c +++ b/src/basic/fileio.c @@ -1304,7 +1304,10 @@ int tempfn_random_child(const char *p, const char *extra, char **ret) { if (!t) return -ENOMEM; - x = stpcpy(stpcpy(stpcpy(t, p), "/.#"), extra); + if (isempty(p)) + x = stpcpy(stpcpy(t, ".#"), extra); + else + x = stpcpy(stpcpy(stpcpy(t, p), "/.#"), extra); u = random_u64(); for (i = 0; i < 16; i++) { diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c index a8e50d4c78..c96a8813ea 100644 --- a/src/basic/fs-util.c +++ b/src/basic/fs-util.c @@ -465,6 +465,31 @@ int mkfifo_atomic(const char *path, mode_t mode) { 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) + return r; + + if (mkfifoat(dirfd, t, mode) < 0) + return -errno; + + if (renameat(dirfd, t, dirfd, path) < 0) { + unlink_noerrno(t); + return -errno; + } + + return 0; +} + int get_files_in_directory(const char *path, char ***list) { _cleanup_closedir_ DIR *d = NULL; struct dirent *de; @@ -808,7 +833,7 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags, fd_is_fs_type(child, AUTOFS_SUPER_MAGIC) > 0) return -EREMOTE; - if (S_ISLNK(st.st_mode)) { + if (S_ISLNK(st.st_mode) && !((flags & CHASE_NOFOLLOW) && isempty(todo))) { char *joined; _cleanup_free_ char *destination = NULL; diff --git a/src/basic/fs-util.h b/src/basic/fs-util.h index 9c4b02eccd..121345e74d 100644 --- a/src/basic/fs-util.h +++ b/src/basic/fs-util.h @@ -80,6 +80,7 @@ int symlink_idempotent(const char *from, const char *to); int symlink_atomic(const char *from, const char *to); 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); int get_files_in_directory(const char *path, char ***list); @@ -106,6 +107,7 @@ enum { CHASE_NO_AUTOFS = 1U << 2, /* If set, return -EREMOTE if autofs mount point found */ CHASE_SAFE = 1U << 3, /* If set, return EPERM if we ever traverse from unprivileged to privileged files or directories */ CHASE_OPEN = 1U << 4, /* If set, return an O_PATH object to the final component */ + CHASE_NOFOLLOW = 1U << 7, /* Only valid with CHASE_OPEN: when the path's right-most component refers to symlink return O_PATH fd of the symlink, rather than following it. */ }; int chase_symlinks(const char *path_with_prefix, const char *root, unsigned flags, char **ret); diff --git a/src/basic/label.h b/src/basic/label.h index d73dacec4f..3ecfed72c6 100644 --- a/src/basic/label.h +++ b/src/basic/label.h @@ -26,6 +26,7 @@ int label_fix(const char *path, bool ignore_enoent, bool ignore_erofs); int mkdir_label(const char *path, mode_t mode); +int mkdirat_label(int dirfd, const char *path, mode_t mode); int symlink_label(const char *old_path, const char *new_path); int btrfs_subvol_make_label(const char *path); diff --git a/src/basic/mkdir-label.c b/src/basic/mkdir-label.c index 6f3a46f467..3c1a227bfa 100644 --- a/src/basic/mkdir-label.c +++ b/src/basic/mkdir-label.c @@ -47,6 +47,23 @@ int mkdir_label(const char *path, mode_t mode) { return mac_smack_fix(path, false, false); } +int mkdirat_label(int dirfd, const char *path, mode_t mode) { + int r; + + assert(path); + + r = mac_selinux_create_file_prepare_at(dirfd, path, S_IFDIR); + if (r < 0) + return r; + + r = mkdirat_errno_wrapper(dirfd, path, mode); + mac_selinux_create_file_clear(); + if (r < 0) + return r; + + return mac_smack_fix_at(dirfd, path, false, false); +} + int mkdir_safe_label(const char *path, mode_t mode, uid_t uid, gid_t gid, bool follow_symlink) { return mkdir_safe_internal(path, mode, uid, gid, follow_symlink, mkdir_label); } diff --git a/src/basic/mkdir.c b/src/basic/mkdir.c index d51518a5a7..418945ad4a 100644 --- a/src/basic/mkdir.c +++ b/src/basic/mkdir.c @@ -77,6 +77,12 @@ int mkdir_errno_wrapper(const char *pathname, mode_t mode) { return 0; } +int mkdirat_errno_wrapper(int dirfd, const char *pathname, mode_t mode) { + if (mkdirat(dirfd, pathname, mode) < 0) + return -errno; + return 0; +} + int mkdir_safe(const char *path, mode_t mode, uid_t uid, gid_t gid, bool follow_symlink) { return mkdir_safe_internal(path, mode, uid, gid, follow_symlink, mkdir_errno_wrapper); } diff --git a/src/basic/mkdir.h b/src/basic/mkdir.h index d6c2d579a3..3ec6f3ed2d 100644 --- a/src/basic/mkdir.h +++ b/src/basic/mkdir.h @@ -24,6 +24,7 @@ #include int mkdir_errno_wrapper(const char *pathname, mode_t mode); +int mkdirat_errno_wrapper(int dirfd, const char *pathname, mode_t mode); int mkdir_safe(const char *path, mode_t mode, uid_t uid, gid_t gid, bool follow_symlink); int mkdir_parents(const char *path, mode_t mode); int mkdir_p(const char *path, mode_t mode); diff --git a/src/basic/path-util.c b/src/basic/path-util.c index df94629385..84404f7ee1 100644 --- a/src/basic/path-util.c +++ b/src/basic/path-util.c @@ -127,10 +127,7 @@ int path_make_absolute_cwd(const char *p, char **ret) { if (r < 0) return r; - if (endswith(cwd, "/")) - c = strjoin(cwd, p); - else - c = strjoin(cwd, "/", p); + c = path_join(NULL, cwd, p); } if (!c) return -ENOMEM; diff --git a/src/basic/path-util.h b/src/basic/path-util.h index 89c285e076..1094baca12 100644 --- a/src/basic/path-util.h +++ b/src/basic/path-util.h @@ -156,3 +156,7 @@ static inline const char *skip_dev_prefix(const char *p) { return e ?: p; } +static inline const char *empty_to_root(const char *path) { + return isempty(path) ? "/" : path; +} + diff --git a/src/basic/selinux-util.c b/src/basic/selinux-util.c index 0c6e99b1d7..bdef7d148b 100644 --- a/src/basic/selinux-util.c +++ b/src/basic/selinux-util.c @@ -34,6 +34,7 @@ #endif #include "alloc-util.h" +#include "fd-util.h" #include "log.h" #include "macro.h" #include "path-util.h" @@ -311,48 +312,89 @@ char* mac_selinux_free(char *label) { return NULL; } -int mac_selinux_create_file_prepare(const char *path, mode_t mode) { - #if HAVE_SELINUX +static int selinux_create_file_prepare_abspath(const char *abspath, mode_t mode) { _cleanup_freecon_ char *filecon = NULL; + _cleanup_free_ char *path = NULL; int r; - assert(path); - - if (!label_hnd) - return 0; - - if (path_is_absolute(path)) - r = selabel_lookup_raw(label_hnd, &filecon, path, mode); - else { - _cleanup_free_ char *newpath = NULL; - - r = path_make_absolute_cwd(path, &newpath); - if (r < 0) - return r; - - r = selabel_lookup_raw(label_hnd, &filecon, newpath, mode); - } + assert(abspath); + assert(path_is_absolute(abspath)); + r = selabel_lookup_raw(label_hnd, &filecon, abspath, mode); if (r < 0) { /* No context specified by the policy? Proceed without setting it. */ if (errno == ENOENT) return 0; - log_enforcing("Failed to determine SELinux security context for %s: %m", path); + log_enforcing("Failed to determine SELinux security context for %s: %m", abspath); } else { if (setfscreatecon_raw(filecon) >= 0) return 0; /* Success! */ - log_enforcing("Failed to set SELinux security context %s for %s: %m", filecon, path); + log_enforcing("Failed to set SELinux security context %s for %s: %m", filecon, abspath); } if (security_getenforce() > 0) return -errno; -#endif return 0; } +#endif + +int mac_selinux_create_file_prepare_at(int dirfd, const char *path, mode_t mode) { + int r = 0; + +#if HAVE_SELINUX + _cleanup_free_ char *abspath = NULL; + _cleanup_close_ int fd = -1; + + assert(path); + + if (!label_hnd) + return 0; + + if (!path_is_absolute(path)) { + _cleanup_free_ char *p = NULL; + + if (dirfd == AT_FDCWD) + r = safe_getcwd(&p); + else + r = fd_get_path(dirfd, &p); + if (r < 0) + return r; + + abspath = path_join(NULL, p, path); + if (!abspath) + return -ENOMEM; + + path = abspath; + } + + r = selinux_create_file_prepare_abspath(path, mode); +#endif + return r; +} + +int mac_selinux_create_file_prepare(const char *path, mode_t mode) { + int r = 0; + +#if HAVE_SELINUX + _cleanup_free_ char *abspath = NULL; + + assert(path); + + if (!label_hnd) + return 0; + + r = path_make_absolute_cwd(path, &abspath); + if (r < 0) + return r; + + r = selinux_create_file_prepare_abspath(abspath, mode); +#endif + return r; +} void mac_selinux_create_file_clear(void) { diff --git a/src/basic/selinux-util.h b/src/basic/selinux-util.h index 9780dca81e..84a8bf9729 100644 --- a/src/basic/selinux-util.h +++ b/src/basic/selinux-util.h @@ -41,6 +41,7 @@ int mac_selinux_get_child_mls_label(int socket_fd, const char *exe, const char * char* mac_selinux_free(char *label); int mac_selinux_create_file_prepare(const char *path, mode_t mode); +int mac_selinux_create_file_prepare_at(int dirfd, const char *path, mode_t mode); void mac_selinux_create_file_clear(void); int mac_selinux_create_socket_prepare(const char *label); diff --git a/src/basic/smack-util.c b/src/basic/smack-util.c index f0018f013f..ea0af3e45f 100644 --- a/src/basic/smack-util.c +++ b/src/basic/smack-util.c @@ -21,18 +21,21 @@ ***/ #include +#include #include #include #include #include #include "alloc-util.h" +#include "fd-util.h" #include "fileio.h" #include "log.h" #include "macro.h" #include "path-util.h" #include "process-util.h" #include "smack-util.h" +#include "stdio-util.h" #include "string-table.h" #include "xattr-util.h" @@ -134,59 +137,111 @@ int mac_smack_apply_pid(pid_t pid, const char *label) { return r; } -int mac_smack_fix(const char *path, bool ignore_enoent, bool ignore_erofs) { +static int smack_fix_fd(int fd , const char *abspath, bool ignore_erofs) { + char procfs_path[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int)]; + const char *label; struct stat st; int r; - assert(path); + /* The caller should have done the sanity checks. */ + assert(abspath); + assert(path_is_absolute(abspath)); - if (!mac_smack_use()) + /* Path must be in /dev. */ + if (!path_startswith(abspath, "/dev")) return 0; + if (fstat(fd, &st) < 0) + return -errno; + /* - * Path must be in /dev and must exist + * Label directories and character devices "*". + * Label symlinks "_". + * Don't change anything else. */ - if (!path_startswith(path, "/dev")) + + if (S_ISDIR(st.st_mode)) + label = SMACK_STAR_LABEL; + else if (S_ISLNK(st.st_mode)) + label = SMACK_FLOOR_LABEL; + else if (S_ISCHR(st.st_mode)) + label = SMACK_STAR_LABEL; + else return 0; - r = lstat(path, &st); - if (r >= 0) { - const char *label; - - /* - * Label directories and character devices "*". - * Label symlinks "_". - * Don't change anything else. - */ - - if (S_ISDIR(st.st_mode)) - label = SMACK_STAR_LABEL; - else if (S_ISLNK(st.st_mode)) - label = SMACK_FLOOR_LABEL; - else if (S_ISCHR(st.st_mode)) - label = SMACK_STAR_LABEL; - else - return 0; + xsprintf(procfs_path, "/proc/self/fd/%i", fd); + if (setxattr(procfs_path, "security.SMACK64", label, strlen(label), 0) < 0) { + _cleanup_free_ char *old_label = NULL; - r = lsetxattr(path, "security.SMACK64", label, strlen(label), 0); + r = -errno; /* If the FS doesn't support labels, then exit without warning */ - if (r < 0 && errno == EOPNOTSUPP) + if (r == -EOPNOTSUPP) + return 0; + + /* It the FS is read-only and we were told to ignore failures caused by that, suppress error */ + if (r == -EROFS && ignore_erofs) + return 0; + + /* If the old label is identical to the new one, suppress any kind of error */ + if (getxattr_malloc(procfs_path, "security.SMACK64", &old_label, false) >= 0 && + streq(old_label, label)) return 0; + + return log_debug_errno(r, "Unable to fix SMACK label of %s: %m", abspath); } - if (r < 0) { - /* Ignore ENOENT in some cases */ + return r; +} + +int mac_smack_fix_at(int dirfd, const char *path, bool ignore_enoent, bool ignore_erofs) { + _cleanup_free_ char *p = NULL; + _cleanup_close_ int fd = -1; + int r; + + assert(path); + + if (!mac_smack_use()) + return 0; + + fd = openat(dirfd, path, O_NOFOLLOW|O_CLOEXEC|O_PATH); + if (fd < 0) { if (ignore_enoent && errno == ENOENT) return 0; - if (ignore_erofs && errno == EROFS) + return -errno; + } + + r = fd_get_path(fd, &p); + if (r < 0) + return r; + + return smack_fix_fd(fd, p, ignore_erofs); +} + +int mac_smack_fix(const char *path, bool ignore_enoent, bool ignore_erofs) { + _cleanup_free_ char *abspath = NULL; + _cleanup_close_ int fd = -1; + int r; + + assert(path); + + if (!mac_smack_use()) + return 0; + + r = path_make_absolute_cwd(path, &abspath); + if (r < 0) + return r; + + fd = open(abspath, O_NOFOLLOW|O_CLOEXEC|O_PATH); + if (fd < 0) { + if (ignore_enoent && errno == ENOENT) return 0; - r = log_debug_errno(errno, "Unable to fix SMACK label of %s: %m", path); + return -errno; } - return r; + return smack_fix_fd(fd, abspath, ignore_erofs); } int mac_smack_copy(const char *dest, const char *src) { @@ -236,6 +291,10 @@ int mac_smack_fix(const char *path, bool ignore_enoent, bool ignore_erofs) { return 0; } +int mac_smack_fix_at(int dirfd, const char *path, bool ignore_enoent, bool ignore_erofs) { + return 0; +} + int mac_smack_copy(const char *dest, const char *src) { return 0; } diff --git a/src/basic/smack-util.h b/src/basic/smack-util.h index e4d46d7736..0c214bbbc0 100644 --- a/src/basic/smack-util.h +++ b/src/basic/smack-util.h @@ -44,6 +44,7 @@ typedef enum SmackAttr { bool mac_smack_use(void); int mac_smack_fix(const char *path, bool ignore_enoent, bool ignore_erofs); +int mac_smack_fix_at(int dirfd, const char *path, bool ignore_enoent, bool ignore_erofs); const char* smack_attr_to_string(SmackAttr i) _const_; SmackAttr smack_attr_from_string(const char *s) _pure_; diff --git a/src/basic/stat-util.c b/src/basic/stat-util.c index 3a54103f1b..801889ae5b 100644 --- a/src/basic/stat-util.c +++ b/src/basic/stat-util.c @@ -63,6 +63,17 @@ int is_dir(const char* path, bool follow) { return !!S_ISDIR(st.st_mode); } +int is_dir_fd(int fd) { + struct stat st; + int r; + + r = fstat(fd, &st); + if (r < 0) + return -errno; + + return !!S_ISDIR(st.st_mode); +} + int is_device_node(const char *path) { struct stat info; diff --git a/src/basic/stat-util.h b/src/basic/stat-util.h index d8d3c20496..7ea68abfa3 100644 --- a/src/basic/stat-util.h +++ b/src/basic/stat-util.h @@ -31,6 +31,7 @@ int is_symlink(const char *path); int is_dir(const char *path, bool follow); +int is_dir_fd(int fd); int is_device_node(const char *path); int dir_is_empty(const char *path); diff --git a/src/test/test-fs-util.c b/src/test/test-fs-util.c index 9f3a500080..a76d6d0f8b 100644 --- a/src/test/test-fs-util.c +++ b/src/test/test-fs-util.c @@ -40,6 +40,7 @@ static void test_chase_symlinks(void) { _cleanup_free_ char *result = NULL; char temp[] = "/tmp/test-chase.XXXXXX"; const char *top, *p, *pslash, *q, *qslash; + struct stat st; int r, pfd; assert_se(mkdtemp(temp)); @@ -288,6 +289,30 @@ static void test_chase_symlinks(void) { assert_se(sd_id128_equal(a, b)); } + /* Test CHASE_NOFOLLOW */ + + p = strjoina(temp, "/target"); + q = strjoina(temp, "/symlink"); + assert_se(symlink(p, q) >= 0); + pfd = chase_symlinks(q, NULL, CHASE_OPEN|CHASE_NOFOLLOW, &result); + assert_se(pfd > 0); + assert_se(path_equal(result, q)); + assert_se(fstat(pfd, &st) >= 0); + assert_se(S_ISLNK(st.st_mode)); + result = mfree(result); + + /* s1 -> s2 -> nonexistent */ + q = strjoina(temp, "/s1"); + assert_se(symlink("s2", q) >= 0); + p = strjoina(temp, "/s2"); + assert_se(symlink("nonexistent", p) >= 0); + pfd = chase_symlinks(q, NULL, CHASE_OPEN|CHASE_NOFOLLOW, &result); + assert_se(pfd > 0); + assert_se(path_equal(result, q)); + assert_se(fstat(pfd, &st) >= 0); + assert_se(S_ISLNK(st.st_mode)); + result = mfree(result); + assert_se(rm_rf(temp, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); } diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index 613d418eb3..d59ccbaa39 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -794,6 +794,7 @@ static bool hardlink_vulnerable(struct stat *st) { static int fd_set_perms(Item *i, int fd, const struct stat *st) { _cleanup_free_ char *path = NULL; + struct stat stbuf; int r; assert(i); @@ -806,6 +807,12 @@ static int fd_set_perms(Item *i, int fd, const struct stat *st) { if (!i->mode_set && !i->uid_set && !i->gid_set) goto shortcut; + if (!st) { + if (fstat(fd, &stbuf) < 0) + return log_error_errno(errno, "fstat(%s) failed: %m", path); + st = &stbuf; + } + if (hardlink_vulnerable(st)) { log_error("Refusing to set permissions on hardlinked file %s while the fs.protected_hardlinks sysctl is turned off.", path); return -EPERM; @@ -863,32 +870,62 @@ shortcut: return label_fix(path, false, false); } -static int path_set_perms(Item *i, const char *path) { - _cleanup_close_ int fd = -1; - struct stat st; +static int path_open_parent_safe(const char *path) { + _cleanup_free_ char *dn = NULL; + int fd; - assert(i); - assert(path); + if (path_equal(path, "/") || !path_is_normalized(path)) { + log_error("Failed to open parent of '%s': invalid path.", path); + return -EINVAL; + } - fd = open(path, O_NOFOLLOW|O_CLOEXEC|O_PATH); - if (fd < 0) { - int level = LOG_ERR, r = -errno; + dn = dirname_malloc(path); + if (!dn) + return log_oom(); - /* Option "e" operates only on existing objects. Do not - * print errors about non-existent files or directories */ - if (i->type == EMPTY_DIRECTORY && errno == ENOENT) { - level = LOG_DEBUG; - r = 0; - } + fd = chase_symlinks(dn, NULL, CHASE_OPEN|CHASE_SAFE, NULL); + if (fd == -EPERM) + return log_error_errno(fd, "Unsafe symlinks encountered in %s, refusing.", path); + if (fd < 0) + return log_error_errno(fd, "Failed to validate path %s: %m", path); - log_full_errno(level, errno, "Adjusting owner and mode for %s failed: %m", path); - return r; + return fd; +} + +static int path_open_safe(const char *path) { + int fd; + + /* path_open_safe() returns a file descriptor opened with O_PATH after + * verifying that the path doesn't contain unsafe transitions, except + * for its final component as the function does not follow symlink. */ + + assert(path); + + if (!path_is_normalized(path)) { + log_error("Failed to open invalid path '%s'.", path); + return -EINVAL; } - if (fstat(fd, &st) < 0) - return log_error_errno(errno, "Failed to fstat() file %s: %m", path); + fd = chase_symlinks(path, NULL, CHASE_OPEN|CHASE_SAFE|CHASE_NOFOLLOW, NULL); + if (fd == -EPERM) + return log_error_errno(fd, "Unsafe symlinks encountered in %s, refusing.", path); + if (fd < 0) + return log_error_errno(fd, "Failed to validate path %s: %m", path); + + return fd; +} + +static int path_set_perms(Item *i, const char *path) { + _cleanup_close_ int fd = -1; + + assert(i); + assert(path); - return fd_set_perms(i, fd, &st); + fd = path_open_safe(path); + if (fd < 0) + return fd; + + return fd_set_perms(i, fd, NULL); } static int parse_xattrs_from_arg(Item *i) { @@ -959,9 +996,9 @@ static int path_set_xattrs(Item *i, const char *path) { assert(i); assert(path); - fd = open(path, O_CLOEXEC|O_NOFOLLOW|O_PATH); + fd = path_open_safe(path); if (fd < 0) - return log_error_errno(errno, "Cannot open '%s': %m", path); + return fd; return fd_set_xattrs(i, fd, NULL); } @@ -1036,15 +1073,21 @@ static int fd_set_acls(Item *item, int fd, const struct stat *st) { #if HAVE_ACL char procfs_path[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(int)]; _cleanup_free_ char *path = NULL; + struct stat stbuf; assert(item); assert(fd); - assert(st); r = fd_get_path(fd, &path); if (r < 0) return r; + if (!st) { + if (fstat(fd, &stbuf) < 0) + return log_error_errno(errno, "fstat(%s) failed: %m", path); + st = &stbuf; + } + if (hardlink_vulnerable(st)) { log_error("Refusing to set ACLs on hardlinked file %s while the fs.protected_hardlinks sysctl is turned off.", path); return -EPERM; @@ -1079,19 +1122,15 @@ static int path_set_acls(Item *item, const char *path) { int r = 0; #ifdef HAVE_ACL _cleanup_close_ int fd = -1; - struct stat st; assert(item); assert(path); - fd = open(path, O_NOFOLLOW|O_CLOEXEC|O_PATH); + fd = path_open_safe(path); if (fd < 0) - return log_error_errno(errno, "Adjusting ACL of %s failed: %m", path); + return fd; - if (fstat(fd, &st) < 0) - return log_error_errno(errno, "Failed to fstat() file %s: %m", path); - - r = fd_set_acls(item, fd, &st); + r = fd_set_acls(item, fd, NULL); #endif return r; } @@ -1199,6 +1238,7 @@ static int fd_set_attribute(Item *item, int fd, const struct stat *st) { char procfs_path[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(int)]; _cleanup_close_ int procfs_fd = -1; _cleanup_free_ char *path = NULL; + struct stat stbuf; unsigned f; int r; @@ -1209,6 +1249,12 @@ static int fd_set_attribute(Item *item, int fd, const struct stat *st) { if (r < 0) return r; + if (!st) { + if (fstat(fd, &stbuf) < 0) + return log_error_errno(errno, "fstat(%s) failed: %m", path); + 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. */ @@ -1241,99 +1287,558 @@ static int fd_set_attribute(Item *item, int fd, const struct stat *st) { static int path_set_attribute(Item *item, const char *path) { _cleanup_close_ int fd = -1; - struct stat st; if (!item->attribute_set || item->attribute_mask == 0) return 0; - fd = open(path, O_CLOEXEC|O_NOFOLLOW|O_PATH); + fd = path_open_safe(path); if (fd < 0) - return log_error_errno(errno, "Cannot open '%s': %m", path); - - if (fstat(fd, &st) < 0) - return log_error_errno(errno, "Cannot stat '%s': %m", path); + return fd; - return fd_set_attribute(item, fd, &st); + return fd_set_attribute(item, fd, NULL); } static int write_one_file(Item *i, const char *path) { - _cleanup_close_ int fd = -1; - int flags, r = 0; - struct stat st; + _cleanup_close_ int fd = -1, dir_fd = -1; + char *bn; + int r; + + assert(i); + assert(path); + assert(i->argument); + assert(i->type == WRITE_FILE); + + /* Validate the path and keep the fd on the directory for opening the + * file so we're sure that it can't be changed behind our back. */ + dir_fd = path_open_parent_safe(path); + if (dir_fd < 0) + return dir_fd; + + bn = basename(path); + + /* Follows symlinks */ + fd = openat(dir_fd, bn, O_NONBLOCK|O_CLOEXEC|O_WRONLY|O_NOCTTY, i->mode); + if (fd < 0) { + if (errno == ENOENT) { + log_debug_errno(errno, "Not writing missing file \"%s\": %m", path); + return 0; + } + return log_error_errno(errno, "Failed to open file \"%s\": %m", path); + } + + /* 'w' is allowed to write into any kind of files. */ + log_debug("Writing to \"%s\".", path); + + r = loop_write(fd, i->argument, strlen(i->argument), false); + if (r < 0) + return log_error_errno(r, "Failed to write file \"%s\": %m", path); + + return fd_set_perms(i, fd, NULL); +} + +static int create_file(Item *i, const char *path) { + _cleanup_close_ int fd = -1, dir_fd = -1; + struct stat stbuf, *st = NULL; + int r = 0; + char *bn; assert(i); assert(path); + assert(i->type == CREATE_FILE); + + /* 'f' operates on regular files exclusively. */ - flags = i->type == CREATE_FILE ? O_CREAT|O_EXCL|O_NOFOLLOW : - i->type == TRUNCATE_FILE ? O_CREAT|O_TRUNC|O_NOFOLLOW : 0; + /* Validate the path and keep the fd on the directory for opening the + * file so we're sure that it can't be changed behind our back. */ + dir_fd = path_open_parent_safe(path); + if (dir_fd < 0) + return dir_fd; + + bn = basename(path); RUN_WITH_UMASK(0000) { mac_selinux_create_file_prepare(path, S_IFREG); - fd = open(path, flags|O_NDELAY|O_CLOEXEC|O_WRONLY|O_NOCTTY, i->mode); + fd = openat(dir_fd, bn, O_CREAT|O_EXCL|O_NOFOLLOW|O_NONBLOCK|O_CLOEXEC|O_WRONLY|O_NOCTTY, i->mode); mac_selinux_create_file_clear(); } if (fd < 0) { - if (i->type == WRITE_FILE && errno == ENOENT) { - log_debug_errno(errno, "Not writing missing file \"%s\": %m", path); - return 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. */ + if (errno != EEXIST) + return log_error_errno(errno, "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. */ + 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); + + if (fstat(fd, &stbuf) < 0) + return log_error_errno(errno, "stat(%s) failed: %m", path); + + if (!S_ISREG(stbuf.st_mode)) { + log_error("%s exists and is not a regular file.", path); + return -EEXIST; } - if (i->type == CREATE_FILE && errno == EEXIST) { - log_debug_errno(errno, "Not writing to pre-existing file \"%s\": %m", path); - goto done; + + st = &stbuf; + } else { + + log_debug("\"%s\" has been created.", path); + + if (i->argument) { + log_debug("Writing to \"%s\".", path); + + r = loop_write(fd, i->argument, strlen(i->argument), false); + if (r < 0) + return log_error_errno(r, "Failed to write file \"%s\": %m", path); } + } - r = -errno; - if (!i->argument && errno == EROFS && stat(path, &st) == 0 && - (i->type == CREATE_FILE || st.st_size == 0)) - goto check_mode; + return fd_set_perms(i, fd, st); +} - return log_error_errno(r, "Failed to create file %s: %m", path); +static int truncate_file(Item *i, const char *path) { + _cleanup_close_ int fd = -1, dir_fd = -1; + struct stat stbuf, *st = NULL; + bool erofs = false; + int r = 0; + char *bn; + + assert(i); + assert(path); + assert(i->type == TRUNCATE_FILE); + + /* We want to operate on regular file exclusively especially since + * O_TRUNC is unspecified if the file is neither a regular file nor a + * fifo nor a terminal device. Therefore we first open the file and make + * sure it's a regular one before truncating it. */ + + /* Validate the path and keep the fd on the directory for opening the + * file so we're sure that it can't be changed behind our back. */ + dir_fd = path_open_parent_safe(path); + if (dir_fd < 0) + return dir_fd; + + bn = basename(path); + + RUN_WITH_UMASK(0000) { + mac_selinux_create_file_prepare(path, S_IFREG); + fd = openat(dir_fd, bn, O_CREAT|O_NOFOLLOW|O_NONBLOCK|O_CLOEXEC|O_WRONLY|O_NOCTTY, i->mode); + mac_selinux_create_file_clear(); } - if (i->argument) { - log_debug("%s to \"%s\".", i->type == CREATE_FILE ? "Appending" : "Writing", path); + if (fd < 0) { + if (errno != EROFS) + return log_error_errno(errno, "Failed to open/create file %s: %m", path); - r = loop_write(fd, i->argument, strlen(i->argument), false); - if (r < 0) - return log_error_errno(r, "Failed to write file \"%s\": %m", path); - } else - log_debug("\"%s\" has been created.", 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. */ + + fd = openat(dir_fd, bn, O_NOFOLLOW|O_CLOEXEC|O_PATH, i->mode); + if (fd < 0) { + if (errno == ENOENT) { + log_error("Cannot create file %s on a read-only file system.", path); + return -EROFS; + } - fd = safe_close(fd); + return log_error_errno(errno, "Failed to re-open file %s: %m", path); + } -done: - if (stat(path, &st) < 0) + erofs = true; + } + + if (fstat(fd, &stbuf) < 0) return log_error_errno(errno, "stat(%s) failed: %m", path); - check_mode: - if (!S_ISREG(st.st_mode)) { - log_error("%s is not a file.", path); + if (!S_ISREG(stbuf.st_mode)) { + log_error("%s exists and is not a regular file.", path); return -EEXIST; } - r = path_set_perms(i, path); + if (stbuf.st_size > 0) { + if (ftruncate(fd, 0) < 0) { + r = erofs ? -EROFS : -errno; + return log_error_errno(r, "Failed to truncate file %s: %m", path); + } + } else + st = &stbuf; + + log_debug("\"%s\" has been created.", path); + + if (i->argument) { + log_debug("Writing to \"%s\".", path); + + r = loop_write(fd, i->argument, strlen(i->argument), false); + if (r < 0) { + r = erofs ? -EROFS : r; + return log_error_errno(r, "Failed to write file %s: %m", path); + } + } + + return fd_set_perms(i, fd, st); +} + +static int copy_files(Item *i) { + _cleanup_close_ int dfd = -1, fd = -1; + char *bn; + int r; + + log_debug("Copying tree \"%s\" to \"%s\".", i->argument, i->path); + + bn = basename(i->path); + + /* Validate the path and use the returned directory fd for copying the + * target so we're sure that the path can't be changed behind our + * back. */ + dfd = path_open_parent_safe(i->path); + if (dfd < 0) + return dfd; + + r = copy_tree_at(AT_FDCWD, i->argument, + dfd, bn, + i->uid_set ? i->uid : UID_INVALID, + i->gid_set ? i->gid : GID_INVALID, + COPY_REFLINK); + 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); + + return fd_set_perms(i, fd, NULL); +} + +typedef enum { + CREATION_NORMAL, + CREATION_EXISTING, + CREATION_FORCE, + _CREATION_MODE_MAX, + _CREATION_MODE_INVALID = -1 +} CreationMode; + +static const char *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) { + _cleanup_close_ int pfd = -1; + CreationMode creation; + int r; + + assert(path); + + pfd = path_open_parent_safe(path); + if (pfd < 0) + return pfd; + + if (subvol) { + if (btrfs_is_subvol(empty_to_root(arg_root)) <= 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 heavy-weight). Thus, chroot() + * environments and suchlike will get a full brtfs + * subvolume set up below their tree only if they + * specifically set up a btrfs subvolume for the root + * dir too. */ + + subvol = false; + else { + RUN_WITH_UMASK((~mode) & 0777) + r = btrfs_subvol_make_fd(pfd, basename(path)); + } + } else + r = 0; + + if (!subvol || r == -ENOTTY) + RUN_WITH_UMASK(0000) + r = mkdirat_label(pfd, basename(path), mode); + + if (r < 0) { + int k; + + if (!IN_SET(r, -EEXIST, -EROFS)) + return log_error_errno(r, "Failed to create directory or subvolume \"%s\": %m", path); + + k = is_dir_fd(pfd); + 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) { + log_warning("\"%s\" already exists and is not a directory.", path); + return -EEXIST; + } + + creation = CREATION_EXISTING; + } else + creation = CREATION_NORMAL; + + log_debug("%s directory \"%s\".", creation_mode_verb_to_string(creation), path); + + r = openat(pfd, basename(path), O_NOCTTY|O_CLOEXEC|O_DIRECTORY); if (r < 0) - return r; + return -errno; + return r; +} - return 0; +static int create_directory(Item *i, const char *path) { + _cleanup_close_ int fd = -1; + + assert(i); + assert(IN_SET(i->type, CREATE_DIRECTORY, TRUNCATE_DIRECTORY)); + + fd = create_directory_or_subvolume(path, i->mode, false); + if (fd == -EEXIST) + return 0; + if (fd < 0) + return fd; + + return fd_set_perms(i, fd, NULL); +} + +static int create_subvolume(Item *i, const char *path) { + _cleanup_close_ int fd = -1; + 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, true); + if (fd == -EEXIST) + return 0; + if (fd < 0) + return fd; + + if (IN_SET(i->type, CREATE_SUBVOLUME_NEW_QUOTA, CREATE_SUBVOLUME_INHERIT_QUOTA)) { + r = btrfs_subvol_auto_qgroup_fd(fd, 0, i->type == CREATE_SUBVOLUME_NEW_QUOTA); + if (r == -ENOTTY) + log_debug_errno(r, "Couldn't adjust quota for subvolume \"%s\" (unsupported fs or dir not a subvolume): %m", i->path); + else if (r == -EROFS) + log_debug_errno(r, "Couldn't adjust quota for subvolume \"%s\" (fs is read-only).", i->path); + else if (r == -ENOPROTOOPT) + log_debug_errno(r, "Couldn't adjust quota for subvolume \"%s\" (quota support is disabled).", i->path); + else if (r < 0) + q = log_error_errno(r, "Failed to adjust quota for subvolume \"%s\": %m", i->path); + else if (r > 0) + log_debug("Adjusted quota for subvolume \"%s\".", i->path); + else if (r == 0) + log_debug("Quota for subvolume \"%s\" already in place, no change made.", i->path); + } + + r = fd_set_perms(i, fd, NULL); + if (q < 0) + return q; + + return r; +} + +static int empty_directory(Item *i, const char *path) { + int r; + + assert(i); + assert(i->type == EMPTY_DIRECTORY); + + r = is_dir(path, false); + 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); + return 0; + } + if (r < 0) + return log_error_errno(r, "is_dir() failed on path %s: %m", path); + + return path_set_perms(i, path); +} + +static int create_device(Item *i, mode_t file_type) { + _cleanup_close_ int dfd = -1, fd = -1; + CreationMode creation; + char *bn; + int r; + + assert(i); + assert(IN_SET(file_type, S_IFBLK, S_IFCHR)); + + bn = basename(i->path); + + /* Validate the path and use the returned directory fd for copying the + * target so we're sure that the path can't be changed behind our + * back. */ + dfd = path_open_parent_safe(i->path); + if (dfd < 0) + return dfd; + + RUN_WITH_UMASK(0000) { + mac_selinux_create_file_prepare(i->path, file_type); + r = mknodat(dfd, bn, i->mode | file_type, i->major_minor); + mac_selinux_create_file_clear(); + } + + if (r < 0) { + struct stat st; + + if (errno == EPERM) { + log_debug("We lack permissions, possibly because of cgroup configuration; " + "skipping creation of device node %s.", i->path); + return 0; + } + + if (errno != EEXIST) + return log_error_errno(errno, "Failed to create device node %s: %m", i->path); + + if (fstatat(dfd, bn, &st, 0) < 0) + return log_error_errno(errno, "stat(%s) failed: %m", i->path); + + if ((st.st_mode & S_IFMT) != file_type) { + + if (i->force) { + + 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_debug("%s is not a device node.", i->path); + return 0; + } + } else + creation = CREATION_EXISTING; + } else + creation = CREATION_NORMAL; + + 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, NULL); +} + +static int create_fifo(Item *i, const char *path) { + _cleanup_close_ int pfd = -1, fd = -1; + CreationMode creation; + struct stat st; + char *bn; + int r; + + pfd = path_open_parent_safe(path); + if (pfd < 0) + return pfd; + + bn = basename(path); + + RUN_WITH_UMASK(0000) { + mac_selinux_create_file_prepare(path, S_IFIFO); + r = mkfifoat(pfd, bn, i->mode); + mac_selinux_create_file_clear(); + } + + if (r < 0) { + if (errno != EEXIST) + return log_error_errno(errno, "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->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); + + fd = openat(pfd, bn, O_NOFOLLOW|O_CLOEXEC|O_PATH); + if (fd < 0) + return log_error_errno(fd, "Failed to openat(%s): %m", path); + + return fd_set_perms(i, fd, NULL); } typedef int (*action_t)(Item *, const char *); typedef int (*fdaction_t)(Item *, int fd, const struct stat *st); -static int item_do(Item *i, int fd, const struct stat *st, fdaction_t action) { +static int item_do(Item *i, int fd, fdaction_t action) { + struct stat st; int r = 0, q; assert(i); assert(fd >= 0); - assert(st); + + if (fstat(fd, &st) < 0) { + r = -errno; + goto finish; + } /* This returns the first error we run into, but nevertheless * tries to go on */ - r = action(i, fd, st); + r = action(i, fd, &st); - if (S_ISDIR(st->st_mode)) { + if (S_ISDIR(st.st_mode)) { char procfs_path[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(int)]; _cleanup_closedir_ DIR *d = NULL; struct dirent *de; @@ -1349,16 +1854,15 @@ static int item_do(Item *i, int fd, const struct stat *st, fdaction_t action) { } FOREACH_DIRENT_ALL(de, d, q = -errno; goto finish) { - struct stat de_st; int de_fd; if (dot_or_dot_dot(de->d_name)) continue; de_fd = openat(fd, de->d_name, O_NOFOLLOW|O_CLOEXEC|O_PATH); - if (de_fd >= 0 && fstat(de_fd, &de_st) >= 0) + if (de_fd >= 0) /* pass ownership of dirent fd over */ - q = item_do(i, de_fd, &de_st, action); + q = item_do(i, de_fd, action); else q = -errno; @@ -1406,7 +1910,6 @@ static int glob_item_recursively(Item *i, fdaction_t action) { STRV_FOREACH(fn, g.gl_pathv) { _cleanup_close_ int fd = -1; - struct stat st; /* Make sure we won't trigger/follow file object (such as * device nodes, automounts, ...) pointed out by 'fn' with @@ -1419,12 +1922,7 @@ static int glob_item_recursively(Item *i, fdaction_t action) { continue; } - if (fstat(fd, &st) < 0) { - r = r ?: -errno; - continue; - } - - k = item_do(i, fd, &st, action); + k = item_do(i, fd, action); if (k < 0 && r == 0) r = k; @@ -1435,27 +1933,9 @@ static int glob_item_recursively(Item *i, fdaction_t action) { return r; } -typedef enum { - CREATION_NORMAL, - CREATION_EXISTING, - CREATION_FORCE, - _CREATION_MODE_MAX, - _CREATION_MODE_INVALID = -1 -} CreationMode; - -static const char *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_item(Item *i) { - struct stat st; - int r = 0; - int q = 0; CreationMode creation; + int r = 0; assert(i); @@ -1470,51 +1950,31 @@ static int create_item(Item *i) { return 0; case CREATE_FILE: - case TRUNCATE_FILE: RUN_WITH_UMASK(0000) (void) mkdir_parents_label(i->path, 0755); - r = write_one_file(i, i->path); + r = create_file(i, i->path); if (r < 0) return r; break; - case COPY_FILES: { - + case TRUNCATE_FILE: RUN_WITH_UMASK(0000) (void) mkdir_parents_label(i->path, 0755); - log_debug("Copying tree \"%s\" to \"%s\".", i->argument, i->path); - r = copy_tree(i->argument, i->path, - i->uid_set ? i->uid : UID_INVALID, - i->gid_set ? i->gid : GID_INVALID, - COPY_REFLINK); - - if (r == -EROFS && stat(i->path, &st) == 0) - r = -EEXIST; - - if (r < 0) { - struct stat a, b; - - 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); + r = truncate_file(i, i->path); + if (r < 0) + return r; + break; - if (stat(i->path, &b) < 0) - return log_error_errno(errno, "stat(%s) failed: %m", i->path); + case COPY_FILES: { - 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; - } - } + RUN_WITH_UMASK(0000) + (void) mkdir_parents_label(i->path, 0755); - r = path_set_perms(i, i->path); + r = copy_files(i); if (r < 0) return r; - break; case WRITE_FILE: @@ -1526,132 +1986,39 @@ static int create_item(Item *i) { case CREATE_DIRECTORY: case TRUNCATE_DIRECTORY: + RUN_WITH_UMASK(0000) + (void) mkdir_parents_label(i->path, 0755); + + r = create_directory(i, i->path); + if (r < 0) + return r; + break; + case CREATE_SUBVOLUME: case CREATE_SUBVOLUME_INHERIT_QUOTA: case CREATE_SUBVOLUME_NEW_QUOTA: RUN_WITH_UMASK(0000) (void) mkdir_parents_label(i->path, 0755); - if (IN_SET(i->type, CREATE_SUBVOLUME, CREATE_SUBVOLUME_INHERIT_QUOTA, CREATE_SUBVOLUME_NEW_QUOTA)) { - - if (btrfs_is_subvol(isempty(arg_root) ? "/" : arg_root) <= 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 - * heavy-weight). Thus, chroot() - * environments and suchlike will get - * a full brtfs subvolume set up below - * their tree only if they - * specifically set up a btrfs - * subvolume for the root dir too. */ - - r = -ENOTTY; - else { - RUN_WITH_UMASK((~i->mode) & 0777) - r = btrfs_subvol_make(i->path); - } - } else - r = 0; - - if (IN_SET(i->type, CREATE_DIRECTORY, TRUNCATE_DIRECTORY) || r == -ENOTTY) - RUN_WITH_UMASK(0000) - r = mkdir_label(i->path, i->mode); - - if (r < 0) { - int k; - - if (!IN_SET(r, -EEXIST, -EROFS)) - return log_error_errno(r, "Failed to create directory or subvolume \"%s\": %m", i->path); - - k = is_dir(i->path, 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.", i->path); - if (k < 0) - return log_error_errno(k, "Failed to check if %s exists: %m", i->path); - if (!k) { - log_warning("\"%s\" already exists and is not a directory.", i->path); - return 0; - } - - creation = CREATION_EXISTING; - } else - creation = CREATION_NORMAL; - - log_debug("%s directory \"%s\".", creation_mode_verb_to_string(creation), i->path); - - if (IN_SET(i->type, CREATE_SUBVOLUME_NEW_QUOTA, CREATE_SUBVOLUME_INHERIT_QUOTA)) { - r = btrfs_subvol_auto_qgroup(i->path, 0, i->type == CREATE_SUBVOLUME_NEW_QUOTA); - if (r == -ENOTTY) - log_debug_errno(r, "Couldn't adjust quota for subvolume \"%s\" (unsupported fs or dir not a subvolume): %m", i->path); - else if (r == -EROFS) - log_debug_errno(r, "Couldn't adjust quota for subvolume \"%s\" (fs is read-only).", i->path); - else if (r == -ENOPROTOOPT) - log_debug_errno(r, "Couldn't adjust quota for subvolume \"%s\" (quota support is disabled).", i->path); - else if (r < 0) - q = log_error_errno(r, "Failed to adjust quota for subvolume \"%s\": %m", i->path); - else if (r > 0) - log_debug("Adjusted quota for subvolume \"%s\".", i->path); - else if (r == 0) - log_debug("Quota for subvolume \"%s\" already in place, no change made.", i->path); - } + r = create_subvolume(i, i->path); + if (r < 0) + return r; + break; - _fallthrough_; case EMPTY_DIRECTORY: - r = path_set_perms(i, i->path); - if (q < 0) - return q; + r = empty_directory(i, i->path); if (r < 0) return r; break; case CREATE_FIFO: - RUN_WITH_UMASK(0000) { + RUN_WITH_UMASK(0000) (void) mkdir_parents_label(i->path, 0755); - mac_selinux_create_file_prepare(i->path, S_IFIFO); - r = mkfifo(i->path, i->mode); - mac_selinux_create_file_clear(); - } - - if (r < 0) { - if (errno != EEXIST) - return log_error_errno(errno, "Failed to create fifo %s: %m", i->path); - - if (lstat(i->path, &st) < 0) - return log_error_errno(errno, "stat(%s) failed: %m", i->path); - - if (!S_ISFIFO(st.st_mode)) { - - if (i->force) { - RUN_WITH_UMASK(0000) { - mac_selinux_create_file_prepare(i->path, S_IFIFO); - r = mkfifo_atomic(i->path, i->mode); - mac_selinux_create_file_clear(); - } - - if (r < 0) - return log_error_errno(r, "Failed to create fifo %s: %m", i->path); - creation = CREATION_FORCE; - } else { - log_warning("\"%s\" already exists and is not a fifo.", i->path); - return 0; - } - } else - creation = CREATION_EXISTING; - } else - creation = CREATION_NORMAL; - log_debug("%s fifo \"%s\".", creation_mode_verb_to_string(creation), i->path); - - r = path_set_perms(i, i->path); + r = create_fifo(i, i->path); if (r < 0) return r; - break; } @@ -1704,9 +2071,7 @@ static int create_item(Item *i) { } case CREATE_BLOCK_DEVICE: - case CREATE_CHAR_DEVICE: { - mode_t file_type; - + case CREATE_CHAR_DEVICE: if (have_effective_cap(CAP_MKNOD) == 0) { /* In a container we lack CAP_MKNOD. We shouldn't attempt to create the device node in @@ -1720,60 +2085,11 @@ static int create_item(Item *i) { RUN_WITH_UMASK(0000) (void) mkdir_parents_label(i->path, 0755); - file_type = i->type == CREATE_BLOCK_DEVICE ? S_IFBLK : S_IFCHR; - - RUN_WITH_UMASK(0000) { - mac_selinux_create_file_prepare(i->path, file_type); - r = mknod(i->path, i->mode | file_type, i->major_minor); - mac_selinux_create_file_clear(); - } - - if (r < 0) { - if (errno == EPERM) { - log_debug("We lack permissions, possibly because of cgroup configuration; " - "skipping creation of device node %s.", i->path); - return 0; - } - - if (errno != EEXIST) - return log_error_errno(errno, "Failed to create device node %s: %m", i->path); - - if (lstat(i->path, &st) < 0) - return log_error_errno(errno, "stat(%s) failed: %m", i->path); - - if ((st.st_mode & S_IFMT) != file_type) { - - if (i->force) { - - RUN_WITH_UMASK(0000) { - mac_selinux_create_file_prepare(i->path, file_type); - 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_debug("%s is not a device node.", i->path); - return 0; - } - } else - creation = CREATION_EXISTING; - } else - creation = CREATION_NORMAL; - - 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)); - - r = path_set_perms(i, i->path); + r = create_device(i, i->type == CREATE_BLOCK_DEVICE ? S_IFBLK : S_IFCHR); if (r < 0) return r; break; - } case ADJUST_MODE: case RELABEL_PATH: -- 2.11.0