port/snprintf(): fix overflow and do padding
authorBruce Momjian <[email protected]>
Mon, 2 Feb 2015 15:00:45 +0000 (10:00 -0500)
committerBruce Momjian <[email protected]>
Mon, 2 Feb 2015 15:00:45 +0000 (10:00 -0500)
Prevent port/snprintf() from overflowing its local fixed-size
buffer and pad to the desired number of digits with zeros, even
if the precision is beyond the ability of the native sprintf().
port/snprintf() is only used on systems that lack a native
snprintf().

Reported by Bruce Momjian. Patch by Tom Lane. Backpatch to all
supported versions.

Security: CVE-2015-0242

src/port/snprintf.c

index c13faeabe5b0200d4b193c2025d14dde62441c35..54e23355f3eb39a485b568db26f257aa55e580d7 100644 (file)
@@ -32,7 +32,9 @@
 
 #include "c.h"
 
+#include <ctype.h>
 #include <limits.h>
+#include <math.h>
 #ifndef WIN32
 #include <sys/ioctl.h>
 #endif
@@ -932,27 +934,80 @@ fmtfloat(double value, char type, int forcesign, int leftjust,
         PrintfTarget *target)
 {
    int         signvalue = 0;
+   int         prec;
    int         vallen;
    char        fmt[32];
-   char        convert[512];
-   int         padlen = 0;     /* amount to pad */
+   char        convert[1024];
+   int         zeropadlen = 0; /* amount to pad with zeroes */
+   int         padlen = 0;     /* amount to pad with spaces */
+
+   /*
+    * We rely on the regular C library's sprintf to do the basic conversion,
+    * then handle padding considerations here.
+    *
+    * The dynamic range of "double" is about 1E+-308 for IEEE math, and not
+    * too wildly more than that with other hardware.  In "f" format, sprintf
+    * could therefore generate at most 308 characters to the left of the
+    * decimal point; while we need to allow the precision to get as high as
+    * 308+17 to ensure that we don't truncate significant digits from very
+    * small values.  To handle both these extremes, we use a buffer of 1024
+    * bytes and limit requested precision to 350 digits; this should prevent
+    * buffer overrun even with non-IEEE math.  If the original precision
+    * request was more than 350, separately pad with zeroes.
+    */
+   if (precision < 0)          /* cover possible overflow of "accum" */
+       precision = 0;
+   prec = Min(precision, 350);
 
-   /* we rely on regular C library's sprintf to do the basic conversion */
    if (pointflag)
-       sprintf(fmt, "%%.%d%c", precision, type);
+   {
+       sprintf(fmt, "%%.%d%c", prec, type);
+       zeropadlen = precision - prec;
+   }
    else
        sprintf(fmt, "%%%c", type);
 
-   if (adjust_sign((value < 0), forcesign, &signvalue))
+   if (!isnan(value) && adjust_sign((value < 0), forcesign, &signvalue))
        value = -value;
 
    vallen = sprintf(convert, fmt, value);
 
-   adjust_padlen(minlen, vallen, leftjust, &padlen);
+   /* If it's infinity or NaN, forget about doing any zero-padding */
+   if (zeropadlen > 0 && !isdigit((unsigned char) convert[vallen - 1]))
+       zeropadlen = 0;
+
+   adjust_padlen(minlen, vallen + zeropadlen, leftjust, &padlen);
 
    leading_pad(zpad, &signvalue, &padlen, target);
 
-   dostr(convert, vallen, target);
+   if (zeropadlen > 0)
+   {
+       /* If 'e' or 'E' format, inject zeroes before the exponent */
+       char       *epos = strrchr(convert, 'e');
+
+       if (!epos)
+           epos = strrchr(convert, 'E');
+       if (epos)
+       {
+           /* pad after exponent */
+           dostr(convert, epos - convert, target);
+           while (zeropadlen-- > 0)
+               dopr_outch('0', target);
+           dostr(epos, vallen - (epos - convert), target);
+       }
+       else
+       {
+           /* no exponent, pad after the digits */
+           dostr(convert, vallen, target);
+           while (zeropadlen-- > 0)
+               dopr_outch('0', target);
+       }
+   }
+   else
+   {
+       /* no zero padding, just emit the number as-is */
+       dostr(convert, vallen, target);
+   }
 
    trailing_pad(&padlen, target);
 }