blob: f348f3f2bddd8cfed897ac1b52ee69be3d0d2600 [file] [log] [blame]
Andrew Geisslerc926e172021-05-07 16:11:35 -05001From a3fdbbc7f271bff7d53d0501b29d910ece0b3789 Mon Sep 17 00:00:00 2001
2From: Stefan Hajnoczi <stefanha@redhat.com>
3Date: Thu, 4 Feb 2021 15:02:08 +0000
4Subject: [PATCH] virtiofsd: prevent opening of special files (CVE-2020-35517)
5
6A well-behaved FUSE client does not attempt to open special files with
7FUSE_OPEN because they are handled on the client side (e.g. device nodes
8are handled by client-side device drivers).
9
10The check to prevent virtiofsd from opening special files is missing in
11a few cases, most notably FUSE_OPEN. A malicious client can cause
12virtiofsd to open a device node, potentially allowing the guest to
13escape. This can be exploited by a modified guest device driver. It is
14not exploitable from guest userspace since the guest kernel will handle
15special files inside the guest instead of sending FUSE requests.
16
17This patch fixes this issue by introducing the lo_inode_open() function
18to check the file type before opening it. This is a short-term solution
19because it does not prevent a compromised virtiofsd process from opening
20device nodes on the host.
21
22Restructure lo_create() to try O_CREAT | O_EXCL first. Note that O_CREAT
23| O_EXCL does not follow symlinks, so O_NOFOLLOW masking is not
24necessary here. If the file exists and the user did not specify O_EXCL,
25open it via lo_do_open().
26
27Reported-by: Alex Xu <alex@alxu.ca>
28Fixes: CVE-2020-35517
29Reviewed-by: Dr. David Alan Gilbert <dgilbert@redhat.com>
30Reviewed-by: Vivek Goyal <vgoyal@redhat.com>
31Reviewed-by: Greg Kurz <groug@kaod.org>
32Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
33Message-Id: <20210204150208.367837-4-stefanha@redhat.com>
34Signed-off-by: Dr. David Alan Gilbert <dgilbert@redhat.com>
35
36Upstream-Status: Backport
37[https://github.com/qemu/qemu/commit/a3fdbbc7f271bff7d53d0501b29d910ece0b3789]
38
39CVE: CVE-2020-35517
40
41Signed-off-by: Dr. David Alan Gilbert <dgilbert@redhat.com>
42Signed-off-by: Khairul Rohaizzat Jamaluddin <khairul.rohaizzat.jamaluddin@intel.com>
43---
44 tools/virtiofsd/passthrough_ll.c | 144 ++++++++++++++++++++-----------
45 1 file changed, 92 insertions(+), 52 deletions(-)
46
47diff --git a/tools/virtiofsd/passthrough_ll.c b/tools/virtiofsd/passthrough_ll.c
48index aa35fc6ba5a5..147b59338a18 100644
49--- a/tools/virtiofsd/passthrough_ll.c
50+++ b/tools/virtiofsd/passthrough_ll.c
51@@ -555,6 +555,38 @@ static int lo_fd(fuse_req_t req, fuse_ino_t ino)
52 return fd;
53 }
54
55+/*
56+ * Open a file descriptor for an inode. Returns -EBADF if the inode is not a
57+ * regular file or a directory.
58+ *
59+ * Use this helper function instead of raw openat(2) to prevent security issues
60+ * when a malicious client opens special files such as block device nodes.
61+ * Symlink inodes are also rejected since symlinks must already have been
62+ * traversed on the client side.
63+ */
64+static int lo_inode_open(struct lo_data *lo, struct lo_inode *inode,
65+ int open_flags)
66+{
67+ g_autofree char *fd_str = g_strdup_printf("%d", inode->fd);
68+ int fd;
69+
70+ if (!S_ISREG(inode->filetype) && !S_ISDIR(inode->filetype)) {
71+ return -EBADF;
72+ }
73+
74+ /*
75+ * The file is a symlink so O_NOFOLLOW must be ignored. We checked earlier
76+ * that the inode is not a special file but if an external process races
77+ * with us then symlinks are traversed here. It is not possible to escape
78+ * the shared directory since it is mounted as "/" though.
79+ */
80+ fd = openat(lo->proc_self_fd, fd_str, open_flags & ~O_NOFOLLOW);
81+ if (fd < 0) {
82+ return -errno;
83+ }
84+ return fd;
85+}
86+
87 static void lo_init(void *userdata, struct fuse_conn_info *conn)
88 {
89 struct lo_data *lo = (struct lo_data *)userdata;
90@@ -684,9 +716,9 @@ static void lo_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr,
91 if (fi) {
92 truncfd = fd;
93 } else {
94- sprintf(procname, "%i", ifd);
95- truncfd = openat(lo->proc_self_fd, procname, O_RDWR);
96+ truncfd = lo_inode_open(lo, inode, O_RDWR);
97 if (truncfd < 0) {
98+ errno = -truncfd;
99 goto out_err;
100 }
101 }
102@@ -848,7 +880,7 @@ static int lo_do_lookup(fuse_req_t req, fuse_ino_t parent, const char *name,
103 struct lo_inode *dir = lo_inode(req, parent);
104
105 if (inodep) {
106- *inodep = NULL;
107+ *inodep = NULL; /* in case there is an error */
108 }
109
110 /*
111@@ -1664,19 +1696,26 @@ static void update_open_flags(int writeback, int allow_direct_io,
112 }
113 }
114
115+/*
116+ * Open a regular file, set up an fd mapping, and fill out the struct
117+ * fuse_file_info for it. If existing_fd is not negative, use that fd instead
118+ * opening a new one. Takes ownership of existing_fd.
119+ *
120+ * Returns 0 on success or a positive errno.
121+ */
122 static int lo_do_open(struct lo_data *lo, struct lo_inode *inode,
123- struct fuse_file_info *fi)
124+ int existing_fd, struct fuse_file_info *fi)
125 {
126- char buf[64];
127 ssize_t fh;
128- int fd;
129+ int fd = existing_fd;
130
131 update_open_flags(lo->writeback, lo->allow_direct_io, fi);
132
133- sprintf(buf, "%i", inode->fd);
134- fd = openat(lo->proc_self_fd, buf, fi->flags & ~O_NOFOLLOW);
135- if (fd == -1) {
136- return errno;
137+ if (fd < 0) {
138+ fd = lo_inode_open(lo, inode, fi->flags);
139+ if (fd < 0) {
140+ return -fd;
141+ }
142 }
143
144 pthread_mutex_lock(&lo->mutex);
145@@ -1699,9 +1738,10 @@ static int lo_do_open(struct lo_data *lo, struct lo_inode *inode,
146 static void lo_create(fuse_req_t req, fuse_ino_t parent, const char *name,
147 mode_t mode, struct fuse_file_info *fi)
148 {
149- int fd;
150+ int fd = -1;
151 struct lo_data *lo = lo_data(req);
152 struct lo_inode *parent_inode;
153+ struct lo_inode *inode = NULL;
154 struct fuse_entry_param e;
155 int err;
156 struct lo_cred old = {};
157@@ -1727,36 +1767,38 @@ static void lo_create(fuse_req_t req, fuse_ino_t parent, const char *name,
158
159 update_open_flags(lo->writeback, lo->allow_direct_io, fi);
160
161- fd = openat(parent_inode->fd, name, (fi->flags | O_CREAT) & ~O_NOFOLLOW,
162- mode);
163+ /* Try to create a new file but don't open existing files */
164+ fd = openat(parent_inode->fd, name, fi->flags | O_CREAT | O_EXCL, mode);
165 err = fd == -1 ? errno : 0;
166- lo_restore_cred(&old);
167
168- if (!err) {
169- ssize_t fh;
170+ lo_restore_cred(&old);
171
172- pthread_mutex_lock(&lo->mutex);
173- fh = lo_add_fd_mapping(lo, fd);
174- pthread_mutex_unlock(&lo->mutex);
175- if (fh == -1) {
176- close(fd);
177- err = ENOMEM;
178- goto out;
179- }
180+ /* Ignore the error if file exists and O_EXCL was not given */
181+ if (err && (err != EEXIST || (fi->flags & O_EXCL))) {
182+ goto out;
183+ }
184
185- fi->fh = fh;
186- err = lo_do_lookup(req, parent, name, &e, NULL);
187+ err = lo_do_lookup(req, parent, name, &e, &inode);
188+ if (err) {
189+ goto out;
190 }
191- if (lo->cache == CACHE_NONE) {
192- fi->direct_io = 1;
193- } else if (lo->cache == CACHE_ALWAYS) {
194- fi->keep_cache = 1;
195+
196+ err = lo_do_open(lo, inode, fd, fi);
197+ fd = -1; /* lo_do_open() takes ownership of fd */
198+ if (err) {
199+ /* Undo lo_do_lookup() nlookup ref */
200+ unref_inode_lolocked(lo, inode, 1);
201 }
202
203 out:
204+ lo_inode_put(lo, &inode);
205 lo_inode_put(lo, &parent_inode);
206
207 if (err) {
208+ if (fd >= 0) {
209+ close(fd);
210+ }
211+
212 fuse_reply_err(req, err);
213 } else {
214 fuse_reply_create(req, &e, fi);
215@@ -1770,7 +1812,6 @@ static struct lo_inode_plock *lookup_create_plock_ctx(struct lo_data *lo,
216 pid_t pid, int *err)
217 {
218 struct lo_inode_plock *plock;
219- char procname[64];
220 int fd;
221
222 plock =
223@@ -1787,12 +1828,10 @@ static struct lo_inode_plock *lookup_create_plock_ctx(struct lo_data *lo,
224 }
225
226 /* Open another instance of file which can be used for ofd locks. */
227- sprintf(procname, "%i", inode->fd);
228-
229 /* TODO: What if file is not writable? */
230- fd = openat(lo->proc_self_fd, procname, O_RDWR);
231- if (fd == -1) {
232- *err = errno;
233+ fd = lo_inode_open(lo, inode, O_RDWR);
234+ if (fd < 0) {
235+ *err = -fd;
236 free(plock);
237 return NULL;
238 }
239@@ -1949,7 +1988,7 @@ static void lo_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
240 return;
241 }
242
243- err = lo_do_open(lo, inode, fi);
244+ err = lo_do_open(lo, inode, -1, fi);
245 lo_inode_put(lo, &inode);
246 if (err) {
247 fuse_reply_err(req, err);
248@@ -2014,39 +2053,40 @@ static void lo_flush(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
249 static void lo_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,
250 struct fuse_file_info *fi)
251 {
252+ struct lo_inode *inode = lo_inode(req, ino);
253+ struct lo_data *lo = lo_data(req);
254 int res;
255 int fd;
256- char *buf;
257
258 fuse_log(FUSE_LOG_DEBUG, "lo_fsync(ino=%" PRIu64 ", fi=0x%p)\n", ino,
259 (void *)fi);
260
261- if (!fi) {
262- struct lo_data *lo = lo_data(req);
263-
264- res = asprintf(&buf, "%i", lo_fd(req, ino));
265- if (res == -1) {
266- return (void)fuse_reply_err(req, errno);
267- }
268+ if (!inode) {
269+ fuse_reply_err(req, EBADF);
270+ return;
271+ }
272
273- fd = openat(lo->proc_self_fd, buf, O_RDWR);
274- free(buf);
275- if (fd == -1) {
276- return (void)fuse_reply_err(req, errno);
277+ if (!fi) {
278+ fd = lo_inode_open(lo, inode, O_RDWR);
279+ if (fd < 0) {
280+ res = -fd;
281+ goto out;
282 }
283 } else {
284 fd = lo_fi_fd(req, fi);
285 }
286
287 if (datasync) {
288- res = fdatasync(fd);
289+ res = fdatasync(fd) == -1 ? errno : 0;
290 } else {
291- res = fsync(fd);
292+ res = fsync(fd) == -1 ? errno : 0;
293 }
294 if (!fi) {
295 close(fd);
296 }
297- fuse_reply_err(req, res == -1 ? errno : 0);
298+out:
299+ lo_inode_put(lo, &inode);
300+ fuse_reply_err(req, res);
301 }
302
303 static void lo_read(fuse_req_t req, fuse_ino_t ino, size_t size, off_t offset,