postgres.git / summary / log / commit / refs

commit    e640042e909d2881117f2b5298eba3a97693a2b2
Author:   Tom Lane <tgl@sss.pgh.pa.us>
Date:     Mon Dec 01 20:25:12 2014 +0000

    Guard against bad "dscale" values in numeric_recv().
    
    We were not checking to see if the supplied dscale was valid for the given
    digit array when receiving binary-format numeric values.  While dscale can
    validly be more than the number of nonzero fractional digits, it shouldn't
    be less; that case causes fractional digits to be hidden on display even
    though they're there and participate in arithmetic.
    
    Bug #12053 from Tommaso Sala indicates that there's at least one broken
    client library out there that sometimes supplies an incorrect dscale value,
    leading to strange behavior.  This suggests that simply throwing an error
    might not be the best response; it would lead to failures in applications
    that might seem to be working fine today.  What seems the least risky fix
    is to truncate away any digits that would be hidden by dscale.  This
    preserves the existing behavior in terms of what will be printed for the
    transmitted value, while preventing subsequent arithmetic from producing
    results inconsistent with that.
    
    In passing, throw a specific error for the case of dscale being outside
    the range that will fit into a numeric's header.  Before you got "value
    overflows numeric format", which is a bit misleading.
    
    Back-patch to all supported branches.


src/backend/utils/adt/numeric.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c index c6b00ef238c..aee9488509f 100644 --- a/src/backend/utils/adt/numeric.c +++ b/src/backend/utils/adt/numeric.c @@ -656,6 +656,8 @@ numeric_recv(PG_FUNCTION_ARGS) alloc_var(&value, len); value.weight = (int16) pq_getmsgint(buf, sizeof(int16)); + /* we allow any int16 for weight --- OK? */ + value.sign = (uint16) pq_getmsgint(buf, sizeof(uint16)); if (!(value.sign == NUMERIC_POS || value.sign == NUMERIC_NEG || @@ -665,6 +667,11 @@ numeric_recv(PG_FUNCTION_ARGS) errmsg("invalid sign in external \"numeric\" value"))); value.dscale = (uint16) pq_getmsgint(buf, sizeof(uint16)); + if ((value.dscale & NUMERIC_DSCALE_MASK) != value.dscale) + ereport(ERROR, + (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION), + errmsg("invalid scale in external \"numeric\" value"))); + for (i = 0; i < len; i++) { NumericDigit d = pq_getmsgint(buf, sizeof(NumericDigit)); @@ -676,6 +683,14 @@ numeric_recv(PG_FUNCTION_ARGS) value.digits[i] = d; } + /* + * If the given dscale would hide any digits, truncate those digits away. + * We could alternatively throw an error, but that would take a bunch of + * extra code (about as much as trunc_var involves), and it might cause + * client compatibility issues. + */ + trunc_var(&value, value.dscale); + apply_typmod(&value, typmod); res = make_result(&value); [parent: 7b46ef1dad84]