summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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);
}