-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathcgo_ref_pool.go
106 lines (88 loc) · 4.72 KB
/
cgo_ref_pool.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
// 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 waf
import (
"strconv"
"github.com/DataDog/go-libddwaf/v3/internal/bindings"
"github.com/DataDog/go-libddwaf/v3/internal/unsafe"
)
// cgoRefPool is a way to make sure we can safely send go allocated data on the C side of the WAF
// The main issue is the following: the wafObject uses a C union to store the tree structure of the full object,
// union equivalent in go are interfaces and they are not compatible with C unions. The only way to be 100% sure
// that the Go wafObject struct have the same layout as the C one is to only use primitive types. So the only way to
// store a raw pointer is to use the uintptr type. But since uintptr do not have pointer semantics (and are just
// basically integers), we need another structure to store the value as Go pointer because the GC is lurking. That's
// where the cgoRefPool object comes into play: All new wafObject elements are created via this API whose especially
// built to make sure there is no gap for the Garbage Collector to exploit. From there, since underlying values of the
// wafObject are either arrays (for maps, structs and arrays) or string (for all ints, booleans and strings),
// we can store 2 slices of arrays and use runtime.KeepAlive in each code path to protect them from the GC.
type cgoRefPool struct {
stringRefs []string
arrayRefs [][]bindings.WafObject
}
// This is used when passing empty Go strings to the WAF in order to avoid passing null string pointers,
// which are not handled by the WAF in all cases.
// FIXME: to be removed when the WAF handles null ptr strings in all expected places
var emptyWAFStringValue = unsafe.NativeStringUnwrap("\x00").Data
func (refPool *cgoRefPool) append(newRefs cgoRefPool) {
refPool.stringRefs = append(refPool.stringRefs, newRefs.stringRefs...)
refPool.arrayRefs = append(refPool.arrayRefs, newRefs.arrayRefs...)
}
// AllocCString is used in the rare cases where we need the WAF to receive standard null-terminated strings.
// All cases where strings a wrapped in wafObject are handled by AllocWafString
func (refPool *cgoRefPool) AllocCString(str string) uintptr {
if len(str) > 0 && str[len(str)-1] != '\x00' {
str = str + "\x00"
}
refPool.stringRefs = append(refPool.stringRefs, str)
return unsafe.NativeStringUnwrap(str).Data
}
// AllocWafString fills the obj parameter wafObject with all parameters needed for the WAF interpret it as a string.
// We take full advantage of the fact that the WAF can receive non-null-terminated strings by directly retrieving the
// underlying array in the string value using the nativeStringUnwrap function. Hence, removing any copy in the process
func (refPool *cgoRefPool) AllocWafString(obj *bindings.WafObject, str string) {
obj.Type = bindings.WafStringType
if len(str) == 0 {
obj.NbEntries = 0
// FIXME: use obj.Value = 0 when the WAF handles null string ptr in all expected places
obj.Value = emptyWAFStringValue
return
}
refPool.stringRefs = append(refPool.stringRefs, str)
stringHeader := unsafe.NativeStringUnwrap(str)
obj.Value = stringHeader.Data
obj.NbEntries = uint64(stringHeader.Len)
}
// AllocWafArray is used to create a tree-like structure since we allocate a wafObject array inside another wafOject.
// wafObject can also represent a map, in that case we use the AllocWafMapKey function to make the wafObject key-value-pair
// like objects.
func (refPool *cgoRefPool) AllocWafArray(obj *bindings.WafObject, typ bindings.WafObjectType, size uint64) []bindings.WafObject {
if typ != bindings.WafMapType && typ != bindings.WafArrayType {
panic("Cannot allocate this waf object data type as an array: " + strconv.Itoa(int(typ)))
}
obj.Type = typ
obj.NbEntries = size
// If the array size is zero no need to allocate anything
if size == 0 {
obj.Value = 0
return nil
}
goArray := make([]bindings.WafObject, size)
refPool.arrayRefs = append(refPool.arrayRefs, goArray)
obj.Value = unsafe.SliceToUintptr(goArray)
return goArray
}
// AllocWafMapKey is used to store a string map key in a wafObject.
// We take full advantage of the fact that the WAF can receive non-null-terminated strings by directly retrieving the
// underlying array in the string value using the nativeStringUnwrap function. Hence, removing any copy in the process
func (refPool *cgoRefPool) AllocWafMapKey(obj *bindings.WafObject, str string) {
if len(str) == 0 {
return
}
refPool.stringRefs = append(refPool.stringRefs, str)
stringHeader := unsafe.NativeStringUnwrap(str)
obj.ParameterName = stringHeader.Data
obj.ParameterNameLength = uint64(stringHeader.Len)
}