Skip to content

Update libddwaf to 2.0.1 - #206

Merged
jandro996 merged 4 commits into
masterfrom
update-waf
Jul 31, 2026
Merged

Update libddwaf to 2.0.1#206
jandro996 merged 4 commits into
masterfrom
update-waf

Conversation

@jandro996

@jandro996 jandro996 commented Jul 29, 2026

Copy link
Copy Markdown
Member

What does this PR do?

Bumps the vendored libddwaf from 1.30.0 to 2.0.1 across all four version
sources of truth (submodule pointer, libddwafVersion in
.github/workflows/actions.yml, Waf.LIB_VERSION, and the artifact version
in build.gradle), and rewrites the JNI binding to follow libddwaf's major
C API change.

Motivation

Jira ticket: APPSEC-69428

Description of the Change

libddwaf 2.x replaces the API surface this binding hand-rolls against:

  • Explicit allocators. ddwaf_context_init/ddwaf_object_destroy now take
    an allocator. Input objects keep using alloc = NULL (view-only, zero-copy)
    • the 1:1 replacement for the old config.free_fn = NULL pattern.
  • ddwaf_object/ddwaf_object_kv layout change. ddwaf_object stays
    16 bytes; map entries now live in a new 32-byte ddwaf_object_kv struct
    (key + value side by side) instead of the key living on the child object.
    ByteBufferSerializer's zero-copy binary layout was rewritten for this,
    with _Static_asserts pinning every offset in ddwaf_layout.h and a
    runtime cross-check (ByteBufferSerializer.checkNativeLayout()) so a
    future libddwaf bump fails loudly instead of silently corrupting memory.
  • ddwaf_run -> ddwaf_context_eval. Persistent evaluation moves to the
    new function; ephemeral evaluation is replaced entirely by
    ddwaf_subcontext_init/eval/destroy (subcontexts), replacing the old
    ephemeral-buffer-per-call flow in WafContext.
  • ddwaf_config removed. Obfuscator regex configuration now goes through
    ddwaf_builder_add_or_update_config instead.
  • New uint16 container size/capacity. Containers are capped at 65535
    entries (previously effectively unbounded); insertion loops in
    waf_jni.c/ByteBufferSerializer.java now clamp to this limit, logging at
    WARN when truncation actually occurs (a regression test now covers the
    Java-side clamp as well).
  • Manual index-based map iteration replaced with the official
    ddwaf_object_get_size/at_key/at_value accessors on the result-reading
    path (output.c).
  • run(persistentData, ephemeralData, ...) now rejects the combined
    persistent+ephemeral case in Java, before any serialization happens
    (libddwaf 2.x has no combined-evaluation mode).
  • v1.0 ruleset test fixtures converted to schema 2.1 (v1.0 support was
    removed upstream).

Additional Notes

Behavior changes worth flagging for consumers (e.g. dd-trace-java):

  • The key_path array indices in event JSON are now emitted as JSON
    numbers instead of strings (matches libddwaf 2.x's own event schema; no
    key was renamed). Confirmed via ObfuscationTests.groovy, where the
    expected key_path for an array index changed from '0' to 0. Any
    consumer that deserializes this JSON into a strictly-typed field (e.g.
    List<String>) will need to update that type - please check before
    upgrading.
  • Calling the WAF with both persistent and ephemeral data at once (or
    neither) now throws InvalidArgumentWafException earlier: during Java
    serialization rather than after a native call. This path isn't reachable
    through the public run()/runEphemeral() API, only via the private
    combined-mode method, so it shouldn't affect existing callers.
  • Container/config entries beyond 65535 elements are now truncated (a new
    hard limit from libddwaf 2.x's uint16 ddwaf_object size field); this
    is logged at WARN.

This is a minor version bump (17.4.0 to 17.5.0), not a major one. No
Java method signature changed, and the one confirmed behavior change
(key_path index encoding) fails loudly for any strictly-typed consumer
rather than silently corrupting data, so it doesn't rise to the level of a
breaking change under this repo's versioning policy (see AGENTS.md).
That said, it's a real wire-format change, called out explicitly above so
downstream consumers can check their own parsing code before adopting this
version.

Major API migration: explicit allocators, ddwaf_object/ddwaf_object_kv
layout change, ddwaf_run -> ddwaf_context_eval, ephemerals -> subcontexts,
ddwaf_config removal (obfuscator config now via ddwaf_builder_add_or_update_config).

- Bump submodule/libddwafVersion/Waf.LIB_VERSION to 2.0.1, build.gradle to 17.5.0
- Rewrite waf_jni.c/output.c/byte_buffer.c for the new C API and ddwaf_object layout
- Add _Static_assert layout pinning (ddwaf_layout.h) plus a native/Java cross-check
  (ByteBufferSerializer.checkNativeLayout) so a future libddwaf bump fails loudly
  instead of corrupting memory
- Rewrite ByteBufferSerializer's zero-copy binary layout for the new 16/32-byte
  ddwaf_object/ddwaf_object_kv structures
- Reject combined persistent+ephemeral evaluation (no longer supported by libddwaf 2.x)
- Fix _has_events() reading a nonexistent key, always-true bug
- Cap container insertion loops at MAX_CONTAINER_SIZE to avoid uint16 overflow
- Convert v1.0 ruleset test fixtures to schema 2.1 (v1.0 support removed upstream)
- Quality pass: dedupe LSTR/duration-read helpers, initialize key_len before use in
  output.c accessors, guard obfuscator config writes, fix FLOAT->JSON inf/nan case,
  pre-size Map insertion, remove hidden mutable offset state in ByteBufferSerializer
- Fix clang-format-18 violations blocking CI (waf_jni.c)
- Rename DDWAF_RUN_TIMEOUT to LIBDDWAF_JAVA_RUN_TIMEOUT to avoid colliding
  with a libddwaf-owned macro name
- Fix wrong unit in comment (duration is nanoseconds, not microseconds)
- Extract duplicated key-string-fallback block in output.c into a helper
- Remove unnecessary FQN from WafContext javadoc
- Add round-trip test comparing ByteBufferSerializer output against the
  official ddwaf_object_set_* C API, plus small-string/string boundary tests
- Document why the MAX_CONTAINER_SIZE guard in writeArrayOrMap is currently
  unreachable
- Zero out write-destination slots on failure across all ByteBufferSerializer
  write paths, as defense in depth against a stale pointer reaching libddwaf
- Document the single-registrant assumption on libddwaf's global obfuscator
  config section
- Raise container-truncation logging from INFO to WARN and note the 65535
  cap is new in libddwaf 2.x
… rule

result.attributes.isEmpty() triggers CodeNarc's UnnecessaryGetter rule
(codenarcTest), which was failing CI (ASAN, Coverage, Development tests).
@datadog-datadog-us1-prod

datadog-datadog-us1-prod Bot commented Jul 29, 2026

Copy link
Copy Markdown

🎯 Code Coverage (details)
Patch Coverage: 60.75%
Overall Coverage: 56.38% (-0.97%)

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 2c6ba9e | Docs | Datadog PR Page | Give us feedback!

@jandro996
jandro996 marked this pull request as ready for review July 29, 2026 12:09
@jandro996
jandro996 requested a review from a team as a code owner July 29, 2026 12:09
@smola

smola commented Jul 29, 2026

Copy link
Copy Markdown
Member

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 🚀

Reviewed commit: 253bf5e49a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

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".

The 65535-entry uint16 cap introduced by libddwaf 2.x had no regression
test. Also align the truncation log level with waf_jni.c (WARN, not
DEBUG) so the new silent-truncation limit is actually visible.
@jandro996
jandro996 merged commit 9a4cf7b into master Jul 31, 2026
42 of 43 checks passed
@jandro996
jandro996 deleted the update-waf branch July 31, 2026 06:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants