@@ -8,16 +8,15 @@ package waf
88import (
99 "errors"
1010 "fmt"
11- "sync"
1211
1312 "github.com/DataDog/go-libddwaf/v2/internal/noopfree"
1413 "go.uber.org/atomic"
1514)
1615
1716// Handle represents an instance of the WAF for a given ruleset.
1817type Handle struct {
19- // Instance of the WAF
20- cHandle wafHandle
18+ // diagnostics holds information about rules initialization
19+ diagnostics Diagnostics
2120
2221 // Lock-less reference counter avoiding blocking calls to the Close() method
2322 // while WAF contexts are still using the WAF handle. Instead, we let the
@@ -31,12 +30,8 @@ type Handle struct {
3130 // block the request handlers for the time of the security rules update.
3231 refCounter * atomic.Int32
3332
34- // RWMutex protecting the R/W accesses to the internal rules data (stored
35- // in the handle).
36- mutex sync.RWMutex
37-
38- // diagnostics holds information about rules initialization
39- diagnostics Diagnostics
33+ // Instance of the WAF
34+ cHandle wafHandle
4035}
4136
4237// NewHandle creates and returns a new instance of the WAF with the given security rules and configuration
@@ -115,7 +110,6 @@ func (handle *Handle) Addresses() []string {
115110// Update the ruleset of a WAF instance into a new handle on its own
116111// the previous handle still needs to be closed manually
117112func (handle * Handle ) Update (newRules any ) (* Handle , error ) {
118-
119113 encoder := newMaxEncoder ()
120114 obj , err := encoder .Encode (newRules )
121115 if err != nil {
@@ -142,36 +136,56 @@ func (handle *Handle) Update(newRules any) (*Handle, error) {
142136 }, nil
143137}
144138
145- // closeContext calls ddwaf_context_destroy and eventually ddwaf_destroy on the handle
146- func (handle * Handle ) closeContext (context * Context ) {
147- wafLib .wafContextDestroy (context .cContext )
148- if handle .addRefCounter (- 1 ) == 0 {
149- wafLib .wafDestroy (handle .cHandle )
150- }
151- }
152-
153139// Close puts the handle in termination state, when all the contexts are closed the handle will be destroyed
154140func (handle * Handle ) Close () {
155- if handle .addRefCounter (- 1 ) > 0 {
156- // There are still Contexts that are not closed
141+ if handle .addRefCounter (- 1 ) != 0 {
142+ // Either the counter is still positive (this Handle is still referenced), or it had previously
143+ // reached 0 and some other call has done the cleanup already.
157144 return
158145 }
159146
160147 wafLib .wafDestroy (handle .cHandle )
148+ handle .diagnostics = Diagnostics {} // Data in diagnostics may no longer be valid (e.g: strings from libddwaf)
149+ handle .cHandle = 0 // Makes it easy to spot use-after-free/double-free issues
150+ }
151+
152+ // retain increments the reference counter of this Handle. Returns true if the
153+ // Handle is still valid, false if it is no longer usable. Calls to retain()
154+ // must be balanced with calls to release() in order to avoid leaking Handles.
155+ func (handle * Handle ) retain () bool {
156+ return handle .addRefCounter (1 ) > 0
161157}
162158
163- // addRefCounter add x to Handle.refCounter.
164- // It relies on a CAS spin-loop implementation in order to avoid changing the
165- // counter when 0 has been reached.
159+ // release decrements the reference counter of this Handle, possibly causing it
160+ // to be completely closed if no other reference to it exist.
161+ func (handle * Handle ) release () {
162+ handle .Close ()
163+ }
164+
165+ // addRefCounter adds x to Handle.refCounter. The return valid indicates whether the refCounter reached 0 as part of
166+ // this call or not, which can be used to perform "only-once" activities:
167+ // - result > 0 => the Handle is still usable
168+ // - result == 0 => the handle is no longer usable, ref counter reached 0 as part of this call
169+ // - result == -1 => the handle is no longer usable, ref counter was already 0 previously
166170func (handle * Handle ) addRefCounter (x int32 ) int32 {
171+ // We use a CAS loop to avoid setting the refCounter to a negative value.
167172 for {
168173 current := handle .refCounter .Load ()
169- if current = = 0 {
170- // The object was released
171- return 0
174+ if current < = 0 {
175+ // The object had already been released
176+ return - 1
172177 }
173- if swapped := handle .refCounter .CompareAndSwap (current , current + x ); swapped {
174- return current + x
178+
179+ next := current + x
180+ if swapped := handle .refCounter .CompareAndSwap (current , next ); swapped {
181+ if next < 0 {
182+ // TODO(romain.marcadier): somehow signal unexpected behavior to the
183+ // caller (panic? error?). We currently clamp to 0 in order to avoid
184+ // causing a customer program crash, but this is the symptom of a bug
185+ // and should be investigated (however this clamping hides the issue).
186+ return 0
187+ }
188+ return next
175189 }
176190 }
177191}
0 commit comments