与用户空间审计系统的netlink通信机制
内核审计系统与用户空间的审计后台auditd、规则设置程序auditctl使用netlink机制进行通信。
应用程序auditctl把用户的设置请求消息发送给内核审计系统,内核审计系统解析消息并进行相应操作,然后将操作的结果回传给应用程序auditctl。
当netlink机制的接收套接字缓冲区数据准备好时,netlink机制调用函数audit_receive接收来自用户空间应用程序auditctl的消息。
函数audit_receive接收auditctl的消息,并根据消息设置规则链表、过滤审计信息、设置内核审计系统状态等。在接收第一个消息时,它还启动内核审计系统后台线程kauditd,线程kauditd专门用于将审计信息发送至用户空间后台进程auditd。函数audit_receive的调用层次图如图2-4所示。
错误!
图2-4 函数audit_receive的调用层次图 |
函数audit_receive列出如下:
/*定义互斥锁,序列化从用户空间来的请求*/ static DEFINE_MUTEX(audit_cmd_mutex); static void audit_receive(struct sock *sk, int length) { struct sk_buff *skb; unsigned int qlen; mutex_lock(&audit_cmd_mutex); //从接收队列中取出skb,逐个进行处理 for (qlen = skb_queue_len(&sk->sk_receive_queue); qlen; qlen--) { skb = skb_dequeue(&sk->sk_receive_queue); audit_receive_skb(skb); kfree_skb(skb); } mutex_unlock(&audit_cmd_mutex); } |
函数audit_receive_skb从接收套接字缓冲skb取出消息,这些消息来自应用程序auditctl。它调用函数audit_receive_msg处理每一条消息,并应答确认信号。该函数列出如下:
static void audit_receive_skb(struct sk_buff *skb) { interr; struct nlmsghdr*nlh; u32rlen; while (skb->len >= NLMSG_SPACE(0)) { nlh = (struct nlmsghdr *)skb->data; if (nlh->nlmsg_len < sizeof(*nlh) || skb->len < nlh->nlmsg_len) return; rlen = NLMSG_ALIGN(nlh->nlmsg_len); //32位对齐的长度 if (rlen > skb->len) rlen = skb->len; if ((err = audit_receive_msg(skb, nlh))) { netlink_ack(skb, nlh, err); //应答错误信号 } else if (nlh->nlmsg_flags & NLM_F_ACK) netlink_ack(skb, nlh, 0);//应答无错误的确认信号 skb_pull(skb, rlen);//从skb开始删除数据 } } |
函数audit_receive_msg处理每条接收的消息,当第一次接收到用户空间auditctl的消息时,它创建专门用来发送审计消息的线程kauditd,用来发送内核的审计消息。该函数列出如下:
static int audit_receive_msg(struct sk_buff *skb, struct nlmsghdr *nlh) { u32uid, pid, seq, sid; void*data; struct audit_status*status_get, status_set; interr; struct audit_buffer*ab; u16msg_type = nlh->nlmsg_type; //得到消息类型 uid_tloginuid; /* 发送者注册uid */ struct audit_sig_info *sig_data; char*ctx; u32len; //检查接收的消息类型是否正确 err = audit_netlink_ok(skb, msg_type); if (err) return err; /* 创建名为kauditd的内核线程,用来将审计消息发送给审计后台auditd*/ if (!kauditd_task) //创建线程并唤醒线程 kauditd_task = kthread_run(kauditd_thread, NULL, "kauditd"); if (IS_ERR(kauditd_task)) { err = PTR_ERR(kauditd_task); kauditd_task = NULL; return err; } pid = NETLINK_CREDS(skb)->pid; uid = NETLINK_CREDS(skb)->uid; loginuid = NETLINK_CB(skb).loginuid; sid = NETLINK_CB(skb).sid; seq = nlh->nlmsg_seq; //消息序列号 data = NLMSG_DATA(nlh); switch (msg_type) { //根据用户空间的应用程序auditctl发来的消息类型进行处理 case AUDIT_GET: //得到内核审计系统状态 status_set.enabled = audit_enabled; status_set.failure = audit_failure; status_set.pid = audit_pid; status_set.rate_limit = audit_rate_limit; status_set.backlog_limit = audit_backlog_limit; status_set.lost = atomic_read(&audit_lost); status_set.backlog = skb_queue_len(&audit_skb_queue); //分配并填充套接字缓冲区,然后调用netlink机制函数netlink_unicast将应答发给auditctl audit_send_reply(NETLINK_CB(skb).pid, seq, AUDIT_GET, 0, 0, &status_set, sizeof(status_set)); break; case AUDIT_SET://开/关审计系统、设置状态 ...... break; case AUDIT_USER: case AUDIT_FIRST_USER_MSG...AUDIT_LAST_USER_MSG://过滤auditctl转发的消息 case AUDIT_FIRST_USER_MSG2...AUDIT_LAST_USER_MSG2: if (!audit_enabled && msg_type != AUDIT_USER_AVC) return 0; err = audit_filter_user(&NETLINK_CB(skb), msg_type); if (err == 1) { //如果auditctl转发的消息满足user规则链表上的规则,则返回转发的消息 err = 0; ab = audit_log_start(NULL, GFP_KERNEL, msg_type); if (ab) { audit_log_format(ab, "user pid=%d uid=%u auid=%u", pid, uid, loginuid);//填充pid等 if (sid) { if (selinux_sid_to_string(sid, &ctx, &len)) { audit_log_format(ab, " ssid=%u", sid); //填充安全ID } else audit_log_format(ab, " subj=%s", ctx); //填充安全上下文 kfree(ctx); } audit_log_format(ab, " msg='%.1024s'", (char *)data); //data是auditctl送来的消息 audit_set_pid(ab, pid); audit_log_end(ab); } } break; case AUDIT_ADD: case AUDIT_DEL: if (nlmsg_len(nlh) < sizeof(struct audit_rule)) return -EINVAL; case AUDIT_LIST: //列出系统调用规则 err = audit_receive_filter(nlh->nlmsg_type, NETLINK_CB(skb).pid, uid, seq, data, nlmsg_len(nlh), loginuid, sid); break; case AUDIT_ADD_RULE://增加系统调用过滤规则 case AUDIT_DEL_RULE: if (nlmsg_len(nlh) < sizeof(struct audit_rule_data)) return -EINVAL; case AUDIT_LIST_RULES: //列出系统调用过滤规则 err = audit_receive_filter(nlh->nlmsg_type, NETLINK_CB(skb).pid, uid, seq, data, nlmsg_len(nlh), loginuid, sid); break; case AUDIT_SIGNAL_INFO: //得到发送者的信号信息 ...... break; default: err = -EINVAL; break; } return err < 0 ? err : 0; } |
线程函数kauditd_thread是一个独立线程的运行函数。它是内核后台线程,是一直在运行的工作线程。它从审计套接字缓冲区链表上取下缓冲区,通过netlink机制函数netlink_unicast将缓冲区发送给用户空间的审计后台。如果链表中没有数据,这个工作线程就进入睡眠等待状态。
函数kauditd_thread列出如下:
static int kauditd_thread(void *dummy) { struct sk_buff *skb; while (!kthread_should_stop()) { skb = skb_dequeue(&audit_skb_queue); //从审计套接字缓冲区链表上取下缓冲区skb wake_up(&audit_backlog_wait); //唤醒等待的进程 if (skb) { if (audit_pid) { //如果用户空间审计后台存在,发送消息 int err = netlink_unicast(audit_sock, skb, audit_pid, 0); if (err < 0) { BUG_ON(err != -ECONNREFUSED); /* Shoudn't happen */ printk(KERN_ERR "audit: *NO* daemon at audit_pid=%d\n", audit_pid); audit_pid = 0; } } else { printk(KERN_NOTICE "%s\n", skb->data + NLMSG_SPACE(0)); kfree_skb(skb);//释放skb } } else { //如果没有需要发送的套接字缓冲区,则将当前进程放入等待队列进行等待 DECLARE_WAITQUEUE(wait, current);//初始化等待队列成员wait set_current_state(TASK_INTERRUPTIBLE); //将当前进程设置为可中断等待状态 add_wait_queue(&kauditd_wait, &wait); //加入等待队列kauditd_wait if (!skb_queue_len(&audit_skb_queue)) { //如果队列长度为0,即没有成员, 则进入等待状态 try_to_freeze(); schedule();//调度 } __set_current_state(TASK_RUNNING);//将当前进程设置为正在运行状态 remove_wait_queue(&kauditd_wait, &wait); //从等待队列kauditd_wait删除成员wait } } return 0; } |