diff options
author | Zbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl> | 2019-05-22 17:23:28 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-05-22 17:23:28 +0200 |
commit | 9e099c9fd8283f263650a7b357f0fae609518678 (patch) | |
tree | 1f854f145c53850443f1111764d9fb6151708bb6 /src/tmpfiles | |
parent | 92c605796de9de3632b86228232a52b3076ec14c (diff) | |
parent | 34015aea1703c7240e5d0188a98aed4e8aab5b3b (diff) | |
download | systemd-9e099c9fd8283f263650a7b357f0fae609518678.tar.gz systemd-9e099c9fd8283f263650a7b357f0fae609518678.tar.bz2 systemd-9e099c9fd8283f263650a7b357f0fae609518678.zip |
Merge pull request #12431 from poettering/tmpfiles-chmod-chown-order
tmpfiles: run chown() before chmod()
Diffstat (limited to 'src/tmpfiles')
-rw-r--r-- | src/tmpfiles/tmpfiles.c | 76 |
1 files changed, 57 insertions, 19 deletions
diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index 73e0e33d0a..eabc51101d 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -776,8 +776,24 @@ static bool hardlink_vulnerable(const struct stat *st) { return !S_ISDIR(st->st_mode) && st->st_nlink > 1 && dangerous_hardlinks(); } +static mode_t process_mask_perms(mode_t mode, mode_t current) { + + if ((current & 0111) == 0) + mode &= ~0111; + if ((current & 0222) == 0) + mode &= ~0222; + if ((current & 0444) == 0) + mode &= ~0444; + if (!S_ISDIR(current)) + mode &= ~07000; /* remove sticky/sgid/suid bit, unless directory */ + + return mode; +} + 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); @@ -797,35 +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); - if (i->mode_set) { + /* 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 (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) { - if (!(st->st_mode & 0111)) - m &= ~0111; - if (!(st->st_mode & 0222)) - m &= ~0222; - if (!(st->st_mode & 0444)) - m &= ~0444; - if (!S_ISDIR(st->st_mode)) - m &= ~07000; /* remove sticky/sgid/suid bit, unless directory */ - } + 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, @@ -839,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); } |