From c42c29f590de6b13703d4566cd02fdde290d426d Mon Sep 17 00:00:00 2001
From: Andrey Rachitskiy <pl0h0yp1@gmail.com>
Date: Tue, 1 Sep 2026 23:40:15 +0500
Subject: [PATCH] Fix IPv4 CIDR prefix-length overflow in inet_net_pton.

Parsing /NNN for IPv4 inet and cidr accumulated digits in an int
without a per-step range check.  A long digit string could overflow
to -1, pass the bits > 32 test, and fall through to classful
inference as if no prefix had been given.

Use getbits() with an explicit max prefix length (32 for IPv4, 128
for IPv6) in both inet_cidr_pton_ipv4() and inet_net_pton_ipv4().
RFC 4632 requires the suffix to be a decimal value between 0 and 32.

Bug: #19671
Reported-by: Tianyu Shi <1950233439@qq.com>
Author: Andrey Rachitskiy <pl0h0yp1@gmail.com>
---
 src/backend/utils/adt/inet_net_pton.c | 115 ++++++++++++++++------------------
 src/test/regress/expected/inet.out    |   9 +++
 src/test/regress/sql/inet.sql         |   5 ++
 3 files changed, 68 insertions(+), 61 deletions(-)

diff --git a/src/backend/utils/adt/inet_net_pton.c b/src/backend/utils/adt/inet_net_pton.c
index 3b0db2a3799..f679722f849 100644
--- a/src/backend/utils/adt/inet_net_pton.c
+++ b/src/backend/utils/adt/inet_net_pton.c
@@ -32,11 +32,31 @@ static const char rcsid[] = "Id: inet_net_pton.c,v 1.4.2.3 2004/03/17 00:40:11 m
 #include "utils/builtins.h"		/* needed on some platforms */
 #include "utils/inet.h"
 
+#define INET4_MAXBITS		32	/* RFC 4632 */
+#define INET6_MAXBITS		128 /* RFC 4291 */
+#define INET4_OCTET_MAX		255
+#define INET4_OCTET_BITS	8
+#define NS_INADDRSZ			4
+#define NS_IN6ADDRSZ		16
+#define NS_INT16SZ			2
+
+#define INET4_CLASSB_MIN	128
+#define INET4_CLASSC_MIN	192
+#define INET4_CLASSD_MIN	224
+#define INET4_CLASSE_MIN	240
+
+#define INET4_CLASSA_BITS	8
+#define INET4_CLASSB_BITS	16
+#define INET4_CLASSC_BITS	24
+#define INET4_CLASSD_BITS	8
+#define INET4_CLASSD_MC_BITS 4
+
 
 static int	inet_net_pton_ipv4(const char *src, u_char *dst);
 static int	inet_cidr_pton_ipv4(const char *src, u_char *dst, size_t size);
 static int	inet_net_pton_ipv6(const char *src, u_char *dst);
 static int	inet_cidr_pton_ipv6(const char *src, u_char *dst, size_t size);
+static int	getbits(const char *src, int *bitsp, int maxbits);
 
 
 /*
@@ -149,7 +169,7 @@ inet_cidr_pton_ipv4(const char *src, u_char *dst, size_t size)
 				assert(n >= 0 && n <= 9);
 				tmp *= 10;
 				tmp += n;
-				if (tmp > 255)
+				if (tmp > INET4_OCTET_MAX)
 					goto enoent;
 			} while ((ch = *src++) != '\0' &&
 					 isdigit((unsigned char) ch));
@@ -171,20 +191,9 @@ inet_cidr_pton_ipv4(const char *src, u_char *dst, size_t size)
 	bits = -1;
 	if (ch == '/' && isdigit((unsigned char) src[0]) && dst > odst)
 	{
-		/* CIDR width specifier.  Nothing can follow it. */
-		ch = *src++;			/* Skip over the /. */
-		bits = 0;
-		do
-		{
-			n = strchr(digits, ch) - digits;
-			assert(n >= 0 && n <= 9);
-			bits *= 10;
-			bits += n;
-		} while ((ch = *src++) != '\0' && isdigit((unsigned char) ch));
-		if (ch != '\0')
-			goto enoent;
-		if (bits > 32)
+		if (getbits(src, &bits, INET4_MAXBITS) <= 0)
 			goto emsgsize;
+		ch = '\0';
 	}
 
 	/* Fiery death and destruction unless we prefetched EOS. */
@@ -197,30 +206,29 @@ inet_cidr_pton_ipv4(const char *src, u_char *dst, size_t size)
 	/* If no CIDR spec was given, infer width from net class. */
 	if (bits == -1)
 	{
-		if (*odst >= 240)		/* Class E */
-			bits = 32;
-		else if (*odst >= 224)	/* Class D */
-			bits = 8;
-		else if (*odst >= 192)	/* Class C */
-			bits = 24;
-		else if (*odst >= 128)	/* Class B */
-			bits = 16;
+		if (*odst >= INET4_CLASSE_MIN)
+			bits = INET4_MAXBITS;
+		else if (*odst >= INET4_CLASSD_MIN)
+			bits = INET4_CLASSD_BITS;
+		else if (*odst >= INET4_CLASSC_MIN)
+			bits = INET4_CLASSC_BITS;
+		else if (*odst >= INET4_CLASSB_MIN)
+			bits = INET4_CLASSB_BITS;
 		else
-			/* Class A */
-			bits = 8;
+			bits = INET4_CLASSA_BITS;
 		/* If imputed mask is narrower than specified octets, widen. */
-		if (bits < ((dst - odst) * 8))
-			bits = (dst - odst) * 8;
+		if (bits < ((dst - odst) * INET4_OCTET_BITS))
+			bits = (dst - odst) * INET4_OCTET_BITS;
 
 		/*
 		 * If there are no additional bits specified for a class D address
 		 * adjust bits to 4.
 		 */
-		if (bits == 8 && *odst == 224)
-			bits = 4;
+		if (bits == INET4_CLASSD_BITS && *odst == INET4_CLASSD_MIN)
+			bits = INET4_CLASSD_MC_BITS;
 	}
 	/* Extend network to cover the actual mask. */
-	while (bits > ((dst - odst) * 8))
+	while (bits > ((dst - odst) * INET4_OCTET_BITS))
 	{
 		if (size-- <= 0U)
 			goto emsgsize;
@@ -263,7 +271,7 @@ inet_net_pton_ipv4(const char *src, u_char *dst)
 				ch,
 				tmp,
 				bits;
-	size_t		size = 4;
+	size_t		size = NS_INADDRSZ;
 
 	/* Get the mantissa. */
 	while (ch = *src++, isdigit((unsigned char) ch))
@@ -275,7 +283,7 @@ inet_net_pton_ipv4(const char *src, u_char *dst)
 			assert(n >= 0 && n <= 9);
 			tmp *= 10;
 			tmp += n;
-			if (tmp > 255)
+			if (tmp > INET4_OCTET_MAX)
 				goto enoent;
 		} while ((ch = *src++) != '\0' && isdigit((unsigned char) ch));
 		if (size-- == 0)
@@ -291,20 +299,9 @@ inet_net_pton_ipv4(const char *src, u_char *dst)
 	bits = -1;
 	if (ch == '/' && isdigit((unsigned char) src[0]) && dst > odst)
 	{
-		/* CIDR width specifier.  Nothing can follow it. */
-		ch = *src++;			/* Skip over the /. */
-		bits = 0;
-		do
-		{
-			n = strchr(digits, ch) - digits;
-			assert(n >= 0 && n <= 9);
-			bits *= 10;
-			bits += n;
-		} while ((ch = *src++) != '\0' && isdigit((unsigned char) ch));
-		if (ch != '\0')
-			goto enoent;
-		if (bits > 32)
+		if (getbits(src, &bits, INET4_MAXBITS) <= 0)
 			goto emsgsize;
+		ch = '\0';
 	}
 
 	/* Fiery death and destruction unless we prefetched EOS. */
@@ -314,8 +311,8 @@ inet_net_pton_ipv4(const char *src, u_char *dst)
 	/* Prefix length can default to /32 only if all four octets spec'd. */
 	if (bits == -1)
 	{
-		if (dst - odst == 4)
-			bits = 32;
+		if (dst - odst == NS_INADDRSZ)
+			bits = INET4_MAXBITS;
 		else
 			goto enoent;
 	}
@@ -325,7 +322,7 @@ inet_net_pton_ipv4(const char *src, u_char *dst)
 		goto enoent;
 
 	/* If prefix length overspecifies mantissa, life is bad. */
-	if ((bits / 8) > (dst - odst))
+	if ((bits / INET4_OCTET_BITS) > (dst - odst))
 		goto enoent;
 
 	/* Extend address to four octets. */
@@ -344,7 +341,7 @@ emsgsize:
 }
 
 static int
-getbits(const char *src, int *bitsp)
+getbits(const char *src, int *bitsp, int maxbits)
 {
 	static const char digits[] = "0123456789";
 	int			n;
@@ -364,7 +361,7 @@ getbits(const char *src, int *bitsp)
 				return 0;
 			val *= 10;
 			val += (pch - digits);
-			if (val > 128)		/* range */
+			if (val > maxbits)	/* RFC 4632 / RFC 4291 prefix range */
 				return 0;
 			continue;
 		}
@@ -398,17 +395,17 @@ getv4(const char *src, u_char *dst, int *bitsp)
 				return 0;
 			val *= 10;
 			val += (pch - digits);
-			if (val > 255)		/* range */
+			if (val > INET4_OCTET_MAX)	/* range */
 				return 0;
 			continue;
 		}
 		if (ch == '.' || ch == '/')
 		{
-			if (dst - odst > 3) /* too many octets? */
+			if (dst - odst > NS_INADDRSZ - 1) /* too many octets */
 				return 0;
 			*dst++ = val;
 			if (ch == '/')
-				return getbits(src, bitsp);
+				return getbits(src, bitsp, INET6_MAXBITS);
 			val = 0;
 			n = 0;
 			continue;
@@ -417,7 +414,7 @@ getv4(const char *src, u_char *dst, int *bitsp)
 	}
 	if (n == 0)
 		return 0;
-	if (dst - odst > 3)			/* too many octets? */
+	if (dst - odst > NS_INADDRSZ - 1) /* too many octets */
 		return 0;
 	*dst++ = val;
 	return 1;
@@ -426,13 +423,9 @@ getv4(const char *src, u_char *dst, int *bitsp)
 static int
 inet_net_pton_ipv6(const char *src, u_char *dst)
 {
-	return inet_cidr_pton_ipv6(src, dst, 16);
+	return inet_cidr_pton_ipv6(src, dst, NS_IN6ADDRSZ);
 }
 
-#define NS_IN6ADDRSZ 16
-#define NS_INT16SZ 2
-#define NS_INADDRSZ 4
-
 static int
 inet_cidr_pton_ipv6(const char *src, u_char *dst, size_t size)
 {
@@ -508,7 +501,7 @@ inet_cidr_pton_ipv6(const char *src, u_char *dst, size_t size)
 			saw_xdigit = 0;
 			break;				/* '\0' was seen by inet_pton4(). */
 		}
-		if (ch == '/' && getbits(src, &bits) > 0)
+		if (ch == '/' && getbits(src, &bits, INET6_MAXBITS) > 0)
 			break;
 		goto enoent;
 	}
@@ -520,9 +513,9 @@ inet_cidr_pton_ipv6(const char *src, u_char *dst, size_t size)
 		*tp++ = (u_char) val & 0xff;
 	}
 	if (bits == -1)
-		bits = 128;
+		bits = INET6_MAXBITS;
 
-	endp = tmp + 16;
+	endp = tmp + NS_IN6ADDRSZ;
 
 	if (colonp != NULL)
 	{
diff --git a/src/test/regress/expected/inet.out b/src/test/regress/expected/inet.out
index 1705bff4dd3..2ea6562ca7c 100644
--- a/src/test/regress/expected/inet.out
+++ b/src/test/regress/expected/inet.out
@@ -43,6 +43,15 @@ ERROR:  invalid cidr value: "ffff:ffff:ffff:ffff::/24"
 LINE 1: INSERT INTO INET_TBL (c, i) VALUES (cidr('ffff:ffff:ffff:fff...
                                                  ^
 DETAIL:  Value has bits set to right of mask.
+-- reject invalid IPv4 CIDR prefix lengths (RFC 4632: decimal 0-32)
+SELECT ('10.0.0.0/' || repeat('9', 32))::cidr;
+ERROR:  invalid input syntax for type cidr: "10.0.0.0/99999999999999999999999999999999"
+SELECT ('10.0.0.0/' || repeat('9', 32))::inet;
+ERROR:  invalid input syntax for type inet: "10.0.0.0/99999999999999999999999999999999"
+SELECT '10.0.0.0/033'::cidr;
+ERROR:  invalid input syntax for type cidr: "10.0.0.0/033"
+SELECT '10.0.0.0/033'::inet;
+ERROR:  invalid input syntax for type inet: "10.0.0.0/033"
 SELECT c AS cidr, i AS inet FROM INET_TBL;
         cidr        |       inet       
 --------------------+------------------
diff --git a/src/test/regress/sql/inet.sql b/src/test/regress/sql/inet.sql
index 8f276856df9..922f4653995 100644
--- a/src/test/regress/sql/inet.sql
+++ b/src/test/regress/sql/inet.sql
@@ -29,6 +29,11 @@ INSERT INTO INET_TBL (c, i) VALUES ('1234::1234::1234', '::1.2.3.4');
 -- check that CIDR rejects invalid input when converting from text:
 INSERT INTO INET_TBL (c, i) VALUES (cidr('192.168.1.2/30'), '192.168.1.226');
 INSERT INTO INET_TBL (c, i) VALUES (cidr('ffff:ffff:ffff:ffff::/24'), '::192.168.1.226');
+-- reject invalid IPv4 CIDR prefix lengths (RFC 4632: decimal 0-32)
+SELECT ('10.0.0.0/' || repeat('9', 32))::cidr;
+SELECT ('10.0.0.0/' || repeat('9', 32))::inet;
+SELECT '10.0.0.0/033'::cidr;
+SELECT '10.0.0.0/033'::inet;
 SELECT c AS cidr, i AS inet FROM INET_TBL;
 
 -- now test some support functions
-- 
2.53.0
