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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
|
From 96ec957e281220f8e137a2d5eb23b83a6377d556 Mon Sep 17 00:00:00 2001
From: Panu Matilainen <pmatilai@redhat.com>
Date: Thu, 10 Feb 2022 14:32:43 +0200
Subject: [PATCH] Validate intermediate symlinks during installation,
CVE-2021-35939
Whenever directory changes during unpacking, walk the entire tree from
starting from / and validate any symlinks crossed, fail the install
on invalid links.
This is the first of step of many towards securing our file operations
against local tamperers and besides plugging that one CVE, paves the way
for the next step by adding the necessary directory fd tracking.
This also bumps the rpm OS requirements to a whole new level by requiring
the *at() family of calls from POSIX-1.2008.
This necessarily does a whole lot of huffing and puffing we previously
did not do. It should be possible to cache secure (ie root-owned)
directory structures to avoid validating everything a million times
but for now, just keeping things simple.
---
INSTALL | 2 +
configure.ac | 3 +-
lib/fsm.c | 144 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
3 files changed, 142 insertions(+), 7 deletions(-)
diff --git a/INSTALL b/INSTALL
index 677ef88..961a160 100644
--- a/INSTALL
+++ b/INSTALL
@@ -103,6 +103,8 @@ option to configure). For GCC, OpenMP 4.5 is fully supported since GCC 6.1,
which is available from
http://www.gnu.org/
+Rpm requires a POSIX.1-2008 level operating system.
+
To compile RPM:
--------------
diff --git a/configure.ac b/configure.ac
index 3ee3407..0099e5f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -580,7 +580,8 @@ AC_CHECK_FUNCS([secure_getenv __secure_getenv])
AC_CHECK_FUNCS(
[mkstemp getcwd basename dirname realpath setenv unsetenv regcomp lchown \
- utimes getline localtime_r statvfs getaddrinfo ],
+ utimes getline localtime_r statvfs getaddrinfo \
+ openat mkdirat fstatat ],
[], [AC_MSG_ERROR([function required by rpm])])
AC_LIBOBJ(fnmatch)
diff --git a/lib/fsm.c b/lib/fsm.c
index 9118983..b6b152a 100644
--- a/lib/fsm.c
+++ b/lib/fsm.c
@@ -8,6 +8,7 @@
#include <inttypes.h>
#include <utime.h>
#include <errno.h>
+#include <fcntl.h>
#if WITH_CAP
#include <sys/capability.h>
#endif
@@ -20,6 +21,7 @@
#include "rpmio/rpmio_internal.h" /* fdInit/FiniDigest */
#include "lib/fsm.h"
#include "lib/rpmte_internal.h" /* XXX rpmfs */
+#include "lib/rpmfi_internal.h" /* rpmfiSetOnChdir */
#include "lib/rpmplugins.h" /* rpm plugins hooks */
#include "lib/rpmug.h"
@@ -406,17 +408,118 @@ static int fsmRmdir(const char *path)
return rc;
}
-static int fsmMkdir(const char *path, mode_t mode)
+static int fsmMkdir(int dirfd, const char *path, mode_t mode)
{
- int rc = mkdir(path, (mode & 07777));
+ int rc = mkdirat(dirfd, path, (mode & 07777));
if (_fsm_debug)
- rpmlog(RPMLOG_DEBUG, " %8s (%s, 0%04o) %s\n", __func__,
- path, (unsigned)(mode & 07777),
+ rpmlog(RPMLOG_DEBUG, " %8s (%d %s, 0%04o) %s\n", __func__,
+ dirfd, path, (unsigned)(mode & 07777),
(rc < 0 ? strerror(errno) : ""));
if (rc < 0) rc = RPMERR_MKDIR_FAILED;
return rc;
}
+static int fsmOpenat(int dirfd, const char *path, int flags)
+{
+ struct stat lsb, sb;
+ int sflags = flags | O_NOFOLLOW;
+ int fd = openat(dirfd, path, sflags);
+
+ /*
+ * Only ever follow symlinks by root or target owner. Since we can't
+ * open the symlink itself, the order matters: we stat the link *after*
+ * opening the target, and if the link ownership changed between the calls
+ * it could've only been the link owner or root.
+ */
+ if (fd < 0 && errno == ELOOP && flags != sflags) {
+ int ffd = openat(dirfd, path, flags);
+ if (ffd >= 0 && fstatat(dirfd, path, &lsb, AT_SYMLINK_NOFOLLOW) == 0) {
+ if (fstat(ffd, &sb) == 0) {
+ if (lsb.st_uid == 0 || lsb.st_uid == sb.st_uid) {
+ fd = ffd;
+ } else {
+ close(ffd);
+ }
+ }
+ }
+ }
+ return fd;
+}
+
+static int fsmDoMkDir(rpmPlugins plugins, int dirfd, const char *dn,
+ int owned, mode_t mode)
+{
+ int rc;
+ rpmFsmOp op = (FA_CREATE);
+ if (!owned)
+ op |= FAF_UNOWNED;
+
+ /* Run fsm file pre hook for all plugins */
+ rc = rpmpluginsCallFsmFilePre(plugins, NULL, dn, mode, op);
+
+ if (!rc)
+ rc = fsmMkdir(dirfd, dn, mode);
+
+ if (!rc) {
+ rc = rpmpluginsCallFsmFilePrepare(plugins, NULL, dn, dn, mode, op);
+ }
+
+ /* Run fsm file post hook for all plugins */
+ rpmpluginsCallFsmFilePost(plugins, NULL, dn, mode, op, rc);
+
+ if (!rc) {
+ rpmlog(RPMLOG_DEBUG,
+ "%s directory created with perms %04o\n",
+ dn, (unsigned)(mode & 07777));
+ }
+
+ return rc;
+}
+
+static int ensureDir(rpmPlugins plugins, const char *p, int owned, int create)
+{
+ char *path = xstrdup(p);
+ char *dp = path;
+ char *sp = NULL, *bn;
+ int oflags = O_RDONLY;
+
+ int dirfd = fsmOpenat(-1, "/", oflags);
+ int fd = dirfd; /* special case of "/" */
+
+ while ((bn = strtok_r(dp, "/", &sp)) != NULL) {
+ struct stat sb;
+ fd = fsmOpenat(dirfd, bn, oflags);
+
+ if (fd < 0 && errno == ENOENT && create) {
+ mode_t mode = S_IFDIR | (_dirPerms & 07777);
+ if (fsmDoMkDir(plugins, dirfd, bn, owned, mode) == 0) {
+ fd = fsmOpenat(dirfd, bn, oflags|O_NOFOLLOW);
+ }
+ }
+
+ if (fd >= 0 && fstat(fd, &sb) == 0 && !S_ISDIR(sb.st_mode)) {
+ close(fd);
+ errno = ENOTDIR;
+ fd = -1;
+ }
+
+ close(dirfd);
+ if (fd >= 0) {
+ dirfd = fd;
+ } else {
+ dirfd = -1;
+ rpmlog(RPMLOG_ERR, _("failed to open dir %s of %s: %s\n"),
+ bn, p, strerror(errno));
+ break;
+ }
+
+ dp = NULL;
+ }
+
+ free(path);
+ return dirfd;
+}
+
static int fsmMkfifo(const char *path, mode_t mode)
{
int rc = mkfifo(path, (mode & 07777));
@@ -507,7 +610,7 @@ static int fsmMkdirs(rpmfiles files, rpmfs fs, rpmPlugins plugins)
rc = rpmpluginsCallFsmFilePre(plugins, NULL, dn, mode, op);
if (!rc)
- rc = fsmMkdir(dn, mode);
+ rc = fsmMkdir(-1, dn, mode);
if (!rc) {
rc = rpmpluginsCallFsmFilePrepare(plugins, NULL, dn, dn,
@@ -874,6 +977,21 @@ static void setFileState(rpmfs fs, int i)
}
}
+struct diriter_s {
+ int dirfd;
+};
+
+static int onChdir(rpmfi fi, void *data)
+{
+ struct diriter_s *di = data;
+
+ if (di->dirfd >= 0) {
+ close(di->dirfd);
+ di->dirfd = -1;
+ }
+ return 0;
+}
+
int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files,
rpmpsm psm, char ** failedFile)
{
@@ -890,6 +1008,7 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files,
char *tid = NULL;
struct filedata_s *fdata = xcalloc(fc, sizeof(*fdata));
struct filedata_s *firstlink = NULL;
+ struct diriter_s di = { -1 };
/* transaction id used for temporary path suffix while installing */
rasprintf(&tid, ";%08x", (unsigned)rpmtsGetTid(ts));
@@ -932,6 +1051,7 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files,
rc = RPMERR_BAD_MAGIC;
goto exit;
}
+ rpmfiSetOnChdir(fi, onChdir, &di);
/* Detect and create directories not explicitly in package. */
if (!rc)
@@ -1063,6 +1063,16 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files,
if (!fp->suffix) {
rc = fsmBackup(fi, fp->action);
}
+
+ if (di.dirfd == -1) {
+ di.dirfd = ensureDir(plugins, rpmfiDN(fi), 0,
+ (fp->action == FA_CREATE));
+ if (di.dirfd == -1) {
+ rc = RPMERR_OPEN_FAILED;
+ break;
+ }
+ }
+
/* Assume file does't exist when tmp suffix is in use */
if (!fp->suffix) {
rc = fsmVerify(fp->fpath, fi);
@@ -980,7 +1110,7 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files,
mode_t mode = fp->sb.st_mode;
mode &= ~07777;
mode |= 00700;
- rc = fsmMkdir(fp->fpath, mode);
+ rc = fsmMkdir(di.dirfd, fp->fpath, mode);
}
} else if (S_ISLNK(fp->sb.st_mode)) {
if (rc == RPMERR_ENOENT) {
@@ -1022,6 +1152,8 @@ int rpmPackageFilesInstall(rpmts ts, rpmte te, rpmfiles files,
fp->stage = FILE_UNPACK;
}
fi = rpmfiFree(fi);
+ close(di.dirfd);
+ di.dirfd = -1;
if (!rc && fx < 0 && fx != RPMERR_ITER_END)
rc = fx;
--
1.8.3.1
|