libddwaf 1.22.0.0.2 → 1.24.1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/lint.yml +2 -2
- data/.github/workflows/test-for-memory-leaks.yml +15 -0
- data/CHANGELOG.md +21 -2
- data/Dockerfile +11 -0
- data/Steepfile +5 -5
- data/lib/datadog/appsec/waf/context.rb +24 -38
- data/lib/datadog/appsec/waf/converter.rb +62 -69
- data/lib/datadog/appsec/waf/errors.rb +19 -0
- data/lib/datadog/appsec/waf/handle.rb +30 -77
- data/lib/datadog/appsec/waf/handle_builder.rb +91 -0
- data/lib/datadog/appsec/waf/lib_ddwaf.rb +94 -94
- data/lib/datadog/appsec/waf/version.rb +3 -3
- data/lib/datadog/appsec/waf.rb +9 -7
- data/lib/libddwaf.rb +1 -1
- data/libddwaf.gemspec +20 -22
- data/sig/datadog/appsec/waf/context.rbs +5 -15
- data/sig/datadog/appsec/waf/errors.rbs +20 -0
- data/sig/datadog/appsec/waf/handle.rbs +6 -27
- data/sig/datadog/appsec/waf/handle_builder.rbs +23 -0
- data/sig/datadog/appsec/waf/lib_ddwaf.rbs +19 -17
- metadata +9 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 10d8d57e5c46bce6df51fd1b551831c8d4421144837498ce916c093c0b255b8e
|
4
|
+
data.tar.gz: d8fd00d07b23de9ff1af3be34b0344f0a1404db5da66e219e312a75d8d191942
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 20d4c8ae3986514d5bf69d830a75468973e3688e971fe03068cd3451d58ddb0bf3a9e0ad86303b04c7e42cbef85dc9bf1e61e23e3db77a57374d30422c54b9c2
|
7
|
+
data.tar.gz: bd3c2665b3b5ab606ad59dc48e4eac7cd8f5568c38141d3cee0cddcbb2ba5681186174bbe1804657c47441a204297a1313a86fb3cf25274e398b857732382c77
|
data/.github/workflows/lint.yml
CHANGED
@@ -0,0 +1,15 @@
|
|
1
|
+
name: Test for memory leaks
|
2
|
+
on: [push]
|
3
|
+
jobs:
|
4
|
+
test-memcheck:
|
5
|
+
runs-on: ubuntu-24.04
|
6
|
+
steps:
|
7
|
+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
8
|
+
- uses: ruby/setup-ruby@f41e084df884422b269f4c01c3748a9df4431a75 # v1.236.0
|
9
|
+
with:
|
10
|
+
ruby-version: "3.4"
|
11
|
+
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
12
|
+
bundler: latest
|
13
|
+
cache-version: v1 # bump this to invalidate cache
|
14
|
+
- run: sudo apt-get update && (sudo apt-get install -y valgrind || sleep 5 && sudo apt-get install -y valgrind --no-install-recommends --no-install-suggests) && valgrind --version
|
15
|
+
- run: bundle exec rake binary spec:memory_leaks:all
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,24 @@
|
|
1
|
+
# Unreleased v1.23.0.0.0
|
2
|
+
|
3
|
+
## Added
|
4
|
+
|
5
|
+
- Add `HandleBuilder` class for managing libddwaf configuration and building WAF handles
|
6
|
+
- Add Error classes:
|
7
|
+
- `LibDDWAFError` for libddwaf errors
|
8
|
+
- `ConversionError` for conversion errors
|
9
|
+
- `InstanceFinalizedError` that is raised when attempting to run methods on finalized instances
|
10
|
+
|
11
|
+
## Changed
|
12
|
+
|
13
|
+
- Change `Handle` instantiation - now it should be done using `HandleBuilder#build_handle` method
|
14
|
+
- Change `Context` instantiation - now is should be done using `Handle#build_context` method
|
15
|
+
- Change configuration of Limits and obfuscation - it is now done when initializing `HandleBuilder`
|
16
|
+
- Change `Context#run` to return a `Result` object instead of an tuple with code and result
|
17
|
+
|
18
|
+
## Removed
|
19
|
+
|
20
|
+
- Remove `WAF::Handle#merge` method - the configuration is now handled by `HandleBuilder`
|
21
|
+
|
1
22
|
# 2025-02-20 v.1.18.0.0.1
|
2
23
|
|
3
24
|
- Fixed memory-leak in `Datadog::AppSec::WAF::Context#run` when non-empty ephemeral data passed
|
@@ -12,7 +33,6 @@
|
|
12
33
|
- Update to libddwaf 1.14.0
|
13
34
|
- Add support for `Float` and `Nil` scalar values when converting from ruby to WAF Object and vice versa.
|
14
35
|
|
15
|
-
|
16
36
|
# 2023-08-29 v.1.11.0.0.0
|
17
37
|
|
18
38
|
- Update to libddwaf 1.11.0
|
@@ -21,7 +41,6 @@
|
|
21
41
|
- Changed `Datadog::AppSec::WAF::Result#data` to `Datadog::AppSec::WAF::Result#events`. (Breaking change)
|
22
42
|
The schema of the events variable can be found [here](https://github.com/DataDog/libddwaf/blob/master/schema/events.json)
|
23
43
|
|
24
|
-
|
25
44
|
# 2023-08-28 v.1.10.0.0.0
|
26
45
|
|
27
46
|
- Update to libddwaf 1.10.0
|
data/Dockerfile
ADDED
data/Steepfile
CHANGED
@@ -13,9 +13,9 @@ target :lib do
|
|
13
13
|
library "jruby"
|
14
14
|
library "gem"
|
15
15
|
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
16
|
+
# configure_code_diagnostics(D::Ruby.strict) # `strict` diagnostics setting
|
17
|
+
# configure_code_diagnostics(D::Ruby.lenient) # `lenient` diagnostics setting
|
18
|
+
# configure_code_diagnostics do |hash| # You can setup everything yourself
|
19
|
+
# hash[D::Ruby::NoMethod] = :information
|
20
|
+
# end
|
21
21
|
end
|
@@ -14,20 +14,16 @@ module Datadog
|
|
14
14
|
ddwaf_err_invalid_argument: :err_invalid_argument
|
15
15
|
}.freeze
|
16
16
|
|
17
|
-
|
18
|
-
|
19
|
-
def initialize(handle)
|
20
|
-
handle_obj = handle.handle_obj
|
21
|
-
retain(handle)
|
22
|
-
|
23
|
-
@context_obj = LibDDWAF.ddwaf_context_init(handle_obj)
|
24
|
-
raise LibDDWAF::Error, 'Could not create context' if @context_obj.null?
|
25
|
-
|
26
|
-
validate!
|
17
|
+
def initialize(context_ptr)
|
18
|
+
@context_ptr = context_ptr
|
27
19
|
end
|
28
20
|
|
29
|
-
|
30
|
-
|
21
|
+
# Destroys the WAF context and sets the pointer to nil.
|
22
|
+
#
|
23
|
+
# The instance becomes unusable after this method is called.
|
24
|
+
def finalize!
|
25
|
+
context_ptr_to_destroy = @context_ptr
|
26
|
+
@context_ptr = nil
|
31
27
|
|
32
28
|
retained.each do |retained_obj|
|
33
29
|
next unless retained_obj.is_a?(LibDDWAF::Object)
|
@@ -36,11 +32,17 @@ module Datadog
|
|
36
32
|
end
|
37
33
|
|
38
34
|
retained.clear
|
39
|
-
LibDDWAF.ddwaf_context_destroy(
|
35
|
+
LibDDWAF.ddwaf_context_destroy(context_ptr_to_destroy)
|
40
36
|
end
|
41
37
|
|
38
|
+
# Runs the WAF context with the given persistent and ephemeral data.
|
39
|
+
#
|
40
|
+
# @raise [ConversionError] if the conversion of persistent or ephemeral data fails
|
41
|
+
# @raise [LibDDWAFError] if libddwaf could not create the result object
|
42
|
+
#
|
43
|
+
# @return [Result] the result of the WAF run
|
42
44
|
def run(persistent_data, ephemeral_data, timeout = LibDDWAF::DDWAF_RUN_TIMEOUT)
|
43
|
-
|
45
|
+
ensure_pointer_presence!
|
44
46
|
|
45
47
|
persistent_data_obj = Converter.ruby_to_object(
|
46
48
|
persistent_data,
|
@@ -50,7 +52,7 @@ module Datadog
|
|
50
52
|
coerce: false
|
51
53
|
)
|
52
54
|
if persistent_data_obj.null?
|
53
|
-
raise
|
55
|
+
raise ConversionError, "Could not convert persistent data: #{persistent_data.inspect}"
|
54
56
|
end
|
55
57
|
|
56
58
|
# retain C objects in memory for subsequent calls to run
|
@@ -64,15 +66,15 @@ module Datadog
|
|
64
66
|
coerce: false
|
65
67
|
)
|
66
68
|
if ephemeral_data_obj.null?
|
67
|
-
raise
|
69
|
+
raise ConversionError, "Could not convert ephemeral data: #{ephemeral_data.inspect}"
|
68
70
|
end
|
69
71
|
|
70
72
|
result_obj = LibDDWAF::Result.new
|
71
|
-
raise
|
73
|
+
raise LibDDWAFError, "Could not create result object" if result_obj.null?
|
72
74
|
|
73
|
-
code = LibDDWAF.ddwaf_run(@
|
75
|
+
code = LibDDWAF.ddwaf_run(@context_ptr, persistent_data_obj, ephemeral_data_obj, result_obj, timeout)
|
74
76
|
|
75
|
-
|
77
|
+
Result.new(
|
76
78
|
RESULT_CODE[code],
|
77
79
|
Converter.object_to_ruby(result_obj[:events]),
|
78
80
|
result_obj[:total_runtime],
|
@@ -80,8 +82,6 @@ module Datadog
|
|
80
82
|
Converter.object_to_ruby(result_obj[:actions]),
|
81
83
|
Converter.object_to_ruby(result_obj[:derivatives])
|
82
84
|
)
|
83
|
-
|
84
|
-
[RESULT_CODE[code], result]
|
85
85
|
ensure
|
86
86
|
LibDDWAF.ddwaf_result_free(result_obj) if result_obj
|
87
87
|
LibDDWAF.ddwaf_object_free(ephemeral_data_obj) if ephemeral_data_obj
|
@@ -89,24 +89,10 @@ module Datadog
|
|
89
89
|
|
90
90
|
private
|
91
91
|
|
92
|
-
|
93
|
-
|
94
|
-
def validate!
|
95
|
-
@valid = true
|
96
|
-
end
|
97
|
-
|
98
|
-
def invalidate!
|
99
|
-
@valid = false
|
100
|
-
end
|
101
|
-
|
102
|
-
def valid?
|
103
|
-
@valid
|
104
|
-
end
|
105
|
-
|
106
|
-
def valid!
|
107
|
-
return if valid?
|
92
|
+
def ensure_pointer_presence!
|
93
|
+
return if @context_ptr
|
108
94
|
|
109
|
-
raise
|
95
|
+
raise InstanceFinalizedError, "Cannot use WAF context after it has been finalized"
|
110
96
|
end
|
111
97
|
|
112
98
|
def retained
|
@@ -7,66 +7,67 @@ module Datadog
|
|
7
7
|
module Converter
|
8
8
|
module_function
|
9
9
|
|
10
|
-
#
|
10
|
+
# standard:disable Metrics/MethodLength,Metrics/CyclomaticComplexity
|
11
11
|
def ruby_to_object(val, max_container_size: nil, max_container_depth: nil, max_string_length: nil, coerce: true)
|
12
12
|
case val
|
13
13
|
when Array
|
14
14
|
obj = LibDDWAF::Object.new
|
15
15
|
res = LibDDWAF.ddwaf_object_array(obj)
|
16
|
-
if res.null?
|
17
|
-
raise LibDDWAF::Error, "Could not convert into object: #{val}"
|
18
|
-
end
|
16
|
+
raise ConversionError, "Could not convert into object: #{val}" if res.null?
|
19
17
|
|
20
18
|
max_index = max_container_size - 1 if max_container_size
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
19
|
+
unless max_container_depth == 0
|
20
|
+
val.each.with_index do |e, i|
|
21
|
+
member = Converter.ruby_to_object(
|
22
|
+
e,
|
23
|
+
max_container_size: max_container_size,
|
24
|
+
max_container_depth: (max_container_depth - 1 if max_container_depth),
|
25
|
+
max_string_length: max_string_length,
|
26
|
+
coerce: coerce
|
27
|
+
)
|
28
|
+
e_res = LibDDWAF.ddwaf_object_array_add(obj, member)
|
29
|
+
raise ConversionError, "Could not add to array object: #{e.inspect}" unless e_res
|
30
|
+
|
31
|
+
break val if max_index && i >= max_index
|
32
|
+
end
|
33
|
+
end
|
32
34
|
|
33
35
|
obj
|
34
36
|
when Hash
|
35
37
|
obj = LibDDWAF::Object.new
|
36
38
|
res = LibDDWAF.ddwaf_object_map(obj)
|
37
|
-
if res.null?
|
38
|
-
raise LibDDWAF::Error, "Could not convert into object: #{val}"
|
39
|
-
end
|
39
|
+
raise ConversionError, "Could not convert into object: #{val}" if res.null?
|
40
40
|
|
41
41
|
max_index = max_container_size - 1 if max_container_size
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
42
|
+
unless max_container_depth == 0
|
43
|
+
val.each.with_index do |e, i|
|
44
|
+
# for Steep, which doesn't handle |(k, v), i|
|
45
|
+
k = e[0]
|
46
|
+
v = e[1]
|
47
|
+
|
48
|
+
k = k.to_s[0, max_string_length] if max_string_length
|
49
|
+
member = Converter.ruby_to_object(
|
50
|
+
v,
|
51
|
+
max_container_size: max_container_size,
|
52
|
+
max_container_depth: (max_container_depth - 1 if max_container_depth),
|
53
|
+
max_string_length: max_string_length,
|
54
|
+
coerce: coerce
|
55
|
+
)
|
56
|
+
kv_res = LibDDWAF.ddwaf_object_map_addl(obj, k.to_s, k.to_s.bytesize, member)
|
57
|
+
raise ConversionError, "Could not add to map object: #{k.inspect} => #{v.inspect}" unless kv_res
|
58
|
+
|
59
|
+
break val if max_index && i >= max_index
|
55
60
|
end
|
56
|
-
|
57
|
-
break val if max_index && i >= max_index
|
58
|
-
end unless max_container_depth == 0
|
61
|
+
end
|
59
62
|
|
60
63
|
obj
|
61
64
|
when String
|
62
65
|
obj = LibDDWAF::Object.new
|
63
|
-
encoded_val = val.to_s.encode(
|
66
|
+
encoded_val = val.to_s.encode(Encoding::UTF_8, invalid: :replace, undef: :replace)
|
64
67
|
val = encoded_val[0, max_string_length] if max_string_length
|
65
68
|
str = val.to_s
|
66
69
|
res = LibDDWAF.ddwaf_object_stringl(obj, str, str.bytesize)
|
67
|
-
if res.null?
|
68
|
-
raise LibDDWAF::Error, "Could not convert into object: #{val.inspect}"
|
69
|
-
end
|
70
|
+
raise ConversionError, "Could not convert into object: #{val.inspect}" if res.null?
|
70
71
|
|
71
72
|
obj
|
72
73
|
when Symbol
|
@@ -74,67 +75,58 @@ module Datadog
|
|
74
75
|
val = val.to_s[0, max_string_length] if max_string_length
|
75
76
|
str = val.to_s
|
76
77
|
res = LibDDWAF.ddwaf_object_stringl(obj, str, str.bytesize)
|
77
|
-
if res.null?
|
78
|
-
raise LibDDWAF::Error, "Could not convert into object: #{val.inspect}"
|
79
|
-
end
|
78
|
+
raise ConversionError, "Could not convert into object: #{val.inspect}" if res.null?
|
80
79
|
|
81
80
|
obj
|
82
81
|
when Integer
|
83
82
|
obj = LibDDWAF::Object.new
|
84
83
|
res = if coerce
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
end
|
91
|
-
if res.null?
|
92
|
-
raise LibDDWAF::Error, "Could not convert into object: #{val.inspect}"
|
84
|
+
LibDDWAF.ddwaf_object_string(obj, val.to_s)
|
85
|
+
elsif val < 0
|
86
|
+
LibDDWAF.ddwaf_object_signed(obj, val)
|
87
|
+
else
|
88
|
+
LibDDWAF.ddwaf_object_unsigned(obj, val)
|
93
89
|
end
|
90
|
+
raise ConversionError, "Could not convert into object: #{val.inspect}" if res.null?
|
94
91
|
|
95
92
|
obj
|
96
93
|
when Float
|
97
94
|
obj = LibDDWAF::Object.new
|
98
95
|
res = if coerce
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
end
|
103
|
-
if res.null?
|
104
|
-
raise LibDDWAF::Error, "Could not convert into object: #{val.inspect}"
|
96
|
+
LibDDWAF.ddwaf_object_string(obj, val.to_s)
|
97
|
+
else
|
98
|
+
LibDDWAF.ddwaf_object_float(obj, val)
|
105
99
|
end
|
100
|
+
raise ConversionError, "Could not convert into object: #{val.inspect}" if res.null?
|
106
101
|
|
107
102
|
obj
|
108
103
|
when TrueClass, FalseClass
|
109
104
|
obj = LibDDWAF::Object.new
|
110
105
|
res = if coerce
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
end
|
115
|
-
if res.null?
|
116
|
-
raise LibDDWAF::Error, "Could not convert into object: #{val.inspect}"
|
106
|
+
LibDDWAF.ddwaf_object_string(obj, val.to_s)
|
107
|
+
else
|
108
|
+
LibDDWAF.ddwaf_object_bool(obj, val)
|
117
109
|
end
|
110
|
+
raise ConversionError, "Could not convert into object: #{val.inspect}" if res.null?
|
118
111
|
|
119
112
|
obj
|
120
113
|
when NilClass
|
121
114
|
obj = LibDDWAF::Object.new
|
122
115
|
res = if coerce
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
end
|
127
|
-
if res.null?
|
128
|
-
raise LibDDWAF::Error, "Could not convert into object: #{val.inspect}"
|
116
|
+
LibDDWAF.ddwaf_object_string(obj, "")
|
117
|
+
else
|
118
|
+
LibDDWAF.ddwaf_object_null(obj)
|
129
119
|
end
|
120
|
+
raise ConversionError, "Could not convert into object: #{val.inspect}" if res.null?
|
130
121
|
|
131
122
|
obj
|
132
123
|
else
|
133
|
-
Converter.ruby_to_object(
|
124
|
+
Converter.ruby_to_object("")
|
134
125
|
end
|
135
126
|
end
|
136
|
-
#
|
127
|
+
# standard:enable Metrics/MethodLength,Metrics/CyclomaticComplexity
|
137
128
|
|
129
|
+
# standard:disable Metrics/MethodLength,Metrics/CyclomaticComplexity
|
138
130
|
def object_to_ruby(obj)
|
139
131
|
case obj[:type]
|
140
132
|
when :ddwaf_obj_invalid, :ddwaf_obj_null
|
@@ -170,6 +162,7 @@ module Datadog
|
|
170
162
|
end
|
171
163
|
end
|
172
164
|
end
|
165
|
+
# standard:enable Metrics/MethodLength,Metrics/CyclomaticComplexity
|
173
166
|
end
|
174
167
|
end
|
175
168
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Datadog
|
2
|
+
module AppSec
|
3
|
+
module WAF
|
4
|
+
Error = Class.new(StandardError)
|
5
|
+
InstanceFinalizedError = Class.new(Error)
|
6
|
+
ConversionError = Class.new(Error)
|
7
|
+
|
8
|
+
class LibDDWAFError < Error
|
9
|
+
attr_reader :diagnostics
|
10
|
+
|
11
|
+
def initialize(msg, diagnostics: nil)
|
12
|
+
@diagnostics = diagnostics
|
13
|
+
|
14
|
+
super(msg)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -6,101 +6,54 @@ module Datadog
|
|
6
6
|
# Ruby representation of the ddwaf_handle in libddwaf
|
7
7
|
# See https://github.com/DataDog/libddwaf/blob/10e3a1dfc7bc9bb8ab11a09a9f8b6b339eaf3271/BINDING_IMPL_NOTES.md?plain=1#L4-L19
|
8
8
|
class Handle
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
rule_obj = Converter.ruby_to_object(rule)
|
13
|
-
if rule_obj.null? || rule_obj[:type] == :ddwaf_object_invalid
|
14
|
-
raise LibDDWAF::Error, "Could not convert object #{rule.inspect}"
|
15
|
-
end
|
16
|
-
|
17
|
-
config_obj = Datadog::AppSec::WAF::LibDDWAF::Config.new
|
18
|
-
if config_obj.null?
|
19
|
-
raise LibDDWAF::Error, 'Could not create config struct'
|
20
|
-
end
|
21
|
-
|
22
|
-
config_obj[:limits][:max_container_size] = limits[:max_container_size] || LibDDWAF::DEFAULT_MAX_CONTAINER_SIZE
|
23
|
-
config_obj[:limits][:max_container_depth] = limits[:max_container_depth] || LibDDWAF::DEFAULT_MAX_CONTAINER_DEPTH
|
24
|
-
config_obj[:limits][:max_string_length] = limits[:max_string_length] || LibDDWAF::DEFAULT_MAX_STRING_LENGTH
|
25
|
-
config_obj[:obfuscator][:key_regex] = FFI::MemoryPointer.from_string(obfuscator[:key_regex]) if obfuscator[:key_regex]
|
26
|
-
config_obj[:obfuscator][:value_regex] = FFI::MemoryPointer.from_string(obfuscator[:value_regex]) if obfuscator[:value_regex]
|
27
|
-
config_obj[:free_fn] = LibDDWAF::ObjectNoFree
|
28
|
-
|
29
|
-
@config = config_obj
|
30
|
-
|
31
|
-
diagnostics_obj = LibDDWAF::Object.new
|
32
|
-
|
33
|
-
@handle_obj = LibDDWAF.ddwaf_init(rule_obj, config_obj, diagnostics_obj)
|
34
|
-
|
35
|
-
@diagnostics = Converter.object_to_ruby(diagnostics_obj)
|
9
|
+
def initialize(handle_ptr)
|
10
|
+
@handle_ptr = handle_ptr
|
11
|
+
end
|
36
12
|
|
37
|
-
|
38
|
-
|
39
|
-
|
13
|
+
# Destroys the WAF handle and sets the pointer to nil.
|
14
|
+
#
|
15
|
+
# The instance becomes unusable after this method is called.
|
16
|
+
def finalize!
|
17
|
+
handle_ptr_to_destroy = @handle_ptr
|
18
|
+
@handle_ptr = nil
|
40
19
|
|
41
|
-
|
42
|
-
ensure
|
43
|
-
LibDDWAF.ddwaf_object_free(diagnostics_obj) if diagnostics_obj
|
44
|
-
LibDDWAF.ddwaf_object_free(rule_obj) if rule_obj
|
20
|
+
LibDDWAF.ddwaf_destroy(handle_ptr_to_destroy)
|
45
21
|
end
|
46
22
|
|
47
|
-
|
48
|
-
|
23
|
+
# Builds a WAF context.
|
24
|
+
#
|
25
|
+
# @raise [LibDDWAFError] if libddwaf could not create the context.
|
26
|
+
# @return [Handle] the WAF handle
|
27
|
+
def build_context
|
28
|
+
ensure_pointer_presence!
|
29
|
+
|
30
|
+
context_obj = LibDDWAF.ddwaf_context_init(@handle_ptr)
|
31
|
+
raise LibDDWAFError, "Could not create context" if context_obj.null?
|
49
32
|
|
50
|
-
|
33
|
+
Context.new(context_obj)
|
51
34
|
end
|
52
35
|
|
53
|
-
|
54
|
-
|
36
|
+
# Returns the list of known addresses in the WAF handle.
|
37
|
+
#
|
38
|
+
# @return [Array<String>] the list of known addresses
|
39
|
+
def known_addresses
|
40
|
+
ensure_pointer_presence!
|
55
41
|
|
56
42
|
count = LibDDWAF::UInt32Ptr.new
|
57
|
-
list = LibDDWAF.ddwaf_known_addresses(
|
43
|
+
list = LibDDWAF.ddwaf_known_addresses(@handle_ptr, count)
|
58
44
|
|
59
45
|
return [] if count == 0 # list is null
|
60
46
|
|
61
47
|
list.get_array_of_string(0, count[:value])
|
62
|
-
|
63
|
-
|
64
|
-
def merge(data)
|
65
|
-
data_obj = Converter.ruby_to_object(data, coerce: false)
|
66
|
-
diagnostics_obj = LibDDWAF::Object.new
|
67
|
-
new_handle = LibDDWAF.ddwaf_update(handle_obj, data_obj, diagnostics_obj)
|
68
|
-
|
69
|
-
return if new_handle.null?
|
70
|
-
|
71
|
-
diagnostics = Converter.object_to_ruby(diagnostics_obj)
|
72
|
-
new_from_handle(new_handle, diagnostics, config)
|
73
|
-
ensure
|
74
|
-
LibDDWAF.ddwaf_object_free(data_obj) if data_obj
|
75
|
-
LibDDWAF.ddwaf_object_free(diagnostics_obj) if diagnostics_obj
|
48
|
+
# TODO: garbage collect the count?
|
76
49
|
end
|
77
50
|
|
78
51
|
private
|
79
52
|
|
80
|
-
def
|
81
|
-
|
82
|
-
obj.instance_variable_set(:@handle_obj, handle_object)
|
83
|
-
obj.instance_variable_set(:@diagnostics, diagnostics)
|
84
|
-
obj.instance_variable_set(:@config, config)
|
85
|
-
obj
|
86
|
-
end
|
87
|
-
|
88
|
-
def validate!
|
89
|
-
@valid = true
|
90
|
-
end
|
91
|
-
|
92
|
-
def invalidate!
|
93
|
-
@valid = false
|
94
|
-
end
|
95
|
-
|
96
|
-
def valid?
|
97
|
-
@valid
|
98
|
-
end
|
99
|
-
|
100
|
-
def valid!
|
101
|
-
return if valid?
|
53
|
+
def ensure_pointer_presence!
|
54
|
+
return if @handle_ptr
|
102
55
|
|
103
|
-
raise
|
56
|
+
raise InstanceFinalizedError, "Cannot use WAF handle after it has been finalized"
|
104
57
|
end
|
105
58
|
end
|
106
59
|
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Datadog
|
4
|
+
module AppSec
|
5
|
+
module WAF
|
6
|
+
# This class represents the libddwaf WAF builder, which is used to generate WAF handles.
|
7
|
+
#
|
8
|
+
# It handles merging of potentially overlapping configurations.
|
9
|
+
class HandleBuilder
|
10
|
+
def initialize(limits: {}, obfuscator: {})
|
11
|
+
handle_config_obj = LibDDWAF::HandleBuilderConfig.new
|
12
|
+
if handle_config_obj.null?
|
13
|
+
raise LibDDWAFError, "Could not create config struct"
|
14
|
+
end
|
15
|
+
|
16
|
+
handle_config_obj[:limits][:max_container_size] = limits[:max_container_size] || LibDDWAF::DEFAULT_MAX_CONTAINER_SIZE
|
17
|
+
handle_config_obj[:limits][:max_container_depth] = limits[:max_container_depth] || LibDDWAF::DEFAULT_MAX_CONTAINER_DEPTH
|
18
|
+
handle_config_obj[:limits][:max_string_length] = limits[:max_string_length] || LibDDWAF::DEFAULT_MAX_STRING_LENGTH
|
19
|
+
|
20
|
+
handle_config_obj[:obfuscator][:key_regex] = FFI::MemoryPointer.from_string(obfuscator[:key_regex]) if obfuscator[:key_regex]
|
21
|
+
handle_config_obj[:obfuscator][:value_regex] = FFI::MemoryPointer.from_string(obfuscator[:value_regex]) if obfuscator[:value_regex]
|
22
|
+
handle_config_obj[:free_fn] = LibDDWAF::ObjectNoFree
|
23
|
+
|
24
|
+
@builder_ptr = LibDDWAF.ddwaf_builder_init(handle_config_obj)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Destroys the WAF builder and sets the pointer to nil.
|
28
|
+
#
|
29
|
+
# The instance becomes unusable after this method is called.
|
30
|
+
def finalize!
|
31
|
+
builder_ptr_to_destroy = @builder_ptr
|
32
|
+
@builder_ptr = nil
|
33
|
+
|
34
|
+
LibDDWAF.ddwaf_builder_destroy(builder_ptr_to_destroy)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Builds a WAF handle from the current state of the builder.
|
38
|
+
#
|
39
|
+
# @raise [LibDDWAFError] if no rules were added to the builder before building the handle
|
40
|
+
# @return [Handle] the WAF handle
|
41
|
+
def build_handle
|
42
|
+
ensure_pointer_presence!
|
43
|
+
|
44
|
+
handle_obj = LibDDWAF.ddwaf_builder_build_instance(@builder_ptr)
|
45
|
+
raise LibDDWAFError, "Could not create handle" if handle_obj.null?
|
46
|
+
|
47
|
+
Handle.new(handle_obj)
|
48
|
+
end
|
49
|
+
|
50
|
+
# :section: Configuration management methods
|
51
|
+
# methods for adding, updating, and removing configurations from the WAF handle builder.
|
52
|
+
|
53
|
+
# Adds or updates a configuration in the WAF handle builder for the given path.
|
54
|
+
#
|
55
|
+
# @return [Hash] diagnostics object
|
56
|
+
# NOTE: default config that was read from file at application startup
|
57
|
+
# has to be removed before adding configurations obtained through Remote Configuration.
|
58
|
+
def add_or_update_config(config, path:)
|
59
|
+
ensure_pointer_presence!
|
60
|
+
|
61
|
+
config_obj = Converter.ruby_to_object(config)
|
62
|
+
diagnostics_obj = LibDDWAF::Object.new
|
63
|
+
|
64
|
+
LibDDWAF.ddwaf_builder_add_or_update_config(@builder_ptr, path, path.length, config_obj, diagnostics_obj)
|
65
|
+
|
66
|
+
Converter.object_to_ruby(diagnostics_obj)
|
67
|
+
ensure
|
68
|
+
LibDDWAF.ddwaf_object_free(config_obj) if config_obj
|
69
|
+
LibDDWAF.ddwaf_object_free(diagnostics_obj) if diagnostics_obj
|
70
|
+
end
|
71
|
+
|
72
|
+
# Removes a configuration from the WAF handle builder for the given path.
|
73
|
+
#
|
74
|
+
# @return [Boolean] true if the configuration was removed, false otherwise
|
75
|
+
def remove_config_at_path(path)
|
76
|
+
ensure_pointer_presence!
|
77
|
+
|
78
|
+
LibDDWAF.ddwaf_builder_remove_config(@builder_ptr, path, path.length)
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def ensure_pointer_presence!
|
84
|
+
return if @builder_ptr
|
85
|
+
|
86
|
+
raise InstanceFinalizedError, "Cannot use WAF handle builder after it has been finalized"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|