]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
rm-rf: fstatat() might fail if containing dir has limited access mode, patch that too
authorLennart Poettering <lennart@poettering.net>
Tue, 26 Jan 2021 15:47:07 +0000 (16:47 +0100)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Thu, 4 Mar 2021 05:50:50 +0000 (14:50 +0900)
src/basic/rm-rf.c

index 4c39ce829042006d9961cb778ac56ee4167208bc..2f2ebc39e9b628e0858b9e3518ad24a167810cf0 100644 (file)
@@ -23,13 +23,38 @@ static bool is_physical_fs(const struct statfs *sfs) {
         return !is_temporary_fs(sfs) && !is_cgroup_fs(sfs);
 }
 
+static int patch_dirfd_mode(
+                int dfd,
+                mode_t *ret_old_mode) {
+
+        struct stat st;
+
+        assert(dfd >= 0);
+        assert(ret_old_mode);
+
+        if (fstat(dfd, &st) < 0)
+                return -errno;
+        if (!S_ISDIR(st.st_mode))
+                return -ENOTDIR;
+        if (FLAGS_SET(st.st_mode, 0700)) /* Already set? */
+                return -EACCES; /* original error */
+        if (st.st_uid != geteuid())  /* this only works if the UID matches ours */
+                return -EACCES;
+
+        if (fchmod(dfd, (st.st_mode | 0700) & 07777) < 0)
+                return -errno;
+
+        *ret_old_mode = st.st_mode;
+        return 0;
+}
+
 static int unlinkat_harder(
                 int dfd,
                 const char *filename,
                 int unlink_flags,
                 RemoveFlags remove_flags) {
 
-        struct stat st;
+        mode_t old_mode;
         int r;
 
         /* Like unlinkat(), but tries harder: if we get EACCESS we'll try to set the r/w/x bits on the
@@ -41,22 +66,46 @@ static int unlinkat_harder(
         if (errno != EACCES || !FLAGS_SET(remove_flags, REMOVE_CHMOD))
                 return -errno;
 
-        if (fstat(dfd, &st) < 0)
-                return -errno;
-        if (!S_ISDIR(st.st_mode))
-                return -ENOTDIR;
-        if (FLAGS_SET(st.st_mode, 0700)) /* Already set? */
-                return -EACCES; /* original error */
-        if (st.st_uid != geteuid())  /* this only works if the UID matches ours */
-                return -EACCES;
-
-        if (fchmod(dfd, (st.st_mode | 0700) & 07777) < 0)
-                return -errno;
+        r = patch_dirfd_mode(dfd, &old_mode);
+        if (r < 0)
+                return r;
 
         if (unlinkat(dfd, filename, unlink_flags) < 0) {
                 r = -errno;
                 /* Try to restore the original access mode if this didn't work */
-                (void) fchmod(dfd, st.st_mode & 07777);
+                (void) fchmod(dfd, old_mode);
+                return r;
+        }
+
+        /* If this worked, we won't reset the old mode, since we'll need it for other entries too, and we
+         * should destroy the whole thing */
+        return 0;
+}
+
+static int fstatat_harder(
+                int dfd,
+                const char *filename,
+                struct stat *ret,
+                int fstatat_flags,
+                RemoveFlags remove_flags) {
+
+        mode_t old_mode;
+        int r;
+
+        /* Like unlink_harder() but does the same for fstatat() */
+
+        if (fstatat(dfd, filename, ret, fstatat_flags) >= 0)
+                return 0;
+        if (errno != EACCES || !FLAGS_SET(remove_flags, REMOVE_CHMOD))
+                return -errno;
+
+        r = patch_dirfd_mode(dfd, &old_mode);
+        if (r < 0)
+                return r;
+
+        if (fstatat(dfd, filename, ret, fstatat_flags) < 0) {
+                r = -errno;
+                (void) fchmod(dfd, old_mode);
                 return r;
         }
 
@@ -112,9 +161,10 @@ int rm_rf_children(int fd, RemoveFlags flags, struct stat *root_dev) {
 
                 if (de->d_type == DT_UNKNOWN ||
                     (de->d_type == DT_DIR && (root_dev || (flags & REMOVE_SUBVOLUME)))) {
-                        if (fstatat(fd, de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) {
-                                if (ret == 0 && errno != ENOENT)
-                                        ret = -errno;
+                        r = fstatat_harder(fd, de->d_name, &st, AT_SYMLINK_NOFOLLOW, flags);
+                        if (r < 0) {
+                                if (ret == 0 && r != -ENOENT)
+                                        ret = r;
                                 continue;
                         }