系统调用挂钩示例参数不正确

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".


有关此更改的背景,请参阅

似乎动机是只传递一个指向 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);