libddwaf 1.15.0.0.0 → 1.18.0.0.1
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/actions/docker-build-ruby/action.yml +2 -13
- data/.github/workflows/package.yml +8 -12
- data/.github/workflows/test.yml +18 -13
- data/CHANGELOG.md +6 -0
- data/lib/datadog/appsec/waf/context.rb +126 -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 +313 -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 -679
- data/sig/datadog/appsec/waf/context.rbs +39 -0
- data/sig/datadog/appsec/waf/converter.rbs +11 -0
- data/sig/datadog/appsec/waf/handle.rbs +42 -0
- data/sig/datadog/appsec/waf/lib_ddwaf.rbs +156 -0
- data/sig/datadog/appsec/waf/result.rbs +33 -0
- data/sig/datadog/appsec/waf.rbs +1 -213
- metadata +12 -3
- data/libddwaf-releases.sha256 +0 -126
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4fad2f4be2e60e15913e130d28130af2f3d3d6d40aea05dba4a1d3d967cc4c12
|
4
|
+
data.tar.gz: aac3e2f4ae75f5a9387341997ab07861f1b8ee8d0ae63cc6ad97bd99b421b132
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 83b667e79adbefbf6d52c3dd425ff2bd973d1a9e9a32c3ca817e08963c86ab9bbf3002e54d7a57fbbfc6e86a0e6254350d6b02e700e4af84852ce8a9d1936ab6
|
7
|
+
data.tar.gz: e242033561aa9e4dfd3d66c44170ea5595f27499ee6fe30bfbff66bd7af61edb5c2986494bbb4b986a7bf4b005da243b9df66ce7d82c78bc8b215cc03f86913c
|
@@ -1,17 +1,12 @@
|
|
1
1
|
name: Build docker image
|
2
2
|
description:
|
3
|
-
|
4
|
-
This action builds the image for the specified architecture and libc.
|
3
|
+
This action builds the image for the specified ruby version and libc.
|
5
4
|
|
6
5
|
inputs:
|
7
6
|
ruby-version:
|
8
7
|
description: Ruby version
|
9
8
|
required: true
|
10
9
|
|
11
|
-
arch:
|
12
|
-
description: Build architecture
|
13
|
-
required: true
|
14
|
-
|
15
10
|
libc:
|
16
11
|
description: Which libc is used
|
17
12
|
required: true
|
@@ -29,11 +24,6 @@ outputs:
|
|
29
24
|
runs:
|
30
25
|
using: "composite"
|
31
26
|
steps:
|
32
|
-
- name: Set up QEMU
|
33
|
-
uses: docker/setup-qemu-action@v3
|
34
|
-
with:
|
35
|
-
platforms: ${{ inputs.arch }}
|
36
|
-
|
37
27
|
- name: Set up Docker Buildx
|
38
28
|
uses: docker/setup-buildx-action@v3
|
39
29
|
|
@@ -48,10 +38,9 @@ runs:
|
|
48
38
|
tags: libddwaf-rb-test:latest
|
49
39
|
cache-from: type=gha
|
50
40
|
cache-to: type=gha,mode=max
|
51
|
-
platforms: linux/${{ inputs.arch }}
|
52
41
|
|
53
42
|
- name: Set run-cmd output
|
54
43
|
id: set-run-cmd
|
55
44
|
shell: bash
|
56
45
|
run: |
|
57
|
-
echo "run-cmd=docker run
|
46
|
+
echo "run-cmd=docker run -v gems:/usr/local/bundle -v ${{ github.workspace }}:/libddwaf-rb -w /libddwaf-rb libddwaf-rb-test:latest" >> "$GITHUB_OUTPUT"
|
@@ -7,17 +7,16 @@ jobs:
|
|
7
7
|
strategy:
|
8
8
|
fail-fast: false
|
9
9
|
matrix:
|
10
|
-
os: [ubuntu-24.04]
|
10
|
+
os: [ubuntu-24.04, ubuntu-24.04-arm]
|
11
11
|
ruby: ["3.3"]
|
12
|
-
arch: [amd64, arm64]
|
13
12
|
libc: [gnu]
|
14
13
|
include:
|
15
|
-
-
|
14
|
+
- os: ubuntu-24.04
|
16
15
|
platform: x86_64-linux
|
17
|
-
-
|
16
|
+
- os: ubuntu-24.04-arm
|
18
17
|
platform: aarch64-linux
|
19
18
|
|
20
|
-
name: Test build without fetching libddwaf (Ruby ${{ matrix.ruby }}, ${{ matrix.
|
19
|
+
name: Test build without fetching libddwaf (Ruby ${{ matrix.ruby }}, ${{ matrix.os }}, ${{ matrix.libc }})
|
21
20
|
runs-on: ${{ matrix.os }}
|
22
21
|
|
23
22
|
steps:
|
@@ -29,7 +28,6 @@ jobs:
|
|
29
28
|
uses: ./.github/actions/docker-build-ruby
|
30
29
|
with:
|
31
30
|
ruby-version: ${{ matrix.ruby }}
|
32
|
-
arch: ${{ matrix.arch }}
|
33
31
|
libc: ${{ matrix.libc }}
|
34
32
|
|
35
33
|
- name: Bundle install
|
@@ -52,14 +50,13 @@ jobs:
|
|
52
50
|
strategy:
|
53
51
|
fail-fast: false
|
54
52
|
matrix:
|
55
|
-
os: [ubuntu-24.04]
|
53
|
+
os: [ubuntu-24.04, ubuntu-24.04-arm]
|
56
54
|
ruby: ["3.3", "9.4"]
|
57
|
-
arch: [amd64, arm64]
|
58
55
|
libc: [gnu, musl]
|
59
56
|
include:
|
60
|
-
-
|
57
|
+
- os: ubuntu-24.04
|
61
58
|
platform: x86_64-linux:llvm
|
62
|
-
-
|
59
|
+
- os: ubuntu-24.04-arm
|
63
60
|
platform: aarch64-linux:llvm
|
64
61
|
- ruby: 3.3
|
65
62
|
jruby: false
|
@@ -69,7 +66,7 @@ jobs:
|
|
69
66
|
- ruby: 9.4
|
70
67
|
libc: musl
|
71
68
|
|
72
|
-
name: Test gem build (${{ matrix.jruby == true && 'Jruby' || 'Ruby'}} ${{ matrix.ruby }}, ${{ matrix.
|
69
|
+
name: Test gem build (${{ matrix.jruby == true && 'Jruby' || 'Ruby'}} ${{ matrix.ruby }}, ${{ matrix.os }}, ${{ matrix.libc }})
|
73
70
|
runs-on: ${{ matrix.os }}
|
74
71
|
|
75
72
|
steps:
|
@@ -81,7 +78,6 @@ jobs:
|
|
81
78
|
uses: ./.github/actions/docker-build-ruby
|
82
79
|
with:
|
83
80
|
ruby-version: ${{ matrix.ruby }}
|
84
|
-
arch: ${{ matrix.arch }}
|
85
81
|
libc: ${{ matrix.libc }}
|
86
82
|
jruby: ${{ matrix.jruby }}
|
87
83
|
|
data/.github/workflows/test.yml
CHANGED
@@ -7,17 +7,16 @@ jobs:
|
|
7
7
|
strategy:
|
8
8
|
fail-fast: false
|
9
9
|
matrix:
|
10
|
-
os: [ubuntu-24.04]
|
11
|
-
ruby: ["2.5", "2.6", "2.7", "3.0", "3.1", "3.2", "3.3"]
|
12
|
-
arch: [amd64, arm64]
|
10
|
+
os: [ubuntu-24.04, ubuntu-24.04-arm]
|
11
|
+
ruby: ["2.5", "2.6", "2.7", "3.0", "3.1", "3.2", "3.3", "3.4"]
|
13
12
|
libc: [gnu, musl]
|
14
13
|
include:
|
15
|
-
-
|
14
|
+
- os: ubuntu-24.04
|
16
15
|
platform: x86_64-linux
|
17
|
-
-
|
16
|
+
- os: ubuntu-24.04-arm
|
18
17
|
platform: aarch64-linux
|
19
18
|
|
20
|
-
name: Test (Ruby ${{ matrix.ruby }}, ${{ matrix.
|
19
|
+
name: Test (Ruby ${{ matrix.ruby }}, ${{ matrix.os }}, ${{ matrix.libc }})
|
21
20
|
runs-on: ${{ matrix.os }}
|
22
21
|
|
23
22
|
steps:
|
@@ -29,7 +28,6 @@ jobs:
|
|
29
28
|
uses: ./.github/actions/docker-build-ruby
|
30
29
|
with:
|
31
30
|
ruby-version: ${{ matrix.ruby }}
|
32
|
-
arch: ${{ matrix.arch }}
|
33
31
|
libc: ${{ matrix.libc }}
|
34
32
|
|
35
33
|
- name: Bundle install
|
@@ -44,20 +42,22 @@ jobs:
|
|
44
42
|
- name: Run specs
|
45
43
|
run: ${{ steps.build-image.outputs.run-cmd }} bundle exec rake spec
|
46
44
|
|
45
|
+
- name: Run stress tests
|
46
|
+
run: ${{ steps.build-image.outputs.run-cmd }} bundle exec rake spec:stress_tests
|
47
|
+
|
47
48
|
test-jruby-linux:
|
48
49
|
strategy:
|
49
50
|
fail-fast: false
|
50
51
|
matrix:
|
51
|
-
os: [ubuntu-24.04]
|
52
|
+
os: [ubuntu-24.04, ubuntu-24.04-arm]
|
52
53
|
jruby: ["9.3", "9.4"]
|
53
|
-
arch: [amd64, arm64]
|
54
54
|
include:
|
55
|
-
-
|
55
|
+
- os: ubuntu-24.04
|
56
56
|
platform: x86_64-linux
|
57
|
-
-
|
57
|
+
- os: ubuntu-24.04-arm
|
58
58
|
platform: aarch64-linux
|
59
59
|
|
60
|
-
name: Test (Jruby ${{ matrix.jruby }}, ${{ matrix.
|
60
|
+
name: Test (Jruby ${{ matrix.jruby }}, ${{ matrix.os }})
|
61
61
|
runs-on: ${{ matrix.os }}
|
62
62
|
|
63
63
|
steps:
|
@@ -70,7 +70,6 @@ jobs:
|
|
70
70
|
with:
|
71
71
|
ruby-version: ${{ matrix.jruby }}
|
72
72
|
jruby: true
|
73
|
-
arch: ${{ matrix.arch }}
|
74
73
|
libc: gnu
|
75
74
|
|
76
75
|
- name: Bundle install
|
@@ -85,6 +84,9 @@ jobs:
|
|
85
84
|
- name: Run specs
|
86
85
|
run: ${{ steps.build-image.outputs.run-cmd }} bundle exec rake spec
|
87
86
|
|
87
|
+
- name: Run stress tests
|
88
|
+
run: ${{ steps.build-image.outputs.run-cmd }} bundle exec rake spec:stress_tests
|
89
|
+
|
88
90
|
test-darwin:
|
89
91
|
strategy:
|
90
92
|
fail-fast: false
|
@@ -116,3 +118,6 @@ jobs:
|
|
116
118
|
|
117
119
|
- name: Run specs
|
118
120
|
run: bundle exec rake spec
|
121
|
+
|
122
|
+
- name: Run stress tests
|
123
|
+
run: bundle exec rake spec:stress_tests
|
data/CHANGELOG.md
CHANGED
@@ -1,8 +1,14 @@
|
|
1
|
+
# 2025-02-20 v.1.18.0.0.1
|
2
|
+
|
3
|
+
- Fixed memory-leak in `Datadog::AppSec::WAF::Context#run` when non-empty ephemeral data passed
|
4
|
+
|
1
5
|
# 2024-10-29 v.1.15.0.0.0
|
6
|
+
|
2
7
|
- Update to libddwaf 1.15.0
|
3
8
|
- 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
9
|
|
5
10
|
# 2023-09-11 v.1.14.0.0.0
|
11
|
+
|
6
12
|
- Update to libddwaf 1.14.0
|
7
13
|
- Add support for `Float` and `Nil` scalar values when converting from ruby to WAF Object and vice versa.
|
8
14
|
|
@@ -0,0 +1,126 @@
|
|
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
|
+
retained.clear
|
39
|
+
LibDDWAF.ddwaf_context_destroy(context_obj)
|
40
|
+
end
|
41
|
+
|
42
|
+
def run(persistent_data, ephemeral_data, timeout = LibDDWAF::DDWAF_RUN_TIMEOUT)
|
43
|
+
valid!
|
44
|
+
|
45
|
+
persistent_data_obj = Converter.ruby_to_object(
|
46
|
+
persistent_data,
|
47
|
+
max_container_size: LibDDWAF::DDWAF_MAX_CONTAINER_SIZE,
|
48
|
+
max_container_depth: LibDDWAF::DDWAF_MAX_CONTAINER_DEPTH,
|
49
|
+
max_string_length: LibDDWAF::DDWAF_MAX_STRING_LENGTH,
|
50
|
+
coerce: false
|
51
|
+
)
|
52
|
+
if persistent_data_obj.null?
|
53
|
+
raise LibDDWAF::Error, "Could not convert persistent data: #{persistent_data.inspect}"
|
54
|
+
end
|
55
|
+
|
56
|
+
# retain C objects in memory for subsequent calls to run
|
57
|
+
retain(persistent_data_obj)
|
58
|
+
|
59
|
+
ephemeral_data_obj = Converter.ruby_to_object(
|
60
|
+
ephemeral_data,
|
61
|
+
max_container_size: LibDDWAF::DDWAF_MAX_CONTAINER_SIZE,
|
62
|
+
max_container_depth: LibDDWAF::DDWAF_MAX_CONTAINER_DEPTH,
|
63
|
+
max_string_length: LibDDWAF::DDWAF_MAX_STRING_LENGTH,
|
64
|
+
coerce: false
|
65
|
+
)
|
66
|
+
if ephemeral_data_obj.null?
|
67
|
+
raise LibDDWAF::Error, "Could not convert ephemeral data: #{ephemeral_data.inspect}"
|
68
|
+
end
|
69
|
+
|
70
|
+
result_obj = LibDDWAF::Result.new
|
71
|
+
raise LibDDWAF::Error, 'Could not create result object' if result_obj.null?
|
72
|
+
|
73
|
+
code = LibDDWAF.ddwaf_run(@context_obj, persistent_data_obj, ephemeral_data_obj, result_obj, timeout)
|
74
|
+
|
75
|
+
result = Result.new(
|
76
|
+
RESULT_CODE[code],
|
77
|
+
Converter.object_to_ruby(result_obj[:events]),
|
78
|
+
result_obj[:total_runtime],
|
79
|
+
result_obj[:timeout],
|
80
|
+
Converter.object_to_ruby(result_obj[:actions]),
|
81
|
+
Converter.object_to_ruby(result_obj[:derivatives])
|
82
|
+
)
|
83
|
+
|
84
|
+
[RESULT_CODE[code], result]
|
85
|
+
ensure
|
86
|
+
LibDDWAF.ddwaf_result_free(result_obj) if result_obj
|
87
|
+
LibDDWAF.ddwaf_object_free(ephemeral_data_obj) if ephemeral_data_obj
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
# FIXME: Rename into something which reflect that it's impossible to run
|
93
|
+
# libddwaf on finalized context (closed)
|
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?
|
108
|
+
|
109
|
+
raise LibDDWAF::Error, "Attempt to use an invalid instance: #{inspect}"
|
110
|
+
end
|
111
|
+
|
112
|
+
def retained
|
113
|
+
@retained ||= []
|
114
|
+
end
|
115
|
+
|
116
|
+
def retain(object)
|
117
|
+
retained << object
|
118
|
+
end
|
119
|
+
|
120
|
+
def release(object)
|
121
|
+
retained.delete(object)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
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
|