Private isn't always so private in Java
There's a few things I take for granted as a backend Java developer. IntelliJ > eclipse, by the time I get back to frontend JS - it's all changed again, and finally in Java, private is pretty private.
A private
method generally means that only the class that defined the method may call it. That said, Java does give you tools to poke holes in it from the start. Let's take the following as a starting point.
class Privacy {
private Privacy() {
System.out.println("Hi there");
}
}
Ths post aims to answer "how can we get the JVM to call the private constructor?"
Method One: Reflection
Using the reflection APIs this is simple.
public class Reflect {
public static void main(String... args) throws Exception {
Constructor<Privacy> constructor = (Constructor<Privacy>) Privacy.class.getDeclaredConstructors()[0];
constructor.setAccessible(true);
constructor.newInstance();
}
}
Lo and behold, the class is available and the instance is ready.
This is a feature of language, and can be guarded against if you don't want code to be using the reflection APIs. The following bans all reflection.
System.setSecurityManager(new SecurityManager() {
@Override
public void checkPermission(Permission perm) {
if (perm instanceof ReflectPermission) {
throw new SecurityException();
}
}
});
Method 2: Using inner classes
How else can we do it? Well, if you have access to the source code (and assuming we can't change private to public) then you can add an inner class.
class Outer {
private Outer() {
System.out.println("Hi there");
}
public static class Inner {
public void goForIt() {
new Outer();
}
}
}
In this case, we're very much able to run new Outer.Inner()
and cause the JVM to output the message. This structure may be familiar to you if you utilise a builder pattern. Often, a builder's build()
method will call straight back to the private constructor of the enclosing class.
What about a third way?
Let's assume for a moment that
- You're unable to use reflection
- You can't change any of the original source code.
- The class containing our private method we want to access has the following
- An inner class
- That inner class accesses the private member we desire (constructor/member)
Given that, let's start by taking a look at our example. This is the code that we want to try call the private constructor on. You can surely imagine that we very much would like to create a bank account with more than $100 starting balance.
class BankAccount {
private BankAccount(int amount) {
System.out.println("Bank balance: " + amount);
}
public static class BankAccountBuilder {
public void balance(int i) {
if (i > 100) throw new IllegalArgumentException("$100, yeah right");
new BankAccount(i);
}
}
}
That is the same structure as we've just had. There's an inner class that calls the private constructor. This time though, we're not going to go through the BankAccountBuilder
, we instead want to create our own instance without using the validation that's performed in the Builder.
These examples take places on the following machine. The overall method has been tested on the Oracle JDK, eclipse compiler, and the Open JDK. Though what you see in the rest of these sections are specific to Oracle, and will require slight adjustments for the other compilers.
Macbook pro 2015 - High Sierra
Oracle java version "1.8.0_144"
Java(TM) SE Runtime Environment (build 1.8.0_144-b01)
Java HotSpot(TM) 64-Bit Server VM (build 25.144-b01, mixed mode)
How does the inner class work?
To get started with this investigation, we can look at what's actually going on in the inner class. In the simplest case, let's take a quick peek at the bytecode of the following MVP.
class Outer {
private Outer() {
}
public static class Inner {
new Outer();
}
}
Let's compile.
javac Outer.java
When compiling an inner class, Java will actually create a separate class file for the inner class. The name of this class is <Name of outer class>__aSyNcId_<_nZbBXRRz__lt;Name of inner class>
. We can see this in the output
# ls | grep class
Outer.class
Outer$1.class
Outer$Inner.class
There's the expected outer class, the inner class. There is also an extra classfile here called Outer$1.class
.
As a note, if you change the constructor to protected
and compile again you'll notice something's missing. The Outer$1.class
file only exists when the private constructor is accessed. This is a clue as to how javac
resolves the fact that we need to access a private member.
So, that $1
class is getting generated all because of Java. Let's take a peek at it. To do this, we use the tool javap
that comes with each JDK installation.
javap -p -s -v <class>
- -p asks the tool to show private, protected, and public methods.
- -s includes the internal type signatures
- -v asks it to be verbose. This is good when you want to really dig in. You'll get the constant pool & the bytecode of the methods.
# javap -p -s -v Outer$1
class Outer$1
minor version: 0
major version: 52
flags: ACC_SUPER, ACC_SYNTHETIC
Constant pool:
<<<snipped>>>
{
}
SourceFile: "Outer.java"
EnclosingMethod: #6.#0 // Outer
InnerClasses:
static #1; //class Outer$1
See those opening, and closing braces? That's javap
's idea of the body of the class. In this case, there are no methods defined at all.
Do note the ACC_SYNTHETIC
, that means this class does not exist in the source code. Keep that in mind for later. Any time you see this flag, it means the Java compiler created something for us that we didn't directly write.
So this class seems to do absolutely nothing, yet is always created when accessing a private member from an inner class. This indicates that this is at least quite important to this functionality.
To find out, let's check out those other classes, starting with the Outer
class.
# java -p -s -v Outer
public class t.Outer
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
<snipped>
{
private t.Outer(); // First constructor
descriptor: ()V
flags: ACC_PRIVATE
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #2 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 4: 0
line 6: 4
t.Outer(t.Outer$1); // Second Constructor
descriptor: (LOuter$1;)V
flags: ACC_SYNTHETIC
Code:
stack=1, locals=2, args_size=2
0: aload_0
1: invokespecial #1 // Method "<init>":()V
4: return
LineNumberTable:
line 3: 0
}
And there we go, there's ACC_SYNTHETIC
on the second constructor. Let's break down interesting things about this:
- The flags do not include privacy. That means we're package private.
- The flags include
ACC_SYNTHETIC
so we definitely didn't write this constructor. - The descriptor attribute references the
$1
class in the following way:- L means an Object parameter, what comes next is the class.
Outer$1
; - V is a void return. This is expected.
- L means an Object parameter, what comes next is the class.
The body of the method is relatively simple as it;
- Loads the
self
reference - Calls the private constructor
- Finally returns
What this shows, is that javac
generated us a constructor that allows for classes in the same package to call the private constructor via this synthetic constructor. The descriptor (method params + return) change is clever as it ensures that there isn't a collision between the regular functions and the ones that javac
generated. We know this for sure because Outer$1
is a synthetic class and thus can't have been referenced in any prior source.
One more class to go. Let's check out the call site in the inner class.
# javap -p -s -v Outer\$Inner
public class t.Outer$Inner
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
<snipped>
{
public Outer$Inner();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=3, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: new #2 // class Outer
7: dup
8: aconst_null
9: invokespecial #3 // Method Outer."<init>":(LOuter$1;)V
12: pop
13: return
}
Alright, so let's beak down the constructor.
- 0: Load
this
- 1: Invoke the constructor for the superclass
Object
- 4: Create a new instance of the
Outer
class - 7: Duplicate the new instance on the stack. This is required for the
invokespecial
step. - 8: Load null into the stack
- 9: Call the synthetically created constructor
- 12: Pop the stack
- 13: Return
If you're wondering why there are breaks between the numbers, the labels at the start are byte offsets since the start of the method. An instruction can take more than 1 byte (eg, new
takes 2). So the jumps in the numbers are due to the fact that the instruction prior has taken more than a single byte.
So, when you have a
- public
- protected
- package private
method, javac has do to bugger all. It's when it's private that we have to jump through these hoops.
Who can call that?
So what gives Inner
the right to call that synthetic method? Is there a way that you can write code that'll allow a non-inner class to call it?
On the surface it seems the only requirements are to be able to call the constructor that takes the Outer$1
as a parameter and to be in the same package.
A difficulty arises though, as javac will not allow you to compile a java source file that references the $1 class. Or at least, I've been unable to.
There's nothing from stopping us from creating our own bytecode though. Perhaps we can create a class that'll allow us to open up this private constructor to the world.
To do this, we can use the java-asm library. This library is a bytecode manipulation framework. You can use it to manipulate existing class files, or even just create your own classes. Let's take our knowledge of how the inner class calls the synthetic method and utilise that.
Looping back for a moment, the core part of the bytecode of the synthetic call was
1: new Inner
2: dup
3: aconst_null
4: invokedynamic Inner.<init>(LOuter$1;)V
5: pop
6: return
What we want to do is insert that bytecode into a method of another class. In java-asm, that looks as follows. Here we're creating a class called Example
.
ClassWriter cw = new ClassWriter(0);
cw.visit(V1_1, ACC_PUBLIC, "Example", null, "java/lang/Object", null);
MethodVisitor mw = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null,
null);
mw.visitVarInsn(ALOAD, 0);
mw.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
mw.visitInsn(RETURN);
mw.visitMaxs(1, 1);
mw.visitEnd();
Firstly, we must create our constructor. Javac will create a constructor when compiling a source file, however when writing bytecode we must create a constructor explicitly.
Now, let's create another method that does contain the bytecode in the same way the inner class has.
mw = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "createBankAccount",
"(I)V", null, null);
mw.visitTypeInsn(NEW, "BankAccount");
mw.visitInsn(DUP);
mw.visitVarInsn(ILOAD, 0);
mw.visitInsn(ACONST_NULL);
mw.visitMethodInsn(INVOKESPECIAL, "BankAccount", "<init>", "(LBankAccount$1;)V", false);
mw.visitInsn(RETURN);
mw.visitMaxs(3, 2);
mw.visitEnd();
byte[] code = cw.toByteArray();
FileOutputStream fos = new FileOutputStream("Example.class");
fos.write(code);
fos.close();
We then write out the class by running the above snippets together.
We should be able to verify this class is valid by using javap
. If for some reason the class file was generated unsuccessfully, javap
will complain that there's some issues. Here's the output, not including the method bodies
# javap -v -s -p Example
...
public static void createBankAccount(int);
descriptor: (I)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=2, args_size=1
0: new #13 // class BankAccount
3: dup
4: iload_0
5: aconst_null
6: invokespecial #15 // Method BankAccount."<init>":(ILBankAccount$1;)V
9: return
...
We should now be able to use our class file.
class Runner {
public static void main(String... args) {
Example.createBankAccount(100_000);
}
}
We then run javac Runner .java
. This should compile successfully! Doing so shows we've been able to create code that has compiled against our generated bytecode.
From here, run it with java Runner
.
# java Runner
Your bank balance is $100000
Your console should now print the amount that you've asked for, even though it the constructor is private. To the jvm, you've only called a package-private method, and that's considered perfectly fine.
The implications of this is that if you're able to insert your own class into a package containing a outer/inner pair, then you're also able to call any private parts of the outer class that the inner class references.
This also holds for private methods. In my instance, calling a private method will create an access$xxx
synthetic method, where x are digits. These are created somewhat deterministically, so a rogue user would have little difficulty figuring out what to call when generating their own class file.
Finally, there's no requirement for any part of the inner class to be public for this technique to be utilised. An entirely private inner class still opens up the holes in the exact same way. This can lead to a false sense of security if steps aren't taken to mitigate this approach.
Overall
When utilising the outer/inner class pattern, you have to be prepared for the fact that javac
is creating holes in the privacy of certain parts of your code. This is by design, when querying Oracle they said it is a known and expected behaviour. So this isn't a crazy security problem in the JDK, it's just an interesting side effect of how Java manages to create a pattern that we all very much enjoy when using patterns such as the Builder
.
If you're running an application, this could only be a problem if you run user-code, and allow classes to be defined in pre-existing packages.
To mitigate this, when running untrusted code, you should ensure that the code does not define itself in a pre-existing package. Of course, if you're running untrusted code, this is probably number 100,000 on the priority listing of possible actual issues and this is mostly an exercise into the possibilities.
For now though, celebrate the fact that you've managed to call a private method/constructor (in another one of the many ways). That's pretty cool, and hopefully you've learnt a little bit about how Java abstracts these language features.