Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 063c0f6

Browse files
committed
Disallow bits beyond the mask length for CIDR values, per discussion
on pghackers. Arrange for the sort ordering of general INET values to be network part as major sort key, host part as minor sort key. I did not force an initdb for this change, but anyone who's running indexes on general INET values may need to recreate those indexes.
1 parent 2f35b4e commit 063c0f6

File tree

3 files changed

+66
-32
lines changed

3 files changed

+66
-32
lines changed

src/backend/utils/adt/network.c

+50-16
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* is for IP V4 CIDR notation, but prepared for V6: just
44
* add the necessary bits where the comments indicate.
55
*
6-
* $Header: /cvsroot/pgsql/src/backend/utils/adt/network.c,v 1.24 2000/08/03 23:07:46 tgl Exp $
6+
* $Header: /cvsroot/pgsql/src/backend/utils/adt/network.c,v 1.25 2000/10/27 01:52:15 tgl Exp $
77
*
88
* Jon Postel RIP 16 Oct 1998
99
*/
@@ -20,8 +20,9 @@
2020
#include "utils/inet.h"
2121

2222

23-
static int v4bitncmp(unsigned int a1, unsigned int a2, int bits);
2423
static int32 network_cmp_internal(inet *a1, inet *a2);
24+
static int v4bitncmp(unsigned long a1, unsigned long a2, int bits);
25+
static bool v4addressOK(unsigned long a1, int bits);
2526

2627
/*
2728
* Access macros. Add IPV6 support.
@@ -50,14 +51,29 @@ network_in(char *src, int type)
5051
inet *dst;
5152

5253
dst = (inet *) palloc(VARHDRSZ + sizeof(inet_struct));
54+
/* make sure any unused bits in a CIDR value are zeroed */
55+
MemSet(dst, 0, VARHDRSZ + sizeof(inet_struct));
5356

5457
/* First, try for an IP V4 address: */
5558
ip_family(dst) = AF_INET;
5659
bits = inet_net_pton(ip_family(dst), src, &ip_v4addr(dst),
5760
type ? ip_addrsize(dst) : -1);
5861
if ((bits < 0) || (bits > 32))
62+
{
5963
/* Go for an IPV6 address here, before faulting out: */
60-
elog(ERROR, "could not parse \"%s\"", src);
64+
elog(ERROR, "invalid %s value '%s'",
65+
type ? "CIDR" : "INET", src);
66+
}
67+
68+
/*
69+
* Error check: CIDR values must not have any bits set beyond the masklen.
70+
* XXX this code not IPV6 ready.
71+
*/
72+
if (type)
73+
{
74+
if (! v4addressOK(ip_v4addr(dst), bits))
75+
elog(ERROR, "invalid CIDR value '%s': width too small", src);
76+
}
6177

6278
VARATT_SIZEP(dst) = VARHDRSZ
6379
+ ((char *) &ip_v4addr(dst) - (char *) VARDATA(dst))
@@ -128,24 +144,29 @@ cidr_out(PG_FUNCTION_ARGS)
128144
/*
129145
* Basic comparison function for sorting and inet/cidr comparisons.
130146
*
131-
* XXX this ignores bits to the right of the mask. That's probably
132-
* correct for CIDR, almost certainly wrong for INET. We need to have
133-
* two sets of comparator routines, not just one. Note that suggests
134-
* that CIDR and INET should not be considered binary-equivalent by
135-
* the parser?
147+
* Comparison is first on the common bits of the network part, then on
148+
* the length of the network part, and then on the whole unmasked address.
149+
* The effect is that the network part is the major sort key, and for
150+
* equal network parts we sort on the host part. Note this is only sane
151+
* for CIDR if address bits to the right of the mask are guaranteed zero;
152+
* otherwise logically-equal CIDRs might compare different.
136153
*/
137154

138155
static int32
139156
network_cmp_internal(inet *a1, inet *a2)
140157
{
141158
if (ip_family(a1) == AF_INET && ip_family(a2) == AF_INET)
142159
{
143-
int order = v4bitncmp(ip_v4addr(a1), ip_v4addr(a2),
144-
Min(ip_bits(a1), ip_bits(a2)));
160+
int order;
145161

162+
order = v4bitncmp(ip_v4addr(a1), ip_v4addr(a2),
163+
Min(ip_bits(a1), ip_bits(a2)));
164+
if (order != 0)
165+
return order;
166+
order = ((int) ip_bits(a1)) - ((int) ip_bits(a2));
146167
if (order != 0)
147168
return order;
148-
return ((int32) ip_bits(a1)) - ((int32) ip_bits(a2));
169+
return v4bitncmp(ip_v4addr(a1), ip_v4addr(a2), 32);
149170
}
150171
else
151172
{
@@ -455,13 +476,11 @@ network_netmask(PG_FUNCTION_ARGS)
455476
*/
456477

457478
static int
458-
v4bitncmp(unsigned int a1, unsigned int a2, int bits)
479+
v4bitncmp(unsigned long a1, unsigned long a2, int bits)
459480
{
460-
unsigned long mask = 0;
461-
int i;
481+
unsigned long mask;
462482

463-
for (i = 0; i < bits; i++)
464-
mask = (mask >> 1) | 0x80000000;
483+
mask = (0xFFFFFFFFL << (32 - bits)) & 0xFFFFFFFFL;
465484
a1 = ntohl(a1);
466485
a2 = ntohl(a2);
467486
if ((a1 & mask) < (a2 & mask))
@@ -470,3 +489,18 @@ v4bitncmp(unsigned int a1, unsigned int a2, int bits)
470489
return (1);
471490
return (0);
472491
}
492+
493+
/*
494+
* Returns true if given address fits fully within the specified bit width.
495+
*/
496+
static bool
497+
v4addressOK(unsigned long a1, int bits)
498+
{
499+
unsigned long mask;
500+
501+
mask = (0xFFFFFFFFL << (32 - bits)) & 0xFFFFFFFFL;
502+
a1 = ntohl(a1);
503+
if ((a1 & mask) == a1)
504+
return true;
505+
return false;
506+
}

src/test/regress/expected/inet.out

+13-15
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ DROP TABLE INET_TBL;
66
ERROR: table "inet_tbl" does not exist
77
CREATE TABLE INET_TBL (c cidr, i inet);
88
INSERT INTO INET_TBL (c, i) VALUES ('192.168.1', '192.168.1.226/24');
9-
INSERT INTO INET_TBL (c, i) VALUES ('192.168.1.2/24', '192.168.1.226');
9+
INSERT INTO INET_TBL (c, i) VALUES ('192.168.1.0/24', '192.168.1.226');
1010
INSERT INTO INET_TBL (c, i) VALUES ('10', '10.1.2.3/8');
1111
INSERT INTO INET_TBL (c, i) VALUES ('10.0.0.0', '10.1.2.3/8');
1212
INSERT INTO INET_TBL (c, i) VALUES ('10.1.2.3', '10.1.2.3/32');
@@ -15,6 +15,9 @@ INSERT INTO INET_TBL (c, i) VALUES ('10.1', '10.1.2.3/16');
1515
INSERT INTO INET_TBL (c, i) VALUES ('10', '10.1.2.3/8');
1616
INSERT INTO INET_TBL (c, i) VALUES ('10', '11.1.2.3/8');
1717
INSERT INTO INET_TBL (c, i) VALUES ('10', '9.1.2.3/8');
18+
-- check that CIDR rejects invalid input:
19+
INSERT INTO INET_TBL (c, i) VALUES ('192.168.1.2/24', '192.168.1.226');
20+
ERROR: invalid CIDR value '192.168.1.2/24': width too small
1821
SELECT '' AS ten, c AS cidr, i AS inet FROM INET_TBL;
1922
ten | cidr | inet
2023
-----+--------------+------------------
@@ -107,15 +110,10 @@ SELECT '' AS four, c AS cidr, masklen(c) AS "masklen(cidr)",
107110

108111
SELECT '' AS six, c AS cidr, i AS inet FROM INET_TBL
109112
WHERE c = i;
110-
six | cidr | inet
111-
-----+--------------+------------------
112-
| 192.168.1/24 | 192.168.1.226/24
113-
| 10/8 | 10.1.2.3/8
114-
| 10.1.2.3/32 | 10.1.2.3
115-
| 10.1.2/24 | 10.1.2.3/24
116-
| 10.1/16 | 10.1.2.3/16
117-
| 10/8 | 10.1.2.3/8
118-
(6 rows)
113+
six | cidr | inet
114+
-----+-------------+----------
115+
| 10.1.2.3/32 | 10.1.2.3
116+
(1 row)
119117

120118
SELECT '' AS ten, i, c,
121119
i < c AS lt, i <= c AS le, i = c AS eq,
@@ -125,14 +123,14 @@ SELECT '' AS ten, i, c,
125123
FROM INET_TBL;
126124
ten | i | c | lt | le | eq | ge | gt | ne | sb | sbe | sup | spe
127125
-----+------------------+--------------+----+----+----+----+----+----+----+-----+-----+-----
128-
| 192.168.1.226/24 | 192.168.1/24 | f | t | t | t | f | f | f | t | f | t
126+
| 192.168.1.226/24 | 192.168.1/24 | f | f | f | t | t | t | f | t | f | t
129127
| 192.168.1.226 | 192.168.1/24 | f | f | f | t | t | t | t | t | f | f
130-
| 10.1.2.3/8 | 10/8 | f | t | t | t | f | f | f | t | f | t
128+
| 10.1.2.3/8 | 10/8 | f | f | f | t | t | t | f | t | f | t
131129
| 10.1.2.3/8 | 10.0.0.0/32 | t | t | f | f | f | t | f | f | t | t
132130
| 10.1.2.3 | 10.1.2.3/32 | f | t | t | t | f | f | f | t | f | t
133-
| 10.1.2.3/24 | 10.1.2/24 | f | t | t | t | f | f | f | t | f | t
134-
| 10.1.2.3/16 | 10.1/16 | f | t | t | t | f | f | f | t | f | t
135-
| 10.1.2.3/8 | 10/8 | f | t | t | t | f | f | f | t | f | t
131+
| 10.1.2.3/24 | 10.1.2/24 | f | f | f | t | t | t | f | t | f | t
132+
| 10.1.2.3/16 | 10.1/16 | f | f | f | t | t | t | f | t | f | t
133+
| 10.1.2.3/8 | 10/8 | f | f | f | t | t | t | f | t | f | t
136134
| 11.1.2.3/8 | 10/8 | f | f | f | t | t | t | f | f | f | f
137135
| 9.1.2.3/8 | 10/8 | t | t | f | f | f | t | f | f | f | f
138136
(10 rows)

src/test/regress/sql/inet.sql

+3-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
DROP TABLE INET_TBL;
88
CREATE TABLE INET_TBL (c cidr, i inet);
99
INSERT INTO INET_TBL (c, i) VALUES ('192.168.1', '192.168.1.226/24');
10-
INSERT INTO INET_TBL (c, i) VALUES ('192.168.1.2/24', '192.168.1.226');
10+
INSERT INTO INET_TBL (c, i) VALUES ('192.168.1.0/24', '192.168.1.226');
1111
INSERT INTO INET_TBL (c, i) VALUES ('10', '10.1.2.3/8');
1212
INSERT INTO INET_TBL (c, i) VALUES ('10.0.0.0', '10.1.2.3/8');
1313
INSERT INTO INET_TBL (c, i) VALUES ('10.1.2.3', '10.1.2.3/32');
@@ -16,6 +16,8 @@ INSERT INTO INET_TBL (c, i) VALUES ('10.1', '10.1.2.3/16');
1616
INSERT INTO INET_TBL (c, i) VALUES ('10', '10.1.2.3/8');
1717
INSERT INTO INET_TBL (c, i) VALUES ('10', '11.1.2.3/8');
1818
INSERT INTO INET_TBL (c, i) VALUES ('10', '9.1.2.3/8');
19+
-- check that CIDR rejects invalid input:
20+
INSERT INTO INET_TBL (c, i) VALUES ('192.168.1.2/24', '192.168.1.226');
1921

2022
SELECT '' AS ten, c AS cidr, i AS inet FROM INET_TBL;
2123

0 commit comments

Comments
 (0)