From 29725b3db67ad3f09da1a7fb6690737d2f8d6c0a Mon Sep 17 00:00:00 2001 From: Bruce Momjian Date: Mon, 2 Feb 2015 10:00:45 -0500 Subject: [PATCH] port/snprintf(): fix overflow and do padding 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 | 69 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 62 insertions(+), 7 deletions(-) diff --git a/src/port/snprintf.c b/src/port/snprintf.c index c13faeabe5..54e23355f3 100644 --- a/src/port/snprintf.c +++ b/src/port/snprintf.c @@ -32,7 +32,9 @@ #include "c.h" +#include #include +#include #ifndef WIN32 #include #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); }