Skip to content

Commit

Permalink
Fix #61700: FILTER_FLAG_IPV6/FILTER_FLAG_NO_PRIV|RES_RANGE failing
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
cmb69 committed Sep 20, 2021
1 parent 49c9fbb commit 288c25f
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 54 deletions.
4 changes: 4 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ PHP NEWS
- Fileinfo:
. Fixed bug #78987 (High memory usage during encoding detection). (Anatol)

- Filter:
. Fixed bug #61700 (FILTER_FLAG_IPV6/FILTER_FLAG_NO_PRIV|RES_RANGE failing).
(cmb, Nikita)

- PCRE:
. Fixed bug #81424 (PCRE2 10.35 JIT performance regression). (cmb)

Expand Down
110 changes: 59 additions & 51 deletions ext/filter/logical_filters.c
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@
#define FORMAT_IPV4 4
#define FORMAT_IPV6 6

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

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

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

static int _php_filter_validate_ipv6(char *str, size_t str_len) /* {{{ */
static int _php_filter_validate_ipv6(char *str, size_t str_len, int ip[8]) /* {{{ */
{
int compressed = 0;
int compressed_pos = -1;
int blocks = 0;
int n;
int num, n, i;
char *ipv4;
char *end;
int ip4elm[4];
Expand Down Expand Up @@ -796,35 +796,67 @@ static int _php_filter_validate_ipv6(char *str, size_t str_len) /* {{{ */
return 0;
}
if (*str == ':') {
if (compressed) {
if (compressed_pos >= 0) {
return 0;
}
blocks++; /* :: means 1 or more 16-bit 0 blocks */
compressed = 1;

if (ip && blocks < 8) {
ip[blocks] = -1;
}
compressed_pos = blocks++; /* :: means 1 or more 16-bit 0 blocks */
if (++str == end) {
return (blocks <= 8);
if (blocks > 8) {
return 0;
}
goto fixup_ip;
}
} else if ((str - 1) == s) {
/* don't allow leading : without another : following */
return 0;
}
}
n = 0;
while ((str < end) &&
((*str >= '0' && *str <= '9') ||
(*str >= 'a' && *str <= 'f') ||
(*str >= 'A' && *str <= 'F'))) {
num = n = 0;
while (str < end) {
if (*str >= '0' && *str <= '9') {
num = 16 * num + (*str - '0');
} else if (*str >= 'a' && *str <= 'f') {
num = 16 * num + (*str - 'a') + 10;
} else if (*str >= 'A' && *str <= 'F') {
num = 16 * num + (*str - 'A') + 10;
} else {
break;
}
n++;
str++;
}
if (ip && blocks < 8) {
ip[blocks] = num;
}
if (n < 1 || n > 4) {
return 0;
}
if (++blocks > 8)
return 0;
}
return ((compressed && blocks <= 8) || blocks == 8);

fixup_ip:
if (ip && ipv4) {
for (i = 0; i < 5; i++) {
ip[i] = 0;
}
ip[i++] = 0xffff;
ip[i++] = 256 * ip4elm[0] + ip4elm[1];
ip[i++] = 256 * ip4elm[2] + ip4elm[3];
} else if (ip && compressed_pos >= 0 && blocks <= 8) {
int offset = 8 - blocks;
for (i = 7; i > compressed_pos + offset; i--) {
ip[i] = ip[i - offset];
}
for (i = compressed_pos + offset; i >= compressed_pos; i--) {
ip[i] = 0;
}
}

return (compressed_pos >= 0 && blocks <= 8) || blocks == 8;
}
/* }}} */

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

int ip[4];
int ip[8];
int mode;

if (memchr(Z_STRVAL_P(value), ':', Z_STRLEN_P(value))) {
Expand Down Expand Up @@ -886,49 +918,25 @@ void php_filter_validate_ip(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */
case FORMAT_IPV6:
{
int res = 0;
res = _php_filter_validate_ipv6(Z_STRVAL_P(value), Z_STRLEN_P(value));
res = _php_filter_validate_ipv6(Z_STRVAL_P(value), Z_STRLEN_P(value), ip);
if (res < 1) {
RETURN_VALIDATION_FAILED
}
/* Check flags */
if (flags & FILTER_FLAG_NO_PRIV_RANGE) {
if (Z_STRLEN_P(value) >=2 && (!strncasecmp("FC", Z_STRVAL_P(value), 2) || !strncasecmp("FD", Z_STRVAL_P(value), 2))) {
if (ip[0] >= 0xfc00 && ip[0] <= 0xfdff) {
RETURN_VALIDATION_FAILED
}
}
if (flags & FILTER_FLAG_NO_RES_RANGE) {
switch (Z_STRLEN_P(value)) {
case 1: case 0:
break;
case 2:
if (!strcmp("::", Z_STRVAL_P(value))) {
RETURN_VALIDATION_FAILED
}
break;
case 3:
if (!strcmp("::1", Z_STRVAL_P(value)) || !strcmp("5f:", Z_STRVAL_P(value))) {
RETURN_VALIDATION_FAILED
}
break;
default:
if (Z_STRLEN_P(value) >= 5) {
if (
!strncasecmp("fe8", Z_STRVAL_P(value), 3) ||
!strncasecmp("fe9", Z_STRVAL_P(value), 3) ||
!strncasecmp("fea", Z_STRVAL_P(value), 3) ||
!strncasecmp("feb", Z_STRVAL_P(value), 3)
) {
RETURN_VALIDATION_FAILED
}
}
if (
(Z_STRLEN_P(value) >= 9 && !strncasecmp("2001:0db8", Z_STRVAL_P(value), 9)) ||
(Z_STRLEN_P(value) >= 2 && !strncasecmp("5f", Z_STRVAL_P(value), 2)) ||
(Z_STRLEN_P(value) >= 4 && !strncasecmp("3ff3", Z_STRVAL_P(value), 4)) ||
(Z_STRLEN_P(value) >= 8 && !strncasecmp("2001:001", Z_STRVAL_P(value), 8))
) {
RETURN_VALIDATION_FAILED
}
if ((ip[0] == 0 && ip[1] == 0 && ip[2] == 0 && ip[3] == 0
&& ip[4] == 0 && ip[5] == 0 && ip[6] == 0 && (ip[7] == 0 || ip[7] == 1))
|| (ip[0] == 0x5f)
|| (ip[0] >= 0xfe80 && ip[0] <= 0xfebf)
|| ((ip[0] == 0x2001 && ip[1] == 0x0db8) || (ip[1] >= 0x0010 && ip[1] <= 0x001f))
|| (ip[0] == 0x3ff3)
) {
RETURN_VALIDATION_FAILED
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions ext/filter/tests/bug47435.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ var_dump(filter_var("::", FILTER_VALIDATE_IP, FILTER_FLAG_IPV6));
var_dump(filter_var("::", FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 | FILTER_FLAG_NO_RES_RANGE));
var_dump(filter_var("::1", FILTER_VALIDATE_IP, FILTER_FLAG_IPV6));
var_dump(filter_var("::1", FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 | FILTER_FLAG_NO_RES_RANGE));
var_dump(filter_var("fe8:5:6::1", FILTER_VALIDATE_IP, FILTER_FLAG_IPV6));
var_dump(filter_var("fe8:5:6::1", FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 | FILTER_FLAG_NO_RES_RANGE));
var_dump(filter_var("fe80:5:6::1", FILTER_VALIDATE_IP, FILTER_FLAG_IPV6));
var_dump(filter_var("fe80:5:6::1", FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 | FILTER_FLAG_NO_RES_RANGE));
var_dump(filter_var("2001:0db8::1", FILTER_VALIDATE_IP, FILTER_FLAG_IPV6));
var_dump(filter_var("2001:0db8::1", FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 | FILTER_FLAG_NO_RES_RANGE));
var_dump(filter_var("5f::1", FILTER_VALIDATE_IP, FILTER_FLAG_IPV6));
Expand All @@ -26,7 +26,7 @@ string(2) "::"
bool(false)
string(3) "::1"
bool(false)
string(10) "fe8:5:6::1"
string(11) "fe80:5:6::1"
bool(false)
string(12) "2001:0db8::1"
bool(false)
Expand Down
16 changes: 16 additions & 0 deletions ext/filter/tests/bug61700.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
--TEST--
Bug #61700 (FILTER_FLAG_IPV6/FILTER_FLAG_NO_PRIV|RES_RANGE failing)
--SKIPIF--
<?php
if (!extension_loaded("filter")) die("skip filter extension not available");
?>
--FILE--
<?php
var_dump(filter_var('::ffff:192.168.1.1', FILTER_VALIDATE_IP, FILTER_FLAG_IPV4));
var_dump(filter_var('::ffff:192.168.1.1', FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE));
var_dump(filter_var('0:0:0:0:0:0:0:1', FILTER_VALIDATE_IP, FILTER_FLAG_NO_RES_RANGE));
?>
--EXPECT--
bool(false)
string(18) "::ffff:192.168.1.1"
bool(false)

0 comments on commit 288c25f

Please sign in to comment.