IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> linux审计子系统:内核层执行原理 -> 正文阅读

[系统运维]linux审计子系统:内核层执行原理

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消息
}
  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2022-10-31 12:39:35  更:2022-10-31 12:41:15 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 18:27:36-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码