mirror of
https://github.com/openjdk/jdk.git
synced 2026-01-28 12:09:14 +00:00
8328874: Class::forName0 should validate the class name length early
Reviewed-by: rriggs, liach, ayang
This commit is contained in:
parent
10fea86002
commit
44aad0786b
@ -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) + "...");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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());
|
||||
|
||||
@ -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");
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user