You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Linux 5.1x正确挂载LSM钩子及RHEL9内核崩溃排查

LSM钩子在RHEL9加载内核崩溃问题

问题描述

以下代码可在Redhat 8服务器上成功实现LSM钩子挂载,但在Redhat 9服务器上加载时会触发内核崩溃。已尝试检索hlist_add_head_rcu接口变更、Linux 5.1x版本LSM挂载方式调整相关资料,暂未找到有效解决方案。核心疑问:为何同一份代码可在Linux 4.x内核正常运行,却无法在Linux 5.1x内核上工作?

测试环境

  • Redhat 8:内核版本 4.18.0-147.el8.x86_64
  • Redhat 9:内核版本 5.14.0-70.13.1.el9_0.x86_64

复现代码

#include <linux/module.h>
#include <linux/kallsyms.h>
#include <linux/version.h>
#include <linux/security.h>
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,2,0)
#include <linux/lsm_hooks.h>
#endif

#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,7,0)
#define KPROBE_LOOKUP 1
#include <linux/kprobes.h>
static struct kprobe kp = {
    .symbol_name = "kallsyms_lookup_name"
};
#endif

#define SECURITY_HOOK_ADDR_NAME "security_hook_heads"

static unsigned long find_symbol_addr(const char *sym){
    const char *cpsMethod = "cris test: find_symbol_addr";
    unsigned long addr;
    #ifdef KPROBE_LOOKUP
    typedef unsigned long (*kallsyms_lookup_name_t)(const char *name);
    kallsyms_lookup_name_t kallsyms_lookup_name;
    register_kprobe(&kp);
    kallsyms_lookup_name = (kallsyms_lookup_name_t) kp.addr;
    unregister_kprobe(&kp);
    #endif
    
    addr = kallsyms_lookup_name(sym);
    if (addr == 0) {
        pr_err("%s: unable to find addr\n", cpsMethod);
        return -EINVAL;
    }
    pr_info("%s: address is %lx\n", cpsMethod, addr);
    return addr;
}

struct security_hook_list cris_hooks[1] = {
};

static int hook_execve_test(struct file *file, int mask)
{
     pr_info("cris test: hook_execve_test\n");
     return 0;

}
static struct security_hook_heads *cris_lsm_hook = NULL;

bool hook_lsm(void){
    const char *cpsMethod = "cris test: hook_lsm";
    int count = 0;
    int i = 0;
    unsigned long addr1;
    
    addr1 = find_symbol_addr(SECURITY_HOOK_ADDR_NAME);
    if (addr1 == 0)
    {
        pr_err("%s: [Fatal] Lookup address for security hook heads failed. Can't enable execve Hook\n", cpsMethod);
        return false;
    }
    
    cris_lsm_hook = (struct security_hook_heads*)addr1;
    cris_hooks[0].head = &(cris_lsm_hook->file_permission);
    cris_hooks[0].hook.file_permission = hook_execve_test;  
    count = ARRAY_SIZE(cris_hooks);
    for (i = 0; i < count; i++){
        
    #if LINUX_VERSION_CODE < KERNEL_VERSION(4,17,0)
    list_add_rcu(&cris_hooks[i].list, cris_hooks[i].head);
    #else
    hlist_add_head_rcu(&cris_hooks[i].list, cris_hooks[i].head);
    #endif
    }
    pr_info("%s: finish hook_lsm.\n", cpsMethod);
 
    return true;
}

void unhook_lsm(void){
    const char *cpsMethod = "cris test: unhook_lsm";
    int count = 0;
    int i = 0;
    count = ARRAY_SIZE(cris_hooks);
    for (i = 0; i < count; i++){
        #if LINUX_VERSION_CODE < KERNEL_VERSION(4,17,0)
        list_del_rcu(&cris_hooks[i].list);
        #else
        hlist_del_rcu(&cris_hooks[i].list);
        #endif
    }
    pr_info("%s: Unregister hook module\n", cpsMethod);
}
static int __init prsyms_init(void)
{
    hook_lsm();
    return 0;
}

static void __exit prsyms_exit(void)
{
    unhook_lsm();
}

module_init(prsyms_init);
module_exit(prsyms_exit);

MODULE_LICENSE("GPL");

RHEL9内核崩溃日志

[ 2644.871335] cris test: find_symbol_addr: address is ffffffffb3215c60
[ 2644.871354] BUG: unable to handle page fault for address: ffffffffb3215ea0
[ 2644.871361] #PF: supervisor write access in kernel mode
[ 2644.871363] #PF: error_code(0x0003) - permissions violation
[ 2644.871365] PGD b5a15067 P4D b5a15067 PUD b5a16063 PMD 80000000b54000e1
[ 2644.871377] Oops: 0003 [#1] PREEMPT SMP PTI
[ 2644.871387] CPU: 0 PID: 2437 Comm: insmod Kdump: loaded Tainted: G S         OE    --------- ---  5.14.0-70.13.1.el9_0.x86_64 #1
[ 2644.871394] Hardware name: VMware, Inc. VMware7,1/440BX Desktop Reference Platform, BIOS VMW71.00V.13989454.B64.1906190538 06/19/2019
[ 2644.871396] RIP: 0010:hook_lsm.cold+0x47/0x95 [testcris]
[ 2644.871416] Code: 02 00 00 48 8d 93 40 02 00 00 48 c7 05 8f 23 00 00 83 70 72 c0 48 89 15 80 23 00 00 48 89 05 69 23 00 00 48 89 15 6a 23 00 00 <48> c7 83 40 02 00 00 40 94 72 c0 48 85 c0 74 08 48 c7 40 08 40 94
[ 2644.871418] RSP: 0018:ffffb5c00260fde0 EFLAGS: 00010246
[ 2644.871421] RAX: ffffffffb3216cf8 RBX: ffffffffb3215c60 RCX: 0000000000000000
[ 2644.871423] RDX: ffffffffb3215ea0 RSI: ffff917efbc17cc0 RDI: ffff917efbc17cc0
[ 2644.871424] RBP: ffffffffc072c000 R08: 0000000000000000 R09: ffffb5c00260fc28
[ 2644.871425] R10: ffffb5c00260fc20 R11: ffffffffb3be8228 R12: ffff917dc12226f0
[ 2644.871427] R13: ffffb5c00260fe88 R14: 0000000000000003 R15: 0000000000000000
[ 2644.871428] FS:  00007f8005409740(0000) GS:ffff917efbc00000(0000) knlGS:0000000000000000
[ 2644.871444] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[ 2644.871447] CR2: ffffffffb3215ea0 CR3: 000000003f536003 CR4: 00000000001706f0
[ 2644.871467] Call Trace:
[ 2644.871473]  prsyms_init+0xa/0x1000 [testcris]
[ 2644.871477]  do_one_initcall+0x44/0x200
[ 2644.871500]  ? load_module+0xab8/0xb80
[ 2644.871503]  ? kmem_cache_alloc_trace+0x45/0x420
[ 2644.871523]  do_init_module+0x5c/0x270
[ 2644.871536]  __do_sys_finit_module+0xae/0x110
[ 2644.871544]  do_syscall_64+0x3b/0x90
[ 2644.871592]  entry_SYSCALL_64_after_hwframe+0x44/0xae

根因分析

崩溃是内核态写入只读内存触发的权限页错误,核心原因有两点:

  1. LSM钩子头内存保护机制升级:Linux 5.7之后的主线内核(含RHEL9使用的5.14版本)将security_hook_heads标记为__ro_after_init,内核初始化流程结束后,该符号所在内存页会被强制设置为只读属性,防止运行时被恶意篡改。RHEL8的4.18内核未启用该保护,因此直接修改链表可以正常执行。从崩溃日志可以看到,错误码0x0003明确标注是写权限违规,出错地址正好落在security_hook_heads的内存范围内。
  2. 结构体偏移不匹配:编译模块时使用的头文件中struct security_hook_heads的成员布局,和RHEL9实际运行内核的结构体布局存在差异,代码通过编译时偏移计算出的file_permission钩子头地址和实际地址不符,进一步触发了非法内存访问。

适配建议

  • 临时调试方案:修改钩子链表前,调用set_memory_rw临时将对应内存页修改为可写属性,操作完成后再通过set_memory_ro改回只读。注意不能依赖编译时的结构体偏移,需要通过运行时kallsyms解析或动态偏移计算定位实际的钩子头地址,避免访问错误内存。该方案稳定性差,不建议在生产环境使用。
  • 正式适配方案:放弃直接篡改内核内部链表的挂载方式,使用内核官方提供的LSM注册接口。5.x版本内核支持通过security_add_hooks接口注册自定义钩子,配合register_lsm将模块注册为合法LSM,不需要手动操作hlist链表,也不会触发内存保护冲突。编译out-of-tree LSM模块时需要按照内核规范定义lsm_info段,确保内核可以正确识别加载。
  • 额外注意:RHEL9默认开启Kernel Lockdown内核锁定模式,即使修改页表属性,部分内核内存操作也会被拦截,测试前需要先关闭锁定模式。

内容的提问来源于stack exchange,提问作者T.Cris

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 15:03:10