I want to update the system call table to use my custom open function. I wrote the following code:
#include <linux/module.h>
#include <linux/kallsyms.h>
MODULE_LICENSE("GPL");
char *sym_name = "sys_call_table";
typedef asmlinkage long (*sys_call_ptr_t)(const struct pt_regs *);
static sys_call_ptr_t *sys_call_table;
typedef asmlinkage long (*custom_open) (const char __user *filename, int flags, umode_t mode);
custom_open old_open;
static asmlinkage long my_open(const char __user *filename, int flags, umode_t mode)
{
pr_info("%s\n",__func__);
return old_open(filename, flags, mode);
}
static int __init hello_init(void)
{
sys_call_table = (sys_call_ptr_t *)kallsyms_lookup_name(sym_name);
old_open = (custom_open)sys_call_table[__NR_open];
sys_call_table[__NR_open] = (sys_call_ptr_t)my_open;
return 0;
}
static void __exit hello_exit(void)
{
sys_call_table[__NR_open] = (sys_call_ptr_t)old_open;
}
module_init(hello_init);
module_exit(hello_exit);
When I load the module, I get the following error:
[69736.192438] BUG: unable to handle page fault for address: ffffffff98e001d0
[69736.192441] #PF: supervisor write access in kernel mode
[69736.192442] #PF: error_code(0x0003) - permissions violation
[69736.192443] PGD 10460e067 P4D 10460e067 PUD 10460f063 PMD 80000001040000e1
[69736.192461] Oops: 0003 [#1] SMP PTI
[69736.192463] CPU: 0 PID: 45249 Comm: insmod Tainted: G OE 5.2.8 #6
Can I update the system call table? How can I resolve such an error?
You're almost there. In Intel x86 CPUs, the Control Register CR0
has a special bit (called Write Protect bit), to control whether the CPU can write to read-only pages while running in privilege level 0 (kernel code does run at privilege level 0).
Since the syscall table is inside a page that is read-only, and by default the "Write Protect" bit is set, you are prevented from writing to it. So if you attempt to do:
sys_call_table[__NR_open] = (sys_call_ptr_t)my_open;
you'll crash everything.
In order to hijack the syscall correctly you will need to disable write protection by setting the "Write Protect" bit of the CR0
register to 0
before overwriting the table entry, and re-enable it after you're done. The two macros read_cr0()
and write_cr0()
are there exactly for manipulating said register.
Here's the correct code:
// Temporarily disable write protection
write_cr0(read_cr0() & (~0x10000));
// Overwrite the syscall table entry
sys_call_table[__NR_open] = /* whatever */;
// Re-enable write protection
write_cr0(read_cr0() | 0x10000);
The mask 0x10000
is used above since the "Write Protect" bit is the 17th least significant bit of the register.
Note that the above steps need to be done both in the init
and exit
functions of your module.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With