Fix memory issues in BIO_*printf functions
authorMatt Caswell <matt@openssl.org>
Thu, 25 Feb 2016 13:09:46 +0000 (13:09 +0000)
committerMatt Caswell <matt@openssl.org>
Thu, 25 Feb 2016 22:45:58 +0000 (22:45 +0000)
The internal |fmtstr| function used in processing a "%s" format string
in the BIO_*printf functions could overflow while calculating the length
of a string and cause an OOB read when printing very long strings.

Additionally the internal |doapr_outch| function can attempt to write to
an OOB memory location (at an offset from the NULL pointer) in the event of
a memory allocation failure. In 1.0.2 and below this could be caused where
the size of a buffer to be allocated is greater than INT_MAX. E.g. this
could be in processing a very long "%s" format string. Memory leaks can also
occur.

These issues will only occur on certain platforms where sizeof(size_t) >
sizeof(int). E.g. many 64 bit systems. The first issue may mask the second
issue dependent on compiler behaviour.

These problems could enable attacks where large amounts of untrusted data
is passed to the BIO_*printf functions. If applications use these functions
in this way then they could be vulnerable. OpenSSL itself uses these
functions when printing out human-readable dumps of ASN.1 data. Therefore
applications that print this data could be vulnerable if the data is from
untrusted sources. OpenSSL command line applications could also be
vulnerable where they print out ASN.1 data, or if untrusted data is passed
as command line arguments.

Libssl is not considered directly vulnerable. Additionally certificates etc
received via remote connections via libssl are also unlikely to be able to
trigger these issues because of message size limits enforced within libssl.

CVE-2016-0799

Issue reported by Guido Vranken.

Reviewed-by: Andy Polyakov <appro@openssl.org>
crypto/bio/b_print.c

index f45fb10..17ea8af 100644 (file)
 # define LLONG long
 #endif
 
-static void fmtstr(char **, char **, size_t *, size_t *,
-                   const char *, int, int, int);
-static void fmtint(char **, char **, size_t *, size_t *,
-                   LLONG, int, int, int, int);
-static void fmtfp(char **, char **, size_t *, size_t *,
-                  LDOUBLE, int, int, int);
-static void doapr_outch(char **, char **, size_t *, size_t *, int);
-static void _dopr(char **sbuffer, char **buffer,
-                  size_t *maxlen, size_t *retlen, int *truncated,
-                  const char *format, va_list args);
+static int fmtstr(char **, char **, size_t *, size_t *,
+                  const char *, int, int, int);
+static int fmtint(char **, char **, size_t *, size_t *,
+                  LLONG, int, int, int, int);
+static int fmtfp(char **, char **, size_t *, size_t *,
+                 LDOUBLE, int, int, int);
+static int doapr_outch(char **, char **, size_t *, size_t *, int);
+static int _dopr(char **sbuffer, char **buffer,
+                 size_t *maxlen, size_t *retlen, int *truncated,
+                 const char *format, va_list args);
 
 /* format read states */
 #define DP_S_DEFAULT    0
@@ -164,7 +164,7 @@ static void _dopr(char **sbuffer, char **buffer,
 #define char_to_int(p) (p - '0')
 #define OSSL_MAX(p,q) ((p >= q) ? p : q)
 
-static void
+static int
 _dopr(char **sbuffer,
       char **buffer,
       size_t *maxlen,
@@ -195,7 +195,8 @@ _dopr(char **sbuffer,
             if (ch == '%')
                 state = DP_S_FLAGS;
             else
-                doapr_outch(sbuffer, buffer, &currlen, maxlen, ch);
+                if(!doapr_outch(sbuffer, buffer, &currlen, maxlen, ch))
+                    return 0;
             ch = *format++;
             break;
         case DP_S_FLAGS:
@@ -301,8 +302,9 @@ _dopr(char **sbuffer,
                     value = va_arg(args, int);
                     break;
                 }
-                fmtint(sbuffer, buffer, &currlen, maxlen,
-                       value, 10, min, max, flags);
+                if (!fmtint(sbuffer, buffer, &currlen, maxlen, value, 10, min,
+                            max, flags))
+                    return 0;
                 break;
             case 'X':
                 flags |= DP_F_UP;
@@ -325,17 +327,19 @@ _dopr(char **sbuffer,
                     value = (LLONG) va_arg(args, unsigned int);
                     break;
                 }
-                fmtint(sbuffer, buffer, &currlen, maxlen, value,
-                       ch == 'o' ? 8 : (ch == 'u' ? 10 : 16),
-                       min, max, flags);
+                if (!fmtint(sbuffer, buffer, &currlen, maxlen, value,
+                            ch == 'o' ? 8 : (ch == 'u' ? 10 : 16),
+                            min, max, flags))
+                    return 0;
                 break;
             case 'f':
                 if (cflags == DP_C_LDOUBLE)
                     fvalue = va_arg(args, LDOUBLE);
                 else
                     fvalue = va_arg(args, double);
-                fmtfp(sbuffer, buffer, &currlen, maxlen,
-                      fvalue, min, max, flags);
+                if (!fmtfp(sbuffer, buffer, &currlen, maxlen, fvalue, min, max,
+                           flags))
+                    return 0;
                 break;
             case 'E':
                 flags |= DP_F_UP;
@@ -354,8 +358,9 @@ _dopr(char **sbuffer,
                     fvalue = va_arg(args, double);
                 break;
             case 'c':
-                doapr_outch(sbuffer, buffer, &currlen, maxlen,
-                            va_arg(args, int));
+                if(!doapr_outch(sbuffer, buffer, &currlen, maxlen,
+                            va_arg(args, int)))
+                    return 0;
                 break;
             case 's':
                 strvalue = va_arg(args, char *);
@@ -365,13 +370,15 @@ _dopr(char **sbuffer,
                     else
                         max = *maxlen;
                 }
-                fmtstr(sbuffer, buffer, &currlen, maxlen, strvalue,
-                       flags, min, max);
+                if (!fmtstr(sbuffer, buffer, &currlen, maxlen, strvalue,
+                            flags, min, max))
+                    return 0;
                 break;
             case 'p':
                 value = (size_t)va_arg(args, void *);
-                fmtint(sbuffer, buffer, &currlen, maxlen,
-                       value, 16, min, max, flags | DP_F_NUM);
+                if (!fmtint(sbuffer, buffer, &currlen, maxlen,
+                            value, 16, min, max, flags | DP_F_NUM))
+                    return 0;
                 break;
             case 'n':          /* XXX */
                 if (cflags == DP_C_SHORT) {
@@ -393,7 +400,8 @@ _dopr(char **sbuffer,
                 }
                 break;
             case '%':
-                doapr_outch(sbuffer, buffer, &currlen, maxlen, ch);
+                if(!doapr_outch(sbuffer, buffer, &currlen, maxlen, ch))
+                    return 0;
                 break;
             case 'w':
                 /* not supported yet, treat as next char */
@@ -417,46 +425,56 @@ _dopr(char **sbuffer,
     *truncated = (currlen > *maxlen - 1);
     if (*truncated)
         currlen = *maxlen - 1;
-    doapr_outch(sbuffer, buffer, &currlen, maxlen, '\0');
+    if(!doapr_outch(sbuffer, buffer, &currlen, maxlen, '\0'))
+        return 0;
     *retlen = currlen - 1;
-    return;
+    return 1;
 }
 
-static void
+static int
 fmtstr(char **sbuffer,
        char **buffer,
        size_t *currlen,
        size_t *maxlen, const char *value, int flags, int min, int max)
 {
-    int padlen, strln;
+    int padlen;
+    size_t strln;
     int cnt = 0;
 
     if (value == 0)
         value = "<NULL>";
-    for (strln = 0; value[strln]; ++strln) ;
+
+    strln = strlen(value);
+    if (strln > INT_MAX)
+        strln = INT_MAX;
+
     padlen = min - strln;
-    if (padlen < 0)
+    if (min < 0 || padlen < 0)
         padlen = 0;
     if (flags & DP_F_MINUS)
         padlen = -padlen;
 
     while ((padlen > 0) && (cnt < max)) {
-        doapr_outch(sbuffer, buffer, currlen, maxlen, ' ');
+        if(!doapr_outch(sbuffer, buffer, currlen, maxlen, ' '))
+            return 0;
         --padlen;
         ++cnt;
     }
     while (*value && (cnt < max)) {
-        doapr_outch(sbuffer, buffer, currlen, maxlen, *value++);
+        if(!doapr_outch(sbuffer, buffer, currlen, maxlen, *value++))
+            return 0;
         ++cnt;
     }
     while ((padlen < 0) && (cnt < max)) {
-        doapr_outch(sbuffer, buffer, currlen, maxlen, ' ');
+        if(!doapr_outch(sbuffer, buffer, currlen, maxlen, ' '))
+            return 0;
         ++padlen;
         ++cnt;
     }
+    return 1;
 }
 
-static void
+static int
 fmtint(char **sbuffer,
        char **buffer,
        size_t *currlen,
@@ -516,37 +534,44 @@ fmtint(char **sbuffer,
 
     /* spaces */
     while (spadlen > 0) {
-        doapr_outch(sbuffer, buffer, currlen, maxlen, ' ');
+        if(!doapr_outch(sbuffer, buffer, currlen, maxlen, ' '))
+            return 0;
         --spadlen;
     }
 
     /* sign */
     if (signvalue)
-        doapr_outch(sbuffer, buffer, currlen, maxlen, signvalue);
+        if(!doapr_outch(sbuffer, buffer, currlen, maxlen, signvalue))
+            return 0;
 
     /* prefix */
     while (*prefix) {
-        doapr_outch(sbuffer, buffer, currlen, maxlen, *prefix);
+        if(!doapr_outch(sbuffer, buffer, currlen, maxlen, *prefix))
+            return 0;
         prefix++;
     }
 
     /* zeros */
     if (zpadlen > 0) {
         while (zpadlen > 0) {
-            doapr_outch(sbuffer, buffer, currlen, maxlen, '0');
+            if(!doapr_outch(sbuffer, buffer, currlen, maxlen, '0'))
+                return 0;
             --zpadlen;
         }
     }
     /* digits */
-    while (place > 0)
-        doapr_outch(sbuffer, buffer, currlen, maxlen, convert[--place]);
+    while (place > 0) {
+        if (!doapr_outch(sbuffer, buffer, currlen, maxlen, convert[--place]))
+            return 0;
+    }
 
     /* left justified spaces */
     while (spadlen < 0) {
-        doapr_outch(sbuffer, buffer, currlen, maxlen, ' ');
+        if (!doapr_outch(sbuffer, buffer, currlen, maxlen, ' '))
+            return 0;
         ++spadlen;
     }
-    return;
+    return 1;
 }
 
 static LDOUBLE abs_val(LDOUBLE value)
@@ -577,7 +602,7 @@ static long roundv(LDOUBLE value)
     return intpart;
 }
 
-static void
+static int
 fmtfp(char **sbuffer,
       char **buffer,
       size_t *currlen,
@@ -656,47 +681,61 @@ fmtfp(char **sbuffer,
 
     if ((flags & DP_F_ZERO) && (padlen > 0)) {
         if (signvalue) {
-            doapr_outch(sbuffer, buffer, currlen, maxlen, signvalue);
+            if (!doapr_outch(sbuffer, buffer, currlen, maxlen, signvalue))
+                return 0;
             --padlen;
             signvalue = 0;
         }
         while (padlen > 0) {
-            doapr_outch(sbuffer, buffer, currlen, maxlen, '0');
+            if (!doapr_outch(sbuffer, buffer, currlen, maxlen, '0'))
+                return 0;
             --padlen;
         }
     }
     while (padlen > 0) {
-        doapr_outch(sbuffer, buffer, currlen, maxlen, ' ');
+        if (!doapr_outch(sbuffer, buffer, currlen, maxlen, ' '))
+            return 0;
         --padlen;
     }
-    if (signvalue)
-        doapr_outch(sbuffer, buffer, currlen, maxlen, signvalue);
+    if (signvalue && !doapr_outch(sbuffer, buffer, currlen, maxlen, signvalue))
+        return 0;
 
-    while (iplace > 0)
-        doapr_outch(sbuffer, buffer, currlen, maxlen, iconvert[--iplace]);
+    while (iplace > 0) {
+        if (!doapr_outch(sbuffer, buffer, currlen, maxlen, iconvert[--iplace]))
+            return 0;
+    }
 
     /*
      * Decimal point. This should probably use locale to find the correct
      * char to print out.
      */
     if (max > 0 || (flags & DP_F_NUM)) {
-        doapr_outch(sbuffer, buffer, currlen, maxlen, '.');
+        if (!doapr_outch(sbuffer, buffer, currlen, maxlen, '.'))
+            return 0;
 
-        while (fplace > 0)
-            doapr_outch(sbuffer, buffer, currlen, maxlen, fconvert[--fplace]);
+        while (fplace > 0) {
+            if(!doapr_outch(sbuffer, buffer, currlen, maxlen,
+                            fconvert[--fplace]))
+                return 0;
+        }
     }
     while (zpadlen > 0) {
-        doapr_outch(sbuffer, buffer, currlen, maxlen, '0');
+        if (!doapr_outch(sbuffer, buffer, currlen, maxlen, '0'))
+            return 0;
         --zpadlen;
     }
 
     while (padlen < 0) {
-        doapr_outch(sbuffer, buffer, currlen, maxlen, ' ');
+        if (!doapr_outch(sbuffer, buffer, currlen, maxlen, ' '))
+            return 0;
         ++padlen;
     }
+    return 1;
 }
 
-static void
+#define BUFFER_INC  1024
+
+static int
 doapr_outch(char **sbuffer,
             char **buffer, size_t *currlen, size_t *maxlen, int c)
 {
@@ -707,24 +746,25 @@ doapr_outch(char **sbuffer,
     assert(*currlen <= *maxlen);
 
     if (buffer && *currlen == *maxlen) {
-        *maxlen += 1024;
+        if (*maxlen > INT_MAX - BUFFER_INC)
+            return 0;
+
+        *maxlen += BUFFER_INC;
         if (*buffer == NULL) {
             *buffer = OPENSSL_malloc(*maxlen);
-            if (*buffer == NULL) {
-                /* Panic! Can't really do anything sensible. Just return */
-                return;
-            }
+            if (*buffer == NULL)
+                return 0;
             if (*currlen > 0) {
                 assert(*sbuffer != NULL);
                 memcpy(*buffer, *sbuffer, *currlen);
             }
             *sbuffer = NULL;
         } else {
-            *buffer = OPENSSL_realloc(*buffer, *maxlen);
-            if (!*buffer) {
-                /* Panic! Can't really do anything sensible. Just return */
-                return;
-            }
+            char *tmpbuf;
+            tmpbuf = OPENSSL_realloc(*buffer, *maxlen);
+            if (tmpbuf == NULL)
+                return 0;
+            *buffer = tmpbuf;
         }
     }
 
@@ -735,7 +775,7 @@ doapr_outch(char **sbuffer,
             (*buffer)[(*currlen)++] = (char)c;
     }
 
-    return;
+    return 1;
 }
 
 /***************************************************************************/
@@ -766,7 +806,11 @@ int BIO_vprintf(BIO *bio, const char *format, va_list args)
     int ignored;
 
     dynbuf = NULL;
-    _dopr(&hugebufp, &dynbuf, &hugebufsize, &retlen, &ignored, format, args);
+    if (!_dopr(&hugebufp, &dynbuf, &hugebufsize, &retlen, &ignored, format,
+                args)) {
+        OPENSSL_free(dynbuf);
+        return -1;
+    }
     if (dynbuf) {
         ret = BIO_write(bio, dynbuf, (int)retlen);
         OPENSSL_free(dynbuf);
@@ -800,7 +844,8 @@ int BIO_vsnprintf(char *buf, size_t n, const char *format, va_list args)
     size_t retlen;
     int truncated;
 
-    _dopr(&buf, NULL, &n, &retlen, &truncated, format, args);
+    if(!_dopr(&buf, NULL, &n, &retlen, &truncated, format, args))
+        return -1;
 
     if (truncated)
         /*