Description
Description of the issue
The sun.misc.Unsafe
class provides methods for directly accessing memory using memory addresses. It is sometimes used in libraries to achieve better performance compared to equivalent Java memory access.
From a security perspective Unsafe
is interesting because if the memory address is incorrect you can crash the JVM (or possibly worse?). This also makes other bug patterns, such as numeric overflow, interesting in this context.
For example:
var field = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
var unsafe = (sun.misc.Unsafe) field.get(null);
// Crashes the JVM
long badAddress = 1000000000L;
unsafe.getInt(badAddress);
What do you think about adding models and a CodeQL class which represents these sinks? Maybe not directly as part of a query (yet) but in a FlowSinks.qll
library (similar to the existing FlowSources.qll
).
There is already sun.misc.model.yml
but it does not cover Unsafe
yet.
Possibly this could also be combined with (future) sinks for other APIs which take native memory addresses as arguments.
Here is my hacky CodeQL code to match these sun.misc.Unsafe
sinks:
UnsafeSink
class UnsafeSink extends DataFlow::Node {
UnsafeSink() {
exists(MethodAccess c, Method m, int argIndex |
m = c.getMethod() and
m.getDeclaringType().hasQualifiedName("sun.misc", "Unsafe") and
c.getArgument(argIndex) = this.asExpr()
|
m.getName().matches(["get%", "put%"]) and
(
m.getParameterType(0) instanceof TypeObject and argIndex = 1
or
m.getParameterType(0).hasName("long") and argIndex = 0
)
or
m.hasName("allocateMemory") and argIndex = 0
or
m.hasName("reallocateMemory") and argIndex = [0, 1]
or
m.hasName("setMemory") and
(
m.getParameterType(0) instanceof TypeObject and argIndex = [1, 2]
or
m.getParameterType(0).hasName("long") and argIndex = [0, 1]
)
or
m.hasName("copyMemory") and
(
m.getParameterType(0) instanceof TypeObject and argIndex = [1, 3, 4]
or
m.getParameterType(0).hasName("long") and argIndex = [0, 1, 2]
)
or
m.hasName("freeMemory") and argIndex = 0
or
m.getName().matches("compareAndSwap%") and argIndex = 1
or
m.getName().matches(["get%Volatile", "put%Volatile"]) and argIndex = 1
or
m.getName().matches("putOrdered%") and argIndex = 1
or
m.getName().matches(["getAndAdd%", "getAndSet%"]) and argIndex = 1
)
}
}
Affected methods (memory address)
getX(Object, ...)
/ putX(Object, ...)
:
Method | Arg index |
---|---|
getInt(Object, ...) |
1 |
putInt(Object, ...) |
1 |
getObject(Object, ...) |
1 |
putObject(Object, ...) |
1 |
getBoolean(Object, ...) |
1 |
putBoolean(Object, ...) |
1 |
getByte(Object, ...) |
1 |
putByte(Object, ...) |
1 |
getShort(Object, ...) |
1 |
putShort(Object, ...) |
1 |
getChar(Object, ...) |
1 |
putChar(Object, ...) |
1 |
getLong(Object, ...) |
1 |
putLong(Object, ...) |
1 |
getFloat(Object, ...) |
1 |
putFloat(Object, ...) |
1 |
getDouble(Object, ...) |
1 |
putDouble(Object, ...) |
1 |
getX(long)
/ putX(long, ...)
:
Method | Arg index |
---|---|
getByte(long) |
0 |
putByte(long, ...) |
0 |
getShort(long) |
0 |
putShort(long, ...) |
0 |
getChar(long) |
0 |
putChar(long, ...) |
0 |
getInt(long) |
0 |
putInt(long, ...) |
0 |
getLong(long) |
0 |
putLong(long, ...) |
0 |
getFloat(long) |
0 |
putFloat(long, ...) |
0 |
getDouble(long) |
0 |
putDouble(long, ...) |
0 |
getXVolatile
/ putXVolatile
:
Method | Arg index |
---|---|
getObjectVolatile |
1 |
putObjectVolatile |
1 |
getIntVolatile |
1 |
putIntVolatile |
1 |
getBooleanVolatile |
1 |
putBooleanVolatile |
1 |
getByteVolatile |
1 |
putByteVolatile |
1 |
getShortVolatile |
1 |
putShortVolatile |
1 |
getCharVolatile |
1 |
putCharVolatile |
1 |
getLongVolatile |
1 |
putLongVolatile |
1 |
getFloatVolatile |
1 |
putFloatVolatile |
1 |
getDoubleVolatile |
1 |
putDoubleVolatile |
1 |
Method | Arg index |
---|---|
getAddress |
0 |
putAddress |
0 |
reallocateMemory |
0 |
setMemory(Object, ...) |
1 |
setMemory(long, ...) |
0 |
copyMemory(Object, ...) |
1, 3 |
copyMemory(long, ...) |
0, 1 |
freeMemory |
0 |
compareAndSwapObject |
1 |
compareAndSwapInt |
1 |
compareAndSwapLong |
1 |
putOrderedObject |
1 |
putOrderedInt |
1 |
putOrderedLong |
1 |
getAndAddInt |
1 |
getAndAddLong |
1 |
getAndSetInt |
1 |
getAndSetLong |
1 |
getAndSetObject |
1 |
Affected methods (memory segment length)
These sinks specify the length of a memory segment to copy or set. Similarly to the memory address sinks above they also allow crashing the JVM, but maybe they cannot be used that easily for other attacks (?).
Method | Arg index |
---|---|
setMemory(Object, ...) |
2 |
setMemory(long, ...) |
1 |
copyMemory(Object, ...) |
4 |
copyMemory(long, ...) |
2 |
Affected methods (memory allocation size)
Maybe this goes more in the direction of #45821, but on the other hand the Unsafe
documentation says some safety checks might be omitted, so maybe for invalid values these methods can also cause other incorrect behavior?
Method | Arg index |
---|---|
allocateMemory |
1 |
reallocateMemory |
1 |
Footnotes
-
I am not completely sure about the status of that PR and whether these sinks have already been covered by the models in the meantime. Sorry if my review comments on that PR back then were too disruptive and prevented that PR from getting merged. ↩