summaryrefslogtreecommitdiff
path: root/src/tmpfiles/tmpfiles.c
diff options
context:
space:
mode:
authorLennart Poettering <lennart@poettering.net>2019-04-29 16:11:52 +0200
committerLennart Poettering <lennart@poettering.net>2019-04-30 09:18:23 +0200
commita9f55d0e7ed8b718641605f91553e4f150390dbd (patch)
tree4f4b4215a0d73dd18754af181faba474e275088e /src/tmpfiles/tmpfiles.c
parent8b364a38238bf828ae6df93fdbca7897bd8f042c (diff)
downloadsystemd-a9f55d0e7ed8b718641605f91553e4f150390dbd.tar.gz
tmpfiles: be more careful when adjusting chmod() + chown()
chown() might drop the suid/sgid bit from files. hence let's chmod() after chown(). But also, let's tighten the transition a bit: before issuing chown() let's set the file mask to the minimum of the old and new access bitmask, so that at no point in time additional privs are available on the file with a non-matching ownership. Fixes: #12354
Diffstat (limited to 'src/tmpfiles/tmpfiles.c')
-rw-r--r--src/tmpfiles/tmpfiles.c52
1 files changed, 41 insertions, 11 deletions
diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c
index e157714662..a5e08d740f 100644
--- a/src/tmpfiles/tmpfiles.c
+++ b/src/tmpfiles/tmpfiles.c
@@ -792,6 +792,8 @@ static mode_t process_mask_perms(mode_t mode, mode_t current) {
static int fd_set_perms(Item *i, int fd, const char *path, const struct stat *st) {
struct stat stbuf;
+ mode_t new_mode;
+ bool do_chown;
assert(i);
assert(fd);
@@ -811,29 +813,39 @@ static int fd_set_perms(Item *i, int fd, const char *path, const struct stat *st
"Refusing to set permissions on hardlinked file %s while the fs.protected_hardlinks sysctl is turned off.",
path);
+ /* Do we need a chown()? */
+ do_chown =
+ (i->uid_set && i->uid != st->st_uid) ||
+ (i->gid_set && i->gid != st->st_gid);
+ /* Calculate the mode to apply */
+ new_mode = i->mode_set ? (i->mask_perms ?
+ process_mask_perms(i->mode, st->st_mode) :
+ i->mode) :
+ (st->st_mode & 07777);
+
+ if (i->mode_set && do_chown) {
+ /* Before we issue the chmod() let's reduce the access mode to the common bits of the old and
+ * the new mode. That way there's no time window where the file exists under the old owner
+ * with more than the old access modes — and not under the new owner with more than the new
+ * access modes either. */
- if (i->mode_set) {
if (S_ISLNK(st->st_mode))
- log_debug("Skipping mode fix for symlink %s.", path);
+ log_debug("Skipping temporary mode fix for symlink %s.", path);
else {
- mode_t m = i->mode;
-
- if (i->mask_perms)
- m = process_mask_perms(m, st->st_mode);
+ mode_t m = new_mode & st->st_mode; /* Mask new mode by old mode */
- if (m == (st->st_mode & 07777))
- log_debug("\"%s\" has correct mode %o already.", path, st->st_mode);
+ if (((m ^ st->st_mode) & 07777) == 0)
+ log_debug("\"%s\" matches temporary mode %o already.", path, m);
else {
- log_debug("Changing \"%s\" to mode %o.", path, m);
+ log_debug("Temporarily changing \"%s\" to mode %o.", path, m);
if (fchmod_opath(fd, m) < 0)
return log_error_errno(errno, "fchmod() of %s failed: %m", path);
}
}
}
- if ((i->uid_set && i->uid != st->st_uid) ||
- (i->gid_set && i->gid != st->st_gid)) {
+ if (do_chown) {
log_debug("Changing \"%s\" to owner "UID_FMT":"GID_FMT,
path,
i->uid_set ? i->uid : UID_INVALID,
@@ -847,6 +859,24 @@ static int fd_set_perms(Item *i, int fd, const char *path, const struct stat *st
return log_error_errno(errno, "fchownat() of %s failed: %m", path);
}
+ /* Now, apply the final mode. We do this in two cases: when the user set a mode explicitly, or after a
+ * chown(), since chown()'s mangle the access mode in regards to sgid/suid in some conditions. */
+ if (i->mode_set || do_chown) {
+ if (S_ISLNK(st->st_mode))
+ log_debug("Skipping mode fix for symlink %s.", path);
+ else {
+ /* Check if the chmod() is unnecessary. Note that if we did a chown() before we always
+ * chmod() here again, since it might have mangled the bits. */
+ if (!do_chown && ((new_mode ^ st->st_mode) & 07777) == 0)
+ log_debug("\"%s\" matches mode %o already.", path, new_mode);
+ else {
+ log_debug("Changing \"%s\" to mode %o.", path, new_mode);
+ if (fchmod_opath(fd, new_mode) < 0)
+ return log_error_errno(errno, "fchmod() of %s failed: %m", path);
+ }
+ }
+ }
+
shortcut:
return label_fix(path, 0);
}