11/**
22 * syscall-ftrace.c
33 *
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.
511 */
612
713#include <linux/kernel.h>
@@ -22,30 +28,30 @@ MODULE_LICENSE("GPL");
2228#define MAX_FILENAME_SIZE 200
2329
2430/* UID we want to spy on - will be filled from the command line. */
25- static int uid ;
31+ static int uid = 0 ;
2632module_param (uid , int , 0644 );
2733
2834/**
29- * This is a helper structure that housekeeps all information
35+ * This is a housekeeping structure that saves all information
3036 * needed for hooking. Usage with `PREPARE_HOOK` is recommended.
3137 *
3238 * 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)
3441 */
3542typedef 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
3946
4047 unsigned long address ; // address to the original function
4148 struct ftrace_ops ops ; // ftrace structure
4249} ftrace_hook_t ;
4350
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+ }
4955
5056unsigned long * * sys_call_table ;
5157
@@ -56,48 +62,65 @@ unsigned long **sys_call_table;
5662 */
5763static int resolve_address (ftrace_hook_t * hook )
5864{
59- static struct kprobe kp = {
60- .symbol_name = "kallsyms_lookup_name"
61- };
65+ static struct kprobe kp = { .symbol_name = "kallsyms_lookup_name" };
6266 unsigned long (* kallsyms_lookup_name )(const char * name );
6367 register_kprobe (& kp );
6468 kallsyms_lookup_name = (unsigned long (* )(const char * ))kp .addr ;
6569 unregister_kprobe (& kp );
6670
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 );
6874 else {
6975 pr_err ("[syscall-ftrace] kallsyms_lookup_name is not found!\n" );
7076 return -1 ;
7177 }
7278
7379 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 );
7583 else {
7684 pr_err ("[syscall-ftrace] sys_call_table is not found!\n" );
7785 return -1 ;
7886 }
7987
8088 hook -> address = (unsigned long )sys_call_table [hook -> nr ];
81- * ((unsigned long * ) hook -> orig ) = hook -> address ;
89+ * ((unsigned long * ) hook -> orig ) = hook -> address ;
8290 return 0 ;
8391}
8492
8593/**
8694 * This is where the magic happens.
8795 *
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.
88106 */
89107#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 )
91111{
92112 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 ;
94115}
95116
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 )
98120{
99121 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 ;
101124}
102125
103126#endif /** Version >= v5.11 */
@@ -106,10 +129,14 @@ int install_hook(ftrace_hook_t *hook)
106129{
107130 int err ;
108131 err = resolve_address (hook );
109- if (err ) return err ;
132+ if (err )
133+ return err ;
110134
135+ /** The callback function */
111136 hook -> ops .func = ftrace_thunk ;
137+ /** We need registers and we're modifying ip */
112138 hook -> ops .flags = FTRACE_OPS_FL_SAVE_REGS | FTRACE_OPS_FL_IPMODIFY ;
139+ /** Only sys_openat should be traced */
113140 err = ftrace_set_filter_ip (& hook -> ops , hook -> address , 0 , 0 );
114141 if (err ) {
115142 pr_err ("[syscall-ftrace] ftrace_set_filter_ip() failed: %d\n" , err );
@@ -129,20 +156,34 @@ void remove_hook(ftrace_hook_t *hook)
129156{
130157 int err ;
131158 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 );
133162
163+ /** Disable the trace by setting remove to 1 */
134164 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 );
136167}
137168
138- /** For some reason the kernel segfaults when the arguments are expanded. */
169+ /** For some reason the kernel segfaults when the parameters are expanded. */
139170static asmlinkage long (* original_call )(struct pt_regs * regs );
140171static asmlinkage long our_sys_openat (struct pt_regs * regs )
141172{
142173 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 );
145179
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+ */
146187 if (copy_from_user (kfilename , (char __user * )regs -> si , MAX_FILENAME_SIZE ) < 0 ) {
147188 kfree (kfilename );
148189 return original_call (regs );
@@ -154,13 +195,15 @@ static asmlinkage long our_sys_openat(struct pt_regs *regs)
154195 return original_call (regs );
155196}
156197
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 );
158200
159201static int __init syscall_ftrace_start (void )
160202{
161203 int err ;
162204 err = install_hook (& sys_openat_hook );
163- if (err ) return err ;
205+ if (err )
206+ return err ;
164207 pr_info ("[syscall-ftrace] hooked, spying on uid %d\n" , uid );
165208 return 0 ;
166209}
0 commit comments