Since the size of the allocation for a System V message can be controlled, it is possible to allocate it in both kmalloc-cg-64 and kmalloc-cg-96 slab caches.
It is important to note that any data to be leaked must be written past the first 48 bytes of the message allocation, otherwise it would overwrite the msg_msg
header. This restriction discards the nft_lookup
object as a candidate to apply this technique to as it is only possible to write the pointer either at offset 24 or offset 32 within the object. The ability of overwriting the msg_msg.m_ts
member, which defines the size of the message, helps building a strong out-of-bounds read primitive if the value is large enough. However, there is a check in the code to ensure that the m_ts
member is not negative when interpreted as a signed long integer and heap addresses start with 0xffff
, making it a negative long integer.
Leaking an nft_set
Pointer
Leaking a pointer to an nft_set
object is quite simple with the memory leak primitive described above. The steps to achieve it are the following:
1. Create a target set where the expressions will be bound to.
2. Create a rule with a lookup expression bound to the target set from step 1.
3. Create a set with an embedded nft_dynset
expression bound to the target set. Since this is considered an invalid expression to be embedded to a set, the nft_dynset
object will be freed but not removed from the target set bindings list, causing a UAF.
4. Spray System V messages in the kmalloc-cg-96 slab cache in order to replace the freed nft_dynset
object (via msgsnd()
function). Tag all the messages at offset 24 so the one corrupted with the nft_set
pointer can later be identified.
5. Remove the rule created, which will remove the entry of the nft_lookup
expression from the target set’s bindings list. Removing this from the list effectively writes a pointer to the target nft_set
object where the original binding.list.prev
member was (offset 72). Since the freed nft_dynset
object was replaced by a System V message, the pointer to the nft_set
will be written at offset 24 within the message data.
6. Use the userland msgrcv()
function to read the messages and check which one does not have the tag anymore, as it would have been replaced by the pointer to the nft_set
.
Leaking a Kernel Function Pointer
Leaking a kernel pointer requires a bit more work than leaking a pointer to an nft_set
object. It requires being able to partially free objects within the target set bindings list as a means of crafting use-after-free conditions. This can be done by using the partial object free primitive using fdtable
object already described. The steps followed to leak a pointer to a kernel function are the following.
1. Increase the number of open file descriptors by calling dup()
on stdout
65 times.
2. Create a target set where the expressions will be bound to (different from the one used in the `nft_set
` adress leak).
3. Create a set with an embedded nft_lookup
expression bound to the target set. Since this is considered an invalid expression to be embedded into a set, the nft_lookup
object will be freed but not removed from the target set bindings list, causing a UAF.
4. Spray fdtable
objects in order to replace the freed nft_lookup
from step 3.
5. Create a set with an embedded nft_dynset
expression bound to the target set. Since this is considered an invalid expression to be embedded into a set, the nft_dynset
object will be freed but not removed from the target set bindings list, causing a UAF. This addition to the bindings list will write the pointer to its binding member into the open_fds
member of the fdtable
object (allocated in step 4) that replaced the nft_lookup
object.
6. Spray System V messages in the kmalloc-cg-96 slab cache in order to replace the freed nft_dynset
object (via msgsnd()
function). Tag all the messages at offset 8 so the one corrupted can be identified.
7. Kill all the child processes created in step 4 in order to trigger the partial free of the System V message that replaced the nft_dynset
object, effectively causing a UAF to a part of a System V message.
8. Spray time_namespace
objects in order to replace the partially freed System V message allocated in step 7. The reason for using the time_namespace
objects is explained later.
9. Since the System V message header was not corrupted, find the System V message whose tag has been overwritten. Use msgrcv()
to read the data from it, which is overlapping with the newly allocated time_namespace
object. The offset 40 of the data portion of the System V message corresponds to time_namespace.ns->ops
member, which is a function table of functions defined within the kernel core. Armed with this information and the knowledge of the offset from the kernel base image to this function it is possible to calculate the kernel image base address.
10. Clean-up the child processes used to spray the time_namespace
objects.
time_namespace
objects are interesting because they contain an ns_common
structure embedded in them, which in turn contains an ops
member that points to a function table with functions defined within the kernel core. The time_namespace
structure definition is listed below.