BUG #19671: IPv4 CIDR Prefix Integer Overflow Bypasses Validation in inet/cidr Casts
Hackorum builds and tests every patch posted to the lists, not only commitfest submissions. This is Hackorum's own CI rather than the PostgreSQL project's, and it is still under testing - please report anything that looks wrong.
You can run a PostgreSQL built from this patch straight from Docker, with no checkout and no build:
docker run --rm -p 5432:5432 ghcr.io/hackorum-dev/postgres-patch:t253747psql -h localhost -U postgresBuilt from patchset v2 (message #2), September 19, 2026 at 02:17 PM.
Every patchset is also pushed to a branch of our PostgreSQL fork, so you can check out the same tree CI built. Without a PostgreSQL checkout:
git clone --branch t253747_2 https://github.com/hackorum-dev/postgres.gitIn a checkout you already have, add the fork once:
git remote add hackorum https://github.com/hackorum-dev/postgres.gitthen, for this patchset and every later one:
git fetch hackorum t253747_2 && git checkout t253747_2Patchset v2 (message #2) is on t253747_2
The following bug has been logged on the website:
Bug reference: 19671
Logged by: Tianyu Shi
Email address: 1950233439@qq.com
PostgreSQL version: 19beta3
Operating system: Ubuntu22.04
Description:
### Summary
In `src/backend/utils/adt/inet_net_pton.c`, both `inet_cidr_pton_ipv4()`
(lines 177–188) and `inet_net_pton_ipv4()` (lines 296–308) accumulate the
CIDR prefix length digit-by-digit with no per-digit overflow guard, allowing
a 32-bit signed `int bits` to wrap silently on inputs such as `4294967297`
(2³²+1 → 1). The post-loop check `if (bits > 32) goto emsgsize` then sees
the wrapped value and passes it, causing any non-privileged SQL user to
store `inet`/`cidr` values with silently corrupted prefix lengths. An
attacker who can INSERT into a table with an `inet`/`cidr` column, or supply
a cast literal, can produce entries whose stored masklen differs arbitrarily
from what was written, potentially bypassing application-layer ACL logic
built on PostgreSQL subnet-containment operators.
### PoC
No superuser required; any user able to execute a `SELECT` or `INSERT` with
an `inet`/`cidr` cast is sufficient.
```sql
\set ON_ERROR_STOP off
-- Test 1: inet cast — 4294967297 = 2^32+1 wraps to 1; should ERROR but does
not
SELECT masklen('1.2.3.4/4294967297'::inet) AS actual_masklen;
-- Observed: 1 Expected: ERROR
-- Test 2: cidr cast — 4294967296 = 2^32 wraps to 0; should ERROR but does
not
SELECT masklen('0.0.0.0/4294967296'::cidr) AS actual_masklen;
-- Observed: 0 Expected: ERROR
-- Control: legitimate out-of-range /33 is correctly rejected
SELECT masklen('1.2.3.4/33'::inet) AS should_error;
-- Observed: ERROR: invalid input syntax for type inet: "1.2.3.4/33"
-- Stored value demonstration
SELECT host('1.2.3.4/4294967297'::inet) AS inet_host,
masklen('1.2.3.4/4294967297'::inet) AS inet_masklen_actual,
1 AS inet_masklen_expected;
-- Returns: 1.2.3.4 | 1 | 1 (value accepted and stored with wrong prefix)
```
### Result
- `'1.2.3.4/4294967297'::inet` — expected `ERROR: invalid mask length`;
actual `masklen() = 1` (2³²+1 wraps to 1, bypass confirmed).
- `'0.0.0.0/4294967296'::cidr` — expected `ERROR: invalid mask length`;
actual `masklen() = 0` (2³² wraps to 0, bypass confirmed).
- `'1.2.3.4/33'::inet` — correctly raises `ERROR: invalid input syntax for
type inet: "1.2.3.4/33"` (normal in-range bound check works).
The asymmetry demonstrates that only the integer-overflow path escapes
validation: overflowing prefix literals are silently accepted and stored
with a wrong (wrapped) prefix length, while a straightforward out-of-range
value is rejected. Any application relying on `<<` / `<<=` subnet
comparisons against stored `inet`/`cidr` values is exposed to logic bypass
via entries whose effective mask is broader than intended.
чт, 10 сент. 2026 г. в 17:11, PG Bug reporting form <noreply@postgresql.org
:
The following bug has been logged on the website:
Bug reference: 19671
Logged by: Tianyu Shi
Email address: 1950233439@qq.com
PostgreSQL version: 19beta3
Operating system: Ubuntu22.04
Description:### Summary
In `src/backend/utils/adt/inet_net_pton.c`, both `inet_cidr_pton_ipv4()`
(lines 177–188) and `inet_net_pton_ipv4()` (lines 296–308) accumulate the
CIDR prefix length digit-by-digit with no per-digit overflow guard,
allowing
a 32-bit signed `int bits` to wrap silently on inputs such as `4294967297`
(2³²+1 → 1). The post-loop check `if (bits > 32) goto emsgsize` then sees
the wrapped value and passes it, causing any non-privileged SQL user to
store `inet`/`cidr` values with silently corrupted prefix lengths. An
attacker who can INSERT into a table with an `inet`/`cidr` column, or
supply
a cast literal, can produce entries whose stored masklen differs
arbitrarily
from what was written, potentially bypassing application-layer ACL logic
built on PostgreSQL subnet-containment operators.### PoC
No superuser required; any user able to execute a `SELECT` or `INSERT` with
an `inet`/`cidr` cast is sufficient.```sql
\set ON_ERROR_STOP off-- Test 1: inet cast — 4294967297 = 2^32+1 wraps to 1; should ERROR but
does
not
SELECT masklen('1.2.3.4/4294967297'::inet) AS actual_masklen;
-- Observed: 1 Expected: ERROR-- Test 2: cidr cast — 4294967296 = 2^32 wraps to 0; should ERROR but does
not
SELECT masklen('0.0.0.0/4294967296'::cidr) AS actual_masklen;
-- Observed: 0 Expected: ERROR-- Control: legitimate out-of-range /33 is correctly rejected
SELECT masklen('1.2.3.4/33'::inet) AS should_error;
-- Observed: ERROR: invalid input syntax for type inet: "1.2.3.4/33"-- Stored value demonstration
SELECT host('1.2.3.4/4294967297'::inet) AS inet_host,
masklen('1.2.3.4/4294967297'::inet) AS inet_masklen_actual,
1 AS inet_masklen_expected;
-- Returns: 1.2.3.4 | 1 | 1 (value accepted and stored with wrong prefix)
```### Result
- `'1.2.3.4/4294967297'::inet` <http://1.2.3.4/4294967297'::inet> —
expected `ERROR: invalid mask length`;
actual `masklen() = 1` (2³²+1 wraps to 1, bypass confirmed).
- `'0.0.0.0/4294967296'::cidr` <http://0.0.0.0/4294967296'::cidr> —
expected `ERROR: invalid mask length`;
actual `masklen() = 0` (2³² wraps to 0, bypass confirmed).
- `'1.2.3.4/33'::inet` <http://1.2.3.4/33'::inet> — correctly raises
`ERROR: invalid input syntax for
type inet: "1.2.3.4/33"` (normal in-range bound check works).The asymmetry demonstrates that only the integer-overflow path escapes
validation: overflowing prefix literals are silently accepted and stored
with a wrong (wrapped) prefix length, while a straightforward out-of-range
value is rejected. Any application relying on `<<` / `<<=` subnet
comparisons against stored `inet`/`cidr` values is exposed to logic bypass
via entries whose effective mask is broader than intended.
Hi!
Thnx for the report.
I've already encountered this problem, I just never got around to making a
report.
Fix in attachment.
Andrey Rachitskiy <pl0h0yp1@gmail.com> writes:
чт, 10 сент. 2026 г. в 17:11, PG Bug reporting form <noreply@postgresql.org
:
In `src/backend/utils/adt/inet_net_pton.c`, both `inet_cidr_pton_ipv4()`
(lines 177–188) and `inet_net_pton_ipv4()` (lines 296–308) accumulate the
CIDR prefix length digit-by-digit with no per-digit overflow guard,
allowing
a 32-bit signed `int bits` to wrap silently on inputs such as `4294967297`
(2³²+1 → 1).
I've already encountered this problem, I just never got around to making a
report.
Fix in attachment.
In a post-scarcity world, I might be interested in fixing edge-case
problems like this (and the adjacent bug reports), but as things are
it's a waste of extremely limited developer time. The argument that
not rejecting garbage input somehow has security consequences is
laughable --- if an attacker has control over data you intend to use
for security-critical purposes, they hardly need to resort to putting
in syntactically-invalid values to cause trouble. I don't foresee
real-world users putting in this sort of data in the first place,
which explains why nobody ever noticed until they could put AI to work
on finding this kind of case.
regards, tom lane
On 10 Sep 2026, at 18:58, Tom Lane <tgl@sss.pgh.pa.us> wrote:
In a post-scarcity world, I might be interested in fixing edge-case
problems like this (and the adjacent bug reports), but as things are
it's a waste of extremely limited developer time.
+1. The main reason to fix these types of issues is to avoid getting umpteen
duplicate reports of this over the coming weeks/months/year, and that really
doesn't seem to fit the bill of improving Postgres for actual users with real
world usecases.
--
Daniel Gustafsson
чт, 10 сент. 2026 г., 21:58 Tom Lane <tgl@sss.pgh.pa.us>:
Andrey Rachitskiy <pl0h0yp1@gmail.com> writes:
чт, 10 сент. 2026 г. в 17:11, PG Bug reporting form <
noreply@postgresql.org
:
In `src/backend/utils/adt/inet_net_pton.c`, both `inet_cidr_pton_ipv4()`
(lines 177–188) and `inet_net_pton_ipv4()` (lines 296–308) accumulatethe
CIDR prefix length digit-by-digit with no per-digit overflow guard,
allowing
a 32-bit signed `int bits` to wrap silently on inputs such as`4294967297`
(2³²+1 → 1).
I've already encountered this problem, I just never got around to making
a
report.
Fix in attachment.In a post-scarcity world, I might be interested in fixing edge-case
problems like this (and the adjacent bug reports), but as things are
it's a waste of extremely limited developer time. The argument that
not rejecting garbage input somehow has security consequences is
laughable --- if an attacker has control over data you intend to use
for security-critical purposes, they hardly need to resort to putting
in syntactically-invalid values to cause trouble. I don't foresee
real-world users putting in this sort of data in the first place,
which explains why nobody ever noticed until they could put AI to work
on finding this kind of case.
Dear Tom,
I completely agree with everything said above. Should add that I didn't
post report it for the same reasons, but since someone already "bug" wrote
it, I already had the patch.
---
Regards,
Rachitskiy Andrey
I completely agree with everything said above. Should add that I didn't post report it for the same reasons, but since someone already "bug" wrote it, I already had the patch.
I would argue that you did the right thing, now the patch is archived and
avialable for future use and reference, so thank you for that.
--
Daniel Gustafsson