Page-level Overflow in esp6 Linux Kernel Module

We discussed this vulnerability as part of our weekly podcast on 30 March 2022

The vulnerability here is a fairly straightforward overflow in the esp6 crypto module. When receiving messages, an 8-page buffer is allocated for the incoming data, but it’s possible for messages to be sent that exceed 8 pages in size. When it comes to exploitation though, it gets more complex, as this overflow is at the page-level and not the cache-level.

Background Without going into too much detail, kmalloc caches request pages from the page allocator when needed. Depending on the object size, different “order” pages are requested. Order-0 pages consist of 1 page, order-1 of 2 pages, order-2 of 4 pages, and order-3 of 8 pages. Where the overflow is in an 8-page buffer, corruption will happen in order-3 pages. Only kmalloc-2k, kmalloc-4k, and kmalloc-8k allocate from order-3. After some heap shaping and mitigating the noise of the page allocator, the author was able to get corruption of carefully setup objects in the kmalloc-4k cache.

Another important bit of background for understanding the primitives is on the msg_msg object. If a message exceeds 4048 bytes in size, the next pointer will be allocated and used to store excess data. For example, if you pass a 4056-byte message, a msg_msg object will be allocated for the first 4048 bytes, then the last 8 bytes will be allocated in kmalloc-32 and stored in next.

Exploitation Two objects were primarily used for building primitives, user_key_payload (from the add_key() syscall) and msg_msg from sendmsg/recvmsg. By setting up a user_key_payload adjacent to the overflow pages, it was possible to corrupt it’s datalen to get an out of bounds read to leak msg_msg objects on an adjacent page. These msg_msg objects would contain a next pointer into kmalloc-32. By then exploiting the bug again to corrupt a msg_msg object directly and smash it’s m_ts (message text size) and next pointer to one leaked in the first step, OOB read in kmalloc-32 can be achieved. This is useful because seq_operations objects end up in this cache (which contain .text pointers), and leaking these objects can be used to defeat kASLR.

Finally, the bug can be exploited once more to obtain arbitrary write, again using msg_msg. Recall that messages > 4048 bytes will use next for extra storage, and load_msg() will copy_from_user() data to it. By freezing the first copy using the FUSE trick, corrupting the next pointer with a pointer to modprobe_path, and resuming the copy, modprobe_pathcan be smashed with arbitrary data to get root.