Tuesday, March 10, 2009

Friends for Java v2

Here's a new attempt at implementing a secure version of something akin to the friend class concept in java, since the previous attempt was rather flawed in view of being used in a "neat'n'clean API" scenario.

This time I'm using a little introspection to try to catch evil code. Hopefully this check can't be circumvented, at least if a SecurityManager is in place, and also hopefully won't be stopped by any SecurityManager (it doesn't immediately seems so: one can use getClass() at any time, right? Can one also use the 1.5 getEnclosingClass() without being stopped?)

(If you use Eclipse, you can mark the entire code below (all classes in one go), Ctrl+C, then activate the project node of a project and hit Ctrl+V. You will probably have to organize imports afterwards).
package com.example;

import api.WantedCode;

public class NeedingCode {
// Driver
public static void main(String[] args) {
// .. we have an instance of this class
WantedCode code = new WantedCode();

// And access its package private method through a "friend-proxy"
FriendProxy.getProxy().invokeWantedMethodOf(code);
}
}

// ------------

package com.example;

import api.WantedCode;

public abstract class FriendProxy {

// :: Proxy interface method

public abstract void invokeWantedMethodOf(WantedCode instance);

// :: Friend infrastructure

private static FriendProxy _proxy;

public static void setProxy(FriendProxy proxy) {
// Verify that the origin of this proxy matches our expectations.
if (proxy.getClass().getEnclosingClass() != WantedCode.class) {
throw new IllegalAccessError("Only my friend can invoke this method.");
}
_proxy = proxy;
}

// :: Package-private accessor to get to friend's package private method.

static FriendProxy getProxy() {
return _proxy;
}
}

// ------------

package api;

import com.example.FriendProxy;

public class WantedCode {

void wantedMethod() {
System.out.println("Hi from the package-private method in the API!");
}

static {
FriendProxy.setProxy(new GivingAccessToNeedingCode());
}

private static class GivingAccessToNeedingCode extends FriendProxy {
@Override
public void invokeWantedMethodOf(WantedCode instance) {
instance.wantedMethod();
}
}
}

// ------------

package com.example;

import api.WantedCode;

/**
* Test whether the evilness is caught.
*/
public class EvilCode {
// Driver
public static void main(String[] args) {
WantedCode code = new WantedCode();
FriendProxy.setProxy(new FriendProxy() {
@Override
public void invokeWantedMethodOf(WantedCode instance) {
System.err.println("I'm evil!");
}
});
FriendProxy.getProxy().invokeWantedMethodOf(code);
}
}

6 comments:

  1. In a really paranoid scenario, an attacker would replace your WantedCode class, i.e. you wouldn't have a distinct EvilCode class, but instead attacking code would pretend to _be_ WantedCode infact. This could be done by hacking the classpath or similar. In this scenario your check would pass, and you would execute the evil code. The solution is to introduce a security permission, check for it in setProxy() and call setProxy() with privileges enabled (AccessController.doPrivileged() ). However, this requires a security policy to be set up, which makes it a big difficult to run as e.g. applet. I'm working on an example.
    ReplyDelete
  2. I thought that if you can manipulate the classpath, then pretty much all bets are off at any rate - you most probably also have control over the Security policy and -Manager then too?
    ReplyDelete
  3. Well no. When talking about security you should not assume things about the source of the code. For applets, I could think of a scenario where a fishing site uses your JAR (your applet jars can easily be downloaded and embedded in another site) and replaces one class inside (if not signed), OR prepend the classpath with the hacked class. Attackers can probably also play tricks with classloaders, so that the same class gets loaded again into the VM, but from a different source (yeah, this is possible). The only way to be really safe is using the security access control mechanisms of the VM, and sign your JAR. Unfortunately, this requires the _user_ to install an appropriate security policy (because you need to guard calls to setProxy()), which is quite unintuitive ('what? you need a policy to run your own code? wtf?').

    At least, you should also check for identical classloader in your setProxy() method. But this wouldn't help in the first scenario. I don't see an easy and practical solution for the super paranoid. But I guess this is why there is no 'friend' in the Java language, as this opens up a can of worms with respect to security.
    ReplyDelete
  4. BTW, when some evil person is able to sneak in patched classes, something is wrong anyway, even without the friends pattern (you want to sign your JAR then to ensure consistency). So maybe this is all just fluff and checking for the proxy to be set twice and checking the (enclosing) class of the proxy (or some other known unique property) plus checking the classloader is more the enough. And of course, you should check what kind of data/functionality you actually expose and if it is really security relevant.
    ReplyDelete
  5. Well, I still don't quite get it: I presume that if you are paranoid, you're running with a SecurityManager already (as Applets do). You'd also sign these jars. The idea of checking classloaders was interesting. However, what about the check against WantedClass.class: This will fail if we're not talking about the same class that NeedingClass has loaded itself.

    My point boils down to: If you are able to switch out the WantedClass in any way, what's the point of the friend security mechanisms in the first place? Isn't this really a) for making a pretty API (in the "normal case" when running java "command line" without SecurityManager, as in this case, then all bets are off: You can do ANYTHING), and b) when you're in a SecurityManaged mode, in which case you'd make it impossible to instantiate this class from anywhere else.

    If the question is what I can manipulate, I could just recompile the entire thing and use that?! I thought that your initial entry were basically trying to make a "friend" logic for the runtime classes - and I don't quite see how I could manipulate anything there without getting caught, in a security managed scenario.

    An example where you could bypass the suggested solution, in a relevant security-conscious world, would be great.
    ReplyDelete