summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorstilez <stilez@users.noreply.github.com>2015-12-23 09:46:43 +0000
committerstilez <stilez@users.noreply.github.com>2015-12-23 09:46:43 +0000
commita2fd89dd074da284d19797d731213f7862814925 (patch)
treeaa5258cc7ef0d515c859a10734b0d0390f19586e /src
parent5a2512b3cdb249e3eeda8e480dfbac80deb37e7d (diff)
downloadpfsense-a2fd89dd074da284d19797d731213f7862814925.zip
pfsense-a2fd89dd074da284d19797d731213f7862814925.tar.gz
REBASE of #1786 and #1788, tightening three IP functions
Resubmit of two PRs that couldn't be merged due to basecode conflicts is_linklocal() - tightened and made correctly IPv4/v6 agnostic per RFCs is_literalipaddrv6() - simplified is_hostnamewithport() - simplified IS_LINKLOCAL() is_linklocal has a few issues, including validating as linklocal, addresses that aren't linklocal according to RFC 4291, validating as a linklocal address input that could contain arbitrary text/no validation of reasonableness on any %(scope/interface) present, and appearing from its function name to be suitable for all linklocal addresses but actually not IPv4/v6 agnostic. 1) IPv4/6 agnostic: while IPv4 linklocal testing isn't much needed, not it should probably be recognised because some code handling linklocal may reasonably expect is_linklocal() to be IPv4/IPv6 agnostic. 2) For IPv6, it tests at least, that the purported scope/interface is [0-9a-z]+ otherwise user input or other text such as "fe80::%\n;ARBIRARYTEXT;" would be validated as a linklocal address and inserted into pf and perhaps other places without further detection, leading to possible vulnerabilities. Also tests scope/interface for a reasonable length of <= 64 chars "just in case". But it doesn't test more than this (and probably should test for valid scope/interface if present). 3) Follows RFC 4291 exactly: IPv6 linklocal isn't just "fe80::", it requires the rest of the first 64 bits to be zero too. The RFC defines it as '1111111010' + 54 zeros (Ref: https://tools.ietf.org/html/rfc4291#section-2.5.6 ) 4) Returns 4 or 6 to give a more exact response to the calling function as to whether the match was an IPv4 linklocal or IPv6 linklocal address (both evaluate to True for Boolean test purposes such as "if (is_linklocal(...))") Note: Net_IPv6::_Ip2Bin() can return shorter binary strings for IPv4 or "junk" input. So this code tests that it returned a 128 bit length, which ensure it was meaningful IPv6. IS_HOSTNAMEWITHPORT() simplified - we don't need to pop() or assign a new variable just to test 2nd member of the array IS_LITERALIPADDRV6() simplified - we don't need an expensive preg_match() to test if it's a valid IPv6 wrapped in "[" ... "]"
Diffstat (limited to 'src')
-rw-r--r--src/etc/inc/util.inc47
1 files changed, 33 insertions, 14 deletions
diff --git a/src/etc/inc/util.inc b/src/etc/inc/util.inc
index b542566..533baff 100644
--- a/src/etc/inc/util.inc
+++ b/src/etc/inc/util.inc
@@ -698,9 +698,31 @@ function is_ipaddrv4($ipaddr) {
return true;
}
-/* returns true if $ipaddr is a valid IPv6 linklocal address */
+/* returns 4 or 6 respectively (== TRUE) if $ipaddr is a valid IPv4 or IPv6 linklocal address
+ returns '' if not a valid linklocal address
+ TODO: does not attempt to validate any IPv6 %scope (if present) other than to check [0-9a-z_] and non-pathological max length 64 */
function is_linklocal($ipaddr) {
- return (strtolower(substr($ipaddr, 0, 5)) == "fe80:");
+ if (is_string($ipaddr)) {
+ // distinguish possible IPv4 and IPv6 using a 'cheap' test first
+ if (strpos($ipaddr, ":") === false) {
+ // input is IPv4 or bad data
+ // test if it's 169.254.1.0 - 169.254.254.255 per rfc3927 2.1
+ // following is probably 'cheaper' than using preg_match
+ $ip4 = explode(".", $ipaddr);
+ if (count($ip4) == 4 && $ip4[0] == '169' && $ip4[1] == '254' && ctype_digit($ip[2]) &&
+ $ip4[2] >= 1 && $ip4[2] <= 254 && ctype_digit($ip4[3]) && $ip4[3] <= 255) {
+ return 4;
+ }
+ } elseif (preg_match('/^([0-9a-f:]{2,39})(%([0-9a-z_]{1,64}))?$/i', $ipaddr, $ip6)) {
+ // IPv6: test whether valid IPv6 and first 64 bits are '1111111010' + 54 zero bits (fe80::) per rfc4291 2.5.6
+ // we don't attempt any validation on scope data captured in $ip6[3] (if any) so long as plausible
+ // Net_IPv6::_Ip2Bin() can return a shorter binary string for non-IPv6/bad data so also test we got 128 bits returned
+ $ipbin = Net_IPv6::_Ip2Bin($ip6[1]);
+ if (strlen($ipbin) == 128 && substr($ipbin,0,64) == '1111111010' . str_repeat('0',54))
+ return 6;
+ }
+ }
+ return '';
}
/* returns scope of a linklocal address */
@@ -714,16 +736,14 @@ function get_ll_scope($addr) {
/* returns true if $ipaddr is a valid literal IPv6 address */
function is_literalipaddrv6($ipaddr) {
- if (preg_match("/\[([0-9a-f:]+)\]/i", $ipaddr, $match)) {
- $ipaddr = $match[1];
- } else {
- return false;
+ if (substr($ipaddr,0,1) == '[' && substr($ipaddr,-1,1) == ']') {
+ // if it's data wrapped in "[ ... ]" then test if middle part is valid IPv6
+ return is_ipaddrv6(substr($ipaddr,1,-1));
}
-
- return is_ipaddrv6($ipaddr);
+ return false;
}
-/* returns true if $iport is a valid IPv4/IPv6 address + port
+/* returns true if $iport is a valid IPv4:port or [Literal IPv6]:port
false - not valid
true (numeric 4 or 6) - if valid, gives type of address */
function is_ipaddrwithport($ipport) {
@@ -748,12 +768,11 @@ function is_ipaddrwithport($ipport) {
function is_hostnamewithport($hostport) {
$parts = explode(":", $hostport);
- $port = array_pop($parts);
- if (count($parts) == 1) {
- return is_hostname($parts[0]) && is_port($port);
- } else {
- return false;
+ // no need to validate with is_string(); if it's not a string then explode won't return 2 parts anyway
+ if (count($parts) == 2) {
+ return is_hostname($parts[0]) && is_port($parts[1]);
}
+ return false;
}
/* returns true if $ipaddr is a valid dotted IPv4 address or an alias thereof */
OpenPOWER on IntegriCloud