Skip to content

Commit 243c76f

Browse files
AlekseiEfimovslowhog
authored andcommitted
8277608: Address IP Addressing
Reviewed-by: dfuchs, rhalade, michaelm
1 parent ddb106b commit 243c76f

File tree

4 files changed

+288
-12
lines changed

4 files changed

+288
-12
lines changed

src/java.base/share/classes/java/net/HostPortrange.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2013, 2022, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -137,7 +137,7 @@ public int hashCode() {
137137
}
138138
this.ipv4 = this.literal = ipv4;
139139
if (ipv4) {
140-
byte[] ip = IPAddressUtil.textToNumericFormatV4(hoststr);
140+
byte[] ip = IPAddressUtil.validateNumericFormatV4(hoststr);
141141
if (ip == null) {
142142
throw new IllegalArgumentException("illegal IPv4 address");
143143
}

src/java.base/share/classes/java/net/InetAddress.java

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1254,7 +1254,11 @@ private String removeComments(String hostsEntry) {
12541254
private byte [] createAddressByteArray(String addrStr) {
12551255
byte[] addrArray;
12561256
// check if IPV4 address - most likely
1257-
addrArray = IPAddressUtil.textToNumericFormatV4(addrStr);
1257+
try {
1258+
addrArray = IPAddressUtil.validateNumericFormatV4(addrStr);
1259+
} catch (IllegalArgumentException iae) {
1260+
return null;
1261+
}
12581262
if (addrArray == null) {
12591263
addrArray = IPAddressUtil.textToNumericFormatV6(addrStr);
12601264
}
@@ -1470,13 +1474,19 @@ public static InetAddress[] getAllByName(String host)
14701474
}
14711475

14721476
// if host is an IP address, we won't do further lookup
1473-
if (Character.digit(host.charAt(0), 16) != -1
1477+
if (IPAddressUtil.digit(host.charAt(0), 16) != -1
14741478
|| (host.charAt(0) == ':')) {
1475-
byte[] addr = null;
1479+
byte[] addr;
14761480
int numericZone = -1;
14771481
String ifname = null;
14781482
// see if it is IPv4 address
1479-
addr = IPAddressUtil.textToNumericFormatV4(host);
1483+
try {
1484+
addr = IPAddressUtil.validateNumericFormatV4(host);
1485+
} catch (IllegalArgumentException iae) {
1486+
var uhe = new UnknownHostException(host);
1487+
uhe.initCause(iae);
1488+
throw uhe;
1489+
}
14801490
if (addr == null) {
14811491
// This is supposed to be an IPv6 literal
14821492
// Check if a numeric or string zone id is present

src/java.base/share/classes/java/net/SocketPermission.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 1997, 2021, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 1997, 2022, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -468,7 +468,7 @@ private void init(String host, int mask) {
468468
if (!host.isEmpty()) {
469469
// see if we are being initialized with an IP address.
470470
char ch = host.charAt(0);
471-
if (ch == ':' || Character.digit(ch, 16) != -1) {
471+
if (ch == ':' || IPAddressUtil.digit(ch, 16) != -1) {
472472
byte ip[] = IPAddressUtil.textToNumericFormatV4(host);
473473
if (ip == null) {
474474
ip = IPAddressUtil.textToNumericFormatV6(host);

src/java.base/share/classes/sun/net/util/IPAddressUtil.java

Lines changed: 270 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2004, 2021, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2004, 2022, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -25,14 +25,16 @@
2525

2626
package sun.net.util;
2727

28-
import java.io.IOException;
28+
import sun.security.action.GetPropertyAction;
29+
2930
import java.io.UncheckedIOException;
3031
import java.net.Inet6Address;
3132
import java.net.InetAddress;
3233
import java.net.InetSocketAddress;
3334
import java.net.NetworkInterface;
3435
import java.net.SocketException;
3536
import java.net.URL;
37+
import java.nio.CharBuffer;
3638
import java.security.AccessController;
3739
import java.security.PrivilegedExceptionAction;
3840
import java.security.PrivilegedActionException;
@@ -100,7 +102,7 @@ public static byte[] textToNumericFormatV4(String src)
100102
tmpValue = 0;
101103
newOctet = true;
102104
} else {
103-
int digit = Character.digit(c, 10);
105+
int digit = digit(c, 10);
104106
if (digit < 0) {
105107
return null;
106108
}
@@ -125,6 +127,29 @@ public static byte[] textToNumericFormatV4(String src)
125127
return res;
126128
}
127129

130+
/**
131+
* Validates if input string is a valid IPv4 address literal.
132+
* If the "jdk.net.allowAmbiguousIPAddressLiterals" system property is set
133+
* to {@code false}, or is not set then validation of the address string is performed as follows:
134+
* If string can't be parsed by following IETF IPv4 address string literals
135+
* formatting style rules (default one), but can be parsed by following BSD formatting
136+
* style rules, the IPv4 address string content is treated as ambiguous and
137+
* {@code IllegalArgumentException} is thrown.
138+
*
139+
* @param src input string
140+
* @return bytes array if string is a valid IPv4 address string
141+
* @throws IllegalArgumentException if "jdk.net.allowAmbiguousIPAddressLiterals" SP is set to
142+
* "false" and IPv4 address string {@code "src"} is ambiguous
143+
*/
144+
public static byte[] validateNumericFormatV4(String src) {
145+
byte[] parsedBytes = textToNumericFormatV4(src);
146+
if (!ALLOW_AMBIGUOUS_IPADDRESS_LITERALS_SP_VALUE
147+
&& parsedBytes == null && isBsdParsableV4(src)) {
148+
throw new IllegalArgumentException("Invalid IP address literal: " + src);
149+
}
150+
return parsedBytes;
151+
}
152+
128153
/*
129154
* Convert IPv6 presentation level address to network order binary form.
130155
* credit:
@@ -170,7 +195,7 @@ public static byte[] textToNumericFormatV6(String src)
170195
val = 0;
171196
while (i < srcb_length) {
172197
ch = srcb[i++];
173-
int chval = Character.digit(ch, 16);
198+
int chval = digit(ch, 16);
174199
if (chval != -1) {
175200
val <<= 4;
176201
val |= chval;
@@ -551,4 +576,245 @@ public static String checkHostString(String host) {
551576
}
552577
return null;
553578
}
579+
580+
/**
581+
* Returns the numeric value of the character {@code ch} in the
582+
* specified radix.
583+
*
584+
* @param ch the character to be converted.
585+
* @param radix the radix.
586+
* @return the numeric value represented by the character in the
587+
* specified radix.
588+
*/
589+
public static int digit(char ch, int radix) {
590+
if (ALLOW_AMBIGUOUS_IPADDRESS_LITERALS_SP_VALUE) {
591+
return Character.digit(ch, radix);
592+
} else {
593+
return parseAsciiDigit(ch, radix);
594+
}
595+
}
596+
597+
/**
598+
* Try to parse String as IPv4 address literal by following
599+
* BSD-style formatting rules.
600+
*
601+
* @param input input string
602+
* @return {@code true} if input string is parsable as IPv4 address literal,
603+
* {@code false} otherwise.
604+
*/
605+
public static boolean isBsdParsableV4(String input) {
606+
char firstSymbol = input.charAt(0);
607+
// Check if first digit is not a decimal digit
608+
if (parseAsciiDigit(firstSymbol, DECIMAL) == -1) {
609+
return false;
610+
}
611+
612+
// Last character is dot OR is not a supported digit: [0-9,A-F,a-f]
613+
char lastSymbol = input.charAt(input.length() - 1);
614+
if (lastSymbol == '.' || parseAsciiHexDigit(lastSymbol) == -1) {
615+
return false;
616+
}
617+
618+
// Parse IP address fields
619+
CharBuffer charBuffer = CharBuffer.wrap(input);
620+
int fieldNumber = 0;
621+
while (charBuffer.hasRemaining()) {
622+
long fieldValue = -1L;
623+
// Try to parse fields in all supported radixes
624+
for (int radix : SUPPORTED_RADIXES) {
625+
fieldValue = parseV4FieldBsd(radix, charBuffer, fieldNumber);
626+
if (fieldValue >= 0) {
627+
fieldNumber++;
628+
break;
629+
} else if (fieldValue == TERMINAL_PARSE_ERROR) {
630+
return false;
631+
}
632+
}
633+
// If field can't be parsed as one of supported radixes stop
634+
// parsing
635+
if (fieldValue < 0) {
636+
return false;
637+
}
638+
}
639+
return true;
640+
}
641+
642+
/**
643+
* Method tries to parse IP address field that starts from {@linkplain CharBuffer#position()
644+
* current position} of the provided character buffer.
645+
* <p>
646+
* This method supports three {@code "radix"} values to decode field values in
647+
* {@code "HEXADECIMAL (radix=16)"}, {@code "DECIMAL (radix=10)"} and
648+
* {@code "OCTAL (radix=8)"} radixes.
649+
* <p>
650+
* If {@code -1} value is returned the char buffer position is reset to the value
651+
* it was before it was called.
652+
* <p>
653+
* Method returns {@code -2} if formatting illegal for all supported {@code radix}
654+
* values is observed, and there is no point in checking other radix values.
655+
* That includes the following cases:<ul>
656+
* <li>Two subsequent dots are observer
657+
* <li>Number of dots more than 3
658+
* <li>Field value exceeds max allowed
659+
* <li>Character is not a valid digit for the requested {@code radix} value, given
660+
* that a field has the radix specific prefix
661+
* </ul>
662+
*
663+
* @param radix digits encoding radix to use for parsing. Valid values: 8, 10, 16.
664+
* @param buffer {@code CharBuffer} with position set to the field's fist character
665+
* @param fieldNumber parsed field number
666+
* @return {@code CANT_PARSE_IN_RADIX} if field can not be parsed in requested {@code radix}.
667+
* {@code TERMINAL_PARSE_ERROR} if field can't be parsed and the whole parse process should be terminated.
668+
* Parsed field value otherwise.
669+
*/
670+
private static long parseV4FieldBsd(int radix, CharBuffer buffer, int fieldNumber) {
671+
int initialPos = buffer.position();
672+
long val = 0;
673+
int digitsCount = 0;
674+
if (!checkPrefix(buffer, radix)) {
675+
val = CANT_PARSE_IN_RADIX;
676+
}
677+
boolean dotSeen = false;
678+
while (buffer.hasRemaining() && val != CANT_PARSE_IN_RADIX && !dotSeen) {
679+
char c = buffer.get();
680+
if (c == '.') {
681+
dotSeen = true;
682+
// Fail if 4 dots in IP address string.
683+
// fieldNumber counter starts from 0, therefore 3
684+
if (fieldNumber == 3) {
685+
// Terminal state, can stop parsing: too many fields
686+
return TERMINAL_PARSE_ERROR;
687+
}
688+
// Check for literals with two dots, like '1.2..3', '1.2.3..'
689+
if (digitsCount == 0) {
690+
// Terminal state, can stop parsing: dot with no digits
691+
return TERMINAL_PARSE_ERROR;
692+
}
693+
if (val > 255) {
694+
// Terminal state, can stop parsing: too big value for an octet
695+
return TERMINAL_PARSE_ERROR;
696+
}
697+
} else {
698+
int dv = parseAsciiDigit(c, radix);
699+
if (dv >= 0) {
700+
digitsCount++;
701+
val *= radix;
702+
val += dv;
703+
} else {
704+
// Spotted digit can't be parsed in the requested 'radix'.
705+
// The order in which radixes are checked - hex, octal, decimal:
706+
// - if symbol is not a valid digit in hex radix - terminal
707+
// - if symbol is not a valid digit in octal radix, and given
708+
// that octal prefix was observed before - terminal
709+
// - if symbol is not a valid digit in decimal radix - terminal
710+
return TERMINAL_PARSE_ERROR;
711+
}
712+
}
713+
}
714+
if (val == CANT_PARSE_IN_RADIX) {
715+
buffer.position(initialPos);
716+
} else if (!dotSeen) {
717+
// It is the last field - check its value
718+
// This check will ensure that address strings with less
719+
// than 4 fields, i.e. A, A.B and A.B.C address types
720+
// contain value less then the allowed maximum for the last field.
721+
long maxValue = (1L << ((4 - fieldNumber) * 8)) - 1;
722+
if (val > maxValue) {
723+
// Terminal state, can stop parsing: last field value exceeds its
724+
// allowed value
725+
return TERMINAL_PARSE_ERROR;
726+
}
727+
}
728+
return val;
729+
}
730+
731+
// This method moves the position of the supplied CharBuffer by analysing the digit prefix
732+
// symbols if any.
733+
// The caller should reset the position when method returns false.
734+
private static boolean checkPrefix(CharBuffer buffer, int radix) {
735+
return switch (radix) {
736+
case OCTAL -> isOctalFieldStart(buffer);
737+
case DECIMAL -> isDecimalFieldStart(buffer);
738+
case HEXADECIMAL -> isHexFieldStart(buffer);
739+
default -> throw new AssertionError("Not supported radix");
740+
};
741+
}
742+
743+
// This method always moves the position of the supplied CharBuffer
744+
// removing the octal prefix symbols '0'.
745+
// The caller should reset the position when method returns false.
746+
private static boolean isOctalFieldStart(CharBuffer cb) {
747+
// .0<EOS> is not treated as octal field
748+
if (cb.remaining() < 2) {
749+
return false;
750+
}
751+
752+
// Fetch two first characters
753+
int position = cb.position();
754+
char first = cb.get();
755+
char second = cb.get();
756+
757+
// Return false if the first char is not octal prefix '0' or second is a
758+
// field separator - parseV4FieldBsd will reset position to start of the field.
759+
// '.0.' fields will be successfully parsed in decimal radix.
760+
boolean isOctalPrefix = first == '0' && second != '.';
761+
762+
// If the prefix looks like octal - consume '0', otherwise 'false' is returned
763+
// and caller will reset the buffer position.
764+
if (isOctalPrefix) {
765+
cb.position(position + 1);
766+
}
767+
return isOctalPrefix;
768+
}
769+
770+
// This method doesn't move the position of the supplied CharBuffer
771+
private static boolean isDecimalFieldStart(CharBuffer cb) {
772+
return cb.hasRemaining();
773+
}
774+
775+
// This method always moves the position of the supplied CharBuffer
776+
// removing the hexadecimal prefix symbols '0x'.
777+
// The caller should reset the position when method returns false.
778+
private static boolean isHexFieldStart(CharBuffer cb) {
779+
if (cb.remaining() < 2) {
780+
return false;
781+
}
782+
char first = cb.get();
783+
char second = cb.get();
784+
return first == '0' && (second == 'x' || second == 'X');
785+
}
786+
787+
// Parse ASCII digit in given radix
788+
private static int parseAsciiDigit(char c, int radix) {
789+
assert radix == OCTAL || radix == DECIMAL || radix == HEXADECIMAL;
790+
if (radix == HEXADECIMAL) {
791+
return parseAsciiHexDigit(c);
792+
}
793+
int val = c - '0';
794+
return (val < 0 || val >= radix) ? -1 : val;
795+
}
796+
797+
// Parse ASCII digit in hexadecimal radix
798+
private static int parseAsciiHexDigit(char digit) {
799+
char c = Character.toLowerCase(digit);
800+
if (c >= 'a' && c <= 'f') {
801+
return c - 'a' + 10;
802+
}
803+
return parseAsciiDigit(c, DECIMAL);
804+
}
805+
806+
// Supported radixes
807+
private static final int HEXADECIMAL = 16;
808+
private static final int DECIMAL = 10;
809+
private static final int OCTAL = 8;
810+
// Order in which field formats are exercised to parse one IP address textual field
811+
private static final int[] SUPPORTED_RADIXES = new int[]{HEXADECIMAL, OCTAL, DECIMAL};
812+
813+
// BSD parser's return values
814+
private final static long CANT_PARSE_IN_RADIX = -1L;
815+
private final static long TERMINAL_PARSE_ERROR = -2L;
816+
817+
private static final String ALLOW_AMBIGUOUS_IPADDRESS_LITERALS_SP = "jdk.net.allowAmbiguousIPAddressLiterals";
818+
private static final boolean ALLOW_AMBIGUOUS_IPADDRESS_LITERALS_SP_VALUE = Boolean.valueOf(
819+
GetPropertyAction.privilegedGetProperty(ALLOW_AMBIGUOUS_IPADDRESS_LITERALS_SP, "false"));
554820
}

0 commit comments

Comments
 (0)