aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2014-12-01 15:25:15 -0500
committerTom Lane <tgl@sss.pgh.pa.us>2014-12-01 15:25:15 -0500
commit2e3cc39556ee2288d9fe4919b5537b6bcb56a6fc (patch)
tree2e681ca2c54f51280fbe01430d98c3a3b47aaca2
parentc2be18c333bc09bae27d6a518677232d6dbcb45d (diff)
downloadpostgresql-2e3cc39556ee2288d9fe4919b5537b6bcb56a6fc.tar.gz
postgresql-2e3cc39556ee2288d9fe4919b5537b6bcb56a6fc.zip
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.
-rw-r--r--src/backend/utils/adt/numeric.c15
1 files changed, 15 insertions, 0 deletions
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index 2ebfcb6e6f7..0ad1178a551 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -655,6 +655,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 ||
@@ -664,6 +666,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));
@@ -675,6 +682,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);