📜 ⬆️ ⬇️

We instantiate java.lang.Class


The java.lang.Class constructor is one of the most protected entities in the Java language. The specification clearly states that only the JVM itself can create objects of type Class and what can we do here, but is it really so?


I propose to dive into the depths of the Reflection API (and not only) and find out how everything is arranged there and how difficult it will be to bypass the existing limitations.


I am experimenting on a 64-bit JDK 1.8.0_151 with default settings. Java 9 is at the very end of the article.


Level 1. Simple


Let's start with the most naive attempts and go incrementally. First, let's face the enemy:


 private Class(ClassLoader loader) { classLoader = loader; } 

This designer is nothing special. The compiler does not make any exceptions for it, and the constructor is also present in the bytecode. Therefore, we will try to do the same as we would do with any other class:


 Constructor<Class> constructor = Class.class.getDeclaredConstructor(ClassLoader.class); constructor.setAccessible(true); Class<?> clazz = constructor.newInstance(ClassLoader.getSystemClassLoader()); 

Quite expectedly, this code will not work and will generate the following error:


 Exception in thread "main" java.lang.SecurityException: Cannot make a java.lang.Class constructor accessible at java.lang.reflect.AccessibleObject.setAccessible0(...) at java.lang.reflect.AccessibleObject.setAccessible(...) at Sample.main(...) 

From the first attempt, we hit the first warning from the setAccessible0 method. It is hard-coded specifically for the constructor of the java.lang.Class class:


 private static void setAccessible0(AccessibleObject obj, boolean flag) throws SecurityException { if (obj instanceof Constructor && flag == true) { Constructor<?> c = (Constructor<?>) obj; if (c.getDeclaringClass() == Class.class) { throw new SecurityException("Cannot make a java.lang.Class" + " constructor accessible"); } } obj.override = flag; } 

Not a problem, because the key line in this method is the last one - setting the override field to true . This is easily done using brute force:


 Field overrideConstructorField = AccessibleObject.class.getDeclaredField("override"); overrideConstructorField.setAccessible(true); overrideConstructorField.set(constructor, true); 

Level 2. More difficult


Naturally, setting the override flag is not the only restriction, but now we can at least move a little further in the newInstance method. Far enough to plan next steps. This time the error will be as follows:


 Exception in thread "main" java.lang.InstantiationException: Can not instantiate java.lang.Class at sun.reflect.InstantiationExceptionConstructorAccessorImpl.newInstance(...) at java.lang.reflect.Constructor.newInstance(...) at Sample.main(...) 

We were brought straight to the class of the sun.reflect package, and we know that the main magic should occur there. It's time to look into the implementation of the newInstance class Constructor and find out how we got there:


 public T newInstance(Object ... initargs) throws ... { ... ConstructorAccessor ca = constructorAccessor; // read volatile if (ca == null) { ca = acquireConstructorAccessor(); } @SuppressWarnings("unchecked") T inst = (T) ca.newInstance(initargs); return inst; } 

From the implementation, it becomes clear that Constructor delegates all the work of instantiating another object of type ConstructorAccessor . It is initialized in a lazy way and does not change in the future. I will not describe the internals of the acquireConstructorAccessor method; I will just say that as a result, it leads to a call to the newConstructorAccessor method of newConstructorAccessor object of the sun.reflect.ReflectionFactory class. And it is for the constructor of the java.lang.Class class (and also for abstract classes) that this method returns an InstantiationExceptionConstructorAccessorImpl object. He does not know how to instantiate, but only throws exceptions at every call to him. All this means only one thing: the correct ConstructorAccessor will have to be instantiated by itself.


Level 3. Native


Time to find out what types of ConstructorAccessor objects are (in addition to the one described above):



So for the most ordinary class under the most ordinary circumstances, we would get an object
NativeConstructorAccessorImpl , which means that we are NativeConstructorAccessorImpl to create it manually:


 Class<?> nativeCAClass = Class.forName("sun.reflect.NativeConstructorAccessorImpl"); Constructor<?> nativeCAConstructor = nativeCAClass.getDeclaredConstructor(Constructor.class); nativeCAConstructor.setAccessible(true); ConstructorAccessor constructorAccessor = (ConstructorAccessor) nativeCAConstructor.newInstance(constructor); 

There are no dirty tricks here: the object is created without unnecessary restrictions, and all we have to do is to instantiate java.lang.Class with it:


 Class<?> clazz = (Class<?>) constructorAccessor.newInstance( new Object[]{ClassLoader.getSystemClassLoader()}); 

But here a surprise awaits:


 # # A fatal error has been detected by the Java Runtime Environment: # # SIGSEGV (0xb) at pc=0x00007f8698589ead, pid=20537, tid=0x00007f8699af3700 # # JRE version: Java(TM) SE Runtime Environment (8.0_151-b12) (build 1.8.0_151-b12) # ... 

It seems that the JVM does not expect such illogical actions from the user, especially after all the warnings. Nevertheless, this result can rightfully be considered an achievement - I’ve been flooded with the JVM, never taking advantage of the classes in the sun.misc package!


Level 4. Magical


Native call does not work - so now you need to deal with GeneratedConstructorAccessor .


In fact, this is not just a class, but a whole family of classes. For each constructor in runtime, its own unique implementation is generated. That is why the native implementation is primarily used: to generate bytecode and create a class of cost from it. The class generation process itself is hidden in the generateConstructor method of the sun.reflect.MethodAccessorGenerator class. Call it manually is not difficult:


 Class<?> methodAccessorGeneratorClass = Class.forName("sun.reflect.MethodAccessorGenerator"); Constructor<?> methodAccessorGeneratorConstructor = methodAccessorGeneratorClass.getDeclaredConstructor(); methodAccessorGeneratorConstructor.setAccessible(true); Object methodAccessorGenerator = methodAccessorGeneratorConstructor.newInstance(); Method generateConstructor = methodAccessorGeneratorClass .getDeclaredMethod("generateConstructor", Class.class, Class[].class, Class[].class, int.class); generateConstructor.setAccessible(true); ConstructorAccessor constructorAccessor = (ConstructorAccessor) generateConstructor.invoke(methodAccessorGenerator, constructor.getDeclaringClass(), constructor.getParameterTypes(), constructor.getExceptionTypes(), constructor.getModifiers()); 

As in the case of NativeConstructorAccessorImpl , there are no pitfalls here - this code will work and do exactly what is expected of it. But let's think for a minute: well, have we generated some class, where will the rights to call the private constructor come from? This should not be, so we simply must dump the generated class and examine its code. It's easy to do this - we set up a debugger in the generateConstructor method and at the right moment we dump the array of bytes we need into a file. Its decompiled version looks like this (after renaming variables):


 public class GeneratedConstructorAccessor1 extends ConstructorAccessorImpl { public Object newInstance(Object[] args) throws InvocationTargetException { Class clazz; ClassLoader classLoader; try { clazz = new Class; if (args.length != 1) { throw new IllegalArgumentException(); } classLoader = (ClassLoader) args[0]; } catch (NullPointerException | ClassCastException e) { throw new IllegalArgumentException(e.toString()); } try { clazz.<init>(classLoader); return clazz; } catch (Throwable e) { throw new InvocationTargetException(e); } } } 

Naturally, such code will not compile back, and there are two reasons for this:



These instructions are spaced in order to be in different try-blocks. Why it was done this way, I do not know. This was probably the only way to handle exceptions so that they fully comply with the language specification.


If we turn a blind eye to the atypical handling of exceptions, then in all other respects this class is absolutely normal, but it is still unclear where it suddenly has the right to call private designers. It turns out that the whole thing is in the superclass:


 abstract class ConstructorAccessorImpl extends MagicAccessorImpl implements ConstructorAccessor { public abstract Object newInstance(Object[] args) throws InstantiationException, IllegalArgumentException, InvocationTargetException; } 

The JVM has a well known crutch called sun.reflect.MagicAccessorImpl . Every successor has unlimited access to any private data of any classes. This is exactly what you need! Once the class is magic, it will help get the java.lang.Class instance. Checking:


 Class<?> clazz = (Class<?>) constructorAccessor.newInstance( new Object[]{ClassLoader.getSystemClassLoader()}); 

and again we get an exception:


 Exception in thread "main" java.lang.IllegalAccessError: java.lang.Class at sun.reflect.GeneratedConstructorAccessor1.newInstance(...) at Sample.main(...) 

This is really interesting. Apparently, the promised magic did not happen. Or I'm wrong?


It is worth examining the error more carefully and comparing it with how the newInstance method should behave. If there were a problem in the clazz.<init>(classLoader) , we would get an InvocationTargetException . In fact, we have IllegalAccessError , that is, it didn’t get to the constructor’s call. With an error, the NEW instruction worked, not allowing memory to be allocated for the java.lang.Class object. Here our powers are all over.


Level 5. Modern


Reflection did not solve the problem. Maybe the fact is that Reflection is old and weak, and instead you should use the young and strong MethodHandles? I think yes. At least worth a try.


And as soon as I decided that Reflection is not needed, it was immediately useful. MethodHandles is, of course, good, but with the help of it it is customary to receive only those data that is accessible. And if you need a private constructor, you will have to get out the old fashioned way.


So, we need MethodHandles.Lookup with private access to the java.lang.Class class. In this case there is a very suitable constructor:


 private Lookup(Class<?> lookupClass, int allowedModes) { ... } 

Call it:


 Constructor<MethodHandles.Lookup> lookupConstructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class); lookupConstructor.setAccessible(true); MethodHandles.Lookup lookup = lookupConstructor .newInstance(Class.class, MethodHandles.Lookup.PRIVATE); 

After receiving the lookup , you can get the MethodHandle object corresponding to the constructor we need:


 MethodHandle handle = lookup.findConstructor(Class.class, MethodType.methodType(Class.class, ClassLoader.class)); 

After running this method, I was frankly surprised - lookup pretends that the constructor does not exist at all, although it is definitely present in the class!


 Exception in thread "main" java.lang.NoSuchMethodException: no such constructor: java.lang.Class.<init>(ClassLoader)Class/newInvokeSpecial at java.lang.invoke.MemberName.makeAccessException(...) at java.lang.invoke.MemberName$Factory.resolveOrFail(...) at java.lang.invoke.MethodHandles$Lookup.resolveOrFail(...) at java.lang.invoke.MethodHandles$Lookup.findConstructor(...) at Sample.main(Sample.java:59) Caused by: java.lang.NoSuchFieldError: method resolution failed at java.lang.invoke.MethodHandleNatives.resolve(...) at java.lang.invoke.MemberName$Factory.resolve(...) at java.lang.invoke.MemberName$Factory.resolveOrFail(...) ... 3 more 

Strangely, the reason for the exception is NoSuchFieldError . Mysteriously ...


This time I was mistaken, but far from immediately I understood it. The findConstructor specification requires that the return type be void , despite the fact that the result MethodType will be exactly the same as I described (all because the <init> method responsible for the constructor does return void for historical reasons).
Anyway, confusion can be avoided, because lookup has a second method for obtaining a constructor, and it is called unreflectConstructor :


 MethodHandle handle = lookup.unreflectConstructor(constructor); 

This method will certainly correctly execute and return the handle that it should.


The moment of truth. Run the instantiation method:


 Class<?> clazz = (Class<?>) handle. invoke(ClassLoader.getSystemClassLoader()); 

I think you have already guessed that nothing good will happen, but let's at least look at the error. Now this is something new:


 Exception in thread "main" java.lang.IllegalAccessException: java.lang.Class at sun.misc.Unsafe.allocateInstance(...) at java.lang.invoke.DirectMethodHandle.allocateInstance(...) at java.lang.invoke.LambdaForm$DMH/925858445.newInvokeSpecial_L_L(...) at java.lang.invoke.LambdaForm$MH/523429237.invoke_MT(...) at Sample.main(...) 

By default, stacktrace is shown shortened, so I added
-XX:+UnlockDiagnosticVMOptions -XX:+ShowHiddenFrames in the launch options. So it becomes easier to understand what a strange place we have come to.


I will not go deep into what classes MethodHandles generates, and it is not essential. The important thing is quite different - we finally got to the bottom of using sun.misc.Unsafe , and even he could not create the java.lang.Class object.


The allocaeInstance method allocaeInstance used in those places where you need to create an object, but not call its constructor. This can be useful, for example, when deserializing objects. In fact, this is the same NEW instruction, but not burdened with access checks. Almost burdened as we just saw.


Since even Unsafe could not, I can only come to a sad conclusion: it is impossible to allocate a new java.lang.Class object. Interestingly it turns out - I thought that the designer is prohibited, and allocation is prohibited! Let's try to get around this thing.


Level 6. Unsafe


I propose to create an empty object and look at what it consists of. To do this, take Unsafe and allocate a new java.lang.Object :


 Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe"); theUnsafeField.setAccessible(true); Unsafe unsafe = (Unsafe) theUnsafeField.get(null); Object object = unsafe.allocateInstance(Object.class); 

On the current JVM, the result will be a 12-byte memory area, which looks like this:



What you see here is the "object header". By and large, it consists of two parts - 8 bytes markword, which do not interest us, and 4 bytes of classword, which are important.


How does the JVM recognize an object class? It does this by reading the classword region, which stores a pointer to the internal structure of the JVM describing the class. So if you write another value in this place, then the class of the object will change!


Further code is very, very bad, never do this:


 System.out.println(object.getClass()); unsafe.putInt(object, 8L, unsafe.getInt(Object.class, 8L)); System.out.println(object.getClass()); 

We read the classword of Object.class and Object.class it into the classword of object . The result of the work is as follows:


 class java.lang.Object class java.lang.Class 

With a stretch we can assume that we have allocated java.lang.Class . We are great! Now we need to call the constructor. You can laugh, but now we will use ASM to generate a class that can call the desired constructor. Naturally, you need to inherit from MagicAccessorImpl .


This is how the creation of the class begins (the constants are imported statically, in short)


 ClassWriter cw = new ClassWriter(COMPUTE_FRAMES | COMPUTE_MAXS); cw.visit(V1_8, ACC_PUBLIC, "sun/reflect/MyConstructorInvocator", null, "sun/reflect/MagicAccessorImpl", null); 

So it creates a constructor:


 MethodVisitor init = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null); init.visitCode(); init.visitVarInsn(ALOAD, 0); init.visitMethodInsn(INVOKESPECIAL, "sun/reflect/MagicAccessorImpl", "<init>", "()V", false); init.visitInsn(RETURN); init.visitMaxs(-1, -1); init.visitEnd(); 

And this is how the void construct(Class<?>, ClassLoader) method is created, which the constructor calls the Class<?> Object inside:


 MethodVisitor construct = cw.visitMethod(ACC_PUBLIC, "construct", "(Ljava/lang/Class;Ljava/lang/ClassLoader;)V", null, null); construct.visitCode(); construct.visitVarInsn(ALOAD, 1); construct.visitVarInsn(ALOAD, 2); construct.visitMethodInsn(INVOKESPECIAL, "java/lang/Class", "<init>", "(Ljava/lang/ClassLoader;)V", false); construct.visitInsn(RETURN); construct.visitMaxs(-1, -1); construct.visitEnd(); 

The class is ready. It remains to load, instantiate and call the required method:


 byte[] bytes = cw.toByteArray(); Class<?> myCustomInvocator = unsafe.defineClass(null, bytes, 0, bytes.length, ClassLoader.getSystemClassLoader(), null); Object ci = myCustomInvocator.newInstance(); Method constructMethod = myCustomInvocator.getDeclaredMethod("construct", Class.class, ClassLoader.class); Class<?> clazz = (Class<?>) object; constructMethod.invoke(ci, clazz, ClassLoader.getSystemClassLoader()); 

And it works! More precisely: lucky it works. You can check by running the following code:


 System.out.println(clazz.getClassLoader()); 

The output will be:


 sun.misc.Launcher$AppClassLoader@18b4aac2 

I will tactfully keep silent about what memory area this ClassLoader and from where it later read. And, as expected, calling almost any other method on this object leads to the immediate collapse of the JVM. And the rest - the goal is completed!


What's in java 9?


Java 9 is pretty much the same. You can do all the same actions, but with a few reservations:



It is also worth considering that sun.reflect transferred to jdk.internal.reflect and that the MyConstructorInvocator class MyConstructorInvocator now be loaded with the same loader as MagicAccessorImpl .
ClassLoader.getSystemClassLoader() will not work anymore, it will not have access.


We also fixed a strange bug with NoSuchFieldError : now in its place is NoSuchMethodError , which should be there. Trifle, but nice.


In general, in Java 9 you need to try much harder to shoot yourself in the foot, even if that is the main goal. I think it's for the best.


Findings:



Do not be too serious to take everything described. The task of instantiating java.lang.Class completely meaningless. Here the knowledge gained in the process of its decision is important.


Thanks for attention!


')

Source: https://habr.com/ru/post/341930/


All Articles