raphw / byte-buddy

Runtime code generation for the Java virtual machine.
https://bytebuddy.net
Apache License 2.0
6.23k stars 804 forks source link

[Question] AgentBuilder.InitializationStrategy.SelfInjection's behaviour in detail #1608

Closed ganschen closed 6 days ago

ganschen commented 6 months ago

Background

I wrote a ThreadSubclassInterceptor to transform Thread's Subclass.

class ThreadSubclassInterceptor {
    public AgentBuilder install(AgentBuilder agentBuilder) {
        return agentBuilder
                .type(hasSuperType(named("java.lang.Thread"))
                        .and(not(named("java.lang.Thread").or(nameStartsWith("java.lang.ref")))))
                .transform((builder, typeDescription, classLoader, module) -> {
                    ...
                    return builder;
                });
    }

I ran the JDK test and exception thrown.

java.lang.ExceptionInInitializerError
    at java.base/java.lang.Class.forName0(Native Method)
    at java.base/java.lang.Class.forName(Class.java:467)
    at Security.setupProxy(Security.java:172)
    at Security.main(Security.java:447)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
    at java.base/java.lang.Thread.run(Thread.java:840)
Caused by: java.security.AccessControlException: access denied ("java.lang.RuntimePermission" "getClassLoader")
    at java.base/java.security.AccessControlContext.checkPermission(AccessControlContext.java:485)
    at java.base/java.security.AccessController.checkPermission(AccessController.java:1068)
    at java.base/java.lang.SecurityManager.checkPermission(SecurityManager.java:416)
    at java.base/java.lang.ClassLoader.checkClassLoaderPermission(ClassLoader.java:2060)
    at java.base/java.lang.ClassLoader.getSystemClassLoader(ClassLoader.java:1948)
    at ProxyServer.<clinit>(ProxyServer.java:56)
    ... 11 more

I found that it's because ProxyServer extends thread class so it will be instrumented, and because of AgentBuilder's default strategy, code below will be added.

    static {
  ClassLoader.getSystemClassLoader().loadClass("net.bytebuddy.dynamic.Nexus").getMethod("initialize", Class.class, Integer.TYPE).invoke((Object)null, ProxyServer.class, -429884501);
        ...
    }

While in the policy of the test, there is no getClassLoader permission, so the exception will be thrown.

I tried to change the strategy to InitializationStrategy.NoOp and it worked but still not sure if it's safe to do so.

Question

May i know In which case it's safe to change the InitializationStrategy to NoOp or Minimal? I tried to understand InitializationStrategy.SelfInjection's behaviour by reading the source code, looks like what it's trying to do is to call the LoadedTypeInitializer registered in the instrumented type and it's auxiliary types, but it's still not very clear to me what does those LoadedTypeInitializers do, can i get some explanation is detail or some examples? Thank you so much.

raphw commented 6 months ago

If any of your instrumentations requires dispatch to an object, it is required. But normally it is not. I will see if I can change it to detect this automatically. But unless you do something like MethodDelegation.to(new MyDispatcher()), it should be safe.

ganschen commented 6 months ago

Thank you for your explanation!