The Linux Audit Subsystem is a system to Collect information regarding events occurring on the system(s) ,Kernel events (syscall events), User events (audit-enabled programs)
syslog记录的信息有限,主要目的是软件调试,跟踪和打印软件的运行状态,而audit的目的则不同,它是linux安全体系的重要组成部分,是一种“被动”的防御体系。在内核里有内核审计模块,记录系统中的各种动作和事件,比如系统调用,文件修改,执行的程序,系统登入登出和记录所有系统中所有的事件,它的主要目的是方便管理员根据日记审计系统是否允许有异常,是否有入侵等等,说穿了就是把和系统安全有关的事件记录下来。
首先内核需要打开CONFIG_AUDIT的配置,在打开了配置重新编译内核后,audit功能默认是关闭的,有两种方法在使能audit:
1)cmdline中加入audit= 1参数,如果这个参数设置为1,而且auditd没有运行,则审计日志会被写到/var/log/messages中。
2)使用守护进程auditd
下面是auditd整体的框架图:
从这个图大概就能看出audit是如何工作的,可以看到 audit 是内核中的一个模块,内核的运行情况都会记录在 audit 中,当然这个记录的规则是由超级用户来设置的。audit.rules 是 audit 的规则文件,auditctl程序负责将规则写入audit模块的过滤器中,过滤后的数据都会传送到 auditd 中,然后再由 auditd 进行其它操作。auditd.conf 是 auditd 的配置文件,确定 auditd 是如何启动的,日志文件放在哪里等等。auditd 收到的数据后会有两个去处。默认的是将日志保存在 audit.log 文件中,默认路径/var/log/audit/audit.log。另一个通过 audispd 将日志进行分发。
简单的使用:
auditd
auditctl -R /etc/audit/rules.d/audit.rules
cat /var/log/audit/audit.log 就可以查看audit记录的信息
auditd对应的源码在:audit-2.7.1/src/auditd.c,其中audit-2.7.1/lib 对应着库的源码,简单的浏览一下源码就能知道auditd每个选项的作用和它的主要工作
-s指定启动时的audit工作状态,可选的状态有:startup_disable,startup_enable,startup_nochange,如果未指定,默认为
enum startup_state opt_startup = startup_enable;
auditctl对应的源码在:audit-2.7.1/src/auditctl.c,简单浏览一下源码就能知道auditctl每个选择的作用和程序的主要原理。
auditctl的工作流程大概如下:
int main(int argc, char *argv[])
{
if ((argc == 3) && (strcmp(argv[1], "-R") == 0)) {
fd = audit_open();
if (is_ready(fd) == 0)
return 0;
fileopt(argv[2])
}
}
其中需要特别注意的是is_ready(),如果-R选项,也就是从文件中读取规则,则audit系统必须要先处于enable状态,is_ready()的实现如下:
static int is_ready(int fd)
{
if (audit_is_enabled(fd) == 2) { //规则不可改变
audit_msg(LOG_ERR, "The audit system is in immutable mode,"
" no rule changes allowed");
return 0;
} else if (errno == ECONNREFUSED) { //audit处于disable状态
audit_msg(LOG_ERR, "The audit system is disabled");
return 0;
}
return 1;
}
接着上面的分析,fileopt()打开规则文件:
static int fileopt(const char *file)
{
rc = open(file, O_RDONLY);
if (rc < 0) {
... //规则文件不存在,返回错误
}
... //接下来判读规则文件的访问权限,当前进程是否是root,**规则文件是否全局可写**,是否是常规文件
while (get_line(f, buf)) { //循环读取文件中的每一行
preprocess(buf);
ptr = audit_strsplit(buf); //去除行首的空白
if (ptr == NULL) //空行标志着规则文件的结束
break;
if (ptr[0] == '#') { //#开头为注释,直接跳过,读取下一行
lineno++;
continue;
}
reset_vars();//复位变量,也就是说规则是以行为单位的,行之间无关联
rc = setopt(i, lineno, fields); //分析每一行的选项,分析的结果放在rule_new中
if (rc != -3) {
handle_request(rc);//如果规则没错,这执行存放在rule_new中的分析结果
}
lineno++;
}//while
}
需要说明的选项是:
-e: 0表示disable,1表示enable,2表示设置规则不可变
-f:当audit反应严重错误是应该采取的动作,0=silent啥都不干, 1=printk 打印错误,2=panic
/etc/audit/auditd.conf存放在配置文件,决定auditd程序的行为,对其中一些重要的选项进行说明:
指定log文件存放的位置
指定单个log文件的最大大小,单位是Mbyte
当log文件达到max_log_file设定的大小时执行的动作,可选的动作 有:ignore/syslog/suspend/rotate, ignore表示忽略max_log_file设置的限制,继续写log文件,syslog表示会向syslog中写入一条warning,suspend表示auditd不再写log文件,但是auditd继续运行,rotate表示分多个log文件,一个log文件达到上限后在创建一个新的不同名字的log文件,后面会继续讲解该选项。
表示log_file 文件所在的分区空闲空间少于这个设定的值时,触发相应的动作,单位是Mbyte
先来一种整体的架构图:
内核audit模块定义了user,task,entry,exit钩子,每个钩子对应一张表,当内核路径调用对应钩子时,对比钩子对应表中的规则,如果条件符合,则打印log到audit子系统,exclude用于用户取audit log时进行过滤,过滤掉不感兴趣的event。
//task,entry,exit钩子
Kernel:
if (audit_enabled) {
struct audit_buffer *ab;
uid_t loginuid = from_kuid(&init_user_ns, audit_get_loginuid(current));
unsigned int sessionid = audit_get_sessionid(current);
ab = audit_log_start(NULL, GFP_KERNEL, AUDIT_KERNEL_OTHER);
if (!ab)
return;
audit_log_format(ab, "auid=%u ses=%u" ,loginuid, sessionid);
audit_log_task_context(ab);
audit_log_format(ab, " comm=");
audit_log_untrustedstring(ab, comm);
audit_log_end(ab);
}
//用户空间也可产生audit消息,此时对应user钩子
User space:
char buf[4096], *acct;
int fd = audit_open();
// acct is untrusted string and must be encoded
acct = audit_encode_nv_string("acct", pamh->user, 0);
snprintf(buf, sizeof(buf), "op=change-password sauid=%d %s",
audit_getloginuid(), acct);
audit_log_user_message(fd, AUDIT_USER_CHAUTHTOK, buf, NULL, NULL,
NULL, 0);
free(acct);
close(fd);
audit_open()和audit_log_user_message()函数在audit lib中定义,用户空间和audit内核空间是通过netlink进行数据交换的。
auid是Audit User IDentity的缩写,下面一段话有助于理解它的作用:
Clearly, we first need a way to track a user, be it an actual person or a system user, in such a way that we won’t lose track e.g. by a “sudo” or “su”. The way this is done is by setting an additional UID, the AUID, which is different from the “normal” UID a user has, and is supposed to remain unchanged, whatever the user does.
4294967295 is just (unsigned long) -1. -1 means that loginuid was not set. This is normal behavior for processes that were not spawned by any login process (e.g. for daemons). loginuid is -1 by default; pam_loginuid module changes it to your user id whenever you login (in a tty/in DM/via ssh), and this value is preserved by child processes.
pam_loginuid 是pam的一个库,通过调用audit_setloginuid()函数来修改:
int audit_setloginuid(uid_t uid)
{
char loginuid[16];
int o, count, rc = 0;
errno = 0;
count = snprintf(loginuid, sizeof(loginuid), "%u", uid);
o = open("/proc/self/loginuid", O_NOFOLLOW|O_WRONLY|O_TRUNC);
if (o >= 0) {
int block, offset = 0;
while (count > 0) {
block = write(o, &loginuid[offset], (unsigned)count);
if (block < 0) {
if (errno == EINTR)
continue;
printf("Error writing loginuid\n");
close(o);
return 1;
}
offset += block;
count -= block;
}
close(o);
} else {
printf("Error opening /proc/self/loginuid\n");
rc = 1;
}
return rc;
}
当然可能有人会说黑客可以篡改这个值,kernel也是有响应的机制来防止auid设置了之后是不可修改的:
static int audit_set_loginuid_perm(kuid_t loginuid)
{
/* if we are unset, we don't need privs */
if (!audit_loginuid_set(current))
return 0;
/* if AUDIT_FEATURE_LOGINUID_IMMUTABLE means never ever allow a change*/
if (is_audit_feature_set(AUDIT_FEATURE_LOGINUID_IMMUTABLE))
return -EPERM;
/* it is set, you need permission */
if (!capable(CAP_AUDIT_CONTROL))
return -EPERM;
/* reject if this is not an unset and we don't allow that */
if (is_audit_feature_set(AUDIT_FEATURE_ONLY_UNSET_LOGINUID) && uid_valid(loginuid))
return -EPERM;
return 0;
}
每个钩子都有一张表,表中每一行存放一条规则,规则由一系列条件组成,在决定是否打印log到audit子系统时,依次比较每一条规则,如果规则满足,则停止比较,产生audit log,如果所有的规则都不满足,则log丢弃。
auditctl工具就是向对应的list(task,entry,exit)中添加,删除规则。
注意user都是从用户空间写入audit log的,因此不需要定义规则。
规则可以存放在文件中,由auditctl -R /etc/audit/rules.d/audit.rules加载规则文件。
There are three types of Audit rules that can be specified
Control rules — allow the Audit system’s behavior and some of its configuration to be modified.
File system rules — also known as file watches, allow the auditing of access to a particular file or a directory.
System call rules — allow logging of system calls that any specified program makes.
可参考:https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/security_guide/sec-defining_audit_rules_and_controls
//kernel/audit.c
static int __net_init audit_net_init(struct net *net)
{
struct netlink_kernel_cfg cfg = {
.input = audit_receive,
.bind = audit_bind,
.flags = NL_CFG_F_NONROOT_RECV,
.groups = AUDIT_NLGRP_MAX,
};
struct audit_net *aunet = net_generic(net, audit_net_id);
aunet->nlsk = netlink_kernel_create(net, NETLINK_AUDIT, &cfg);
if (aunet->nlsk == NULL) {
audit_panic("cannot initialize netlink socket in namespace");
return -ENOMEM;
}
aunet->nlsk->sk_sndtimeo = MAX_SCHEDULE_TIMEOUT;
return 0;
}
/* Receive messages from netlink socket. */
static void audit_receive(struct sk_buff *skb)
{
mutex_lock(&audit_cmd_mutex);
audit_receive_skb(skb);
mutex_unlock(&audit_cmd_mutex);
}
static void audit_receive_skb(struct sk_buff *skb)
{
struct nlmsghdr *nlh;
/*
* len MUST be signed for nlmsg_next to be able to dec it below 0
* if the nlmsg_len was not aligned
*/
int len;
int err;
nlh = nlmsg_hdr(skb);
len = skb->len;
while (nlmsg_ok(nlh, len)) {
err = audit_receive_msg(skb, nlh);
/* if err or if this message says it wants a response */
if (err || (nlh->nlmsg_flags & NLM_F_ACK))
netlink_ack(skb, nlh, err);
nlh = nlmsg_next(nlh, &len);
}
}
static int audit_receive_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
{
...
case AUDIT_ADD_RULE:
case AUDIT_DEL_RULE:
if (nlmsg_len(nlh) < sizeof(struct audit_rule_data))
return -EINVAL;
if (audit_enabled == AUDIT_LOCKED) {
audit_log_common_recv_msg(&ab, AUDIT_CONFIG_CHANGE);
audit_log_format(ab, " audit_enabled=%d res=0", audit_enabled);
audit_log_end(ab);
return -EPERM;
}
err = audit_rule_change(msg_type, NETLINK_CB(skb).portid,
seq, data, nlmsg_len(nlh));
break;
...
}
//kernel/auditfilter.c
int audit_rule_change(int type, __u32 portid, int seq, void *data,
size_t datasz)
{
int err = 0;
struct audit_entry *entry;
entry = audit_data_to_entry(data, datasz);
if (IS_ERR(entry))
return PTR_ERR(entry);
switch (type) {
case AUDIT_ADD_RULE:
err = audit_add_rule(entry);
audit_log_rule_change("add_rule", &entry->rule, !err);
break;
case AUDIT_DEL_RULE:
err = audit_del_rule(entry);
audit_log_rule_change("remove_rule", &entry->rule, !err);
break;
default:
err = -EINVAL;
WARN_ON(1);
}
...
}
//kernel/auditsc.c
void __audit_syscall_entry(int major, unsigned long a1, unsigned long a2,
unsigned long a3, unsigned long a4)
{
struct task_struct *tsk = current;
struct audit_context *context = tsk->audit_context;
enum audit_state state;
if (!context)
return;
BUG_ON(context->in_syscall || context->name_count);
if (!audit_enabled)
return;
context->arch = syscall_get_arch();
context->major = major;
context->argv[0] = a1;
context->argv[1] = a2;
context->argv[2] = a3;
context->argv[3] = a4;
state = context->state;
context->dummy = !audit_n_rules;
if (!context->dummy && state == AUDIT_BUILD_CONTEXT) {
context->prio = 0;
state = audit_filter_syscall(tsk, context, &audit_filter_list[AUDIT_FILTER_ENTRY]); //比较规则
}
if (state == AUDIT_DISABLED) //如果规则不匹配,则返回
return;
context->serial = 0;
context->ctime = CURRENT_TIME;
context->in_syscall = 1; //否则设置in_syscall 标志位
context->current_state = state;
context->ppid = 0;
}
void __audit_syscall_exit(int success, long return_code)
{
struct task_struct *tsk = current;
struct audit_context *context;
if (success)
success = AUDITSC_SUCCESS;
else
success = AUDITSC_FAILURE;
context = audit_take_context(tsk, success, return_code);
if (!context)
return;
if (context->in_syscall && context->current_state == AUDIT_RECORD_CONTEXT) //如果in_syscall 为1,也就是规则满足
audit_log_exit(context, tsk); //打印审计日志
...
}
static void audit_log_exit(struct audit_context *context, struct task_struct *tsk)
{
int i, call_panic = 0;
struct audit_buffer *ab;
struct audit_aux_data *aux;
struct audit_names *n;
/* tsk == current */
context->personality = tsk->personality;
ab = audit_log_start(context, GFP_KERNEL, AUDIT_SYSCALL);
if (!ab)
return; /* audit_panic has been called */
audit_log_format(ab, "arch=%x syscall=%d",
context->arch, context->major);
if (context->personality != PER_LINUX)
audit_log_format(ab, " per=%lx", context->personality);
if (context->return_valid)
audit_log_format(ab, " success=%s exit=%ld",
(context->return_valid==AUDITSC_SUCCESS)?"yes":"no",
context->return_code);
audit_log_format(ab,
" a0=%lx a1=%lx a2=%lx a3=%lx items=%d",
context->argv[0],
context->argv[1],
context->argv[2],
context->argv[3],
context->name_count);
audit_log_task_info(ab, tsk);
audit_log_key(ab, context->filterkey);
audit_log_end(ab);
}
参考资料:
https://doc.opensuse.org/documentation/leap/security/html/book.security/cha.audit.comp.html
https://www.suse.com/documentation/sled11/book_security/data/sec_audit_auditd.html
https://www.ibm.com/developerworks/cn/linux/l-lo-use-space-audit-tool/index.html
https://blog.csdn.net/qwertyupoiuytr/article/details/58278349
https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/security_guide/sec-defining_audit_rules_and_controls
https://github.com/linux-audit/audit-documentation/wiki