CORDA-2654 - Repair the DJVM CLI tool. (#4796) (#4799)

* Replace SandboxedRunnable with Function interface.
Remove DJVM from "Key Concepts" release notes.
Update installation of shell tool.
Fix broken sandbox package names.
Make sure we only resolve each class once when loading.
Also remove some unused default parameter values.
Don't discard "bootstrap" sandbox.* classes because SourceClassLoader may need them.

* Restore DJVM to the "Key Concepts" docs.

* Remove all mention of "whitelisting" from the DJVM CLI.
This commit is contained in:
Tommy Lillehagen 2019-02-21 17:44:46 +00:00 committed by Katelyn Baker
parent 9a1ac70959
commit f9e07c0158
13 changed files with 21 additions and 206 deletions

View File

@ -51,22 +51,6 @@ shadowJar {
baseName 'corda-djvm'
classifier ''
relocate 'org.objectweb.asm', 'djvm.org.objectweb.asm'
// These particular classes are only needed to "bootstrap"
// the compilation of the other sandbox classes. At runtime,
// we will generate better versions from deterministic-rt.jar.
exclude 'sandbox/java/lang/Appendable.class'
exclude 'sandbox/java/lang/CharSequence.class'
exclude 'sandbox/java/lang/Character\$Subset.class'
exclude 'sandbox/java/lang/Character\$Unicode*.class'
exclude 'sandbox/java/lang/Comparable.class'
exclude 'sandbox/java/lang/Enum.class'
exclude 'sandbox/java/lang/Iterable.class'
exclude 'sandbox/java/lang/StackTraceElement.class'
exclude 'sandbox/java/lang/StringBuffer.class'
exclude 'sandbox/java/lang/StringBuilder.class'
exclude 'sandbox/java/nio/**'
exclude 'sandbox/java/util/**'
}
assemble.dependsOn shadowJar

View File

@ -32,14 +32,6 @@ abstract class ClassCommand : CommandBase() {
@Option(names = ["--ignore-definition-providers"], description = ["Disable all definition providers."])
var ignoreDefinitionProviders: Boolean = false
@Option(
names = ["-w", "--whitelist"],
description = ["Override the default whitelist. Use provided whitelist instead. If NONE is provided, the " +
"whitelist will be ignored. If ALL is provided, all references will be whitelisted. LANG can be " +
"used to only whitelist select classes and their members from the java.lang package."]
)
var whitelist: Path? = null
@Option(names = ["-c", "--classpath"], description = ["Additions to the default class path."], split = ":")
var classPath: Array<Path> = emptyArray()
@ -65,8 +57,6 @@ abstract class ClassCommand : CommandBase() {
protected var executor = SandboxExecutor<Any, Any>(SandboxConfiguration.DEFAULT)
private var derivedWhitelist: Whitelist = Whitelist.MINIMAL
abstract fun processClasses(classes: List<Class<*>>)
open fun printSuccess(classes: List<Class<*>>) {}
@ -74,8 +64,7 @@ abstract class ClassCommand : CommandBase() {
override fun validateArguments() = filters.isNotEmpty()
override fun handleCommand(): Boolean {
derivedWhitelist = whitelistFromPath(whitelist)
val configuration = getConfiguration(derivedWhitelist)
val configuration = getConfiguration(Whitelist.MINIMAL)
classLoader = SourceClassLoader(getClasspath(), configuration.analysisConfiguration.classResolver)
createExecutor(configuration)

View File

@ -15,8 +15,7 @@ import picocli.CommandLine.Command
NewCommand::class,
RunCommand::class,
ShowCommand::class,
TreeCommand::class,
WhitelistCommand::class
TreeCommand::class
]
)
@Suppress("KDocMissingDocumentation")

View File

@ -55,13 +55,13 @@ class NewCommand : CommandBase() {
companion object {
val TEMPLATE = """
|package net.corda.djvm;
|package net.corda.sandbox;
|
|import net.corda.djvm.execution.SandboxedRunnable;
|import java.util.function.Function;
|
|public class [NAME] implements SandboxedRunnable<[FROM], [TO]> {
|public class [NAME] implements Function<[FROM], [TO]> {
| @Override
| public [TO] run([FROM] input) {
| public [TO] apply([FROM] input) {
| return [RETURN];
| }
|}

View File

@ -60,7 +60,7 @@ fun openOptions(force: Boolean) = if (force) {
* Get the path of where any generated code will be placed. Create the directory if it does not exist.
*/
fun createCodePath(): Path {
return Paths.get("tmp", "net", "corda", "djvm").let {
return Paths.get("tmp", "net", "corda", "sandbox").let {
Files.createDirectories(it)
}
}
@ -92,7 +92,7 @@ val userClassPath: String = System.getProperty("java.class.path")
/**
* Get a reference of each concrete class that implements interface or class [T].
*/
inline fun <reified T> find(scanSpec: String = "net/corda/djvm"): List<Class<*>> {
inline fun <reified T> find(scanSpec: String = "net/corda/sandbox"): List<Class<*>> {
return ClassGraph()
.whitelistPaths(scanSpec)
.enableAllInfo()

View File

@ -1,14 +0,0 @@
package net.corda.djvm.tools.cli
import picocli.CommandLine.Command
@Command(
name = "whitelist",
description = ["Utilities and commands related to the whitelist for the deterministic sandbox."],
subcommands = [
WhitelistGenerateCommand::class,
WhitelistShowCommand::class
]
)
@Suppress("KDocMissingDocumentation")
class WhitelistCommand : CommandBase()

View File

@ -1,92 +0,0 @@
package net.corda.djvm.tools.cli
import net.corda.djvm.analysis.AnalysisConfiguration
import net.corda.djvm.analysis.AnalysisContext
import net.corda.djvm.analysis.ClassAndMemberVisitor
import net.corda.djvm.references.ClassRepresentation
import net.corda.djvm.references.Member
import net.corda.djvm.source.ClassSource
import picocli.CommandLine.*
import java.io.PrintStream
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.StandardOpenOption
import java.util.zip.GZIPOutputStream
@Command(
name = "generate",
description = ["Generate and export whitelist from the class and member declarations provided in one or more " +
"JARs."]
)
@Suppress("KDocMissingDocumentation")
class WhitelistGenerateCommand : CommandBase() {
@Parameters(description = ["The paths of the JARs that the whitelist is to be generated from."])
var paths: Array<Path> = emptyArray()
@Option(
names = ["-o", "--output"],
description = ["The file to which the whitelist will be written. If not provided, STDOUT will be used."]
)
var output: Path? = null
override fun validateArguments() = paths.isNotEmpty()
override fun handleCommand(): Boolean {
val entries = AnalysisConfiguration.createRoot().use { configuration ->
val entries = mutableListOf<String>()
val visitor = object : ClassAndMemberVisitor(configuration, null) {
override fun visitClass(clazz: ClassRepresentation): ClassRepresentation {
entries.add(clazz.name)
return super.visitClass(clazz)
}
override fun visitMethod(clazz: ClassRepresentation, method: Member): Member {
visitMember(clazz, method)
return super.visitMethod(clazz, method)
}
override fun visitField(clazz: ClassRepresentation, field: Member): Member {
visitMember(clazz, field)
return super.visitField(clazz, field)
}
private fun visitMember(clazz: ClassRepresentation, member: Member) {
entries.add("${clazz.name}.${member.memberName}:${member.signature}")
}
}
val context = AnalysisContext.fromConfiguration(configuration)
for (path in paths) {
ClassSource.fromPath(path).getStreamIterator().forEach {
visitor.analyze(it, context)
}
}
entries
}
output?.also {
Files.newOutputStream(it, StandardOpenOption.CREATE).use { out ->
GZIPOutputStream(out).use { gzip ->
PrintStream(gzip).use { pout ->
pout.println("""
|java/.*
|javax/.*
|jdk/.*
|com/sun/.*
|sun/.*
|---
""".trimMargin().trim())
printEntries(pout, entries)
}
}
}
} ?: printEntries(System.out, entries)
return true
}
private fun printEntries(stream: PrintStream, entries: List<String>) {
for (entry in entries.sorted().distinct()) {
stream.println(entry)
}
}
}

View File

@ -1,33 +0,0 @@
package net.corda.djvm.tools.cli
import picocli.CommandLine.*
import java.nio.file.Path
@Command(
name = "show",
description = ["Print the whitelist used for the deterministic sandbox."]
)
@Suppress("KDocMissingDocumentation")
class WhitelistShowCommand : CommandBase() {
@Option(
names = ["-w", "--whitelist"],
description = ["Override the default whitelist. Use provided whitelist instead."]
)
var whitelist: Path? = null
@Parameters(description = ["Words or phrases to use to filter down the result."])
var filters: Array<String> = emptyArray()
override fun validateArguments() = true
override fun handleCommand(): Boolean {
val whitelist = whitelistFromPath(whitelist)
val filters = filters.map(String::toLowerCase)
whitelist.items
.filter { item -> filters.all { it in item.toLowerCase() } }
.forEach { println(it) }
return true
}
}

View File

@ -4,7 +4,7 @@ file="${BASH_SOURCE[0]}"
linked_file="$(test -L "$file" && readlink "$file" || echo "$file")"
base_dir="$(cd "$(dirname "$linked_file")/../" && pwd)"
version="$(cat $base_dir/../build.gradle | sed -n 's/^[ ]*ext\.corda_release_version[ =]*"\([^"]*\)".*$/\1/p')"
jar_file="$base_dir/cli/build/libs/corda-djvm-$version-all.jar"
jar_file="$base_dir/cli/build/libs/corda-djvm-$version-cli.jar"
CLASSPATH="${CLASSPATH:-}"

View File

@ -10,7 +10,7 @@ cd "$base_dir/.."
# Generate auto-completion file for Bash and ZSH
cd "$base_dir"
java -cp "$base_dir/../cli/build/libs/corda-djvm-$version-all.jar" \
java -cp "$base_dir/../cli/build/libs/corda-djvm-$version-cli.jar" \
picocli.AutoComplete -n djvm net.corda.djvm.tools.cli.Commands -f
# Create a symbolic link to the `djvm` utility

View File

@ -131,7 +131,7 @@ class SandboxClassLoader private constructor(
if (!isSandboxClass || parent is SandboxClassLoader) {
try {
clazz = super.loadClass(name, resolve)
clazz = super.loadClass(name, false)
} catch (e: ClassNotFoundException) {
} catch (e: SandboxClassLoadingException) {
e.messages.clearProvisional()

View File

@ -11,8 +11,8 @@ import java.nio.file.Path
* @property origin The origin of the class source, if any.
*/
class ClassSource private constructor(
val qualifiedClassName: String = "",
val origin: String? = null
val qualifiedClassName: String,
val origin: String?
) {
val internalClassName: String = qualifiedClassName.asResourcePath

View File

@ -59,8 +59,8 @@ Abstraction
~~~~~~~~~~~
The sandbox is abstracted away as an executor which takes as input an implementation of the interface
``SandboxedRunnable<in Input, out Output>``, dereferenced by a ``ClassSource``. This interface has a single method that
needs implementing, namely ``run(Input): Output``.
``Function<in Input, out Output>``, dereferenced by a ``ClassSource``. This interface has a single method that
needs implementing, namely ``apply(Input): Output``.
A ``ClassSource`` object referencing such an implementation can be passed into the ``SandboxExecutor<in Input, out
Output>`` together with an input of type ``Input``. The executor has operations for both execution and static
@ -68,7 +68,7 @@ validation, namely ``run()`` and ``validate()``. These methods both return a sum
* In the case of execution, this summary object has information about:
* Whether or not the runnable was successfully executed.
* If successful, the return value of ``SandboxedRunnable.run()``.
* If successful, the return value of ``Function.apply()``.
* If failed, the exception that was raised.
* And in both cases, a summary of all accrued costs during execution.
@ -80,7 +80,7 @@ validation, namely ``run()`` and ``validate()``. These methods both return a sum
severity ``ERROR`` will prevent execution.
The sandbox has a configuration that applies to the execution of a specific runnable. This configuration, on a higher
level, contains a set of rules, definition providers, emitters and a whitelist.
level, contains a set of rules, definition providers and emitters.
.. image:: resources/djvm-overview.png
@ -107,7 +107,7 @@ work may well introduce additional constraints that we would want to place on th
.. note::
It is worth noting that not only smart contract code is instrumented by the sandbox, but all code that it can
transitively reach. In particular this means that the Java runtime classes (that have not been whitelisted) and any
transitively reach. In particular this means that the Java runtime classes and any
other library code used in the program are also instrumented and persisted ahead of time.
@ -129,15 +129,6 @@ tries to catch such exceptions, as doing so would allow the user to bypass the t
profile.
Only Allow Explicitly Whitelisted Runtime API
.............................................
Ensures that constant pool references are mapped against a verified subset of the Java runtime libraries. Said subset
excludes functionality that contract code should not have access to, such as native code. This whitelist has been
trimmed down to the bare minimum needed, a few classes in ``java.lang``, so that also the Java runtime libraries
themselves are subjected to the same amount of scrutiny that the rest of the code is.
Disallow Dynamic Invocation
...........................
@ -154,9 +145,6 @@ network requests, general hardware interaction, threading, *etc.* These all cons
allowing such code to be called arbitrarily from the JVM would require deterministic guarantees on the native machine
code level. This falls out of scope for the DJVM.
Java runtime classes that call into native code and that are needed from within the sandbox environment, can be
whitelisted explicitly.
Disallow Finalizer Methods
..........................
@ -278,9 +266,8 @@ The loaded classes are further rewritten in two ways:
Disable Synchronised Methods and Blocks
.......................................
Since Java's multi-threading API has been excluded from the whitelist, synchronised methods and code blocks have little
use in sandboxed code. Consequently, we log informational messages about occurrences of this in your sandboxed code and
automatically transform them into ordinary methods and code blocks instead.
The DJVM doesn't support multi-threading and so synchronised methods and code blocks have little
use in sandboxed code. Consequently, we automatically transform them into ordinary methods and code blocks instead.
Future Work
@ -290,9 +277,6 @@ Further work is planned:
* To enable controlled use of reflection APIs.
* Strip out the dependency on the extensive whitelist of underlying Java
runtime classes.
* Currently, dynamic invocation is disallowed. Allow specific lambda and
string concatenation meta-factories used by Java code itself.
@ -351,7 +335,7 @@ This run will produce some output similar to this:
The output should be pretty self-explanatory, but just to summarise:
* It prints out the return value from the ``SandboxedRunnable<Object, Object>.run()`` method implemented in
* It prints out the return value from the ``Function<Object, Object>.apply()`` method implemented in
``net.corda.sandbox.Hello``.
* It also prints out the aggregated costs for allocations, invocations, jumps and throws.
@ -363,5 +347,3 @@ Other commands to be aware of are:
* ``djvm inspect`` which allows you to inspect what byte code modifications will be applied to a class.
* ``djvm show`` which displays the transformed byte code of a class, *i.e.*, the end result and not the difference.
* ``djvm whitelist`` which displays the content of the whitelist in use.