CVE-2016-0728 [HALF]


漏洞成因

每一个进程都可以使用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
CONFIG_SECURITY_SELINUX_DISABLE=y
# for debug
CONFIG_KALLSYMS=y
CONFIG_KALLSYMS_ALL=y
CONFIG_DEBUG_INFO_DWARF4=y
# for msg_msg copy
CONFIG_CHECKPOINT_RESTORE=y
# for syzkaller image
CONFIG_CONFIGFS_FS=y
CONFIG_SECURITYFS=y

CONFIG_SLAB_FREELIST_RANDOM=n
CONFIG_SLAB_FREELIST_HARDENED=n
CONFIG_SHUFFLE_PAGE_ALLOCATOR=n
CONFIG_HARDENED_USERCOPY=n
CONFIG_FORTIFY_SOURCE=n
CONFIG_STATIC_USERMODEHELPER=n
CONFIG_DEBUG_INFO_NONE=n
CONFIG_RANDOMIZE_BASE=n

强制-g:

sudo make  CFLAGS_KERNEL="-g" CFLAGS_MODULE="-g" -j4

参考

https://www.anquanke.com/post/id/83342


文章作者: q1ming
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 q1ming !
  目录