
14.2.2 规则列表内核为 audit 规则设置了六个列表User、Task、Entry、Watch、Exit 和 Type依次对应audit_filter_list 数组的第 0第 5 个元素。代码如下。kernel/auditfilter.c struct list_head audit_filter_list[AUDIT_NR_FILTERS] { LIST_HEAD_INIT(audit_filter_list[0]), LIST_HEAD_INIT(audit_filter_list[1]), LIST_HEAD_INIT(audit_filter_list[2]), LIST_HEAD_INIT(audit_filter_list[3]), LIST_HEAD_INIT(audit_filter_list[4]), LIST_HEAD_INIT(audit_filter_list[5]), #if AUDIT_NR_FILTERS ! 6 #error Fix audit_filter_list initialiser #endif }; static struct list_head audit_rules_list[AUDIT_NR_FILTERS] { LIST_HEAD_INIT(audit_rules_list[0]), LIST_HEAD_INIT(audit_rules_list[1]), LIST_HEAD_INIT(audit_rules_list[2]), LIST_HEAD_INIT(audit_rules_list[3]), LIST_HEAD_INIT(audit_rules_list[4]), LIST_HEAD_INIT(audit_rules_list[5]), }audit_filter_list 用于规则匹配audit_rules_list 只用于规则显示。当用户态进程通过 netlink套接字传送“AUDIT_LIST_RULES”消息给内核时内核就会把所有的审计规则发还给用户态进程。Watch 列表已经没有用处保留它多半是为了和旧版本的用户态程序兼容。用户态程序大多数情况下是 auditctl会向内核请求访问某个规则列表。比如请求向编号为 4Exit的列表中加入规则。Watch 列表的编号是 3删除它会导致其后的列表编号变动。这或许是保留它的原因。下面逐一解释规则列表。1. User此处的 User 指用户态审计消息。内核缺省是将所有接收到的用户态类型的审计消息发送给auditd。通过这个规则队列管理员可以配置规则让内核忽略某些用户态类型 audit 消息。相关代码如下下面看一下 audit_filter_user 的实现 kernel/auditfilter.c int audit_filter_user(int type) { … ret 1; /* Audit by default */ … list_for_each_entry_rcu(e, audit_filter_list[AUDIT_FILTER_USER], list) { rc audit_filter_user_rules(e-rule, type, state); if (rc) { if (rc 0 state AUDIT_DISABLED) ret 0; break; } } … return ret; }2. Type前面的 User 规则列表针对的是用户态消息类型下面要介绍的 Type 规则列表针对的是内核产生的消息类型。如果管理员不想看到某种类型的内核 audit 消息则可以生成一条规则让内核不发送这种类型的 audit 消息到 auditd。前面讲过其他内核子系统如果要产生一条内核审计消息日志性质的消息需要先调用audit_log_start 函数。下面看一下 audit_log_startkernel/audit.c struct audit_buffer *audit_log_start(struct audit_context *ctx, gfp_t gfp_mask, int type) { … if (unlikely(audit_filter_type(type))) return NULL; … }如果在 Type 规则队列中有条规则规定某个消息类型“不受欢迎”则使用此消息类型获取的 audit_buffer 是空的也就产生不了相应的 audit 消息。3. Taskaudit 子系统对进程创建有特殊照顾为它专门创建了一个规则列表。所以对于 clone 和fork 系统调用除了要经过下面提到的 Entry 和 Exit 两个规则列表外还会检查 Task 规则列表中的规则。4. EntryEntry 对应于系统调用的入口。在刚刚进入系统调用时能检查的只是系统调用号和系统调用的前四个参数。kernel/auditsc.c void __audit_syscall_entry(int arch, int major, unsigned long a1, unsigned long a2, unsigned long a3, unsigned long a4) { … context-arch arch; context-major major; context-argv[0] a1; context-argv[1] a2; context-argv[2] a3; context-argv[3] a4; … if (!context-dummy state AUDIT_BUILD_CONTEXT) { context-prio 0; state audit_filter_syscall(tsk, context, audit_filter_list[AUDIT_ FILTER_ENTRY]); } … }5. ExitExit 列表是最重要的。Exit 规则的检查点是在系统调用结束时此时是数据最丰富的时候。调用它的地方在 audit_get_context 中audit_get_context 这个函数名很难让人想到规则匹配。kernel/auditsc.c static inline struct audit_context *audit_get_context(struct task_struct *tsk, int return_valid, long return_code) { … if (context-in_syscall !context-dummy) { audit_filter_syscall(tsk, context, audit_filter_list[AUDIT_FILTER_ EXIT]); audit_filter_inodes(tsk, context); } tsk-audit_context NULL; return context; }14.2.3 对文件的审计Linux audit 子系统对文件的审计有三套方案。第一套使用文件的 inode 号第二套使用文件的路径名第三套使用目录的路径名。1. 使用 inode 号审计文件来看一下规则匹配的代码kernel/auditsc.c static int audit_filter_rules(struct task_struct *tsk, struct audit_krule *rule, struct audit_context *ctx, struct audit_names *name, enum audit_state *state, bool task_creation) { … for (i 0; i rule-field_count; i) { struct audit_field *f rule-fields[i]; struct audit_names *n; int result 0; switch (f-type) { case AUDIT_INODE: if (name) result audit_comparator(name-ino, f-op, f-val); else if (ctx) { list_for_each_entry(n, ctx-names_list, list) { if (audit_comparator(n-ino, f-op, f-val)) { result; break; } } } break; … } } }在 audit context 中逐一匹配规则中存储的 inode 号。2. 使用文件的路径名审计文件使用文件的路径名来审计文件依赖关键数据结构 audit_watch。在 audit_filter_rules 函数中有下面这段语句case AUDIT_WATCH: if (name)result audit_watch_compare(rule-watch, name-ino, name-dev); break;audit_watch_compare 的函数实现是kernel/audit_watch.c int audit_watch_compare(struct audit_watch *watch, unsigned long ino, dev_t dev) { return (watch-ino ! (unsigned long)-1) (watch-ino ino) (watch-dev dev); }看起来和前面的基于 inode 号的方案没有什么区别也是比较 inode 号。奥妙在于 watch 中的 ino 的值可以是“-1”表示这个 watch 还未生效。watch 的生效依赖于另一个数据类型—— audit_parent:kernel/audit_watch.c struct audit_watch { atomic_t count; /* reference count */ dev_t dev; /* associated superblock device */ char *path; /* insertion path */ unsigned long ino; /* associated inode number */ struct audit_parent *parent; /* associated parent */ struct list_head wlist; /* entry in parent-watches list */ struct list_head rules; /* anchor for krule-rlist */ }; struct audit_parent { struct list_head watches; /* anchor for audit_watch-wlist */ struct fsnotify_mark mark; /* fsnotify mark on the inode */ }audit_parent 只有两个成员一个是 fsnotify mark另一个是 list串起相关的 watch。audit_parent 对应一个目录audit_watch 对应一个文件。当用户为一个文件创建一个 audit watch时这个 audit watch 就被串入 audit parent 的 watches 链表中。audit watch 的神奇之处在于用户可以为还不存在的文件创建 watch。做到这一点需要 audit_parent 中的类型为 fsnotify_mark 的成员 mark。kernel/audit_watch.c static int audit_watch_handle_event(struct fsnotify_group *group, struct inode *to_tell, struct fsnotify_mark *inode_mark, struct fsnotify_mark *vfsmount_mark, u32 mask, void *data, int data_type, const unsigned char *dname) { struct inode *inode; struct audit_parent *parent; parent container_of(inode_mark, struct audit_parent, mark); BUG_ON(group ! audit_watch_group); switch (data_type) { case (FSNOTIFY_EVENT_PATH): inode ((struct path *)data)-dentry-d_inode; break; case (FSNOTIFY_EVENT_INODE): inode (struct inode *)data; break; default: BUG(); inode NULL; break; }; if (mask (FS_CREATE|FS_MOVED_TO) inode) audit_update_watch(parent, dname, inode-i_sb-s_dev, inode-i_ino, 0); else if (mask (FS_DELETE|FS_MOVED_FROM)) audit_update_watch(parent, dname, (dev_t)-1, (unsigned long)-1, 1); else if (mask (FS_DELETE_SELF|FS_UNMOUNT|FS_MOVE_SELF)) audit_remove_parent_watches(parent); return 0; } static const struct fsnotify_ops audit_watch_fsnotify_ops { .handle_event audit_watch_handle_event, }fsnotify 是 Linux 内核中的一个机制audit 依靠它在文件发生变化时得到通知。当目录下有文件创建或删除时audit_watch_handle_event 就会被调用。当文件创建时audit_watch 中的 ino 会被更新为实际的文件 ino 号当文件被删除时audit_watch 中的 ino 会被赋值为表示无效的“-1”。3. 使用目录的路径名来审计文件使用目录的路径名审计文件依赖关键数据结构 audit_tree。如果用 audit_watch 审计一个目录下的所有文件那么就需要建立许多条审计规则。即使如此仍然无法对还不存在并且不知道名字的文件实行审计。audit_watch 只能对知道名字但还不存在的文件建立审计规则。这种情况就需要 audit_tree 了。audit 子系统设计了两个结构体audit_tree 和 audit_chunk。在逻辑上是一个 tree 包含若干chunk。tree 代表目录树那 chunk 呢chunk 这个英文单词的意思是“一大块”这个单词多半是 trunk意思为树干的误用。可能 audit_tree.c 的作者是个母语非英语的程序员。目录树的概念比较容易理解。“树干”是什么呢“树干”就是文件系统的挂载点。在建立一条包含目录的审计规则时内核 audit 子系统会构建一个 audit_tree 实例同时循目录遍历一遇到挂载点就构建 audit_chunk 实例并将其关联到 audit_tree 中。在涉及文件的系统调用中内核 audit 系统的钩子函数会查询文件所在文件系统的挂载点将其记录到进程的 audit_context中。在系统调用结束时对 audit_context 进行检查。