#ReflectiveAccessByInstrumentationAgents
Andrew Dinn
adinn at redhat.com
Thu May 5 16:31:55 UTC 2016
Hi Alan,
I'm finally replying to your previous post and also reporting news of
the solution I have currently investigated to retain existing Byteman
agent capabilities (I have a working implementation and am interested to
see how high it causes your eyebrows to rise :-).
> If the instrumentation is just injecting code to call into Byteman then
> I assume it just works now. That is, it doesn't appear that Byteman has
> embraced modules yet and so I assume it must be an agent specified via
> -javaagent with perhaps supporting code in other JAR files. In that
> case, the Byteman code is the unnamed module of the app class loader and
> the VM will automatically add a read edge when the transformer touches
> code in any named module. This is the compatibility support that I
> mentioned in another mail.
All the Byteman code which matters is in a single jar loaded using
-javaagent (or the VM_Attach API for dynamic load). The Main class
(implementing premain/agentmain) is completely isolated from the rest of
the code (i.e. no link references -- it loads by name and executes by
reflection).
That isolation is necessary in order to be able to inject into JDK
runtime classes. Main is loaded by the system classloader. However,
Main.premain/agentmain will 'hoist' the jar into the bootstrap path on
request, ensuring that the rest of the agent jar contents are loaded by
the bootstrap loader. In particular this means JDK classes can reference
the 3 Byteman exception types and 1 static method that appear in
injected code.
n.b. I have zero desire to make Byteman (Jigsaw-)modular. That would
definitely require a dual source implementation (since I need to keep it
working on JDK6+).
> If you right this it should be simple as you say. On the other hand,
> some of the mails mention accessing non-public members, setAccessible,
> and even (it would appear) adjusting class loader delegation on the fly
> to facilitate the patches.
Byteman is mostly used for
i) unit, integration and system tests
ii) monitoring/debugging apps -- normally (but not always :-) in a sandbox
In the former case, breaking the app (injection of code which
generates a fault scenario) or validating execution (injecting code
which asserts invariants) is often only only feasible if injected code
can access non-public APIs and/or data. In any significant size app the
stuff you need to check/call at test time is almost always stuff you
also want to hide behind an encapsulation barrier at runtime.
In the latter case identifying what is broken when trouble-shooting a
problem generally requires the same license. If you are lucky your error
might affect public code/data but you'd be very lucky.
So, if Jigsaw were able to remove the existing opportunity for an agent
to access non-public code/data then that would seriously limit the
usefulness of Byteman -- almost to the point of making it useless. Test
code can often get by in cases where there is public access to data
(modulo getting a handle on the relevant objects). It's precisely the
private stuff that cannot easily be tested.
The reason I use setAccessible in the current implementation is that it
is the only way for injected code to access private data, methods and
constructors. Field, Method and Constructor instances are used by the
interpreter (which executes reflectively) and also as a backdoor to
perform such accesses when Byteman translates injected code to bytecode.
I am not (yet) interested in the issue of injecting into non-public app
classes/methods embedded in Modules nor am I (yet) interested in
allowing injected code to access non-public data/methods of such app
classes. That's not really an important problem for me (yet) because no
such code exists. When I need to address this issue I hope to resolve
any problems by extending the existing module IMPORT syntax which is
used to notify Byteman that injected code needs access to classes (&
their methods/data) deployed in JBoss Modules.
My current concern is for injected code to retain the ability to access
non-public fields of classes residing in JDK modules, in particular
java.base but also the other modules into which the JDK code base has
been partitioned. This currently works on JDK8- and a lot of projects
are relying on it continuing to work in order to be able to test their code.
Luckily, java.base exports to any module and also exports many of the
packages which users tend to inject into (e.g. java.lang.Thread,
java.io.FileInputStream, etc). So ,the checks performed by method
AccessibleObject.checkCanSetAccessible(Class<?>,Class<?>()) tend to vote
yes for most attempts to execute setAccessible i.e. the legacy problem
is not as pressing as it appears at first glance. However, there will
still be cases where existing Byteman usage will fail on JDK9.
I looked at several ways of making this work and decided the best thing
was to have the agent redefine AccessibleObject.checkCanSetAccessible so
that it grants Byteman code (specifically one Byteman class called Rule)
free reign when it calls this method. This still retains security
manager checks made around this call. Choosing this method targets the
code which does module access checks.
Basically I transformed this method so that it looks like this
public boolean checkCanSetAccessible(Class<?> caller, Class<?>
declaringClass)
{
// injected code
if ("org.jboss.byteman.rule.Rule".equals(caller.getName()) {
if (caller.getClassLoader == Classloader.getSystemCalssLoader())
return true
}
}
// original code
Module callerModule = caller.getModule();
Module declaringModule = declaringClass.getModule();
. . .
n.b. when the agent code is loaded in the bootstrap the 2nd if compares
against null rather than the system loader. Either way the name+loader
check ensures that it is not possible to spoof calls from any old app
class called Rule. When this code is injected the oonly caller class
which will pass this initial test is Byteman's Rule class.
So, with that in place whenever the agent needs to grant access to a
Field/Method I ensure it calls calls method
Rule.grantAccess(AccessibleObject) which is only accessible from code
provided by the agent. The resulting accessible is private to agent Rule
instances and can only be accessed by the interpreter or by generated
bytecode when executing injected code. So, there is no danger of leaking
these access objects.
The nice thing is that I can do all this using only JDK6 compatible code
with no need for it to know about anything Jigsaw-related other than
whether class java.lang.reflect Module exists (thanks, Peter :-).
Now, I know setting an arbitrary Member accessible looks a tad dangerous
from the point of view that it might foil expectations of the JDK
runtime or JVM (from the point of view of app this is the users
responsibilty -- they ask for it they get it). However, I looked at the
rest of the code in method checkCanSetAccessible and noted that any
caller in the java.base module is allowed to set any Member to be
accessible. So, I adopted the working hypothesis that for now, at least,
making this method say 'yes' is not going to break anything that could
not already potentially be broken -- in particular it's not going to
foil the expectations of the JIT (yet? :-).
So, I'd be very interested to know how horrified you are to hear about
what I am doing and whether you have any caveats to throw my way :-)
regards,
Andrew Dinn
-----------
Senior Principal Software Engineer
Red Hat UK Ltd
Registered in England and Wales under Company Registration No. 03798903
Directors: Michael Cunningham, Michael ("Mike") O'Neill, Eric Shander
More information about the jigsaw-dev
mailing list