pipe_buf_operations hijack¶
Bir
pipe_buffer'ınopspointer'ını (const struct pipe_buf_operations *) overwrite etmek kernel'i forged bir operations table'a yönlendirir; pipe buffer confirm, steal ya da release edildiğinde kernel attacker'ın kontrol ettiği bir function pointer'ı çağırır — bir pipe corruption'ından çıkan bir control-flow hijack.
Mechanism¶
Note
Her struct pipe_buffer kendi operations vtable'ına bir pointer taşır:
struct pipe_buffer {
struct page *page; /* the backing page */
unsigned int offset, len;
const struct pipe_buf_operations *ops; /* <-- hijack target */
unsigned int flags;
unsigned long private;
};
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 *);
};
Bir anonymous pipe için ops normalde read-only kernel global'i
anon_pipe_buf_ops'u gösterir. Attack'in kırdığı invariant: kernel ops'a güvenir
ve hâlâ kernel rodata'sını gösterip göstermediğini yeniden doğrulamadan onun üzerinden
çağrı yapar. Bir bug ops'u, fake bir pipe_buf_operations tutan attacker-controlled
memory'yi gösterecek şekilde overwrite ederse, o buffer üzerindeki bir sonraki operation
forged bir function pointer'ı dereference eder. ->release(pipe, buf) en çok kullanılan
trigger'dır çünkü tüketilen bir buffer söküldüğünde — örn. pipe drain ya da close
edildiğinde — tetiklenir, dolayısıyla çağrının ne zaman olacağını attacker kontrol eder.
Bu, pipe_buffer array'ine bir write gerektirir (bir kmalloc cache'inden allocate edilir —
örn. 16 buffer'lı varsayılan 64 KB pipe için kmalloc-1k, ya da pipe resize edilip array
küçülürse kmalloc-192). slab OOB, UAF ya da attacker verisini bir pipe_buffer üzerine
düşüren bir page-uaf/page-spray reclaim üzerinden ulaşılabilir.
data-only kuzene dikkat: ops yerine page/offset/len'i overwrite etmek bir code
pointer'a dokunmadan arbitrary read/write verir — bkz.
arbitrary-read-write-via-pipe-buffer ve
pipe-buffer-hijack.
Walkthrough¶
/* 1. Open a pipe and write so at least one pipe_buffer has ops = &anon_pipe_buf_ops. */
int p[2]; pipe(p);
write(p[1], "A", 1);
/* 2. Via the memory-corruption primitive, overwrite the target pipe_buffer:
* buf->ops = <addr of a fake pipe_buf_operations under attacker control>;
* The fake table's ->release (offset 8) holds the hijack target (e.g. a
* stack-pivot / ROP entry, or with CFI a permitted call target). */
corrupt_pipe_buffer_ops(target, fake_ops);
/* 3. Trigger the call: draining/closing the pipe invokes ops->release(pipe, buf). */
close(p[0]); /* consumes buffers -> pipe_buf_release -> ops->release() */
close(p[1]);
Kernel içinde, dispatch helper'ları doğrudan ops üzerinden çağrı yapar:
/* fs/pipe.c (paraphrased) */
static inline void pipe_buf_release(struct pipe_inode_info *pipe,
struct pipe_buffer *buf)
{
const struct pipe_buf_operations *ops = buf->ops;
buf->ops = NULL;
ops->release(pipe, buf); /* forged pointer is called here */
}
Neden ->release tercih edilen slot
confirm read'de erken çalışır ve buffer zaten tüketilmişse tetiklenmeyebilir;
try_steal/get splice/tee path'lerine bağlıdır. release, pipe teardown'da
populate edilmiş herhangi bir buffer için çalışacağı garantilidir ve teardown
close() üzerinden attacker tarafından başlatılır, bu da tamamen attacker'ın seçtiği
bir pointer'ın güvenilir, iyi zamanlanmış bir çağrısını verir.
Warning
Fine-grained Control-Flow Integrity (kCFI/CFI) olan bir kernel'de, ops->release
üzerinden bir indirect call type-check'lenir, dolayısıyla ham bir ROP entry fault
verir. Bu, gerçek dünya pipe exploitation'ını ops hijack yerine data-only
pipe_buffer->page rewrite'ına (arbitrary R/W, code pointer yok) iter. Dirty Pipe
bug'ı ilişkili-ama-ayrı bir pipe_buffer.flags sorunudur, bir ops overwrite değil —
bkz. dirty-pipe-page-cache-write.
Mitigation¶
CONFIG_CFI_CLANG (kCFI) indirect ops->release call'ını type-check'ler ve arbitrary
hedefleri engeller. page'in gösterdiği veriyi ve pipe_buffer array'ini hardened/separated
cache'lerden allocate etmek ve struct page-ownership kontrolleri, corruption surface'ini
azaltır. rodata'daki read-only anon_pipe_buf_ops, meşru table'ın yamalanamayacağı anlamına
gelir — yalnızca ona giden pointer yamalanabilir — dolayısıyla pipe_buffer allocation'ını
korumak asıl savunmadır.