Skip to content

Commit 54e8a5d

Browse files
committed
fix
1 parent 6b32bdd commit 54e8a5d

File tree

2 files changed

+118
-22
lines changed

2 files changed

+118
-22
lines changed

dd-java-agent/appsec/src/main/java/com/datadog/appsec/api/security/AppSecSpanPostProcessor.java

Lines changed: 36 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -31,20 +31,26 @@ public AppSecSpanPostProcessor(ApiSecuritySampler sampler, EventProducerService
3131

3232
@Override
3333
public void process(@Nonnull AgentSpan span, @Nonnull BooleanSupplier timeoutCheck) {
34-
final RequestContext ctx_ = span.getRequestContext();
35-
if (ctx_ == null) {
36-
return;
37-
}
38-
final AppSecRequestContext ctx = ctx_.getData(RequestContextSlot.APPSEC);
39-
if (ctx == null) {
40-
return;
41-
}
42-
43-
if (!ctx.isKeepOpenForApiSecurityPostProcessing()) {
44-
return;
45-
}
34+
AppSecRequestContext ctx = null;
35+
RequestContext ctx_ = null;
36+
boolean needsRelease = false;
4637

4738
try {
39+
ctx_ = span.getRequestContext();
40+
if (ctx_ == null) {
41+
return;
42+
}
43+
ctx = ctx_.getData(RequestContextSlot.APPSEC);
44+
if (ctx == null) {
45+
return;
46+
}
47+
48+
// Check if we acquired a permit for this request - must be inside try to ensure finally runs
49+
needsRelease = ctx.isKeepOpenForApiSecurityPostProcessing();
50+
if (!needsRelease) {
51+
return;
52+
}
53+
4854
if (timeoutCheck.getAsBoolean()) {
4955
log.debug("Timeout detected, skipping API security post-processing");
5056
return;
@@ -56,17 +62,25 @@ public void process(@Nonnull AgentSpan span, @Nonnull BooleanSupplier timeoutChe
5662
log.debug("Request sampled, processing API security post-processing");
5763
extractSchemas(ctx, ctx_.getTraceSegment());
5864
} finally {
59-
ctx.setKeepOpenForApiSecurityPostProcessing(false);
60-
try {
61-
// XXX: Close the additive first. This is not strictly needed, but it'll prevent getting it
62-
// detected as a
63-
// missed request-ended event.
64-
ctx.closeWafContext();
65-
ctx.close();
66-
} catch (Exception e) {
67-
log.debug("Error closing AppSecRequestContext", e);
65+
// Always release the semaphore permit if we acquired one
66+
if (needsRelease) {
67+
if (ctx != null) {
68+
ctx.setKeepOpenForApiSecurityPostProcessing(false);
69+
// XXX: Close the additive first. This is not strictly needed, but it'll prevent getting
70+
// it detected as a missed request-ended event.
71+
try {
72+
ctx.closeWafContext();
73+
} catch (Exception e) {
74+
log.debug("Error closing WAF context", e);
75+
}
76+
try {
77+
ctx.close();
78+
} catch (Exception e) {
79+
log.debug("Error closing AppSecRequestContext", e);
80+
}
81+
}
82+
sampler.releaseOne();
6883
}
69-
sampler.releaseOne();
7084
}
7185
}
7286

dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/api/security/AppSecSpanPostProcessorTest.groovy

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,4 +248,86 @@ class AppSecSpanPostProcessorTest extends DDSpecification {
248248
1 * sampler.releaseOne()
249249
0 * _
250250
}
251+
252+
void 'permit is released even if extractSchemas throws exception'() {
253+
given:
254+
def sampler = Mock(ApiSecuritySamplerImpl)
255+
def producer = Mock(EventProducerService)
256+
def span = Mock(AgentSpan)
257+
def reqCtx = Mock(RequestContext)
258+
def traceSegment = Mock(TraceSegment)
259+
def ctx = Mock(AppSecRequestContext)
260+
def processor = new AppSecSpanPostProcessor(sampler, producer)
261+
262+
when:
263+
processor.process(span, { false })
264+
265+
then:
266+
def ex = thrown(RuntimeException)
267+
ex.message == "Unexpected error"
268+
1 * span.getRequestContext() >> reqCtx
269+
1 * reqCtx.getData(_) >> ctx
270+
1 * ctx.isKeepOpenForApiSecurityPostProcessing() >> true
271+
1 * sampler.sampleRequest(_) >> true
272+
1 * reqCtx.getTraceSegment() >> { throw new RuntimeException("Unexpected error") }
273+
1 * ctx.setKeepOpenForApiSecurityPostProcessing(false)
274+
1 * ctx.closeWafContext()
275+
1 * ctx.close()
276+
1 * sampler.releaseOne() // Critical: permit is still released despite exception
277+
0 * _
278+
}
279+
280+
void 'multiple requests do not exhaust semaphore permits'() {
281+
given:
282+
// Use real ApiSecuritySamplerImpl which has a semaphore with 4 permits
283+
def realSampler = new ApiSecuritySamplerImpl()
284+
def producer = Mock(EventProducerService)
285+
def processor = new AppSecSpanPostProcessor(realSampler, producer)
286+
287+
when: 'Process 5 consecutive requests that acquire permits'
288+
5.times { i ->
289+
def span = Mock(AgentSpan)
290+
def reqCtx = Mock(RequestContext)
291+
def ctx = Mock(AppSecRequestContext)
292+
293+
// Mock the interactions
294+
span.getRequestContext() >> reqCtx
295+
reqCtx.getData(_) >> ctx
296+
ctx.isKeepOpenForApiSecurityPostProcessing() >> true
297+
ctx.setKeepOpenForApiSecurityPostProcessing(false)
298+
ctx.closeWafContext()
299+
ctx.close()
300+
301+
// Process should complete without issues, releasing permit each time
302+
processor.process(span, { false })
303+
}
304+
305+
then: 'All requests complete successfully without permit exhaustion'
306+
noExceptionThrown()
307+
}
308+
309+
void 'permit is released when ctx cleanup operations fail'() {
310+
given:
311+
def sampler = Mock(ApiSecuritySamplerImpl)
312+
def producer = Mock(EventProducerService)
313+
def span = Mock(AgentSpan)
314+
def reqCtx = Mock(RequestContext)
315+
def ctx = Mock(AppSecRequestContext)
316+
def processor = new AppSecSpanPostProcessor(sampler, producer)
317+
318+
when:
319+
processor.process(span, { false })
320+
321+
then:
322+
noExceptionThrown()
323+
1 * span.getRequestContext() >> reqCtx
324+
1 * reqCtx.getData(_) >> ctx
325+
1 * ctx.isKeepOpenForApiSecurityPostProcessing() >> true
326+
1 * sampler.sampleRequest(_) >> false
327+
1 * ctx.setKeepOpenForApiSecurityPostProcessing(false)
328+
1 * ctx.closeWafContext() >> { throw new RuntimeException("WAF context close failed") }
329+
1 * ctx.close() >> { throw new RuntimeException("Context close failed") }
330+
1 * sampler.releaseOne() // Critical: permit is still released despite cleanup failures
331+
0 * _
332+
}
251333
}

0 commit comments

Comments
 (0)