Linux内核auditsyscall的机制和原理
Linux内核auditsyscall的机制和原理
Linux 内核中的系统调用审计机制(通常被称为 auditsyscall)是 Linux Auditing Framework (auditd) 的核心组成部分。它允许系统管理员详细记录特定进程执行了哪些系统调用、何时执行、以什么参数执行,以及调用结果如何。这对于安全审计、入侵检测、故障排查和合规性至关重要。
核心机制与原理:
审计子系统集成:
- Linux 内核内置了一个名为
CONFIG_AUDIT的编译选项(通常默认启用),它提供了审计的基础设施。 - 审计子系统维护一个内核空间的事件队列(通常基于
sk_buff结构实现)。 - 用户空间的审计守护进程
auditd通过NETLINK_AUDIT套接字(一种特殊的 IPC 机制)连接到内核审计子系统,持续读取队列中的审计事件。
- Linux 内核内置了一个名为
系统调用入口点的挂钩:
- 这是审计发生的最关键点。内核在执行任何系统调用之前(在系统调用分发函数,如
do_syscall_64或ia32_do_call中)和系统调用返回之后,插入了特定的审计钩子函数。 - 主要钩子函数:
audit_syscall_entry(int arch, unsigned long syscall, ...): 在系统调用实际处理函数执行之前被调用。- 参数:
arch(体系结构标识符),syscall(系统调用号),args(包含系统调用参数的数组,通常最多 6 个参数)。 - 作用:记录“系统调用开始”事件。它捕获即将执行的系统调用的编号和参数。
- 参数:
audit_syscall_exit(long retval): 在系统调用实际处理函数执行之后、结果返回给用户空间之前被调用。- 参数:
retval(系统调用的返回值/错误码)。 - 作用:记录“系统调用结束”事件。它捕获系统调用的执行结果(成功值或错误码)。同时,它会将
entry和exit事件关联起来(通常使用当前任务的audit_context结构)。
- 参数:
- 这是审计发生的最关键点。内核在执行任何系统调用之前(在系统调用分发函数,如
审计上下文:
- 每个进程(任务)在内核中都有一个
task_struct结构。审计子系统为每个需要审计的进程维护一个audit_context结构(通常是task_struct->audit_context)。 - 这个上下文在进程生命周期内(或至少在需要审计的事件期间)存在,用于:
- 临时存储与当前正在审计的事件链相关的信息(例如,关联
entry和exit事件)。 - 存储过滤器匹配结果,避免对同一个系统调用多次评估规则。
- 记录额外的审计点信息(如路径名、文件描述符等),这些信息可能在系统调用执行过程中被填充。
- 临时存储与当前正在审计的事件链相关的信息(例如,关联
- 每个进程(任务)在内核中都有一个
审计规则与过滤器:
- 用户空间工具
auditctl用于配置审计规则。规则定义了哪些事件需要被记录。 - 规则被编译成一种高效的、在内核中运行的 BPF (Berkeley Packet Filter) 字节码。这些规则会被加载到内核审计子系统中。
- 规则匹配过程:
- 当
audit_syscall_entry被调用时,内核会使用加载的 BPF 过滤器检查当前系统调用(结合进程的凭证uid,gid,euid,egid,suid,sgid,fsuid,fsgid,进程IDpid, 父进程IDppid, 可执行文件路径exe, 命令行comm, 架构arch, 系统调用号syscall和参数a0-a5等上下文信息)是否匹配任何审计规则。 - 如果匹配成功,内核会:
- 在
audit_context中设置标志,表明这个系统调用需要被审计。 - 记录
entry事件(包含系统调用号和参数)。
- 在
- 当
audit_syscall_exit被调用时,它会检查audit_context中的标志:- 如果标志被设置(表示在
entry时匹配了规则),则记录exit事件(包含系统调用号和返回值retval)。 - 即使
exit时没有显式规则匹配retval,只要entry匹配了,exit事件就会被记录(因为规则通常关注的是“尝试执行某个动作”)。
- 如果标志被设置(表示在
- 规则也可以基于返回值进行过滤(例如,只记录失败
open的exit事件)。
- 当
- 用户空间工具
审计记录的生成与传递:
- 当决定记录一个事件(
SYSCALL类型的entry或exit)时:- 内核分配一个
audit_buffer。 - 使用
audit_log_format系列函数将事件信息格式化写入缓冲区。信息包括:- 类型:
SYSCALL - 时间戳: 精确到纳秒。
- 序列号: 唯一标识事件的递增数字。
- 进程信息:
pid,ppid,uid,gid,euid,suid,fsuid,egid,sgid,fsgid,auid(审计用户ID,通常是登录用户的原始UID),ses(会话ID)。 - 可执行文件信息:
exe(路径),comm(命令行)。 - 系统调用信息:
syscall(编号),arch(体系结构),success(是/否,基于exit的retval),exit(具体的返回值/错误码)。 - 参数:
a0-a5(对于entry事件)。 - 其他上下文信息: 如当前工作目录 (
cwd), 文件路径(如果审计点捕获了,如open的path规则项)。
- 类型:
- 格式化的记录被放入内核审计队列。
- 内核分配一个
- 用户空间的
auditd守护进程持续监控队列。一旦有新记录到达:auditd通过netlink套接字读取记录。auditd根据其配置文件 (auditd.conf) 将记录写入日志文件(通常是/var/log/audit/audit.log)。auditd也可以配置为实时发送警报或执行其他操作。
- 当决定记录一个事件(
性能考量:
- 审计,尤其是记录所有系统调用,会带来显著的性能开销。内核做了大量优化:
- BPF 过滤器: 规则编译成高效的字节码,在内核中快速评估。
- 快速路径/慢速路径: 如果
audit_context为空且没有全局规则要求审计,内核会快速跳过审计检查。只有在需要时才创建audit_context并执行完整规则匹配。 - 异步记录: 事件记录是异步进行的,放入队列后系统调用即可继续执行,不阻塞调用进程(
auditd处理队列)。 - 选择性审计: 管理员应谨慎定义规则,只审计真正需要的特定系统调用、用户或文件,以最小化性能影响。
- 审计,尤其是记录所有系统调用,会带来显著的性能开销。内核做了大量优化:
总结流程:
- 用户进程发起系统调用(如
open)。 - 内核陷入系统调用入口。
- 调用
audit_syscall_entry(arch, SYS_open, fd, pathname, flags, mode)。 - 内核审计子系统用加载的 BPF 过滤器检查:
(arch, syscall=SYS_open, uid=..., pathname=...)是否匹配任何规则? - 如果匹配:
- 为当前任务设置
audit_context(如果还没有)。 - 在
audit_context中标记需要审计此系统调用。 - 生成
SYSCALLentry记录(含syscall,args),放入队列。
- 为当前任务设置
- 内核执行实际的
open系统调用代码。 - 系统调用执行完毕,准备返回用户空间。
- 调用
audit_syscall_exit(retval)。 - 检查
audit_context:如果之前entry被标记需要审计:- 生成
SYSCALLexit记录(含syscall,success,exit=retval),放入队列。
- 生成
- 内核返回系统调用结果 (
retval) 给用户进程。 - 用户空间
auditd从队列读取entry和exit记录,写入/var/log/audit/audit.log。
关键点:
auditsyscall不是单个函数,而是指代内核在系统调用入口/出口处触发审计记录的整个机制。- 审计发生在内核空间,由内核代码直接挂钩系统调用执行路径。
- 是否记录一个系统调用由用户配置的、在内核中运行的 BPF 规则决定。
- 记录包含极其详细的上下文信息(用户、进程、参数、结果)。
- 性能影响显著,必须精确配置规则以避免过度开销。
- 用户空间工具 (
auditctl,auditd,ausearch,aureport) 用于配置规则、收集和解读日志。
理解这个机制对于有效利用 Linux Audit 框架进行安全监控和合规性报告至关重要。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Helloeuler!
