Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
113a42c
fix: add reachabilityFence to prevent premature GC of Arena buffers
jandro996 May 11, 2026
a41d964
fix: move reachabilityFences into finally block to cover exception paths
jandro996 May 12, 2026
ed2e715
fix: use volatile-write fence for Java-8 compatibility
jandro996 May 12, 2026
446a719
test: fix CodeNarc violations and ASAN timeout in ReachabilityFenceTest
jandro996 May 13, 2026
0a991d9
fix: remove non-ASCII em dash, reduce GC stress test iterations
jandro996 May 21, 2026
c7080e5
Merge branch 'master' into fix/reachability-fence-crash-jdk21-jdk25
jandro996 Jun 6, 2026
cf86386
test: temporarily comment out reachabilityFence to verify CI regression
jandro996 Jun 6, 2026
466fc1f
test: add JIT warmup to force C2 compilation before GC pressure
jandro996 Jun 8, 2026
4c1871b
test: add testGCRace task without Jacoco instrumentation
jandro996 Jun 8, 2026
a0cc266
test: force C2 compilation in testGCRace with -XX:-TieredCompilation
jandro996 Jun 8, 2026
0087e0d
fix: restore reachabilityFence - confirmed crash on JDK 21.0.11 in pr…
jandro996 Jun 8, 2026
6084ed9
test: add concurrent-thread stress test to maximise GC race surface
jandro996 Jun 8, 2026
31fad79
test: fix CatchThrowable CodeNarc violation and WafHandle UAF in conc…
jandro996 Jun 8, 2026
9d73015
fix(test): avoid DuplicateNumberLiteral/DuplicateStringLiteral in con…
jandro996 Jun 8, 2026
4a2cfaa
fix(test): fix spotless indentation and move private helper after pub…
jandro996 Jun 8, 2026
727d8d2
fix(test): fix 3 CodeNarc violations and drain ArenaPool after concur…
jandro996 Jun 8, 2026
724a385
fix(testGCRace): increase heap to 1g for ZGC+aggressive JIT test worker
jandro996 Jun 9, 2026
f282010
build: increase Gradle daemon heap to 2g for test report generation
jandro996 Jun 9, 2026
89760a7
test: temporarily disable reachabilityFence to verify crash (revert me)
jandro996 Jun 9, 2026
d164221
style: fix spotless for commented-out fence lines
jandro996 Jun 9, 2026
f162528
fix: restore reachabilityFence (APPSEC-62784)
jandro996 Jun 9, 2026
6725658
test: remove reachability fence to validate CI crash detection (APPSE…
jandro996 Jun 10, 2026
bcdb97c
test: increase GC pressure aggressiveness in ReachabilityFenceTest (A…
jandro996 Jun 10, 2026
e1dfcaa
fix: exclude ReachabilityFenceTest from standard :test task (APPSEC-6…
jandro996 Jun 10, 2026
d5123ed
fix: restore reachability fence (APPSEC-62784)
jandro996 Jun 10, 2026
960957f
fix: suppress DEBUG logging in testGCRace to prevent OOM (APPSEC-62784)
jandro996 Jun 10, 2026
eaeb563
fix: increase testGCRace heap to 2g to prevent OOM under JDK25 ZGC
jandro996 Jun 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,47 @@ tasks.register('testgc', Test) {
}
check.dependsOn testgc

// Dedicated task for the GC race regression (APPSEC-62784).
// Must run without the Jacoco coverage agent: the agent instruments WafContext.run()
// bytecode in a way that keeps DirectByteBuffer references alive past the JNI boundary,
// preventing the reference elision the test is designed to detect.
// Only wired into 'check' when -PuseZGC is set (alpine-temurin21/25 CI matrix entries).
tasks.register('testGCRace', Test) {
description = 'GC race regression test without coverage instrumentation (ZGC builds only)'
group = 'verification'
useJUnit()
// ZGC + aggressive JIT (-XX:-TieredCompilation -XX:CompileThreshold=1) consume more
// native and heap memory than the default Gradle test worker allocation (512m).
maxHeapSize = '2g'
filter {
includeTestsMatching 'com.datadog.ddwaf.ReachabilityFenceTest'
}
}
tasks.named('testGCRace') {
jacoco.enabled = false
// Force immediate C2 compilation: disables tiered compilation and compiles
// methods after 1 invocation, ensuring reference elision happens from the
// first GC stress iteration without needing a warmup run.
// ExplicitGCInvokesConcurrent: makes System.gc() trigger ZGC concurrent
// cycles (instead of a potential stop-the-world), maximising the chance
// of a collection running concurrently with ddwaf_run().
jvmArgs '-XX:-TieredCompilation', '-XX:CompileThreshold=1',
'-XX:+ExplicitGCInvokesConcurrent',
// Suppress per-call DEBUG logging from ddwaf_native: each ddwaf_run()
// emits ~25 lines at DEBUG level. With 16 threads x 1000 iterations the
// log buffer alone exhausts the 1g worker heap and triggers GC overhead
// limit. WARN is sufficient; the test asserts on return values, not logs.
'-Dorg.slf4j.simpleLogger.defaultLogLevel=WARN'
}
if (project.hasProperty('useZGC')) {
check.dependsOn 'testGCRace'
}
// ReachabilityFenceTest contains long warmup loops and concurrent GC pressure
// threads designed for ZGC+C2. Running it in the standard :test task (ASAN,
// coverage, dev builds) makes those jobs take hours. Exclude it here; it runs
// exclusively via testGCRace when -PuseZGC is set.
test.filter.excludeTestsMatching 'com.datadog.ddwaf.ReachabilityFenceTest'

tasks.withType(Test).configureEach {
if (System.getenv('TEST_EXECUTABLE')) {
it.executable System.getenv('TEST_EXECUTABLE')
Expand Down
1 change: 1 addition & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m
18 changes: 16 additions & 2 deletions src/main/java/com/datadog/ddwaf/WafContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,21 @@

/**
* Originally intended to be a {@code final} class to enforce immutability and usage constraints.
* The {@code final} modifier was intentionally removed to improve testabilityspecifically to allow
* mocking in unit tests
* The {@code final} modifier was intentionally removed to improve testability - specifically to
* allow mocking in unit tests
*
* <p>This class should still be treated as final in spirit: it is not designed for extension , and
* should only be subclassed or mocked in test environments.
*/
public class WafContext implements Closeable {
private static final Logger LOGGER = LoggerFactory.getLogger(WafContext.class);

// Java-8 compatible reachability fence: a volatile write is a memory barrier
// the JIT cannot elide, keeping the written object strongly reachable until
// this point. Equivalent to Reference.reachabilityFence(obj) on JDK 9+.
@SuppressWarnings("unused")
private static volatile Object leaseFenceSink;

private final ByteBufferSerializer.ArenaLease lease;
private final LeakDetection.PhantomRefWithName<Object> selfRef;

Expand Down Expand Up @@ -120,6 +126,14 @@ private Waf.ResultWithData run(

result = runWafContext(persistentBuffer, ephemeralBuffer, newLimits, metrics);
} finally {
// Keep lease/ephemeralLease strongly reachable past the ddwaf_run JNI boundary.
// The JIT may elide references to this.lease and ephemeralLease after the last
// Java-visible use, letting the GC Cleaner free the underlying native memory while
// ddwaf_run is still executing (observed on ZGC Generational, JDK 21.0.8+/JDK 25).
// Volatile writes are memory barriers the JIT cannot remove. Placed in finally so
// they run on both normal and exceptional returns. See: APPSEC-62784
leaseFenceSink = this.lease;
leaseFenceSink = ephemeralLease;
if (ephemeralLease != null) {
ephemeralLease.close();
}
Expand Down
Loading
Loading