Skip to content

Commit 288c25f

Browse files
committed
Fix #61700: FILTER_FLAG_IPV6/FILTER_FLAG_NO_PRIV|RES_RANGE failing
It makes no sense to compare IPv6 address ranges as strings; there are too many different representation possibilities. Instead, we change `_php_filter_validate_ipv6()` so that it can calculate the IP address as integer array. We do not rely on `inet_pton()` which may not be available everywhere, at least IPv6 support may not, but rather parse the IP address manually. Finally, we compare the integers. Note that this patch does not fix what we consider as reserved and private, respectively, but merely tries to keep what we had so far. Co-authored-by: Nikita Popov <nikita.ppv@gmail.com> Closes GH-7476.
1 parent 49c9fbb commit 288c25f

File tree

4 files changed

+82
-54
lines changed

4 files changed

+82
-54
lines changed

NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ PHP NEWS
1313
- Fileinfo:
1414
. Fixed bug #78987 (High memory usage during encoding detection). (Anatol)
1515

16+
- Filter:
17+
. Fixed bug #61700 (FILTER_FLAG_IPV6/FILTER_FLAG_NO_PRIV|RES_RANGE failing).
18+
(cmb, Nikita)
19+
1620
- PCRE:
1721
. Fixed bug #81424 (PCRE2 10.35 JIT performance regression). (cmb)
1822

ext/filter/logical_filters.c

Lines changed: 59 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@
9191
#define FORMAT_IPV4 4
9292
#define FORMAT_IPV6 6
9393

94-
static int _php_filter_validate_ipv6(char *str, size_t str_len);
94+
static int _php_filter_validate_ipv6(char *str, size_t str_len, int ip[8]);
9595

9696
static int php_filter_parse_int(const char *str, size_t str_len, zend_long *ret) { /* {{{ */
9797
zend_long ctx_value;
@@ -609,7 +609,7 @@ void php_filter_validate_url(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */
609609
t = e - 1;
610610

611611
/* An IPv6 enclosed by square brackets is a valid hostname */
612-
if (*s == '[' && *t == ']' && _php_filter_validate_ipv6((s + 1), l - 2)) {
612+
if (*s == '[' && *t == ']' && _php_filter_validate_ipv6((s + 1), l - 2, NULL)) {
613613
php_url_free(url);
614614
return;
615615
}
@@ -749,11 +749,11 @@ static int _php_filter_validate_ipv4(char *str, size_t str_len, int *ip) /* {{{
749749
}
750750
/* }}} */
751751

752-
static int _php_filter_validate_ipv6(char *str, size_t str_len) /* {{{ */
752+
static int _php_filter_validate_ipv6(char *str, size_t str_len, int ip[8]) /* {{{ */
753753
{
754-
int compressed = 0;
754+
int compressed_pos = -1;
755755
int blocks = 0;
756-
int n;
756+
int num, n, i;
757757
char *ipv4;
758758
char *end;
759759
int ip4elm[4];
@@ -796,35 +796,67 @@ static int _php_filter_validate_ipv6(char *str, size_t str_len) /* {{{ */
796796
return 0;
797797
}
798798
if (*str == ':') {
799-
if (compressed) {
799+
if (compressed_pos >= 0) {
800800
return 0;
801801
}
802-
blocks++; /* :: means 1 or more 16-bit 0 blocks */
803-
compressed = 1;
804-
802+
if (ip && blocks < 8) {
803+
ip[blocks] = -1;
804+
}
805+
compressed_pos = blocks++; /* :: means 1 or more 16-bit 0 blocks */
805806
if (++str == end) {
806-
return (blocks <= 8);
807+
if (blocks > 8) {
808+
return 0;
809+
}
810+
goto fixup_ip;
807811
}
808812
} else if ((str - 1) == s) {
809813
/* don't allow leading : without another : following */
810814
return 0;
811815
}
812816
}
813-
n = 0;
814-
while ((str < end) &&
815-
((*str >= '0' && *str <= '9') ||
816-
(*str >= 'a' && *str <= 'f') ||
817-
(*str >= 'A' && *str <= 'F'))) {
817+
num = n = 0;
818+
while (str < end) {
819+
if (*str >= '0' && *str <= '9') {
820+
num = 16 * num + (*str - '0');
821+
} else if (*str >= 'a' && *str <= 'f') {
822+
num = 16 * num + (*str - 'a') + 10;
823+
} else if (*str >= 'A' && *str <= 'F') {
824+
num = 16 * num + (*str - 'A') + 10;
825+
} else {
826+
break;
827+
}
818828
n++;
819829
str++;
820830
}
831+
if (ip && blocks < 8) {
832+
ip[blocks] = num;
833+
}
821834
if (n < 1 || n > 4) {
822835
return 0;
823836
}
824837
if (++blocks > 8)
825838
return 0;
826839
}
827-
return ((compressed && blocks <= 8) || blocks == 8);
840+
841+
fixup_ip:
842+
if (ip && ipv4) {
843+
for (i = 0; i < 5; i++) {
844+
ip[i] = 0;
845+
}
846+
ip[i++] = 0xffff;
847+
ip[i++] = 256 * ip4elm[0] + ip4elm[1];
848+
ip[i++] = 256 * ip4elm[2] + ip4elm[3];
849+
} else if (ip && compressed_pos >= 0 && blocks <= 8) {
850+
int offset = 8 - blocks;
851+
for (i = 7; i > compressed_pos + offset; i--) {
852+
ip[i] = ip[i - offset];
853+
}
854+
for (i = compressed_pos + offset; i >= compressed_pos; i--) {
855+
ip[i] = 0;
856+
}
857+
}
858+
859+
return (compressed_pos >= 0 && blocks <= 8) || blocks == 8;
828860
}
829861
/* }}} */
830862

@@ -835,7 +867,7 @@ void php_filter_validate_ip(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */
835867
* allow_ipv4 and allow_ipv6 flags flag are used, then the first dot or
836868
* colon determine the format */
837869

838-
int ip[4];
870+
int ip[8];
839871
int mode;
840872

841873
if (memchr(Z_STRVAL_P(value), ':', Z_STRLEN_P(value))) {
@@ -886,49 +918,25 @@ void php_filter_validate_ip(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */
886918
case FORMAT_IPV6:
887919
{
888920
int res = 0;
889-
res = _php_filter_validate_ipv6(Z_STRVAL_P(value), Z_STRLEN_P(value));
921+
res = _php_filter_validate_ipv6(Z_STRVAL_P(value), Z_STRLEN_P(value), ip);
890922
if (res < 1) {
891923
RETURN_VALIDATION_FAILED
892924
}
893925
/* Check flags */
894926
if (flags & FILTER_FLAG_NO_PRIV_RANGE) {
895-
if (Z_STRLEN_P(value) >=2 && (!strncasecmp("FC", Z_STRVAL_P(value), 2) || !strncasecmp("FD", Z_STRVAL_P(value), 2))) {
927+
if (ip[0] >= 0xfc00 && ip[0] <= 0xfdff) {
896928
RETURN_VALIDATION_FAILED
897929
}
898930
}
899931
if (flags & FILTER_FLAG_NO_RES_RANGE) {
900-
switch (Z_STRLEN_P(value)) {
901-
case 1: case 0:
902-
break;
903-
case 2:
904-
if (!strcmp("::", Z_STRVAL_P(value))) {
905-
RETURN_VALIDATION_FAILED
906-
}
907-
break;
908-
case 3:
909-
if (!strcmp("::1", Z_STRVAL_P(value)) || !strcmp("5f:", Z_STRVAL_P(value))) {
910-
RETURN_VALIDATION_FAILED
911-
}
912-
break;
913-
default:
914-
if (Z_STRLEN_P(value) >= 5) {
915-
if (
916-
!strncasecmp("fe8", Z_STRVAL_P(value), 3) ||
917-
!strncasecmp("fe9", Z_STRVAL_P(value), 3) ||
918-
!strncasecmp("fea", Z_STRVAL_P(value), 3) ||
919-
!strncasecmp("feb", Z_STRVAL_P(value), 3)
920-
) {
921-
RETURN_VALIDATION_FAILED
922-
}
923-
}
924-
if (
925-
(Z_STRLEN_P(value) >= 9 && !strncasecmp("2001:0db8", Z_STRVAL_P(value), 9)) ||
926-
(Z_STRLEN_P(value) >= 2 && !strncasecmp("5f", Z_STRVAL_P(value), 2)) ||
927-
(Z_STRLEN_P(value) >= 4 && !strncasecmp("3ff3", Z_STRVAL_P(value), 4)) ||
928-
(Z_STRLEN_P(value) >= 8 && !strncasecmp("2001:001", Z_STRVAL_P(value), 8))
929-
) {
930-
RETURN_VALIDATION_FAILED
931-
}
932+
if ((ip[0] == 0 && ip[1] == 0 && ip[2] == 0 && ip[3] == 0
933+
&& ip[4] == 0 && ip[5] == 0 && ip[6] == 0 && (ip[7] == 0 || ip[7] == 1))
934+
|| (ip[0] == 0x5f)
935+
|| (ip[0] >= 0xfe80 && ip[0] <= 0xfebf)
936+
|| ((ip[0] == 0x2001 && ip[1] == 0x0db8) || (ip[1] >= 0x0010 && ip[1] <= 0x001f))
937+
|| (ip[0] == 0x3ff3)
938+
) {
939+
RETURN_VALIDATION_FAILED
932940
}
933941
}
934942
}

ext/filter/tests/bug47435.phpt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ var_dump(filter_var("::", FILTER_VALIDATE_IP, FILTER_FLAG_IPV6));
1010
var_dump(filter_var("::", FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 | FILTER_FLAG_NO_RES_RANGE));
1111
var_dump(filter_var("::1", FILTER_VALIDATE_IP, FILTER_FLAG_IPV6));
1212
var_dump(filter_var("::1", FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 | FILTER_FLAG_NO_RES_RANGE));
13-
var_dump(filter_var("fe8:5:6::1", FILTER_VALIDATE_IP, FILTER_FLAG_IPV6));
14-
var_dump(filter_var("fe8:5:6::1", FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 | FILTER_FLAG_NO_RES_RANGE));
13+
var_dump(filter_var("fe80:5:6::1", FILTER_VALIDATE_IP, FILTER_FLAG_IPV6));
14+
var_dump(filter_var("fe80:5:6::1", FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 | FILTER_FLAG_NO_RES_RANGE));
1515
var_dump(filter_var("2001:0db8::1", FILTER_VALIDATE_IP, FILTER_FLAG_IPV6));
1616
var_dump(filter_var("2001:0db8::1", FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 | FILTER_FLAG_NO_RES_RANGE));
1717
var_dump(filter_var("5f::1", FILTER_VALIDATE_IP, FILTER_FLAG_IPV6));
@@ -26,7 +26,7 @@ string(2) "::"
2626
bool(false)
2727
string(3) "::1"
2828
bool(false)
29-
string(10) "fe8:5:6::1"
29+
string(11) "fe80:5:6::1"
3030
bool(false)
3131
string(12) "2001:0db8::1"
3232
bool(false)

ext/filter/tests/bug61700.phpt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
--TEST--
2+
Bug #61700 (FILTER_FLAG_IPV6/FILTER_FLAG_NO_PRIV|RES_RANGE failing)
3+
--SKIPIF--
4+
<?php
5+
if (!extension_loaded("filter")) die("skip filter extension not available");
6+
?>
7+
--FILE--
8+
<?php
9+
var_dump(filter_var('::ffff:192.168.1.1', FILTER_VALIDATE_IP, FILTER_FLAG_IPV4));
10+
var_dump(filter_var('::ffff:192.168.1.1', FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE));
11+
var_dump(filter_var('0:0:0:0:0:0:0:1', FILTER_VALIDATE_IP, FILTER_FLAG_NO_RES_RANGE));
12+
?>
13+
--EXPECT--
14+
bool(false)
15+
string(18) "::ffff:192.168.1.1"
16+
bool(false)

0 commit comments

Comments
 (0)