Skip to content

Commit 9a64933

Browse files
authored
feat: upgrade libddwaf v1.25.1 -> v1.28.1 (#151)
- [x] upgrade libddwaf v1.25.1 -> v1.28.1 - [x] Rotate old CI images that were failing - [x] Move the waflib singleton to the bindings package - [x] Remove the `json` package - [x] Replace calls to the `json` package by `ddwaf_object_from_json` --------- Signed-off-by: Eliott Bouhana <[email protected]>
1 parent d22ee0c commit 9a64933

24 files changed

+189
-286
lines changed

.github/workflows/_test_bare_metal.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ jobs:
2828
strategy:
2929
fail-fast: false
3030
matrix:
31-
runs-on: [ macos-15, macos-14, macos-13, ubuntu-24.04, ubuntu-22.04, windows-latest, arm-4core-linux ]
31+
runs-on: [ macos-26, macos-latest-large, macos-13, ubuntu-24.04, ubuntu-22.04, windows-latest, ubuntu-24.04-arm ]
3232
go-version: ${{ fromJson(needs.go-versions-matrix.outputs.json) }}
3333
include:
3434
# Test with DD_APPSEC_WAF_LOG_LEVEL (only latest go version)

.github/workflows/_test_containerized.yml

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ jobs:
2929
image:
3030
# Standard golang images
3131
- golang:{0}-alpine
32+
- golang:{0}-trixie
3233
- golang:{0}-bookworm
33-
- golang:{0}-bullseye
3434
go-version:
3535
- ${{ needs.go-versions.outputs.stable }}
3636
- ${{ needs.go-versions.outputs.oldstable }}
@@ -41,23 +41,8 @@ jobs:
4141
waf-log-level: TRACE
4242
name: ${{ matrix.arch }} ${{ format(matrix.image, matrix.go-version) }} go${{ matrix.go-version }}${{ matrix.waf-log-level && format(' (DD_APPSEC_WAF_LOG_LEVEL={0})', matrix.waf-log-level) || '' }}
4343
# We use ARM runners when needed to avoid the performance hit of QEMU
44-
runs-on: ${{ matrix.arch == 'amd64' && 'ubuntu-latest' || 'arm-4core-linux' }}
44+
runs-on: ${{ matrix.arch == 'amd64' && 'ubuntu-latest' || 'ubuntu-24.04-arm' }}
4545
steps:
46-
# Docker is not present on early-access ARM runners
47-
- name: Prepare ARM Runner
48-
if: matrix.arch == 'arm64'
49-
run: |-
50-
for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove -y $pkg || echo "Not present: $pkg"; done
51-
52-
sudo apt update
53-
sudo apt install -y ca-certificates curl
54-
sudo install -m 0755 -d /etc/apt/keyrings
55-
sudo curl -fsSL "https://download.docker.com/linux/ubuntu/gpg" -o /etc/apt/keyrings/docker.asc
56-
sudo chmod a+r /etc/apt/keyrings/docker.asc
57-
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list
58-
sudo apt update
59-
sudo apt install -y docker-ce docker-ce-cli containerd.io
60-
6146
- uses: actions/checkout@v4
6247
- uses: actions/cache@v4
6348
with:

_tools/libddwaf-updater/update.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
#!/bin/bash
22

3-
cd $(dirname $0)
3+
cd "$(dirname "$0")" || exit
44
exec go run ./update.go "$@"

alignement_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ func TestWafObject(t *testing.T) {
2929
_, err := Load()
3030
require.NoError(t, err)
3131

32-
lib := gWafLib.Handle()
32+
lib := bindings.Lib.Handle()
3333

3434
t.Run("invalid", func(t *testing.T) {
3535
var actual bindings.WAFObject

builder.go

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ func NewBuilder(keyObfuscatorRegex string, valueObfuscatorRegex string) (*Builde
3737

3838
var pinner runtime.Pinner
3939
defer pinner.Unpin()
40-
hdl := gWafLib.BuilderInit(newConfig(&pinner, keyObfuscatorRegex, valueObfuscatorRegex))
40+
hdl := bindings.Lib.BuilderInit(newConfig(&pinner, keyObfuscatorRegex, valueObfuscatorRegex))
4141

4242
if hdl == 0 {
4343
return nil, errors.New("failed to initialize the WAF builder")
@@ -51,7 +51,7 @@ func (b *Builder) Close() {
5151
if b == nil || b.handle == 0 {
5252
return
5353
}
54-
gWafLib.BuilderDestroy(b.handle)
54+
bindings.Lib.BuilderDestroy(b.handle)
5555
b.handle = 0
5656
}
5757

@@ -65,15 +65,13 @@ const defaultRecommendedRulesetPath = "::/go-libddwaf/default/recommended.json"
6565
// AddDefaultRecommendedRuleset adds the default recommended ruleset to the
6666
// receiving [Builder], and returns the [Diagnostics] produced in the process.
6767
func (b *Builder) AddDefaultRecommendedRuleset() (Diagnostics, error) {
68-
var pinner runtime.Pinner
69-
defer pinner.Unpin()
70-
71-
ruleset, err := ruleset.DefaultRuleset(&pinner)
68+
defaultRuleset, err := ruleset.DefaultRuleset()
69+
defer bindings.Lib.ObjectFree(&defaultRuleset)
7270
if err != nil {
7371
return Diagnostics{}, fmt.Errorf("failed to load default recommended ruleset: %w", err)
7472
}
7573

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

127-
res := gWafLib.BuilderAddOrUpdateConfig(b.handle, path, cfg, &diagnosticsWafObj)
125+
res := bindings.Lib.BuilderAddOrUpdateConfig(b.handle, path, cfg, &diagnosticsWafObj)
128126

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

153-
return gWafLib.BuilderRemoveConfig(b.handle, path)
151+
return bindings.Lib.BuilderRemoveConfig(b.handle, path)
154152
}
155153

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

162-
return gWafLib.BuilderGetConfigPaths(b.handle, filter)
160+
return bindings.Lib.BuilderGetConfigPaths(b.handle, filter)
163161
}
164162

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

174-
hdl := gWafLib.BuilderBuildInstance(b.handle)
172+
hdl := bindings.Lib.BuilderBuildInstance(b.handle)
175173
if hdl == 0 {
176174
return nil
177175
}

builder_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -297,8 +297,8 @@ func TestBuilder(t *testing.T) {
297297
require.NotEmpty(t, res.Events)
298298
require.Equal(t,
299299
map[string]any{"block_request": map[string]any{
300-
"grpc_status_code": "10",
301-
"status_code": "403",
300+
"grpc_status_code": uint64(10),
301+
"status_code": uint64(403),
302302
"type": "auto",
303303
}},
304304
res.Actions,

context.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -225,12 +225,12 @@ func (context *Context) run(persistentData, ephemeralData *bindings.WAFObject, r
225225

226226
var result bindings.WAFObject
227227
pinner.Pin(&result)
228-
defer gWafLib.ObjectFree(&result)
228+
defer bindings.Lib.ObjectFree(&result)
229229

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

235235
decodeTimer := runTimer.MustLeaf(DecodeTimeKey)
236236
decodeTimer.Start()
@@ -324,7 +324,7 @@ func (context *Context) Close() {
324324
context.mutex.Lock()
325325
defer context.mutex.Unlock()
326326

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

handle.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ func (handle *Handle) NewContext(timerOptions ...timer.Option) (*Context, error)
5555
return nil, fmt.Errorf("handle was released")
5656
}
5757

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

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

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

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

internal/bindings/libddwaf.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,16 @@ type wafSymbols struct {
1616
builderBuildInstance uintptr
1717
builderGetConfigPaths uintptr
1818
builderDestroy uintptr
19-
setLogCb uintptr
2019
destroy uintptr
2120
knownAddresses uintptr
2221
knownActions uintptr
2322
getVersion uintptr
2423
contextInit uintptr
2524
contextDestroy uintptr
2625
objectFree uintptr
26+
objectFromJSON uintptr
2727
run uintptr
28+
setLogCb uintptr
2829
}
2930

3031
// newWafSymbols resolves the symbols of [wafSymbols] from the provided
@@ -69,6 +70,9 @@ func newWafSymbols(handle uintptr) (syms wafSymbols, err error) {
6970
if syms.objectFree, err = purego.Dlsym(handle, "ddwaf_object_free"); err != nil {
7071
return syms, err
7172
}
73+
if syms.objectFromJSON, err = purego.Dlsym(handle, "ddwaf_object_from_json"); err != nil {
74+
return syms, err
75+
}
7276
if syms.run, err = purego.Dlsym(handle, "ddwaf_run"); err != nil {
7377
return syms, err
7478
}

internal/bindings/singleton.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// Unless explicitly stated otherwise all files in this repository are licensed
2+
// under the Apache License Version 2.0.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/).
4+
// Copyright 2016-present Datadog, Inc.
5+
6+
package bindings
7+
8+
import (
9+
"errors"
10+
"sync"
11+
12+
"github.com/DataDog/go-libddwaf/v4/internal/support"
13+
)
14+
15+
// Globally dlopen() libddwaf only once because several dlopens (eg. in tests)
16+
// aren't supported by macOS.
17+
var (
18+
// Lib is libddwaf's dynamic library handle and entrypoints. This is only safe to
19+
// read after calling [Load] or having acquired [gMu].
20+
Lib *WAFLib
21+
// libddwaf's dlopen error if any. This is only safe to read after calling
22+
// [Load] or having acquired [gMu].
23+
gWafLoadErr error
24+
// Protects the global variables above.
25+
gMu sync.Mutex
26+
27+
openWafOnce sync.Once
28+
)
29+
30+
// Load loads libddwaf's dynamic library. The dynamic library is opened only
31+
// once by the first call to this function and internally stored globally.
32+
// No function is currently provided in this API to unload it.
33+
//
34+
// This function is automatically called by [NewBuilder], and most users need
35+
// not explicitly call it. It is however useful in order to explicitly check
36+
// for the status of the Lib library's initialization.
37+
//
38+
// The function returns true when libddwaf was successfully loaded, along with
39+
// an error value. An error might still be returned even though the Lib load was
40+
// successful: in such cases the error is indicative that some non-critical
41+
// features are not available; but the Lib may still be used.
42+
func Load() (bool, error) {
43+
if ok, err := Usable(); !ok {
44+
return false, err
45+
}
46+
47+
openWafOnce.Do(func() {
48+
// Acquire the global state mutex so we don't have a race condition with
49+
// [Usable] here.
50+
gMu.Lock()
51+
defer gMu.Unlock()
52+
53+
Lib, gWafLoadErr = newWAFLib()
54+
if gWafLoadErr != nil {
55+
return
56+
}
57+
wafVersion = Lib.GetVersion()
58+
})
59+
60+
return Lib != nil, gWafLoadErr
61+
}
62+
63+
var wafVersion string
64+
65+
// Version returns the version returned by libddwaf.
66+
// It relies on the dynamic loading of the library, which can fail and return
67+
// an empty string or the previously loaded version, if any.
68+
func Version() string {
69+
_, _ = Load()
70+
return wafVersion
71+
}
72+
73+
// Usable returns true if the Lib is usable, false and an error otherwise.
74+
//
75+
// If the Lib is usable, an error value may still be returned and should be
76+
// treated as a warning (it is non-blocking).
77+
//
78+
// The following conditions are checked:
79+
// - The Lib library has been loaded successfully (you need to call [Load] first for this case to be
80+
// taken into account)
81+
// - The Lib library has not been manually disabled with the `datadog.no_waf` go build tag
82+
// - The Lib library is not in an unsupported OS/Arch
83+
// - The Lib library is not in an unsupported Go version
84+
func Usable() (bool, error) {
85+
wafSupportErrors := errors.Join(support.WafSupportErrors()...)
86+
wafManuallyDisabledErr := support.WafManuallyDisabledError()
87+
88+
// Acquire the global state mutex as we are not calling [Load] here, so we
89+
// need to explicitly avoid a race condition with it.
90+
gMu.Lock()
91+
defer gMu.Unlock()
92+
return (Lib != nil || gWafLoadErr == nil) && wafSupportErrors == nil && wafManuallyDisabledErr == nil, errors.Join(gWafLoadErr, wafSupportErrors, wafManuallyDisabledErr)
93+
}

0 commit comments

Comments
 (0)