Class Unfettered

java.lang.Object
dev.pfaff.unfettered.Unfettered

public final class Unfettered extends Object

Breaking free of the chains:

This class goes through a lengthy process to shatter the restraints of JPMS without requiring the end-user, launcher, or mod loader to inject any JVM args.

First, we get a reference to Unsafe.theUnsafe via reflection, which is not yet locked down by the ongoing encapsulation crusade.

Then, using this reference, we get the static field base and offset of MethodHandles.Lookup.IMPL_LOOKUP. As far as I am aware, this is the only instance of MethodHandles.Lookup with an allowedModes value of TRUSTED. We then check the OOP pointer width and, equipped with these, use our reference to Unsafe.theUnsafe to copy the OOP pointer stored in the static field into a 1-element array on the LVT.

Now might be a good time to interject and point out how pointless it is for the JDK to hide MethodHandles.Lookup.allowedModes (see MethodHandles.java line 1433: Reflection.registerFieldsToFilter(Lookup.class, Set.of("lookupClass", "allowedModes"));) from reflection without also hiding MethodHandles.Lookup.IMPL_LOOKUP.

Now equipped with an unrestricted MethodHandles.Lookup, we get a MethodHandle for Module.implAddExportsOrOpens(String, Module, boolean, boolean).

Phew, almost there! Now we get a reference to this and the java.base Modules via Class.getModule() (UnsafeUtil.class.getModule() and String.class.getModule()). Next, we use that handle we just got to export the jdk.internal.misc package in the java.base module to this module.

Finally, references to jdk.internal.misc.Unsafe#theUnsafe and a usable MethodHandles.Lookup (the one we borrowed earlier doesn't have access to any of our classes): For the former, we simply call jdk.internal.misc.Unsafe#getUnsafe(). It's a bit more complicated for the latter, but nothing compared to what we've accomplished thus far.

First, we set a static final field to the value of MethodHandles.lookup(). Then, we get the offset of the allowedModes field using our shiny new reference to jdk.internal.misc.Unsafe#theUnsafe and then, once again using jdk.internal.misc.Unsafe#theUnsafe, set the value to MethodHandles.Lookup.TRUSTED.

And that's it! We now have full access to the classpath and the memory space of the JVM.

  • Field Details

    • theJdkTrustedLookup

      public static final MethodHandles.Lookup theJdkTrustedLookup
    • C_Unsafe

      public static final Class<?> C_Unsafe
    • theUnsafe

      public static final Object theUnsafe
    • theTrustedLookup

      public static final MethodHandles.Lookup theTrustedLookup
      Method handle lookup with the allowedModes field set to MethodHandles.Lookup.TRUSTED, giving it unrestricted access to every class in the JVM.

      Note that this will not be trusted until part of the way through static init

    • COMPRESSED_OOP_SHIFT

      public static final int COMPRESSED_OOP_SHIFT
    • OBJECT_HEADER_SIZE

      public static final int OBJECT_HEADER_SIZE
      The header size of an object instance, excluding the extra 4 bytes representing an array's length.
  • Constructor Details

    • Unfettered

      public Unfettered()
  • Method Details

    • getTrustedLookup

      public static MethodHandles.Lookup getTrustedLookup()
    • forceInit

      public static void forceInit()
      Forces this class to be static-initialized. Calling this method more than once will have no effect. Calling this method is not even necessary before using any of the exposed unsafe APIs.
    • export

      public static void export(Module module, String pkgName, boolean opens, Module target)
      Exports the pkgName from the module to target. If opens is true, then the target will have access to package-private members in addition to public ones.
    • pointerWidth

      public static int pointerWidth()
      The width of a native pointer.
    • oopWidth

      public static int oopWidth()
      Width of an Ordinary Object Pointer as stored in the JVM. These may be compressed (see areOopsCompressed() on 64-bit JVMs.
    • klassPtrWidth

      public static int klassPtrWidth()
      Width of a klass pointer.
    • unexpectedOopWidth

      public static AssertionError unexpectedOopWidth()
    • areOopsCompressed

      public static boolean areOopsCompressed()
      When true, the JVM is using compressed pointers (this is the default). This will only be true on 64-bit JVMs.
    • areKpsCompressed

      public static boolean areKpsCompressed()
    • getOopOfObject

      public static long getOopOfObject(Object obj)
      Gets the OOP of the object. If CompressedOops is enabled (see areOopsCompressed()) then the returned value will not be valid native pointer. To make it so, use uncompressOop(long).
    • getUncompressedOopOfObject

      public static long getUncompressedOopOfObject(Object obj)
      Gets the OOP of the object and decompresses it if it is compressed.
    • getOopOfObjectAssumeCompressed

      public static int getOopOfObjectAssumeCompressed(Object obj)

      Gets the OOP of the object. If CompressedOops is enabled (see areOopsCompressed()) then the returned value will not be valid native pointer. To make it so, use uncompressOop(long).

      You probably want getOopOfObjectAssume32bit(Object) instead.

    • getOopOfObjectAssume32bit

      public static int getOopOfObjectAssume32bit(Object obj)
      Gets the OOP of the object, assuming that it is 32-bits wide.
    • getOopOfObjectAssume64bit

      public static long getOopOfObjectAssume64bit(Object obj)
      Gets the OOP of the object, assuming that it is 64-bits wide.
    • dereferenceOop

      public static Object dereferenceOop(long oop)
      Dereferences an OOP.
    • dereferenceOopAssumeCompressed

      public static Object dereferenceOopAssumeCompressed(int oop)
      Dereferences an OOP, assuming that it is compressed.
    • dereferenceOopAssume32bit

      public static Object dereferenceOopAssume32bit(int oop)
      Dereferences an OOP, assuming that it is 32-bits wide.
    • dereferenceOopAssume64bit

      public static Object dereferenceOopAssume64bit(long oop)
      Dereferences an OOP, assuming that it is 64-bits wide.
    • uncompressOop

      public static long uncompressOop(long oop)
      If CompressesOops are enabled (see areOopsCompressed()), returns the oop with compression removed. Otherwise, returns the oop itself.
    • compressOop

      public static long compressOop(long oop)
      If CompressesOops are enabled (see areOopsCompressed()), returns the oop with compression applied. Otherwise, returns the oop itself.
    • maybeUncompressPtr

      public static long maybeUncompressPtr(long oop, boolean enableCompression)
      If enableCompression, returns the ptr with compression removed. Otherwise, returns the ptr itself.
    • maybeCompressPtr

      public static long maybeCompressPtr(long oop, boolean enableCompression)
      If enableCompression, returns the ptr with compression applied. Otherwise, returns the ptr itself.
    • classMaxFieldOffset

      public static long classMaxFieldOffset(Class<?> clazz)
      Returns the maximum offset of the class's fields. This is not an efficient operation. Callers are strongly encouraged to calculate the result once and store it for reuse.
    • classFieldsLength

      public static long classFieldsLength(Class<?> clazz)
      Returns the total length of all the class's fields. This is not an efficient operation. Callers are strongly encouraged to calculate the result once and store it for reuse.
    • getKlassPointer

      public static long getKlassPointer(long oop)