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_hook
和 call_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 这个链接从内核源码取一个实例进行讲解,非常不错。