1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
|
From: Lennart Poettering <lennart@poettering.net>
Date: Tue, 26 Jan 2021 16:47:07 +0100
Subject: rm-rf: fstatat() might fail if containing dir has limited access
mode, patch that too
(cherry picked from commit 1b55621dabf741dd963f59ac706ea62cd6e3e95c)
(cherry picked from commit ce53b81a600e2162ee86e2f4d202e7f28eceb2c6)
---
src/basic/rm-rf.c | 82 ++++++++++++++++++++++++++++++++++++++++++++-----------
1 file changed, 66 insertions(+), 16 deletions(-)
diff --git a/src/basic/rm-rf.c b/src/basic/rm-rf.c
index 4c39ce8..2f2ebc3 100644
--- a/src/basic/rm-rf.c
+++ b/src/basic/rm-rf.c
@@ -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;
}
|