8328874: Class::forName0 should validate the class name length early

Reviewed-by: rriggs, liach, ayang
This commit is contained in:
Guanqiang Han 2025-09-12 14:46:12 +00:00 committed by Roger Riggs
parent 10fea86002
commit 44aad0786b
4 changed files with 131 additions and 4 deletions

View File

@ -78,6 +78,7 @@ import jdk.internal.reflect.CallerSensitiveAdapter;
import jdk.internal.reflect.ConstantPool;
import jdk.internal.reflect.Reflection;
import jdk.internal.reflect.ReflectionFactory;
import jdk.internal.util.ModifiedUtf;
import jdk.internal.vm.annotation.AOTRuntimeSetup;
import jdk.internal.vm.annotation.AOTSafeClassInitializer;
import jdk.internal.vm.annotation.IntrinsicCandidate;
@ -467,6 +468,7 @@ public final class Class<T> implements java.io.Serializable,
@CallerSensitiveAdapter
private static Class<?> forName(String className, Class<?> caller)
throws ClassNotFoundException {
validateClassNameLength(className);
ClassLoader loader = (caller == null) ? ClassLoader.getSystemClassLoader()
: ClassLoader.getClassLoader(caller);
return forName0(className, true, loader, caller);
@ -549,6 +551,7 @@ public final class Class<T> implements java.io.Serializable,
public static Class<?> forName(String name, boolean initialize, ClassLoader loader)
throws ClassNotFoundException
{
validateClassNameLength(name);
return forName0(name, initialize, loader, null);
}
@ -598,6 +601,9 @@ public final class Class<T> implements java.io.Serializable,
public static Class<?> forName(Module module, String name) {
Objects.requireNonNull(module);
Objects.requireNonNull(name);
if (!ModifiedUtf.isValidLengthInConstantPool(name)) {
return null;
}
ClassLoader cl = module.getClassLoader();
if (cl != null) {
@ -4148,4 +4154,14 @@ public final class Class<T> implements java.io.Serializable,
int getClassFileAccessFlags() {
return classFileAccessFlags;
}
// Validates the length of the class name and throws an exception if it exceeds the maximum allowed length.
private static void validateClassNameLength(String name) throws ClassNotFoundException {
if (!ModifiedUtf.isValidLengthInConstantPool(name)) {
throw new ClassNotFoundException(
"Class name length exceeds limit of "
+ ModifiedUtf.CONSTANT_POOL_UTF8_MAX_BYTES
+ ": " + name.substring(0,256) + "...");
}
}
}

View File

@ -33,6 +33,10 @@ import jdk.internal.vm.annotation.ForceInline;
* @since 24
*/
public abstract class ModifiedUtf {
// Maximum number of bytes allowed for a Modified UTF-8 encoded string
// in a ClassFile constant pool entry (CONSTANT_Utf8_info).
public static final int CONSTANT_POOL_UTF8_MAX_BYTES = 65535;
private ModifiedUtf() {
}
@ -68,4 +72,26 @@ public abstract class ModifiedUtf {
}
return utflen;
}
/**
* Checks whether the Modified UTF-8 encoded length of the given string
* fits within the ClassFile constant pool limit (u2 length = 65535 bytes).
* @param str the string to check
*/
@ForceInline
public static boolean isValidLengthInConstantPool(String str) {
// Quick approximation: each char can be at most 3 bytes in Modified UTF-8.
// If the string is short enough, it definitely fits.
int strLen = str.length();
if (strLen <= CONSTANT_POOL_UTF8_MAX_BYTES / 3) {
return true;
}
if (strLen > CONSTANT_POOL_UTF8_MAX_BYTES) {
return false;
}
// Check exact Modified UTF-8 length.
// The check strLen > CONSTANT_POOL_UTF8_MAX_BYTES above ensures that utfLen can't overflow here.
int utfLen = utfLen(str, 0);
return utfLen <= CONSTANT_POOL_UTF8_MAX_BYTES;
}
}

View File

@ -54,6 +54,7 @@ import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Random;
@ -1365,7 +1366,7 @@ public class NullPointerExceptionTest {
// If NPE is thrown in a native method, the message should
// not be generated.
try {
Class.forName(null);
Array.get(null,0);
Asserts.fail();
} catch (NullPointerException e) {
Asserts.assertNull(e.getMessage());

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -23,7 +23,7 @@
/**
* @test
* @bug 8310242
* @bug 8310242 8328874
* @run junit ForNameNames
* @summary Verify class names for Class.forName
*/
@ -37,6 +37,16 @@ import org.junit.jupiter.params.provider.MethodSource;
import static org.junit.jupiter.api.Assertions.*;
public class ForNameNames {
//Max length in Modified UTF-8 bytes for class names.
private static final int JAVA_CLASSNAME_MAX_LEN = 65535;
private static final String ONE_BYTE = "A"; // 1-byte UTF-8
private static final String TWO_BYTE = "\u0100"; // 2-byte UTF-8
private static final String THREE_BYTE = "\u2600"; // 3-byte UTF-8
private static final String ERR_MSG_IN_CORE = "Class name length exceeds limit of"; // check in corelib
private static final String ERR_MSG_IN_JVM = "Class name exceeds maximum length"; // check in jvm
static class Inner {}
static Stream<Arguments> testCases() {
return Stream.of(
@ -90,4 +100,78 @@ public class ForNameNames {
assertNull(c);
}
}
static Stream<Arguments> validLen() {
return Stream.of(
// 1-byte character
Arguments.of(ONE_BYTE.repeat(JAVA_CLASSNAME_MAX_LEN - 1)),
Arguments.of(ONE_BYTE.repeat(JAVA_CLASSNAME_MAX_LEN)),
Arguments.of(ONE_BYTE.repeat(JAVA_CLASSNAME_MAX_LEN / 3 - 1)),
Arguments.of(ONE_BYTE.repeat(JAVA_CLASSNAME_MAX_LEN / 3)),
Arguments.of(ONE_BYTE.repeat(JAVA_CLASSNAME_MAX_LEN / 3 + 1)),
// 2-byte characters
Arguments.of(TWO_BYTE.repeat(JAVA_CLASSNAME_MAX_LEN / 2)),
Arguments.of(TWO_BYTE.repeat(JAVA_CLASSNAME_MAX_LEN / 6)),
Arguments.of(TWO_BYTE.repeat(JAVA_CLASSNAME_MAX_LEN / 6 + 1)),
// 3-byte characters
Arguments.of(THREE_BYTE.repeat(JAVA_CLASSNAME_MAX_LEN / 3 - 1)),
Arguments.of(THREE_BYTE.repeat(JAVA_CLASSNAME_MAX_LEN / 3)),
Arguments.of(THREE_BYTE.repeat(JAVA_CLASSNAME_MAX_LEN / 9)),
Arguments.of(THREE_BYTE.repeat(JAVA_CLASSNAME_MAX_LEN / 9 + 1))
);
}
/*
* Test class name length handling in 1-arg and 3-arg Class::forName
* with valid length.
*/
@ParameterizedTest
@MethodSource("validLen")
void testValidLen(String cn) {
ClassLoader loader = ForNameNames.class.getClassLoader();
// 3-arg Class.forName
ClassNotFoundException ex = assertThrows(ClassNotFoundException.class,
() -> Class.forName(cn, false, loader));
assertFalse(ex.getMessage().contains(ERR_MSG_IN_CORE)
|| ex.getMessage().contains(ERR_MSG_IN_JVM),
"Unexpected exception message");
// 1-arg Class.forName
ex = assertThrows(ClassNotFoundException.class,
() -> Class.forName(cn));
assertFalse(ex.getMessage().contains(ERR_MSG_IN_CORE)
|| ex.getMessage().contains(ERR_MSG_IN_JVM),
"Unexpected exception message");
}
static Stream<Arguments> invalidLen() {
return Stream.of(
// 1-byte characters over the limit
Arguments.of(ONE_BYTE.repeat(JAVA_CLASSNAME_MAX_LEN + 1)),
// 2-byte characters over the limit
Arguments.of(TWO_BYTE.repeat(JAVA_CLASSNAME_MAX_LEN / 2 + 1)),
// 3-byte characters over the limit
Arguments.of(THREE_BYTE.repeat(JAVA_CLASSNAME_MAX_LEN / 3 + 1))
);
}
/*
* Test class name length handling in 1-arg and 3-arg Class::forName
* with invalid (too long) length.
*/
@ParameterizedTest
@MethodSource("invalidLen")
void testInvalidLen(String cn) {
ClassLoader loader = ForNameNames.class.getClassLoader();
// 3-arg Class.forName
ClassNotFoundException ex = assertThrows(ClassNotFoundException.class,
() -> Class.forName(cn, false, loader));
assertTrue(ex.getMessage().contains(ERR_MSG_IN_CORE),
"Unexpected exception message");
// 1-arg Class.forName
ex = assertThrows(ClassNotFoundException.class,
() -> Class.forName(cn));
assertTrue(ex.getMessage().contains(ERR_MSG_IN_CORE),
"Unexpected exception message");
}
}