分类: LINUX
2012-08-15 13:18:35
1.2.7 与用户空间审计系统的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;
}