From bb441f3445f7b437c630ebd098d309f1ec8aaa4a Mon Sep 17 00:00:00 2001 From: Cindy Krafft Date: Thu, 24 Sep 2026 02:45:20 +0000 Subject: [PATCH 1/2] Tighten the InetAddress check to IP literals The pattern that decides whether a string may be passed to InetAddress.getByName also matched strings that getByName does not parse as literals, such as "300.1.1.1" or "zz:1". getByName hands those to the system resolver, which is the lookup the check is meant to avoid. Limit each IPv4 part to 0-255 (leading zeros are still accepted, as getByName accepts them), and require an IPv6 candidate to start with a hex digit, ':' or '['; getByName parses such strings as literals and rejects invalid ones without a lookup. Fixes #3128 Claude-Session: https://claude.ai/code/session_01X59fWmqnA4Nmb9rggkTP7n --- .../gson/internal/bind/TypeAdapters.java | 14 ++++++---- .../DefaultInetAddressTypeAdapterTest.java | 26 +++++++++++++++++++ 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java b/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java index 45f9536b54..2937a20f5d 100644 --- a/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java +++ b/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java @@ -761,11 +761,15 @@ public void write(JsonWriter out, URI value) throws IOException { public static final TypeAdapter INET_ADDRESS = new TypeAdapter() { - // A pattern that matches every IP address and no DNS address. It matches plenty of things - // that aren't either of those, which is fine. An IPv4 address is n.n.n.n where each n is a - // non-negative integer. An IPv6 address contains at least one colon. (There are further - // constraints in both cases, but they don't matter here.) - private final Pattern ipAddressPattern = Pattern.compile(".*:.*|[0-9]+(\\.[0-9]+){3}"); + // A pattern that matches every IP address and no DNS address. It matches some things that + // aren't either of those, but only ones that InetAddress.getByName will reject without + // attempting a lookup. An IPv4 address is n.n.n.n where each n is between 0 and 255, + // possibly with leading zeros. An IPv6 address contains at least one colon, and + // getByName only treats a string as an IPv6 literal (rather than looking it up) if it + // starts with a hex digit, a colon, or an opening bracket. + private static final String IPV4_PART = "0*(25[0-5]|2[0-4][0-9]|1?[0-9]?[0-9])"; + private final Pattern ipAddressPattern = + Pattern.compile("[0-9A-Fa-f:\\[].*:.*|" + IPV4_PART + "(\\." + IPV4_PART + "){3}"); @Override public InetAddress read(JsonReader in) throws IOException { diff --git a/gson/src/test/java/com/google/gson/DefaultInetAddressTypeAdapterTest.java b/gson/src/test/java/com/google/gson/DefaultInetAddressTypeAdapterTest.java index d4a0d1aae7..920cd46450 100644 --- a/gson/src/test/java/com/google/gson/DefaultInetAddressTypeAdapterTest.java +++ b/gson/src/test/java/com/google/gson/DefaultInetAddressTypeAdapterTest.java @@ -67,6 +67,32 @@ public void testInetAddressDeserializeNonIpAddress() { .startsWith("Failed parsing 'localhost' as InetAddress; at path $"); } + @Test + public void testInetAddressDeserializeIpLikeNonIpAddress() { + // These look like IP addresses but aren't, so InetAddress.getByName would look them up. + for (String address : new String[] {"256.1.1.1", "300.1.1.1", "1.2.3.999", "zz:1", "g::1"}) { + String jsonAddress = "\"" + address + "\""; + JsonSyntaxException e = + assertThrows( + JsonSyntaxException.class, () -> gson.fromJson(jsonAddress, InetAddress.class)); + assertThat(e) + .hasMessageThat() + .startsWith("Failed parsing '" + address + "' as InetAddress; at path $"); + } + } + + @Test + public void testInetAddressDeserializeIpAddressForms() throws Exception { + for (String address : + new String[] { + "0.0.0.0", "255.255.255.255", "01.2.3.4", "::1", "[::1]", "::ffff:1.2.3.4", "fe80::1%1" + }) { + String jsonAddress = "\"" + address + "\""; + assertThat(gson.fromJson(jsonAddress, InetAddress.class)) + .isEqualTo(InetAddress.getByName(address)); + } + } + @Test public void testInetAddressDeserializeNonIpAddressAllowed() throws Exception { String jsonAddress = "\"localhost\""; From 719804c13fcd206bd579c83a60a73a3fe8173937 Mon Sep 17 00:00:00 2001 From: Cindy Krafft Date: Thu, 24 Sep 2026 03:17:17 +0000 Subject: [PATCH 2/2] Limit IPv4 parts in the InetAddress check to three digits getByName only parses an IPv4 literal of at most 15 characters. A longer string such as "00000000001.2.3.4" matched the previous pattern. By default getByName rejects it without a lookup, but with jdk.net.allowAmbiguousIPAddressLiterals=true it is looked up. Allowing at most three digits per part keeps every IPv4 match within what getByName parses as a literal. Claude-Session: https://claude.ai/code/session_01X59fWmqnA4Nmb9rggkTP7n --- .../google/gson/internal/bind/TypeAdapters.java | 10 ++++++---- .../gson/DefaultInetAddressTypeAdapterTest.java | 14 +++++++++++--- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java b/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java index 2937a20f5d..06b0eb9727 100644 --- a/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java +++ b/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java @@ -764,10 +764,12 @@ public void write(JsonWriter out, URI value) throws IOException { // A pattern that matches every IP address and no DNS address. It matches some things that // aren't either of those, but only ones that InetAddress.getByName will reject without // attempting a lookup. An IPv4 address is n.n.n.n where each n is between 0 and 255, - // possibly with leading zeros. An IPv6 address contains at least one colon, and - // getByName only treats a string as an IPv6 literal (rather than looking it up) if it - // starts with a hex digit, a colon, or an opening bracket. - private static final String IPV4_PART = "0*(25[0-5]|2[0-4][0-9]|1?[0-9]?[0-9])"; + // written with at most three digits (getByName accepts leading zeros such as "001"). + // An IPv6 address contains at least one colon, and getByName only tries to parse a string + // as an IP literal (rather than looking it up) if it starts with a hex digit, a colon, or + // an opening bracket; a string that it tries to parse and that contains a colon is either + // parsed or rejected. + private static final String IPV4_PART = "(25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])"; private final Pattern ipAddressPattern = Pattern.compile("[0-9A-Fa-f:\\[].*:.*|" + IPV4_PART + "(\\." + IPV4_PART + "){3}"); diff --git a/gson/src/test/java/com/google/gson/DefaultInetAddressTypeAdapterTest.java b/gson/src/test/java/com/google/gson/DefaultInetAddressTypeAdapterTest.java index 920cd46450..8f807015ae 100644 --- a/gson/src/test/java/com/google/gson/DefaultInetAddressTypeAdapterTest.java +++ b/gson/src/test/java/com/google/gson/DefaultInetAddressTypeAdapterTest.java @@ -69,8 +69,9 @@ public void testInetAddressDeserializeNonIpAddress() { @Test public void testInetAddressDeserializeIpLikeNonIpAddress() { - // These look like IP addresses but aren't, so InetAddress.getByName would look them up. - for (String address : new String[] {"256.1.1.1", "300.1.1.1", "1.2.3.999", "zz:1", "g::1"}) { + // These look like IP addresses but are not IP literals that InetAddress.getByName parses. + for (String address : + new String[] {"256.1.1.1", "300.1.1.1", "1.2.3.999", "0001.2.3.4", "zz:1", "g::1"}) { String jsonAddress = "\"" + address + "\""; JsonSyntaxException e = assertThrows( @@ -85,7 +86,14 @@ public void testInetAddressDeserializeIpLikeNonIpAddress() { public void testInetAddressDeserializeIpAddressForms() throws Exception { for (String address : new String[] { - "0.0.0.0", "255.255.255.255", "01.2.3.4", "::1", "[::1]", "::ffff:1.2.3.4", "fe80::1%1" + "0.0.0.0", + "255.255.255.255", + "01.2.3.4", + "001.002.003.004", + "::1", + "[::1]", + "::ffff:1.2.3.4", + "fe80::1%1" }) { String jsonAddress = "\"" + address + "\""; assertThat(gson.fromJson(jsonAddress, InetAddress.class))