@Potential implementer of below trick BEWARE!

While I tried to be smart by "injecting" special java agent powers into designated trusted class, the presented mechanism is NOT SAFE as I identify the class only by it's name. An attacker might create it's own pair of classes with same names (runtime.Opener, runtime.Opener$Holder) loaded in some isolated class loader and the presented agent will happily inject the powers into the attacker's runtime.Opener$Holder. For safe variant of such agent, the Opener$Holder class would have to prove the authenticity to the Agent 1st. The mechanism of such authentication will be left as an exercise to the reader...

Regards, Peter

On 01/22/2018 02:04 PM, Peter Levart wrote:
Hi Rony,

On 01/22/2018 10:58 AM, Peter Levart wrote:
The 2nd problem is not trivial as you want to access a protected member on behalf of some other sub-class of the member's declaring class which is not cooperating (voluntarily handing you an instance of its Lookup object). This currently requires the package containing the member's declaring class to be opened at least to you (the Rexx interpreter) and using the member.setAccessible(true) trick or MethodHandles.privateLookupIn(declaringClass) equivalent for method handles. Which is awkward because libraries packed as modules would normally not specify that in their module descriptors and system modules don't either. So you are left with either --add-opens command line switches or deploying a javaagent to the JVM and using it's API point java.lang.instrument.Instrumentation#redefineModule to add opens to modules that way. Both approaches are not elegant, but that's what is currently available, I think.

Just one more thing... While solutions for tackling the 2nd problem might seem attractive to use for solving the 1st problem too, I would recommend not doing that. Opening all the packages of public API(s) might inhibit possible optimizations John Rose has been talking about. For reflective access to public API(s) you don't need to open the packages because public API(s) are in exported packages and all the "static" types that are needed to access them (field types, method return an parameter types) are also guaranteed to be part of public API(s) (at least good modules guarantee that). Public API(s) are transitively public. For public API(s) it is just a matter of finding the accessible member in the hierarchy where there will always be at least one.

For the 2nd problem, the main difficulty seems to be how to open just the packages that are involved in accessing the protected members on behalf of subclasses hoping that those packages are in minority. Here's one trick by using javaagent. Suppose your Rexx runtime had the following nonpublic class in its heart:

package runtime;

import java.util.function.BiConsumer;

class Opener {
    private static class Holder {
        static BiConsumer<Class<?>, Module> opener;
    }

    static void openPackageOfTo(Class<?> clazz, Module module) {
        Holder.opener.accept(clazz, module);
    }
}


Now if you start the JVM by supplying the -javaagent:agent.jar command line in addition to everything else and pack the following compiled code into agent.jar with the following MANIFEST:

Manifest-Version: 1.0
Premain-Class: agent.Agent

---
package agent;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.lang.reflect.Field;
import java.security.ProtectionDomain;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;

public class Agent {
    private static final String OPENER_BINARY_CLASS_NAME = "runtime/Opener";     private static final String HOLDER_CLASS_NAME = "runtime.Opener$Holder";
    private static final String OPENER_FIELD_NAME = "opener";

    private static Instrumentation instrumentation;

    public static void premain(String agentArgs, Instrumentation inst) {
        instrumentation = inst;
        inst.addTransformer(new ClassFileTransformer() {
            @Override
            public byte[] transform(Module module,
                                    ClassLoader loader,
                                    String className,
                                    Class<?> classBeingRedefined,
                                    ProtectionDomain protectionDomain,
                                    byte[] classfileBuffer) throws IllegalClassFormatException {

                // when runtime.Opener starts loading...
                if (className.equals(OPENER_BINARY_CLASS_NAME) && classBeingRedefined == null) {
                    try {
                        // ...load runtime.Opener$Holder upfront using the same classloader                         Class<?> holderClass = Class.forName(HOLDER_CLASS_NAME, true, loader);
                        // find the runtime.Opener$Holder#opener field
                        Field openerField = holderClass.getDeclaredField(OPENER_FIELD_NAME);
                        // and make it accessible
                        openerField.setAccessible(true);
                        // inject the BiConsumer
                        openerField.set(null, (BiConsumer<Class<?>, Module>) Agent::openPackageOfTo);
                    } catch (ReflectiveOperationException e) {
                        throw new InternalError(e);
                    }
                }

                // perform no actual transformation
                return null;
            }
        }, false);
    }

    static void openPackageOfTo(Class<?> clazz, Module module) {
        String pn = clazz.getPackageName();
        System.out.println("Opening package " + pn + " to " + module);
        instrumentation.redefineModule(
            clazz.getModule(),
            Set.of(),
            Map.of(),
            Map.of(pn, Set.of(module)), // extra opens
            Set.of(),
            Map.of()
        );
    }
}


With such Rexx runtime specific helper agent jar you can extend the controlled power of java agent to your Rexx interpreter so you now have the power to dynamically add just those opens that are absolutely necessary for performing the accesses to protected members.


Regards, Peter


Reply via email to