
An exploit primitive in linux kernel inspired by DirtyPipe
An exploit primitive in linux kernel inspired by DirtyPipe (CVE-2022-0847).
A while ago, like many security veterans, I studied and reproduced the DirtyPipe (CVE-2022-0847) vulnerability, and deeply felt how useful this bug is. It starts from an uninitialized memory issue and ends with arbitrary file modification, without involving KASLR leaks or ROP, JOP operations along the way. Therefore, there is no need to bypass protections such as SMEP and SMAP.
After reproducing it, I began to think: why is DirtyPipe so useful? Will it, once fixed, be like a flash in the pan, with no learning value for future exploitation?
Suddenly, I realized that the structure where DirtyPipe resides — struct pipe_buffer — sounded very familiar. Ah! Isn't it that structure allocated with the GFP_KERNEL_ACCOUNT flag, located in slab-1k, containing an ops field that I often use for KASLR leaks and RIP hijacking?
Then I felt like an idiot. Why did I modify pipe_buffer's ops to do ROP back then? By directly modifying pipe_buffer's flags and combining it with splice, couldn't I directly achieve arbitrary file writes? That way there is no need to leak KASLR, no need to adapt the exploit for different kernel versions, no need to care about gadgets, and no need to bypass protections like SMEP, SMAP, KPTI.
I immediately pulled out the exploits I had previously reproduced and studied — CVE-2021-22555 and others involving the pipe_buffer structure. With slight modifications, they succeeded immediately on unpatched kernels of different versions. Here are a few examples:
Since I haven't seen anyone use this as a primitive in Linux kernel exploits before, I took the liberty of calling it "Pipe Primitive" XD.
To sum up, what is needed is the ability to modify the Pipe structure, for example a UAF in slab-1k, or an out-of-bounds write in slab-1k with an offset (i.e., not a direct sequential overflow).
In this way, on kernel >= 5.8, we only need to modify the flag |= PIPE_BUF_FLAG_CAN_MERGE of the splice page in pipe_buffer (if capable, you can also set offset and len to 0, so you can start writing from the beginning of the file); on kernel < 5.8, you first need to leak the anon_pipe_ops in pipe_buffer, then change the ops of the splice page to anon_pipe_ops (because in versions < 5.8, whether merging is possible depends on ops) (if capable, you can still set offset and len to 0).
As for your question about how to do container escape in this case? What I want to do should be similar to CVE-2019-5736 XD.