postgres.git / summary / log / commit / refs

commit    fec40878c8702ff4e58241d00a1914d8568bec03
Author:   David Rowley <drowley@postgresql.org>
Date:     Tue Aug 04 06:01:55 2026 +0000

    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 <pl0h0yp1@gmail.com>
    Reported-by: Michael Malis <malis@pgrust.com>
    Reviewed-by: Tristan Partin <tristan@partin.io>
    Reviewed-by: Rafia Sabih <rafia.pghackers@gmail.com>
    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 387bef50d62..29846610309 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 90140f66767..7cebe8d2efb 100644 --- a/src/test/regress/expected/money.out +++ b/src/test/regress/expected/money.out @@ -514,6 +514,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 1eb471d1e4e..6f8f7263a59 100644 --- a/src/test/regress/sql/money.sql +++ b/src/test/regress/sql/money.sql @@ -136,6 +136,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; [parent: 529fc19ccc9c]