about summary refs log tree commit diff
path: root/shared/systemd/src/basic/fs-util.c
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2019-07-31 10:51:42 +0200
committerMichael Biebl <biebl@debian.org>2019-07-31 10:51:42 +0200
commit2e5fa45ddfbb5cffa1e78221f1cea706e2f298af (patch)
tree86f69d36c56de3074280456eddc854a780b8e04b /shared/systemd/src/basic/fs-util.c
parent85563b7fc7ec2cd21e38debb9b28db342e2e8e7c (diff)
New upstream version 1.19.90 upstream/1.19.90
Diffstat (limited to 'shared/systemd/src/basic/fs-util.c')
-rw-r--r--shared/systemd/src/basic/fs-util.c128
1 files changed, 37 insertions, 91 deletions
diff --git a/shared/systemd/src/basic/fs-util.c b/shared/systemd/src/basic/fs-util.c
index be85eef1..56385fa2 100644
--- a/shared/systemd/src/basic/fs-util.c
+++ b/shared/systemd/src/basic/fs-util.c
@@ -218,113 +218,65 @@ int readlink_and_make_absolute(const char *p, char **r) {
 }
 
 int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid) {
-        char fd_path[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int) + 1];
         _cleanup_close_ int fd = -1;
-        bool st_valid = false;
-        struct stat st;
-        int r;
 
         assert(path);
 
-        /* Under the assumption that we are running privileged we first change the access mode and only then
-         * hand out ownership to avoid a window where access is too open. */
-
         fd = open(path, O_PATH|O_CLOEXEC|O_NOFOLLOW); /* Let's acquire an O_PATH fd, as precaution to change
                                                        * mode/owner on the same file */
         if (fd < 0)
                 return -errno;
 
-        xsprintf(fd_path, "/proc/self/fd/%i", fd);
-
-        if (mode != MODE_INVALID) {
-                if ((mode & S_IFMT) != 0) {
-
-                        if (stat(fd_path, &st) < 0)
-                                return -errno;
-
-                        if ((mode & S_IFMT) != (st.st_mode & S_IFMT))
-                                return -EINVAL;
-
-                        st_valid = true;
-                }
-
-                if (chmod(fd_path, mode & 07777) < 0) {
-                        r = -errno;
-
-                        if (!st_valid && stat(fd_path, &st) < 0)
-                                return -errno;
-
-                        if ((mode & 07777) != (st.st_mode & 07777))
-                                return r;
-
-                        st_valid = true;
-                }
-        }
-
-        if (uid != UID_INVALID || gid != GID_INVALID) {
-                if (chown(fd_path, uid, gid) < 0) {
-                        r = -errno;
-
-                        if (!st_valid && stat(fd_path, &st) < 0)
-                                return -errno;
-
-                        if (uid != UID_INVALID && st.st_uid != uid)
-                                return r;
-                        if (gid != GID_INVALID && st.st_gid != gid)
-                                return r;
-                }
-        }
-
-        return 0;
+        return fchmod_and_chown(fd, mode, uid, gid);
 }
 
 int fchmod_and_chown(int fd, mode_t mode, uid_t uid, gid_t gid) {
-        bool st_valid = false;
+        bool do_chown, do_chmod;
         struct stat st;
-        int r;
 
-        /* Under the assumption that we are running privileged we first change the access mode and only then hand out
-         * ownership to avoid a window where access is too open. */
+        /* Change ownership and access mode of the specified fd. Tries to do so safely, ensuring that at no
+         * point in time the access mode is above the old access mode under the old ownership or the new
+         * access mode under the new ownership. Note: this call tries hard to leave the access mode
+         * unaffected if the uid/gid is changed, i.e. it undoes implicit suid/sgid dropping the kernel does
+         * on chown().
+         *
+         * This call is happy with O_PATH fds. */
 
-        if (mode != MODE_INVALID) {
-                if ((mode & S_IFMT) != 0) {
+        if (fstat(fd, &st) < 0)
+                return -errno;
 
-                        if (fstat(fd, &st) < 0)
-                                return -errno;
+        do_chown =
+                (uid != UID_INVALID && st.st_uid != uid) ||
+                (gid != GID_INVALID && st.st_gid != gid);
 
-                        if ((mode & S_IFMT) != (st.st_mode & S_IFMT))
-                                return -EINVAL;
+        do_chmod =
+                !S_ISLNK(st.st_mode) && /* chmod is not defined on symlinks */
+                ((mode != MODE_INVALID && ((st.st_mode ^ mode) & 07777) != 0) ||
+                 do_chown); /* If we change ownership, make sure we reset the mode afterwards, since chown()
+                             * modifies the access mode too */
 
-                        st_valid = true;
-                }
+        if (mode == MODE_INVALID)
+                mode = st.st_mode; /* If we only shall do a chown(), save original mode, since chown() might break it. */
+        else if ((mode & S_IFMT) != 0 && ((mode ^ st.st_mode) & S_IFMT) != 0)
+                return -EINVAL; /* insist on the right file type if it was specified */
 
-                if (fchmod(fd, mode & 07777) < 0) {
-                        r = -errno;
+        if (do_chown && do_chmod) {
+                mode_t minimal = st.st_mode & mode; /* the subset of the old and the new mask */
 
-                        if (!st_valid && fstat(fd, &st) < 0)
+                if (((minimal ^ st.st_mode) & 07777) != 0)
+                        if (fchmod_opath(fd, minimal & 07777) < 0)
                                 return -errno;
-
-                        if ((mode & 07777) != (st.st_mode & 07777))
-                                return r;
-
-                        st_valid = true;
-                }
         }
 
-        if (uid != UID_INVALID || gid != GID_INVALID)
-                if (fchown(fd, uid, gid) < 0) {
-                        r = -errno;
-
-                        if (!st_valid && fstat(fd, &st) < 0)
-                                return -errno;
+        if (do_chown)
+                if (fchownat(fd, "", uid, gid, AT_EMPTY_PATH) < 0)
+                        return -errno;
 
-                        if (uid != UID_INVALID && st.st_uid != uid)
-                                return r;
-                        if (gid != GID_INVALID && st.st_gid != gid)
-                                return r;
-                }
+        if (do_chmod)
+                if (fchmod_opath(fd, mode & 07777) < 0)
+                        return -errno;
 
-        return 0;
+        return do_chown || do_chmod;
 }
 #endif /* NM_IGNORED */
 
@@ -411,13 +363,7 @@ int touch_file(const char *path, bool parents, usec_t stamp, uid_t uid, gid_t gi
          * something fchown(), fchmod(), futimensat() don't allow. */
         xsprintf(fdpath, "/proc/self/fd/%i", fd);
 
-        if (mode != MODE_INVALID)
-                if (chmod(fdpath, mode) < 0)
-                        ret = -errno;
-
-        if (uid_is_valid(uid) || gid_is_valid(gid))
-                if (chown(fdpath, uid, gid) < 0 && ret >= 0)
-                        ret = -errno;
+        ret = fchmod_and_chown(fd, mode, uid, gid);
 
         if (stamp != USEC_INFINITY) {
                 struct timespec ts[2];
@@ -1043,9 +989,9 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
 
                                 /* Prefix what's left to do with what we just read, and start the loop again, but
                                  * remain in the current directory. */
-                                joined = strjoin(destination, todo);
+                                joined = path_join(destination, todo);
                         } else
-                                joined = strjoin("/", destination, todo);
+                                joined = path_join("/", destination, todo);
                         if (!joined)
                                 return -ENOMEM;