CVE-2017-16995
"One of the best/worst Linux kernel vulns of all time" - @bleidl
Bruce Leidl @bleidl
"One of the worst vulnerabilities we have seen lately" - @nkhayet
The vulnerability allows for arbitrary read/write access to the linux kernel, bypassing SMEP/SMAP
June 4, 2017 https://twitter.com/bleidl/status/871527499984982016
Jann Horn @tehjh
December 4, 2017 - https://bugs.chromium.org/p/project-zero/issues/detail?id=1454&desc=3
https://github.com/torvalds/linux/commit/95a762e2c8c942780948091f8f2a4f32fce1ac6f
https://lwn.net/Articles/742169/
December 23
https://github.com/brl/grlh/blob/master/get-rekt-linux-hardened.c
Vitaly Nikolenko @vnik5287
March 15, 2018 - https://twitter.com/vnik5287/status/974277953394651137
http://cyseclabs.com/pub/upstream44.c
Vulnerability
It is possible to bypass the bpf verifier (verifier.c), load bpf code, and create a read/write primitive. The root cause of this vulnerability is improper arithmetic/sign-extention in the 'check_alu_op()' function located within verifier.c. The improper arithmetic makes it possible for sign extension to occur in either of the following cases:BPF_ALU64|BPF_MOV|BPF_K (load 32-bit immediate, sign-extended to 64-bit)
BPF_ALU|BPF_MOV|BPF_K (load 32-bit immediate, zero-padded to 64-bit);
Please see https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=95a762e2c8c942780948091f8f2a4f32fce1ac6f for specifics. Ultimately, the was patched by correcting the arithmatic in the 'check_alu_op()' function and ensuring sign extension only occurs in the first case "BPF_ALU64|BPF_MOV|BPF_K (load 32-bit immediate, sign-extended to 64-bit)"
Exploit/POC - get-rekt-linux-hardened.c
The BPF instruction set that bypasses the verify function is:#define BPF_DISABLE_VERIFIER() \
BPF_MOV32_IMM(BPF_REG_2, 0xFFFFFFFF), /* r2 = (u32)0xFFFFFFFF */ \
BPF_JMP_IMM(BPF_JNE, BPF_REG_2, 0xFFFFFFFF, 2), /* if (r2 == -1) { */ \
BPF_MOV64_IMM(BPF_REG_0, 0), /* exit(0); */ \
BPF_EXIT_INSN() /* } */
The bpf instruction set is well documented.
OP parameter
0 = Read the address for the frame pointer
1 = Read the address for the sk_buff structure
2 = Read a user supplied address
3 = Write a value to a specified address
Using the sendcmd function with the parameters as
sendcmd(op, address, value to write)
Privilege escalation
BFP code is setup to obtain the the pointer to sk_buff. ( OP = 1)With access to the sk_buff structure, it is possible to find the sock structure using an offset of 0x24
include/linux/skbuff.h
struct sk_buff {
union {
struct {
/* These two members must be first. */
struct sk_buff *next;
struct sk_buff *prev;
union {
ktime_t tstamp;
u64 skb_mstamp;
};
};
struct rb_node rbnode; /* used in netem & tcp stack */
};
struct sock *sk; <=========
With the address of the sock structure, scan down looking for sk_rcvtimeo by testing for the value 0x7fffffffffffffff .
Check the address 24 bytes above (sk_uid) to determine if contains the UID of the current user. If it does, sk_rcvtimeo has been found and the address for sk_peercred will be 8 bytes above it
struct sock {
...snipped...
u16 sk_gso_max_segs;
unsigned long sk_lingertime;
struct proto *sk_prot_creator;
rwlock_t sk_callback_lock;
int sk_err,
sk_err_soft;
u32 sk_ack_backlog;
u32 sk_max_ack_backlog;
kuid_t sk_uid; <======= test for current UID
struct pid *sk_peer_pid;
const struct cred *sk_peer_cred;
long sk_rcvtimeo; <======= scan to look for 0x7fffffffffffffff
ktime_t sk_stamp;
u16 sk_tsflags;
u8 sk_shutdown;
...snipped...
}
sk_peer_cred contains the address to the credential structure that can be overwritten to escalate privileges
Exploit/POC - Adapting @bleidl's exploit to include 4.4.x kernels
https://github.com/rlarabee/exploits/blob/master/cve-2017-16995/cve-2017-16995.cI have tested it on the following kernels
Ubuntu 16.04
4.4.0-31-generic
4.4.0-62-generic
4.4.0-81-generic
4.4.0-116-generic
4.8.0-58-generic
4.10.0.42-generic
4.13.0-21-generic
Fedora 27
4.13.9-300
The issue with the 4.4.x kernels in Ubuntu is the sock structure does not have the field @sk_uid, which contains the user id of the owner.
4.4: /usr/src/linux-source-4.4.0/linux-source-4.4.0/include/net/sock.h
struct sock {
...snipped...
u16 sk_gso_max_segs;
int sk_rcvlowat;
unsigned long sk_lingertime;
struct sk_buff_head sk_error_queue;
struct proto *sk_prot_creator;
rwlock_t sk_callback_lock;
int sk_err,
sk_err_soft;
u32 sk_ack_backlog;
u32 sk_max_ack_backlog;
__u32 sk_priority;
#if IS_ENABLED(CONFIG_CGROUP_NET_PRIO)
__u32 sk_cgrp_prioidx;
#endif
struct pid *sk_peer_pid;
const struct cred *sk_peer_cred;
long sk_rcvtimeo; <======= scan to look for 0x7fffffffffffffff
long sk_sndtimeo;
struct timer_list sk_timer;
...snipped...
}
4.13 /usr/src/linux-source-4.13.0/linux-source-4.13.0/include/net/sock.h
struct sock {
...snipped...
u16 sk_gso_max_segs;
unsigned long sk_lingertime;
struct proto *sk_prot_creator;
rwlock_t sk_callback_lock;
int sk_err,
sk_err_soft;
u32 sk_ack_backlog;
u32 sk_max_ack_backlog;
kuid_t sk_uid; <======= test for current UID (Missing in 4.4)
struct pid *sk_peer_pid;
const struct cred *sk_peer_cred;
long sk_rcvtimeo; <======= scan to look for 0x7fffffffffffffff
ktime_t sk_stamp;
u16 sk_tsflags;
u8 sk_shutdown;
...snipped...
}
Because this vulnerability allows for arbitrary r/w access, we can scan through the sock structure and test addresses to see if they contain the correct cred structure.
Using the same logic, the code scans through the sock structure, looking for 0x7fffffffffffffff.
Once it finds the value, it tests the address 8 bytes above it:
1) Is it in kernel memory (address > physoffset (0xffff880000000000))
2) Evaluates the address as a cred structure and test to see if it contains the current UID.
struct sock {
...snipped...
u16 sk_gso_max_segs;
int sk_rcvlowat;
unsigned long sk_lingertime;
struct sk_buff_head sk_error_queue;
struct proto *sk_prot_creator;
rwlock_t sk_callback_lock;
int sk_err,
sk_err_soft;
u32 sk_ack_backlog;
u32 sk_max_ack_backlog;
__u32 sk_priority;
#if IS_ENABLED(CONFIG_CGROUP_NET_PRIO)
__u32 sk_cgrp_prioidx;
#endif
struct pid *sk_peer_pid;
const struct cred *sk_peer_cred; <======= Evaluate this address
long sk_rcvtimeo; <======= scan to look for 0x7fffffffffffffff
long sk_sndtimeo;
struct timer_list sk_timer;
...snipped...
}
struct cred {
atomic_t usage;
#ifdef CONFIG_DEBUG_CREDENTIALS
atomic_t subscribers; /* number of processes subscribed */
void *put_addr;
unsigned magic;
#define CRED_MAGIC 0x43736564
#define CRED_MAGIC_DEAD 0x44656144
#endif
kuid_t uid; /* real UID of the task */ <== Evaluate if this equals current UID
kgid_t gid; /* real GID of the task */
kuid_t suid; /* saved UID of the task */
kgid_t sgid; /* saved GID of the task */
kuid_t euid; /* effective UID of the task */
kgid_t egid; /* effective GID of the task */
kuid_t fsuid; /* UID for VFS ops */
kgid_t fsgid; /* GID for VFS ops */
unsigned securebits; /* SUID-less security management */
kernel_cap_t cap_inheritable; /* caps our children can inherit */
kernel_cap_t cap_permitted; /* caps we're permitted */
kernel_cap_t cap_effective; /* caps we can actually use */
kernel_cap_t cap_bset; /* capability bounding set */
kernel_cap_t cap_ambient; /* Ambient capability set */
As before sk_peer_cred contains the address to the credential structure that can be overwritten to escalate privileges
Exploit/POC - Upstream.c (4.4.x kernels)
Dangokyo has a good description of the vulnerability, exploit, and privilege escalation technique.
In the 4.4 kernel, the thread_info struct is available at the top of the stack
linux-source-4.4.0_4.4.0-116.140_all.deb
/usr/src/linux-source-4.4.0/linux-source-4.4.0/arch/x86/include/asm/thread_info.h
struct thread_info {
struct task_struct *task; /* main task structure */ <==========
__u32 flags; /* low level flags */
__u32 status; /* thread synchronous flags */
__u32 cpu; /* current CPU */
mm_segment_t addr_limit;
unsigned int sig_on_uaccess_error:1;
unsigned int uaccess_err:1; /* uaccess failed */
};
In later versions, the task_struct has been removed.
linux-source-4.13.0_4.13.0-21.24_all.deb
/usr/src/linux-source-4.13.0/linux-source-4.13.0/arch/x86/include/asm/thread_info.h
struct thread_info {
unsigned long flags; /* low level flags */
};
Privilege Escalation
Linux kernel stacks start with a struct thread_info that look like this:* struct thread_info {
* struct task_struct *task; // <--- pointer to current task_struct
The task structure contains lots of fields, but the one we are most
interested in are:
* struct task_struct {
* // ...
* const struct cred *real_cred; <========
Struct cred contains:
* struct cred {
* // ...
* uid_t uid;
* gid_t gid;
* uid_t suid;
* gid_t sgid;
* uid_t euid;
* gid_t egid;
* uid_t fsuid;
* gid_t fsgid;
* unsigned securebits;
* kernel_cap_t cap_inheritable;
* kernel_cap_t cap_permitted;
* kernel_cap_t cap_effective;
real_cred contains the address to the credential structure that can be overwritten to escalate privileges
Mitigations
Kernel Patch levelUbuntu
https://people.canonical.com/~ubuntu-security/cve/2017/CVE-2017-16995.html
4.4.x: Fixed 4.4.0-119.143
4.8.x: vulnerable up through 4.8.0-58-generic
4.10.x: vulnerable up through 4.10.0-42-generic
4.13.x: Fixed 4.13.0-25-generic
Redhat
https://bugzilla.redhat.com/show_bug.cgi?id=1528518
RHEL 5,6,7 not affected
Fedora: Fixed kernel-4.14.11 which pushed to stable on January 4, 2018
echo 1 > /proc/sys/kernel/unprivileged_bpf_disabled
sysctl kernel.unprivileged_bpf_disabled=1
References/Resources
eBPF
https://www.kernel.org/doc/Documentation/networking/filter.txthttps://www.kernel.org/doc/Documentation/bpf/bpf_design_QA.txt
http://www.brendangregg.com/ebpf.html
https://github.com/iovisor/bcc
https://qmonnet.github.io/whirl-offload/2016/09/01/dive-into-bpf/
https://ferrisellis.com/posts/ebpf_past_present_future/
https://ferrisellis.com/posts/ebpf_syscall_and_maps/
https://suchakra.wordpress.com/2015/05/18/bpf-internals-i/
https://suchakra.wordpress.com/2015/08/12/bpf-internals-ii/
CVE-2017-16995
https://github.com/rapid7/metasploit-framework/blob/691d8f2c413bbda942fd49afe45d79771a5d1b10/modules/exploits/linux/local/bpf_sign_extension_priv_esc.rbhttp://www.openwall.com/lists/oss-security/2017/12/21/2
https://github.com/brl/grlh/blob/master/get-rekt-linux-hardened.c
https://github.com/rlarabee/exploits/blob/master/cve-2017-16995/cve-2017-16995.c
http://cyseclabs.com/pub/upstream44.c
https://www.exploit-db.com/exploits/44298/
https://github.com/dangokyo/CVE_2017_16995 - nice disassembler.c to better visualize bpf code
https://blog.aquasec.com/ebpf-vulnerability-cve-2017-16995-when-the-doorman-becomes-the-backdoor
https://www.securityfocus.com/bid/102288
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-16995
https://nvd.nist.gov/vuln/detail/CVE-2017-16995