Merge pull request #12431 from poettering/tmpfiles-chmod-chown-order

tmpfiles: run chown() before chmod()
This commit is contained in:
Zbigniew Jędrzejewski-Szmek 2019-05-22 17:23:28 +02:00 committed by GitHub
commit 9e099c9fd8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 124 additions and 24 deletions

View file

@ -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;
mode_t m = new_mode & st->st_mode; /* Mask new mode by old 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 */
}
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);
}

View file

@ -0,0 +1,59 @@
#!/bin/bash
set -e
set -x
# Make sure that the "stat" output is not locale dependent.
export LANG=C LC_ALL=C
# first, create file without suid/sgid
systemd-tmpfiles --create - <<EOF
f /tmp/xxx 0755 1 1 - -
f /tmp/yyy 0755 1 1 - -
EOF
test "$(stat -c %F:%u:%g:%a /tmp/xxx)" = "regular empty file:1:1:755"
test "$(stat -c %F:%u:%g:%a /tmp/yyy)" = "regular empty file:1:1:755"
# then, add suid/sgid
systemd-tmpfiles --create - <<EOF
f /tmp/xxx 04755
f /tmp/yyy 02755
EOF
test "$(stat -c %F:%u:%g:%a /tmp/xxx)" = "regular empty file:1:1:4755"
test "$(stat -c %F:%u:%g:%a /tmp/yyy)" = "regular empty file:1:1:2755"
# then, chown the files to somebody else
systemd-tmpfiles --create - <<EOF
f /tmp/xxx - 2 2
f /tmp/yyy - 2 2
EOF
test "$(stat -c %F:%u:%g:%a /tmp/xxx)" = "regular empty file:2:2:4755"
test "$(stat -c %F:%u:%g:%a /tmp/yyy)" = "regular empty file:2:2:2755"
# then, chown the files to a third user/group but also drop to a mask that has
# both more and fewer bits set
systemd-tmpfiles --create - <<EOF
f /tmp/xxx 0770 3 3
f /tmp/yyy 0770 3 3
EOF
test "$(stat -c %F:%u:%g:%a /tmp/xxx)" = "regular empty file:3:3:770"
test "$(stat -c %F:%u:%g:%a /tmp/yyy)" = "regular empty file:3:3:770"
# return to the beginning
systemd-tmpfiles --create - <<EOF
f /tmp/xxx 0755 1 1 - -
f /tmp/yyy 0755 1 1 - -
EOF
test "$(stat -c %F:%u:%g:%a /tmp/xxx)" = "regular empty file:1:1:755"
test "$(stat -c %F:%u:%g:%a /tmp/yyy)" = "regular empty file:1:1:755"
# remove everything
systemd-tmpfiles --remove - <<EOF
r /tmp/xxx
r /tmp/yyy
EOF

View file

@ -706,17 +706,20 @@ install_libnss() {
install_dbus() {
inst $ROOTLIBDIR/system/dbus.socket
# Fedora rawhide replaced dbus.service with dbus-daemon.service
if [ -f $ROOTLIBDIR/system/dbus-daemon.service ]; then
# Newer Fedora versions use dbus-broker by default. Let's install it is available.
if [ -f $ROOTLIBDIR/system/dbus-broker.service ]; then
inst $ROOTLIBDIR/system/dbus-broker.service
inst_symlink /etc/systemd/system/dbus.service
inst /usr/bin/dbus-broker
inst /usr/bin/dbus-broker-launch
elif [ -f $ROOTLIBDIR/system/dbus-daemon.service ]; then
# Fedora rawhide replaced dbus.service with dbus-daemon.service
inst $ROOTLIBDIR/system/dbus-daemon.service
# Alias symlink
inst_symlink /etc/systemd/system/dbus.service
else
inst $ROOTLIBDIR/system/dbus.service
fi
# Newer Fedora versions use dbus-broker by default. Let's install it is available.
[ -f /usr/bin/dbus-broker ] && inst /usr/bin/dbus-broker
[ -f /usr/bin/dbus-broker-launch ] && inst /usr/bin/dbus-broker-launch
find \
/etc/dbus-1 /usr/share/dbus-1 -xtype f \