From c42c29f590de6b13703d4566cd02fdde290d426d Mon Sep 17 00:00:00 2001 From: Andrey Rachitskiy 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 --- 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