This is an automated email from the ASF dual-hosted git repository. rmaucher pushed a commit to branch 11.0.x in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit 033db11ff73fdb3920b3b239540e8f8d3c3f37d7 Author: opencode <[email protected]> AuthorDate: Thu Oct 15 13:19:04 2026 +0200 Fix ArrayIndexOutOfBoundsException in IPv6Utils.canonize() Inputs that pass the mayBeIPv6Address() heuristic but contain more than 8 colon-separated groups (e.g. "1:2:3:4:5:6:7:8:9" or forms with more than two consecutive colons), or groups with more than 4 significant characters (e.g. "12345::"), indexed the fixed-size group store out of bounds and threw an undocumented ArrayIndexOutOfBoundsException. With the non-default access log attribute ipv6Canonical="true", such values (e.g. from a client-supplied Host header via the %v element) caused loss of the access log record plus a warning per request. canonize() now returns such malformed inputs unchanged, consistent with how non-IPv6 input is already passed through. Add tests for the pass-through behaviour and for the valid 8-colon forms ("1:2:3:4:5:6:7::") whose canonicalization must be preserved. --- java/org/apache/tomcat/util/net/IPv6Utils.java | 20 ++++++++++++++ test/org/apache/tomcat/util/net/TestIPv6Utils.java | 31 ++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/java/org/apache/tomcat/util/net/IPv6Utils.java b/java/org/apache/tomcat/util/net/IPv6Utils.java index 8e82d1320c..4b4cb50142 100644 --- a/java/org/apache/tomcat/util/net/IPv6Utils.java +++ b/java/org/apache/tomcat/util/net/IPv6Utils.java @@ -90,6 +90,20 @@ public class IPv6Utils { ipv6AddressLength = ipv6Address.lastIndexOf('%'); } + // A valid IPv6 address contains at most 8 colons: 7 separate 8 explicit groups, and 8 are only reached + // when "::" compresses a single zero group next to 7 explicit groups (RFC 4291 allows "::" to stand for + // one or more zero groups). Inputs with more colons than that are not valid IPv6 addresses but the + // mayBeIPv6Address() heuristic above does not exclude them. Return such inputs unchanged. + int colonCount = 0; + for (int i = 0; i < ipv6AddressLength; i++) { + if (ipv6Address.charAt(i) == ':') { + colonCount++; + } + } + if (colonCount > MAX_NUMBER_OF_GROUPS) { + return ipv6Address; + } + StringBuilder result = new StringBuilder(); char[][] groups = new char[MAX_NUMBER_OF_GROUPS][MAX_GROUP_LENGTH]; int groupCounter = 0; @@ -151,6 +165,12 @@ public class IPv6Utils { c = (char) (c + 32); } if (c != ':') { + if (groupCounter >= MAX_NUMBER_OF_GROUPS || charInGroupCounter >= MAX_GROUP_LENGTH) { + // A ninth group with content, or a group with more than 4 significant characters. Neither is a + // valid IPv6 address but the mayBeIPv6Address() heuristic above does not exclude them. Return + // the input unchanged. + return ipv6Address; + } groups[groupCounter][charInGroupCounter] = c; if (!(groupStart && c == '0')) { ++charInGroupCounter; diff --git a/test/org/apache/tomcat/util/net/TestIPv6Utils.java b/test/org/apache/tomcat/util/net/TestIPv6Utils.java index 5d667ed67b..fd909bb9c8 100644 --- a/test/org/apache/tomcat/util/net/TestIPv6Utils.java +++ b/test/org/apache/tomcat/util/net/TestIPv6Utils.java @@ -114,6 +114,12 @@ public class TestIPv6Utils { // Already ends with "::" Assert.assertEquals("2001:db8::", IPv6Utils.canonize("2001:db8::")); + // "::" at the start or end standing for a single zero group next to 7 explicit groups (8 colons) + Assert.assertEquals("1:2:3:4:5:6:7:0", IPv6Utils.canonize("1:2:3:4:5:6:7::")); + Assert.assertEquals("0:2:3:4:5:6:7:8", IPv6Utils.canonize("::2:3:4:5:6:7:8")); + Assert.assertEquals("::", IPv6Utils.canonize("0:0:0:0:0:0:0::")); + Assert.assertEquals("1:2:3:4:5:6:7:0%eth0", IPv6Utils.canonize("1:2:3:4:5:6:7::%eth0")); + // IPv4 inside IPv6 Assert.assertEquals("::ffff:192.0.2.1", IPv6Utils.canonize("::ffff:192.0.2.1")); Assert.assertEquals("::ffff:192.0.2.1", IPv6Utils.canonize("0:0:0:0:0:ffff:192.0.2.1")); @@ -141,4 +147,29 @@ public class TestIPv6Utils { Assert.assertEquals("www.apache.org", IPv6Utils.canonize("www.apache.org")); Assert.assertEquals("ipv6.google.com", IPv6Utils.canonize("ipv6.google.com")); } + + @Test + public void testCanonizeTooManyGroups() { + // Strings with more than 8 groups pass the mayBeIPv6Address() heuristic but are not valid IPv6 + // addresses. They used to trigger an ArrayIndexOutOfBoundsException. The input is now returned + // unchanged. + Assert.assertEquals("1:2:3:4:5:6:7:8:9", IPv6Utils.canonize("1:2:3:4:5:6:7:8:9")); + Assert.assertEquals("1:2:3:4:5:6:7::8:9", IPv6Utils.canonize("1:2:3:4:5:6:7::8:9")); + Assert.assertEquals("1:2:3:4:5:6:7:8:9:10", IPv6Utils.canonize("1:2:3:4:5:6:7:8:9:10")); + Assert.assertEquals("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", + IPv6Utils.canonize("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")); + Assert.assertEquals("1:2:3:4:5:6:7:8:9%1", IPv6Utils.canonize("1:2:3:4:5:6:7:8:9%1")); + // Additional empty groups created by more than two consecutive colons + Assert.assertEquals("1:2:3:4:5:6:7:8:::", IPv6Utils.canonize("1:2:3:4:5:6:7:8:::")); + Assert.assertEquals("::::a:::::", IPv6Utils.canonize("::::a:::::")); + } + + @Test + public void testCanonizeGroupTooLong() { + // A group with more than 4 significant hex digits passes the mayBeIPv6Address() heuristic but is + // not valid IPv6. It used to trigger an ArrayIndexOutOfBoundsException. The input is now returned + // unchanged. + Assert.assertEquals("12345:0:0:0:0:0:0:0", IPv6Utils.canonize("12345:0:0:0:0:0:0:0")); + Assert.assertEquals("12345::", IPv6Utils.canonize("12345::")); + } } --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
