On techniques to prevent commit_creds() user-space abuse

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