Years ago, Java could be used on websites trough applets. To make these applets secure and not let them access files or do other dangerous stuff, Java introduced the SecurityManager. Before some action was performed, the SecurityManager was asked if the code is privileged to perform this action. However, since the SecurityManager lives in the same running program and can be accessed via System.getSecurityManager(), there existed some ways to remove it.
Today, the SecurityManager still exists and might be used by some Java users to restrict the actions a Java program can perform. Hence, finding these bugs is not anymore that lucrative as they usually have a lower impact. Nevertheless, with every new release of Java, I take a look at some changes and see if there are new bugs of this sort and last year I was able to report two security bugs:
- Buffer Overflow with ByteBuffer (CVE-2020-2803)
- Mutable MethodType (CVE-2020-2805)
To be able to abuse these bugs, it is useful to know how to abuse type confusion within Java. A type confusion bug can occur when some Java code expects a certain type of object, but the code can be made to operate on some different type of object. For example, some code may operate on a String variable, but a bug exists such that it points to an Integer.
Type confusion bugs can be used to access private members of an object by duplicating the class without any method and setting all fields to public. One prime candidate is the java.lang.invoke.MethodHandles.Lookup
class. It has a private field private final int allowedModes;
, which specifies which methods can be looked up. A value of -1 allows everything. Therefore, the following class is used to get the same memory layout as the original Lookup class and to set all members to public.
class LookupMirror {
public Class<?> clasz;
public int perm;
}
If now a Lookup object is confused to a LookupMirror object, the allowedModes in the lookup object can be written to via the LookupMirror.perm
field and set to -1. Afterwards the Lookup object can be used to get a field setter for the private field System.security
, holding the SecurityManager instance and setting it to null.
MethodHandle mh = lookup.findStaticSetter(System.class, "security", SecurityManager.class);
mh.invoke(null);
Based on this method, it is possible to remove the SecurityManager with the following two vulnerabilities.
The first bug is a buffer overflow with ByteBuffer. The HeapByteBuffer’s slice method looks as follows:
public ByteBuffer slice() {
int rem = this.remaining();
return new HeapByteBuffer(hb,
-1,
0,
rem,
rem,
this.position() + offset);
}
This method has a problem with multithreading. If the position is changed between the remaining()
and position()
calls from the value 0 to the value limit()
, then remaining()
returns limit()
and position()
returns also limit()
. Hence, the returned buffer will read out of bounds. However, HeapByteBuffer is backed by a Java array and arrays do bound checks when they are accessed, so we changed the exception from an IndexOutOfBoundsException
to an ArrayIndexOutOfBoundsException
. But VarHandles can help here. The new VarHandleByteArrayAsInts was introduced recently which allows to view a ByteBuffer as an array of integers. It can be obtained by a call to MethodHandles.byteBufferViewVarHandle(int[].class, ByteOrder.nativeOrder())
. However, when it accesses an index, the index is checked against the ByteBuffer’s limit and then the array is accessed with Unsafe.getIntUnaligned
, which does no bounds checks.
static int get(ByteBufferHandle handle, Object obb, int index) {
ByteBuffer bb = (ByteBuffer) Objects.requireNonNull(obb);
return UNSAFE.getIntUnaligned(
UNSAFE.getReference(bb, BYTE_BUFFER_HB),
((long) index(bb, index)) + UNSAFE.getLong(bb, BUFFER_ADDRESS),
handle.be);
}
The same holds true for writing. It is then possible to allocate an object in the range where bytes can be changed through the ByteBuffer. It is quite helpful, that most Java heaps will allocate young objects in a linear buffer with pointer increment, so that objects from two consecutive allocations are next to each other in memory. It is then possible to do type confusion on the object by copying the value of a Lookup field to the location of a LookupMirror field. With this we have type confusion which can be used to remove the SecurityManger like described in the beginning.
The second bug is a problem in MethodType that makes the normally immutable type mutable. Since all API working with MethodType assumes that it is immutable a lot will break if it is mutated.
The problem is the deserialization of MethodType objects. It is intended that there is one instance per unique MethodType. When deserialized this is achieved by returning the unique object with the readResolve() method.
private Object readResolve() {
try {
return methodType(rtype, ptypes);
} finally {
// Re-assign defaults in case this object escapes
MethodType_init(void.class, NO_PTYPES);
}
}
However, this method is called on the temporary created MethodType from the deserialization.
private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException {
// Assign temporary defaults in case this object escapes
MethodType_init(void.class, NO_PTYPES);
s.defaultReadObject();
Class<?> returnType = (Class<?>) s.readObject();
Class<?>[] parameterArray = (Class<?>[]) s.readObject();
parameterArray = parameterArray.clone();
// Assign deserialized values
MethodType_init(returnType, parameterArray);
}
To make this temporary object behave in a clean way, it was decided to initialize it as a (void)void MethodType, then transition it to the type given by the deserialization and after the readResolve call back to a (void)void MethodType.
(Un)fortunately it is possible to get a reference to this temporary object with ObjectStreamInput references. These references are required to deserialize cyclic object graphs and allow to return unfinished objects. This gives the ability to have a mutating MethodType. This MethodType can then be used to transition a MethodHandle of any Type to a (void)void and then to any other MethodType by first casting the MethodHandle with .asType()
to the temporary object and after the temporary MethodType mutated to the unique MethodType, allowing for type confusion which can be used to remove the SecurityManager like described in the beginning.
These two vulnerabilities show how important Thread Safety and Mutability are for a secure computing environment and how easy it can crumble if they are broken.
11/22/2019: Initial report
11/26/2019: The vulnerability was acknowledged by Oracle
04/14/2020: Fixed in the Critical Patch Update.
Best,
Nils