Return to main page

Preventing exploitation with bounds checking

(started 24/7/2007, added 18/8/2007)

This article will cover various how the GCC bounds checking patch applies in the real world, and how it can help prevent/mitigate software from being exploitable. This article will also look at the conditions where the bounds checking patch does not help, so the reader gets a good idea where bounds checking may fit in for their needs.

This article is based upon Gentoo Hardened 2006.0 hardened release, completely upgraded. My /etc/make.profile is /usr/portage/profiles/x86/hardened/2.6, with CFLAGS="-O2 -march=pentium4 -pipe -Wa,—noexecstack". This article is vaguely based on recollections of a previous article I once wrote up.

As a side note, the bounds checking patch may not work straight away for the software you want to work with - as some packages may implement functionality via shared libraries, which won't load into the process because the bounds checking patch doesn't export the required symbols for that to work.

And with all that out of the way, let's get started.

OpenSSH Challenge Response Pre-authentication overflow (CVE-2002-0639)

The vulnerable code (openssh-3.3p1/auth2-chall.c):

240 static void
241 input_userauth_info_response(int type, u_int32_t seq, void *ctxt)
242 {
243         Authctxt *authctxt = ctxt;
244         KbdintAuthctxt *kbdintctxt;
245         int i, authenticated = 0, res, len;
246         u_int nresp;
247         char **response = NULL, *method;
248
<snip>
258         nresp = packet_get_int();   [1]
259         if (nresp > 0) {
260                 response = xmalloc(nresp * sizeof(char*)); [2]
261                 for (i = 0; i < nresp; i++) [3]
262                         response[i] = packet_get_string(NULL); [4]
263         }
264         packet_check_eom();
265
<snip>

The below explains a bit of the applicable code flow

  1. Read in an unsigned integer from the network, and stores it to the local variable called nresp

  2. Allocate a variable called response, based on the multiplication of nresp with the result of sizeof(char*). If nresp is large enough, this multiplication will overflow, and the allocated space for the response variable will not be enough to contain everything that will be read in.

  3. Loop over nresp, reading in a string from the network, till a suitable terminating condition appears. This could be end of the loop, a network error, or some such.

Further information on integer overflows can be found here

Triggering the vulnerablity

For this part, we trigger the vulnerability, and see how the bounds checking patch responds:

auth2-chall.c:262:Bounds error: attempt to reference memory overrunning the
end of an object.
auth2-chall.c:262:  Pointer value: 0x822e000, Size: 4
auth2-chall.c:262:  Object `malloc':
auth2-chall.c:262:    Address in memory:    0x822d000 .. 0x822dfff
auth2-chall.c:262:    Size:                 4096 bytes
auth2-chall.c:262:    Element size:         1 bytes
auth2-chall.c:262:    Number of elements:   4096
auth2-chall.c:262:    Created at:           xmalloc.c, line 28
auth2-chall.c:262:    Storage class:        heap

This means it stopped execution in auth2-chall.c, at line 262 (where it is writing to the response variable), that the memory was allocated in xmalloc.c, on line 28. It also tells us other information which isn't applicable to our situation.

Summary

The bounds checking patch is this case stopped OpenSSH accessing memory outside of the allocated memory for response, which stopped exploitation of the vulnerable version of OpenSSH.

OpenSSL key_arg buffer overflow (CAN-2002-0656)

Unfortunately, this overflow is not that good of an example to use with bounds checking, as the openssl libray version uses has various programming bugs which cause the bounds checking patch to terminate the process. The apache process had to be run with the following variable set:

GCC_BOUNDS_OPTS="-never-fatal -output-file=/tmp/apache.%p"

With -never-fatal indicating that if a boundary issue was noticed, it wouldn't cause the process to terminate. On the other hand, it was a widely exploited vulnerability, and the bounds checking patch would have of indicated that the service was exploited, even though it couldn't prevent it.

The vulnerable code (ssl/s2_srvr.c)

 423         /* SSL2_ST_GET_CLIENT_MASTER_KEY_B */
 424         p=(unsigned char *)s->init_buf->data;
 425         keya=s->session->key_arg_length;
 426         len = 10 + (unsigned long)s->s2->tmp.clear + (unsigned long)s->s2->tmp.enc + (unsigned long)keya;
 427         if (len > SSL2_MAX_RECORD_LENGTH_3_BYTE_HEADER)
 428                 {
 429                 SSLerr(SSL_F_GET_CLIENT_MASTER_KEY,SSL_R_MESSAGE_TOO_LONG);
 430                 return -1;
 431                 }
 432         n = (int)len - s->init_num;
 433         i = ssl2_read(s,(char *)&(p[s->init_num]),n);
 434         if (i != n) return(ssl2_part_read(s,SSL_F_GET_CLIENT_MASTER_KEY,i));
 435         p += 10;
 436
 437         memcpy(s->session->key_arg,&(p[s->s2->tmp.clear+s->s2->tmp.enc]),
 438                 (unsigned int)keya);
  1. Line 425: set the keya variable, which is read directly from the network

  2. Line 426: calcuate how much more data needs to be read in, and read it in.

  3. Line 437: perform a memory copy to the heap, from user controlled data, with a length specified by the client.

When a keya size is specified which is larger than the size of structure allocated, minus the offset to the key_arg variable, the bounds checking code will alert that the memcpy is invalid.

However, there are other exploitation avenues that could be explored if accessing past the size of the allocated structure was prevented. It would of been possible to try and exploit the service via malloc() / free() tricks, as the overflow would allow arbirary overwrites of pointers in the structure.

The structure code looks like:

typedef struct ssl_session_st
        {
        int ssl_version;        /* what ssl version session info is
                                 * being kept in here? */

        /* only really used in SSLv2 */
        unsigned int key_arg_length;
        unsigned char key_arg[SSL_MAX_KEY_ARG_LENGTH];
        int master_key_length;
        unsigned char master_key[SSL_MAX_MASTER_KEY_LENGTH];
        /* session_id - valid? */
        unsigned int session_id_length;
        unsigned char session_id[SSL_MAX_SSL_SESSION_ID_LENGTH];
        /* this is used to determine whether the session is being reused in
         * the appropriate context. It is up to the application to set this,
         * via SSL_new */
        unsigned int sid_ctx_length;
        unsigned char sid_ctx[SSL_MAX_SID_CTX_LENGTH];

        int not_resumable;

        /* The cert is the certificate used to establish this connection */
        struct sess_cert_st /* SESS_CERT */ *sess_cert;

        /* This is the cert for the other end.
         * On clients, it will be the same as sess_cert->peer_key->x509
         * (the latter is not enough as sess_cert is not retained
         * in the external representation of sessions, see ssl_asn1.c). */
        X509 *peer;
        /* when app_verify_callback accepts a session where the peer's certificate
         * is not ok, we must remember the error for session reuse: */
        long verify_result; /* only for servers */

        int references;
        long timeout;
        long time;

        int compress_meth;              /* Need to lookup the method */

        SSL_CIPHER *cipher;
        unsigned long cipher_id;        /* when ASN.1 loaded, this
                                         * needs to be used to load
                                         * the 'cipher' structure */

        STACK_OF(SSL_CIPHER) *ciphers; /* shared ciphers? */

        CRYPTO_EX_DATA ex_data; /* application specific data */

        /* These are used to make removal of session-ids more
         * efficient and to implement a maximum cache size. */
        struct ssl_session_st *prev,*next;
} SSL_SESSION;

Anything from key_arg onwards could be modified (till end of structure), and possibly used to obtain arbitrary execution.

Triggering the vulnerability

Using the www.phreedom.org/solar/exploits/apache-openssl/[openssl-too-open] exploit code, we get the following output:

: openssl-too-open : OpenSSL remote exploit
  by Solar Eclipse <solareclipse@phreedom.org>

: Opening 30 connections
  Establishing SSL connections

 -> ssl_connect_host
 -> ssl_connect_host
 -> ssl_connect_host
 -> ssl_connect_host
: Using the OpenSSL info leak to retrieve the addresses
 -> send_client_hello
 -> get_server_hello
 -> send_client_master_key
 -> generate_session_keys
 -> get_server_verify
 -> send_client_finished
 -> get_server_finished
  ssl0 : 0x86ada40
 -> send_client_hello
 -> get_server_hello
 -> send_client_master_key
 -> generate_session_keys
 -> get_server_verify
 -> send_client_finished
 -> get_server_finished
  ssl1 : 0x86ada40
 -> send_client_hello
 -> get_server_hello
 -> send_client_master_key
 -> generate_session_keys
 -> get_server_verify
 -> send_client_finished
 -> get_server_finished
  ssl2 : 0x86ada40

: Sending shellcode
 -> send_client_hello
 -> get_server_hello
ciphers: 0x86ada40   start_addr: 0x86ad980   SHELLCODE_OFS: 208
 -> send_client_master_key
 -> generate_session_keys
 -> get_server_verify
 -> send_client_finished
 -> get_server_error
Connection unexpectedly closed

The bounds checking code returned the following output (in a log file):

s2_srvr.c:438:Bounds error: memcpy with this destination pointer and size 281 would overrun the end of the object's allocated memory.
s2_srvr.c:438:  Pointer value: 0x86a3808
s2_srvr.c:438:  Object `malloc':
s2_srvr.c:438:    Address in memory:    0x86a3800 .. 0x86a38c7
s2_srvr.c:438:    Size:                 200 bytes
s2_srvr.c:438:    Element size:         1 bytes
s2_srvr.c:438:    Number of elements:   200
s2_srvr.c:438:    Storage class:        heap

This shows where the memcpy would have exceeded the size of the structure.

Summary

The OpenSSL s2_srvr.c key_arg overflow is a good example of how bounds checking could of:

  1. helped clean up the OpenSSL code base, by showing various other programming mistakes first

  2. Preventing a method of exploitation (however, malloc/free tricks still may've been possible)

  3. Noticing exploitation in progress

3proxy 0.5.3g logurl() overflow (CVE-2007-2031)

Vulnerable code

The below code is the vulnerable code in the 3proxy-0.5.3g release.


#define BUFSIZE 4096
#define LINESIZE 2048

void logurl(struct clientparam * param, char * req, int ftp){
 char *sb;
 char *se;
 char buf[LINESIZE];            <--

 if(req) {
        strcpy(buf,req);        <--
        sb = strchr(buf, '\r');
        if(sb)*sb = 0;
        if(ftp && (se = strchr(buf + 10, ':')) && (sb = strchr(se, '@')) ) {
                strcpy(se, sb);
        }
 }
 if(param->res != 555)(*param->logfunc)(param, (unsigned char *)(req?buf:NULL));
}

It is possible for the req function parameter to be longer than the buf variable allows for, and thus is a standard stack based overflow.

Triggering the vulnerability

As expected, the bounds checking patch picks this up without any errors, as the below shows.

proxy.c:129:Bounds error: strcpy with this destination string and size 2486 would overrun the end of the object's allocated memory.
proxy.c:129:  Pointer value: 0x5f1fd9d0
proxy.c:129:  Object `buf':
proxy.c:129:    Address in memory:    0x5f1fd9d0 .. 0x5f1fe1cf
proxy.c:129:    Size:                 2048 bytes
proxy.c:129:    Element size:         1 bytes
proxy.c:129:    Number of elements:   2048
proxy.c:129:    Created at:           proxy.c, line 126
proxy.c:129:    Storage class:        stack

Summary

As the above examples have shown, bounds checking can be a valuable resource to help prevent (and mitigate) exploitation of processes, and can prevent/reduce/mitigate real world exploitable conditions.