Pipe buffer grooming¶
Linux pipe'ları ring'lerini boyutu
fcntl(F_SETPIPE_SZ)üzerinden attacker tarafından seçilebilen,kmalloc'lanmış birstruct pipe_bufferarray'iyle destekler ve bu, pipe'ları kernel heap feng shui için kesin, spray'lenebilir, reclaim edilebilir bir object kılar.
Mechanism¶
Bir pipe'ın data ring'i, kmalloc() ile allocate edilen bir struct pipe_buffer array'idir (alloc_pipe_info() / pipe_resize_ring() içinde). Her eleman küçük ve iyi yapılandırılmıştır:
struct pipe_buffer {
struct page *page; // physical page backing this slot
unsigned int offset, len; // window into the page
const struct pipe_buf_operations *ops; // function-pointer table
unsigned int flags;
unsigned long private;
};
Note
Grooming gücü üç özellikten gelir. (1) Size kontrolü: pipe_buffer slot sayısı ve dolayısıyla arkadaki kmalloc allocation'ının boyutu, attacker tarafından fcntl(fd, F_SETPIPE_SZ, n) ile seçilir (round_pipe_size() tarafından power-of-two bir page sayısına yuvarlanır). Array'in hangi kmalloc-N cache'ine düşeceğini sen seçersin. (2) Toplu instantiation: birçok pipe açmak birçok aynı-boyutlu allocation spray'ler ve bir alt kümeyi seçerek close()'lamak öngörülebilir delikler açar — freed bir victim slot'unu kontrol edilebilir allocation'ların yanına yerleştirmek ya da aynı cache size'ından freed bir object'i reclaim etmek için klasik feng shui. (3) Yararlı içerik: ops ve page field'ları sulu corruption hedefleridir — ops'u overwrite etmek function pointer'ları yönlendirir (pipe_buf_operations hijack); kontrol edilen bir page pointer'ı sıradan read()/write() üzerinden arbitrary read/write verir.
Yani pipe'lar çifte görev yapar: bir grooming object'i (istediğin gibi spray'leyip/free edebileceğin öngörülebilir kmalloc allocation'ları) ve bir target object'i (field'ları bir heap overflow/UAF'i R/W'ye çeviren bir object).
Walkthrough¶
Temsili bir grooming dizisi (Alexander Popov'un pipe_buffer deneylerindeki gibi):
#define N 1024
int p[N][2];
for (int i = 0; i < N; i++) {
pipe(p[i]);
// shrink right after creation so we don't hit the
// /proc/sys/fs/pipe-user-pages-soft limit (default 16384 pages)
fcntl(p[i][1], F_SETPIPE_SZ, 2 * PAGE_SIZE); // -> small kmalloc cache
// write a unique tag so we can recognize this pipe's buffer later
write(p[i][1], &i, sizeof(i));
}
Sonra bir victim'in yanında delikler aç ve vulnerable allocation'ın freed bir slot'u yeniden kullanmasına izin ver:
// free every other pipe's buffer array to create gaps in the slab
for (int i = 0; i < N; i += 2) close(p[i][0]), close(p[i][1]);
// trigger the bug here: the vulnerable kmalloc lands in one of the holes,
// overlapping a surviving pipe_buffer array.
F_SETPIPE_SZ cache seçimi için neden önemli
Kritik nokta: pipe kapasitesi byte olarak değil, slot olarak sayılır — her
pipe_buffer slot'u tam olarak bir page'i backler, yani F_SETPIPE_SZ ile istenen
N-page'lik bir pipe = N adet slot demektir. Array'in kmalloc boyutu N * sizeof(struct
pipe_buffer) (64-bit'te slot başına ~40 byte) olur, pipe'ın veri kapasitesi (N page) değil.
Böylece resize, array'in hangi slab'ı kullandığını değiştirir; örn. 2-page'lik bir pipe =
2 slot ≈ 80 byte, düşük bir kmalloc cache'ine (kernel'e göre kmalloc-96) düşerken,
varsayılan 64 KiB / 16-page pipe = 16 slot ≈ 640 byte, kmalloc-1k'ya düşer. Victim
object'inin cache'ini tutturmak oyunun tamamıdır.
Warning
Per-user pipe page accounting (pipe-user-pages-soft/-hard) ne kadar spray'leyebileceğini sınırlar; her pipe'ı pipe()'tan hemen sonra küçültmek için F_SETPIPE_SZ çağırmak seni limitin altında tutar, böylece büyük spray'ler mümkün kalır.
Detection¶
- Tek bir process'ten gelen olağandışı büyük
pipe2()+fcntl(F_SETPIPE_SZ)patlamaları anomalidir. - KASAN/
CONFIG_SLAB_FREELIST_HARDENED, grooming'in kurduğu corruption'ların çoğunu yakalar.
Mitigation¶
CONFIG_SLAB_FREELIST_RANDOMveCONFIG_SLAB_FREELIST_HARDENEDadjacency öngörülebilirliğini azaltır.- Dedike/segregated cache'ler (örn.
kmalloc-cg, hardened usercopy) grooming'in dayandığı cross-object reuse'u sınırlar.