diff --git a/node/capsule/build.gradle b/node/capsule/build.gradle index dcc488d403..33e95ddd2b 100644 --- a/node/capsule/build.gradle +++ b/node/capsule/build.gradle @@ -9,7 +9,6 @@ apply plugin: 'com.jfrog.artifactory' description 'Corda standalone node' evaluationDependsOn(':node') -evaluationDependsOn(':tools:aegis4j') configurations { runtimeArtifacts.extendsFrom runtimeClasspath @@ -38,11 +37,9 @@ capsule { } def nodeProject = project(':node') -def aegisProject = project(':tools:aegis4j') task buildCordaJAR(type: FatCapsule, dependsOn: [ nodeProject.tasks.named('jar'), - aegisProject.tasks.named('shadowJar'), project(':core-deterministic').tasks.named('assemble'), project(':serialization-deterministic').tasks.named('assemble') ]) { @@ -54,7 +51,6 @@ task buildCordaJAR(type: FatCapsule, dependsOn: [ applicationSource = files( nodeProject.configurations.runtimeClasspath, nodeProject.tasks.jar, - aegisProject.tasks.named('shadowJar'), nodeProject.buildDir.toString() + '/resources/main/corda-reference.conf', nodeProject.buildDir.toString() + '/resources/main/mods.properties', "$rootDir/config/dev/log4j2.xml", @@ -104,7 +100,6 @@ task buildCordaJAR(type: FatCapsule, dependsOn: [ def quasarExcludeExpression = "x(antlr**;bftsmart**;co.paralleluniverse**;com.codahale**;com.esotericsoftware**;com.fasterxml**;com.google**;com.ibm**;com.intellij**;com.jcabi**;com.nhaarman**;com.opengamma**;com.typesafe**;com.zaxxer**;de.javakaffee**;groovy**;groovyjarjarantlr**;groovyjarjarasm**;io.atomix**;io.github**;io.netty**;jdk**;kotlin**;net.corda.djvm**;djvm**;net.bytebuddy**;net.i2p**;org.apache**;org.bouncycastle**;org.codehaus**;org.crsh**;org.dom4j**;org.fusesource**;org.h2**;org.hibernate**;org.jboss**;org.jcp**;org.joda**;org.objectweb**;org.objenesis**;org.slf4j**;org.w3c**;org.xml**;org.yaml**;reflectasm**;rx**;org.jolokia**;com.lmax**;picocli**;liquibase**;com.github.benmanes**;org.json**;org.postgresql**;nonapi.io.github.classgraph**)" def quasarClassLoaderExclusion = "l(net.corda.djvm.**;net.corda.core.serialization.internal.**)" javaAgents = quasar_classifier ? ["quasar-core-${quasar_version}-${quasar_classifier}.jar=${quasarExcludeExpression}${quasarClassLoaderExclusion}"] : ["quasar-core-${quasar_version}.jar=${quasarExcludeExpression}${quasarClassLoaderExclusion}"] - javaAgents += "aegis4j-1.2.jar=path=mods.properties" systemProperties['visualvm.display.name'] = 'Corda' if (JavaVersion.current() == JavaVersion.VERSION_1_8) { minJavaVersion = '1.8.0' diff --git a/tools/aegis4j/src/main/java/net/gredler/aegis4j/AegisAgent.java b/tools/aegis4j/src/main/java/net/gredler/aegis4j/AegisAgent.java index eb38975368..48d13b8e91 100644 --- a/tools/aegis4j/src/main/java/net/gredler/aegis4j/AegisAgent.java +++ b/tools/aegis4j/src/main/java/net/gredler/aegis4j/AegisAgent.java @@ -36,41 +36,45 @@ public final class AegisAgent { */ public static void premain(String args, Instrumentation instr) { instrumentation = instr; - Path path = null; - boolean started = false; - - if (args != null) { - if (args.trim().equalsIgnoreCase("dynamic")) return; - for (String arg : args.split(";")) { - if (started) throw new IllegalArgumentException("Aegis4j ERROR: parameter ordering means patching already started"); - String normalisedaArg = arg.trim().toLowerCase(); - if (normalisedaArg.isEmpty() || normalisedaArg.startsWith("block=") || normalisedaArg.startsWith("unblock=")) { - try { - Patcher.start(instr, toBlockList(normalisedaArg, path), getModificationsInputStream(path)); + try { + boolean started = false; + Properties props = null; + if (args != null) { + if (args.trim().equalsIgnoreCase("dynamic")) return; + for (String arg : args.split(";")) { + if (started) throw new IllegalArgumentException("Aegis4j ERROR: parameter ordering means patching already started"); + String normalisedaArg = arg.trim().toLowerCase(); + if (normalisedaArg.isEmpty() || normalisedaArg.startsWith("block=") || normalisedaArg.startsWith("unblock=")) { + Patcher.start(instr, toBlockList(normalisedaArg, props), getModificationsProperties(props)); started = true; - } catch (IOException e) { - throw new IllegalArgumentException("Aegis4j ERROR: Unable to process mods file", e); - } - } else if (normalisedaArg.startsWith("path=")) { - String pathString = arg.trim().substring(5); - if (pathString.startsWith(File.pathSeparator)) { - path = Paths.get(pathString); + } else if (normalisedaArg.startsWith("path=")) { + String pathString = arg.trim().substring(5); + Path path; + if (pathString.startsWith(File.pathSeparator)) { + path = Paths.get(pathString); + } else { + Path agentJar = Paths.get(AegisAgent.class.getProtectionDomain().getCodeSource().getLocation().getPath()); + path = agentJar.resolveSibling(pathString); + } + InputStream in = path.toUri().toURL().openStream(); + props = readPropertiesFromStream(in); + System.out.println("Aegis4j patching from " + path + " mods file"); + } else if (normalisedaArg.startsWith("resource=")) { + String pathString = arg.trim().substring(9); + InputStream in = ClassLoader.getSystemResourceAsStream(pathString); + if (in == null) throw new IOException("Unable to load mods resource " + pathString); + props = readPropertiesFromStream(in); + System.out.println("Aegis4j patching from " + pathString + " mods resource"); } else { - Path agentJar = Paths.get(AegisAgent.class.getProtectionDomain().getCodeSource().getLocation().getPath()); - path = agentJar.resolveSibling(pathString); + throw new IllegalArgumentException("Aegis4j ERROR: unrecognised parameters " + arg); } - System.out.println("Aegis4j patching from " + path + " mods file"); - } else { - throw new IllegalArgumentException("Aegis4j ERROR: unrecognised parameters " + arg); } } - } - if (!started) { - try { - Patcher.start(instr, toBlockList("", path), getModificationsInputStream(path)); - } catch (IOException e) { - throw new IllegalArgumentException("Aegis4j ERROR: Unable to process mods file", e); + if (!started) { + Patcher.start(instr, toBlockList("", props), getModificationsProperties(props)); } + } catch (IOException e) { + throw new IllegalArgumentException("Aegis4j ERROR: Unable to process mods file", e); } } @@ -95,8 +99,8 @@ public final class AegisAgent { * @param args agent arguments * @return the block list derived from the agent arguments */ - protected static Set toBlockList(String args, Path path) { - Set all = loadFeaturesFromModifications(path); + protected static Set toBlockList(String args, Properties override) throws IOException { + Set all = loadFeaturesFromModifications(override); if (args == null || args.trim().isEmpty()) { // no arguments provided by user return all; @@ -150,13 +154,8 @@ public final class AegisAgent { return Collections.unmodifiableSet(features); } - private static Set loadFeaturesFromModifications(Path path) { - Properties props = new Properties(); - try { - props.load(getModificationsInputStream(path)); - } catch (IOException e) { - e.printStackTrace(); - } + private static Set loadFeaturesFromModifications(Properties override) throws IOException { + Properties props = getModificationsProperties(override); Set features = new HashSet(); for (String key : props.stringPropertyNames()) { int first = key.indexOf('.'); @@ -166,8 +165,19 @@ public final class AegisAgent { return Collections.unmodifiableSet(features); } - public static InputStream getModificationsInputStream(Path path) throws IOException { - if(path != null) return path.toUri().toURL().openStream(); - return AegisAgent.class.getResourceAsStream("mods.properties"); + public static Properties getModificationsProperties(Properties props) throws IOException { + if (props != null) return props; + return readPropertiesFromStream(Patcher.class.getResourceAsStream("mods.properties")); + } + + public static Properties readPropertiesFromStream(InputStream stream) throws IOException { + if (stream == null) return null; + try { + Properties props = new Properties(); + props.load(stream); + return props; + } finally { + stream.close(); + } } } diff --git a/tools/aegis4j/src/main/java/net/gredler/aegis4j/Patcher.java b/tools/aegis4j/src/main/java/net/gredler/aegis4j/Patcher.java index 636a09af77..b12fd05108 100644 --- a/tools/aegis4j/src/main/java/net/gredler/aegis4j/Patcher.java +++ b/tools/aegis4j/src/main/java/net/gredler/aegis4j/Patcher.java @@ -12,7 +12,6 @@ import javassist.LoaderClassPath; import javassist.NotFoundException; import java.io.IOException; -import java.io.InputStream; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.Instrumentation; import java.lang.instrument.UnmodifiableClassException; @@ -40,8 +39,8 @@ public final class Patcher implements ClassFileTransformer { * * @param block the features to block */ - public Patcher(Set block, InputStream inputStream) { - modifications = loadModifications(inputStream, block); + public Patcher(Set block, Properties props) { + modifications = loadModifications(props, block); } /** @@ -50,10 +49,10 @@ public final class Patcher implements ClassFileTransformer { * @param instr the instrumentation instance to add a new patcher to * @param block the features to block */ - public static void start(Instrumentation instr, Set block, InputStream inputStream) { + public static void start(Instrumentation instr, Set block, Properties props) { System.out.println("Aegis4j patching starting"); if (patcher != null) instr.removeTransformer(patcher); - patcher = new Patcher(block, inputStream); + patcher = new Patcher(block, props); instr.addTransformer(patcher, true); for (String className : patcher.modifications.keySet()) { @@ -112,22 +111,15 @@ public final class Patcher implements ClassFileTransformer { } } - private static Map> loadModifications(InputStream inputStream, Set block) { - Properties props = new Properties(); - try { - props.load(inputStream); - } catch (IOException e) { - e.printStackTrace(); - } - + private static Map> loadModifications(Properties props, Set block) { List mods = new ArrayList<>(); for (String key : props.stringPropertyNames()) { int first = key.indexOf('.'); int last = key.lastIndexOf('.'); String feature = key.substring(0, first).toLowerCase(); //if (block.contains(feature)) { - String className = key.substring(first + 1, last); - String methodName = key.substring(last + 1); + String className = key.substring(first + 1, last); + String methodName = key.substring(last + 1); String newBody = props.getProperty(key); Modification mod = new Modification(className, methodName, newBody, block.contains(feature)); mods.add(mod); diff --git a/tools/aegis4j/src/test/java/net/gredler/aegis4j/AegisAgentTest.java b/tools/aegis4j/src/test/java/net/gredler/aegis4j/AegisAgentTest.java index 395164dee3..90d589fdbb 100644 --- a/tools/aegis4j/src/test/java/net/gredler/aegis4j/AegisAgentTest.java +++ b/tools/aegis4j/src/test/java/net/gredler/aegis4j/AegisAgentTest.java @@ -51,7 +51,7 @@ public class AegisAgentTest { } @Test - public void testParseBlockList() { + public void testParseBlockList() throws IOException { assertEquals(TestUtils.setOf("jndi", "rmi", "process", "httpserver", "serialization", "scripting"), toBlockList("", null)); assertEquals(TestUtils.setOf("jndi", "rmi", "process", "httpserver", "serialization", "scripting"), toBlockList(" ", null)); assertEquals(TestUtils.setOf("jndi", "rmi", "process", "httpserver", "scripting"), toBlockList("unblock=serialization", null)); diff --git a/tools/cliutils/build.gradle b/tools/cliutils/build.gradle index 2ab54773ae..80cdb60e31 100644 --- a/tools/cliutils/build.gradle +++ b/tools/cliutils/build.gradle @@ -8,7 +8,7 @@ description 'CLI Utilities' dependencies { compile project(":core") compile project(":common-logging") - + compile "info.picocli:picocli:$picocli_version" compile "commons-io:commons-io:$commons_io_version" compile "com.jcabi:jcabi-manifests:$jcabi_manifests_version" @@ -17,6 +17,12 @@ dependencies { compile "org.fusesource.jansi:jansi:$jansi_version" compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" + + // Need the JDK + compile files(org.gradle.internal.jvm.Jvm.current().toolsJar) + + // Aegis4J + compile project(':tools:aegis4j') } jar { diff --git a/tools/cliutils/src/main/kotlin/net/corda/cliutils/AttachAegis4j.kt b/tools/cliutils/src/main/kotlin/net/corda/cliutils/AttachAegis4j.kt new file mode 100644 index 0000000000..4c71321682 --- /dev/null +++ b/tools/cliutils/src/main/kotlin/net/corda/cliutils/AttachAegis4j.kt @@ -0,0 +1,48 @@ +package net.corda.cliutils + +import com.sun.tools.attach.VirtualMachine +import net.gredler.aegis4j.AegisAgent +import java.lang.management.ManagementFactory +import java.nio.file.Files +import java.util.jar.JarEntry +import java.util.jar.JarOutputStream +import java.util.jar.Manifest + +object AttachAegis4j { + private fun toBytes(clazz: Class<*>): ByteArray? { + val path = clazz.name.replace('.', '/') + ".class" + val stream = clazz.classLoader.getResourceAsStream(path) + return stream.readBytes() + } + + private fun createAgentJar(): String { + val clazz: Class<*> = AegisAgent::class.java + val jar = Files.createTempFile("aegis4j-", ".jar") + jar.toFile().deleteOnExit() + val manifest = Manifest() + manifest.mainAttributes.putValue("Manifest-Version", "1.0") + manifest.mainAttributes.putValue("Main-Class", clazz.name) + manifest.mainAttributes.putValue("Agent-Class", clazz.name) + manifest.mainAttributes.putValue("Premain-Class", clazz.name) + manifest.mainAttributes.putValue("Can-Redefine-Classes", "true") + manifest.mainAttributes.putValue("Can-Retransform-Classes", "true") + manifest.mainAttributes.putValue("Can-Set-Native-Method-Prefix", "false") + Files.newOutputStream(jar).use { os -> + JarOutputStream(os, manifest).use { jos -> + val entry = JarEntry(clazz.name.replace('.', '/') + ".class") + entry.time = System.currentTimeMillis() + jos.putNextEntry(entry) + jos.write(toBytes(clazz)) + jos.closeEntry() + } + } + return jar.toAbsolutePath().toString() + } + + init { + val pid = ManagementFactory.getRuntimeMXBean().getName().substringBefore('@') + var jvm = VirtualMachine.attach(pid) + jvm.loadAgent(createAgentJar(), "resource=mods.properties") + jvm.detach() + } +} \ No newline at end of file diff --git a/tools/cliutils/src/main/kotlin/net/corda/cliutils/CordaCliWrapper.kt b/tools/cliutils/src/main/kotlin/net/corda/cliutils/CordaCliWrapper.kt index 8bb9cb6432..5d9bccb4af 100644 --- a/tools/cliutils/src/main/kotlin/net/corda/cliutils/CordaCliWrapper.kt +++ b/tools/cliutils/src/main/kotlin/net/corda/cliutils/CordaCliWrapper.kt @@ -5,7 +5,16 @@ import net.corda.core.utilities.contextLogger import org.fusesource.jansi.AnsiConsole import org.slf4j.event.Level import picocli.CommandLine -import picocli.CommandLine.* +import picocli.CommandLine.Command +import picocli.CommandLine.DefaultExceptionHandler +import picocli.CommandLine.ExecutionException +import picocli.CommandLine.Help +import picocli.CommandLine.ITypeConverter +import picocli.CommandLine.Option +import picocli.CommandLine.ParameterException +import picocli.CommandLine.ParseResult +import picocli.CommandLine.RunLast +import picocli.CommandLine.TypeConversionException import java.nio.file.Path import java.nio.file.Paths import java.util.* @@ -114,6 +123,10 @@ fun CordaCliWrapper.start(args: Array) { optionListHeading = "%n@|bold,underline Options|@:%n%n", commandListHeading = "%n@|bold,underline Commands|@:%n%n") abstract class CliWrapperBase(val alias: String, val description: String) : Callable { + init { + AttachAegis4j + } + companion object { private val logger by lazy { contextLogger() } } diff --git a/node/src/main/resources/mods.properties b/tools/cliutils/src/main/resources/mods.properties similarity index 93% rename from node/src/main/resources/mods.properties rename to tools/cliutils/src/main/resources/mods.properties index 4f48d9ac88..76ff3d11a3 100644 --- a/node/src/main/resources/mods.properties +++ b/tools/cliutils/src/main/resources/mods.properties @@ -57,7 +57,7 @@ HTTPSERVER.com.sun.net.httpserver.spi.HttpServerProvider.provider=throw new java # CVE-2022-1471 SNAKEYAML.org.yaml.snakeyaml.constructor.Constructor.Constructor=throw new java.lang.RuntimeException("SnakeYAML Constructor blocked by aegis4j"); # CVE-2020-29582 -KOTLIN.kotlin.io.FilesKt.createTempDir=throw new java.lang.RuntimeException("Kotlin createTempDir blocked by aegis4j"); -KOTLIN.kotlin.io.FilesKt.createTempFile=throw new java.lang.RuntimeException("Kotlin createTempFile blocked by aegis4j"); +KOTLIN.kotlin.io.FilesKt__UtilsKt.createTempDir=throw new java.lang.RuntimeException("Kotlin createTempDir blocked by aegis4j"); +KOTLIN.kotlin.io.FilesKt__UtilsKt.createTempFile=throw new java.lang.RuntimeException("Kotlin createTempFile blocked by aegis4j"); # CVE-2020-8908 GUAVA.com.google.common.io.Files.createTempDir=throw new java.lang.RuntimeException("Guava createTempDir blocked by aegis4j"); \ No newline at end of file