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.
Buffer Overflow with ByteBuffer (CVE-2020-2803)
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.
Mutable MethodType (CVE-2020-2805)
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.
Timeline
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