1
1
/**
2
2
* syscall-ftrace.c
3
3
*
4
- * System call "stealing" with Ftrace
4
+ * System call "stealing" with ftrace
5
+ *
6
+ * We create a callback function that contains
7
+ * an unconditional jump to our spying function,
8
+ * which will then return control to the original one.
9
+ *
10
+ * The callback function is triggered by ftrace.
5
11
*/
6
12
7
13
#include <linux/kernel.h>
@@ -22,30 +28,30 @@ MODULE_LICENSE("GPL");
22
28
#define MAX_FILENAME_SIZE 200
23
29
24
30
/* UID we want to spy on - will be filled from the command line. */
25
- static int uid ;
31
+ static int uid = 0 ;
26
32
module_param (uid , int , 0644 );
27
33
28
34
/**
29
- * This is a helper structure that housekeeps all information
35
+ * This is a housekeeping structure that saves all information
30
36
* needed for hooking. Usage with `PREPARE_HOOK` is recommended.
31
37
*
32
38
* Example:
33
- * static ftrace_hook_t sys_clone_hook = PREPARE_HOOK(__NR_openat, my_sys_clone, &orig_sys_clone)
39
+ * static ftrace_hook_t sys_clone_hook =
40
+ * PREPARE_HOOK(__NR_clone, my_sys_clone, &orig_sys_clone)
34
41
*/
35
42
typedef struct ftrace_hook {
36
- unsigned long nr ; // syscall name
37
- void * new ; // hook function
38
- void * orig ; // original function
43
+ unsigned long nr ; // syscall name
44
+ void * new ; // hook function
45
+ void * orig ; // original function
39
46
40
47
unsigned long address ; // address to the original function
41
48
struct ftrace_ops ops ; // ftrace structure
42
49
} ftrace_hook_t ;
43
50
44
- #define PREPARE_HOOK (_nr , _hook , _orig ) { \
45
- .nr = (_nr), \
46
- .new = (_hook), \
47
- .orig = (_orig) \
48
- }
51
+ #define PREPARE_HOOK (_nr , _hook , _orig ) \
52
+ { \
53
+ .nr = (_nr), .new = (_hook), .orig = (_orig) \
54
+ }
49
55
50
56
unsigned long * * sys_call_table ;
51
57
@@ -56,48 +62,65 @@ unsigned long **sys_call_table;
56
62
*/
57
63
static int resolve_address (ftrace_hook_t * hook )
58
64
{
59
- static struct kprobe kp = {
60
- .symbol_name = "kallsyms_lookup_name"
61
- };
65
+ static struct kprobe kp = { .symbol_name = "kallsyms_lookup_name" };
62
66
unsigned long (* kallsyms_lookup_name )(const char * name );
63
67
register_kprobe (& kp );
64
68
kallsyms_lookup_name = (unsigned long (* )(const char * ))kp .addr ;
65
69
unregister_kprobe (& kp );
66
70
67
- if (kallsyms_lookup_name ) pr_info ("[syscall-ftrace] kallsyms_lookup_name is found at 0x%lx\n" , (unsigned long )kallsyms_lookup_name );
71
+ if (kallsyms_lookup_name )
72
+ pr_info ("[syscall-ftrace] kallsyms_lookup_name is found at 0x%lx\n" ,
73
+ (unsigned long )kallsyms_lookup_name );
68
74
else {
69
75
pr_err ("[syscall-ftrace] kallsyms_lookup_name is not found!\n" );
70
76
return -1 ;
71
77
}
72
78
73
79
sys_call_table = (unsigned long * * )kallsyms_lookup_name ("sys_call_table" );
74
- if (sys_call_table ) pr_info ("[syscall-ftrace] sys_call_table is found at 0x%lx\n" , (unsigned long )sys_call_table );
80
+ if (sys_call_table )
81
+ pr_info ("[syscall-ftrace] sys_call_table is found at 0x%lx\n" ,
82
+ (unsigned long )sys_call_table );
75
83
else {
76
84
pr_err ("[syscall-ftrace] sys_call_table is not found!\n" );
77
85
return -1 ;
78
86
}
79
87
80
88
hook -> address = (unsigned long )sys_call_table [hook -> nr ];
81
- * ((unsigned long * ) hook -> orig ) = hook -> address ;
89
+ * ((unsigned long * ) hook -> orig ) = hook -> address ;
82
90
return 0 ;
83
91
}
84
92
85
93
/**
86
94
* This is where the magic happens.
87
95
*
96
+ * We check whether this function is called by the kernel or this module
97
+ * by checking whether parent_ip is within this module.
98
+ *
99
+ * During the first call, parent_ip points to somewhere in the kernel
100
+ * that's not in this module,
101
+ * while the second call is in this module
102
+ * since it's called from our_sys_openat.
103
+ *
104
+ * If it is the first call, we modify ip to be our_sys_openat,
105
+ * which will pass control to it after ftrace is done.
88
106
*/
89
107
#if LINUX_VERSION_CODE >= KERNEL_VERSION (5 , 11 , 0 )
90
- static void notrace ftrace_thunk (unsigned long ip , unsigned long parent_ip , struct ftrace_ops * ops , struct ftrace_regs * fregs )
108
+ static void notrace ftrace_thunk (unsigned long ip , unsigned long parent_ip ,
109
+ struct ftrace_ops * ops ,
110
+ struct ftrace_regs * fregs )
91
111
{
92
112
ftrace_hook_t * hook = container_of (ops , ftrace_hook_t , ops );
93
- if (!within_module (parent_ip , THIS_MODULE )) fregs -> regs .ip = (unsigned long ) hook -> new ;
113
+ if (!within_module (parent_ip , THIS_MODULE ))
114
+ fregs -> regs .ip = (unsigned long )hook -> new ;
94
115
}
95
116
96
- #else
97
- static void notrace ftrace_thunk (unsigned long ip , unsigned long parent_ip , struct ftrace_ops * ops , struct pt_regs * regs )
117
+ #else /** Version < v5.11 */
118
+ static void notrace ftrace_thunk (unsigned long ip , unsigned long parent_ip ,
119
+ struct ftrace_ops * ops , struct pt_regs * regs )
98
120
{
99
121
ftrace_hook_t * hook = container_of (ops , ftrace_hook_t , ops );
100
- if (!within_module (parent_ip , THIS_MODULE )) regs -> ip = (unsigned long ) hook -> new ;
122
+ if (!within_module (parent_ip , THIS_MODULE ))
123
+ regs -> ip = (unsigned long )hook -> new ;
101
124
}
102
125
103
126
#endif /** Version >= v5.11 */
@@ -106,10 +129,14 @@ int install_hook(ftrace_hook_t *hook)
106
129
{
107
130
int err ;
108
131
err = resolve_address (hook );
109
- if (err ) return err ;
132
+ if (err )
133
+ return err ;
110
134
135
+ /** The callback function */
111
136
hook -> ops .func = ftrace_thunk ;
137
+ /** We need registers and we're modifying ip */
112
138
hook -> ops .flags = FTRACE_OPS_FL_SAVE_REGS | FTRACE_OPS_FL_IPMODIFY ;
139
+ /** Only sys_openat should be traced */
113
140
err = ftrace_set_filter_ip (& hook -> ops , hook -> address , 0 , 0 );
114
141
if (err ) {
115
142
pr_err ("[syscall-ftrace] ftrace_set_filter_ip() failed: %d\n" , err );
@@ -129,20 +156,34 @@ void remove_hook(ftrace_hook_t *hook)
129
156
{
130
157
int err ;
131
158
err = unregister_ftrace_function (& hook -> ops );
132
- if (err ) pr_err ("[syscall-ftrace] unregister_ftrace_function() failed: %d\n" , err );
159
+ if (err )
160
+ pr_err ("[syscall-ftrace] unregister_ftrace_function() failed: %d\n" ,
161
+ err );
133
162
163
+ /** Disable the trace by setting remove to 1 */
134
164
err = ftrace_set_filter_ip (& hook -> ops , hook -> address , 1 , 0 );
135
- if (err ) pr_err ("[syscall-ftrace] ftrace_set_filter_ip() failed: %d\n" , err );
165
+ if (err )
166
+ pr_err ("[syscall-ftrace] ftrace_set_filter_ip() failed: %d\n" , err );
136
167
}
137
168
138
- /** For some reason the kernel segfaults when the arguments are expanded. */
169
+ /** For some reason the kernel segfaults when the parameters are expanded. */
139
170
static asmlinkage long (* original_call )(struct pt_regs * regs );
140
171
static asmlinkage long our_sys_openat (struct pt_regs * regs )
141
172
{
142
173
char * kfilename ;
143
- kfilename = kmalloc (GFP_KERNEL , MAX_FILENAME_SIZE * sizeof (char ));
144
- if (!kfilename ) return original_call (regs );
174
+ if (current -> cred -> uid .val != uid )
175
+ return original_call (regs );
176
+ kfilename = kmalloc (GFP_KERNEL , MAX_FILENAME_SIZE * sizeof (char ));
177
+ if (!kfilename )
178
+ return original_call (regs );
145
179
180
+ /**
181
+ * This may only work in x86_64 because getting parameters
182
+ * from CPU registers is architecture-dependent.
183
+ *
184
+ * Change regs->si to appropriate registers
185
+ * if you are trying on different architecture.
186
+ */
146
187
if (copy_from_user (kfilename , (char __user * )regs -> si , MAX_FILENAME_SIZE ) < 0 ) {
147
188
kfree (kfilename );
148
189
return original_call (regs );
@@ -154,13 +195,15 @@ static asmlinkage long our_sys_openat(struct pt_regs *regs)
154
195
return original_call (regs );
155
196
}
156
197
157
- static ftrace_hook_t sys_openat_hook = PREPARE_HOOK (__NR_openat , our_sys_openat , & original_call );
198
+ static ftrace_hook_t sys_openat_hook =
199
+ PREPARE_HOOK (__NR_openat , our_sys_openat , & original_call );
158
200
159
201
static int __init syscall_ftrace_start (void )
160
202
{
161
203
int err ;
162
204
err = install_hook (& sys_openat_hook );
163
- if (err ) return err ;
205
+ if (err )
206
+ return err ;
164
207
pr_info ("[syscall-ftrace] hooked, spying on uid %d\n" , uid );
165
208
return 0 ;
166
209
}
0 commit comments