Violating Java's privacy

Fernando
Fernando

I found myself in the need to invoke a private method of a Java class that was out of my control. I really needed it.

So, I went ahead and violated the method's privacy declaration via reflection. You can (under certain circumstances) invoke methods which are declared as private using the Reflection APIs (java.lang.reflect).

But before using reflection, I created two classes:

class A {
  public String method1() {
    return "Hello World!";
  }
}

class B {
  public static void main(String[] args) {
    A a = new A();
    System.out.println(a.method1());
  }
}

They compiled, and java B said "Hello World!" as expected.

Then, I made A's method private and recompiled A. And I run java B again. Nothing changed. It just worked again.

That cannot be right. This would mean you can handcraft a class with the same name and methods as the original one but making everything public. Then you could use this class only at compilation time, allowing your code to call any method. (Or, one could modify the Java compiler to ignore access declarations altogether).

Returning to the reflection APIs for a sec., here's how I called private method ProcessService.getSession(String):

Class clazz = ProcessService.class;
Class[] params = new Class[] { String.class };
Method m = clazz.getDeclaredMethod("getSession", params);

m.setAccessible(true); // allow access, as if it were public

Object session = m.invoke(this, new Object[] {sessionId});

The Java compiler enforces access restrictions at compilation time. But it's the Security Manager (an object of type java.lang.SecurityManager) the man on duty at runtime. The above code works because, by default, the JVM does not use a SecurityManager.

For the curious, the implementation for method "setAccessible(boolean)" (part of the JDK) looks like:

public void setAccessible(boolean flag) throws SecurityException {
  SecurityManager sm = System.getSecurityManager();
  if (sm != null) sm.checkPermission(ACCESS_PERMISSION);
  setAccessible0(this, flag);
}

It simply asks the current SecurityManager for permission before actually changing the "accessible" property. In general, before any "sensitive" operation, the code in the JDK asks the SecurityManager for permission.

You enable the default SecurityManager passing -Djava.security.manager to the java command. This would make the first code snipet above to throw an AccessControlException. Both methods Class.getDeclaredMethod() and Method.setAccessible() are guarded by the SecurityManager.

To allow the above use of reflection with the SecurityManager enabled, one must define a security policy for explicitly permitting those operations.

Anyway, back to my original concern of class B being able to invoke class A's private method directly. I could try to run it with the SecurityManager enabled:

$ java -Djava.security.manager B
Hello World!

It still works. So, I run the same test but from an applet (a more restricted container):

import java.awt.Label;
import java.applet.Applet;
public class BApplet extends Applet
{
  public void start() {
    A a = new A();
    add(new Label(a.method1()));
  }
}

<html>
  <body>
    <applet width="300" height="300" code="BApplet.class">
    </applet>
  </body>
</html>

And now it did fail, finally:

$ appletviewer BApplet.html
java.lang.IllegalAccessError: tried to access method
                  A.method1()Ljava/lang/String; from class BApplet
  at BApplet.start(BApplet.java:7)
  at sun.applet.AppletPanel.run(AppletPanel.java:414)
  at java.lang.Thread.run(Thread.java:595)

For this particular type of runtime checks, the Security Manager is clearly not involved. Nothing shows up in the StackTrace. It happens at a lower level, in the JVM itself. Makes sense now: it would be really inefficient to invoke the SecurityManager for every non-public method call... (and who'd check the calls to the SecurityManager itself!?).

But still, Why does it work with the java command, but fails within an applet?

To help with performace, the JVM only enforces these checks on classes loaded by custom classloaders. Classes loaded by the standard class loaders (bootstrap, extensions and System) are considered trusted. There are "levels" of trust actually, (bootstrap being the most trusted), but for this particular case, even the System (classpath) class loader trusted my class.

Inside the JVM covers the JVM internals extensively, including security and class loaders.