Make command line cli stuff self instrument, and remove manual adding of agent in node capsule.

This commit is contained in:
rick.parker 2023-04-05 19:25:13 +01:00
parent 8eae615170
commit 9ec5cfcecb
8 changed files with 130 additions and 66 deletions

View File

@ -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'

View File

@ -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<String> toBlockList(String args, Path path) {
Set<String> all = loadFeaturesFromModifications(path);
protected static Set<String> toBlockList(String args, Properties override) throws IOException {
Set<String> 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<String> loadFeaturesFromModifications(Path path) {
Properties props = new Properties();
try {
props.load(getModificationsInputStream(path));
} catch (IOException e) {
e.printStackTrace();
}
private static Set<String> loadFeaturesFromModifications(Properties override) throws IOException {
Properties props = getModificationsProperties(override);
Set<String> features = new HashSet<String>();
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();
}
}
}

View File

@ -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<String> block, InputStream inputStream) {
modifications = loadModifications(inputStream, block);
public Patcher(Set<String> 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<String> block, InputStream inputStream) {
public static void start(Instrumentation instr, Set<String> 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<String, List<Modification>> loadModifications(InputStream inputStream, Set<String> block) {
Properties props = new Properties();
try {
props.load(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
private static Map<String, List<Modification>> loadModifications(Properties props, Set<String> block) {
List<Modification> 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);

View File

@ -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));

View File

@ -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 {

View File

@ -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()
}
}

View File

@ -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<String>) {
optionListHeading = "%n@|bold,underline Options|@:%n%n",
commandListHeading = "%n@|bold,underline Commands|@:%n%n")
abstract class CliWrapperBase(val alias: String, val description: String) : Callable<Int> {
init {
AttachAegis4j
}
companion object {
private val logger by lazy { contextLogger() }
}

View File

@ -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");