Skip to content

Commit d6fd88f

Browse files
committed
[appsec] Implement SCA hot instrumentation via ClassFileTransformer
Adds dynamic bytecode instrumentation for Supply Chain Analysis (SCA) vulnerability detection. When Remote Config sends instrumentation targets, the agent retransforms loaded classes and injects detection logic at method entry using ASM. Instrumented methods call AppSecSCADetector.onMethodInvocation() to log vulnerable library usage (POC implementation). Future versions will report to Datadog backend with CVE metadata and stack traces.
1 parent 6893a5d commit d6fd88f

File tree

4 files changed

+250
-3
lines changed

4 files changed

+250
-3
lines changed

dd-java-agent/appsec/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ dependencies {
1717
implementation project(':telemetry')
1818
implementation group: 'io.sqreen', name: 'libsqreen', version: '17.2.0'
1919
implementation libs.moshi
20+
implementation libs.bundles.asm
2021

2122
testImplementation libs.bytebuddy
2223
testImplementation project(':remote-config:remote-config-core')
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package com.datadog.appsec.config;
2+
3+
import org.slf4j.Logger;
4+
import org.slf4j.LoggerFactory;
5+
6+
/**
7+
* Detection handler for Supply Chain Analysis (SCA) vulnerability detection.
8+
*
9+
* <p>This class is called from instrumented methods to report when vulnerable third-party
10+
* library methods are invoked at runtime.
11+
*
12+
* <p>POC implementation: Currently just logs detections. Future versions will report to Datadog
13+
* backend with vulnerability metadata, stack traces, and context.
14+
*/
15+
public class AppSecSCADetector {
16+
17+
private static final Logger log = LoggerFactory.getLogger(AppSecSCADetector.class);
18+
19+
/**
20+
* Called when an instrumented SCA target method is invoked.
21+
*
22+
* <p>This method is invoked from bytecode injected by {@link AppSecSCATransformer}.
23+
*
24+
* @param className the internal class name (e.g., "org/springframework/web/client/RestTemplate")
25+
* @param methodName the method name (e.g., "execute")
26+
* @param descriptor the method descriptor (e.g., "(Ljava/lang/String;)V")
27+
*/
28+
public static void onMethodInvocation(String className, String methodName, String descriptor) {
29+
try {
30+
// POC: Log the detection
31+
// Future: Report to Datadog backend with vulnerability context
32+
log.info(
33+
"[SCA DETECTION] Vulnerable method invoked: {}.{}{}",
34+
className.replace('/', '.'),
35+
methodName,
36+
descriptor);
37+
38+
// TODO: Future enhancements:
39+
// - Capture stack trace
40+
// - Add vulnerability metadata (CVE ID, severity, etc.)
41+
// - Report to Datadog backend via telemetry
42+
// - Rate limiting to avoid log spam
43+
// - Include request context if available
44+
45+
} catch (Throwable t) {
46+
// Catch all exceptions to avoid breaking the instrumented method
47+
log.error("Error in SCA detection handler", t);
48+
}
49+
}
50+
}

dd-java-agent/appsec/src/main/java/com/datadog/appsec/config/AppSecSCAInstrumentationUpdater.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,10 +103,9 @@ private void applyInstrumentation(AppSecSCAConfig oldConfig, AppSecSCAConfig new
103103
}
104104

105105
// Install new transformer
106-
// TODO: Create AppSecSCATransformer
107106
log.debug("Installing new SCA transformer for targets: {}", targetClassNames);
108-
// currentTransformer = new AppSecSCATransformer(newConfig);
109-
// instrumentation.addTransformer(currentTransformer, true);
107+
currentTransformer = new AppSecSCATransformer(newConfig);
108+
instrumentation.addTransformer(currentTransformer, true);
110109

111110
// Find loaded classes that match targets
112111
List<Class<?>> classesToRetransform = findLoadedClasses(targetClassNames);
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
package com.datadog.appsec.config;
2+
3+
import java.lang.instrument.ClassFileTransformer;
4+
import java.lang.instrument.IllegalClassFormatException;
5+
import java.security.ProtectionDomain;
6+
import java.util.HashMap;
7+
import java.util.Map;
8+
import org.objectweb.asm.ClassReader;
9+
import org.objectweb.asm.ClassVisitor;
10+
import org.objectweb.asm.ClassWriter;
11+
import org.objectweb.asm.MethodVisitor;
12+
import org.objectweb.asm.Opcodes;
13+
import org.slf4j.Logger;
14+
import org.slf4j.LoggerFactory;
15+
16+
/**
17+
* ClassFileTransformer for Supply Chain Analysis (SCA) vulnerability detection.
18+
*
19+
* <p>Instruments methods specified in the SCA configuration to detect when vulnerable
20+
* third-party library methods are called at runtime.
21+
*
22+
* <p>This is a POC implementation that logs method invocations. Future versions will report
23+
* to the Datadog backend with vulnerability details.
24+
*/
25+
public class AppSecSCATransformer implements ClassFileTransformer {
26+
27+
private static final Logger log = LoggerFactory.getLogger(AppSecSCATransformer.class);
28+
29+
private final Map<String, TargetMethods> targetsByClass;
30+
31+
/**
32+
* Creates a new SCA transformer with the given instrumentation targets.
33+
*
34+
* @param config the SCA configuration containing instrumentation targets
35+
*/
36+
public AppSecSCATransformer(AppSecSCAConfig config) {
37+
this.targetsByClass = buildTargetsMap(config);
38+
log.debug("Created SCA transformer with {} target classes", targetsByClass.size());
39+
}
40+
41+
private Map<String, TargetMethods> buildTargetsMap(AppSecSCAConfig config) {
42+
Map<String, TargetMethods> map = new HashMap<>();
43+
44+
if (config.instrumentationTargets == null) {
45+
return map;
46+
}
47+
48+
for (AppSecSCAConfig.InstrumentationTarget target : config.instrumentationTargets) {
49+
if (target.className == null || target.methodName == null) {
50+
continue;
51+
}
52+
53+
// Convert internal format (org/foo/Bar) to internal format (already is internal)
54+
String internalClassName = target.className;
55+
56+
TargetMethods methods = map.computeIfAbsent(internalClassName, k -> new TargetMethods());
57+
methods.addMethod(target.methodName);
58+
}
59+
60+
return map;
61+
}
62+
63+
@Override
64+
public byte[] transform(
65+
ClassLoader loader,
66+
String className,
67+
Class<?> classBeingRedefined,
68+
ProtectionDomain protectionDomain,
69+
byte[] classfileBuffer)
70+
throws IllegalClassFormatException {
71+
72+
if (className == null) {
73+
return null;
74+
}
75+
76+
// Check if this class is a target
77+
TargetMethods targetMethods = targetsByClass.get(className);
78+
if (targetMethods == null) {
79+
return null; // Not a target class
80+
}
81+
82+
try {
83+
log.debug("Instrumenting SCA target class: {}", className);
84+
return instrumentClass(classfileBuffer, className, targetMethods);
85+
} catch (Exception e) {
86+
log.error("Failed to instrument SCA target class: {}", className, e);
87+
return null; // Return null to keep original bytecode
88+
}
89+
}
90+
91+
private byte[] instrumentClass(
92+
byte[] originalBytecode, String className, TargetMethods targetMethods) {
93+
ClassReader reader = new ClassReader(originalBytecode);
94+
ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_FRAMES);
95+
96+
ClassVisitor visitor = new SCAClassVisitor(writer, className, targetMethods);
97+
98+
try {
99+
reader.accept(visitor, ClassReader.EXPAND_FRAMES);
100+
byte[] transformedBytecode = writer.toByteArray();
101+
log.info("Successfully instrumented SCA target class: {}", className);
102+
return transformedBytecode;
103+
} catch (Exception e) {
104+
log.error("Error during ASM transformation for class: {}", className, e);
105+
return null;
106+
}
107+
}
108+
109+
/**
110+
* ASM ClassVisitor that instruments methods matching SCA targets.
111+
*/
112+
private static class SCAClassVisitor extends ClassVisitor {
113+
private final String className;
114+
private final TargetMethods targetMethods;
115+
116+
SCAClassVisitor(ClassVisitor cv, String className, TargetMethods targetMethods) {
117+
super(Opcodes.ASM9, cv);
118+
this.className = className;
119+
this.targetMethods = targetMethods;
120+
}
121+
122+
@Override
123+
public MethodVisitor visitMethod(
124+
int access, String name, String descriptor, String signature, String[] exceptions) {
125+
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
126+
127+
// Check if this method is a target
128+
if (targetMethods.contains(name)) {
129+
log.debug("Instrumenting SCA target method: {}::{}", className, name);
130+
return new SCAMethodVisitor(mv, className, name, descriptor);
131+
}
132+
133+
return mv;
134+
}
135+
}
136+
137+
/**
138+
* ASM MethodVisitor that injects SCA detection logic at method entry.
139+
*/
140+
private static class SCAMethodVisitor extends MethodVisitor {
141+
private final String className;
142+
private final String methodName;
143+
private final String descriptor;
144+
145+
SCAMethodVisitor(MethodVisitor mv, String className, String methodName, String descriptor) {
146+
super(Opcodes.ASM9, mv);
147+
this.className = className;
148+
this.methodName = methodName;
149+
this.descriptor = descriptor;
150+
}
151+
152+
@Override
153+
public void visitCode() {
154+
// Inject logging call at method entry
155+
// This is POC code - in production this would call a detection handler
156+
injectSCADetectionCall();
157+
super.visitCode();
158+
}
159+
160+
private void injectSCADetectionCall() {
161+
// Generate bytecode equivalent to:
162+
// AppSecSCADetector.onMethodInvocation("className", "methodName", "descriptor");
163+
164+
// Load the class name
165+
mv.visitLdcInsn(className);
166+
167+
// Load the method name
168+
mv.visitLdcInsn(methodName);
169+
170+
// Load the descriptor
171+
mv.visitLdcInsn(descriptor);
172+
173+
// Call the static detection method
174+
mv.visitMethodInsn(
175+
Opcodes.INVOKESTATIC,
176+
"com/datadog/appsec/config/AppSecSCADetector",
177+
"onMethodInvocation",
178+
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V",
179+
false);
180+
}
181+
}
182+
183+
/**
184+
* Helper class to store target methods for a class.
185+
*/
186+
private static class TargetMethods {
187+
private final Map<String, Boolean> methods = new HashMap<>();
188+
189+
void addMethod(String methodName) {
190+
methods.put(methodName, Boolean.TRUE);
191+
}
192+
193+
boolean contains(String methodName) {
194+
return methods.containsKey(methodName);
195+
}
196+
}
197+
}

0 commit comments

Comments
 (0)