Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
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
2 changes: 1 addition & 1 deletion _tools/libddwaf-updater/update.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/bin/bash

cd $(dirname $0)
cd "$(dirname "$0")" || exit
exec go run ./update.go "$@"
2 changes: 1 addition & 1 deletion alignement_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func TestWafObject(t *testing.T) {
_, err := Load()
require.NoError(t, err)

lib := gWafLib.Handle()
lib := bindings.Lib.Handle()

t.Run("invalid", func(t *testing.T) {
var actual bindings.WAFObject
Expand Down
22 changes: 10 additions & 12 deletions builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func NewBuilder(keyObfuscatorRegex string, valueObfuscatorRegex string) (*Builde

var pinner runtime.Pinner
defer pinner.Unpin()
hdl := gWafLib.BuilderInit(newConfig(&pinner, keyObfuscatorRegex, valueObfuscatorRegex))
hdl := bindings.Lib.BuilderInit(newConfig(&pinner, keyObfuscatorRegex, valueObfuscatorRegex))

if hdl == 0 {
return nil, errors.New("failed to initialize the WAF builder")
Expand All @@ -51,7 +51,7 @@ func (b *Builder) Close() {
if b == nil || b.handle == 0 {
return
}
gWafLib.BuilderDestroy(b.handle)
bindings.Lib.BuilderDestroy(b.handle)
b.handle = 0
}

Expand All @@ -65,15 +65,13 @@ const defaultRecommendedRulesetPath = "::/go-libddwaf/default/recommended.json"
// AddDefaultRecommendedRuleset adds the default recommended ruleset to the
// receiving [Builder], and returns the [Diagnostics] produced in the process.
func (b *Builder) AddDefaultRecommendedRuleset() (Diagnostics, error) {
var pinner runtime.Pinner
defer pinner.Unpin()

ruleset, err := ruleset.DefaultRuleset(&pinner)
defaultRuleset, err := ruleset.DefaultRuleset()
defer bindings.Lib.ObjectFree(&defaultRuleset)
if err != nil {
return Diagnostics{}, fmt.Errorf("failed to load default recommended ruleset: %w", err)
}

diag, err := b.addOrUpdateConfig(defaultRecommendedRulesetPath, &ruleset)
diag, err := b.addOrUpdateConfig(defaultRecommendedRulesetPath, &defaultRuleset)
if err == nil {
b.defaultLoaded = true
}
Expand Down Expand Up @@ -122,9 +120,9 @@ func (b *Builder) AddOrUpdateConfig(path string, fragment any) (Diagnostics, err
// Returns the [Diagnostics] produced by adding or updating this configuration.
func (b *Builder) addOrUpdateConfig(path string, cfg *bindings.WAFObject) (Diagnostics, error) {
var diagnosticsWafObj bindings.WAFObject
defer gWafLib.ObjectFree(&diagnosticsWafObj)
defer bindings.Lib.ObjectFree(&diagnosticsWafObj)

res := gWafLib.BuilderAddOrUpdateConfig(b.handle, path, cfg, &diagnosticsWafObj)
res := bindings.Lib.BuilderAddOrUpdateConfig(b.handle, path, cfg, &diagnosticsWafObj)

var diags Diagnostics
if !diagnosticsWafObj.IsInvalid() {
Expand All @@ -150,7 +148,7 @@ func (b *Builder) RemoveConfig(path string) bool {
return false
}

return gWafLib.BuilderRemoveConfig(b.handle, path)
return bindings.Lib.BuilderRemoveConfig(b.handle, path)
}

// ConfigPaths returns the list of currently loaded configuration paths.
Expand All @@ -159,7 +157,7 @@ func (b *Builder) ConfigPaths(filter string) []string {
return nil
}

return gWafLib.BuilderGetConfigPaths(b.handle, filter)
return bindings.Lib.BuilderGetConfigPaths(b.handle, filter)
}

// Build creates a new [Handle] instance that uses the current configuration.
Expand All @@ -171,7 +169,7 @@ func (b *Builder) Build() *Handle {
return nil
}

hdl := gWafLib.BuilderBuildInstance(b.handle)
hdl := bindings.Lib.BuilderBuildInstance(b.handle)
if hdl == 0 {
return nil
}
Expand Down
4 changes: 2 additions & 2 deletions builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -297,8 +297,8 @@ func TestBuilder(t *testing.T) {
require.NotEmpty(t, res.Events)
require.Equal(t,
map[string]any{"block_request": map[string]any{
"grpc_status_code": "10",
"status_code": "403",
"grpc_status_code": uint64(10),
"status_code": uint64(403),
"type": "auto",
}},
res.Actions,
Expand Down
6 changes: 3 additions & 3 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,12 +225,12 @@ func (context *Context) run(persistentData, ephemeralData *bindings.WAFObject, r

var result bindings.WAFObject
pinner.Pin(&result)
defer gWafLib.ObjectFree(&result)
defer bindings.Lib.ObjectFree(&result)

// The value of the timeout cannot exceed 2^55
// cf. https://en.cppreference.com/w/cpp/chrono/duration
timeout := uint64(runTimer.SumRemaining().Microseconds()) & 0x008FFFFFFFFFFFFF
ret := gWafLib.Run(context.cContext, persistentData, ephemeralData, &result, timeout)
ret := bindings.Lib.Run(context.cContext, persistentData, ephemeralData, &result, timeout)

decodeTimer := runTimer.MustLeaf(DecodeTimeKey)
decodeTimer.Start()
Expand Down Expand Up @@ -324,7 +324,7 @@ func (context *Context) Close() {
context.mutex.Lock()
defer context.mutex.Unlock()

gWafLib.ContextDestroy(context.cContext)
bindings.Lib.ContextDestroy(context.cContext)
defer context.handle.Close() // Reduce the reference counter of the Handle.
context.cContext = 0 // Makes it easy to spot use-after-free/double-free issues

Expand Down
8 changes: 4 additions & 4 deletions handle.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func (handle *Handle) NewContext(timerOptions ...timer.Option) (*Context, error)
return nil, fmt.Errorf("handle was released")
}

cContext := gWafLib.ContextInit(handle.cHandle)
cContext := bindings.Lib.ContextInit(handle.cHandle)
if cContext == 0 {
handle.Close() // We couldn't get a context, so we no longer have an implicit reference to the Handle in it...
return nil, fmt.Errorf("could not get C context")
Expand All @@ -77,13 +77,13 @@ func (handle *Handle) NewContext(timerOptions ...timer.Option) (*Context, error)
// Addresses returns the list of addresses the WAF has been configured to monitor based on the input
// ruleset.
func (handle *Handle) Addresses() []string {
return gWafLib.KnownAddresses(handle.cHandle)
return bindings.Lib.KnownAddresses(handle.cHandle)
}

// Actions returns the list of actions the WAF has been configured to monitor based on the input
// ruleset.
func (handle *Handle) Actions() []string {
return gWafLib.KnownActions(handle.cHandle)
return bindings.Lib.KnownActions(handle.cHandle)
}

// Close decrements the reference counter of this [Handle], possibly allowing it to be destroyed
Expand All @@ -95,7 +95,7 @@ func (handle *Handle) Close() {
return
}

gWafLib.Destroy(handle.cHandle)
bindings.Lib.Destroy(handle.cHandle)
handle.cHandle = 0 // Makes it easy to spot use-after-free/double-free issues
}

Expand Down
6 changes: 5 additions & 1 deletion internal/bindings/libddwaf.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,16 @@ type wafSymbols struct {
builderBuildInstance uintptr
builderGetConfigPaths uintptr
builderDestroy uintptr
setLogCb uintptr
destroy uintptr
knownAddresses uintptr
knownActions uintptr
getVersion uintptr
contextInit uintptr
contextDestroy uintptr
objectFree uintptr
objectFromJSON uintptr
run uintptr
setLogCb uintptr
}

// newWafSymbols resolves the symbols of [wafSymbols] from the provided
Expand Down Expand Up @@ -69,6 +70,9 @@ func newWafSymbols(handle uintptr) (syms wafSymbols, err error) {
if syms.objectFree, err = purego.Dlsym(handle, "ddwaf_object_free"); err != nil {
return syms, err
}
if syms.objectFromJSON, err = purego.Dlsym(handle, "ddwaf_object_from_json"); err != nil {
return syms, err
}
if syms.run, err = purego.Dlsym(handle, "ddwaf_run"); err != nil {
return syms, err
}
Expand Down
93 changes: 93 additions & 0 deletions internal/bindings/singleton.go
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved from waf.go to avoid circular dependencies

Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2016-present Datadog, Inc.

package bindings

import (
"errors"
"sync"

"github.com/DataDog/go-libddwaf/v4/internal/support"
)

// Globally dlopen() libddwaf only once because several dlopens (eg. in tests)
// aren't supported by macOS.
var (
// Lib is libddwaf's dynamic library handle and entrypoints. This is only safe to
// read after calling [Load] or having acquired [gMu].
Lib *WAFLib
// libddwaf's dlopen error if any. This is only safe to read after calling
// [Load] or having acquired [gMu].
gWafLoadErr error
// Protects the global variables above.
gMu sync.Mutex

openWafOnce sync.Once
)

// Load loads libddwaf's dynamic library. The dynamic library is opened only
// once by the first call to this function and internally stored globally.
// No function is currently provided in this API to unload it.
//
// This function is automatically called by [NewBuilder], and most users need
// not explicitly call it. It is however useful in order to explicitly check
// for the status of the Lib library's initialization.
//
// The function returns true when libddwaf was successfully loaded, along with
// an error value. An error might still be returned even though the Lib load was
// successful: in such cases the error is indicative that some non-critical
// features are not available; but the Lib may still be used.
func Load() (bool, error) {
if ok, err := Usable(); !ok {
return false, err
}

openWafOnce.Do(func() {
// Acquire the global state mutex so we don't have a race condition with
// [Usable] here.
gMu.Lock()
defer gMu.Unlock()

Lib, gWafLoadErr = newWAFLib()

Check failure on line 53 in internal/bindings/singleton.go

View workflow job for this annotation

GitHub Actions / GitHub Runner / ubuntu-24.04 oldstable

undefined: newWAFLib

Check failure on line 53 in internal/bindings/singleton.go

View workflow job for this annotation

GitHub Actions / GitHub Runner / ubuntu-24.04 stable (DD_APPSEC_WAF_LOG_LEVEL=TRACE)

undefined: newWAFLib

Check failure on line 53 in internal/bindings/singleton.go

View workflow job for this annotation

GitHub Actions / GitHub Runner / macos-14 oldstable

undefined: newWAFLib

Check failure on line 53 in internal/bindings/singleton.go

View workflow job for this annotation

GitHub Actions / GitHub Runner / macos-15 stable (DD_APPSEC_WAF_LOG_LEVEL=TRACE)

undefined: newWAFLib

Check failure on line 53 in internal/bindings/singleton.go

View workflow job for this annotation

GitHub Actions / GitHub Runner / windows-latest stable (DD_APPSEC_WAF_LOG_LEVEL=TRACE)

undefined: newWAFLib

Check failure on line 53 in internal/bindings/singleton.go

View workflow job for this annotation

GitHub Actions / GitHub Runner / macos-15 oldstable

undefined: newWAFLib

Check failure on line 53 in internal/bindings/singleton.go

View workflow job for this annotation

GitHub Actions / GitHub Runner / windows-latest oldstable

undefined: newWAFLib

Check failure on line 53 in internal/bindings/singleton.go

View workflow job for this annotation

GitHub Actions / GitHub Runner / ubuntu-22.04 oldstable

undefined: newWAFLib

Check failure on line 53 in internal/bindings/singleton.go

View workflow job for this annotation

GitHub Actions / GitHub Runner / ubuntu-22.04 stable (DD_APPSEC_WAF_LOG_LEVEL=TRACE)

undefined: newWAFLib

Check failure on line 53 in internal/bindings/singleton.go

View workflow job for this annotation

GitHub Actions / GitHub Runner / arm-4core-linux oldstable

undefined: newWAFLib

Check failure on line 53 in internal/bindings/singleton.go

View workflow job for this annotation

GitHub Actions / GitHub Runner / macos-14 stable (DD_APPSEC_WAF_LOG_LEVEL=TRACE)

undefined: newWAFLib

Check failure on line 53 in internal/bindings/singleton.go

View workflow job for this annotation

GitHub Actions / GitHub Runner / arm-4core-linux stable (DD_APPSEC_WAF_LOG_LEVEL=TRACE)

undefined: newWAFLib

Check failure on line 53 in internal/bindings/singleton.go

View workflow job for this annotation

GitHub Actions / GitHub Runner / macos-13 oldstable

undefined: newWAFLib

Check failure on line 53 in internal/bindings/singleton.go

View workflow job for this annotation

GitHub Actions / GitHub Runner / macos-13 stable (DD_APPSEC_WAF_LOG_LEVEL=TRACE)

undefined: newWAFLib
if gWafLoadErr != nil {
return
}
wafVersion = Lib.GetVersion()
})

return Lib != nil, gWafLoadErr
}

var wafVersion string

// Version returns the version returned by libddwaf.
// It relies on the dynamic loading of the library, which can fail and return
// an empty string or the previously loaded version, if any.
func Version() string {
_, _ = Load()
return wafVersion
}

// Usable returns true if the Lib is usable, false and an error otherwise.
//
// If the Lib is usable, an error value may still be returned and should be
// treated as a warning (it is non-blocking).
//
// The following conditions are checked:
// - The Lib library has been loaded successfully (you need to call [Load] first for this case to be
// taken into account)
// - The Lib library has not been manually disabled with the `datadog.no_waf` go build tag
// - The Lib library is not in an unsupported OS/Arch
// - The Lib library is not in an unsupported Go version
func Usable() (bool, error) {
wafSupportErrors := errors.Join(support.WafSupportErrors()...)
wafManuallyDisabledErr := support.WafManuallyDisabledError()

// Acquire the global state mutex as we are not calling [Load] here, so we
// need to explicitly avoid a race condition with it.
gMu.Lock()
defer gMu.Unlock()
return (Lib != nil || gWafLoadErr == nil) && wafSupportErrors == nil && wafManuallyDisabledErr == nil, errors.Join(gWafLoadErr, wafSupportErrors, wafManuallyDisabledErr)
}
18 changes: 16 additions & 2 deletions internal/bindings/waf_dl.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ type WAFLib struct {
handle uintptr
}

// NewWAFLib loads the libddwaf shared library and resolves all tge relevant symbols.
// newWAFLib loads the libddwaf shared library and resolves all tge relevant symbols.
// The caller is responsible for calling wafDl.Close on the returned object once they
// are done with it so that associated resources can be released.
func NewWAFLib() (dl *WAFLib, err error) {
func newWAFLib() (dl *WAFLib, err error) {
path, closer, err := lib.DumpEmbeddedWAF()
if err != nil {
return nil, fmt.Errorf("dump embedded WAF: %w", err)
Expand Down Expand Up @@ -237,6 +237,20 @@ func (waf *WAFLib) Handle() uintptr {
return waf.handle
}

func (waf *WAFLib) ObjectFromJSON(json []byte) (WAFObject, bool) {
var (
obj WAFObject
pinner runtime.Pinner
)

defer pinner.Unpin()
pinner.Pin(&json)
pinner.Pin(&obj)

success := waf.syscall(waf.objectFromJSON, unsafe.PtrToUintptr(&obj), unsafe.SliceToUintptr(json), uintptr(len(json))) != 0
return obj, success
}

// syscall is the only way to make C calls with this interface.
// purego implementation limits the number of arguments to 9, it will panic if more are provided
// Note: `purego.SyscallN` has 3 return values: these are the following:
Expand Down
2 changes: 2 additions & 0 deletions internal/bindings/waf_dl_unsupported.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,6 @@ func (*WAFLib) Run(WAFContext, *WAFObject, *WAFObject, *WAFObject, uint64) WAFRe
return WAFErrInternal
}

func (waf *WAFLib) ObjectFromJSON(obj *WAFObject, json []byte) bool { return false }

func (*WAFLib) Handle() uintptr { return 0 }
2 changes: 1 addition & 1 deletion internal/lib/.version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.25.1
1.28.1
Binary file modified internal/lib/libddwaf-darwin-amd64.dylib.gz
Binary file not shown.
Binary file modified internal/lib/libddwaf-darwin-arm64.dylib.gz
Binary file not shown.
Binary file modified internal/lib/libddwaf-linux-amd64.so.gz
Binary file not shown.
Binary file modified internal/lib/libddwaf-linux-arm64.so.gz
Binary file not shown.
21 changes: 20 additions & 1 deletion internal/log/ddwaf.h
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,7 @@ ddwaf_handle ddwaf_builder_build_instance(ddwaf_builder builder);
* provided regular expression is used unanchored so matches can be found
* at any point within the path, any necessary anchors must be explicitly
* added to the regex. (nullable).
* @oaran filter_len The length of the filter string (or 0 otherwise).
* @param filter_len The length of the filter string (or 0 otherwise).
*
* @return The total number of configurations loaded or, if provided, the number
* of those matching the filter.
Expand Down Expand Up @@ -647,6 +647,25 @@ bool ddwaf_object_map_addl(ddwaf_object *map, const char *key, size_t length, dd
**/
bool ddwaf_object_map_addl_nc(ddwaf_object *map, const char *key, size_t length, ddwaf_object *object);

/**
* ddwaf_object_from_json
*
* Creates a ddwaf_object from a JSON string. The JSON will be parsed and converted
* into the appropriate ddwaf_object structure, supporting all JSON types including
* objects, arrays, strings, numbers, booleans, and null values.
*
* @param output Object to populate with the parsed JSON data. (nonnull)
* @param json_str The JSON string to parse. (nonnull)
* @param length Length of the JSON string.
*
* @return The success or failure of the operation.
*
* @note The output object must be freed by the caller using ddwaf_object_free.
* @note If parsing fails, the output object will be left in an undefined state.
* @note The provided JSON string is owned by the caller.
**/
bool ddwaf_object_from_json(ddwaf_object *output, const char *json_str, uint32_t length);

/**
* ddwaf_object_type
*
Expand Down
Loading
Loading