libddwaf 1.14.0.0.0-x86_64-linux → 1.18.0.0.0-x86_64-linux
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -1
- data/lib/datadog/appsec/waf/context.rb +122 -0
- data/lib/datadog/appsec/waf/converter.rb +172 -0
- data/lib/datadog/appsec/waf/handle.rb +108 -0
- data/lib/datadog/appsec/waf/lib_ddwaf.rb +307 -0
- data/lib/datadog/appsec/waf/result.rb +33 -0
- data/lib/datadog/appsec/waf/version.rb +2 -2
- data/lib/datadog/appsec/waf.rb +17 -670
- data/vendor/libddwaf/libddwaf-1.18.0-linux-x86_64/lib/libddwaf.so +0 -0
- metadata +13 -8
- data/vendor/libddwaf/libddwaf-1.14.0-linux-x86_64/lib/libddwaf.so +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d4b9dc918f767d7f4320f1028abe05bf12694598a1c2d79855310f57e757c204
|
4
|
+
data.tar.gz: 129101ead82a9771636006ce64e4d9352b5d0746c0dd411adbfdf6164e0c91ed
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: da26d9ea842942b28de110f04fcbce3d8b16acd068083736df070313aa122195d72f73c88b6fb5298e3b857c3609a233bf479999e17acc38ebf34da67879e0f4
|
7
|
+
data.tar.gz: b5344119847906e408839017242bf9d8511c9047abe3e0cc5a2b8aa941dee0d0191947f0b1ecbf1b853d1007943ae60d98cbd4dab1188cb2a5236bff4cb0d2df
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
# 2024-10-29 v.1.15.0.0.0
|
2
|
+
- Update to libddwaf 1.15.0
|
3
|
+
- Changed `Datadog::AppSec::WAF::Context#run` interface to accommodate ephemeral data ([Breaking change](https://github.com/DataDog/libddwaf/blob/master/CHANGELOG.md#v1150-unstable))
|
4
|
+
|
1
5
|
# 2023-09-11 v.1.14.0.0.0
|
2
6
|
- Update to libddwaf 1.14.0
|
3
7
|
- Add support for `Float` and `Nil` scalar values when converting from ruby to WAF Object and vice versa.
|
@@ -6,7 +10,7 @@
|
|
6
10
|
# 2023-08-29 v.1.11.0.0.0
|
7
11
|
|
8
12
|
- Update to libddwaf 1.11.0
|
9
|
-
- Changed `Datadog::AppSec::WAF::Handle#ruleset_info` to `Datadog::AppSec::WAF::Handle#diagnostics
|
13
|
+
- Changed `Datadog::AppSec::WAF::Handle#ruleset_info` to `Datadog::AppSec::WAF::Handle#diagnostics`. (Breaking change)
|
10
14
|
The schema of the diagnostics variable can be found [here](https://github.com/DataDog/libddwaf/blob/master/schema/diagnostics.json)
|
11
15
|
- Changed `Datadog::AppSec::WAF::Result#data` to `Datadog::AppSec::WAF::Result#events`. (Breaking change)
|
12
16
|
The schema of the events variable can be found [here](https://github.com/DataDog/libddwaf/blob/master/schema/events.json)
|
@@ -0,0 +1,122 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Datadog
|
4
|
+
module AppSec
|
5
|
+
module WAF
|
6
|
+
# Ruby representation of the ddwaf_context in libddwaf
|
7
|
+
# See https://github.com/DataDog/libddwaf/blob/10e3a1dfc7bc9bb8ab11a09a9f8b6b339eaf3271/BINDING_IMPL_NOTES.md?plain=1#L125-L158
|
8
|
+
class Context
|
9
|
+
RESULT_CODE = {
|
10
|
+
ddwaf_ok: :ok,
|
11
|
+
ddwaf_match: :match,
|
12
|
+
ddwaf_err_internal: :err_internal,
|
13
|
+
ddwaf_err_invalid_object: :err_invalid_object,
|
14
|
+
ddwaf_err_invalid_argument: :err_invalid_argument
|
15
|
+
}.freeze
|
16
|
+
|
17
|
+
attr_reader :context_obj
|
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!
|
27
|
+
end
|
28
|
+
|
29
|
+
def finalize
|
30
|
+
invalidate!
|
31
|
+
|
32
|
+
retained.each do |retained_obj|
|
33
|
+
next unless retained_obj.is_a?(LibDDWAF::Object)
|
34
|
+
|
35
|
+
LibDDWAF.ddwaf_object_free(retained_obj)
|
36
|
+
end
|
37
|
+
|
38
|
+
LibDDWAF.ddwaf_context_destroy(context_obj)
|
39
|
+
end
|
40
|
+
|
41
|
+
def run(persistent_data, ephemeral_data, timeout = LibDDWAF::DDWAF_RUN_TIMEOUT)
|
42
|
+
valid!
|
43
|
+
|
44
|
+
persistent_data_obj = Converter.ruby_to_object(
|
45
|
+
persistent_data,
|
46
|
+
max_container_size: LibDDWAF::DDWAF_MAX_CONTAINER_SIZE,
|
47
|
+
max_container_depth: LibDDWAF::DDWAF_MAX_CONTAINER_DEPTH,
|
48
|
+
max_string_length: LibDDWAF::DDWAF_MAX_STRING_LENGTH,
|
49
|
+
coerce: false
|
50
|
+
)
|
51
|
+
if persistent_data_obj.null?
|
52
|
+
raise LibDDWAF::Error, "Could not convert persistent data: #{persistent_data.inspect}"
|
53
|
+
end
|
54
|
+
|
55
|
+
# retain C objects in memory for subsequent calls to run
|
56
|
+
retain(persistent_data_obj)
|
57
|
+
|
58
|
+
ephemeral_data_obj = Converter.ruby_to_object(
|
59
|
+
ephemeral_data,
|
60
|
+
max_container_size: LibDDWAF::DDWAF_MAX_CONTAINER_SIZE,
|
61
|
+
max_container_depth: LibDDWAF::DDWAF_MAX_CONTAINER_DEPTH,
|
62
|
+
max_string_length: LibDDWAF::DDWAF_MAX_STRING_LENGTH,
|
63
|
+
coerce: false
|
64
|
+
)
|
65
|
+
if ephemeral_data_obj.null?
|
66
|
+
raise LibDDWAF::Error, "Could not convert ephemeral data: #{ephemeral_data.inspect}"
|
67
|
+
end
|
68
|
+
|
69
|
+
result_obj = LibDDWAF::Result.new
|
70
|
+
raise LibDDWAF::Error, 'Could not create result object' if result_obj.null?
|
71
|
+
|
72
|
+
code = LibDDWAF.ddwaf_run(@context_obj, persistent_data_obj, ephemeral_data_obj, result_obj, timeout)
|
73
|
+
|
74
|
+
result = Result.new(
|
75
|
+
RESULT_CODE[code],
|
76
|
+
Converter.object_to_ruby(result_obj[:events]),
|
77
|
+
result_obj[:total_runtime],
|
78
|
+
result_obj[:timeout],
|
79
|
+
Converter.object_to_ruby(result_obj[:actions]),
|
80
|
+
Converter.object_to_ruby(result_obj[:derivatives])
|
81
|
+
)
|
82
|
+
|
83
|
+
[RESULT_CODE[code], result]
|
84
|
+
ensure
|
85
|
+
LibDDWAF.ddwaf_result_free(result_obj) if result_obj
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def validate!
|
91
|
+
@valid = true
|
92
|
+
end
|
93
|
+
|
94
|
+
def invalidate!
|
95
|
+
@valid = false
|
96
|
+
end
|
97
|
+
|
98
|
+
def valid?
|
99
|
+
@valid
|
100
|
+
end
|
101
|
+
|
102
|
+
def valid!
|
103
|
+
return if valid?
|
104
|
+
|
105
|
+
raise LibDDWAF::Error, "Attempt to use an invalid instance: #{inspect}"
|
106
|
+
end
|
107
|
+
|
108
|
+
def retained
|
109
|
+
@retained ||= []
|
110
|
+
end
|
111
|
+
|
112
|
+
def retain(object)
|
113
|
+
retained << object
|
114
|
+
end
|
115
|
+
|
116
|
+
def release(object)
|
117
|
+
retained.delete(object)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,172 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Datadog
|
4
|
+
module AppSec
|
5
|
+
module WAF
|
6
|
+
# Module responsible for Ruby-to-C and C-to-Ruby conversions
|
7
|
+
module Converter
|
8
|
+
module_function
|
9
|
+
|
10
|
+
# rubocop:disable Metrics/MethodLength,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
|
11
|
+
def ruby_to_object(val, max_container_size: nil, max_container_depth: nil, max_string_length: nil, coerce: true)
|
12
|
+
case val
|
13
|
+
when Array
|
14
|
+
obj = LibDDWAF::Object.new
|
15
|
+
res = LibDDWAF.ddwaf_object_array(obj)
|
16
|
+
if res.null?
|
17
|
+
raise LibDDWAF::Error, "Could not convert into object: #{val}"
|
18
|
+
end
|
19
|
+
|
20
|
+
max_index = max_container_size - 1 if max_container_size
|
21
|
+
val.each.with_index do |e, i| # rubocop:disable Style/MultilineIfModifier
|
22
|
+
member = Converter.ruby_to_object(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
|
+
e_res = LibDDWAF.ddwaf_object_array_add(obj, member)
|
28
|
+
raise LibDDWAF::Error, "Could not add to array object: #{e.inspect}" unless e_res
|
29
|
+
|
30
|
+
break val if max_index && i >= max_index
|
31
|
+
end unless max_container_depth == 0
|
32
|
+
|
33
|
+
obj
|
34
|
+
when Hash
|
35
|
+
obj = LibDDWAF::Object.new
|
36
|
+
res = LibDDWAF.ddwaf_object_map(obj)
|
37
|
+
if res.null?
|
38
|
+
raise LibDDWAF::Error, "Could not convert into object: #{val}"
|
39
|
+
end
|
40
|
+
|
41
|
+
max_index = max_container_size - 1 if max_container_size
|
42
|
+
val.each.with_index do |e, i| # rubocop:disable Style/MultilineIfModifier
|
43
|
+
# for Steep, which doesn't handle |(k, v), i|
|
44
|
+
k, v = e[0], e[1] # rubocop:disable Style/ParallelAssignment
|
45
|
+
|
46
|
+
k = k.to_s[0, max_string_length] if max_string_length
|
47
|
+
member = Converter.ruby_to_object(v,
|
48
|
+
max_container_size: max_container_size,
|
49
|
+
max_container_depth: (max_container_depth - 1 if max_container_depth),
|
50
|
+
max_string_length: max_string_length,
|
51
|
+
coerce: coerce)
|
52
|
+
kv_res = LibDDWAF.ddwaf_object_map_addl(obj, k.to_s, k.to_s.bytesize, member)
|
53
|
+
unless kv_res
|
54
|
+
raise LibDDWAF::Error, "Could not add to map object: #{k.inspect} => #{v.inspect}"
|
55
|
+
end
|
56
|
+
|
57
|
+
break val if max_index && i >= max_index
|
58
|
+
end unless max_container_depth == 0
|
59
|
+
|
60
|
+
obj
|
61
|
+
when String
|
62
|
+
obj = LibDDWAF::Object.new
|
63
|
+
encoded_val = val.to_s.encode('utf-8', invalid: :replace, undef: :replace)
|
64
|
+
val = encoded_val[0, max_string_length] if max_string_length
|
65
|
+
str = val.to_s
|
66
|
+
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
|
+
|
71
|
+
obj
|
72
|
+
when Symbol
|
73
|
+
obj = LibDDWAF::Object.new
|
74
|
+
val = val.to_s[0, max_string_length] if max_string_length
|
75
|
+
str = val.to_s
|
76
|
+
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
|
80
|
+
|
81
|
+
obj
|
82
|
+
when Integer
|
83
|
+
obj = LibDDWAF::Object.new
|
84
|
+
res = if coerce
|
85
|
+
LibDDWAF.ddwaf_object_string(obj, val.to_s)
|
86
|
+
elsif val < 0
|
87
|
+
LibDDWAF.ddwaf_object_signed(obj, val)
|
88
|
+
else
|
89
|
+
LibDDWAF.ddwaf_object_unsigned(obj, val)
|
90
|
+
end
|
91
|
+
if res.null?
|
92
|
+
raise LibDDWAF::Error, "Could not convert into object: #{val.inspect}"
|
93
|
+
end
|
94
|
+
|
95
|
+
obj
|
96
|
+
when Float
|
97
|
+
obj = LibDDWAF::Object.new
|
98
|
+
res = if coerce
|
99
|
+
LibDDWAF.ddwaf_object_string(obj, val.to_s)
|
100
|
+
else
|
101
|
+
LibDDWAF.ddwaf_object_float(obj, val)
|
102
|
+
end
|
103
|
+
if res.null?
|
104
|
+
raise LibDDWAF::Error, "Could not convert into object: #{val.inspect}"
|
105
|
+
end
|
106
|
+
|
107
|
+
obj
|
108
|
+
when TrueClass, FalseClass
|
109
|
+
obj = LibDDWAF::Object.new
|
110
|
+
res = if coerce
|
111
|
+
LibDDWAF.ddwaf_object_string(obj, val.to_s)
|
112
|
+
else
|
113
|
+
LibDDWAF.ddwaf_object_bool(obj, val)
|
114
|
+
end
|
115
|
+
if res.null?
|
116
|
+
raise LibDDWAF::Error, "Could not convert into object: #{val.inspect}"
|
117
|
+
end
|
118
|
+
|
119
|
+
obj
|
120
|
+
when NilClass
|
121
|
+
obj = LibDDWAF::Object.new
|
122
|
+
res = if coerce
|
123
|
+
LibDDWAF.ddwaf_object_string(obj, '')
|
124
|
+
else
|
125
|
+
LibDDWAF.ddwaf_object_null(obj)
|
126
|
+
end
|
127
|
+
if res.null?
|
128
|
+
raise LibDDWAF::Error, "Could not convert into object: #{val.inspect}"
|
129
|
+
end
|
130
|
+
|
131
|
+
obj
|
132
|
+
else
|
133
|
+
Converter.ruby_to_object('')
|
134
|
+
end
|
135
|
+
end
|
136
|
+
# rubocop:enable Metrics/MethodLength,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
|
137
|
+
|
138
|
+
def object_to_ruby(obj)
|
139
|
+
case obj[:type]
|
140
|
+
when :ddwaf_obj_invalid, :ddwaf_obj_null
|
141
|
+
nil
|
142
|
+
when :ddwaf_obj_bool
|
143
|
+
obj[:valueUnion][:boolean]
|
144
|
+
when :ddwaf_obj_string
|
145
|
+
obj[:valueUnion][:stringValue].read_bytes(obj[:nbEntries])
|
146
|
+
when :ddwaf_obj_signed
|
147
|
+
obj[:valueUnion][:intValue]
|
148
|
+
when :ddwaf_obj_unsigned
|
149
|
+
obj[:valueUnion][:uintValue]
|
150
|
+
when :ddwaf_obj_float
|
151
|
+
obj[:valueUnion][:f64]
|
152
|
+
when :ddwaf_obj_array
|
153
|
+
(0...obj[:nbEntries]).each.with_object([]) do |i, a|
|
154
|
+
ptr = obj[:valueUnion][:array] + i * LibDDWAF::Object.size
|
155
|
+
e = Converter.object_to_ruby(LibDDWAF::Object.new(ptr))
|
156
|
+
a << e # steep:ignore
|
157
|
+
end
|
158
|
+
when :ddwaf_obj_map
|
159
|
+
(0...obj[:nbEntries]).each.with_object({}) do |i, h|
|
160
|
+
ptr = obj[:valueUnion][:array] + i * Datadog::AppSec::WAF::LibDDWAF::Object.size
|
161
|
+
o = Datadog::AppSec::WAF::LibDDWAF::Object.new(ptr)
|
162
|
+
l = o[:parameterNameLength]
|
163
|
+
k = o[:parameterName].read_bytes(l)
|
164
|
+
v = Converter.object_to_ruby(LibDDWAF::Object.new(ptr))
|
165
|
+
h[k] = v # steep:ignore
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Datadog
|
4
|
+
module AppSec
|
5
|
+
module WAF
|
6
|
+
# Ruby representation of the ddwaf_handle in libddwaf
|
7
|
+
# See https://github.com/DataDog/libddwaf/blob/10e3a1dfc7bc9bb8ab11a09a9f8b6b339eaf3271/BINDING_IMPL_NOTES.md?plain=1#L4-L19
|
8
|
+
class Handle
|
9
|
+
attr_reader :handle_obj, :diagnostics, :config
|
10
|
+
|
11
|
+
def initialize(rule, limits: {}, obfuscator: {})
|
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)
|
36
|
+
|
37
|
+
if @handle_obj.null?
|
38
|
+
raise LibDDWAF::Error.new('Could not create handle', diagnostics: @diagnostics)
|
39
|
+
end
|
40
|
+
|
41
|
+
validate!
|
42
|
+
ensure
|
43
|
+
LibDDWAF.ddwaf_object_free(diagnostics_obj) if diagnostics_obj
|
44
|
+
LibDDWAF.ddwaf_object_free(rule_obj) if rule_obj
|
45
|
+
end
|
46
|
+
|
47
|
+
def finalize
|
48
|
+
invalidate!
|
49
|
+
|
50
|
+
LibDDWAF.ddwaf_destroy(handle_obj)
|
51
|
+
end
|
52
|
+
|
53
|
+
def required_addresses
|
54
|
+
valid!
|
55
|
+
|
56
|
+
count = LibDDWAF::UInt32Ptr.new
|
57
|
+
list = LibDDWAF.ddwaf_known_addresses(handle_obj, count)
|
58
|
+
|
59
|
+
return [] if count == 0 # list is null
|
60
|
+
|
61
|
+
list.get_array_of_string(0, count[:value])
|
62
|
+
end
|
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
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def new_from_handle(handle_object, diagnostics, config)
|
81
|
+
obj = Handle.allocate
|
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?
|
102
|
+
|
103
|
+
raise LibDDWAF::Error, "Attempt to use an invalid instance: #{inspect}"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|