LSM HOOK 学习及踩坑(recipe for target ‘__modpost‘ failed)

LSM HOOK 学习及踩坑

本文中的内容基于内核版本:4.4.232

注意,据说低版本(2.x)的LSM只能有一个hook,本文不适用于此类版本

LSM HOOK和其他hook的不同

1.正常的hook一般流程是,(1)保存原始的函数指针 (2)用hook函数替换原始函数 (3)在hook函数执行后将指针指向原函数指针 (4) 执行原函数

2.LSM HOOK采用的方式不一样,它维护了一个hook函数的链表,每次有新的hook函数添加时,就在链表的尾部插入该函数,并不影响原链表的执行,也没有修改原函数的函数指针

从结构体开始

这里先把每个结构体讲清楚,后边好串起来用,要觉得麻烦,直接跳到后面的流程

1.struct security_hook_heads

从文件include/lsm_hooks.h中可以找到该结构体

	struct security_hook_heads {
	struct list_head binder_set_context_mgr;
	struct list_head binder_transaction;
	struct list_head binder_transfer_binder;
	struct list_head binder_transfer_file;
	struct list_head ptrace_access_check;
	struct list_head ptrace_traceme;
	struct list_head capget;
	struct list_head capset;
	struct list_head capable;
	struct list_head quotactl;
	struct list_head quota_on;
	struct list_head syslog;
	struct list_head settime;
	struct list_head vm_enough_memory;
	struct list_head bprm_set_creds;
	struct list_head bprm_check_security;
	struct list_head bprm_secureexec;
	struct list_head bprm_committing_creds;
	struct list_head bprm_committed_creds;
	struct list_head sb_alloc_security;
	struct list_head sb_free_security;
	struct list_head sb_copy_data;
	struct list_head sb_remount;
	struct list_head sb_kern_mount;
	struct list_head sb_show_options;
	struct list_head sb_statfs;
	struct list_head sb_mount;
	struct list_head sb_umount;
	struct list_head sb_pivotroot;
	struct list_head sb_set_mnt_opts;
	struct list_head sb_clone_mnt_opts;
	struct list_head sb_parse_opts_str;
	struct list_head dentry_init_security;
	....
	}

可以看到,该结构体由许许多多个list_head 结构体组成,再来看看list_head这个结构体

struct list_head {
    struct list_head *next, *prev;
};

就是两个list_head指针组成结构体,可以理解为双链表的一个基础单元

struct security_hook_heads 在security/security.c中实例化,并通过宏LIST_HEAD_INIT进行初始化

// 实例并初始化security_hook_heads
struct security_hook_heads security_hook_heads = {
	.binder_set_context_mgr =
		LIST_HEAD_INIT(security_hook_heads.binder_set_context_mgr),
	.binder_transaction =
		LIST_HEAD_INIT(security_hook_heads.binder_transaction),
	.binder_transfer_binder =
		LIST_HEAD_INIT(security_hook_heads.binder_transfer_binder),
	.binder_transfer_file =
		LIST_HEAD_INIT(security_hook_heads.binder_transfer_file),

	.ptrace_access_check =
		LIST_HEAD_INIT(security_hook_heads.ptrace_access_check),
	......
	};
LIST_HEAD_INIT(security_hook_heads.abc)
{
.abc = {&security_hook_heads.abc = &security_hook_heads.abc}
}

这里先记住 security_hook_heads 这个结构体,稍后用得着

2.struct security_hook_list

还是在include/lsm_hooks.h中能找到定义

struct security_hook_list {
	struct list_head		list;
	struct list_head		*head;
	union security_list_options	hook;
};

可以看到,该结构体有一个list_head 一个list_head 指针和一个联合体union security_list_options组成,先不管这个结构体具体的作用,再来看一下这个联合体union security_list_options

3.union security_list_options

同样是在include/lsm_hooks.h中

union security_list_options {
	int (*binder_set_context_mgr)(struct task_struct *mgr);
	int (*binder_transaction)(struct task_struct *from,
					struct task_struct *to);
	int (*binder_transfer_binder)(struct task_struct *from,
					struct task_struct *to);
	int (*binder_transfer_file)(struct task_struct *from,
					struct task_struct *to,
					struct file *file);
	int (*ptrace_access_check)(struct task_struct *child,
					unsigned int mode);
	int (*ptrace_traceme)(struct task_struct *parent);
	int (*capget)(struct task_struct *target, kernel_cap_t *effective,
			kernel_cap_t *inheritable, kernel_cap_t *permitted);
	int (*capset)(struct cred *new, const struct cred *old,
			const kernel_cap_t *effective,
			const kernel_cap_t *inheritable,
			const kernel_cap_t *permitted);
	int (*capable)(const struct cred *cred, struct user_namespace *ns,
			int cap, int audit);
	int (*quotactl)(int cmds, int type, int id, struct super_block *sb);
	int (*quota_on)(struct dentry *dentry);
	int (*syslog)(int type);
	int (*settime)(const struct timespec *ts, const struct timezone *tz);
	int (*vm_enough_memory)(struct mm_struct *mm, long pages);
	....}

可以理解为该联合体中定义了许许多多的不同的可hook函数,每一个可选的lsm hook都必须和该联合体中的某一个函数定义一致

不知道取啥标题

LSM_HOOK_INIT

该宏用于初始化一个security_hook_list

#define LSM_HOOK_INIT(HEAD, HOOK) \
	{ .head = &security_hook_heads.HEAD, .hook = { .HEAD = HOOK } }

该宏进行解析以后就是

举个例子:
LSM_HOOK_INIT(abc,def)
{
.head = &security_hook_heads.HEAD.abc,
.hook = {
		.abc = def
	}
}
// 还记得前面的security_hook_heads吗?这里的.head 就会取到一个heads实例的abc成员的地址,该成员是一个list_head 结构
// 然后.hook 是一个union结构,其中也包含了一个.abc的成员,这里就是将该union转换成.abc成员,然后赋值为def
// 这点,我看了好几次才明白,害,菜是原罪

维护一个要hook的所有函数的数组

struct security_hook_list hooks[] ={
	LSM_HOOK_INIT(abc,def),
	LSM_HOOK_INIT(cba,fed),
	......
};
// 其中abc和cba为系统提供的security_hook_heads中的某个成员名
// def,fed为用户自定义的与之相匹配的hook函数函数名

函数 security_add_hooks

至此,hooks已经初始化完毕,就需要将hooks添加到相应的security_hook_heads实例对应的成员链表中

static inline void security_add_hooks(struct security_hook_list *hooks,
				      int count)
{
	int i;

	for (i = 0; i < count; i++)
		list_add_tail_rcu(&hooks[i].list, hooks[i].head);
}
// hooks[i]对应的即为一个security_hook_list结构
// 利用list_add_tail_rcu,将security_hook_list插入到security_hook_heads实例对应成员链表中,该成员由security_hook_list.head成员指定,在security_hook_list初始化时已经初始化

security_add_hooks以后,lsm hook已经注册到security_hook_heads里面,那么什么时候开始调用呢?

call_int_hookcall_void_hook

当触发到相应的事件以后,相应事件的security_xxx函数就会根据xxx的定义调用相应的上述两个函数

//  security/security.c
/*
 * Hook list operation macros.
 *
 * call_void_hook:
 *	This is a hook that does not return a value.
 *
 * call_int_hook:
 *	This is a hook that returns a value.
 */

#define call_void_hook(FUNC, ...)				\
	do {							\
		struct security_hook_list *P;			\
								\
		list_for_each_entry(P, &security_hook_heads.FUNC, list)	\
			P->hook.FUNC(__VA_ARGS__);		\
	} while (0)

#define call_int_hook(FUNC, IRC, ...) ({			\
	int RC = IRC;						\
	
	do {							\
		struct security_hook_list *P;			\
								\
		list_for_each_entry(P, &security_hook_heads.FUNC, list) { \
			RC = P->hook.FUNC(__VA_ARGS__);		\
			if (RC != 0)				\
				break;				\
		}						\
	} while (0);						\
	RC;							\
})

//比如触发函数abc的条件
int security_abc(__VA_ARGS__)
{
    return call_int_hook(abc,0,__VA_ARGS__);
    // 或者call_void_hook
}


// 因为abc要作为参数,不能直接未定义,所以内核会定义一个bool型的函数来占位
#ifdef CONFIG_XXX
extern bool abc(__AV_ARGS);
....
#else
static inline bool abc(__VA_ARGS)
{
	return true;
};
....
#endif

可以看到,该宏会遍历security_hook_heads对应的成员链表,然后执行hook函数

从start_kernel开始

在 init/main.c中有一个函数start_kernel

 //   init/main.c
asmlinkage __visible void __init start_kernel(void){
.....
	thread_info_cache_init();
	cred_init();
	fork_init();
	proc_caches_init();
	buffer_init();
	key_init();
	security_init(); // 注意到这个函数
	dbg_late_init();
	vfs_caches_init();
	signals_init();
......
}
//  security\security.c
int __init security_init(void)
{
	pr_info("Security Framework initialized\n");

	/*
	 * Load minor LSMs, with the capability module always first.
	 */
	capability_add_hooks();
	yama_add_hooks();

	/*
	 * Load all the remaining security modules.
	 */
	do_security_initcalls();

	return 0;
}
// security_init 初始化了两个hooks数组,取其中一个看看
void __init capability_add_hooks(void)
{
	security_add_hooks(capability_hooks, ARRAY_SIZE(capability_hooks));
}
// 可以看到套路和前面讲的差不多,最终就是初始化一个hooks数组,并注册到到security_hook_heads实例相应的链表

实例???没有,俺写不出来

/** 此代码运行不起来,只做示范用,因为security_add_hooks 和LSM_HOOK_INIT中的security_hook_heads并不导出,make时会报如下错

ERROR: "security_hook_heads" [/root/zyc/test/lsm/test.ko] undefined!
ERROR: "security_add_hooks" [/root/zyc/test/lsm/test.ko] undefined!

解决办法是通过kallsyms_lookup_name("xxxx")将xxx符号导出,至于是否需要重新再定义一次LSM_HOOK_INIT,有待测试

此代码编译测试环境为:Linux ubuntu 5.3.0-51-generic  其security_add_hooks函数和hook用的my_rename函数原型都与本文前面贴的代码有细微差异,建议按照自身系统对应的源代码文件去调用
*/
#include <linux/security.h>
#include <linux/module.h>
#include <linux/lsm_hooks.h>

//struct security_hook_heads sec_hd;


static int my_rename(const struct path *old_dir, struct dentry *old_dentry,
                                const struct path *new_dir,
                                struct dentry *new_dentry)
{
        printk("you are rename some file\n");
        return 0;
}

struct security_hook_list hooks[] =
{
        LSM_HOOK_INIT(path_rename,my_rename),
};

static int lsm_init(void)
{
        security_add_hooks(hooks,1,"yclsm");
        printk("start");
        return 0;
}

static void lsm_exit(void)
{
// lsm 可以调用security_delete_hooks进行hook卸载,这里就不写了
        printk("remove\n");
}

MODULE_LICENSE("GPL");
module_init(lsm_init);
module_exit(lsm_exit);

参考资料: https://blog.51cto.com/2559640/2365794 这个链接从内核源码取一个实例进行讲解,非常不错。

  • 3
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
LSM (Linux Security Modules) 是 Linux 内核提供的一种机制,允许在内核中实现各种安全策略,包括访问控制、强制访问控制、安全审计等。可以通过 LSM 来实现文件防止修改的功能。 在 LSM 中,有一个叫做 Security Module 的概念。Security Module 可以是一个内核模块,也可以是内核代码的一部分。每个 Security Module 都会实现一些回调函数,在特定的事件发生时被调用。这些回调函数可以覆盖默认的实现,从而实现定制化的安全策略。 要实现文件防止修改的功能,可以通过 hook inode_setattr 和 file_permission 这两个回调函数来实现。 1. hook inode_setattr inode_setattr 回调函数在文件属性被修改时被调用。在这个回调函数中,可以检查是否允许文件修改。如果不允许,则可以返回 -EPERM 错误,拒绝修改。 以下是一个简单的示例代码: ```c #include <linux/lsm_hooks.h> static int my_inode_setattr(struct dentry *dentry, struct iattr *attr) { if (/* 判断是否允许修改 */) { return -EPERM; } return security_inode_setattr(dentry, attr); } static struct security_hook_list my_hooks[] __lsm_ro_after_init = { LSM_HOOK_INIT(inode_setattr, my_inode_setattr), }; ``` 在这个示例中,我们定义了一个 my_inode_setattr 函数。在这个函数中,我们可以检查是否允许文件修改,并返回相应的错误码。 同时,我们也调用了 security_inode_setattr 函数,这个函数会调用默认的 inode_setattr 实现。这样做是为了确保其他的安全模块也能正常工作。 最后,我们把这个函数注册到 LSMhook 中。 2. hook file_permission file_permission 回调函数在文件被访问时被调用。在这个回调函数中,可以检查是否允许访问。如果不允许,则可以返回 -EACCES 错误,拒绝访问。 以下是一个简单的示例代码: ```c #include <linux/lsm_hooks.h> static int my_file_permission(struct file *file, int mask) { if (/* 判断是否允许访问 */) { return -EACCES; } return security_file_permission(file, mask); } static struct security_hook_list my_hooks[] __lsm_ro_after_init = { LSM_HOOK_INIT(file_permission, my_file_permission), }; ``` 在这个示例中,我们定义了一个 my_file_permission 函数。在这个函数中,我们可以检查是否允许文件访问,并返回相应的错误码。 同时,我们也调用了 security_file_permission 函数,这个函数会调用默认的 file_permission 实现。这样做是为了确保其他的安全模块也能正常工作。 最后,我们把这个函数注册到 LSMhook 中。 需要注意的是,以上示例代码并不完整,需要根据具体场景进行修改。同时,还需要编写其他的回调函数,如 file_open、inode_permission 等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值