系统调用挂钩示例参数不正确
System call hooking example arguments are incorrect
我从我们的 Linux 内核模块写了一个系统调用挂钩的例子。
在系统调用 table 中更新了打开系统调用以使用我的入口点而不是默认入口点。
#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)
{
char user_msg[256];
pr_info("%s\n",__func__);
memset(user_msg, 0, sizeof(user_msg));
long copied = strncpy_from_user(user_msg, filename, sizeof(user_msg));
pr_info("copied:%ld\n", copied);
pr_info("%s\n",user_msg);
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];
// Temporarily disable write protection
write_cr0(read_cr0() & (~0x10000));
sys_call_table[__NR_open] = (sys_call_ptr_t)my_open;
// Re-enable write protection
write_cr0(read_cr0() | 0x10000);
return 0;
}
static void __exit hello_exit(void)
{
// Temporarily disable write protection
write_cr0(read_cr0() & (~0x10000));
sys_call_table[__NR_open] = (sys_call_ptr_t)old_open;
// Re-enable write protection
write_cr0(read_cr0() | 0x10000);
}
module_init(hello_init);
module_exit(hello_exit);
我写了一个简单的用户程序来验证。
#define _GNU_SOURCE
#include <sys/syscall.h>
#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
int fd = syscall(__NR_open, "hello.txt", O_RDWR|O_CREAT, 0777);
exit(EXIT_SUCCESS);
}
文件已在我的文件夹中创建,但 strncpy_user 因地址错误而失败
[ 927.415905] my_open
[ 927.415906] copied:-14
上面的代码有什么错误?
OP 可能正在使用 kernel/architecture,它使用 "syscall wrappers",其中系统调用 table 包含调用真正系统调用函数(可能作为内联函数调用)的包装函数。 x86_64 架构从内核版本 4.17 开始使用系统调用包装器。
对于内核 4.17 或更高版本的 x86_64,sys_call_table[__NR_open]
指向 __x64_sys_open
(原型为 asmlinkage long __x64_sys_open(const struct pt_regs *regs)
),它调用 static
函数 __se_sys_open
(原型static long __se_sys_open(const __user *filename, int flags, umode_t mode)
),它调用内联函数__do_sys_open
(原型static inline long __do_sys_open(const __user *filename, int flags, umode_t mode)
。这些都将由"fs/open.c"中的SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)
宏调用定义以及宏调用之后的函数体。
SYSCALL_DEFINE3
定义在"include/linux/syscalls.h"中,在同一个文件中使用了SYSCALL_DEFINEx
宏,后者使用了__SYSCALL_DEFINEx
宏。由于 x86_64 定义了 CONFIG_ARCH_HAS_SYSCALL_WRAPPER
,因此 __SYSCALL_DEFINEx
宏由 #include <asm/syscall_wrapper.h>
定义,它映射到 "arch/x86/include/asm/syscall_wrapper.h".
有关此更改的背景,请参阅
- LWN: use struct pt_regs based syscall calling for x86-64
- LKML:[PATCH 000/109] 删除对系统调用的内核调用 https://lkml.org/lkml/2018/3/29/409
似乎动机是只传递一个指向 pt_regs
的指针,而不是在调用链下的寄存器中有一堆用户 space 值。 (也许通过降低小工具的用处来增加对 Spectre 攻击的抵抗力?)
为什么 open
仍然有效,即使包装器没有:
如果 OP 确实在使用 x86_64 内核 4.17 或更高版本,并将 sys_call_table[__NR_open]
条目替换为指向使用不同原型并调用原始函数的函数的指针(指向 old_open
) 具有相同的参数,这解释了为什么调用 strncpy_from_user(user_msg, filename, sizeof(user_msg))
失败。虽然声明为const char * __user filename
,但filename
指针实际上指向内核space中原来的struct pt_regs
。
在随后对 old_open(filename, flags, mode)
的调用中,第一个参数 filename
仍然指向原来的 struct pt_regs
所以旧函数(需要一个 [=36 类型的参数) =]) 仍然按预期工作。
即函数传递给它的第一个指针 arg 没有改变,尽管调用它的类型不同。
更新:下面是工作代码,感谢大家提供意见
#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;
sys_call_ptr_t old_open;
static asmlinkage long my_open(const struct pt_regs *regs)
{
char __user *filename = (char *)regs->di;
char user_filename[256] = {0};
long copied = strncpy_from_user(user_filename, filename, sizeof(user_filename));
if (copied > 0)
pr_info("%s filename:%s\n",__func__, user_filename);
return old_open(regs);
}
static int __init hello_init(void)
{
sys_call_table = (sys_call_ptr_t *)kallsyms_lookup_name(sym_name);
old_open = sys_call_table[__NR_open];
// Temporarily disable write protection
write_cr0(read_cr0() & (~0x10000));
sys_call_table[__NR_open] = my_open;
// Re-enable write protection
write_cr0(read_cr0() | 0x10000);
return 0;
}
static void __exit hello_exit(void)
{
// Temporarily disable write protection
write_cr0(read_cr0() & (~0x10000));
sys_call_table[__NR_open] = old_open;
// Re-enable write protection
write_cr0(read_cr0() | 0x10000);
}
module_init(hello_init);
module_exit(hello_exit);
我从我们的 Linux 内核模块写了一个系统调用挂钩的例子。
在系统调用 table 中更新了打开系统调用以使用我的入口点而不是默认入口点。
#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)
{
char user_msg[256];
pr_info("%s\n",__func__);
memset(user_msg, 0, sizeof(user_msg));
long copied = strncpy_from_user(user_msg, filename, sizeof(user_msg));
pr_info("copied:%ld\n", copied);
pr_info("%s\n",user_msg);
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];
// Temporarily disable write protection
write_cr0(read_cr0() & (~0x10000));
sys_call_table[__NR_open] = (sys_call_ptr_t)my_open;
// Re-enable write protection
write_cr0(read_cr0() | 0x10000);
return 0;
}
static void __exit hello_exit(void)
{
// Temporarily disable write protection
write_cr0(read_cr0() & (~0x10000));
sys_call_table[__NR_open] = (sys_call_ptr_t)old_open;
// Re-enable write protection
write_cr0(read_cr0() | 0x10000);
}
module_init(hello_init);
module_exit(hello_exit);
我写了一个简单的用户程序来验证。
#define _GNU_SOURCE
#include <sys/syscall.h>
#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
int fd = syscall(__NR_open, "hello.txt", O_RDWR|O_CREAT, 0777);
exit(EXIT_SUCCESS);
}
文件已在我的文件夹中创建,但 strncpy_user 因地址错误而失败
[ 927.415905] my_open
[ 927.415906] copied:-14
上面的代码有什么错误?
OP 可能正在使用 kernel/architecture,它使用 "syscall wrappers",其中系统调用 table 包含调用真正系统调用函数(可能作为内联函数调用)的包装函数。 x86_64 架构从内核版本 4.17 开始使用系统调用包装器。
对于内核 4.17 或更高版本的 x86_64,sys_call_table[__NR_open]
指向 __x64_sys_open
(原型为 asmlinkage long __x64_sys_open(const struct pt_regs *regs)
),它调用 static
函数 __se_sys_open
(原型static long __se_sys_open(const __user *filename, int flags, umode_t mode)
),它调用内联函数__do_sys_open
(原型static inline long __do_sys_open(const __user *filename, int flags, umode_t mode)
。这些都将由"fs/open.c"中的SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)
宏调用定义以及宏调用之后的函数体。
SYSCALL_DEFINE3
定义在"include/linux/syscalls.h"中,在同一个文件中使用了SYSCALL_DEFINEx
宏,后者使用了__SYSCALL_DEFINEx
宏。由于 x86_64 定义了 CONFIG_ARCH_HAS_SYSCALL_WRAPPER
,因此 __SYSCALL_DEFINEx
宏由 #include <asm/syscall_wrapper.h>
定义,它映射到 "arch/x86/include/asm/syscall_wrapper.h".
有关此更改的背景,请参阅
- LWN: use struct pt_regs based syscall calling for x86-64
- LKML:[PATCH 000/109] 删除对系统调用的内核调用 https://lkml.org/lkml/2018/3/29/409
似乎动机是只传递一个指向 pt_regs
的指针,而不是在调用链下的寄存器中有一堆用户 space 值。 (也许通过降低小工具的用处来增加对 Spectre 攻击的抵抗力?)
为什么 open
仍然有效,即使包装器没有:
如果 OP 确实在使用 x86_64 内核 4.17 或更高版本,并将 sys_call_table[__NR_open]
条目替换为指向使用不同原型并调用原始函数的函数的指针(指向 old_open
) 具有相同的参数,这解释了为什么调用 strncpy_from_user(user_msg, filename, sizeof(user_msg))
失败。虽然声明为const char * __user filename
,但filename
指针实际上指向内核space中原来的struct pt_regs
。
在随后对 old_open(filename, flags, mode)
的调用中,第一个参数 filename
仍然指向原来的 struct pt_regs
所以旧函数(需要一个 [=36 类型的参数) =]) 仍然按预期工作。
即函数传递给它的第一个指针 arg 没有改变,尽管调用它的类型不同。
更新:下面是工作代码,感谢大家提供意见
#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;
sys_call_ptr_t old_open;
static asmlinkage long my_open(const struct pt_regs *regs)
{
char __user *filename = (char *)regs->di;
char user_filename[256] = {0};
long copied = strncpy_from_user(user_filename, filename, sizeof(user_filename));
if (copied > 0)
pr_info("%s filename:%s\n",__func__, user_filename);
return old_open(regs);
}
static int __init hello_init(void)
{
sys_call_table = (sys_call_ptr_t *)kallsyms_lookup_name(sym_name);
old_open = sys_call_table[__NR_open];
// Temporarily disable write protection
write_cr0(read_cr0() & (~0x10000));
sys_call_table[__NR_open] = my_open;
// Re-enable write protection
write_cr0(read_cr0() | 0x10000);
return 0;
}
static void __exit hello_exit(void)
{
// Temporarily disable write protection
write_cr0(read_cr0() & (~0x10000));
sys_call_table[__NR_open] = old_open;
// Re-enable write protection
write_cr0(read_cr0() | 0x10000);
}
module_init(hello_init);
module_exit(hello_exit);