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.
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);
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.
Time to find out what types of ConstructorAccessor
objects are (in addition to the one described above):
BootstrapConstructorAccessorImpl
:ConstructorAccessor
. Probably saves some code from endless recursion. The thing is highly specialized, I will not touch it;GeneratedConstructorAccessor
:NativeConstructorAccessorImpl
and DelegatingConstructorAccessorImpl
:DelegatingConstructorAccessorImpl
simply delegates its work to another object stored in its field. The advantage of this approach is that it allows you to replace the implementation on the fly. This is exactly what is happening - NativeConstructorAccessorImpl
for each designer works maximum as many times as specified in the sun.reflect.inflationThreshold
system property (15 by default), after which it is replaced with GeneratedConstructorAccessor
. To be fair, it’s worth adding that setting the sun.reflect.noInflation
property to "true"
essentially resets inflationThreshhold
to zero, and NativeConstructorAccessorImpl
stops being created in principle. By default, this property is set to "false"
.So for the most ordinary class under the most ordinary circumstances, we would get an objectNativeConstructorAccessorImpl
, 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!
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:
new Class
without parentheses. It corresponds to the NEW
instruction, which allocates memory for the object, but the constructor does not call it;clazz.<init>(classLoader)
is just a constructor call that is not possible in this explicit form in Java.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.
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.
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!
Java 9 is pretty much the same. You can do all the same actions, but with a few reservations:
--add-exports java.base/jdk.internal.reflect=sample
to the compiler parameters (where sample is the name of your module);--add-opens java.base/jdk.internal.reflect=sample
--add-opens java.base/java.lang=sample
--add-opens java.base/java.lang.reflect=sample
--add-opens java.base/java.lang.invoke=sample
--add-opens java.base/jdk.internal.reflect=java.base
requires jdk.unsupported
;java.lang.Class
constructor has changed its signature, it must be taken into account.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.
MagicAccessorImpl
may not all;sun.misc.Unsafe
may not all, but almost;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