首码网站免费推广,什么是网络营销环境?,wordpress for sea,特乐网站建设文章目录 2.6.6版本的audit审计机制分析1 关于内核版本2 入口3 audit初始化4 系统调用审计流程5 配置下发流程6 总结 2.6.6版本的audit审计机制分析
1 关于内核版本
linux内核从2.6.6版本开始支持audit机制#xff0c;为了更好的理解audit本身的机制#xff0c;需要对audit… 文章目录 2.6.6版本的audit审计机制分析1 关于内核版本2 入口3 audit初始化4 系统调用审计流程5 配置下发流程6 总结 2.6.6版本的audit审计机制分析
1 关于内核版本
linux内核从2.6.6版本开始支持audit机制为了更好的理解audit本身的机制需要对audit的内核代码进行分析。
2 入口
内核中audit相关的代码主要有三个文件
include/linux/audit.h 头文件主要包含常量、数据结构和接口声明kernel/audit.c 全局变量定义以及接口的实现kernel/auditsc.c 系统调用的审计的实现
从audit的功能来说包含三个部分
audit的初始化审计日志的生成和写入规则以及其他的操作函数
3 audit初始化
audit的初始化包括两个函数
audit_init()该函数用__initcall宏进行修饰该函数会被放到.init.text段在audit模块加载时会被调用是整个audit模块的初始化函数audit_enable()该函数用__setup宏进行修饰当bootloader传给kernel的参数是audit0或者audit1就会调用audit_enable()函数并将0或者1作为字符串参数传给audit_enable()
对于audit_init()函数根据CONFIG_NET宏还定义了不同的实现如果定义了CONFIG_NET就会创建aduit的netlink的socket否则就不会创建socket。
4 系统调用审计流程
当前的audit版本只支持系统调用的审计因此主要查看系统调用的审计流程。
系统调用的入口位于arch/x86_64/kernel/entry.S中间的x86_64随实际的架构有所变化。
ENTRY(system_call)CFI_STARTPROCswapgsmovq %rsp,%gs:pda_oldrsp movq %gs:pda_kernelstack,%rspsti SAVE_ARGS 8,1movq %rax,ORIG_RAX-ARGOFFSET(%rsp) movq %rcx,RIP-ARGOFFSET(%rsp) GET_THREAD_INFO(%rcx)testl $(_TIF_SYSCALL_TRACE|_TIF_SYSCALL_AUDIT),threadinfo_flags(%rcx)jnz tracesyscmpq $__NR_syscall_max,%raxja badsysmovq %r10,%rcxcall *sys_call_table(,%rax,8) # XXX: rip relativemovq %rax,RAX-ARGOFFSET(%rsp)上面是系统调用的入口开始会处理一些参数中间就会判断是否启用trace或者audit如果启用就会调用tracesys
tracesys: SAVE_RESTmovq $-ENOSYS,RAX(%rsp)FIXUP_TOP_OF_STACK %rdimovq %rsp,%rdicall syscall_trace_enterLOAD_ARGS ARGOFFSET /* reload args from stack in case ptrace changed it */RESTORE_RESTcmpq $__NR_syscall_max,%raxja 1fmovq %r10,%rcx /* fixup for C */call *sys_call_table(,%rax,8)movq %rax,RAX-ARGOFFSET(%rsp)
1: SAVE_RESTmovq %rsp,%rdicall syscall_trace_leaveRESTORE_TOP_OF_STACK %rbxRESTORE_RESTjmp ret_from_sys_call这里就进行系统调用的审计里面有三个call中间的call就是调用系统调用第一个call是系统调用入口的审计第三个call是系统调用出口的审计。
// arch/x86_64/kernel/ptrace.c
asmlinkage void syscall_trace_enter(struct pt_regs *regs)
{if (unlikely(current-audit_context))audit_syscall_entry(current, regs-orig_rax,regs-rdi, regs-rsi,regs-rdx, regs-r10);if (test_thread_flag(TIF_SYSCALL_TRACE) (current-ptrace PT_PTRACED))syscall_trace(regs);
}这里的unlikely可以进行编译优化Linux内核入门-- likely和unlikely。
后续调用流程如下
audit_syscall_entry 位于kernel/auditsc.c audit_alloc_context 创建audit_contextaudit_filter_syscall audit_filter_rules 对比进程的信息和规则确定是否匹配此处实现的就是-F的过滤选项过滤选项可以对产生日志的进程的字段进行过滤该函数的返回值表明过滤选项是否匹配上同时会根据action设置state 如果规则判断不是AUDIT_DISABLE则将serial、time保存到context中并在context中保存两个标记in_syscall和auditable
系统调用结束时的调用流程
audit_syscall_exit 位于kernel/auditsc.c audit_get_context 从task_struct中获取audit的contextaudit_filter_syscall用进程的信息填充context例如填充pidcontext-pid tsk-pid audit_log_exit 如果context中的in_syscall1和auditable1则执行 audit_log_start 如果audit_backlog大于audit_backlog_limit并且audit_rate_check()为真则内核日志会出现audit: audit_backlogXX audit_backlog_limitXX的打印audit_log_lost audit_lost加1atomic_inc(audit_lost)如果rate_limit为0则会在内核日志中打印audit: audit_lostXX audit_backlogXX audit_rate_limitXX audit_backlog_limitXX从audit_freelist链表中取一个audit_buffer如果获取不到则分配一个如果分配失败则调用audit_log_lostaudit_backlog加1atomic_inc(audit_backlog)audit_log_format 将audit(second.milisecond:serial)写入刚才分配的audit_buffer audit_log_format 先将syscall、per、exit、参数以及一些id作为审计日志输出然后再输出系统调用过程中访问的文件和设备信息 audit_log_vformat 将要审计日志写入audit_buffer audit_log_end audit_log_end_irq 如果当前处于硬件中断上下文时调用将audit_buffer放到audit_txlist队列 list_add_tailtasklet_schedule(audit_tasklet) 进行tasklet的任务调度执行audit_tasklet audit_log_end_fast 如果当前不处于硬件中断上下文时调用直接将audit_buffer中的数据发送到用户空间 audit_log_move 将audit_buffer中的内容拷贝到sk_buffaudit_log_drain 遍历audit_buffer中的sk_buff将sk_buffer发送到用户空间 从audit_buffer中的sklist队列获取sk_buff然后根据audit_pid将调用netlink_unicast将数据发送出去如果多次发送失败且错误码是EAGAIN就会调用audit_log_end_irq将audit_buffer放到audit_txlist队列其他错误则会打印错误日志如果发送数据的返回值是ECONNREFUSED则在内核日中打印audit: *NO* daemon at audit_pid%d并将audit_pid设置为0如果audit_pid为0也就是没有进程订阅audit则会将审计日志输出到内核日志 audit_backlog减1atomic_dec(audit_backlog)kfree/list_add如果当前空闲链表的节点过多则直接将audit_buffer释放否则将audit_buffer放到空闲链表 将context中的in_syscall和auditable都设置为0释放context
从整个实现的流程看主要依赖audit_context进行数据传递在真正要执行系统调用前创建audit_context将参数保存到audit_context并生成audit日志的audit(second.milisecond:serial)部分保证单次系统调用生成的多条审计日志的这部分内容是一样的将audit_context保存到进程的task_struct在系统调用结束后获取进程的audit_context用进程的信息填充audit_context再将数据写入到audit_buffer缓存中最后要么直接通过netlink_unicast发送给用户态进程要么将数据放到audit_txlist队列。
audit_log_exit()会将audit_context中的数据输出到审计日志
struct audit_names {const char *name;unsigned long ino;dev_t rdev;
};struct audit_context {int in_syscall; /* 标记是否正在执行系统调用 */enum audit_state state;unsigned int serial; /* 审计日志的序列号 */struct timespec ctime; /* 系统调用入口的时间 */uid_t loginuid; /* login uid (identity) */int major; /* 系统调用号 */unsigned long argv[4]; /* 系统调用参数 */int return_valid; /* 返回值是否有效 */int return_code;/* 系统调用返回值 */int auditable; /* 标记是否需要写入审计日志 */int name_count;struct audit_names names[AUDIT_NAMES];struct audit_context *previous; /* 系统调用嵌套 *//* 写入审计日志的字段 */pid_t pid;uid_t uid, euid, suid, fsuid;gid_t gid, egid, sgid, fsgid;unsigned long personality;#if AUDIT_DEBUGint put_count;int ino_count;
#endif
};在打印日志时会先输出一条日志里面会输出pid到personality这些字段以及上面的liginuid、major、argv、return_code等字段然后就会打印names这个数组中的字段这个里面是什么呢看字面意思就是名字是什么的名字呢而且里面还有name、inode、rdev这些字段。其实这里保存的就是系统调用执行过程中访问的文件或者目录的信息。
有两个地方会向names中加入元素
fs/namei.c中的getname()将用户空间的文件名拷贝到内核空间然后将文件名保存到audit_namesfs/namei.c中的path_lookup()调用audit_inode()将查找的路径的dentry的inode的i_ino和i_rdev保存到audit_names
names中的元素打印的格式类似于item0 namefname inode11111 devxx:xx因此这里打印的就是系统调用过程中查找或者访问过的目录或者文件。
5 配置下发流程
上面是系统调用过程中根据给定的规则然后输出审计日志那么审计规则是怎么来的呢当用户在机器上执行auditctl -l或者auditctl -S进行审计规则的读取和设置时依然是通过NETLINK_AUDIT的netlink socket进行通信的。audit自身的一些配置也是采用同样的流程。
当用户态程序需要增加规则时通常会使用libaudit的audit_add_rule_data()
int audit_add_rule_data(int fd, struct audit_rule_data *rule,int flags, int action) {int rc;if (flags AUDIT_FILTER_ENTRY) {audit_msg(LOG_WARNING, Use of entry filter is deprecated);return -2;}rule-flags flags;rule-action action;rc audit_send(fd, AUDIT_ADD_RULE, rule,sizeof (struct audit_rule_data) rule-buflen);if (rc 0)audit_msg(audit_priority(errno),Error sending add rule data request (%s),errno EEXIST ?Rule exists : strerror(-rc));return rc;
}这里的audit_send就是调用sendto系统调用向内核发送规则数据调用sendto时addr.nl_family设置为AF_NETLINK下一步就会进入到内核的sys_sendto系统调用。
sys_sendto的调用流程如下
sys_sendto sock_sendmsg __sock_sendmsg sock-ops-sendmsg 调用套接字的sendmsg函数此处不同的family类型就会调用不同的函数对于AF_NETLINK来说就会调用netlink_sendmsg netlink_unicast netlink_attachskbnetlink_sendskb sk-sk_data_ready 此处将sk_buff放到socket的接收队列的尾部然后调用sk_data_ready进行处理
sk_data_ready是在创建内核netlink socket时设置的通过netlink_data_ready最终调用到audit_init中的audit_receive在audit_init初始化函数中在创建netlink socket时会指定一个回调函数用于处理收到的数据audit_sock netlink_kernel_create(NETLINK_AUDIT, audit_receive)。
audit_receive的调用链
audit_receive skb_dequeue audit_receive_skb audit_receive_msgnetlink_ack
在audit_receive_msg中会根据收到的消息的类型执行不同的业务逻辑 AUDIT_GET用户态程序查询audit的状态对应auditctl -s命令 AUDIT_SET用户态程序设置audit的配置对应auditctl的设置命令例如auditctl -b设置backlog_limitauditctl -e设置enabledauditctl --reset-lost重置lost AUDIT_USER接收用户态发送的数据直接写入到审计日志对应auditctl -m命令 AUDIT_LOGIN用户和注销时的审计日志 AUDIT_LIST列出系统调用监控规则对应auditctl -l命令 AUDIT_ADD增加系统调用监控规则对应auditctl -a命令 AUDIT_DEL删除系统调用监控规则对应auditctl -d命令 audit_receive_msg audit_receive_filter AUDIT_LIST向用户态程序发送audit_tsklist、audit_entlist、audit_extlist的数据然后发送一个空AUDIT_ADD将用户空间的规则拷贝到audit_entry然后根据规则类型加入到不同的链表AUDIT_DEL根据规则类型删除不同的链表中的元素
所以规则的操作也就是操作audit_tsklist、audit_entlist、audit_extlist三个链表这三个链表的含义分别是task、entry、exit。
audit_tsklist的调用链
sys_fork arch/x86_64/kernel/process.c do_fork kernel/fork.c copy_process audit_alloc kernel/auditsc.c audit_filter_task audit_filter_rules(audit_tsklist)
audit_entlist的调用链
audit_syscall_entry audit_filter_syscall(audit_entlist)
audit_extlist的调用链
audit_syscall_exit audit_get_context audit_filter_syscall(audit_extlist)
6 总结
从整个流程来说需要了解以下内容
用户态程序和内核态程序通过netlink机制进行交互且接口与网络套接字的一致都是采用类似sendto/recvfrom的系统调用只是里面的family字段不同2.6.6版本的audit只支持系统调用的审计不支持文件和目录的监控2.6.6版本的audit中没有内核态的kaudit线程审计日志的发送在系统调用退出阶段相当于是个同步的发送过程可以想象当审计日志数量比较大时可能会影响系统调用