BUG #19672: int8shl/int8shr Undefined Behavior on Out-of-Range Shift Amounts

From: PG Bug reporting form <noreply(at)postgresql(dot)org>
To: pgsql-bugs(at)lists(dot)postgresql(dot)org
Cc: 1950233439(at)qq(dot)com
Subject: BUG #19672: int8shl/int8shr Undefined Behavior on Out-of-Range Shift Amounts
Date: 2026-09-07 14:10:42
Message-ID: 19672-33e7d9d1be2229ea@postgresql.org
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-bugs

The following bug has been logged on the website:

Bug reference: 19672
Logged by: Tianyu Shi
Email address: 1950233439(at)qq(dot)com
PostgreSQL version: 19beta3
Operating system: Ubuntu22.04
Description:

### Summary

`int8shl()` and `int8shr()` in `src/backend/utils/adt/int8.c` (lines
1255–1270) apply `arg1 << arg2` and `arg1 >> arg2` directly on `int64`
without validating the shift amount `arg2`. Under C11 §6.5.7, shifting by a
negative count, by a count ≥ 64, or left-shifting a signed value into
overflow are all undefined behavior. Every other bigint arithmetic operator
in PostgreSQL (`+`, `-`, `*`, unary `-`) raises `ERROR: bigint out of range`
on overflow, making the shift operators the sole exception and creating a
semantic inconsistency that can silently corrupt permission bitmasks or
financial calculations.

### PoC

Pure SQL — any authenticated database user can trigger the issue without
special privileges.

```sql
-- Shift count >= bit width: returns 1 instead of 0 or ERROR
SELECT 1::bigint << 64;

-- Negative shift count: returns INT64_MIN instead of ERROR
SELECT 1::bigint << -1;

-- Left-shift MAX_INT64 by 1: silently returns -2 instead of raising "bigint
out of range"
SELECT 9223372036854775807::bigint << 1;

-- Differential: multiplication raises a proper error for the same value
SELECT 9223372036854775807::bigint * 2;
-- ERROR: bigint out of range

-- Negative right-shift: returns 0 instead of ERROR
SELECT 1::bigint >> -1;

-- Right-shift count >= bit width: returns 1 instead of 0 or ERROR
SELECT 1::bigint >> 64;
```

```bash
# Initialize and start a UBSAN-instrumented PostgreSQL instance
./build/bin/initdb -D pgdata --no-locale -E UTF8
mkdir -p ./build/run
./build/bin/pg_ctl -D pgdata -l pgdata/postgres.log \
-o "-p 55433 -k ./build/run" start

# Run the PoC queries (Test 4 will error; that is expected)
./build/bin/psql -h ./build/run -p 55433 postgres <<'EOF'
SELECT 1::bigint << 64;
SELECT 1::bigint << -1;
SELECT 9223372036854775807::bigint << 1;
SELECT 9223372036854775807::bigint * 2;
SELECT 1::bigint >> -1;
SELECT 1::bigint >> 64;
EOF

# Inspect server log for UBSAN output
grep -E "runtime error|UndefinedBehaviorSanitizer|SUMMARY"
pgdata/postgres.log

./build/bin/pg_ctl -D pgdata stop
```

### Result

UndefinedBehaviorSanitizer fired in the PostgreSQL server log at both shift
sites: `int8.c:1260:2: runtime error: shift exponent 64 is too large for
64-bit type 'int64' (aka 'long')` and `int8.c:1269:2: runtime error: shift
exponent -1 is negative`. The SQL output confirms wrong values: `1::bigint
<< 64` returns `1` (expected `0` or error), `1::bigint << -1` returns
`-9223372036854775808` (expected error), and `9223372036854775807::bigint <<
1` returns `-2` (expected `ERROR: bigint out of range`). The differential
confirms the inconsistency: `9223372036854775807::bigint * 2` correctly
raises `ERROR: bigint out of range`, while the semantically equivalent
left-shift silently wraps.

Browse pgsql-bugs by date

  From Date Subject
Next Message PG Bug reporting form 2026-09-07 14:12:15 BUG #19673: inetmi_int8 Signed Integer Overflow Returns Wrong IPv6 Address
Previous Message PG Bug reporting form 2026-09-07 14:09:09 BUG #19671: IPv4 CIDR Prefix Integer Overflow Bypasses Validation in inet/cidr Casts