linux审计子系统用于记录内核部分子模块及应用层(应用程序)的运行状态,如文件系统、进程、systemd应用的执行进展、遇到的问题等信息。审计子系统通过netlink与auditd应用建立kernel <-> audit直接的通信,并通过auditd把信息写入/var/log/audit/audit.log文件(默认地址,可以设置)。 linux审计子系统内核部分设计相对较为简单,甚至内核文档都没有找到关于它的单独描述(只找到了它的函数定义描述)。 linux审计子系统主要看audit_init和audit_fsnotify_init函数即可明白它的大致流程。audit_init函数主要用于分配审计缓存(audit_log_start中获取审计缓冲区的时候使用),初始化audit_queue(审计队列)、audit_retry_queue(重审队列)、audit_hold_queue(保留队列),注册audit_net_ops(网络命名空间子系统,在net结构对象init_net中记录、遍历),运行队列服务线程,它的核心点在kauditd_thread线程函数, kauditd_thread线程函数随着审计系统启动一直运行到审计系统退出,它在有信息时按顺序检查、发送队列信息(audit_hold_queue -> audit_retry_queue -> audit_queue),无信息时进入休眠状态,依次循环一直到退出。而audit_fsnotify_init函数主要用于状态的变更,如多播侦听器是否生效,是否可以多发消息,是否从冻结状态唤醒等等。
1. 函数分析
1.1 audit_init
1.2 audit_fsnotify_init
2. 源码结构
3. 部分结构定义
4. 扩展函数
1. 函数分析
1.1 audit_init
? audit_init 分配审计缓存(audit_log_start中获取审计缓冲区的时候使用),初始化audit_queue(审计队列)、audit_retry_queue(重审队列)、audit_hold_queue(保留队列),注册audit_net_ops(网络命名空间子系统,在net结构对象init_net中记录、遍历),运行队列服务线程 ? kauditd_thread线程函数随着审计系统启动一直运行到审计系统退出,它在有信息时按顺序检查、发送队列信息(audit_hold_queue -> audit_retry_queue -> audit_queue),无信息时进入休眠状态,依次循环一直到退出
static int __init audit_init(void)
{
int i;
if (audit_initialized == AUDIT_DISABLED) // 如果禁用审计模块,直接返回
return 0;
audit_buffer_cache = kmem_cache_create("audit_buffer",
sizeof(struct audit_buffer),
0, SLAB_PANIC, NULL); // 分配审计缓存
skb_queue_head_init(&audit_queue); // 初始化审计队列,通过kauditd_task发送(kauditd_thread)
skb_queue_head_init(&audit_retry_queue); // 重审队列,由于临时单播发送问题而将消息排队
skb_queue_head_init(&audit_hold_queue); // 保留队列,队列消息等待新的审核连接
for (i = 0; i < AUDIT_INODE_BUCKETS; i++)
INIT_LIST_HEAD(&audit_inode_hash[i]); // 基于索引节点的规则的散列(哈希节点)
register_pernet_subsys(&audit_net_ops);
// 注册网络命名空间子系统
// 注册一个子系统,该子系统具有
// 分别在创建和销毁网络命名空间时调用的init和exit函数
// 注册后,所有网络命名空间的初始化函数
// 都会为每个现有的网络命名空间调用
// 允许内核模块拥有一组网络命名空间的无竞争视图
// first_device <- ops
audit_net_ops
audit_initialized = AUDIT_INITIALIZED; // 已初始化
kauditd_task = kthread_run(kauditd_thread, NULL, "kauditd"); // 运行队列服务线程
// kauditd_thread 向用户空间发送审核记录的工作线程
kauditd_thread
audit_log(NULL, GFP_KERNEL, AUDIT_KERNEL,
"state=initialized audit_enabled=%u res=1",
audit_enabled); // 记录审计信息,初始化完成
return 0;
}
postcore_initcall(audit_init);
1.2 audit_fsnotify_init
? audit_fsnotify_init 分配审计fs事件通知组,事件可分为dev(设备)或inode(流)形式,这关系到多播侦听器是否可以发送数据
static int __init audit_fsnotify_init(void)
{
audit_fsnotify_group = fsnotify_alloc_group(&audit_mark_fsnotify_ops,
FSNOTIFY_GROUP_DUPS); // 分配审计fs事件通知组
// 独立的组,它只处理内部的事务
if (IS_ERR(audit_fsnotify_group)) {
audit_fsnotify_group = NULL;
audit_panic("cannot create audit fsnotify group");
}
return 0;
}
device_initcall(audit_fsnotify_init);
audit_mark_fsnotify_ops
2. 源码结构
? audit_net_ops 审计网络操作 初始化和清理工作
static struct pernet_operations audit_net_ops __net_initdata = {
.init = audit_net_init,
.exit = audit_net_exit,
.id = &audit_net_id,
.size = sizeof(struct audit_net),
};
audit_net_init audit_net_exit
? audit_mark_fsnotify_ops 审计fs事件通知操作
static const struct fsnotify_ops audit_mark_fsnotify_ops = {
.handle_inode_event = audit_mark_handle_event, // 一个组处理fs事件的主调用,用于只有inode标记而没有忽略掩码的组
.free_mark = audit_fsnotify_free_mark,
};
3. 部分结构定义
? net 网络结构
struct net {
/* First cache line can be often dirtied.
* Do not place here read-mostly fields.
*/
refcount_t passive; /* 决定何时释放网络命名空间 */
spinlock_t rules_mod_lock;
atomic_t dev_unreg_count;
unsigned int dev_base_seq; /* 受rtnl_mutex保护 */
int ifindex;
spinlock_t nsid_lock;
atomic_t fnhe_genid;
struct list_head list; /* 网络命名空间列表 */
struct list_head exit_list; /* 链接调用pernet退出方法(pernet_ops_rwsem read locked),
* 或者注销pernet操作(pernet_ops_rwsem write locked)
*/
struct llist_node cleanup_list; /* namespaces on death row */
#ifdef CONFIG_KEYS
struct key_tag *key_domain; /* 操作标签的关键域 */
#endif
struct user_namespace *user_ns; /* 拥有用户命名空间 */
struct ucounts *ucounts;
struct idr netns_ids;
struct ns_common ns;
struct ref_tracker_dir refcnt_tracker;
struct list_head dev_base_head;
struct proc_dir_entry *proc_net;
struct proc_dir_entry *proc_net_stat;
#ifdef CONFIG_SYSCTL
struct ctl_table_set sysctls;
#endif
struct sock *rtnl; /* rtnetlink套接字 */
struct sock *genl_sock;
struct uevent_sock *uevent_sock; /* uevent socket */
struct hlist_head *dev_name_head;
struct hlist_head *dev_index_head;
struct raw_notifier_head netdev_chain;
/* 请注意,@hash_mix每秒可以读取数百万次,关键是它位于read_mostly缓存行上 */
u32 hash_mix;
struct net_device *loopback_dev; /* The loopback */
/* 核心fib_rules */
struct list_head rules_ops;
struct netns_core core;
struct netns_mib mib;
struct netns_packet packet;
#if IS_ENABLED(CONFIG_UNIX)
struct netns_unix unx;
#endif
struct netns_nexthop nexthop;
struct netns_ipv4 ipv4;
#if IS_ENABLED(CONFIG_IPV6)
struct netns_ipv6 ipv6;
#endif
#if IS_ENABLED(CONFIG_IEEE802154_6LOWPAN)
struct netns_ieee802154_lowpan ieee802154_lowpan;
#endif
#if defined(CONFIG_IP_SCTP) || defined(CONFIG_IP_SCTP_MODULE)
struct netns_sctp sctp;
#endif
#ifdef CONFIG_NETFILTER
struct netns_nf nf;
#if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE)
struct netns_ct ct;
#endif
#if defined(CONFIG_NF_TABLES) || defined(CONFIG_NF_TABLES_MODULE)
struct netns_nftables nft;
#endif
#if IS_ENABLED(CONFIG_NF_FLOW_TABLE)
struct netns_ft ft;
#endif
#endif
#ifdef CONFIG_WEXT_CORE
struct sk_buff_head wext_nlevents;
#endif
/* 模块使用泛型网指针将一些私有的东西放到结构网上,而不需要显式的结构网修改 */
*
*规则很简单:
*1.设置pernet_operations->id。在register_pernet_device之后
*将具有您的私有指针的id。
*2.设置pernet_operations->size以分配和释放代码
*从结构网指向的私有结构。
*3.当网络处于活动状态时,不要更改此指针;
*4.不要尝试对net_generic对象进行任何私有引用。
*
*完成以上所有操作后,私有指针可以
*使用net_generic()调用访问。
*/
struct net_generic __rcu *gen;
/* 用于存储附加的BPF程序 */
struct netns_bpf bpf;
/* 注意:以下结构是缓存行对齐的 */
#ifdef CONFIG_XFRM
struct netns_xfrm xfrm;
#endif
u64 net_cookie; /* written once */
#if IS_ENABLED(CONFIG_IP_VS)
struct netns_ipvs *ipvs;
#endif
#if IS_ENABLED(CONFIG_MPLS)
struct netns_mpls mpls;
#endif
#if IS_ENABLED(CONFIG_CAN)
struct netns_can can;
#endif
#ifdef CONFIG_XDP_SOCKETS
struct netns_xdp xdp;
#endif
#if IS_ENABLED(CONFIG_MCTP)
struct netns_mctp mctp;
#endif
#if IS_ENABLED(CONFIG_CRYPTO_USER)
struct sock *crypto_nlsk;
#endif
struct sock *diag_nlsk;
#if IS_ENABLED(CONFIG_SMC)
struct netns_smc smc;
#endif
} __randomize_layout;
4. 扩展函数
? audit_net_init 创建netlink数据服务端(内核socket用用户态通信 kernel <-> user),默认设置数据发送时间为100毫秒
static int __net_init audit_net_init(struct net *net)
{
struct netlink_kernel_cfg cfg = {
.input = audit_receive,
.bind = audit_multicast_bind,
.unbind = audit_multicast_unbind,
.flags = NL_CFG_F_NONROOT_RECV,
.groups = AUDIT_NLGRP_MAX,
};
struct audit_net *aunet = net_generic(net, audit_net_id); // 私有审计网络命名空间索引ID(audit_net_id)
// 审核专用网络命名空间数据
// 使用泛型网指针将一些私有的东西放到结构网上
aunet->sk = netlink_kernel_create(net, NETLINK_AUDIT, &cfg); // 创建内核socket用用户态通信
if (aunet->sk == NULL) {
audit_panic("cannot initialize netlink socket in namespace");
return -ENOMEM;
}
/* limit the timeout in case auditd is blocked/stopped */
aunet->sk->sk_sndtimeo = HZ / 10; // 发送超时时间 100毫秒
return 0;
}
? 关闭netlink数据服务端,释放inode(数据流,参考<<socket:内核初始化及创建流(文件)详细过程>>)
static void __net_exit audit_net_exit(struct net *net)
{
struct audit_net *aunet = net_generic(net, audit_net_id);
netlink_kernel_release(aunet->sk); // 关闭套接字
// 如果套接字有释放回调,则从协议栈中释放,如果套接字绑定的是一个inode而不是一个文件,则释放该inode(数据流)
}
? kauditd_thread 向用户空间发送审核记录的工作线程
/**
* kauditd_thread - 向用户空间发送审核记录的工作线程
* @dummy: unused
*/
static int kauditd_thread(void *dummy)
{
int rc;
u32 portid = 0;
struct net *net = NULL;
struct sock *sk = NULL;
struct auditd_connection *ac;
#define UNICAST_RETRIES 5
set_freezable(); // 设置任务冻结,freeze为true时执行schedule(),并修改为TASK_RUNNING
// 任务冻结是一种机制,通过这种机制,在休眠或系统范围的挂起期间(在某些体系结构上)可以控制用户空间进程和某些内核线程。
while (!kthread_should_stop()) { // 线程状态不是KTHREAD_SHOULD_STOP
/* NOTE: see the lock comments in auditd_send_unicast_skb() */
rcu_read_lock();
ac = rcu_dereference(auditd_conn); // 内核/auditd连接状态
if (!ac) {
rcu_read_unlock(); // 释放rcu读锁
goto main_queue;
}
net = get_net(ac->net); // 网络结构
sk = audit_get_sk(net); // netlink结构(内核socket用用户态通信)
portid = ac->portid; // netlink portid
rcu_read_unlock(); // 释放rcu读锁
// 尝试刷新保留队列
rc = kauditd_send_queue(sk, portid,
&audit_hold_queue, UNICAST_RETRIES,
NULL, kauditd_rehold_skb); // kauditd_thread刷新skb队列的帮助程序
if (rc < 0) {
sk = NULL;
auditd_reset(ac); // 断开auditd/kauditd连接,并将所有排队记录移动到保留队列中,以防auditd重新连接
goto main_queue;
}
kauditd_rehold_skb
/* 尝试刷新重审队列 */
rc = kauditd_send_queue(sk, portid,
&audit_retry_queue, UNICAST_RETRIES,
NULL, kauditd_hold_skb); // audit_retry_queue
kauditd_hold_skb
main_queue:
/* 处理主队列-执行多播发送并尝试
*单播、转储失败的记录发送到重试队列;如果
*sk==NULL,由于以前的失败,我们将只执行
*多播发送并将记录移动到保留队列 */
rc = kauditd_send_queue(sk, portid, &audit_queue, 1,
kauditd_send_multicast_skb, // 向任何多播侦听器发送一条记录
(sk ?
kauditd_retry_skb : kauditd_hold_skb)); // audit_queue
kauditd_send_multicast_skb
/* 删除我们的netns引用,没有审计发送超过这一行 */
if (net) {
put_net(net);
net = NULL;
}
/* 我们已经处理了所有的队列,所以唤醒大家 */
wake_up(&audit_backlog_wait);
// audit_backlog_wait 为在审计积压中被阻塞的调用者设置Waitqueue
/* 注意:我们希望在队列中有任何东西时唤醒,
* 无论审计是否被连接,因为我们需要
* 做多播发送和旋转记录从
* 主队列到重试/保持队列 */
wait_event_freezable(kauditd_wait,
(skb_queue_len(&audit_queue) ? 1 : 0)); // 为0睡眠
}
return 0;
}
? kauditd_rehold_skb 处理保留队列中的审核记录发送失败
/**
*kauditd_rehold_skb-处理保留队列中的审核记录发送失败
*@skb:审核记录
*@error:error代码(未使用)
*
*描述:
*只有当kauditd_thread无法刷新保持队列时,它才应该使用该方法。
*/
tatic void kauditd_rehold_skb(struct sk_buff *skb, __always_unused int error)
{
/* 将记录放回队列 */
skb_queue_tail(&audit_hold_queue, skb);
}
? kauditd_hold_skb 将审核记录排队,等待审核
kauditd_hold_skb-将审核记录排队,等待审核
*@skb:审核记录
*@error:错误代码
*
*描述:
*将审核记录排队,等待auditd的实例。当这个
*函数被调用,我们还没有放弃发送记录,但是
*看起来不太好。我们要做的第一件事是尝试编写
*通过printk记录,然后看看我们是否想尝试保存记录
*如果我们有空的话,就排队。如果我们想保持记录,但我们
*没有空间,录制一条记录丢失的消息。
*/
static void kauditd_hold_skb(struct sk_buff *skb, int error)
{
kauditd_printk_skb(skb); // 将审核记录打印到环形缓冲区
if (!audit_default) // 内核在没有任何参数的情况下启动时的默认状态(AUDIT_OFF 0)
goto drop;
/*
* 保持队列仅用于守护进程完全退出时,而不是-EAGAIN失败;
* 如果我们处于-EAGAIN状态,则在重试队列上重新排队记录,
* 除非它已满,在这种情况下,将其删除
*/
if (error == -EAGAIN) {
if (!audit_backlog_limit || // 允许的未完成audit_buffer数,当设置为零时,这意味着无限制
skb_queue_len(&audit_retry_queue) < audit_backlog_limit) { // 或者队列长度未达到设置值
skb_queue_tail(&audit_retry_queue, skb); // 将记录放回队列
return;
}
audit_log_lost("kauditd retry queue overflow"); // 有条件地记录丢失的审计消息事件
// print = (audit_failure == AUDIT_FAIL_PANIC || !audit_rate_limit); 记录条件 ->恐慌或者队列长度设置为0
goto drop;
}
/* 如果保留队列中有空间,请将消息排队 */
if (!audit_backlog_limit ||
skb_queue_len(&audit_hold_queue) < audit_backlog_limit) {
skb_queue_tail(&audit_hold_queue, skb);
return;
}
/* 我们没有其他选择了,放弃这条消息 */
audit_log_lost("kauditd hold queue overflow");
drop:
kfree_skb(skb);
}
? kauditd_send_multicast_skb 向任何多播侦听器发送一条记录
kauditd_send_multicast_skb——向任何多播侦听器发送一条记录
/* @skb:审计记录
*
*描述:
*写一个多播消息给在初始网络命名空间中监听的任何人。这个函数不像预期的那样消耗skb,因为它无论如何都要复制它。
*/
static void kauditd_send_multicast_skb(struct sk_buff *skb)
{
struct sk_buff *copy;
struct sock *sock = audit_get_sk(&init_net); // 通过审计索引ID(audit_net_id)拿出netlink对象
struct nlmsghdr *nlh;
/* 注意:我们没有对init_net进行额外的引用,因为我们不必担心它会消失 */
if (!netlink_has_listeners(sock, AUDIT_NLGRP_READLOG)) // "best effort"只读套接字
// nl_table[sk->sk_protocol].listeners中获取监听对象
return;
copy = skb_copy(skb, GFP_KERNEL); // 创建sk_buff的私有拷贝 -> memcpy
nlh = nlmsg_hdr(copy); // (struct nlmsghdr *)skb->data
nlh->nlmsg_len = skb->len;
nlmsg_multicast(sock, copy, 0, AUDIT_NLGRP_READLOG, GFP_KERNEL); // 多发netlink消息
}
|