1
0
mirror of https://github.com/systemd/systemd.git synced 2025-03-31 14:50:15 +03:00

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
This commit is contained in:
Lennart Poettering 2019-04-29 16:11:52 +02:00
parent 8b364a3823
commit a9f55d0e7e

View File

@ -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;
mode_t m = new_mode & st->st_mode; /* Mask new mode by old mode */
if (i->mask_perms)
m = process_mask_perms(m, st->st_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);
}