Reflection: how does one access a protected member in a superclass reflectively?

Peter Levart peter.levart at gmail.com
Mon Jan 22 13:33:03 UTC 2018


@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
>



More information about the jigsaw-dev mailing list