Linux内核审计规则及其数据结构

发布于:2025-03-29 ⋅ 阅读:(24) ⋅ 点赞:(0)

一、基本概念

        在Linux内核中,审计(Auditing) 是一种安全机制,用于跟踪和记录系统中发生的安全相关事件,例如用户操作、文件访问、系统调用等。其核心目标是提供对系统活动的透明监控,便于事后审查、入侵检测或合规性检查。

        Linux内核审计机制的核心功能在于实时监控和记录系统中的安全关键事件(如系统调用、文件访问和用户操作),通过动态规则管理允许管理员在不重启系统的条件下灵活调整监控策略,结合内核级高效过滤和异步日志处理,确保对生产环境性能影响最小;其支持细粒度的审计策略配置与丰富的日志分析工具,不仅满足合规性要求和入侵检测需求,还为事后溯源提供了可靠依据,成为保障系统安全与稳定运行的重要基石。

        程序员可以在专用于开发的机器上进行试验,而管理员面对的是一个不同的问题:他们必须监控的计算机通常作为生产机器。这对审计机制提出了如下两个关键性的约束。
・用于选择所记录事件类型的规则,必须能够动态改变。特别是,不能要求重启系统或插入 / 移除内核模块。
・在使用审计特性时,系统性能不能下降太多。而禁用审计机制,也不应该对系统性能带来负面影响。

        审计子系统如下图所示:

 

二、Linux审计的核心组件

1. 内核审计框架

  • 功能:在内核层面捕获和过滤安全事件,通过钩子(hooks)监控关键操作(如系统调用、文件访问等)。

    • 事件触发:当特定操作发生时(如open系统调用),内核检查是否匹配审计规则,决定是否记录。

    • 高效过滤:基于内核数据结构(如哈希表)快速匹配规则,减少性能损耗。

2. 用户空间工具

  • auditd:守护进程,负责从内核接收审计事件并写入日志文件(如/var/log/audit/audit.log)。

  • auditctl:命令行工具,用于动态配置审计规则(无需重启系统或修改内核模块)。

  • ausearch/aureport:查询和分析审计日志的工具,支持按事件类型、关键字等过滤。

三、审计规则的组成与匹配机制

1. 规则结构:过滤器(Filter)与值(Value)

  • 过滤器:指定事件类型,如:

    • entry:系统调用入口(如open开始执行)。

    • exit:系统调用退出(如open执行完成)。

    • task:进程创建事件(如fork)。

    • file:文件访问事件。

    • ALWAYS:启用规则,记录事件。

    • NEVER:禁用规则,跳过记录。

2. 规则匹配机制

  • 首次匹配原则:规则按添加顺序存储,第一个匹配的规则生效,后续规则被忽略。

  • 应用场景

    • 排除事件:将NEVER规则放在ALWAYS规则之前。

    auditctl -a never,exit -F dir=/tmp -k ignore_tmp  # 忽略/tmp目录事件
    auditctl -a always,exit -S open -k global_file    # 全局记录文件打开事件

四、动态规则管理与性能优化

1. 动态规则调整

  • 实时生效:通过auditctl增删改规则,无需重启系统或服务。

    # 添加规则:监控/etc/passwd的写和属性修改
    auditctl -w /etc/passwd -p wa -k sensitive_file
    
    # 删除规则:按关键字删除
    auditctl -d -k sensitive_file

2. 性能优化措施

  • 轻量级过滤:仅匹配规则时生成事件,避免全量日志。

  • 异步日志写入:内核通过Netlink套接字将事件发送给auditd,减少阻塞。

  • 零开销禁用:关闭审计(auditctl -e 0)后,内核代码路径仅保留空检查。

五、具体示例与日志分析

1. 监控敏感命令执行(如sudosu

  • 规则配置

    • auditctl -a exit,always -S execve -F exe=/usr/bin/sudo -k admin_cmd
      auditctl -a exit,always -S execve -F exe=/usr/bin/su -k admin_cmd

      auditctl -a exit,always -S execve -F exe=/usr/bin/sudo -k admin_cmd

        -a exit,always :-a 表示添加审计规则,exit,always 指无论系统调用成功或失败,在其退出时都记录相关事件 。

        -S execve :-S 用于指定监控的系统调用,execve 系统调用负责在进程中加载并执行新的程序文件 ,即监控对 execve 系统调用的执行情况。

        -F exe=/usr/bin/sudo :-F 用来添加过滤条件,exe=/usr/bin/sudo 表示只针对执行 /usr/bin/sudo 这个程序文件的 execve 系统调用进行审计 。

           -k admin_cmd -k 用于给这条审计规则关联一个关键字 admin_cmd ,方便后续通过该关键字检索相关审计记录。

              总体来说,这条命令是设置审计规则,当系统中执行 /usr/bin/sudo 程序触发 execve 系统调用时,无论调用成功或失败,在调用退出时都记录相关事件,且记录可通过 admin_cmd 关键字检索。

      auditctl -a exit,always -S execve -F exe=/usr/bin/su -k  

      admin_cmd  

        -a exit,always 、-S execve 、-k admin_cmd :含义与上条命令中对应部分一致。  

        -F exe=/usr/bin/su :-F 添加过滤条件,exe=/usr/bin/su 表示只针对执行 /usr/bin/su 这个程序文件的 execve 系统调用进行审计 。

              这条命令是设置审计规则,当系统中执行 /usr/bin/su 程序触发 execve 系统调用时,无论调用成功或失败,在调用退出时都记录相关事件,同样可通过 admin_cmd 关键字检索这些记录。

  • 日志输出

  • type=EXECVE msg=audit(1625145600.123:456): a0="sudo" a1="-i" a2="root"
    type=SYSCALL msg=audit(...): syscall=59 (execve) success=yes ...
  • 分析命令

    ausearch -k admin_cmd | aureport -execve

2. 监控关键文件访问(如/etc/shadow

  • 规则配置

    auditctl -w /etc/shadow -p rwa -k shadow_access
  • 日志输出

    type=PATH msg=audit(...): item=0 name="/etc/shadow" inode=12345 ...
  • 分析命令

    ausearch -k shadow_access | aureport -file

3. 监控用户登录事件

  • 规则配置

    auditctl -a exit,always -F path=/bin/login -k user_login
  • 日志输出

    type=USER_LOGIN msg=audit(...): pid=1234 uid=0 auid=1000 res=success

六、审计机制如何满足管理员需求

需求 实现方式
动态规则调整 通过auditctl实时增删改规则,支持复杂过滤条件(路径、用户ID、系统调用等)。
高性能保障 内核级过滤减少冗余事件;异步日志写入避免阻塞。
精细化监控 支持从文件访问到特权命令的全覆盖,满足合规性和入侵检测需求。

 

 七、审计实现

        审计实现属于内核最核心的部分(其源代码位于 kernel / 目录下)。该代码基本上分布在以下 3 个文件中。
        ・kernel/audit.c 提供了核心的审计机制。
        ・kernel/auditsc.c 实现了系统调用审计。
        ・kernel/auditfilter.c 包含了过滤审计事件的机制。

1.数据结构

        审计机制使用的数据结构分为 3 个主要的类别。首先,需要向进程中加入一个各任务数据结构,这对系统调用审计是特别重要的。其次,审计事件、过滤规则等都需要在内核中表示出来。最后,需要与用户层实用程序建立一种通信机制。审计机制使用的数据结构如下:

 

 2.对task_struct的扩展

        各进程审计上下文: 

  • 生命周期

    • 进程创建时,audit_context初始化为NULL

    • 当进程触发需审计的操作时,动态分配audit_context并填充数据。

    • 进程退出时,释放audit_context

 3.初始化

        审计子系统的初始化由audit_init执行。

  • 关键操作

    • 初始化通信机制(Netlink)。

    • 准备规则存储结构(哈希表)。

    • 注册系统调用审计钩子(如syscall_entrysyscall_exit)。

    • 启动内核线程kauditd,负责将审计事件从内核缓冲区发送到用户空间。

 

4.记录事件

        在所有基础设施都就位之后,现在开始详细讲述审计是如何实际实现的。该过程分为 3 个阶段。首先,用 audit_log_start 开始记录过程。然后,用 audit_log_format 格式化一个记录消息,最后用 audit_log_end 结束该审计记录,消息将排队传输到审计守护进程。

阶段1:开始记录(audit_log_start
  • 作用:分配并初始化一个审计缓冲区(struct audit_buffer)。

  • 示例代码

    struct audit_buffer *audit_log_start(struct audit_context *ctx, gfp_t gfp_mask, int type) {
        struct audit_buffer *ab;
        ab = audit_buffer_alloc(ctx, gfp_mask, type); // 分配缓冲区
        if (!ab) return NULL;
        audit_log_format(ab, "type=%d ", type);       // 记录事件类型
        return ab;
    }
阶段2:格式化消息(audit_log_format
  • 作用:将事件详细信息(如进程ID、文件路径、系统调用参数)格式化为字符串。

  • 示例代码

    void audit_log_format(struct audit_buffer *ab, const char *fmt, ...) {
        va_list args;
        va_start(args, fmt);
        audit_log_vformat(ab, fmt, args); // 格式化到缓冲区
        va_end(args);
    }
阶段3:结束记录(audit_log_end
  • 作用:将缓冲区中的审计事件加入发送队列,由kauditd线程异步传输给auditd

  • 示例代码

    void audit_log_end(struct audit_buffer *ab) {
        if (ab) {
            audit_log_nvspace(ab); // 确保消息完整
            kauditd_send_skb(ab->skb); // 发送到用户空间
            audit_buffer_free(ab);     // 释放缓冲区
        }
    }

5.系统调用审计

        在所有基础设施都就位之后,现在开始详细讲述审计是如何实际实现的。该过程分为 3 个阶段。首先,用 audit_log_start 开始记录过程。然后,用 audit_log_format 格式化一个记录消息,最后用 audit_log_end 结束该审计记录,消息将排队传输到审计守护进程。

当进程执行系统调用时,审计框架通过以下步骤记录事件:

  1. 入口钩子(syscall_entry

    • 检查是否匹配entry类型的审计规则。

    • 若匹配,记录系统调用名称和参数。

  2. 出口钩子(syscall_exit

    • 检查是否匹配exit类型的审计规则。

    • 若匹配,记录返回值或错误码。

示例流程(以open系统调用为例):

// 系统调用入口处理
void syscall_entry_audit(struct pt_regs *regs, long syscall) {
    struct audit_context *ctx = current->audit_context;
    if (audit_match_syscall(ctx, syscall)) {
        audit_log_start(ctx, GFP_KERNEL, AUDIT_SYSCALL);
        audit_log_format(ab, "syscall=%d args=%lx,%lx,%lx", syscall, regs->di, regs->si, regs->dx);
        audit_log_end(ab);
    }
}

// 系统调用退出处理
void syscall_exit_audit(struct pt_regs *regs, long ret) {
    struct audit_context *ctx = current->audit_context;
    if (audit_match_syscall(ctx, syscall)) {
        audit_log_start(ctx, GFP_KERNEL, AUDIT_SYSCALL);
        audit_log_format(ab, "result=%ld", ret);
        audit_log_end(ab);
    }
}

具体示例:用户配置与审计流程

场景:监控/etc/shadow文件的读写操作。

步骤1:添加审计规则

auditctl -w /etc/shadow -p rwa -k shadow_access
  • 内核动作

    • auditctl通过Netlink将规则发送到内核。

    • 内核解析规则并添加到audit_rule链表。

步骤2:用户尝试读取/etc/shadow

cat /etc/shadow
  • 内核动作

    1. open系统调用触发syscall_entry_audit,记录调用参数。

    2. 文件访问触发file过滤器,匹配规则后生成审计事件。

    3. 事件通过audit_log_start -> audit_log_format -> audit_log_end写入日志。

 步骤3:查看审计日志

ausearch -k shadow_access | aureport -file

日志输出示例

type=SYSCALL msg=audit(1625145600.123:456): arch=c000003e syscall=2 success=yes ...
type=PATH msg=audit(...): item=0 name="/etc/shadow" inode=12345 ...

关键数据结构与函数的使用流程

  1. audit_context的创建与使用

    • 创建时机:当进程首次触发需要审计的操作时,内核分配audit_context并绑定到task_struct

    • 内容填充:在系统调用入口和出口阶段,记录参数、返回值到audit_context

  2. audit_init的初始化流程

    • 内核启动时:调用audit_init初始化Netlink、规则哈希表和系统调用钩子。

    • 守护进程启动auditd通过Netlink监听内核事件,准备接收日志。

https://github.com/0voice