Linux audit详解

什么是audit

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安全体系的重要组成部分,是一种“被动”的防御体系。在内核里有内核审计模块,记录系统中的各种动作和事件,比如系统调用,文件修改,执行的程序,系统登入登出和记录所有系统中所有的事件,它的主要目的是方便管理员根据日记审计系统是否允许有异常,是否有入侵等等,说穿了就是把和系统安全有关的事件记录下来。

audit可以用来干什么

  • Watching file access
  • Monitoring system calls
  • Recording commands run by a user
  • Recording security events
  • Monitoring network access

怎么开启audit

首先内核需要打开CONFIG_AUDIT的配置,在打开了配置重新编译内核后,audit功能默认是关闭的,有两种方法在使能audit:
1)cmdline中加入audit= 1参数,如果这个参数设置为1,而且auditd没有运行,则审计日志会被写到/var/log/messages中。
2)使用守护进程auditd

auditd是如何工作的

下面是auditd整体的框架图:
Linux audit详解_第1张图片
从这个图大概就能看出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_file

指定log文件存放的位置

  • max_log_file

指定单个log文件的最大大小,单位是Mbyte

  • max_log_file_action

当log文件达到max_log_file设定的大小时执行的动作,可选的动作 有:ignore/syslog/suspend/rotate, ignore表示忽略max_log_file设置的限制,继续写log文件,syslog表示会向syslog中写入一条warning,suspend表示auditd不再写log文件,但是auditd继续运行,rotate表示分多个log文件,一个log文件达到上限后在创建一个新的不同名字的log文件,后面会继续讲解该选项。

  • space_left

表示log_file 文件所在的分区空闲空间少于这个设定的值时,触发相应的动作,单位是Mbyte

  • space_left_action
    指定space_left触发后执行的动作,可选的选项有:ignore/syslog/suspend/single/halt,前面三个选项与max_log_file_action相似,single表示audit进程会将系统模式变为单用户模式,halt表示audit进程将会触发系统关机
  • admin_space_left
    系统管理员用户对于的空间,space_left讲的是普通用户
  • admin_space_left_action
    与space_left_action类似,但是对于系统管理员
  • disk_full_action
    磁盘满了之后应该采取的动作
  • disk_error_action
    磁盘写错误之后应该采取的动作
  • flush
    表示日志文件的刷新方式,可选的选项有:NONE、INCREMENTAL、DATA和SYNC,如果设置为NONE,则不需要做特殊努力来将数据刷新到日志文件中。如果设置为INCREMENTAL,则用freq选项的值确定多长时间发生一次向磁盘的刷新。如果设置为DATA,则审计数据和日志文件一直是同步的。如果设置为SYNC,则每次写到日志文件时,数据和元数据是同步的。
  • num_logs
    表示保留日志文件的最大个数,只有在max_log_file_action=rotate时该选项该有意义,必须是0~99之间的数。如果设置为小于2,则不会循环日志。如果递增了日志文件的数目,就可能有必要递增/etc/audit/audit.rules中的内核backlog设置值,以便留出日志循环的时间。如果没有设置num_logs值,它就默认为0,意味着从来不循环日志文件。当达到指定文件容量后会循环日志文件,但是只会保存一定数目的老文件,这个数目由num_logs参数指定。老文件的文件名将为audit.log.N,其中 N是一个数字。这个数字越大,则文件越老。

规则文件详解

先来一种整体的架构图:
Linux audit详解_第2张图片
内核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

你可能感兴趣的:(kernel基础,安全)