From 86f42357c281bea857dc5c048beb08413622bf3b Mon Sep 17 00:00:00 2001 From: David Rowley Date: Tue, 4 Aug 2026 18:01:10 +1200 Subject: [PATCH] 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 --- src/backend/utils/adt/cash.c | 16 ++++++++++++++++ src/test/regress/expected/money.out | 8 ++++++++ src/test/regress/sql/money.sql | 4 ++++ 3 files changed, 28 insertions(+) diff --git a/src/backend/utils/adt/cash.c b/src/backend/utils/adt/cash.c index b20c358486d..88763f05281 100644 --- a/src/backend/utils/adt/cash.c +++ b/src/backend/utils/adt/cash.c @@ -160,6 +160,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;