BUG #19671: IPv4 CIDR Prefix Integer Overflow Bypasses Validation in inet/cidr Casts

Started by PG Bug reporting form13 days ago6 messagesbugs
Beta feature

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.

appliestests failedCI history

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:t253747
psql -h localhost -U postgres

Built 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.git

In a checkout you already have, add the fork once:

git remote add hackorum https://github.com/hackorum-dev/postgres.git

then, for this patchset and every later one:

git fetch hackorum t253747_2 && git checkout t253747_2

Patchset v2 (message #2) is on t253747_2

Jump to latest
#1PG 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` — 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.

#2Andrey Rachitskiy
pl0h0yp1@gmail.com
In reply to: PG Bug reporting form (#1)
Re: BUG #19671: IPv4 CIDR Prefix Integer Overflow Bypasses Validation in inet/cidr Casts

чт, 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&#39;::inet&gt;
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&#39;::cidr&gt;
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&#39;::inet&gt; — 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.

Attachments:

t253747_2
0001-Fix-IPv4-CIDR-prefix-length-overflow-in-inet_net_pto.patchtext/x-patch; charset=US-ASCII; name=0001-Fix-IPv4-CIDR-prefix-length-overflow-in-inet_net_pto.patchDownload+68-62
#3Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andrey Rachitskiy (#2)
Re: BUG #19671: IPv4 CIDR Prefix Integer Overflow Bypasses Validation in inet/cidr Casts

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

#4Daniel Gustafsson
daniel@yesql.se
In reply to: Tom Lane (#3)
Re: BUG #19671: IPv4 CIDR Prefix Integer Overflow Bypasses Validation in inet/cidr Casts

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

#5Andrey Rachitskiy
pl0h0yp1@gmail.com
In reply to: Tom Lane (#3)
Re: BUG #19671: IPv4 CIDR Prefix Integer Overflow Bypasses Validation in inet/cidr Casts

чт, 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) 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.

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

#6Daniel Gustafsson
daniel@yesql.se
In reply to: Andrey Rachitskiy (#5)
Re: BUG #19671: IPv4 CIDR Prefix Integer Overflow Bypasses Validation in inet/cidr Casts

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