漏洞成因
每一个进程都可以使用keyctl(全名为KEYCTL_JOIN_SESSION_KEYRING)来为当前的会话创建相应的keyring,而且还可以为keyring指定名称,如果不需要指定名称的话,传入NULL参数即可。
通过引用相同的keyring名称,程序就可以在不同进程间共享keyring对象了。
如果某一进程已经拥有一个会话keyring了,那么这个系统调用便会为其创建一个新的keyring,并替换掉原有的keyring。
如果某一对象被多个进程所使用,那么该对象的内部引用计数(该信息存储在一个名为“usage”的数据域中)将会自动增加。
当进程尝试使用相同的keyring替换其当前的会话keyring时,泄漏就发生了。
源码分析
keyctl源码:https://elixir.bootlin.com/linux/v3.8/source/security/keys/keyctl.c#L1569
使用KEYCTL_JOIN_SESSION_KEYRING会走到keyctl_join_session_keyring函数:
该函数会在堆上分配一个页来存放name:
然后就是json_session_keyring函数(https://elixir.bootlin.com/linux/v3.8/source/security/keys/process_keys.c#L749),该函数中如果neme是null,就会直接安装一个空的keyring:
否则就会尝试根据name获取keyring:
如果没有找到,说明之前没有,那么就创建一个新的keyring:
否则直接安装这个已经找到的(新的也会走到这里,keyring已经被重新alloc了:
最后提交新的cred:
安装keyring
https://elixir.bootlin.com/linux/v3.8/source/security/keys/process_keys.c#L219
直接看函数:
可以看到,如果传进来的keyring不为NULL,那么就会先增加其usage,然后
借助这个宏进行赋值,把keyring赋值给cred->session_keyring(rcu赋值);之后释放掉原来的keyring;
cred的session_keyring成员就是一个key结构体:
key结构体的具体定义如下,其第一个成员就是usage:
https://elixir.bootlin.com/linux/v3.8/source/include/linux/key.h#L125
key_put函数如下,主要是要key->usage == 0时才能释放:
漏洞分析
根据博客的提示,如果传进去的keyring就是原来的keyring,那么会在下图1处增加usage,然后在代码2处的old得到这个keyring,后续的rcu赋值其实没什么作用,最后3处会减少usage,尝试进行释放:
环境搭建
commit:
69973b830859bc6529a7a0468ba0d80ee5117826 |
config:
make defconfig + config_setup.py:
CONFIG_USER_NS=y |
强制-g:
sudo make CFLAGS_KERNEL="-g" CFLAGS_MODULE="-g" -j4 |