aboutsummaryrefslogtreecommitdiffstats
path: root/meta/recipes-core
diff options
context:
space:
mode:
authorGeorge McCollister <george.mccollister@gmail.com>2019-02-25 10:37:12 -0600
committerArmin Kuster <akuster808@gmail.com>2019-03-17 12:07:56 -0700
commite749c579acbf4c5ddef4c875635af6da89e17d17 (patch)
treecd29b557a603d2a2fb63b812437de708a5c5ebb5 /meta/recipes-core
parent454cbaa1157be8e4e930c89983399a9b5a5aaaa0 (diff)
downloadopenembedded-core-contrib-e749c579acbf4c5ddef4c875635af6da89e17d17.tar.gz
systemd: fix CVE-2018-6954
Apply patches to fix CVE-2018-6954 NVD description from https://nvd.nist.gov/vuln/detail/CVE-2018-6954 systemd-tmpfiles in systemd through 237 mishandles symlinks present in non-terminal path components, which allows local users to obtain ownership of arbitrary files via vectors involving creation of a directory and a file under that directory, and later replacing that directory with a symlink. This occurs even if the fs.protected_symlinks sysctl is turned on. Patches from systemd_237-3ubuntu10.13.debian. These patches shouldn't be required on newer OE releases since they use systemd v239 or higher. Signed-off-by: George McCollister <george.mccollister@gmail.com>
Diffstat (limited to 'meta/recipes-core')
-rw-r--r--meta/recipes-core/systemd/systemd/0001-tmpfiles-don-t-resolve-pathnames-when-traversing-rec.patch643
-rw-r--r--meta/recipes-core/systemd/systemd/0002-Make-tmpfiles-safe.patch1828
-rw-r--r--meta/recipes-core/systemd/systemd_237.bb2
3 files changed, 2473 insertions, 0 deletions
diff --git a/meta/recipes-core/systemd/systemd/0001-tmpfiles-don-t-resolve-pathnames-when-traversing-rec.patch b/meta/recipes-core/systemd/systemd/0001-tmpfiles-don-t-resolve-pathnames-when-traversing-rec.patch
new file mode 100644
index 0000000000..108e4ad8b8
--- /dev/null
+++ b/meta/recipes-core/systemd/systemd/0001-tmpfiles-don-t-resolve-pathnames-when-traversing-rec.patch
@@ -0,0 +1,643 @@
+From 33dc9a280f952f503e5493ee29f6815bef29d551 Mon Sep 17 00:00:00 2001
+From: Franck Bui <fbui@suse.com>
+Date: Fri, 2 Mar 2018 17:19:32 +0100
+Subject: [PATCH] tmpfiles: don't resolve pathnames when traversing recursively
+ through directory trees
+
+Otherwise we can be fooled if one path component is replaced underneath us.
+
+The patch achieves that by always operating at file descriptor level (by using
+*at() helpers) and by making sure we do not any path resolution when traversing
+direcotry trees.
+
+However this is not always possible, for instance when listing the content of a
+directory or some operations don't provide the *at() helpers or others (such as
+fchmodat()) don't have the AT_EMPTY_PATH flag. In such cases we operate on
+/proc/self/fd/%i pseudo-symlink instead, which works the same for all kinds of
+objects and requires no checking of type beforehand.
+
+Also O_PATH flag is used when opening file objects in order to prevent
+undesired behaviors: device nodes from reacting, automounts from
+triggering, etc...
+
+Fixes: CVE-2018-6954
+
+Origin: upstream, https://github.com/systemd/systemd/commit/936f6bdb803c432578e2cdcc5f93f3bfff93aff0
+Bug: https://github.com/systemd/systemd/issues/7986
+
+Patch from:
+systemd_237-3ubuntu10.13.debian CVE-2018-6954.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 <george.mccollister@gmail.com>
+---
+ src/tmpfiles/tmpfiles.c | 363 +++++++++++++++++++++++++++++++-----------------
+ 1 file changed, 239 insertions(+), 124 deletions(-)
+
+diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c
+index 88cc543f09..613d418eb3 100644
+--- a/src/tmpfiles/tmpfiles.c
++++ b/src/tmpfiles/tmpfiles.c
+@@ -792,94 +792,105 @@ static bool hardlink_vulnerable(struct stat *st) {
+ return !S_ISDIR(st->st_mode) && st->st_nlink > 1 && dangerous_hardlinks();
+ }
+
+-static int path_set_perms(Item *i, const char *path) {
+- char fn[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int)];
+- _cleanup_close_ int fd = -1;
+- struct stat st;
++static int fd_set_perms(Item *i, int fd, const struct stat *st) {
++ _cleanup_free_ char *path = NULL;
++ int r;
+
+ assert(i);
+- assert(path);
+-
+- if (!i->mode_set && !i->uid_set && !i->gid_set)
+- goto shortcut;
+-
+- /* We open the file with O_PATH here, to make the operation
+- * somewhat atomic. Also there's unfortunately no fchmodat()
+- * with AT_SYMLINK_NOFOLLOW, hence we emulate it here via
+- * O_PATH. */
+-
+- fd = open(path, O_NOFOLLOW|O_CLOEXEC|O_PATH);
+- if (fd < 0) {
+- int level = LOG_ERR, r = -errno;
++ assert(fd);
+
+- /* 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;
+- }
+-
+- log_full_errno(level, errno, "Adjusting owner and mode for %s failed: %m", path);
++ r = fd_get_path(fd, &path);
++ if (r < 0)
+ return r;
+- }
+
+- if (fstatat(fd, "", &st, AT_EMPTY_PATH) < 0)
+- return log_error_errno(errno, "Failed to fstat() file %s: %m", path);
++ if (!i->mode_set && !i->uid_set && !i->gid_set)
++ goto shortcut;
+
+- if (hardlink_vulnerable(&st)) {
++ 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;
+ }
+
+- xsprintf(fn, "/proc/self/fd/%i", fd);
+-
+ if (i->mode_set) {
+- if (S_ISLNK(st.st_mode))
++ if (S_ISLNK(st->st_mode))
+ log_debug("Skipping mode fix for symlink %s.", path);
+ else {
+ mode_t m = i->mode;
+
+ if (i->mask_perms) {
+- if (!(st.st_mode & 0111))
++ if (!(st->st_mode & 0111))
+ m &= ~0111;
+- if (!(st.st_mode & 0222))
++ if (!(st->st_mode & 0222))
+ m &= ~0222;
+- if (!(st.st_mode & 0444))
++ if (!(st->st_mode & 0444))
+ m &= ~0444;
+- if (!S_ISDIR(st.st_mode))
++ if (!S_ISDIR(st->st_mode))
+ m &= ~07000; /* remove sticky/sgid/suid bit, unless directory */
+ }
+
+- if (m == (st.st_mode & 07777))
+- log_debug("\"%s\" has correct mode %o already.", path, st.st_mode);
++ if (m == (st->st_mode & 07777))
++ log_debug("\"%s\" has correct mode %o already.", path, st->st_mode);
+ else {
++ char procfs_path[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(int)];
++
+ log_debug("Changing \"%s\" to mode %o.", path, m);
+
+- if (chmod(fn, m) < 0)
+- return log_error_errno(errno, "chmod() of %s via %s failed: %m", path, fn);
++ /* fchmodat() still doesn't have AT_EMPTY_PATH flag. */
++ xsprintf(procfs_path, "/proc/self/fd/%i", fd);
++
++ if (chmod(procfs_path, m) < 0)
++ return log_error_errno(errno, "chmod() of %s via %s failed: %m", path, procfs_path);
+ }
+ }
+ }
+
+- if ((i->uid_set && i->uid != st.st_uid) ||
+- (i->gid_set && i->gid != st.st_gid)) {
++ if ((i->uid_set && i->uid != st->st_uid) ||
++ (i->gid_set && i->gid != st->st_gid)) {
+ 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);
+
+- if (chown(fn,
+- i->uid_set ? i->uid : UID_INVALID,
+- i->gid_set ? i->gid : GID_INVALID) < 0)
+- return log_error_errno(errno, "chown() of %s via %s failed: %m", path, fn);
++ if (fchownat(fd,
++ "",
++ i->uid_set ? i->uid : UID_INVALID,
++ i->gid_set ? i->gid : GID_INVALID,
++ AT_EMPTY_PATH) < 0)
++ return log_error_errno(errno, "fchownat() of %s failed: %m", path);
+ }
+
+- fd = safe_close(fd);
+-
+ 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;
++
++ assert(i);
++ assert(path);
++
++ fd = open(path, O_NOFOLLOW|O_CLOEXEC|O_PATH);
++ if (fd < 0) {
++ int level = LOG_ERR, r = -errno;
++
++ /* 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;
++ }
++
++ log_full_errno(level, errno, "Adjusting owner and mode for %s failed: %m", path);
++ return r;
++ }
++
++ if (fstat(fd, &st) < 0)
++ return log_error_errno(errno, "Failed to fstat() file %s: %m", path);
++
++ return fd_set_perms(i, fd, &st);
++}
++
+ static int parse_xattrs_from_arg(Item *i) {
+ const char *p;
+ int r;
+@@ -918,21 +929,43 @@ static int parse_xattrs_from_arg(Item *i) {
+ return 0;
+ }
+
+-static int path_set_xattrs(Item *i, const char *path) {
++static int fd_set_xattrs(Item *i, int fd, const struct stat *st) {
++ char procfs_path[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(int)];
++ _cleanup_free_ char *path = NULL;
+ char **name, **value;
++ int r;
+
+ assert(i);
+- assert(path);
++ assert(fd);
++
++ r = fd_get_path(fd, &path);
++ if (r < 0)
++ return r;
++
++ xsprintf(procfs_path, "/proc/self/fd/%i", fd);
+
+ STRV_FOREACH_PAIR(name, value, i->xattrs) {
+ log_debug("Setting extended attribute '%s=%s' on %s.", *name, *value, path);
+- if (lsetxattr(path, *name, *value, strlen(*value), 0) < 0)
++ if (setxattr(procfs_path, *name, *value, strlen(*value), 0) < 0)
+ return log_error_errno(errno, "Setting extended attribute %s=%s on %s failed: %m",
+ *name, *value, path);
+ }
+ return 0;
+ }
+
++static int path_set_xattrs(Item *i, const char *path) {
++ _cleanup_close_ int fd = -1;
++
++ assert(i);
++ assert(path);
++
++ fd = open(path, O_CLOEXEC|O_NOFOLLOW|O_PATH);
++ if (fd < 0)
++ return log_error_errno(errno, "Cannot open '%s': %m", path);
++
++ return fd_set_xattrs(i, fd, NULL);
++}
++
+ static int parse_acls_from_arg(Item *item) {
+ #if HAVE_ACL
+ int r;
+@@ -998,52 +1031,71 @@ static int path_set_acl(const char *path, const char *pretty, acl_type_t type, a
+ }
+ #endif
+
+-static int path_set_acls(Item *item, const char *path) {
++static int fd_set_acls(Item *item, int fd, const struct stat *st) {
+ int r = 0;
+ #if HAVE_ACL
+- char fn[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int)];
+- _cleanup_close_ int fd = -1;
+- struct stat st;
++ char procfs_path[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(int)];
++ _cleanup_free_ char *path = NULL;
+
+ assert(item);
+- assert(path);
+-
+- fd = open(path, O_NOFOLLOW|O_CLOEXEC|O_PATH);
+- if (fd < 0)
+- return log_error_errno(errno, "Adjusting ACL of %s failed: %m", path);
++ assert(fd);
++ assert(st);
+
+- if (fstatat(fd, "", &st, AT_EMPTY_PATH) < 0)
+- return log_error_errno(errno, "Failed to fstat() file %s: %m", path);
++ r = fd_get_path(fd, &path);
++ if (r < 0)
++ return r;
+
+- if (hardlink_vulnerable(&st)) {
++ 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;
+ }
+
+- if (S_ISLNK(st.st_mode)) {
++ if (S_ISLNK(st->st_mode)) {
+ log_debug("Skipping ACL fix for symlink %s.", path);
+ return 0;
+ }
+
+- xsprintf(fn, "/proc/self/fd/%i", fd);
++ xsprintf(procfs_path, "/proc/self/fd/%i", fd);
+
+ if (item->acl_access)
+- r = path_set_acl(fn, path, ACL_TYPE_ACCESS, item->acl_access, item->force);
++ r = path_set_acl(procfs_path, path, ACL_TYPE_ACCESS, item->acl_access, item->force);
+
+ if (r == 0 && item->acl_default)
+- r = path_set_acl(fn, path, ACL_TYPE_DEFAULT, item->acl_default, item->force);
++ r = path_set_acl(procfs_path, path, ACL_TYPE_DEFAULT, item->acl_default, item->force);
+
+ if (r > 0)
+ return -r; /* already warned */
+- else if (r == -EOPNOTSUPP) {
++ if (r == -EOPNOTSUPP) {
+ log_debug_errno(r, "ACLs not supported by file system at %s", path);
+ return 0;
+- } else if (r < 0)
+- log_error_errno(r, "ACL operation on \"%s\" failed: %m", path);
++ }
++ if (r < 0)
++ return log_error_errno(r, "ACL operation on \"%s\" failed: %m", path);
+ #endif
+ return r;
+ }
+
++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);
++ if (fd < 0)
++ return log_error_errno(errno, "Adjusting ACL of %s failed: %m", path);
++
++ if (fstat(fd, &st) < 0)
++ return log_error_errno(errno, "Failed to fstat() file %s: %m", path);
++
++ r = fd_set_acls(item, fd, &st);
++ #endif
++ return r;
++ }
++
+ #define ATTRIBUTES_ALL \
+ (FS_NOATIME_FL | \
+ FS_SYNC_FL | \
+@@ -1143,30 +1195,24 @@ static int parse_attribute_from_arg(Item *item) {
+ return 0;
+ }
+
+-static int path_set_attribute(Item *item, const char *path) {
+- _cleanup_close_ int fd = -1;
+- struct stat st;
++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;
+ unsigned f;
+ int r;
+
+ if (!item->attribute_set || item->attribute_mask == 0)
+ return 0;
+
+- fd = open(path, O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_NOATIME|O_NOFOLLOW);
+- if (fd < 0) {
+- if (errno == ELOOP)
+- return log_error_errno(errno, "Skipping file attributes adjustment on symlink %s.", path);
+-
+- 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);
++ r = fd_get_path(fd, &path);
++ if (r < 0)
++ return r;
+
+ /* 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)) {
++ if (!S_ISREG(st->st_mode) && !S_ISDIR(st->st_mode)) {
+ log_error("Setting file flags is only supported on regular files and directories, cannot set on '%s'.", path);
+ return -EINVAL;
+ }
+@@ -1174,10 +1220,16 @@ static int path_set_attribute(Item *item, const char *path) {
+ f = item->attribute_value & item->attribute_mask;
+
+ /* Mask away directory-specific flags */
+- if (!S_ISDIR(st.st_mode))
++ if (!S_ISDIR(st->st_mode))
+ f &= ~FS_DIRSYNC_FL;
+
+- r = chattr_fd(fd, f, item->attribute_mask);
++ xsprintf(procfs_path, "/proc/self/fd/%i", fd);
++
++ procfs_fd = open(procfs_path, O_RDONLY|O_CLOEXEC|O_NOATIME);
++ if (procfs_fd < 0)
++ return -errno;
++
++ r = chattr_fd(procfs_fd, f, item->attribute_mask);
+ if (r < 0)
+ log_full_errno(IN_SET(r, -ENOTTY, -EOPNOTSUPP) ? LOG_DEBUG : LOG_WARNING,
+ r,
+@@ -1187,6 +1239,23 @@ static int path_set_attribute(Item *item, const char *path) {
+ return 0;
+ }
+
++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);
++ 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_set_attribute(item, fd, &st);
++}
++
+ static int write_one_file(Item *i, const char *path) {
+ _cleanup_close_ int fd = -1;
+ int flags, r = 0;
+@@ -1251,48 +1320,58 @@ done:
+ }
+
+ typedef int (*action_t)(Item *, const char *);
++typedef int (*fdaction_t)(Item *, int fd, const struct stat *st);
+
+-static int item_do_children(Item *i, const char *path, action_t action) {
+- _cleanup_closedir_ DIR *d;
+- struct dirent *de;
+- int r = 0;
++static int item_do(Item *i, int fd, const struct stat *st, fdaction_t action) {
++ int r = 0, q;
+
+ assert(i);
+- assert(path);
++ assert(fd >= 0);
++ assert(st);
+
+ /* This returns the first error we run into, but nevertheless
+ * tries to go on */
++ r = action(i, fd, st);
+
+- d = opendir_nomod(path);
+- if (!d)
+- return IN_SET(errno, ENOENT, ENOTDIR, ELOOP) ? 0 : -errno;
++ 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;
+
+- FOREACH_DIRENT_ALL(de, d, r = -errno) {
+- _cleanup_free_ char *p = NULL;
+- int q;
++ /* The passed 'fd' was opened with O_PATH. We need to convert
++ * it into a 'regular' fd before reading the directory content. */
++ xsprintf(procfs_path, "/proc/self/fd/%i", fd);
+
+- if (dot_or_dot_dot(de->d_name))
+- continue;
++ d = opendir(procfs_path);
++ if (!d) {
++ r = r ?: -errno;
++ goto finish;
++ }
+
+- p = strjoin(path, "/", de->d_name);
+- if (!p)
+- return -ENOMEM;
++ 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;
+
+- q = action(i, p);
+- if (q < 0 && q != -ENOENT && r == 0)
+- r = q;
++ de_fd = openat(fd, de->d_name, O_NOFOLLOW|O_CLOEXEC|O_PATH);
++ if (de_fd >= 0 && fstat(de_fd, &de_st) >= 0)
++ /* pass ownership of dirent fd over */
++ q = item_do(i, de_fd, &de_st, action);
++ else
++ q = -errno;
+
+- if (IN_SET(de->d_type, DT_UNKNOWN, DT_DIR)) {
+- q = item_do_children(i, p, action);
+ if (q < 0 && r == 0)
+ r = q;
+ }
+ }
+-
++finish:
++ safe_close(fd);
+ return r;
+ }
+
+-static int glob_item(Item *i, action_t action, bool recursive) {
++static int glob_item(Item *i, action_t action) {
+ _cleanup_globfree_ glob_t g = {
+ #ifdef GLOB_ALTDIRFUNC
+ .gl_opendir = (void *(*)(const char *)) opendir_nomod,
+@@ -1309,12 +1388,48 @@ static int glob_item(Item *i, action_t action, bool recursive) {
+ k = action(i, *fn);
+ if (k < 0 && r == 0)
+ r = k;
++ }
+
+- if (recursive) {
+- k = item_do_children(i, *fn, action);
+- if (k < 0 && r == 0)
+- r = k;
++ return r;
++}
++
++static int glob_item_recursively(Item *i, fdaction_t action) {
++ _cleanup_globfree_ glob_t g = {
++ .gl_opendir = (void *(*)(const char *)) opendir_nomod,
++ };
++ int r = 0, k;
++ char **fn;
++
++ k = safe_glob(i->path, GLOB_NOSORT|GLOB_BRACE, &g);
++ if (k < 0 && k != -ENOENT)
++ return log_error_errno(k, "glob(%s) failed: %m", i->path);
++
++ 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
++ * O_PATH. Note, when O_PATH is used, flags other than
++ * O_CLOEXEC, O_DIRECTORY, and O_NOFOLLOW are ignored. */
++
++ fd = open(*fn, O_CLOEXEC|O_NOFOLLOW|O_PATH);
++ if (fd < 0) {
++ r = r ?: -errno;
++ continue;
++ }
++
++ if (fstat(fd, &st) < 0) {
++ r = r ?: -errno;
++ continue;
+ }
++
++ k = item_do(i, fd, &st, action);
++ if (k < 0 && r == 0)
++ r = k;
++
++ /* we passed fd ownership to the previous call */
++ fd = -1;
+ }
+
+ return r;
+@@ -1403,7 +1518,7 @@ static int create_item(Item *i) {
+ break;
+
+ case WRITE_FILE:
+- r = glob_item(i, write_one_file, false);
++ r = glob_item(i, write_one_file);
+ if (r < 0)
+ return r;
+
+@@ -1662,49 +1777,49 @@ static int create_item(Item *i) {
+
+ case ADJUST_MODE:
+ case RELABEL_PATH:
+- r = glob_item(i, path_set_perms, false);
++ r = glob_item(i, path_set_perms);
+ if (r < 0)
+ return r;
+ break;
+
+ case RECURSIVE_RELABEL_PATH:
+- r = glob_item(i, path_set_perms, true);
++ r = glob_item_recursively(i, fd_set_perms);
+ if (r < 0)
+ return r;
+ break;
+
+ case SET_XATTR:
+- r = glob_item(i, path_set_xattrs, false);
++ r = glob_item(i, path_set_xattrs);
+ if (r < 0)
+ return r;
+ break;
+
+ case RECURSIVE_SET_XATTR:
+- r = glob_item(i, path_set_xattrs, true);
++ r = glob_item_recursively(i, fd_set_xattrs);
+ if (r < 0)
+ return r;
+ break;
+
+ case SET_ACL:
+- r = glob_item(i, path_set_acls, false);
++ r = glob_item(i, path_set_acls);
+ if (r < 0)
+ return r;
+ break;
+
+ case RECURSIVE_SET_ACL:
+- r = glob_item(i, path_set_acls, true);
++ r = glob_item_recursively(i, fd_set_acls);
+ if (r < 0)
+ return r;
+ break;
+
+ case SET_ATTRIBUTE:
+- r = glob_item(i, path_set_attribute, false);
++ r = glob_item(i, path_set_attribute);
+ if (r < 0)
+ return r;
+ break;
+
+ case RECURSIVE_SET_ATTRIBUTE:
+- r = glob_item(i, path_set_attribute, true);
++ r = glob_item_recursively(i, fd_set_attribute);
+ if (r < 0)
+ return r;
+ break;
+@@ -1754,7 +1869,7 @@ static int remove_item(Item *i) {
+ case REMOVE_PATH:
+ case TRUNCATE_DIRECTORY:
+ case RECURSIVE_REMOVE_PATH:
+- return glob_item(i, remove_item_instance, false);
++ return glob_item(i, remove_item_instance);
+
+ default:
+ return 0;
+@@ -1828,7 +1943,7 @@ static int clean_item(Item *i) {
+ return 0;
+ case EMPTY_DIRECTORY:
+ case IGNORE_DIRECTORY_PATH:
+- return glob_item(i, clean_item_instance, false);
++ return glob_item(i, clean_item_instance);
+ default:
+ return 0;
+ }
+--
+2.11.0
+
diff --git a/meta/recipes-core/systemd/systemd/0002-Make-tmpfiles-safe.patch b/meta/recipes-core/systemd/systemd/0002-Make-tmpfiles-safe.patch
new file mode 100644
index 0000000000..80d27c141b
--- /dev/null
+++ b/meta/recipes-core/systemd/systemd/0002-Make-tmpfiles-safe.patch
@@ -0,0 +1,1828 @@
+From fb95c890cf5116e698347c6a7bb3daeeb2d28cf9 Mon Sep 17 00:00:00 2001
+From: George McCollister <george.mccollister@gmail.com>
+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 <george.mccollister@gmail.com>
+---
+ 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 <sys/types.h>
+
+ 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 <errno.h>
++#include <fcntl.h>
+ #include <string.h>
+ #include <sys/stat.h>
+ #include <sys/xattr.h>
+ #include <unistd.h>
+
+ #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
+
diff --git a/meta/recipes-core/systemd/systemd_237.bb b/meta/recipes-core/systemd/systemd_237.bb
index 96f419a7f9..bc33fbebdc 100644
--- a/meta/recipes-core/systemd/systemd_237.bb
+++ b/meta/recipes-core/systemd/systemd_237.bb
@@ -61,6 +61,8 @@ SRC_URI += "file://touchscreen.rules \
file://0025-journald-set-a-limit-on-the-number-of-fields-1k.patch \
file://0026-journal-remote-set-a-limit-on-the-number-of-fields-i.patch \
file://0027-journal-fix-out-of-bounds-read-CVE-2018-16866.patch \
+ file://0001-tmpfiles-don-t-resolve-pathnames-when-traversing-rec.patch \
+ file://0002-Make-tmpfiles-safe.patch \
"
SRC_URI_append_qemuall = " file://0001-core-device.c-Change-the-default-device-timeout-to-2.patch"