Skip to content
This repository has been archived by the owner. It is now read-only.
Permalink
Browse files
8245061: Lookup::defineHiddenClass should throw ClassFormatError if t…
…his_class is not Class_info structure

8245432: Lookup::defineHiddenClass should throw UnsupportedClassVersionError if bytes are of an unsupported major or minor version
8245596: Clarify Lookup::defineHiddenClass spec @throws IAE if the bytes has ACC_MODULE flag set

Reviewed-by: alanb, dholmes
  • Loading branch information
Mandy Chung committed Jun 1, 2020
1 parent 1f698a3 commit 5e5880d4f1d1f3e2cf63d556636506d5b614d4a7
@@ -314,7 +314,7 @@ private static int getConstantPoolSize(byte[] classFile) {
* Extract the MemberName of a newly-defined method.
*/
private MemberName loadMethod(byte[] classFile) {
Class<?> invokerClass = LOOKUP.makeHiddenClassDefiner(classFile)
Class<?> invokerClass = LOOKUP.makeHiddenClassDefiner(className(), classFile)
.defineClass(true, classDataValues());
return resolveInvokerMember(invokerClass, invokerName, invokerType);
}
@@ -31,6 +31,7 @@
import jdk.internal.module.IllegalAccessLogger;
import jdk.internal.org.objectweb.asm.ClassReader;
import jdk.internal.org.objectweb.asm.Opcodes;
import jdk.internal.org.objectweb.asm.Type;
import jdk.internal.reflect.CallerSensitive;
import jdk.internal.reflect.Reflection;
import jdk.internal.vm.annotation.ForceInline;
@@ -1661,8 +1662,9 @@ public Lookup dropLookupMode(int modeToDrop) {
* @return the {@code Class} object for the class
* @throws IllegalAccessException if this lookup does not have {@code PACKAGE} access
* @throws ClassFormatError if {@code bytes} is not a {@code ClassFile} structure
* @throws IllegalArgumentException the bytes are for a class in a different package
* to the lookup class
* @throws IllegalArgumentException if {@code bytes} denotes a class in a different package
* than the lookup class or {@code bytes} is not a class or interface
* ({@code ACC_MODULE} flag is set in the value of the {@code access_flags} item)
* @throws VerifyError if the newly created class cannot be verified
* @throws LinkageError if the newly created class cannot be linked for any other reason
* @throws SecurityException if a security manager is present and it
@@ -1923,8 +1925,9 @@ static int optionsToFlag(Set<ClassOption> options) {
* <a href="MethodHandles.Lookup.html#secmgr">refuses access</a>
* @throws ClassFormatError if {@code bytes} is not a {@code ClassFile} structure
* @throws UnsupportedClassVersionError if {@code bytes} is not of a supported major or minor version
* @throws IllegalArgumentException if {@code bytes} is not a class or interface or
* {@bytes} denotes a class in a different package than the lookup class
* @throws IllegalArgumentException if {@code bytes} denotes a class in a different package
* than the lookup class or {@code bytes} is not a class or interface
* ({@code ACC_MODULE} flag is set in the value of the {@code access_flags} item)
* @throws IncompatibleClassChangeError if the class or interface named as
* the direct superclass of {@code C} is in fact an interface, or if any of the classes
* or interfaces named as direct superinterfaces of {@code C} are not in fact interfaces
@@ -1987,8 +1990,9 @@ public Lookup defineHiddenClass(byte[] bytes, boolean initialize, ClassOption...
* <a href="MethodHandles.Lookup.html#secmgr">refuses access</a>
* @throws ClassFormatError if {@code bytes} is not a {@code ClassFile} structure
* @throws UnsupportedClassVersionError if {@code bytes} is not of a supported major or minor version
* @throws IllegalArgumentException if {@code bytes} is not a class or interface or
* {@bytes} denotes a class in a different package than the lookup class
* @throws IllegalArgumentException if {@code bytes} denotes a class in a different package
* than the lookup class or {@code bytes} is not a class or interface
* ({@code ACC_MODULE} flag is set in the value of the {@code access_flags} item)
* @throws IncompatibleClassChangeError if the class or interface named as
* the direct superclass of {@code C} is in fact an interface, or if any of the classes
* or interfaces named as direct superinterfaces of {@code C} are not in fact interfaces
@@ -2018,36 +2022,95 @@ public Lookup defineHiddenClass(byte[] bytes, boolean initialize, ClassOption...
.defineClassAsLookup(true, classData);
}

/*
* Validates the given bytes to be a class or interface and the class name
* is in the same package as the lookup class.
*
* This method returns the class name.
*/
private String validateAndGetClassName(byte[] bytes) {
try {
ClassReader reader = new ClassReader(bytes);
if ((reader.getAccess() & Opcodes.ACC_MODULE) != 0) {
static class ClassFile {
final String name;
final int accessFlags;
final byte[] bytes;
ClassFile(String name, int accessFlags, byte[] bytes) {
this.name = name;
this.accessFlags = accessFlags;
this.bytes = bytes;
}

static ClassFile newInstanceNoCheck(String name, byte[] bytes) {
return new ClassFile(name, 0, bytes);
}

/**
* This method checks the class file version and the structure of `this_class`.
* and checks if the bytes is a class or interface (ACC_MODULE flag not set)
* that is in the named package.
*
* @throws IllegalArgumentException if ACC_MODULE flag is set in access flags
* or the class is not in the given package name.
*/
static ClassFile newInstance(byte[] bytes, String pkgName) {
int magic = readInt(bytes, 0);
if (magic != 0xCAFEBABE) {
throw new ClassFormatError("Incompatible magic value: " + magic);
}
int minor = readUnsignedShort(bytes, 4);
int major = readUnsignedShort(bytes, 6);
if (!VM.isSupportedClassFileVersion(major, minor)) {
throw new UnsupportedClassVersionError("Unsupported class file version " + major + "." + minor);
}

String name;
int accessFlags;
try {
ClassReader reader = new ClassReader(bytes);
// ClassReader::getClassName does not check if `this_class` is CONSTANT_Class_info
// workaround to read `this_class` using readConst and validate the value
int thisClass = reader.readUnsignedShort(reader.header + 2);
Object constant = reader.readConst(thisClass, new char[reader.getMaxStringLength()]);
if (!(constant instanceof Type)) {
throw new ClassFormatError("this_class item: #" + thisClass + " not a CONSTANT_Class_info");
}
Type type = ((Type) constant);
if (!type.getDescriptor().startsWith("L")) {
throw new ClassFormatError("this_class item: #" + thisClass + " not a CONSTANT_Class_info");
}
name = type.getClassName();
accessFlags = reader.readUnsignedShort(reader.header);
} catch (RuntimeException e) {
// ASM exceptions are poorly specified
ClassFormatError cfe = new ClassFormatError();
cfe.initCause(e);
throw cfe;
}

// must be a class or interface
if ((accessFlags & Opcodes.ACC_MODULE) != 0) {
throw newIllegalArgumentException("Not a class or interface: ACC_MODULE flag is set");
}
String name = reader.getClassName().replace('/', '.');

// check if it's in the named package
int index = name.lastIndexOf('.');
String pn = (index == -1) ? "" : name.substring(0, index);
if (!pn.equals(lookupClass.getPackageName())) {
throw newIllegalArgumentException(name + " not in same package as lookup class: " +
lookupClass.getName());
if (!pn.equals(pkgName)) {
throw newIllegalArgumentException(name + " not in same package as lookup class");
}
return name;
} catch (IllegalArgumentException e) {
throw e;
} catch (RuntimeException e) {
// ASM exceptions are poorly specified
ClassFormatError cfe = new ClassFormatError();
cfe.initCause(e);
throw cfe;

return new ClassFile(name, accessFlags, bytes);
}
}

private static int readInt(byte[] bytes, int offset) {
if ((offset+4) > bytes.length) {
throw new ClassFormatError("Invalid ClassFile structure");
}
return ((bytes[offset] & 0xFF) << 24)
| ((bytes[offset + 1] & 0xFF) << 16)
| ((bytes[offset + 2] & 0xFF) << 8)
| (bytes[offset + 3] & 0xFF);
}

private static int readUnsignedShort(byte[] bytes, int offset) {
if ((offset+2) > bytes.length) {
throw new ClassFormatError("Invalid ClassFile structure");
}
return ((bytes[offset] & 0xFF) << 8) | (bytes[offset + 1] & 0xFF);
}
}

/*
* Returns a ClassDefiner that creates a {@code Class} object of a normal class
@@ -2060,7 +2123,8 @@ private String validateAndGetClassName(byte[] bytes) {
* {@bytes} denotes a class in a different package than the lookup class
*/
private ClassDefiner makeClassDefiner(byte[] bytes) {
return new ClassDefiner(this, validateAndGetClassName(bytes), bytes, STRONG_LOADER_LINK);
ClassFile cf = ClassFile.newInstance(bytes, lookupClass().getPackageName());
return new ClassDefiner(this, cf, STRONG_LOADER_LINK);
}

/**
@@ -2077,7 +2141,8 @@ private ClassDefiner makeClassDefiner(byte[] bytes) {
* {@bytes} denotes a class in a different package than the lookup class
*/
ClassDefiner makeHiddenClassDefiner(byte[] bytes) {
return makeHiddenClassDefiner(validateAndGetClassName(bytes), bytes, Set.of(), false);
ClassFile cf = ClassFile.newInstance(bytes, lookupClass().getPackageName());
return makeHiddenClassDefiner(cf, Set.of(), false);
}

/**
@@ -2099,7 +2164,8 @@ ClassDefiner makeHiddenClassDefiner(byte[] bytes) {
ClassDefiner makeHiddenClassDefiner(byte[] bytes,
Set<ClassOption> options,
boolean accessVmAnnotations) {
return makeHiddenClassDefiner(validateAndGetClassName(bytes), bytes, options, accessVmAnnotations);
ClassFile cf = ClassFile.newInstance(bytes, lookupClass().getPackageName());
return makeHiddenClassDefiner(cf, options, accessVmAnnotations);
}

/**
@@ -2111,30 +2177,29 @@ ClassDefiner makeHiddenClassDefiner(byte[] bytes,
* @return ClassDefiner that defines a hidden class of the given bytes.
*/
ClassDefiner makeHiddenClassDefiner(String name, byte[] bytes) {
return makeHiddenClassDefiner(name, bytes, Set.of(), false);
// skip name and access flags validation
return makeHiddenClassDefiner(ClassFile.newInstanceNoCheck(name, bytes), Set.of(), false);
}

/**
* Returns a ClassDefiner that creates a {@code Class} object of a hidden class
* from the given bytes and options. No package name check on the given name.
* from the given class file and options.
*
* @param name the name of the class and the name in the class bytes is ignored.
* @param bytes class bytes
* @param cf ClassFile
* @param options class options
* @param accessVmAnnotations true to give the hidden class access to VM annotations
*/
ClassDefiner makeHiddenClassDefiner(String name,
byte[] bytes,
Set<ClassOption> options,
boolean accessVmAnnotations) {
private ClassDefiner makeHiddenClassDefiner(ClassFile cf,
Set<ClassOption> options,
boolean accessVmAnnotations) {
int flags = HIDDEN_CLASS | ClassOption.optionsToFlag(options);
if (accessVmAnnotations | VM.isSystemDomainLoader(lookupClass.getClassLoader())) {
// jdk.internal.vm.annotations are permitted for classes
// defined to boot loader and platform loader
flags |= ACCESS_VM_ANNOTATIONS;
}

return new ClassDefiner(this, name, bytes, flags);
return new ClassDefiner(this, cf, flags);
}

static class ClassDefiner {
@@ -2143,12 +2208,12 @@ static class ClassDefiner {
private final byte[] bytes;
private final int classFlags;

private ClassDefiner(Lookup lookup, String name, byte[] bytes, int flags) {
private ClassDefiner(Lookup lookup, ClassFile cf, int flags) {
assert ((flags & HIDDEN_CLASS) != 0 || (flags & STRONG_LOADER_LINK) == STRONG_LOADER_LINK);
this.lookup = lookup;
this.bytes = bytes;
this.bytes = cf.bytes;
this.name = cf.name;
this.classFlags = flags;
this.name = name;
}

String className() {
@@ -27,11 +27,11 @@

import static java.lang.Thread.State.*;

import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import jdk.internal.access.SharedSecrets;

@@ -46,7 +46,6 @@ public class VM {
private static final int SYSTEM_BOOTED = 4;
private static final int SYSTEM_SHUTDOWN = 5;


// 0, 1, 2, ...
private static volatile int initLevel;
private static final Object lock = new Object();
@@ -148,6 +147,45 @@ public static boolean isDirectMemoryPageAligned() {
return pageAlignDirectMemory;
}

private static int classFileMajorVersion;
private static int classFileMinorVersion;
private static final int PREVIEW_MINOR_VERSION = 65535;

/**
* Tests if the given version is a supported {@code class}
* file version.
*
* A {@code class} file depends on the preview features of Java SE {@code N}
* if the major version is {@code N} and the minor version is 65535.
* This method returns {@code true} if the given version is a supported
* {@code class} file version regardless of whether the preview features
* are enabled or not.
*
* @jvms 4.1 Table 4.1-A. class file format major versions
*/
public static boolean isSupportedClassFileVersion(int major, int minor) {
if (major < 45 || major > classFileMajorVersion) return false;
// for major version is between 45 and 55 inclusive, the minor version may be any value
if (major < 56) return true;
// otherwise, the minor version must be 0 or 65535
return minor == 0 || minor == PREVIEW_MINOR_VERSION;
}

/**
* Tests if the given version is a supported {@code class}
* file version for module descriptor.
*
* major.minor version >= 53.0
*/
public static boolean isSupportedModuleDescriptorVersion(int major, int minor) {
if (major < 53 || major > classFileMajorVersion) return false;
// for major version is between 45 and 55 inclusive, the minor version may be any value
if (major < 56) return true;
// otherwise, the minor version must be 0 or 65535
// preview features do not apply to module-info.class but JVMS allows it
return minor == 0 || minor == PREVIEW_MINOR_VERSION;
}

/**
* Returns true if the given class loader is the bootstrap class loader
* or the platform class loader.
@@ -222,6 +260,15 @@ public static void saveProperties(Map<String, String> props) {
s = props.get("sun.nio.PageAlignDirectMemory");
if ("true".equals(s))
pageAlignDirectMemory = true;

s = props.get("java.class.version");
int index = s.indexOf('.');
try {
classFileMajorVersion = Integer.valueOf(s.substring(0, index));
classFileMinorVersion = Integer.valueOf(s.substring(index+1, s.length()));
} catch (NumberFormatException e) {
throw new InternalError(e);
}
}

// Initialize any miscellaneous operating system settings that need to be
@@ -49,6 +49,7 @@

import jdk.internal.access.JavaLangModuleAccess;
import jdk.internal.access.SharedSecrets;
import jdk.internal.misc.VM;

import static jdk.internal.module.ClassFileConstants.*;

@@ -62,9 +63,6 @@

public final class ModuleInfo {

private final int JAVA_MIN_SUPPORTED_VERSION = 53;
private final int JAVA_MAX_SUPPORTED_VERSION = 59;

private static final JavaLangModuleAccess JLMA
= SharedSecrets.getJavaLangModuleAccess();

@@ -190,8 +188,7 @@ private Attributes doRead(DataInput in) throws IOException {

int minor_version = in.readUnsignedShort();
int major_version = in.readUnsignedShort();
if (major_version < JAVA_MIN_SUPPORTED_VERSION ||
major_version > JAVA_MAX_SUPPORTED_VERSION) {
if (!VM.isSupportedModuleDescriptorVersion(major_version, minor_version)) {
throw invalidModuleDescriptor("Unsupported major.minor version "
+ major_version + "." + minor_version);
}

0 comments on commit 5e5880d

Please sign in to comment.