Skip to content

Commit f3b3d0c

Browse files
authored
fix: data race between libddwaf.Load() and libddwaf.Usable() (#149)
A data race existed between `libddwaf.Load()` and `libddwaf.Usable()` as both access shared global state that is not managed through atomic pointers. This PR adds a `sync.Mutex` to resolve the race condition between `Load` and `Usable`; as the use of a `sync.Once` in `Load` ensures there is no race condition in other places (all uses of the `gWafLib` are guaranteed to be preceded by a call to `Load`, which means the reads "synchronize after" the write).
1 parent 5ea6f53 commit f3b3d0c

File tree

6 files changed

+41
-24
lines changed

6 files changed

+41
-24
lines changed

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 := wafLib.Handle()
32+
lib := gWafLib.Handle()
3333

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

builder.go

Lines changed: 7 additions & 7 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 := wafLib.BuilderInit(newConfig(&pinner, keyObfuscatorRegex, valueObfuscatorRegex))
40+
hdl := gWafLib.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-
wafLib.BuilderDestroy(b.handle)
54+
gWafLib.BuilderDestroy(b.handle)
5555
b.handle = 0
5656
}
5757

@@ -122,9 +122,9 @@ func (b *Builder) AddOrUpdateConfig(path string, fragment any) (Diagnostics, err
122122
// Returns the [Diagnostics] produced by adding or updating this configuration.
123123
func (b *Builder) addOrUpdateConfig(path string, cfg *bindings.WAFObject) (Diagnostics, error) {
124124
var diagnosticsWafObj bindings.WAFObject
125-
defer wafLib.ObjectFree(&diagnosticsWafObj)
125+
defer gWafLib.ObjectFree(&diagnosticsWafObj)
126126

127-
res := wafLib.BuilderAddOrUpdateConfig(b.handle, path, cfg, &diagnosticsWafObj)
127+
res := gWafLib.BuilderAddOrUpdateConfig(b.handle, path, cfg, &diagnosticsWafObj)
128128

129129
var diags Diagnostics
130130
if !diagnosticsWafObj.IsInvalid() {
@@ -150,7 +150,7 @@ func (b *Builder) RemoveConfig(path string) bool {
150150
return false
151151
}
152152

153-
return wafLib.BuilderRemoveConfig(b.handle, path)
153+
return gWafLib.BuilderRemoveConfig(b.handle, path)
154154
}
155155

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

162-
return wafLib.BuilderGetConfigPaths(b.handle, filter)
162+
return gWafLib.BuilderGetConfigPaths(b.handle, filter)
163163
}
164164

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

174-
hdl := wafLib.BuilderBuildInstance(b.handle)
174+
hdl := gWafLib.BuilderBuildInstance(b.handle)
175175
if hdl == 0 {
176176
return nil
177177
}

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 wafLib.ObjectFree(&result)
228+
defer gWafLib.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 := wafLib.Run(context.cContext, persistentData, ephemeralData, &result, timeout)
233+
ret := gWafLib.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-
wafLib.ContextDestroy(context.cContext)
327+
gWafLib.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 := wafLib.ContextInit(handle.cHandle)
58+
cContext := gWafLib.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 wafLib.KnownAddresses(handle.cHandle)
80+
return gWafLib.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 wafLib.KnownActions(handle.cHandle)
86+
return gWafLib.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-
wafLib.Destroy(handle.cHandle)
98+
gWafLib.Destroy(handle.cHandle)
9999
handle.cHandle = 0 // Makes it easy to spot use-after-free/double-free issues
100100
}
101101

waf.go

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,15 @@ import (
1616
// Globally dlopen() libddwaf only once because several dlopens (eg. in tests)
1717
// aren't supported by macOS.
1818
var (
19-
// libddwaf's dynamic library handle and entrypoints
20-
wafLib *bindings.WAFLib
21-
// libddwaf's dlopen error if any
22-
wafLoadErr error
19+
// libddwaf's dynamic library handle and entrypoints. This is only safe to
20+
// read after calling [Load] or having acquired [gMu].
21+
gWafLib *bindings.WAFLib
22+
// libddwaf's dlopen error if any. This is only safe to read after calling
23+
// [Load] or having acquired [gMu].
24+
gWafLoadErr error
25+
// Protects the global variables above.
26+
gMu sync.Mutex
27+
2328
openWafOnce sync.Once
2429
)
2530

@@ -41,14 +46,19 @@ func Load() (bool, error) {
4146
}
4247

4348
openWafOnce.Do(func() {
44-
wafLib, wafLoadErr = bindings.NewWAFLib()
45-
if wafLoadErr != nil {
49+
// Acquire the global state mutex so we don't have a race condition with
50+
// [Usable] here.
51+
gMu.Lock()
52+
defer gMu.Unlock()
53+
54+
gWafLib, gWafLoadErr = bindings.NewWAFLib()
55+
if gWafLoadErr != nil {
4656
return
4757
}
48-
wafVersion = wafLib.GetVersion()
58+
wafVersion = gWafLib.GetVersion()
4959
})
5060

51-
return wafLib != nil, wafLoadErr
61+
return gWafLib != nil, gWafLoadErr
5262
}
5363

5464
var wafVersion string
@@ -76,5 +86,9 @@ func Usable() (bool, error) {
7686
wafSupportErrors := errors.Join(support.WafSupportErrors()...)
7787
wafManuallyDisabledErr := support.WafManuallyDisabledError()
7888

79-
return (wafLib != nil || wafLoadErr == nil) && wafSupportErrors == nil && wafManuallyDisabledErr == nil, errors.Join(wafLoadErr, wafSupportErrors, wafManuallyDisabledErr)
89+
// Acquire the global state mutex as we are not calling [Load] here, so we
90+
// need to explicitly avoid a race condition with it.
91+
gMu.Lock()
92+
defer gMu.Unlock()
93+
return (gWafLib != nil || gWafLoadErr == nil) && wafSupportErrors == nil && wafManuallyDisabledErr == nil, errors.Join(gWafLoadErr, wafSupportErrors, wafManuallyDisabledErr)
8094
}

waf_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,9 @@ func TestTimeout(t *testing.T) {
358358
})
359359

360360
t.Run("both-timeout", func(t *testing.T) {
361+
// TODO(eliott.bouhana): APPSEC-58637
362+
t.Skip("TODO(eliott.bouhana): This test is flaky and needs to be fixed")
363+
361364
context, err := waf.NewContext(timer.WithBudget(time.Millisecond), timer.WithComponents(wafTimerKey, raspTimerKey))
362365
require.NoError(t, err)
363366
require.NotNil(t, context)

0 commit comments

Comments
 (0)