Audit Inode
title: audit inode author: fuqiang date: 2025-12-04 20:05:00 +0800 categories: [audit] tags: [audit] math:true —
overflow
对于path-based的syscall(例如sys_open()),其中比较关键的审计信息是path以及 其文件的inode信息. audit subsystem想将这些信息保存下来。
理想情况下是, 如果我们open("./aaa", )
我们希望得到的审计信息包含哪些呢?
- ./aaa的绝对路径:
$pwd/aaa - inode 相关信息: ino..
- 其所在的块设备: dev number
理想是很美好的, 但是, 我们知道audit 子系统是在内核的一些现有的流程中插入一些审计 代码,这些代码会从当前的执行上下文中 获取审计信息,然后在系统调用退出之前RECORD write(这里只关心syscall audit)
那么上面的这些信息在那些上下文中呢?
- ./aaa绝对路径: 系统调用开始,
getname()后,可得到安全的内核地址(copy_from_user) - inode 相关信息: 在 path walk 后,
path_lookup()函数 - 所在的块设备: 也需要在path walk 后,才能确定
但是我们思考几个问题:
- 我们在
path_lookup获取到的inode信息一定是path-basedsyscall 参数传过来的路 径么? - 我们要不要获取非
path-basedsyscall 所进行文件操作的审计信息
带着这两个问题, 我们来看下linux历史长河中关于audit inode轰轰烈烈的改革。
create file, only record parent ?
第一版patch 在1中引入, 其中:
在getname()路径中,会记录文件的路径
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*
* getname()
* audit_getname()
*/
/* Add a name to the list. Called from fs/namei.c:getname(). */
void audit_getname(const char *name)
{
struct audit_context *context = current->audit_context;
BUG_ON(!context);
if (!context->in_syscall) {
#if AUDIT_DEBUG == 2
printk(KERN_ERR "%s:%d(:%d): ignoring getname(%p)\n",
__FILE__, __LINE__, context->serial, name);
dump_stack();
#endif
return;
}
BUG_ON(context->name_count >= AUDIT_NAMES);
context->names[context->name_count].name = name;
context->names[context->name_count].ino = (unsigned long)-1;
context->names[context->name_count].rdev = -1;
++context->name_count;
}
可以看到这里只赋值了name, 而未赋值ino, rdev.
在path_lookup()中, 会记录inode相关信息:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/*
* path_lookup
* audit_inode()
*/
/* Store the inode and device from a lookup. Called from
* fs/namei.c:path_lookup(). */
void audit_inode(const char *name, unsigned long ino, dev_t rdev)
{
int idx;
struct audit_context *context = current->audit_context;
if (!context->in_syscall)
return;
if (context->name_count
&& context->names[context->name_count-1].name
&& context->names[context->name_count-1].name == name)
idx = context->name_count - 1;
else if (context->name_count > 1
&& context->names[context->name_count-2].name
&& context->names[context->name_count-2].name == name)
idx = context->name_count - 2;
else {
/* FIXME: how much do we care about inodes that have no
* associated name? */
if (context->name_count >= AUDIT_NAMES - AUDIT_NAMES_RESERVED)
return;
idx = context->name_count++;
context->names[idx].name = NULL;
#if AUDIT_DEBUG
++context->ino_count;
#endif
}
context->names[idx].ino = ino;
context->names[idx].rdev = rdev;
}
可以看到这里作者做了一些匹配, 只匹配数组的最后两个,如果不匹配,那干脆就不匹 配了, 直接搞个新的name, 将 name 更新为NULL
另外,我们期望得到的是绝对路径,但是getname()可能传递绝对路径(也可能传绝对路径) 所以作者采用了另一种方式记录该信息: 记录pwd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/*
* audit_log_exit
*/
static void audit_log_exit(void)
{
...
if (context->pwd.dentry && context->pwd.mnt) {
ab = audit_log_start(context, GFP_KERNEL, AUDIT_CWD);
if (ab) {
audit_log_d_path(ab, "cwd=", &context->pwd);
audit_log_end(ab);
}
}
...
}
但是呢? 如果我们执行open(O_CREAT, )创建新文件,
这时path_lookup获取的是父节点的 inode:
1
2
3
4
5
open_namei
=> if (!(flag & O_CREAT))
=> path_lookup(pathname, lookup_flags(flag)|LOOKUP_OPEN, nd);
=> goto ok
=> path_lookup(pathname, LOOKUP_PARENT|LOOKUP_OPEN|LOOKUP_CREATE, nd);
所以我们通过审计在当前版本内核中,只能获取到父节点的inode.
相关commit
- first commit -
v2.6.6-rc1