mirror of
https://github.com/corda/corda.git
synced 2025-01-04 12:14:17 +00:00
Some modifications to specify custom patching file and some work towards dynamic init with view to re-enabling tests
This commit is contained in:
parent
11aaa018c0
commit
e09c6473ad
@ -30,7 +30,7 @@ dependencies {
|
|||||||
testImplementation('log4j:apache-log4j-extras:1.2.17') {
|
testImplementation('log4j:apache-log4j-extras:1.2.17') {
|
||||||
exclude group: 'log4j', module: 'log4j'
|
exclude group: 'log4j', module: 'log4j'
|
||||||
}
|
}
|
||||||
testImplementation group: 'com.ea.agentloader', name: 'ea-agent-loader', version: '1.0.3'
|
// testImplementation group: 'com.ea.agentloader', name: 'ea-agent-loader', version: '1.0.3'
|
||||||
testImplementation "com.google.guava:guava:$guava_version"
|
testImplementation "com.google.guava:guava:$guava_version"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,11 +2,20 @@
|
|||||||
|
|
||||||
package net.gredler.aegis4j;
|
package net.gredler.aegis4j;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.lang.instrument.Instrumentation;
|
import java.lang.instrument.Instrumentation;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Properties;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.TreeMap;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -20,24 +29,48 @@ import java.util.stream.Collectors;
|
|||||||
*/
|
*/
|
||||||
public final class AegisAgent {
|
public final class AegisAgent {
|
||||||
|
|
||||||
|
private static Instrumentation instrumentation;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Supports static attach (via -javaagent parameter at JVM startup).
|
* Supports static attach (via -javaagent parameter at JVM startup).
|
||||||
*
|
*
|
||||||
* @param args agent arguments
|
* @param args agent arguments
|
||||||
* @param instr instrumentation services
|
* @param instr instrumentation services
|
||||||
*/
|
*/
|
||||||
public static void premain(String args, Instrumentation instr) {
|
public static void premain(String args, Instrumentation instr) {
|
||||||
Patcher.start(instr, toBlockList(args));
|
instrumentation = instr;
|
||||||
|
if(args.trim().equalsIgnoreCase("dynamic")) return;
|
||||||
|
Path path = null;
|
||||||
|
boolean started = false;
|
||||||
|
for(String arg: args.split(";")) {
|
||||||
|
if(started) throw new IllegalArgumentException("ERROR: argument 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));
|
||||||
|
started = true;
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
} else if(normalisedaArg.startsWith("path=")) {
|
||||||
|
path= Paths.get(arg.trim().substring(5));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!started) throw new IllegalArgumentException("ERROR: patching not started");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Supports dynamic attach (via the com.sun.tools.attach.* API).
|
* Supports dynamic attach (via the com.sun.tools.attach.* API).
|
||||||
*
|
*
|
||||||
* @param args agent arguments
|
* @param args agent arguments
|
||||||
* @param instr instrumentation services
|
* @param instr instrumentation services
|
||||||
*/
|
*/
|
||||||
public static void agentmain(String args, Instrumentation instr) {
|
public static void agentmain(String args, Instrumentation instr) {
|
||||||
Patcher.start(instr, toBlockList(args));
|
premain(args, instr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dynamicLoad(String args) {
|
||||||
|
agentmain(args, instrumentation);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -47,9 +80,8 @@ public final class AegisAgent {
|
|||||||
* @param args agent arguments
|
* @param args agent arguments
|
||||||
* @return the block list derived from the agent arguments
|
* @return the block list derived from the agent arguments
|
||||||
*/
|
*/
|
||||||
protected static Set< String > toBlockList(String args) {
|
protected static Set<String> toBlockList(String args, Path path) {
|
||||||
|
Set<String> all = loadFeaturesFromModifications(path);
|
||||||
Set< String > all = new HashSet<String>(Arrays.<String>asList("jndi", "rmi", "process", "httpserver", "serialization", "unsafe", "scripting", "jshell"));
|
|
||||||
if (args == null || args.trim().isEmpty()) {
|
if (args == null || args.trim().isEmpty()) {
|
||||||
// no arguments provided by user
|
// no arguments provided by user
|
||||||
return all;
|
return all;
|
||||||
@ -70,8 +102,8 @@ public final class AegisAgent {
|
|||||||
return split(value, all);
|
return split(value, all);
|
||||||
} else if ("unblock".equals(name)) {
|
} else if ("unblock".equals(name)) {
|
||||||
// user is modifying the default block list
|
// user is modifying the default block list
|
||||||
Set< String > block = new HashSet<>(all);
|
Set<String> block = new HashSet<>(all);
|
||||||
Set< String > unblock = split(value, all);
|
Set<String> unblock = split(value, all);
|
||||||
block.removeAll(unblock);
|
block.removeAll(unblock);
|
||||||
return Collections.unmodifiableSet(block);
|
return Collections.unmodifiableSet(block);
|
||||||
} else {
|
} else {
|
||||||
@ -84,16 +116,15 @@ public final class AegisAgent {
|
|||||||
* Splits the specified comma-delimited feature list, validating that all specified features are valid.
|
* Splits the specified comma-delimited feature list, validating that all specified features are valid.
|
||||||
*
|
*
|
||||||
* @param values the comma-delimited feature list
|
* @param values the comma-delimited feature list
|
||||||
* @param all the list of valid features to validate against
|
* @param all the list of valid features to validate against
|
||||||
* @return the feature list, split into individual (validated) feature names
|
* @return the feature list, split into individual (validated) feature names
|
||||||
* @throws IllegalArgumentException if any unrecognized feature names are included in the comma-delimited feature list
|
* @throws IllegalArgumentException if any unrecognized feature names are included in the comma-delimited feature list
|
||||||
*/
|
*/
|
||||||
private static Set< String > split(String values, Set< String > all) {
|
private static Set<String> split(String values, Set<String> all) {
|
||||||
|
Set<String> features = Arrays.asList(values.split(","))
|
||||||
Set< String > features = Arrays.asList(values.split(","))
|
.stream()
|
||||||
.stream()
|
.map(String::trim)
|
||||||
.map(String::trim)
|
.collect(Collectors.toSet());
|
||||||
.collect(Collectors.toSet());
|
|
||||||
|
|
||||||
for (String feature : features) {
|
for (String feature : features) {
|
||||||
if (!all.contains(feature)) {
|
if (!all.contains(feature)) {
|
||||||
@ -103,4 +134,25 @@ public final class AegisAgent {
|
|||||||
|
|
||||||
return Collections.unmodifiableSet(features);
|
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();
|
||||||
|
}
|
||||||
|
Set<String> features = new HashSet<String>();
|
||||||
|
for (String key : props.stringPropertyNames()) {
|
||||||
|
int first = key.indexOf('.');
|
||||||
|
String feature = key.substring(0, first).toLowerCase();
|
||||||
|
features.add(feature);
|
||||||
|
}
|
||||||
|
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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
package net.gredler.aegis4j;
|
package net.gredler.aegis4j;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.lang.instrument.ClassFileTransformer;
|
import java.lang.instrument.ClassFileTransformer;
|
||||||
import java.lang.instrument.Instrumentation;
|
import java.lang.instrument.Instrumentation;
|
||||||
import java.lang.instrument.UnmodifiableClassException;
|
import java.lang.instrument.UnmodifiableClassException;
|
||||||
@ -31,15 +32,15 @@ import javassist.NotFoundException;
|
|||||||
*/
|
*/
|
||||||
public final class Patcher implements ClassFileTransformer {
|
public final class Patcher implements ClassFileTransformer {
|
||||||
|
|
||||||
private final Map< String, List< Modification > > modifications; // class name -> modifications
|
private final Map<String, List<Modification>> modifications; // class name -> modifications
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new class patcher which blocks the specified features.
|
* Creates a new class patcher which blocks the specified features.
|
||||||
*
|
*
|
||||||
* @param block the features to block
|
* @param block the features to block
|
||||||
*/
|
*/
|
||||||
public Patcher(Set< String > block) {
|
public Patcher(Set<String> block, InputStream inputStream) {
|
||||||
modifications = loadModifications(block);
|
modifications = loadModifications(inputStream, block);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -48,16 +49,15 @@ public final class Patcher implements ClassFileTransformer {
|
|||||||
* @param instr the instrumentation instance to add a new patcher to
|
* @param instr the instrumentation instance to add a new patcher to
|
||||||
* @param block the features to block
|
* @param block the features to block
|
||||||
*/
|
*/
|
||||||
public static void start(Instrumentation instr, Set< String > block) {
|
public static void start(Instrumentation instr, Set<String> block, InputStream inputStream) {
|
||||||
|
|
||||||
System.out.println("Aegis4j patching starting");
|
System.out.println("Aegis4j patching starting");
|
||||||
Patcher patcher = new Patcher(block);
|
Patcher patcher = new Patcher(block ,inputStream);
|
||||||
instr.addTransformer(patcher, true);
|
instr.addTransformer(patcher, true);
|
||||||
|
|
||||||
for (String className : patcher.modifications.keySet()) {
|
for (String className : patcher.modifications.keySet()) {
|
||||||
try {
|
try {
|
||||||
System.out.println("Aegis4j patching " + className + "...");
|
System.out.println("Aegis4j patching " + className + "...");
|
||||||
Class< ? > clazz = Class.forName(className);
|
Class<?> clazz = Class.forName(className);
|
||||||
instr.retransformClasses(clazz);
|
instr.retransformClasses(clazz);
|
||||||
} catch (ClassNotFoundException | UnmodifiableClassException e) {
|
} catch (ClassNotFoundException | UnmodifiableClassException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
@ -69,13 +69,12 @@ public final class Patcher implements ClassFileTransformer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] transform(ClassLoader loader, String className, Class< ? > clazz, ProtectionDomain domain, byte[] classBytes) {
|
public byte[] transform(ClassLoader loader, String className, Class<?> clazz, ProtectionDomain domain, byte[] classBytes) {
|
||||||
return patch(className.replace('/', '.'), classBytes);
|
return patch(className.replace('/', '.'), classBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] patch(String className, byte[] classBytes) {
|
private byte[] patch(String className, byte[] classBytes) {
|
||||||
|
List<Modification> mods = modifications.get(className);
|
||||||
List< Modification > mods = modifications.get(className);
|
|
||||||
if (mods == null || mods.isEmpty()) {
|
if (mods == null || mods.isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -109,16 +108,15 @@ public final class Patcher implements ClassFileTransformer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Map< String, List< Modification > > loadModifications(Set< String > block) {
|
private static Map<String, List<Modification>> loadModifications(InputStream inputStream, Set<String> block) {
|
||||||
|
|
||||||
Properties props = new Properties();
|
Properties props = new Properties();
|
||||||
try {
|
try {
|
||||||
props.load(AegisAgent.class.getResourceAsStream("mods.properties"));
|
props.load(inputStream);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
List< Modification > mods = new ArrayList<>();
|
List<Modification> mods = new ArrayList<>();
|
||||||
for (String key : props.stringPropertyNames()) {
|
for (String key : props.stringPropertyNames()) {
|
||||||
int first = key.indexOf('.');
|
int first = key.indexOf('.');
|
||||||
int last = key.lastIndexOf('.');
|
int last = key.lastIndexOf('.');
|
||||||
@ -133,7 +131,7 @@ public final class Patcher implements ClassFileTransformer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Collections.unmodifiableMap(new TreeMap<>(
|
return Collections.unmodifiableMap(new TreeMap<>(
|
||||||
mods.stream().collect(Collectors.groupingBy(mod -> mod.className, Collectors.toList()))
|
mods.stream().collect(Collectors.groupingBy(mod -> mod.className, Collectors.toList()))
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,6 +33,8 @@ public class AegisAgentCommandLineTest {
|
|||||||
testStaticAttach(jar, "block=jndi", "");
|
testStaticAttach(jar, "block=jndi", "");
|
||||||
testStaticAttach(jar, "unblock=serialization", "");
|
testStaticAttach(jar, "unblock=serialization", "");
|
||||||
testStaticAttach(jar, "block=serialization", "Java serialization blocked by aegis4j");
|
testStaticAttach(jar, "block=serialization", "Java serialization blocked by aegis4j");
|
||||||
|
testStaticAttach(jar, "block=serialization;", "Java serialization blocked by aegis4j");
|
||||||
|
testStaticAttach(jar, ";block=serialization", "ERROR: argument ordering means patching already started");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void testStaticAttach(String jar, String config, String expectedErr) throws Exception {
|
private static void testStaticAttach(String jar, String config, String expectedErr) throws Exception {
|
||||||
|
@ -20,7 +20,6 @@ import java.util.jar.JarEntry;
|
|||||||
import java.util.jar.JarOutputStream;
|
import java.util.jar.JarOutputStream;
|
||||||
import java.util.jar.Manifest;
|
import java.util.jar.Manifest;
|
||||||
|
|
||||||
import com.ea.agentloader.AgentLoader;
|
|
||||||
import com.google.common.base.Charsets;
|
import com.google.common.base.Charsets;
|
||||||
import com.google.common.io.ByteStreams;
|
import com.google.common.io.ByteStreams;
|
||||||
import org.junit.jupiter.api.function.Executable;
|
import org.junit.jupiter.api.function.Executable;
|
||||||
@ -136,7 +135,8 @@ public final class TestUtils {
|
|||||||
jvm.loadAgent(createAgentJar(), options);
|
jvm.loadAgent(createAgentJar(), options);
|
||||||
jvm.detach();
|
jvm.detach();
|
||||||
*/
|
*/
|
||||||
AgentLoader.loadAgentClass(AegisAgent.class.getName(), options);
|
//AgentLoader.loadAgentClass(AegisAgent.class.getName(), options);
|
||||||
|
AegisAgent.dynamicLoad(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] inputStreamReadAllBytes(InputStream stream) throws IOException {
|
public static byte[] inputStreamReadAllBytes(InputStream stream) throws IOException {
|
||||||
|
Loading…
Reference in New Issue
Block a user