-
Notifications
You must be signed in to change notification settings - Fork 0
/
interceptor.c
522 lines (445 loc) · 17.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
#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;
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. Returns 0 if same owner.
*/
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)
{
pid_t pid = current->pid;//get lock and free it after modifying table(done by del_pid function), a shared data resource
spin_lock(&pidlist_lock);
del_pid(pid);//removing pid for all lists
spin_unlock(&pidlist_lock);
(*orig_exit_group)(status);//call original exit group function after we are done the work above since we need to restore it
}
//----------------------------------------------------------------
/**
* 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) {
pid_t pid = current->pid;
int sysc = reg.ax;
int result = check_pid_monitored(sysc, pid);
if(result == 1)//log a message if pid is monitored for specificed syscall
log_message(pid, sysc, reg.bx, (long unsigned int)reg.cx, reg.dx, reg.si, reg.di, reg.bp);
return table[sysc].f(reg);//call the original syscall function and return its return value
}
/*
De-intercept the specified syscall. Clear its' pidlist and set original call back in sys_call_table.
*/
void deintercept(int syscall){
spin_lock(&calltable_lock);//get lock and free it after done modifying sys_call_table, a shared data resource
set_addr_rw((unsigned long)sys_call_table);
sys_call_table[syscall] = table[syscall].f;
set_addr_ro((unsigned long)sys_call_table);
spin_unlock(&calltable_lock);
spin_lock(&pidlist_lock);//get lock and free it after done modifying table, a shared data resource
destroy_list(syscall);
table[syscall].intercepted = 0;
spin_unlock(&pidlist_lock);
}
/**
* 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 if syscall is valid
if(syscall < 0 || syscall > NR_syscalls || syscall == MY_CUSTOM_SYSCALL){
return -EINVAL;
}
//Check if pid is valid
if(pid < 0 || (pid_task(find_vpid(pid), PIDTYPE_PID) == NULL && pid != 0)){
return -EINVAL;
}
//Must be root to intercept or de-intercept system calls
if(cmd == REQUEST_SYSCALL_INTERCEPT || cmd == REQUEST_SYSCALL_RELEASE){
if(current_uid() != 0){
return -EPERM;
}
}
//Permissions checks
if(cmd == REQUEST_START_MONITORING || cmd == REQUEST_STOP_MONITORING){
if(current_uid() != 0){
//calling process is not root
if(pid == 0){ //non-root process can't monitor all processes
return -EPERM;
}
if(check_pid_from_list(pid, current->pid) != 0 || check_pid_from_list(pid, current->pid) == -EPERM){ //the two processes don't have same owner
return -EPERM;
}
}//process is root, all is good
}
switch(cmd){
case REQUEST_SYSCALL_INTERCEPT:
if(table[syscall].intercepted == 1){ //Cannot intercept a call that's already intercepted
return -EBUSY;
}
spin_lock(&calltable_lock);//get the lock, and then free it after we are done mofying sys_call_table
set_addr_rw((unsigned long)sys_call_table);
sys_call_table[syscall] = &interceptor;
set_addr_ro((unsigned long)sys_call_table);
spin_unlock(&calltable_lock);
spin_lock(&pidlist_lock);
table[syscall].intercepted = 1;
spin_unlock(&pidlist_lock);
break;
case REQUEST_SYSCALL_RELEASE:
if(table[syscall].intercepted == 0){ //Cannot de-intercept a call thats isnt intercepted yet
return -EINVAL;
}
deintercept(syscall);
break;
case REQUEST_START_MONITORING:
if(check_pid_monitored(syscall, pid) == 1){//pid is already being monitored
return -EBUSY;
}
if(table[syscall].intercepted == 0){//cannot start monitoring for a call thats not intercepted
return -EINVAL;
}
if(pid == 0){//all processes are monitored for this syscall
spin_lock(&pidlist_lock);
destroy_list(syscall);
table[syscall].monitored = 2;
spin_unlock(&pidlist_lock);
}
else{//add this pid to the monitored list
spin_lock(&pidlist_lock);
add_pid_sysc(pid, syscall);
spin_unlock(&pidlist_lock);
}
break;
case REQUEST_STOP_MONITORING:
if(table[syscall].intercepted == 0){//cannot stop monitoring for a call thats not intercepted
return -EINVAL;
}
if(check_pid_monitored(syscall, pid) == 0 && pid != 0){//cannot stop monitoring pid for a syscall thats not being monitored
return -EINVAL;
}
if(table[syscall].monitored == 2 && pid != 0){//cant stop monitoring a specific pid when monitoring all pids
return -EINVAL;
}
if(pid == 0){
spin_lock(&pidlist_lock);
destroy_list(syscall);//stop monitoring all processes for this syscall
spin_unlock(&pidlist_lock);
}
else{
spin_lock(&pidlist_lock);
del_pid_sysc(pid, syscall);//stop monitoring just this pid
spin_unlock(&pidlist_lock);
}
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;
spin_lock(&pidlist_lock);//get the lock, because modifying shared data
for(s = 0; s < NR_syscalls; s++) {//initialize all the system call info into table
table[s].f = sys_call_table[s];
table[s].intercepted = 0;
table[s].monitored = 0;
table[s].listcount = 0;
INIT_LIST_HEAD(&table[s].my_list);//initialize head of list for pids that are monitored
}
spin_unlock(&pidlist_lock);//free the lock since we are done modifying this shared resource
spin_lock(&calltable_lock);
set_addr_rw((unsigned long)sys_call_table);//setting sys_call_table to rw and then ro after we are done modifying it
orig_custom_syscall = sys_call_table[0];//keep track of original custom syscall
sys_call_table[0] = my_syscall; //hijack custom syscall by modifying sys_call_table entry to point to custom syscall
orig_exit_group = sys_call_table[__NR_exit_group];//keep track of original exit group
sys_call_table[__NR_exit_group] = &my_exit_group;//hijack exit group by modifying sys_call_table entry to point to our exit group
set_addr_ro((unsigned long)sys_call_table);
spin_unlock(&calltable_lock);
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)
{
spin_lock(&calltable_lock);//get lock and free it after done modifying sys_call_table, a shared data resource
set_addr_rw((unsigned long)sys_call_table);
sys_call_table[0] = orig_custom_syscall;//store original custom syscall
sys_call_table[__NR_exit_group] = orig_exit_group;//restore original exit group
set_addr_ro((unsigned long)sys_call_table);
spin_unlock(&calltable_lock);
int s;
for(s = 0; s < NR_syscalls; s++){ //de-intercept all calls before exiting module
deintercept(s);
}
}
module_init(init_function);
module_exit(exit_function);