mirror of
https://github.com/corda/corda.git
synced 2025-01-18 02:39:51 +00:00
ENT-1906: Don't transform java.Object[] to sandbox.Object[]. (#4115)
* Don't transform java.Object[] to sandbox.Object[] because an array is still java.Object inside the sandbox. * Add extra test for sandboxing an array of int[]. * Ensure hashCode() is deterministic for Java arrays. * Remove some information about DJVM from some exception stack traces.
This commit is contained in:
parent
34cadc4264
commit
ff9061b968
@ -24,8 +24,12 @@ class AlwaysInheritFromSandboxedObject : ClassDefinitionProvider, Emitter {
|
||||
|
||||
override fun emit(context: EmitterContext, instruction: Instruction) = context.emit {
|
||||
if (instruction is TypeInstruction &&
|
||||
instruction.typeName == OBJECT_NAME) {
|
||||
instruction.typeName == OBJECT_NAME &&
|
||||
instruction.operation != Opcodes.ANEWARRAY &&
|
||||
instruction.operation != Opcodes.MULTIANEWARRAY) {
|
||||
// When creating new objects, make sure the sandboxed type gets used.
|
||||
// However, an array is always [java.lang.Object] so we must exclude
|
||||
// arrays from this so that we can still support arrays of arrays.
|
||||
new(SANDBOX_OBJECT_NAME, instruction.operation)
|
||||
preventDefault()
|
||||
}
|
||||
|
@ -0,0 +1,29 @@
|
||||
package net.corda.djvm.rules.implementation
|
||||
|
||||
import net.corda.djvm.code.Emitter
|
||||
import net.corda.djvm.code.EmitterContext
|
||||
import net.corda.djvm.code.Instruction
|
||||
import net.corda.djvm.code.instructions.MemberAccessInstruction
|
||||
import org.objectweb.asm.Opcodes.*
|
||||
|
||||
/**
|
||||
* We cannot wrap Java array objects - and potentially others - and so these would still
|
||||
* use the non-deterministic [java.lang.Object.hashCode] by default. Therefore we intercept
|
||||
* these invocations and redirect them to our [sandbox.java.lang.DJVM] object.
|
||||
*/
|
||||
class RewriteObjectMethods : Emitter {
|
||||
override fun emit(context: EmitterContext, instruction: Instruction) = context.emit {
|
||||
if (instruction is MemberAccessInstruction && instruction.owner == "java/lang/Object") {
|
||||
when (instruction.operation) {
|
||||
INVOKEVIRTUAL -> if (instruction.memberName == "hashCode" && instruction.signature == "()I") {
|
||||
invokeStatic(
|
||||
owner = "sandbox/java/lang/DJVM",
|
||||
name = "hashCode",
|
||||
descriptor = "(Ljava/lang/Object;)I"
|
||||
)
|
||||
preventDefault()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -43,14 +43,15 @@ fun Any.sandbox(): Any {
|
||||
private fun Array<*>.fromDJVMArray(): Array<*> = Object.fromDJVM(this)
|
||||
|
||||
/**
|
||||
* Use the sandbox's classloader explicitly, because this class
|
||||
* might belong to the shared parent classloader.
|
||||
* Use [Class.forName] so that we can also fetch classes for arrays of primitive types.
|
||||
* Also use the sandbox's classloader explicitly here, because this invoking class
|
||||
* might belong to a shared parent classloader.
|
||||
*/
|
||||
@Throws(ClassNotFoundException::class)
|
||||
internal fun Class<*>.toDJVMType(): Class<*> = SandboxRuntimeContext.instance.classLoader.loadClass(name.toSandboxPackage())
|
||||
internal fun Class<*>.toDJVMType(): Class<*> = Class.forName(name.toSandboxPackage(), false, SandboxRuntimeContext.instance.classLoader)
|
||||
|
||||
@Throws(ClassNotFoundException::class)
|
||||
internal fun Class<*>.fromDJVMType(): Class<*> = SandboxRuntimeContext.instance.classLoader.loadClass(name.fromSandboxPackage())
|
||||
internal fun Class<*>.fromDJVMType(): Class<*> = Class.forName(name.fromSandboxPackage(), false, SandboxRuntimeContext.instance.classLoader)
|
||||
|
||||
private fun kotlin.String.toSandboxPackage(): kotlin.String {
|
||||
return if (startsWith(SANDBOX_PREFIX)) {
|
||||
@ -139,6 +140,21 @@ private fun createEnumDirectory(clazz: Class<out Enum<*>>): sandbox.java.util.Ma
|
||||
private val allEnums: sandbox.java.util.Map<Class<out Enum<*>>, Array<out Enum<*>>> = sandbox.java.util.LinkedHashMap()
|
||||
private val allEnumDirectories: sandbox.java.util.Map<Class<out Enum<*>>, sandbox.java.util.Map<String, out Enum<*>>> = sandbox.java.util.LinkedHashMap()
|
||||
|
||||
/**
|
||||
* Replacement function for Object.hashCode(), because some objects
|
||||
* (i.e. arrays) cannot be replaced by [sandbox.java.lang.Object].
|
||||
*/
|
||||
fun hashCode(obj: Any?): Int {
|
||||
return if (obj is Object) {
|
||||
obj.hashCode()
|
||||
} else if (obj != null) {
|
||||
System.identityHashCode(obj)
|
||||
} else {
|
||||
// Throw the same exception that the JVM would throw in this case.
|
||||
throw NullPointerException().sanitise()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replacement functions for Class<*>.forName(...) which protect
|
||||
* against users loading classes from outside the sandbox.
|
||||
@ -161,7 +177,7 @@ fun classForName(className: kotlin.String, initialize: kotlin.Boolean, classLoad
|
||||
*/
|
||||
private fun toSandbox(className: kotlin.String): kotlin.String {
|
||||
if (bannedClasses.any { it.matches(className) }) {
|
||||
throw ClassNotFoundException(className)
|
||||
throw ClassNotFoundException(className).sanitise()
|
||||
}
|
||||
return SANDBOX_PREFIX + className
|
||||
}
|
||||
@ -200,7 +216,7 @@ fun fromDJVM(t: Throwable?): kotlin.Throwable {
|
||||
.newInstance(t) as kotlin.Throwable
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
RuleViolationError(e.message)
|
||||
RuleViolationError(e.message).sanitise()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -225,10 +241,20 @@ fun catch(t: kotlin.Throwable): Throwable {
|
||||
try {
|
||||
return t.toDJVMThrowable()
|
||||
} catch (e: Exception) {
|
||||
throw RuleViolationError(e.message)
|
||||
throw RuleViolationError(e.message).sanitise()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up exception stack trace for throwing.
|
||||
*/
|
||||
private fun <T: kotlin.Throwable> T.sanitise(): T {
|
||||
stackTrace = stackTrace.let {
|
||||
it.sliceArray(1 until findEntryPointIndex(it))
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Worker functions to convert [java.lang.Throwable] into [sandbox.java.lang.Throwable].
|
||||
*/
|
||||
@ -266,12 +292,16 @@ private fun Class<*>.createJavaThrowable(t: Throwable): kotlin.Throwable {
|
||||
}
|
||||
}
|
||||
|
||||
private fun sanitiseToDJVM(source: Array<java.lang.StackTraceElement>): Array<StackTraceElement> {
|
||||
private fun findEntryPointIndex(source: Array<java.lang.StackTraceElement>): Int {
|
||||
var idx = 0
|
||||
while (idx < source.size && !isEntryPoint(source[idx])) {
|
||||
++idx
|
||||
}
|
||||
return copyToDJVM(source, 0, idx)
|
||||
return idx
|
||||
}
|
||||
|
||||
private fun sanitiseToDJVM(source: Array<java.lang.StackTraceElement>): Array<StackTraceElement> {
|
||||
return copyToDJVM(source, 0, findEntryPointIndex(source))
|
||||
}
|
||||
|
||||
internal fun copyToDJVM(source: Array<java.lang.StackTraceElement>, fromIdx: Int, toIdx: Int): Array<StackTraceElement> {
|
||||
|
@ -1,6 +1,7 @@
|
||||
package net.corda.djvm;
|
||||
|
||||
import net.corda.djvm.execution.ExecutionSummaryWithResult;
|
||||
import net.corda.djvm.execution.SandboxException;
|
||||
import net.corda.djvm.execution.SandboxExecutor;
|
||||
import net.corda.djvm.source.ClassSource;
|
||||
|
||||
@ -13,12 +14,15 @@ public interface WithJava {
|
||||
try {
|
||||
return executor.run(ClassSource.fromClassName(task.getName(), null), input);
|
||||
} catch (Exception e) {
|
||||
if (e instanceof RuntimeException) {
|
||||
throw (RuntimeException) e;
|
||||
if (e instanceof SandboxException) {
|
||||
throw asRuntime(e.getCause());
|
||||
} else {
|
||||
throw new RuntimeException(e.getMessage(), e);
|
||||
throw asRuntime(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static RuntimeException asRuntime(Throwable t) {
|
||||
return (t instanceof RuntimeException) ? (RuntimeException) t : new RuntimeException(t.getMessage(), t);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,91 @@
|
||||
package net.corda.djvm.execution;
|
||||
|
||||
import net.corda.djvm.TestBase;
|
||||
import net.corda.djvm.WithJava;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import static net.corda.djvm.messages.Severity.WARNING;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
||||
|
||||
public class SandboxObjectHashCodeJavaTest extends TestBase {
|
||||
|
||||
@Test
|
||||
public void testHashForArray() {
|
||||
parentedSandbox(WARNING, true, ctx -> {
|
||||
SandboxExecutor<Object, Integer> executor = new DeterministicSandboxExecutor<>(ctx.getConfiguration());
|
||||
ExecutionSummaryWithResult<Integer> output = WithJava.run(executor, ArrayHashCode.class, null);
|
||||
assertThat(output.getResult()).isEqualTo(0xfed_c0de + 1);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHashForObjectInArray() {
|
||||
parentedSandbox(WARNING, true, ctx -> {
|
||||
SandboxExecutor<Object, Integer> executor = new DeterministicSandboxExecutor<>(ctx.getConfiguration());
|
||||
ExecutionSummaryWithResult<Integer> output = WithJava.run(executor, ObjectInArrayHashCode.class, null);
|
||||
assertThat(output.getResult()).isEqualTo(0xfed_c0de + 1);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHashForNullObject() {
|
||||
assertThatExceptionOfType(NullPointerException.class)
|
||||
.isThrownBy(() -> new HashCode().apply(null));
|
||||
|
||||
parentedSandbox(WARNING, true, ctx -> {
|
||||
SandboxExecutor<Object, Integer> executor = new DeterministicSandboxExecutor<>(ctx.getConfiguration());
|
||||
assertThatExceptionOfType(NullPointerException.class)
|
||||
.isThrownBy(() -> WithJava.run(executor, HashCode.class, null));
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHashForWrappedInteger() {
|
||||
parentedSandbox(WARNING, true, ctx -> {
|
||||
SandboxExecutor<Object, Integer> executor = new DeterministicSandboxExecutor<>(ctx.getConfiguration());
|
||||
ExecutionSummaryWithResult<Integer> output = WithJava.run(executor, HashCode.class, 1234);
|
||||
assertThat(output.getResult()).isEqualTo(Integer.hashCode(1234));
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHashForWrappedString() {
|
||||
parentedSandbox(WARNING, true, ctx -> {
|
||||
SandboxExecutor<Object, Integer> executor = new DeterministicSandboxExecutor<>(ctx.getConfiguration());
|
||||
ExecutionSummaryWithResult<Integer> output = WithJava.run(executor, HashCode.class, "Burble");
|
||||
assertThat(output.getResult()).isEqualTo("Burble".hashCode());
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
public static class ObjectInArrayHashCode implements Function<Object, Integer> {
|
||||
@Override
|
||||
public Integer apply(Object obj) {
|
||||
Object[] arr = new Object[1];
|
||||
arr[0] = new Object();
|
||||
return arr[0].hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
public static class ArrayHashCode implements Function<Object, Integer> {
|
||||
@SuppressWarnings("all")
|
||||
@Override
|
||||
public Integer apply(Object obj) {
|
||||
return new Object[0].hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
public static class HashCode implements Function<Object, Integer> {
|
||||
@Override
|
||||
public Integer apply(Object obj) {
|
||||
return obj.hashCode();
|
||||
}
|
||||
}
|
||||
}
|
@ -49,6 +49,7 @@ abstract class TestBase {
|
||||
HandleExceptionUnwrapper(),
|
||||
ReturnTypeWrapper(),
|
||||
RewriteClassMethods(),
|
||||
RewriteObjectMethods(),
|
||||
StringConstantWrapper(),
|
||||
ThrowExceptionWrapper()
|
||||
)
|
||||
|
@ -733,4 +733,32 @@ class SandboxExecutorTest : TestBase() {
|
||||
return cl.find()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test creating arrays of arrays`() = parentedSandbox {
|
||||
val contractExecutor = DeterministicSandboxExecutor<Any, Array<Any>>(configuration)
|
||||
contractExecutor.run<ArraysOfArrays>("THINGY").apply {
|
||||
assertThat(result).isEqualTo(arrayOf(arrayOf("THINGY")))
|
||||
}
|
||||
}
|
||||
|
||||
class ArraysOfArrays : Function<Any, Array<Any>> {
|
||||
override fun apply(input: Any): Array<Any> {
|
||||
return arrayOf(arrayOf(input))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test creating arrays of int arrays`() = parentedSandbox {
|
||||
val contractExecutor = DeterministicSandboxExecutor<Int, Array<IntArray>>(configuration)
|
||||
contractExecutor.run<ArrayOfIntArrays>(0).apply {
|
||||
assertThat(result).isEqualTo(arrayOf(intArrayOf(0)))
|
||||
}
|
||||
}
|
||||
|
||||
class ArrayOfIntArrays : Function<Int, Array<IntArray>> {
|
||||
override fun apply(input: Int): Array<IntArray> {
|
||||
return arrayOf(intArrayOf(input))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user