Skip to content

feat(waferrors): add ToWafReturnCode - #239

Merged
RomainMuller merged 5 commits into
mainfrom
feat/waferrors-to-waf-return-code
Nov 9, 2026
Merged

RomainMuller merged 5 commits into
mainfrom
feat/waferrors-to-waf-return-code

Conversation

@RomainMuller

@RomainMuller RomainMuller commented Nov 6, 2026 •

Copy link
Copy Markdown
Collaborator

What does this PR do?

Adds waferrors.ToWafReturnCode(in error) int. It returns the code to report for an error from go-libddwaf. Recognized errors use the native ddwaf_run return codes (DDWAF_RET_CODE). -127 is the reporting fallback when no recognized code is available. ToWafErrorCode does not change.

Contract

  • nil gives 0.
  • ErrInternal, ErrInvalidObject and ErrInvalidArgument give -3, -2 and -1.
  • Any other error gives -127: ErrOutOfMemory, ErrEmptyRuleAddresses, an unknown RunError, an unknown return code (ErrUnknownReturnCode), and any error that is not a RunError. libddwaf never returns -127; the Datadog tracers report it for errors from the bindings (RFC-1012). It is an unexported constant.
  • A timeout (ErrTimeout) gives no code, and it never hides another error of the tree. On a timeout, ddwaf_run returns DDWAF_OK or DDWAF_MATCH, or is not called at all (the time budget is used up in Go first). A tree that holds only timeouts gives 0. Zero means no error to report; it does not distinguish DDWAF_OK from DDWAF_MATCH. To know if a timeout occurred, use errors.Is(err, ErrTimeout). It finds the timeout errors that go-libddwaf returns, but not a custom error that only has an As method.
  • It looks at the whole error tree (Unwrap() error and Unwrap() []error, for example errors.Join), with an iterative work list (no recursion, no visited set). An error matches a RunError with the same rules as one step of errors.As (its type, or its As(any) bool method), but a match does not stop the search: the wrapped errors count too. The errors are visited in the same order as errors.As (an error before the errors it wraps; Unwrap() []error from left to right). When the tree has more than one code, the highest one wins (the closest to zero). Like errors.As, it does not detect a cycle.

Results (the 39 cases of TestToWafReturnCode)

Input Result
nil 0
ErrInternal / ErrInvalidObject / ErrInvalidArgument -3 / -2 / -1
ErrTimeout; wrapped ErrTimeout 0
ErrOutOfMemory; ErrEmptyRuleAddresses -127
RunError(99); RunError(0); RunError(-3) -127
ErrUnknownReturnCode wrapped with a code -127
errors.New(...); ErrContextClosed; *PanicError -127
ErrInvalidObject wrapped once; ErrInvalidArgument wrapped 3 times; wrapped 10,000 times -2; -1; -1
Join(ErrInternal, ErrInvalidArgument, ErrInvalidObject) -1 (highest)
Join(error, ErrInternal) -3
Join(error, ErrOutOfMemory) -127
Join(ErrTimeout, ErrInternal); Join(ErrTimeout, ErrInvalidObject); Join(ErrInvalidArgument, ErrTimeout) -3; -2; -1 (a timeout never hides a code)
Join(ErrTimeout, ErrOutOfMemory); Join(ErrTimeout, RunError(99)); Join(ErrTimeout, error) -127
Join(ErrTimeout, wrapped ErrTimeout) 0
fmt.Errorf("%w: %w", ErrResultInvalidType, ErrInvalidObject) -2
fmt.Errorf("run: %w", Join(error, wrapped ErrInvalidArgument)) -1
As method gives ErrInternal; gives ErrTimeout -3; 0
As gives ErrInternal and wraps ErrInvalidArgument -1 (a match does not stop the search)
As gives RunError(99) and wraps ErrInternal -3
As gives ErrTimeout and wraps an error; wraps ErrInternal -127; -3
As returns true without setting the target -127
Unwrap() []error that returns only nil -127
Join(ErrTimeout, <error whose As returns true without setting the target>) -127 (the target is reset between nodes)

TestToWafReturnCodeOrder checks that the errors are visited in the same order as errors.As. TestGoRunErrorReturnCodeRoundTrip (result_code_test.go, no build tag) checks the result against the bindings constants (WAFErrInternal, WAFErrInvalidObject, WAFErrInvalidArgument, WAFOK: same code; WAFMatch: 0; an unknown code: -127). internal/bindings cannot be the source of the values: it imports waferrors.

API compatibility

gorelease -base=v5.0.1:

# github.com/DataDog/go-libddwaf/v5/waferrors
## compatible changes
ToWafReturnCode: added

# summary
Suggested version: v5.1.0

Motivation

dd-trace-go reports this code in its AppSec telemetry (waf_error tag, RFC-1012) and had its own copy of the mapping. See DataDog/dd-trace-go#5827 and the review there ("Wouldn't be better to directly do this in go-libddwaf?"). With this function, dd-trace-go deletes its copy and its -127 fallback, and it reports a real error even when the same run also timed out.

Add `waferrors.ToWafReturnCode`. It returns the ddwaf_run return code
(`DDWAF_RET_CODE`) that an error comes from: -3 for `ErrInternal`, -2
for `ErrInvalidObject` and -1 for `ErrInvalidArgument`. It returns zero
for every other error, including `nil`, an error that is not a
`RunError`, and a `RunError` that go-libddwaf reports itself, such as
`ErrTimeout`.

`ToWafErrorCode` does not change. It returns the positive `RunError`
value, not the libddwaf code. Callers that report the libddwaf code (for
example the tracers' telemetry) had to copy the mapping.
@RomainMuller
RomainMuller requested a review from a team as a code owner November 6, 2026 15:02
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 6, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-09T13:14:11.300953Z 959407e New commits
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@datadog-datadog-prod-us1

datadog-datadog-prod-us1 Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

Pipelines

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 94b84f2 | Docs | Give us feedback!

…7 for other errors

`ToWafReturnCode` now returns the code to report for any error from
go-libddwaf, not only for the three native errors:

- `nil` gives 0.
- `ErrInternal`, `ErrInvalidObject` and `ErrInvalidArgument` give -3,
  -2 and -1.
- Any other error gives -127: `ErrOutOfMemory`, `ErrEmptyRuleAddresses`,
  an unknown `RunError`, an unknown return code, and errors that are not
  a `RunError`.
- A timeout gives no code, and never hides another error. A tree that
  holds only timeouts gives 0.

It walks the whole error tree (`Unwrap() error` and `Unwrap() []error`)
with an iterative work list, visits the errors in the same order as
`errors.As`, and returns the highest code (the closest to zero). An error
that matches by its `As` method does not stop the search.
RomainMuller added a commit to DataDog/dd-trace-go that referenced this pull request Nov 9, 2026
Use the head of DataDog/go-libddwaf#239 through a pseudo-version, in the
root module only. This commit never lands.
… nodes

An error whose As method returns true without setting the target must
give -127, also when an earlier node of the tree matched a timeout.
Declaring the target outside the walk loop would keep the timeout and
give 0.
RomainMuller added a commit to DataDog/dd-trace-go that referenced this pull request Nov 9, 2026
…waf#239

Use the head of DataDog/go-libddwaf#239 (95a3e52a8) through a
pseudo-version, in the root module only, to test waferrors.ToWafReturnCode
before go-libddwaf v5.1.0 is released.

Do not merge this commit. Before the merge of this PR, merge main again,
after the go-libddwaf upgrade of main, so that go.mod and go.sum come from
main.
Comment thread waferrors/waf.go Resolved
RomainMuller and others added 2 commits November 9, 2026 14:12
Zero means no error to report; it does not distinguish DDWAF_OK from
DDWAF_MATCH. Remove a sentence that repeated the one before it.
@RomainMuller
RomainMuller merged commit 0ee7e32 into main Nov 9, 2026
109 checks passed
@RomainMuller
RomainMuller deleted the feat/waferrors-to-waf-return-code branch November 9, 2026 14:20
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