Skip to content

Commit 9d6a07b

Browse files
Propagate AppSec blocking exceptions from bytebuddy supressions (#7516)
Update our exception handler to propagate blocking exceptions
1 parent ede24ee commit 9d6a07b

File tree

9 files changed

+146
-24
lines changed

9 files changed

+146
-24
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package datadog.trace.bootstrap.blocking;
2+
3+
import datadog.appsec.api.blocking.BlockingException;
4+
5+
public class BlockingExceptionHandler {
6+
7+
public static Throwable rethrowIfBlockingException(final Throwable e) {
8+
if (e instanceof BlockingException) {
9+
throw (BlockingException) e;
10+
}
11+
return e;
12+
}
13+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package datadog.trace.bootstrap.blocking
2+
3+
import datadog.appsec.api.blocking.BlockingException
4+
import spock.lang.Specification
5+
6+
class BlockingExceptionHandlerTest extends Specification {
7+
8+
void 'test blocking exception handler'() {
9+
10+
when:
11+
BlockingExceptionHandler.rethrowIfBlockingException(new BlockingException())
12+
13+
then:
14+
thrown(BlockingException)
15+
16+
when:
17+
def result = BlockingExceptionHandler.rethrowIfBlockingException(new UnsupportedOperationException())
18+
19+
then:
20+
noExceptionThrown()
21+
result instanceof UnsupportedOperationException
22+
}
23+
}

dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/ExceptionHandlers.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package datadog.trace.agent.tooling.bytebuddy;
22

33
import datadog.trace.api.InstrumenterConfig;
4+
import datadog.trace.api.ProductActivation;
45
import datadog.trace.bootstrap.ExceptionLogger;
56
import net.bytebuddy.ClassFileVersion;
67
import net.bytebuddy.asm.Advice.ExceptionHandler;
@@ -24,6 +25,8 @@ public class ExceptionHandlers {
2425
new StackManipulation() {
2526
// Pops one Throwable off the stack. Maxes the stack to at least 3.
2627
private final Size size = new StackManipulation.Size(-1, 3);
28+
private final boolean appSecEnabled =
29+
InstrumenterConfig.get().getAppSecActivation() != ProductActivation.FULLY_DISABLED;
2730

2831
@Override
2932
public boolean isValid() {
@@ -38,15 +41,19 @@ public Size apply(final MethodVisitor mv, final Implementation.Context context)
3841

3942
// Writes the following bytecode if exitOnFailure is false:
4043
//
44+
// BlockingExceptionHandler.rethrowIfBlockingException(t);
4145
// try {
46+
// InstrumentationErrors.incrementErrorCount();
4247
// org.slf4j.LoggerFactory.getLogger((Class)ExceptionLogger.class)
4348
// .debug("Failed to handle exception in instrumentation for ...", t);
4449
// } catch (Throwable t2) {
4550
// }
4651
//
4752
// And the following bytecode if exitOnFailure is true:
4853
//
54+
// BlockingExceptionHandler.rethrowIfBlockingException(t);
4955
// try {
56+
// InstrumentationErrors.incrementErrorCount();
5057
// org.slf4j.LoggerFactory.getLogger((Class)ExceptionLogger.class)
5158
// .error("Failed to handle exception in instrumentation for ...", t);
5259
// System.exit(1);
@@ -62,6 +69,16 @@ public Size apply(final MethodVisitor mv, final Implementation.Context context)
6269
final boolean frames =
6370
context.getClassFileVersion().isAtLeast(ClassFileVersion.JAVA_V6);
6471

72+
if (appSecEnabled) {
73+
// rethrow blocking exceptions
74+
// stack: (top) throwable
75+
mv.visitMethodInsn(
76+
Opcodes.INVOKESTATIC,
77+
"datadog/trace/bootstrap/blocking/BlockingExceptionHandler",
78+
"rethrowIfBlockingException",
79+
"(Ljava/lang/Throwable;)Ljava/lang/Throwable;");
80+
}
81+
6582
mv.visitTryCatchBlock(logStart, logEnd, eatException, "java/lang/Throwable");
6683
mv.visitLabel(logStart);
6784
// invoke incrementAndGet on our exception counter
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package datadog.trace.agent.test
2+
3+
import ch.qos.logback.classic.Level
4+
import datadog.trace.api.Platform
5+
import spock.lang.IgnoreIf
6+
7+
@IgnoreIf(reason = "SecurityManager used in the test is marked for removal and throws exceptions", value = {
8+
Platform.isJavaVersionAtLeast(21)
9+
})
10+
class AppSecDisabledExceptionHandlerForkedTest extends BaseExceptionHandlerTest {
11+
12+
@Override
13+
protected void changeConfig() {
14+
injectSysConfig('appsec.enabled', 'false')
15+
}
16+
17+
@Override
18+
protected int expectedFailureExitStatus() {
19+
return 0
20+
}
21+
22+
@Override
23+
protected Level expectedFailureLogLevel() {
24+
return Level.DEBUG
25+
}
26+
27+
protected boolean expectedBlockingException() {
28+
return false
29+
}
30+
}

dd-java-agent/agent-tooling/src/test/groovy/datadog/trace/agent/test/BaseExceptionHandlerTest.groovy

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ package datadog.trace.agent.test
33
import ch.qos.logback.classic.Level
44
import ch.qos.logback.classic.Logger
55
import ch.qos.logback.core.read.ListAppender
6+
import datadog.appsec.api.blocking.BlockingException
67
import datadog.trace.agent.tooling.bytebuddy.ExceptionHandlers
78
import datadog.trace.api.Platform
89
import datadog.trace.bootstrap.ExceptionLogger
910
import datadog.trace.bootstrap.InstrumentationErrors
11+
import datadog.trace.bootstrap.blocking.BlockingExceptionHandler
1012
import datadog.trace.test.util.DDSpecification
1113
import net.bytebuddy.agent.ByteBuddyAgent
1214
import net.bytebuddy.agent.builder.AgentBuilder
@@ -60,6 +62,13 @@ abstract class BaseExceptionHandlerTest extends DDSpecification {
6062
.advice(
6163
isMethod().and(namedOneOf("smallStack", "largeStack")),
6264
BadAdvice.NoOpAdvice.getName()))
65+
.transform(
66+
new AgentBuilder.Transformer.ForAdvice()
67+
.with(new AgentBuilder.LocationStrategy.Simple(ClassFileLocator.ForClassLoader.of(BadAdvice.getClassLoader())))
68+
.withExceptionHandler(ExceptionHandlers.defaultExceptionHandler())
69+
.advice(
70+
isMethod().and(named("blockingException")),
71+
BlockingExceptionAdvice.getName()))
6372

6473
ByteBuddyAgent.install()
6574
transformer = builder.installOn(ByteBuddyAgent.getInstrumentation())
@@ -94,6 +103,10 @@ abstract class BaseExceptionHandlerTest extends DDSpecification {
94103

95104
abstract protected Level expectedFailureLogLevel()
96105

106+
protected boolean expectedBlockingException() {
107+
true
108+
}
109+
97110
def "exception handler invoked"() {
98111
setup:
99112
int initLogEvents = testAppender.list.size()
@@ -113,9 +126,21 @@ abstract class BaseExceptionHandlerTest extends DDSpecification {
113126
int initLogEvents = testAppender.list.size()
114127
URL[] classpath = [
115128
SomeClass.getProtectionDomain().getCodeSource().getLocation(),
116-
GroovyObject.getProtectionDomain().getCodeSource().getLocation()
129+
GroovyObject.getProtectionDomain().getCodeSource().getLocation(),
117130
]
118-
URLClassLoader loader = new URLClassLoader(classpath, (ClassLoader) null)
131+
URLClassLoader loader = new URLClassLoader(classpath, null, null) {
132+
@Override
133+
Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
134+
if (name == BlockingExceptionHandler.name) {
135+
return BlockingExceptionHandler
136+
}
137+
if (name == BlockingException.name) {
138+
return BlockingException
139+
}
140+
return super.loadClass(name, resolve)
141+
}
142+
}
143+
119144
when:
120145
loader.loadClass(InstrumentationErrors.getName())
121146
then:
@@ -140,6 +165,23 @@ abstract class BaseExceptionHandlerTest extends DDSpecification {
140165
exitStatus.get() == 0
141166
}
142167

168+
void 'blocking exception is rethrown'() {
169+
when:
170+
Throwable exception = null
171+
try {
172+
SomeClass.blockingException()
173+
} catch (Throwable t) {
174+
exception = t
175+
}
176+
177+
then:
178+
if (expectedBlockingException()) {
179+
exception instanceof BlockingException
180+
} else {
181+
exception == null
182+
}
183+
}
184+
143185
static class SomeClass {
144186
static boolean isInstrumented() {
145187
return false
@@ -157,6 +199,10 @@ abstract class BaseExceptionHandlerTest extends DDSpecification {
157199
Object o = new Object()
158200
println "large stack: $l $i $d $o"
159201
}
202+
203+
static void blockingException() {
204+
// do nothing and throw from the advice
205+
}
160206
}
161207

162208
private static class NoExitSecurityManager extends SecurityManager {
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package datadog.trace.agent.test;
2+
3+
import datadog.appsec.api.blocking.BlockingException;
4+
import net.bytebuddy.asm.Advice;
5+
6+
public class BlockingExceptionAdvice {
7+
8+
@Advice.OnMethodExit(suppress = Throwable.class)
9+
public static void throwAnException() {
10+
throw new BlockingException("You are blocked");
11+
}
12+
}

dd-java-agent/instrumentation/graal/native-image/src/main/java/datadog/trace/instrumentation/graal/nativeimage/NativeImageGeneratorRunnerInstrumentation.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ public static void onEnter(@Advice.Argument(value = 0, readOnly = false) String[
9494
+ "datadog.trace.bootstrap.InstrumentationClassLoader:build_time,"
9595
+ "datadog.trace.bootstrap.FieldBackedContextStores:build_time,"
9696
+ "datadog.trace.bootstrap.benchmark.StaticEventLogger:build_time,"
97+
+ "datadog.trace.bootstrap.blocking.BlockingExceptionHandler:build_time,"
9798
+ "datadog.trace.bootstrap.InstrumentationErrors:build_time,"
9899
+ "datadog.trace.bootstrap.instrumentation.java.concurrent.ConcurrentState:build_time,"
99100
+ "datadog.trace.bootstrap.instrumentation.java.concurrent.ExcludeFilter:build_time,"

dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/InstrumentationLogger.java

Lines changed: 0 additions & 11 deletions
This file was deleted.

dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/StatementInstrumentation.java

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,7 @@ public Map<String, String> contextStore() {
5656

5757
@Override
5858
public String[] helperClassNames() {
59-
return new String[] {
60-
packageName + ".JDBCDecorator",
61-
packageName + ".SQLCommenter",
62-
packageName + ".InstrumentationLogger",
63-
};
59+
return new String[] {packageName + ".JDBCDecorator", packageName + ".SQLCommenter"};
6460
}
6561

6662
// prepend mode will prepend the SQL comment to the raw sql query
@@ -74,7 +70,7 @@ public void methodAdvice(MethodTransformer transformer) {
7470
}
7571

7672
public static class StatementAdvice {
77-
@Advice.OnMethodEnter()
73+
@Advice.OnMethodEnter(suppress = Throwable.class)
7874
public static AgentScope onEnter(
7975
@Advice.Argument(value = 0, readOnly = false) String sql,
8076
@Advice.This final Statement statement) {
@@ -135,11 +131,6 @@ public static AgentScope onEnter(
135131
CallDepthThreadLocalMap.reset(Statement.class);
136132
// re-throw blocking exceptions
137133
throw e;
138-
} catch (Throwable e) {
139-
// suppress anything else
140-
InstrumentationLogger.debug(
141-
"datadog.trace.instrumentation.jdbc.StatementInstrumentation", statement.getClass(), e);
142-
return null;
143134
}
144135
}
145136

0 commit comments

Comments
 (0)