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