[AAP] Migrate libddwaf to 2.0.1 - #8959
Conversation
libddwaf 2.0 is a deliberate redesign of the C API: explicit memory ownership through allocators, a 16 byte ddwaf_object with map keys moved out into ddwaf_object_kv, no ddwaf_config, and subcontexts in place of ephemeral data. - pin 2.0.1 and require major >= 2, moving all 1.x to the incompatible list - rewrite the native bindings to the new object model and API surface - move the obfuscator regexes to a builder configuration - evaluate ephemeral (RASP) batches in a per-call subcontext - migrate both encoders, clamping containers to the new uint16 limit - require events to be present before reporting a security result, since 2.x also returns DDWAF_MATCH for attribute-only runs Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…s.csv Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Nothing ran in that case, so the call's wall clock would otherwise leak into the aggregated WAF runtime. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Execution-Time Benchmarks Report ⏱️Execution-time results for samples comparing This PR (8959) and master. ✅ No regressions detected - check the details below Full Metrics ComparisonFakeDbCommand
HttpMessageHandler
Comparison explanationExecution-time benchmarks measure the whole time it takes to execute a program, and are intended to measure the one-off costs. Cases where the execution time results for the PR are worse than latest master results are highlighted in **red**. The following thresholds were used for comparing the execution times:
Note that these results are based on a single point-in-time result for each branch. For full results, see the dashboard. Graphs show the p99 interval based on the mean and StdDev of the test run, as well as the mean value of the run (shown as a diamond below the graph). Duration chartsFakeDbCommand (.NET Framework 4.8)gantt
title Execution time (ms) FakeDbCommand (.NET Framework 4.8)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8959) - mean (74ms) : 70, 78
master - mean (71ms) : 69, 73
section Bailout
This PR (8959) - mean (76ms) : 74, 79
master - mean (79ms) : 73, 84
section CallTarget+Inlining+NGEN
This PR (8959) - mean (1,096ms) : 1042, 1150
master - mean (1,096ms) : 1041, 1152
FakeDbCommand (.NET Core 3.1)gantt
title Execution time (ms) FakeDbCommand (.NET Core 3.1)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8959) - mean (112ms) : 107, 118
master - mean (113ms) : 107, 120
section Bailout
This PR (8959) - mean (112ms) : 109, 114
master - mean (115ms) : 110, 120
section CallTarget+Inlining+NGEN
This PR (8959) - mean (792ms) : 766, 818
master - mean (785ms) : 766, 803
FakeDbCommand (.NET 6)gantt
title Execution time (ms) FakeDbCommand (.NET 6)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8959) - mean (99ms) : 96, 102
master - mean (100ms) : 96, 103
section Bailout
This PR (8959) - mean (100ms) : 98, 102
master - mean (102ms) : 98, 107
section CallTarget+Inlining+NGEN
This PR (8959) - mean (959ms) : 919, 999
master - mean (948ms) : 900, 995
FakeDbCommand (.NET 8)gantt
title Execution time (ms) FakeDbCommand (.NET 8)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8959) - mean (102ms) : 97, 107
master - mean (97ms) : 94, 99
section Bailout
This PR (8959) - mean (99ms) : 96, 103
master - mean (99ms) : 95, 104
section CallTarget+Inlining+NGEN
This PR (8959) - mean (826ms) : 776, 877
master - mean (824ms) : 775, 873
HttpMessageHandler (.NET Framework 4.8)gantt
title Execution time (ms) HttpMessageHandler (.NET Framework 4.8)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8959) - mean (209ms) : 204, 214
master - mean (209ms) : 203, 215
section Bailout
This PR (8959) - mean (214ms) : 210, 218
master - mean (214ms) : 210, 217
section CallTarget+Inlining+NGEN
This PR (8959) - mean (1,248ms) : 1205, 1292
master - mean (1,251ms) : 1208, 1294
HttpMessageHandler (.NET Core 3.1)gantt
title Execution time (ms) HttpMessageHandler (.NET Core 3.1)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8959) - mean (299ms) : 294, 304
master - mean (299ms) : 293, 306
section Bailout
This PR (8959) - mean (300ms) : 296, 304
master - mean (301ms) : 296, 306
section CallTarget+Inlining+NGEN
This PR (8959) - mean (997ms) : 978, 1016
master - mean (995ms) : 971, 1019
HttpMessageHandler (.NET 6)gantt
title Execution time (ms) HttpMessageHandler (.NET 6)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8959) - mean (292ms) : 288, 296
master - mean (294ms) : 289, 299
section Bailout
This PR (8959) - mean (293ms) : 288, 298
master - mean (293ms) : 289, 298
section CallTarget+Inlining+NGEN
This PR (8959) - mean (1,192ms) : 1161, 1224
master - mean (1,191ms) : 1147, 1236
HttpMessageHandler (.NET 8)gantt
title Execution time (ms) HttpMessageHandler (.NET 8)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8959) - mean (293ms) : 288, 299
master - mean (293ms) : 288, 299
section Bailout
This PR (8959) - mean (293ms) : 288, 299
master - mean (294ms) : 290, 298
section CallTarget+Inlining+NGEN
This PR (8959) - mean (1,068ms) : 1003, 1132
master - mean (1,076ms) : 1006, 1146
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
BenchmarksBenchmark execution time: 2026-07-30 18:21:39 Comparing candidate commit bde2214 in PR branch Found 1 performance improvements and 0 performance regressions! Performance is the same for 71 metrics, 0 unstable metrics, 64 known flaky benchmarks, 62 flaky benchmarks without significant changes.
|
This comment was marked as low quality.
This comment was marked as low quality.
|
@codex review |
|
Codex Review: Didn't find any major issues. 🎉 Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
e-n-0
left a comment
There was a problem hiding this comment.
I found one additional concurrency issue that cannot be anchored inline because the affected lock block is unchanged in this diff.
tracer/src/Datadog.Trace/AppSec/Waf/Waf.cs:104: the replacement WAF is built before the write lock is acquired. If EnterWriteLock() fails, or disposal wins and sets Disposed, the new handle is neither installed nor freed, but the successful update result is still returned. Please destroy every candidate handle that is not adopted and return a failed update when installation cannot occur.
Addresses review feedback on the libddwaf 2.0 migration: - Bind ddwaf_builder_destroy and release the builder on Waf.Dispose, plus free both native handles in Waf.Create when no Waf takes ownership. - Guard the builder with its own lock so Dispose can't destroy it while an update is still calling ddwaf_builder_*, which crashed WafConcurrencyTests with an AccessViolationException. - Pass builder configuration paths as UTF-8 bytes with a byte count: path_len feeds a std::string_view, so a character count truncated or overran any non-ASCII path. - Destroy every candidate instance Update builds but doesn't install, and return a failed result when installation can't happen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This comment was marked as outdated.
This comment was marked as outdated.
|
@e-n-0 thanks — all three findings were real and are fixed in 4d7b668. On the third one (
Verifying that fix turned up a crash worth flagging: once Local verification after the fixes: security unit tests 1041 passed / 0 failed / 4 skipped on net8.0 and 1029 / 0 / 4 on net48 — both matching the pre-change baseline — |
e-n-0
left a comment
There was a problem hiding this comment.
Two related issues are outside the changed hunks, so I could not anchor them inline:
-
RaspModulestill records everyDDWAF_MATCHas a RASP rule match. In v2,Matchalso covers attribute- or action-only output, so benign evaluations can inflate the match metric. UseShouldReportSecurityResultfor the match flag and block outcome. RaspModule.cs:228-238 -
GetKnownAddresses()still usesMarshal.PtrToStringAnsi. libddwaf strings are UTF-8, so this can corrupt non-ASCII addresses on Windows. Decode those buffers as UTF-8. WafLibraryInvoker.cs:303-308
Release every native handle nobody took ownership of: the builder when initialization throws before the Waf is built, and the freshly built instance when the update that produced it can't install it. A Dispose that couldn't take a lock now leaves the handle in place and lets the next caller finish the job, instead of leaking it for good. The obfuscator configuration also reports what the WAF complained about when it fails to load, as those diagnostics aren't surfaced anywhere else. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Since libddwaf 2.x a run that only produces attributes or actions also returns DDWAF_MATCH, so RASP was counting attribute only evaluations as rule matches. Use the same event aware status the reporting path already uses. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This comment was marked as duplicate.
This comment was marked as duplicate.
…ked it When Dispose can't take a lock, the operation holding it releases the native handles on its way out, so the ruleset isn't leaked for the process lifetime.
This comment was marked as duplicate.
This comment was marked as duplicate.
andrewlock
left a comment
There was a problem hiding this comment.
:blindfold:
:approval: for the build.steps.cs and smoke tests changes, you may want another pair of eyes on the ASM stuff given the extent of the changes, up to you!
Summary of changes
Migrates the
libddwafnative dependency from 1.30.0 to 2.0.1: new bindings for the 2.0 object model (16 byteddwaf_object, map keys in a separateddwaf_object_kv),ddwaf_configreplaced by builder configuration, ephemeral data replaced by subcontexts, both encoders migrated, and 1.x moved to the incompatible list. Managed only — no C++ in this repo calls the WAF API.Reason for change
libddwaf 2.0 is a breaking redesign of the C API (allocator-based ownership, smaller
ddwaf_object, noddwaf_config, subcontexts). Staying on 1.x means missing every future WAF release.Implementation details
Worth a reviewer's attention:
DDWAF_MATCHnow means "produced an event, attribute, etc", so attribute-only runs (fingerprints, API Security) returnMatch.ShouldReportSecurityResulttherefore requiresevents, otherwiseRuleTriggeredwould count every API Security run as an attack.Marshal.PtrToStringAnsi, and encoding passed an ANSI-marshalledstringwith a UTF-16 count, so length and encoding already disagreed for non-ASCII.EncoderLegacyconverts into a reused per-thread buffer, which is safe because libddwaf copies the bytes it is handed.uint16, so both encoders clamp to 65535 regardless ofapplySafetyLimits, reporting the truncation through the existing telemetry.Test coverage
DdwafObjectLayoutTestspins the object/kv sizes and every field offset, round-trips a map built by libddwaf itself, and cross-checks the hand-written encoder against a natively built object — a wrong offset corrupts memory rather than failing to compile.DD_EXPERIMENTAL_APPSEC_USE_UNSAFE_ENCODER=true._dd.appsec.waf.version, andkey_patharray indices now being integers (["arg",0]).Other details
Jira: APPSEC-69462
Removed a dead
libddwaf 1.8.2PackageReferencefromDatadog.Trace.Security.IntegrationTests.🤖 Generated with Claude Code