From 7f7790027befe5576b92f7b05e94e12e322b697f Mon Sep 17 00:00:00 2001 From: Sagar Chanchal Date: Thu, 27 Aug 2026 18:41:16 +0530 Subject: [PATCH] Fix out-of-bounds read for truncated percent-escape in opaque host The opaque-host percent-escape validation guard reads input.codePointAt(i + 2) after only checking 'input.length() - i < 2', so an input such as 'foo://%4' throws StringIndexOutOfBoundsException instead of reporting a validation error. Fix the bounds guard to require two code points after '%' and check ASCII hex digits rather than ASCII digits, matching the URL spec, where invalid percent-escapes in opaque hosts are validation errors, not failures. Signed-off-by: Sagar Chanchal --- .../web/util/WhatWgUrlParser.java | 4 ++-- .../web/util/WhatWgUrlParserTests.java | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/web/util/WhatWgUrlParser.java b/spring-web/src/main/java/org/springframework/web/util/WhatWgUrlParser.java index 6b5c508eb95e..fd9a15d54db2 100644 --- a/spring-web/src/main/java/org/springframework/web/util/WhatWgUrlParser.java +++ b/spring-web/src/main/java/org/springframework/web/util/WhatWgUrlParser.java @@ -2241,8 +2241,8 @@ public static OpaqueHost parse(String input, WhatWgUrlParser p) { // If input contains a U+0025 (%) and the two code points following it // are not ASCII hex digits, invalid-URL-unit validation error. if (p.validate() && ch == '%' && - (input.length() - i < 2 || !isAsciiDigit(input.codePointAt(i + 1)) || - !isAsciiDigit(input.codePointAt(i + 2)))) { + (input.length() - i < 3 || !isAsciiHexDigit(input.codePointAt(i + 1)) || + !isAsciiHexDigit(input.codePointAt(i + 2)))) { p.validationError("Code point \"" + ch + "\" is not a URL unit."); } } diff --git a/spring-web/src/test/java/org/springframework/web/util/WhatWgUrlParserTests.java b/spring-web/src/test/java/org/springframework/web/util/WhatWgUrlParserTests.java index ad4c4e697a3b..e29b0638b0d2 100644 --- a/spring-web/src/test/java/org/springframework/web/util/WhatWgUrlParserTests.java +++ b/spring-web/src/test/java/org/springframework/web/util/WhatWgUrlParserTests.java @@ -16,6 +16,9 @@ package org.springframework.web.util; +import java.util.ArrayList; +import java.util.List; + import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; @@ -63,6 +66,21 @@ void parseAsciiHost() { testParse("file://xn--/p", "file", "xn--", null, "/p", null, null); } + @Test + void parseOpaqueHostTruncatedPercentEscape() { + // A truncated or non-hex percent-escape in an opaque host is a validation error, + // not a failure, and must not read past the end of the input + // (see https://url.spec.whatwg.org/#concept-opaque-host-parser). + List errors = new ArrayList<>(); + WhatWgUrlParser.UrlRecord record = WhatWgUrlParser.parse("foo://%4", EMPTY_URL_RECORD, null, errors::add); + assertThat(record.host().toString()).isEqualTo("%4"); + + record = WhatWgUrlParser.parse("foo://%4x", EMPTY_URL_RECORD, null, errors::add); + assertThat(record.host().toString()).isEqualTo("%4x"); + + assertThat(errors).isNotEmpty(); + } + private void testParse(String input, String scheme, @Nullable String host, @Nullable String port, String path, @Nullable String query, @Nullable String fragment) { WhatWgUrlParser.UrlRecord result = WhatWgUrlParser.parse(input, EMPTY_URL_RECORD, null, null); assertThat(result.scheme()).as("Invalid scheme").isEqualTo(scheme);