openssl: reduce memory consumption
authorBert Belder <bertbelder@gmail.com>
Tue, 11 Sep 2012 19:33:15 +0000 (21:33 +0200)
committerBert Belder <bertbelder@gmail.com>
Wed, 12 Sep 2012 03:23:47 +0000 (05:23 +0200)
SSL records may be as large as 16K, but are typically < 2K.  In
addition, a historic bug in Windows allowed records to be as large
32K.  OpenSSL statically allocates read and write buffers (34K and
18K respectively) used for processing records.

With this patch, OpenSSL statically allocates 4K + 4K buffers, with
the option of dynamically growing buffers to 34K + 4K, which is a
saving of 44K per connection for the typical case.

This patch is taken from the Android Open Source Project.

deps/openssl/openssl/ssl/d1_pkt.c
deps/openssl/openssl/ssl/s23_srvr.c
deps/openssl/openssl/ssl/s3_both.c
deps/openssl/openssl/ssl/s3_pkt.c
deps/openssl/openssl/ssl/ssl.h
deps/openssl/openssl/ssl/ssl3.h
deps/openssl/openssl/ssl/ssltest.c
deps/openssl/openssl/test/testssl

index 987af60..e3e9864 100644 (file)
@@ -614,6 +614,24 @@ again:
                        goto again;
                        }
 
+               /* If we receive a valid record larger than the current buffer size,
+                * allocate some memory for it.
+                */
+               if (rr->length > s->s3->rbuf.len - DTLS1_RT_HEADER_LENGTH)
+                       {
+                       unsigned char *pp;
+                       unsigned int newlen = rr->length + DTLS1_RT_HEADER_LENGTH;
+                       if ((pp=OPENSSL_realloc(s->s3->rbuf.buf, newlen))==NULL)
+                               {
+                               SSLerr(SSL_F_DTLS1_GET_RECORD,ERR_R_MALLOC_FAILURE);
+                               return(-1);
+                               }
+                       p = pp + (p - s->s3->rbuf.buf);
+                       s->s3->rbuf.buf=pp;
+                       s->s3->rbuf.len=newlen;
+                       s->packet= &(s->s3->rbuf.buf[0]);
+                       }
+
                /* now s->rstate == SSL_ST_READ_BODY */
                }
 
@@ -1452,6 +1470,7 @@ int do_dtls1_write(SSL *s, int type, const unsigned char *buf, unsigned int len,
        SSL3_BUFFER *wb;
        SSL_SESSION *sess;
        int bs;
+       unsigned int len_with_overhead = len + SSL3_RT_DEFAULT_WRITE_OVERHEAD;
 
        /* first check if there is a SSL3_BUFFER still being written
         * out.  This will happen with non blocking IO */
@@ -1461,6 +1480,16 @@ int do_dtls1_write(SSL *s, int type, const unsigned char *buf, unsigned int len,
                return(ssl3_write_pending(s,type,buf,len));
                }
 
+       if (s->s3->wbuf.len < len_with_overhead)
+               {
+               if ((p=OPENSSL_realloc(s->s3->wbuf.buf, len_with_overhead)) == NULL) {
+                       SSLerr(SSL_F_DO_DTLS1_WRITE,ERR_R_MALLOC_FAILURE);
+                       goto err;
+               }
+               s->s3->wbuf.buf = p;
+               s->s3->wbuf.len = len_with_overhead;
+               }
+
        /* If we have an alert to send, lets send it */
        if (s->s3->alert_dispatch)
                {
index 4877849..4c4721f 100644 (file)
@@ -444,8 +444,13 @@ int ssl23_get_client_hello(SSL *s)
                v[0] = p[3]; /* == SSL3_VERSION_MAJOR */
                v[1] = p[4];
 
+/* The SSL2 protocol allows n to be larger, just pick
+ * a reasonable buffer size. */
+#if SSL3_RT_DEFAULT_PACKET_SIZE < 1024*4 - SSL3_RT_DEFAULT_WRITE_OVERHEAD
+#error "SSL3_RT_DEFAULT_PACKET_SIZE is too small."
+#endif
                n=((p[0]&0x7f)<<8)|p[1];
-               if (n > (1024*4))
+               if (n > SSL3_RT_DEFAULT_PACKET_SIZE - 2)
                        {
                        SSLerr(SSL_F_SSL23_GET_CLIENT_HELLO,SSL_R_RECORD_TOO_LARGE);
                        goto err;
index b63460a..87ddd82 100644 (file)
@@ -745,13 +745,20 @@ int ssl3_setup_read_buffer(SSL *s)
 
        if (s->s3->rbuf.buf == NULL)
                {
-               len = SSL3_RT_MAX_PLAIN_LENGTH
-                       + SSL3_RT_MAX_ENCRYPTED_OVERHEAD
-                       + headerlen + align;
-               if (s->options & SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER)
+               if (SSL_get_mode(s) & SSL_MODE_SMALL_BUFFERS)
+                       {
+                       len = SSL3_RT_DEFAULT_PACKET_SIZE;
+                       }
+               else
                        {
-                       s->s3->init_extra = 1;
-                       len += SSL3_RT_MAX_EXTRA;
+                       len = SSL3_RT_MAX_PLAIN_LENGTH
+                               + SSL3_RT_MAX_ENCRYPTED_OVERHEAD
+                               + headerlen + align;
+                       if (s->options & SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER)
+                               {
+                               s->s3->init_extra = 1;
+                               len += SSL3_RT_MAX_EXTRA;
+                               }
                        }
 #ifndef OPENSSL_NO_COMP
                if (!(s->options & SSL_OP_NO_COMPRESSION))
@@ -787,7 +794,15 @@ int ssl3_setup_write_buffer(SSL *s)
 
        if (s->s3->wbuf.buf == NULL)
                {
-               len = s->max_send_fragment
+               if (SSL_get_mode(s) & SSL_MODE_SMALL_BUFFERS)
+                       {
+                       len = SSL3_RT_DEFAULT_PACKET_SIZE;
+                       }
+               else
+                       {
+                       len = s->max_send_fragment;
+                       }
+               len += 0
                        + SSL3_RT_SEND_MAX_ENCRYPTED_OVERHEAD
                        + headerlen + align;
 #ifndef OPENSSL_NO_COMP
@@ -797,7 +812,6 @@ int ssl3_setup_write_buffer(SSL *s)
                if (!(s->options & SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS))
                        len += headerlen + align
                                + SSL3_RT_SEND_MAX_ENCRYPTED_OVERHEAD;
-
                if ((p=freelist_extract(s->ctx, 0, len)) == NULL)
                        goto err;
                s->s3->wbuf.buf = p;
@@ -840,4 +854,3 @@ int ssl3_release_read_buffer(SSL *s)
                }
        return 1;
        }
-
index adf8c38..1cc3442 100644 (file)
@@ -295,6 +295,11 @@ static int ssl3_get_record(SSL *s)
        size_t extra;
        int decryption_failed_or_bad_record_mac = 0;
        unsigned char *mac = NULL;
+#if defined(SSL3_ALIGN_PAYLOAD) && SSL3_ALIGN_PAYLOAD!=0
+       long align=SSL3_ALIGN_PAYLOAD;
+#else
+       long align=0;
+#endif
 
        rr= &(s->s3->rrec);
        sess=s->session;
@@ -303,7 +308,8 @@ static int ssl3_get_record(SSL *s)
                extra=SSL3_RT_MAX_EXTRA;
        else
                extra=0;
-       if (extra && !s->s3->init_extra)
+       if (!(SSL_get_mode(s) & SSL_MODE_SMALL_BUFFERS) &&
+               extra && !s->s3->init_extra)
                {
                /* An application error: SLS_OP_MICROSOFT_BIG_SSLV3_BUFFER
                 * set after ssl3_setup_buffers() was done */
@@ -352,6 +358,21 @@ fprintf(stderr, "Record type=%d, Length=%d\n", rr->type, rr->length);
                        goto err;
                        }
 
+               /* If we receive a valid record larger than the current buffer size,
+                * allocate some memory for it.
+                */
+               if (rr->length > s->s3->rbuf.len - SSL3_RT_HEADER_LENGTH - align)
+                       {
+                       if ((p=OPENSSL_realloc(s->s3->rbuf.buf, rr->length + SSL3_RT_HEADER_LENGTH + align))==NULL)
+                               {
+                               SSLerr(SSL_F_SSL3_GET_RECORD,ERR_R_MALLOC_FAILURE);
+                               goto err;
+                               }
+                       s->s3->rbuf.buf=p;
+                       s->s3->rbuf.len=rr->length + SSL3_RT_HEADER_LENGTH + align;
+                       s->packet= &(s->s3->rbuf.buf[0]);
+                       }
+
                if (rr->length > s->s3->rbuf.len - SSL3_RT_HEADER_LENGTH)
                        {
                        al=SSL_AD_RECORD_OVERFLOW;
@@ -578,6 +599,7 @@ int ssl3_write_bytes(SSL *s, int type, const void *buf_, int len)
        const unsigned char *buf=buf_;
        unsigned int tot,n,nw;
        int i;
+       unsigned int max_plain_length;
 
        s->rwstate=SSL_NOTHING;
        tot=s->s3->wnum;
@@ -597,8 +619,13 @@ int ssl3_write_bytes(SSL *s, int type, const void *buf_, int len)
        n=(len-tot);
        for (;;)
                {
-               if (n > s->max_send_fragment)
-                       nw=s->max_send_fragment;
+               if (type == SSL3_RT_APPLICATION_DATA && (SSL_get_mode(s) & SSL_MODE_SMALL_BUFFERS))
+                       max_plain_length = SSL3_RT_DEFAULT_PLAIN_LENGTH;
+               else
+                       max_plain_length = s->max_send_fragment;
+
+               if (n > max_plain_length)
+                       nw = max_plain_length;
                else
                        nw=n;
 
@@ -707,6 +734,18 @@ static int do_ssl3_write(SSL *s, int type, const unsigned char *buf,
                s->s3->empty_fragment_done = 1;
                }
 
+       /* resize if necessary to hold the data. */
+       if (len + SSL3_RT_DEFAULT_WRITE_OVERHEAD > wb->len)
+               {
+               if ((p=OPENSSL_realloc(wb->buf, len + SSL3_RT_DEFAULT_WRITE_OVERHEAD))==NULL)
+                       {
+                       SSLerr(SSL_F_DO_SSL3_WRITE,ERR_R_MALLOC_FAILURE);
+                       goto err;
+                       }
+               wb->buf = p;
+               wb->len = len + SSL3_RT_DEFAULT_WRITE_OVERHEAD;
+               }
+
        if (create_empty_fragment)
                {
 #if defined(SSL3_ALIGN_PAYLOAD) && SSL3_ALIGN_PAYLOAD!=0
index 8b0c2a2..e2ec0eb 100644 (file)
@@ -638,6 +638,9 @@ struct ssl_session_st
  * TLS only.)  "Released" buffers are put onto a free-list in the context
  * or just freed (depending on the context's setting for freelist_max_len). */
 #define SSL_MODE_RELEASE_BUFFERS 0x00000010L
+/* Use small read and write buffers: (a) lazy allocate read buffers for
+ * large incoming records, and (b) limit the size of outgoing records. */
+#define SSL_MODE_SMALL_BUFFERS 0x00000020L
 
 /* Note: SSL[_CTX]_set_{options,mode} use |= op on the previous value,
  * they cannot be used to clear bits. */
index 112e627..d8459fa 100644 (file)
@@ -280,6 +280,9 @@ extern "C" {
 
 #define SSL3_RT_MAX_EXTRA                      (16384)
 
+/* Default buffer length used for writen records.  Thus a generated record
+ * will contain plaintext no larger than this value. */
+#define SSL3_RT_DEFAULT_PLAIN_LENGTH   2048
 /* Maximum plaintext length: defined by SSL/TLS standards */
 #define SSL3_RT_MAX_PLAIN_LENGTH               16384
 /* Maximum compression overhead: defined by SSL/TLS standards */
@@ -311,6 +314,13 @@ extern "C" {
 #define SSL3_RT_MAX_PACKET_SIZE                \
                (SSL3_RT_MAX_ENCRYPTED_LENGTH+SSL3_RT_HEADER_LENGTH)
 
+/* Extra space for empty fragment, headers, MAC, and padding. */
+#define SSL3_RT_DEFAULT_WRITE_OVERHEAD  256
+#define SSL3_RT_DEFAULT_PACKET_SIZE     4096 - SSL3_RT_DEFAULT_WRITE_OVERHEAD
+#if SSL3_RT_DEFAULT_PLAIN_LENGTH + SSL3_RT_DEFAULT_WRITE_OVERHEAD > SSL3_RT_DEFAULT_PACKET_SIZE
+#error "Insufficient space allocated for write buffers."
+#endif
+
 #define SSL3_MD_CLIENT_FINISHED_CONST  "\x43\x4C\x4E\x54"
 #define SSL3_MD_SERVER_FINISHED_CONST  "\x53\x52\x56\x52"
 
@@ -675,4 +685,3 @@ typedef struct ssl3_state_st
 }
 #endif
 #endif
-
index 0f8fd39..5fa4466 100644 (file)
@@ -369,6 +369,8 @@ static void sv_usage(void)
                       "                 (default is sect163r2).\n");
 #endif
        fprintf(stderr," -test_cipherlist - verifies the order of the ssl cipher lists\n");
+       fprintf(stderr," -c_small_records - enable client side use of small SSL record buffers\n");
+       fprintf(stderr," -s_small_records - enable server side use of small SSL record buffers\n");
        }
 
 static void print_details(SSL *c_ssl, const char *prefix)
@@ -497,6 +499,9 @@ int opaque_prf_input_cb(SSL *ssl, void *peerinput, size_t len, void *arg_)
        return arg->ret;
        }
 #endif
+       int ssl_mode = 0;
+       int c_small_records=0;
+       int s_small_records=0;
 
 int main(int argc, char *argv[])
        {
@@ -765,6 +770,14 @@ int main(int argc, char *argv[])
                        {
                        test_cipherlist = 1;
                        }
+               else if (strcmp(*argv, "-c_small_records") == 0)
+                       {
+                       c_small_records = 1;
+                       }
+               else if (strcmp(*argv, "-s_small_records") == 0)
+                       {
+                       s_small_records = 1;
+                       }
                else
                        {
                        fprintf(stderr,"unknown option %s\n",*argv);
@@ -901,6 +914,21 @@ bad:
                SSL_CTX_set_cipher_list(s_ctx,cipher);
                }
 
+       ssl_mode = 0;
+       if (c_small_records)
+               {
+               ssl_mode = SSL_CTX_get_mode(c_ctx);
+               ssl_mode |= SSL_MODE_SMALL_BUFFERS;
+               SSL_CTX_set_mode(c_ctx, ssl_mode);
+               }
+       ssl_mode = 0;
+       if (s_small_records)
+               {
+               ssl_mode = SSL_CTX_get_mode(s_ctx);
+               ssl_mode |= SSL_MODE_SMALL_BUFFERS;
+               SSL_CTX_set_mode(s_ctx, ssl_mode);
+               }
+
 #ifndef OPENSSL_NO_DH
        if (!no_dhe)
                {
index 5ae4dc8..0e3d6aa 100644 (file)
@@ -70,6 +70,15 @@ $ssltest -client_auth $CA $extra || exit 1
 echo test sslv2/sslv3 with both client and server authentication
 $ssltest -server_auth -client_auth $CA $extra || exit 1
 
+echo test sslv2/sslv3 with both client and server authentication and small client buffers
+$ssltest -server_auth -client_auth -c_small_records $CA $extra || exit 1
+
+echo test sslv2/sslv3 with both client and server authentication and small server buffers
+$ssltest -server_auth -client_auth -s_small_records $CA $extra || exit 1
+
+echo test sslv2/sslv3 with both client and server authentication and small client and server buffers
+$ssltest -server_auth -client_auth -c_small_records -s_small_records $CA $extra || exit 1
+
 echo test sslv2 via BIO pair
 $ssltest -bio_pair -ssl2 $extra || exit 1