Here are some motivators for this post.
1. Wired recently featured a piece on a new game called Portal. (Be sure to check out the awesome illustration here!) Essentially, players have warp guns that can bend the laws of physics: the guns create entry and exit portals though which the players can jump/dive. I'm not a puzzle gamer but this one looks neat. And, hey, it wowed Valve.
2. At the recent No Fluff Just Stuff conference, Jeff Brown gave a talk on Groovy's Meta Object Protocol. It dawned on me, and perhaps others, that just because things compile to JVM bytecode doesn't mean that all things are equal. For example, if you add a method to a class using Groovy, what does it mean if you access it via Java? By the end of the talk, you could sense everyone struggle with these warp portals on the JVM. We literally had to warp -- er, wrap -- our mind around the multiple dimensions of bytecode.
3. Eric Burke recently posted some Java/reflection behaviour that he found surprising (in the spirit of the excellent book, Java Puzzlers, by Joshua Bloch and Neal Gafter).
All of this adds up to some recent experimentation with Java, Groovy, and the SecurityManager.
These examples use Java 1.5 and Groovy 1.1 RC 1. If you want to follow along, the CLASSPATH contains only the local dir "." and the Groovy jar, groovy-all-1.1-rc-1.jar.
No Warp: Straight Java
To add some fantasy to this article, the JVM is a Temple, which contains a Dragon. We are thieves and seek to capture some of the Dragon's breath, for immeasurable riches and formidable power. However we must elude the dreaded Gatekeepers, who stand vigilantly on guard. Here is a POJO:
public class Dragon {
private String catchBreath() {
return "essence of hot magic";
}
}
Clearly, with straight-up Java, the compiler prevents us from attaining the goal.
As Eric pointed out, we can do some reflection magic:
import java.lang.reflect.*;
public class PureJavaQuest {
public static void main(String[] args) throws Exception {
Method catchBreath =
Dragon.class.getDeclaredMethod("catchBreath");
catchBreath.setAccessible(true);
Dragon dragon = new Dragon();
System.out.println( catchBreath.invoke(dragon) );
}
}
This works fine. But if the Gatekeepers invoke the Spell of Java Security, we're stymied:
java -Djava.security.manager PureJavaQuest
Exception in thread "main" java.security.AccessControlException:
access denied (
java.lang.reflect.ReflectPermission suppressAccessChecks)
[ stack trace snipped ]
As an aside, the following security policy would allow us to succeed:
grant {
permission java.lang.reflect.ReflectPermission
"suppressAccessChecks";
};
Used like so (if the above were in my.security.policy):
java -Djava.security.manager
-Djava.security.policy=my.security.policy
PureJavaQuest
Warp One: Using Java with GroovyLet's write a Groovy script to use the Dragon class directly:
println "Are gatekeepers using spell? "
+ System.getProperty("java.security.manager")
Dragon dragon = new Dragon()
println dragon.catchBreath()
Here's the run:
$ groovy EasyQuest.groovy
Are gatekeepers using spell? null
essence of hot magic
It works! I believe this is due to
a bug in Groovy with respect to private access. (I'm ok with this because I suspect that it is a well-known issue that will be fixed soon). (Is this really a problem in Groovy 1.1 ? Am I missing something?)
Can we exploit this somehow? What happens if the Gatekeepers use the Spell of Java Security:
$ groovy.bat -Djava.security.manager EasyQuest.groovy
Are gatekeepers using spell? true
essence of hot magic
Hmm. The script implies that security is on, but it didn't seem to work. Though Groovy
documents security here, perhaps I'm missing something on the setup. Groovy does sophisticated class loading and on-the-fly mojo. I suspect that it might be using a privileged block, and reaching that because of a mistake of mine. (If you know what I've done wrong, please comment or email me at codetojoy @t gmail)
Warp Two: Groovy to JavaCan we exploit this behaviour using Java? Here's the Groovy class that is effectively the same as the above script:
public class Quest {
public String engage() {
Dragon dragon = new Dragon()
return dragon.catchBreath()
}
public static void main(String[] args) {
Quest quest = new Quest()
println quest.engage()
}
}
Two steps: we use
groovyc
to compile Quest to a class file. Then, we'll warp and run it with
java
(with classpath as described).
groovyc Quest.groovy
java Quest
essence of hot magic
It works... Not really surprising, given the previous behaviour. Let's try it with the Gatekeepers' spell:
java -Djava.security.manager Quest
Exception in thread "main"
java.security.AccessControlException: access denied (
java.lang.RuntimePermission accessDeclaredMembers)
[ stacktrace snipped ]
Blammo. Busted by the Gatekeepers. Do you feel the warp here? A Groovy class is compiled to bytecode and run via the
java
command. Clearly Groovy's magic ultimately boils down to reflection, and Java Security cuts to the chase by simply shutting down the call, no matter from whence it originated.
Hyper Warp: Adding MethodsOne more tactic... A standard trick for theft is to
cause a commotion. Distract, then act.
With blatant abuse of the
ExpandoMetaClass
, we can do just that.
The
ExpandoMetaClass
allows us to add (or
override) class methods in Groovy. Prepare to warp. The Temple is gonna get crazy.
If you look at the
main()
method below, it seems like an innocuous print statement. However, the
static
block is replacing the
toString
method
for the String class. This commotion throws a NPE with the truly weird comment that the JVM has crashed.
public class Commotion {
static {
ExpandoMetaClass.enableGlobally()
String.metaClass.toString = {
->
String msg = "JVM crash. Contact sys admin"
throw new NullPointerException(msg);
}
}
static public void main(String[] args) {
String s = new String("All is well in the Temple");
println s.toString();
}
}
Game on. Bring it, Gatekeepers!
Here is the output, when run as Groovy:
groovy Commotion
Caught: java.lang.NullPointerException: JVM crash. Contact sys admin
at Commotion$__clinit__closure1.doCall(Commotion.groovy:6)
at Commotion.main(Commotion.groovy:12)
As it turns out, the behaviour is the same as Warp Two: when compiled to bytecode, Java will throw the same nefarious error when run without the Security Manager (aka AccessContoller). With Java Security on, Java catches the reflection attempt.
ConclusionsDid we ultimately steal the Dragon's breath? To be honest, I don't think so: certainly not with the Java Security turned on. In pure Groovy, there are either some bugs or perhaps my configuration is not quite right (I'll report as soon as I find out what is going on.)
Also, remember that we had a Groovy jar (the big, embeddable jar) in the classpath. So things aren't quite as 'fluid' as these output snippets may imply.
What we
have done is seen the power of Java Security and, hopefully, bent some holes in your perception of the JVM.
Finally, all experimentation aside, we should strive to be Gatekeepers.
But beware: there
are thieves in the Temple. And they have warp guns.