-
Notifications
You must be signed in to change notification settings - Fork 1
/
interceptor.c
596 lines (477 loc) · 18.4 KB
/
interceptor.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
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <asm/current.h>
#include <asm/ptrace.h>
#include <linux/sched.h>
#include <linux/cred.h>
#include <asm/unistd.h>
#include <linux/spinlock.h>
#include <linux/semaphore.h>
#include <linux/syscalls.h>
#include "interceptor.h"
MODULE_DESCRIPTION("My kernel module");
MODULE_AUTHOR("Me");
MODULE_LICENSE("GPL");
//----- System Call Table Stuff ------------------------------------
/* Symbol that allows access to the kernel system call table */
extern void* sys_call_table[];
/* The sys_call_table is read-only => must make it RW before replacing a syscall */
void set_addr_rw(unsigned long addr) {
unsigned int level;
pte_t *pte = lookup_address(addr, &level);
if (pte->pte &~ _PAGE_RW) pte->pte |= _PAGE_RW;
}
/* Restores the sys_call_table as read-only */
void set_addr_ro(unsigned long addr) {
unsigned int level;
pte_t *pte = lookup_address(addr, &level);
pte->pte = pte->pte &~_PAGE_RW;
}
//-------------------------------------------------------------
//----- Data structures and bookkeeping -----------------------
/**
* This block contains the data structures needed for keeping track of
* intercepted system calls (including their original calls), pid monitoring
* synchronization on shared data, etc.
* It's highly unlikely that you will need any globals other than these.
*/
/* List structure - each intercepted syscall may have a list of monitored pids */
struct pid_list {
pid_t pid;
struct list_head list;
};
/* Store info about intercepted/replaced system calls */
typedef struct {
/* Original system call */
asmlinkage long (*f)(struct pt_regs);
/* Status: 1=intercepted, 0=not intercepted */
int intercepted;
/* Are any PIDs being monitored for this syscall? */
int monitored;
/* List of monitored PIDs */
int listcount;
struct list_head my_list;
}mytable;
/* An entry for each system call */
mytable table[NR_syscalls+1];
/* Access to the table and pid lists must be synchronized */
spinlock_t pidlist_lock = SPIN_LOCK_UNLOCKED;
spinlock_t calltable_lock = SPIN_LOCK_UNLOCKED;
//-------------------------------------------------------------
//----------LIST OPERATIONS------------------------------------
/**
* These operations are meant for manipulating the list of pids
* Nothing to do here, but please make sure to read over these functions
* to understand their purpose, as you will need to use them!
*/
/**
* Add a pid to a syscall's list of monitored pids.
* Returns -ENOMEM if the operation is unsuccessful.
*/
static int add_pid_sysc(pid_t pid, int sysc)
{
struct pid_list *ple=(struct pid_list*)kmalloc(sizeof(struct pid_list), GFP_KERNEL);
if (!ple)
return -ENOMEM; //out of memory
INIT_LIST_HEAD(&ple->list);
ple->pid=pid;
list_add(&ple->list, &(table[sysc].my_list));
table[sysc].listcount++;
return 0;
}
/**
* Remove a pid from a system call's list of monitored pids.
* Returns -EINVAL if no such pid was found in the list.
*/
static int del_pid_sysc(pid_t pid, int sysc)
{
struct list_head *i;
struct pid_list *ple;
list_for_each(i, &(table[sysc].my_list)) {
ple=list_entry(i, struct pid_list, list);
if(ple->pid == pid) {
list_del(i);
kfree(ple);
table[sysc].listcount--;
/* If there are no more pids in sysc's list of pids, then
* stop the monitoring only if it's not for all pids (monitored=2) */
if(table[sysc].listcount == 0 && table[sysc].monitored == 1) {
table[sysc].monitored = 0;
}
return 0;
}
}
return -EINVAL;
}
/**
* Remove a pid from all the lists of monitored pids (for all intercepted syscalls).
* Returns -1 if this process is not being monitored in any list.
*/
static int del_pid(pid_t pid)
{
struct list_head *i, *n;
struct pid_list *ple;
int ispid = 0, s = 0;
for(s = 1; s < NR_syscalls; s++) {
list_for_each_safe(i, n, &(table[s].my_list)) {
ple=list_entry(i, struct pid_list, list);
if(ple->pid == pid) {
list_del(i);
ispid = 1;
kfree(ple);
table[s].listcount--;
/* If there are no more pids in sysc's list of pids, then
* stop the monitoring only if it's not for all pids (monitored=2) */
if(table[s].listcount == 0 && table[s].monitored == 1) {
table[s].monitored = 0;
}
}
}
}
if (ispid) return 0;
return -1;
}
/**
* Clear the list of monitored pids for a specific syscall.
*/
static void destroy_list(int sysc) {
struct list_head *i, *n;
struct pid_list *ple;
list_for_each_safe(i, n, &(table[sysc].my_list)) {
ple=list_entry(i, struct pid_list, list);
list_del(i);
kfree(ple);
}
table[sysc].listcount = 0;
table[sysc].monitored = 0;
}
/**
* Check if two pids have the same owner - useful for checking if a pid
* requested to be monitored is owned by the requesting process.
* Remember that when requesting to start monitoring for a pid, only the
* owner of that pid is allowed to request that.
*/
static int check_pid_from_list(pid_t pid1, pid_t pid2) {
struct task_struct *p1 = pid_task(find_vpid(pid1), PIDTYPE_PID);
struct task_struct *p2 = pid_task(find_vpid(pid2), PIDTYPE_PID);
if(p1->real_cred->uid != p2->real_cred->uid)
return -EPERM;
return 0;
}
/**
* Check if a pid is already being monitored for a specific syscall.
* Returns 1 if it already is, or 0 if pid is not in sysc's list.
*/
static int check_pid_monitored(int sysc, pid_t pid) {
struct list_head *i;
struct pid_list *ple;
list_for_each(i, &(table[sysc].my_list)) {
ple=list_entry(i, struct pid_list, list);
if(ple->pid == pid)
return 1;
}
return 0;
}
//----------------------------------------------------------------
//----- Intercepting exit_group ----------------------------------
/**
* Since a process can exit without its owner specifically requesting
* to stop monitoring it, we must intercept the exit_group system call
* so that we can remove the exiting process's pid from *all* syscall lists.
*/
/**
* Stores original exit_group function - after all, we must restore it
* when our kernel module exits.
*/
void (*orig_exit_group)(int);
/**
* Our custom exit_group system call.
*
* TODO: When a process exits, make sure to remove that pid from all lists.
* The exiting process's PID can be retrieved using the current variable (current->pid).
* Don't forget to call the original exit_group.
*/
void my_exit_group(int status)
{
// remove pid from all lists
del_pid(current->pid);
// call exit_group function
orig_exit_group(status);
}
//----------------------------------------------------------------
/**
* This is the generic interceptor function.
* It should just log a message and call the original syscall.
*
* TODO: Implement this function.
* - Check first to see if the syscall is being monitored for the current->pid.
* - Recall the convention for the "monitored" flag in the mytable struct:
* monitored=0 => not monitored
* monitored=1 => some pids are monitored, check the corresponding my_list
* monitored=2 => all pids are monitored for this syscall
* - Use the log_message macro, to log the system call parameters!
* Remember that the parameters are passed in the pt_regs registers.
* The syscall parameters are found (in order) in the
* ax, bx, cx, dx, si, di, and bp registers (see the pt_regs struct).
* - Don't forget to call the original system call, so we allow processes to proceed as normal.
*/
asmlinkage long interceptor(struct pt_regs reg) {
// use ax of reg to find syscall and check whether pid is being monitered
if (table[reg.ax].monitored == 2 || check_pid_monitored(reg.ax, current->pid) == 1) {
// log a message
log_message(current->pid, reg.ax, reg.bx, reg.cx, reg.dx, reg.si, reg.di, reg.bp);
}
// call original system call
return table[reg.ax].f(reg);
}
/**
* My system call - this function is called whenever a user issues a MY_CUSTOM_SYSCALL system call.
* When that happens, the parameters for this system call indicate one of 4 actions/commands:
* - REQUEST_SYSCALL_INTERCEPT to intercept the 'syscall' argument
* - REQUEST_SYSCALL_RELEASE to de-intercept the 'syscall' argument
* - REQUEST_START_MONITORING to start monitoring for 'pid' whenever it issues 'syscall'
* - REQUEST_STOP_MONITORING to stop monitoring for 'pid'
* For the last two, if pid=0, that translates to "all pids".
*
* TODO: Implement this function, to handle all 4 commands correctly.
*
* - For each of the commands, check that the arguments are valid (-EINVAL):
* a) the syscall must be valid (not negative, not > NR_syscalls, and not MY_CUSTOM_SYSCALL itself)
* b) the pid must be valid for the last two commands. It cannot be a negative integer,
* and it must be an existing pid (except for the case when it's 0, indicating that we want
* to start/stop monitoring for "all pids").
* If a pid belongs to a valid process, then the following expression is non-NULL:
* pid_task(find_vpid(pid), PIDTYPE_PID)
* - Check that the caller has the right permissions (-EPERM)
* For the first two commands, we must be root (see the current_uid() macro).
* For the last two commands, the following logic applies:
* - is the calling process root? if so, all is good, no doubts about permissions.
* - if not, then check if the 'pid' requested is owned by the calling process
* - also, if 'pid' is 0 and the calling process is not root, then access is denied
* (monitoring all pids is allowed only for root, obviously).
* To determine if two pids have the same owner, use the helper function provided above in this file.
* - Check for correct context of commands (-EINVAL):
* a) Cannot de-intercept a system call that has not been intercepted yet.
* b) Cannot stop monitoring for a pid that is not being monitored, or if the
* system call has not been intercepted yet.
* - Check for -EBUSY conditions:
* a) If intercepting a system call that is already intercepted.
* b) If monitoring a pid that is already being monitored.
* - If a pid cannot be added to a monitored list, due to no memory being available,
* an -ENOMEM error code should be returned.
*
* - Make sure to keep track of all the metadata on what is being intercepted and monitored.
* Use the helper functions provided above for dealing with list operations.
*
* - Whenever altering the sys_call_table, make sure to use the set_addr_rw/set_addr_ro functions
* to make the system call table writable, then set it back to read-only.
* For example: set_addr_rw((unsigned long)sys_call_table);
* Also, make sure to save the original system call (you'll need it for 'interceptor' to work correctly).
*
* - Make sure to use synchronization to ensure consistency of shared data structures.
* Use the calltable_spinlock and pidlist_spinlock to ensure mutual exclusion for accesses
* to the system call table and the lists of monitored pids. Be careful to unlock any spinlocks
* you might be holding, before you exit the function (including error cases!).
*/
asmlinkage long my_syscall(int cmd, int syscall, int pid) {
// check all validity cases of syscall
if (syscall < 0 || syscall > NR_syscalls || syscall == MY_CUSTOM_SYSCALL ) {
return -EINVAL;
}
switch(cmd){
case 1: //REQUEST_SYSCALL_INTERCEPT
// check for proper permission
if (current_uid() != 0) { //if not root
return -EPERM;
}
//check whether trying to intercept something intercepted
else if (table[syscall].intercepted == 1) {
return -EBUSY;
}
// protect table data from being accessed concurrently
spin_lock(&calltable_lock);
// set to rw
set_addr_rw((long)&sys_call_table);
// backup the original system call
table[syscall].f = sys_call_table[syscall];
// replace call with interceptor
sys_call_table[syscall] = interceptor;
table[syscall].intercepted = 1;
// set back to ro
set_addr_ro((long)&sys_call_table);
//unlock spin lock
spin_unlock(&calltable_lock);
break;
case 2: //REQUEST_SYSCALL_RELEASE
// check for proper permission
if (current_uid() != 0) { //if not root
return -EPERM;
}
//check if call isn't intercepted
else if (table[syscall].intercepted == 0) {
return -EINVAL;
}
// protect table data from being accessed concurrently
spin_lock(&calltable_lock);
// set to rw
set_addr_rw((long)&sys_call_table);
// restore the original system call
sys_call_table[syscall] = table[syscall].f;
table[syscall].intercepted = 0;
// set back to ro
set_addr_ro((long)&sys_call_table);
//unlock spin lock
spin_unlock(&calltable_lock);
break;
case 3:
//check if pid valid, ignore special case where 0 means all
if (pid < 0 || (pid != 0 && pid_task(find_vpid(pid), PIDTYPE_PID) == NULL)) {
return -EINVAL;
}
// check if not root
else if (current_uid() != 0 && pid == 0) {
return -EPERM;
}
//process doesn't own pid
else if (current_uid() != 0 && check_pid_from_list(pid, current->pid) != 0) {
return -EPERM;
}
//check attempts to moniter a pid already monitored
else if ((check_pid_monitored(syscall, pid)) == 1) {
return -EBUSY;
}
if (pid == 0) {
//monitor all pids
destroy_list(syscall);
table[syscall].monitored = 2;
} else if (table[syscall].monitored != 2) {
// protect monitored pids from being accessed concurrently
spin_lock(&pidlist_lock);
// set monitored pids to 1 if it hasn't been monitoring yet
if (add_pid_sysc(pid, syscall) == 0 && table[syscall].monitored == 0) {
table[syscall].monitored = 1;
} else {
// if not enough memory to add pid to monitored list
return -ENOMEM;
}
// unlock spin lock
spin_unlock(&pidlist_lock);
}
break;
case 4:
//check if pid valid, ignore special case where 0 means all
if (pid < 0 || (pid != 0 && pid_task(find_vpid(pid), PIDTYPE_PID) == NULL)) {
return -EINVAL;
}
// check if not root
else if (current_uid() != 0 && pid == 0) {
return -EPERM;
}
//process doesn't own pid
else if (current_uid() != 0 && check_pid_from_list(pid, current->pid) != 0) {
return -EPERM;
}
// check if not intercepted or pid isn't being monitered
else if ((table[syscall].intercepted == 0) || (table[syscall].monitored != 2 && (check_pid_monitored(syscall, pid)) == 0)) {
return -EINVAL;
}
if (pid == 0) {
// stop monitoring all pids for syscall
// remove all pids
destroy_list(syscall);
table[syscall].monitored = 0;
} else {
// protect monitored pids from being accessed concurrently
spin_lock(&pidlist_lock);
// remove pid from monitored list
del_pid_sysc(pid, syscall);
// unlock spin lock
spin_unlock(&pidlist_lock);
}
break;
default:
return -EINVAL;
break;
}
return 0;
}
/**
*
*/
long (*orig_custom_syscall)(void);
/**
* Module initialization.
*
* TODO: Make sure to:
* - Hijack MY_CUSTOM_SYSCALL and save the original in orig_custom_syscall.
* - Hijack the exit_group system call (__NR_exit_group) and save the original
* in orig_exit_group.
* - Make sure to set the system call table to writable when making changes,
* then set it back to read only once done.
* - Perform any necessary initializations for bookkeeping data structures.
* To initialize a list, use
* INIT_LIST_HEAD (&some_list);
* where some_list is a "struct list_head".
* - Ensure synchronization as needed.
*/
static int init_function(void) {
int s;
// Hijack MY_CUSTOM_SYSCALL
// protect table data from being accessed concurrently
spin_lock(&calltable_lock);
// set to rw
set_addr_rw((long)&sys_call_table);
//save original syscall
orig_custom_syscall = sys_call_table[MY_CUSTOM_SYSCALL];
// replace the system call with our custom system call
sys_call_table[MY_CUSTOM_SYSCALL] = my_syscall;
// Hijack exit_group system
//save original exit group
orig_exit_group = sys_call_table[__NR_exit_group];
// replace the exit call with custom exit group
sys_call_table[__NR_exit_group] = my_exit_group;
// set back to ro
set_addr_ro((long)&sys_call_table);
//unlock spin lock
spin_unlock(&calltable_lock);
// initialize each table for every system call
for(s = 1; s < NR_syscalls; s++) {
// initialize
table[s].f = sys_call_table[s];
table[s].intercepted = 0;
table[s].monitored = 0;
table[s].listcount = 0;
// initialize head of pid list
INIT_LIST_HEAD (&(table[s].my_list));
}
return 0;
}
/**
* Module exits.
*
* TODO: Make sure to:
* - Restore MY_CUSTOM_SYSCALL to the original syscall.
* - Restore __NR_exit_group to its original syscall.
* - Make sure to set the system call table to writable when making changes,
* then set it back to read only once done.
* - Ensure synchronization, if needed.
*/
static void exit_function(void)
{
// restore MY_CUSTOM_SYSCALL to original
// protect table data from being accessed concurrently
spin_lock(&calltable_lock);
// set to rw
set_addr_rw((long)&sys_call_table);
//restore
sys_call_table[MY_CUSTOM_SYSCALL] = orig_custom_syscall;
//restore original exit group
sys_call_table[__NR_exit_group] = orig_exit_group;
// set back to ro
set_addr_ro((long)&sys_call_table);
//unlock spin lock
spin_unlock(&calltable_lock);
}
module_init(init_function);
module_exit(exit_function);