From: David Rowley Date: Tue, 4 Aug 2026 05:59:13 +0000 (+1200) Subject: Fix missing money overflow checks for INT64_MIN / -1 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=b4dfae2ffac25ea6caf116091b5ed15e140ddfc0;p=thirdparty%2Fpostgresql.git Fix missing money overflow checks for INT64_MIN / -1 Similar to what 1f7cb5c30 did for the INT types, protect against overflow when dividing the lowest possible money value by -1. This cannot be represented on a two's complement machine. Without this check, the result depends on the machine, and in the worst case, could result in a crash. With the fix installed, this will now result in: ERROR: money out of range Bug: #19585 Author: Andrey Rachitskiy Reported-by: Michael Malis Reviewed-by: Tristan Partin Reviewed-by: Rafia Sabih Discussion: https://postgr.es/m/19586-bb603bf5ad9934dd%40postgresql.org Discussion: https://postgr.es/m/CAB8bMisnXJVXte6s3kUOpuuAY9%3D9kehG6MMX-%2BTQoFsSGan22Q%40mail.gmail.com Backpatch-through: 14 --- diff --git a/src/backend/utils/adt/cash.c b/src/backend/utils/adt/cash.c index 4bf60085c61..310b3bb3fca 100644 --- a/src/backend/utils/adt/cash.c +++ b/src/backend/utils/adt/cash.c @@ -161,6 +161,22 @@ cash_div_int64(Cash c, int64 i) (errcode(ERRCODE_DIVISION_BY_ZERO), errmsg("division by zero"))); + /* + * INT64_MIN / -1 is problematic, since the result can't be represented on + * a two's-complement machine. Some machines produce INT64_MIN, some + * produce zero, some throw an exception. We can dodge the problem by + * recognizing that division by -1 is the same as negation. + */ + if (i == -1) + { + if (unlikely(c == PG_INT64_MIN)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("money out of range"))); + return -c; + } + + /* No overflow is possible */ return c / i; } diff --git a/src/test/regress/expected/money.out b/src/test/regress/expected/money.out index cc2ff4d96e8..e7fdb9b7d0d 100644 --- a/src/test/regress/expected/money.out +++ b/src/test/regress/expected/money.out @@ -539,6 +539,14 @@ SELECT '-1'::money / 1.175494e-38::float4; ERROR: money out of range SELECT '92233720368547758.07'::money * 2::int4; ERROR: money out of range +SELECT '-92233720368547758.08'::money * -1::int8; +ERROR: money out of range +SELECT '-92233720368547758.08'::money / -1::int8; +ERROR: money out of range +SELECT '-92233720368547758.08'::money / -1::int4; +ERROR: money out of range +SELECT '-92233720368547758.08'::money / -1::int2; +ERROR: money out of range SELECT '1'::money / 0::int2; ERROR: division by zero SELECT '42'::money * 'inf'::float8; diff --git a/src/test/regress/sql/money.sql b/src/test/regress/sql/money.sql index b888ec21c30..d769a211090 100644 --- a/src/test/regress/sql/money.sql +++ b/src/test/regress/sql/money.sql @@ -142,6 +142,10 @@ SELECT '-92233720368547758.08'::money - '0.01'::money; SELECT '92233720368547758.07'::money * 2::float8; SELECT '-1'::money / 1.175494e-38::float4; SELECT '92233720368547758.07'::money * 2::int4; +SELECT '-92233720368547758.08'::money * -1::int8; +SELECT '-92233720368547758.08'::money / -1::int8; +SELECT '-92233720368547758.08'::money / -1::int4; +SELECT '-92233720368547758.08'::money / -1::int2; SELECT '1'::money / 0::int2; SELECT '42'::money * 'inf'::float8; SELECT '42'::money * '-inf'::float8;