Skip to content

EPOLL/eventpoll waiter kernel address leak

Bir fd'yi bir epoll instance'ına register etmek, kernel .text pointer'ı ep_poll_callback ile heap list pointer'larını tutan bir wait structure allocate eder; o object üzerinden bir infoleak KASLR'ı kırar.

Mechanism

Note

Bir process bir epoll instance'ına bir fd eklediğinde, kernel .func'ı kernel function'ı ep_poll_callback'e set edilmiş bir wait_queue_entry içeren bir struct eppoll_entry allocate eder. O tek qword'ü ayrı bir infoleak (OOB read, UAF read, uninitialized disclosure) ile leak etmek kernel .text base'ini açığa çıkarır; structure'ın list/whead/base pointer'ları kernel heap adreslerini açığa çıkarır.

İlgili upstream structure (fs/eventpoll.c):

struct eppoll_entry {
    struct eppoll_entry *next;      /* link to next in epitem->pwqlist */
    struct epitem       *base;      /* the owning epitem (heap ptr)    */
    wait_queue_entry_t   wait;      /* .func = ep_poll_callback (.text)*/
    wait_queue_head_t   *whead;     /* target file's wait queue head   */
};

ep_ptable_queue_proc() onu özel pwq_cache slab'ından allocate eder ve callback'i register eder:

pwq = kmem_cache_alloc(pwq_cache, GFP_KERNEL);
init_waitqueue_func_entry(&pwq->wait, ep_poll_callback);  /* writes .text ptr */
pwq->whead = whead;
pwq->base  = epi;
add_wait_queue(whead, &pwq->wait);   /* links wait.entry into heap list */

pwq_cache sabit bir size class olduğundan ve ep_poll_callback kernel base'inden bilinen bir symbol offset olduğundan, bu object'ler güvenilir bir spray + leak hedefidir.

Walkthrough

int epfd = epoll_create1(0);
struct epoll_event ev = { .events = EPOLLIN, .data.u64 = 0 };
/* target_fd is an fd whose .poll calls poll_wait (pipe, eventfd, socket, ...) */
epoll_ctl(epfd, EPOLL_CTL_ADD, target_fd, &ev);  /* allocates eppoll_entry,
                                                    writes ep_poll_callback */

pwq_cache'i doldurmak için (ya da vulnerable bir object'in yanındaki delikleri doldurmak için) bu tür kayıtlardan çokça spray et, sonra bir eppoll_entry üzerinden okumak için ayrı infoleak bug'ını tetikle:

  • wait.func'ı (== ep_poll_callback) okumak -> kernel .text base, KASLR'ı kırarak;
  • whead, base, ya da wait.entry.{next,prev} list pointer'larını okumak -> kernel heap adresleri.

Warning

Bu structure'ların önem taşıdığı gerçek-dünya bir vaka CVE-2019-2215'tir (Android /dev/binder UAF): free edilmiş bir binder_thread'in inline wait queue'su, register edilmiş eppoll_entry'yi dangling bir whead/wait.entry ile bırakır ve epoll teardown onu dereference eder. Pasif "eventpoll spray et, ep_poll_callback'i leak et" pattern'i doğrudan yukarıdaki fs/eventpoll.c mekanikleri üzerine kuruludur.

Mitigation

Hash'lenmiş %pK ve pointer sanitization, bir bug tarafından okunan ham bir object'i korumaz; dolayısıyla gerçek savunmalar upstream infoleak'i ortadan kaldırmak, KASLR/FG-KASLR ve bitişikliği daha az deterministik kılan slab freelist hardening'idir.

References