Update
Implementation of this kind of protection is pointless, since by the time the attacker is executing code he can already bypass the protections as discussed in kernel-hardening. Defeating a similar protection implemented in Windows 8 is described in Defeating Windows 8 ROP Mitigation
Analysis
Given the typical path for kernel exploitation is the
commit_creds(prepare_kernel_cred(0))
being called from user space as detailed in [1].
Why is not a check placed in commit_creds()
to checks the return address
and ensure that the call is a legit one coming from kernel space?.
This blocks direct calls to commit_creds
from user space,
however, it remains vulnerable to alternative exploit routes.
The alternatives to bypass this protection are:
- Indirect jump
An attacker can perform an indirect jump to a kernel location that does
the
commit_creds()
for him, but it complicates the task of the attacker. To prevent indirect calls check the remaining return addresses of the call trace. - Direct override Another bypass route is to figure out the memory location of the process creds, and perform the change directly in memory, but AFAIK SMAP and its ARM equivalent would deter this route.
Therefore, I started implementing some exploits for testing it,
and implemented the commit_cred
protection checks technique to
check its viability for preventing the commit_creds
abuse.
The Implementation provides a patch that implements the technique.
The Evaluation provides the tests performed
showing it effectively blocks commit_creds
abuse from user space.
Threat Model
The steps involved in commit_creds()
exploits:
- Prepare user code to get root: user_addr = commit_creds(prepare_creds(0)) - Override kernel code with: kernel_struct.fptr = user_addr (1st vuln point SMAP) - Trigger a syscall that calls kernel_struct.fptr - Invoke syscall from user - Results in kernel_struct.fptr() being called Calls user-space code from kernel code (2nd vuln point. At this point we're already toasted SMEP) Then call commit_creds from user-space (3rd vuln: check return address) At this point we detect commit_creds called from user
Implementation
The patch implements the commit_creds()
abuse prevention:
0001-cred-Prevent-commit_creds-user-space-abuse.patch
diff --git a/kernel/cred.c b/kernel/cred.c index 71179a0..7191db3 100644 --- a/kernel/cred.c +++ b/kernel/cred.c @@ -428,6 +428,13 @@ int commit_creds(struct cred *new) atomic_read(&new->usage), read_cred_subscribers(new)); + /* block attempts to use commit_creds from user space */ + if (__builtin_return_address(0) < PAGE_OFFSET) { + printk(KERN_ERR "CRED: BUG commit_creds called from user space\n"); + WARN_ON(1); + return -1; + } +
Evaluation
The dmesg(1)
output below shows the prevention of an exploit attempt using [1]
where commit_creds
is being called from user-space.
[ 1979.132453] exploit[8549]: segfault at 0 ip 08048719 sp bf8ff430 error 6 in exploit[8048000+1000] [ 1981.658186] BUG: commit_creds called from user space [ 1981.658201] ------------[ cut here ]------------ [ 1981.658207] WARNING: CPU: 0 PID: 8552 at kernel/cred.c:436 commit_creds+0x1e5/0x210() [ 1981.658209] Modules linked in: nullderef(O) nls_utf8 isofs udf crc_itu_
Discussion
Further discussion about this technique can be found at the kernel-hardening
mailing-list:
http://www.openwall.com/lists/kernel-hardening/2015/11/26/
References
- [1] Much ado about NULL: Exploiting a kernel NULL dereference by Nelson Elhage.