Skip to content

pipe_buf_operations hijack

Bir pipe_buffer'ın ops pointer'ı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.

References