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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0df9a5856b3d088cb3df3831c6ff9a8effbbfafbca8648e39aac1e7d54c26252
4
- data.tar.gz: 162b988831c6b41f5f4ff556eee37b117120c02aa66e288f569b663759c9fc67
3
+ metadata.gz: 10d8d57e5c46bce6df51fd1b551831c8d4421144837498ce916c093c0b255b8e
4
+ data.tar.gz: d8fd00d07b23de9ff1af3be34b0344f0a1404db5da66e219e312a75d8d191942
5
5
  SHA512:
6
- metadata.gz: c29189c22f7b96212ef2bf3e9c5089ee7984e29da3d4dabe3a639ba033b8f6126c3aa72f94e7ca82963bcef78eb8efdeee4b27eee0645adf9ad2cebac5107504
7
- data.tar.gz: bda9cb832b37a022553f64903d3aeceb4432ecccfcf6c7d5fb3f46f0cad0e37bb493f9559e005c24003744053c00d68593d7a15790c323701f6e07d4f0c5ca1d
6
+ metadata.gz: 20d4c8ae3986514d5bf69d830a75468973e3688e971fe03068cd3451d58ddb0bf3a9e0ad86303b04c7e42cbef85dc9bf1e61e23e3db77a57374d30422c54b9c2
7
+ data.tar.gz: bd3c2665b3b5ab606ad59dc48e4eac7cd8f5568c38141d3cee0cddcbb2ba5681186174bbe1804657c47441a204297a1313a86fb3cf25274e398b857732382c77
@@ -15,8 +15,8 @@ jobs:
15
15
  - name: Bundle
16
16
  run: bundle install
17
17
 
18
- - name: Run Rubocop
19
- run: bundle exec rubocop -D
18
+ - name: Run standardrb
19
+ run: standardrb
20
20
 
21
21
  type-check:
22
22
  name: Type check
@@ -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
@@ -0,0 +1,11 @@
1
+ FROM ruby:3.4
2
+
3
+ RUN apt-get update && apt-get install -y valgrind --no-install-recommends --no-install-suggests
4
+
5
+ ADD . /libddwaf-rb
6
+
7
+ WORKDIR /libddwaf-rb
8
+
9
+ RUN bundle install
10
+
11
+ CMD /bin/bash
data/Steepfile CHANGED
@@ -13,9 +13,9 @@ target :lib do
13
13
  library "jruby"
14
14
  library "gem"
15
15
 
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
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
- 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!
17
+ def initialize(context_ptr)
18
+ @context_ptr = context_ptr
27
19
  end
28
20
 
29
- def finalize
30
- invalidate!
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(context_obj)
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
- valid!
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 LibDDWAF::Error, "Could not convert persistent data: #{persistent_data.inspect}"
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 LibDDWAF::Error, "Could not convert ephemeral data: #{ephemeral_data.inspect}"
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 LibDDWAF::Error, 'Could not create result object' if result_obj.null?
73
+ raise LibDDWAFError, "Could not create result object" if result_obj.null?
72
74
 
73
- code = LibDDWAF.ddwaf_run(@context_obj, persistent_data_obj, ephemeral_data_obj, result_obj, timeout)
75
+ code = LibDDWAF.ddwaf_run(@context_ptr, persistent_data_obj, ephemeral_data_obj, result_obj, timeout)
74
76
 
75
- result = Result.new(
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
- # 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?
92
+ def ensure_pointer_presence!
93
+ return if @context_ptr
108
94
 
109
- raise LibDDWAF::Error, "Attempt to use an invalid instance: #{inspect}"
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
- # rubocop:disable Metrics/MethodLength,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
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
- 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
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
- 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}"
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('utf-8', invalid: :replace, undef: :replace)
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
- 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}"
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
- 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}"
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
- 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}"
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
- 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}"
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
- # rubocop:enable Metrics/MethodLength,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
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
- 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)
9
+ def initialize(handle_ptr)
10
+ @handle_ptr = handle_ptr
11
+ end
36
12
 
37
- if @handle_obj.null?
38
- raise LibDDWAF::Error.new('Could not create handle', diagnostics: @diagnostics)
39
- end
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
- validate!
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
- def finalize
48
- invalidate!
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
- LibDDWAF.ddwaf_destroy(handle_obj)
33
+ Context.new(context_obj)
51
34
  end
52
35
 
53
- def required_addresses
54
- valid!
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(handle_obj, count)
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
- 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
48
+ # TODO: garbage collect the count?
76
49
  end
77
50
 
78
51
  private
79
52
 
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?
53
+ def ensure_pointer_presence!
54
+ return if @handle_ptr
102
55
 
103
- raise LibDDWAF::Error, "Attempt to use an invalid instance: #{inspect}"
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