Insert a custom class into Bootstrap Classloader into java.base module

Viewed 211

I'm trying to insert my custom class to Bootstrap Classloader to be able to call sun.nio.ch.IOUtil#fdVal(FileDescriptor) to get the value of the FileDescriptor and use it in my Advice (I wrapped sun.nio.ch.FileChannelImpl)

I successfully inserted my pbouda.agents.core.Utils into the bootstrap classloader.

public abstract class Utils {

    public static final int ERROR_FD_VALUE = Integer.MIN_VALUE;

    private static Method FD_VALUE_METHOD;

    static {
        try {
            Class<?> clazz = Class.forName("sun.nio.ch.IOUtil");
            FD_VALUE_METHOD = clazz.getMethod("fdVal", FileDescriptor.class);
        } catch (ClassNotFoundException | NoSuchMethodException e) {
            e.printStackTrace();
        }
    }

    public static int fdVal(FileDescriptor fd) {
        if (FD_VALUE_METHOD != null) {
            try {
                return (int) FD_VALUE_METHOD.invoke(null, fd);
            } catch (IllegalAccessException | InvocationTargetException e) {
                e.printStackTrace();
            }
        }

        return ERROR_FD_VALUE;
    }
}

using

public abstract class UtilsInitializer {

    private static boolean INITIALIZED = false;

    public static synchronized void initialize() {
        if (!INITIALIZED) {
            TypePool typePool = TypePool.Default.ofSystemLoader();

            Map<TypeDescription, byte[]> types = new ByteBuddy()
                    .redefine(
                            typePool.describe("pbouda.agents.core.Utils").resolve(),
                            ClassFileLocator.ForClassLoader.ofSystemLoader())
                    .make()
                    .getAllTypes();

            ClassInjector.UsingUnsafe.ofBootLoader()
                    .inject(types);

            INITIALIZED = true;
        }
    }
}

I've proved it!

~ jcmd 281091 VM.classloaders show-classes                                                                                         [21/09/21|11:50AM]
281091:
+-- <bootstrap>
     |     
     |               Classes: java.lang.Object
     |                        [Ljava.lang.Object;
     |                        [[Ljava.lang.Object;
     |                        java.io.Serializable
...
     |                        pbouda.agents.core.Utils
     |                        (1522 classes)
     |     
     |        Hidden Classes: java.lang.invoke.LambdaForm$DMH/0x0000000800d89c00
     |                        java.lang.invoke.LambdaForm$DMH/0x0000000800d89800

Unfortunately, it's in a different module of course :(

     java.lang.IllegalAccessException: class pbouda.agents.core.Utils cannot access class sun.nio.ch.IOUtil 
     (in module java.base) because module java.base does not export sun.nio.ch to unnamed module @7e928e2f
        at java.base/jdk.internal.reflect.Reflection.newIllegalAccessException(Reflection.java:392)
        at java.base/java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:674)
        at java.base/java.lang.reflect.Method.invoke(Method.java:560)
      
        at pbouda.agents.core.Utils.fdVal(Utils.java:26)

      at java.base/sun.nio.ch.FileChannelImpl.open(FileChannelImpl.java:151)
        at java.base/sun.nio.fs.UnixChannelFactory.newFileChannel(UnixChannelFactory.java:134)
        at java.base/sun.nio.fs.UnixChannelFactory.newFileChannel(UnixChannelFactory.java:146)
        at java.base/sun.nio.fs.UnixFileSystemProvider.newByteChannel(UnixFileSystemProvider.java:217)
        at java.base/java.nio.file.spi.FileSystemProvider.newOutputStream(FileSystemProvider.java:484)
        at java.base/java.nio.file.Files.newOutputStream(Files.java:228)
        at java.base/java.nio.file.Files.newBufferedWriter(Files.java:3008)
        at java.base/java.nio.file.Files.newBufferedWriter(Files.java:3056)
        at pbouda.agents.file.FileTest.bufferedWriter(FileTest.java:38)

Do you know how to inject it into java.base to avoid running the program with add-opens ?

2 Answers

Any module predeclares its packages. In order to add something to java.base, you must add a class to a package that java.base already contains such as java.lang.

This is no longer possible using Instrumentation.appendToBootSearchPath as the packages of java.base are only directed to the jmod in question. Instead, you can open modules, including java.base by using the new modifyModule API.

If you really wanted to inject classes, this is still possible using Unsafe. Byte Buddy offers a factory for this in correspondance with its AgentBuilder. Have a look at ClassInjector.UsingUnsafe.Factory which offers to create such an injector. The Instrumentation instance is required to fall back to the JDKs internal Ùnsafe API after the semi-public one was removed.

Thanks Rafael!

I changed the package pbouda.agents.core.Utils -> sun.nio.ch.Utils

And changed the initializer and it works!! (Nasty but it's a good start for implementing a searcher of Leaked Descriptors :)

public abstract class UtilsInitializer {

    private static boolean INITIALIZED = false;

    public static synchronized void initialize(Instrumentation inst) {
        if (!INITIALIZED) {
            TypePool typePool = TypePool.Default.ofSystemLoader();

            Map<TypeDescription, byte[]> types = new ByteBuddy()
                    .redefine(
                            typePool.describe("sun.nio.ch.Utils").resolve(),
                            ClassFileLocator.ForClassLoader.ofSystemLoader())
                    .make()
                    .getAllTypes();

            ClassInjector.UsingUnsafe.ofBootLoader()
                    .inject(types);

            INITIALIZED = true;
        }
    }
}
Related