libddwaf 1.3.0.0.0 → 1.5.1.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.
@@ -6,7 +6,13 @@ module Datadog
6
6
  module AppSec
7
7
  module WAF
8
8
  module LibDDWAF
9
- class Error < StandardError; end
9
+ class Error < StandardError
10
+ attr_reader :ruleset_info
11
+
12
+ def initialize(msg, ruleset_info: nil)
13
+ @ruleset_info = ruleset_info
14
+ end
15
+ end
10
16
 
11
17
  extend ::FFI::Library
12
18
 
@@ -26,12 +32,22 @@ module Datadog
26
32
  Gem::Platform.local.os
27
33
  end
28
34
 
35
+ def self.local_version
36
+ return nil unless local_os == 'linux'
37
+
38
+ # Old rubygems don't handle non-gnu linux correctly
39
+ return $1 if RUBY_PLATFORM =~ /linux-(.+)$/
40
+
41
+ 'gnu'
42
+ end
43
+
29
44
  def self.local_cpu
30
45
  if RUBY_ENGINE == 'jruby'
31
46
  os_arch = java.lang.System.get_property('os.arch')
32
47
 
33
48
  cpu = case os_arch
34
49
  when 'amd64' then 'x86_64'
50
+ when 'aarch64' then 'aarch64'
35
51
  else raise Error, "unsupported JRuby os.arch: #{os_arch.inspect}"
36
52
  end
37
53
 
@@ -41,27 +57,50 @@ module Datadog
41
57
  Gem::Platform.local.cpu
42
58
  end
43
59
 
60
+ def self.source_dir
61
+ __dir__ || raise('__dir__ is nil: eval?')
62
+ end
63
+
64
+ def self.vendor_dir
65
+ File.join(source_dir, '../../../vendor')
66
+ end
67
+
68
+ def self.libddwaf_vendor_dir
69
+ File.join(vendor_dir, 'libddwaf')
70
+ end
71
+
72
+ def self.shared_lib_triplet(version: local_version)
73
+ version ? "#{local_os}-#{version}-#{local_cpu}" : "#{local_os}-#{local_cpu}"
74
+ end
75
+
76
+ def self.libddwaf_dir
77
+ default = File.join(libddwaf_vendor_dir,
78
+ "libddwaf-#{Datadog::AppSec::WAF::VERSION::BASE_STRING}-#{shared_lib_triplet}")
79
+ candidates = [
80
+ default
81
+ ]
82
+
83
+ if local_os == 'linux'
84
+ candidates << File.join(libddwaf_vendor_dir,
85
+ "libddwaf-#{Datadog::AppSec::WAF::VERSION::BASE_STRING}-#{shared_lib_triplet(version: nil)}")
86
+ end
87
+
88
+ candidates.find { |d| Dir.exist?(d) } || default
89
+ end
90
+
44
91
  def self.shared_lib_extname
45
92
  Gem::Platform.local.os == 'darwin' ? '.dylib' : '.so'
46
93
  end
47
94
 
48
95
  def self.shared_lib_path
49
- File.join(__dir__, "../../../vendor/libddwaf/libddwaf-#{Datadog::AppSec::WAF::VERSION::BASE_STRING}-#{local_os}-#{local_cpu}/lib/libddwaf#{shared_lib_extname}")
96
+ File.join(libddwaf_dir, 'lib', "libddwaf#{shared_lib_extname}")
50
97
  end
51
98
 
52
99
  ffi_lib [shared_lib_path]
53
100
 
54
101
  # version
55
102
 
56
- class Version < ::FFI::Struct
57
- layout :major, :uint16,
58
- :minor, :uint16,
59
- :patch, :uint16
60
- end
61
-
62
- typedef Version.by_ref, :ddwaf_version
63
-
64
- attach_function :ddwaf_get_version, [:ddwaf_version], :void
103
+ attach_function :ddwaf_get_version, [], :string
65
104
 
66
105
  # ddwaf::object data structure
67
106
 
@@ -70,7 +109,9 @@ module Datadog
70
109
  :ddwaf_obj_unsigned, 1 << 1,
71
110
  :ddwaf_obj_string, 1 << 2,
72
111
  :ddwaf_obj_array, 1 << 3,
73
- :ddwaf_obj_map, 1 << 4
112
+ :ddwaf_obj_map, 1 << 4,
113
+ :ddwaf_obj_bool, 1 << 5
114
+ typedef DDWAF_OBJ_TYPE, :ddwaf_obj_type
74
115
 
75
116
  typedef :pointer, :charptr
76
117
  typedef :pointer, :charptrptr
@@ -97,7 +138,8 @@ module Datadog
97
138
  layout :stringValue, :charptr,
98
139
  :uintValue, :uint64,
99
140
  :intValue, :int64,
100
- :array, :pointer
141
+ :array, :pointer,
142
+ :boolean, :bool
101
143
  end
102
144
 
103
145
  class Object < ::FFI::Struct
@@ -105,7 +147,7 @@ module Datadog
105
147
  :parameterNameLength, :uint64,
106
148
  :valueUnion, ObjectValueUnion,
107
149
  :nbEntries, :uint64,
108
- :type, DDWAF_OBJ_TYPE
150
+ :type, :ddwaf_obj_type
109
151
  end
110
152
 
111
153
  typedef Object.by_ref, :ddwaf_object
@@ -120,6 +162,7 @@ module Datadog
120
162
  attach_function :ddwaf_object_signed, [:ddwaf_object, :int64], :ddwaf_object
121
163
  attach_function :ddwaf_object_unsigned_force, [:ddwaf_object, :uint64], :ddwaf_object
122
164
  attach_function :ddwaf_object_signed_force, [:ddwaf_object, :int64], :ddwaf_object
165
+ attach_function :ddwaf_object_bool, [:ddwaf_object, :bool], :ddwaf_object
123
166
 
124
167
  attach_function :ddwaf_object_array, [:ddwaf_object], :ddwaf_object
125
168
  attach_function :ddwaf_object_array_add, [:ddwaf_object, :ddwaf_object], :bool
@@ -150,6 +193,8 @@ module Datadog
150
193
  typedef :pointer, :ddwaf_handle
151
194
  typedef Object.by_ref, :ddwaf_rule
152
195
 
196
+ callback :ddwaf_object_free_fn, [:ddwaf_object], :void
197
+
153
198
  class Config < ::FFI::Struct
154
199
  class Limits < ::FFI::Struct
155
200
  layout :max_container_size, :uint32,
@@ -158,12 +203,13 @@ module Datadog
158
203
  end
159
204
 
160
205
  class Obfuscator < ::FFI::Struct
161
- layout :key_regex, :string,
162
- :value_regex, :string
206
+ layout :key_regex, :pointer, # should be :charptr
207
+ :value_regex, :pointer # should be :charptr
163
208
  end
164
209
 
165
210
  layout :limits, Limits,
166
- :obfuscator, Obfuscator
211
+ :obfuscator, Obfuscator,
212
+ :free_fn, :pointer #:ddwaf_object_free_fn
167
213
  end
168
214
 
169
215
  typedef Config.by_ref, :ddwaf_config
@@ -183,34 +229,44 @@ module Datadog
183
229
  attach_function :ddwaf_init, [:ddwaf_rule, :ddwaf_config, :ddwaf_ruleset_info], :ddwaf_handle
184
230
  attach_function :ddwaf_destroy, [:ddwaf_handle], :void
185
231
 
186
- attach_function :ddwaf_required_addresses, [:ddwaf_handle, :uint32ptr], :charptrptr
232
+ attach_function :ddwaf_required_addresses, [:ddwaf_handle, UInt32Ptr], :charptrptr
233
+ attach_function :ddwaf_required_rule_data_ids, [:ddwaf_handle, UInt32Ptr], :charptrptr
234
+
235
+ # updating
236
+
237
+ DDWAF_RET_CODE = enum :ddwaf_err_internal, -3,
238
+ :ddwaf_err_invalid_object, -2,
239
+ :ddwaf_err_invalid_argument, -1,
240
+ :ddwaf_ok, 0,
241
+ :ddwaf_match, 1
242
+ typedef DDWAF_RET_CODE, :ddwaf_ret_code
243
+
244
+ attach_function :ddwaf_update_rule_data, [:ddwaf_handle, :ddwaf_object], :ddwaf_ret_code
245
+ attach_function :ddwaf_toggle_rules, [:ddwaf_handle, :ddwaf_object], :ddwaf_ret_code
187
246
 
188
247
  # running
189
248
 
190
249
  typedef :pointer, :ddwaf_context
191
250
 
192
- callback :ddwaf_object_free_fn, [:ddwaf_object], :void
193
-
194
- attach_function :ddwaf_context_init, [:ddwaf_handle, :ddwaf_object_free_fn], :ddwaf_context
251
+ attach_function :ddwaf_context_init, [:ddwaf_handle], :ddwaf_context
195
252
  attach_function :ddwaf_context_destroy, [:ddwaf_context], :void
196
253
 
197
- DDWAF_RET_CODE = enum :ddwaf_err_internal, -3,
198
- :ddwaf_err_invalid_object, -2,
199
- :ddwaf_err_invalid_argument, -1,
200
- :ddwaf_good, 0,
201
- :ddwaf_monitor, 1,
202
- :ddwaf_block, 2
254
+ class ResultActions < ::FFI::Struct
255
+ layout :array, :charptrptr,
256
+ :size, :uint32
257
+ end
203
258
 
204
259
  class Result < ::FFI::Struct
205
260
  layout :timeout, :bool,
206
261
  :data, :string,
262
+ :actions, ResultActions,
207
263
  :total_runtime, :uint64
208
264
  end
209
265
 
210
266
  typedef Result.by_ref, :ddwaf_result
211
267
  typedef :uint64, :timeout_us
212
268
 
213
- attach_function :ddwaf_run, [:ddwaf_context, :ddwaf_object, :ddwaf_result, :timeout_us], DDWAF_RET_CODE, blocking: true
269
+ attach_function :ddwaf_run, [:ddwaf_context, :ddwaf_object, :ddwaf_result, :timeout_us], :ddwaf_ret_code, blocking: true
214
270
  attach_function :ddwaf_result_free, [:ddwaf_result], :void
215
271
 
216
272
  # logging
@@ -221,20 +277,28 @@ module Datadog
221
277
  :ddwaf_log_warn,
222
278
  :ddwaf_log_error,
223
279
  :ddwaf_log_off
280
+ typedef DDWAF_LOG_LEVEL, :ddwaf_log_level
281
+
282
+ callback :ddwaf_log_cb, [:ddwaf_log_level, :string, :string, :uint, :charptr, :uint64], :void
283
+
284
+ attach_function :ddwaf_set_log_cb, [:ddwaf_log_cb, :ddwaf_log_level], :bool
285
+
286
+ DEFAULT_MAX_CONTAINER_SIZE = 0
287
+ DEFAULT_MAX_CONTAINER_DEPTH = 0
288
+ DEFAULT_MAX_STRING_LENGTH = 0
224
289
 
225
- callback :ddwaf_log_cb, [DDWAF_LOG_LEVEL, :string, :string, :uint, :charptr, :uint64], :void
290
+ DDWAF_MAX_CONTAINER_SIZE = 256
291
+ DDWAF_MAX_CONTAINER_DEPTH = 20
292
+ DDWAF_MAX_STRING_LENGTH = 4096
226
293
 
227
- attach_function :ddwaf_set_log_cb, [:ddwaf_log_cb, DDWAF_LOG_LEVEL], :bool
294
+ DDWAF_RUN_TIMEOUT = 5000
228
295
  end
229
296
 
230
297
  def self.version
231
- version = LibDDWAF::Version.new
232
- LibDDWAF.ddwaf_get_version(version.pointer)
233
-
234
- [version[:major], version[:minor], version[:patch]]
298
+ LibDDWAF.ddwaf_get_version
235
299
  end
236
300
 
237
- def self.ruby_to_object(val)
301
+ def self.ruby_to_object(val, max_container_size: nil, max_container_depth: nil, max_string_length: nil, coerce: true)
238
302
  case val
239
303
  when Array
240
304
  obj = LibDDWAF::Object.new
@@ -243,12 +307,20 @@ module Datadog
243
307
  fail LibDDWAF::Error, "Could not convert into object: #{val}"
244
308
  end
245
309
 
246
- val.each do |e|
247
- res = LibDDWAF.ddwaf_object_array_add(obj, ruby_to_object(e))
248
- unless res
249
- fail LibDDWAF::Error, "Could not add to map object: #{k.inspect} => #{v.inspect}"
310
+ max_index = max_container_size - 1 if max_container_size
311
+ val.each.with_index do |e, i|
312
+ member = ruby_to_object(e,
313
+ max_container_size: max_container_size,
314
+ max_container_depth: (max_container_depth - 1 if max_container_depth),
315
+ max_string_length: max_string_length,
316
+ coerce: coerce)
317
+ e_res = LibDDWAF.ddwaf_object_array_add(obj, member)
318
+ unless e_res
319
+ fail LibDDWAF::Error, "Could not add to array object: #{e.inspect}"
250
320
  end
251
- end
321
+
322
+ break val if max_index && i >= max_index
323
+ end unless max_container_depth == 0
252
324
 
253
325
  obj
254
326
  when Hash
@@ -258,35 +330,56 @@ module Datadog
258
330
  fail LibDDWAF::Error, "Could not convert into object: #{val}"
259
331
  end
260
332
 
261
- val.each do |k, v|
262
- res = LibDDWAF.ddwaf_object_map_addl(obj, k.to_s, k.to_s.size, ruby_to_object(v))
263
- unless res
333
+ max_index = max_container_size - 1 if max_container_size
334
+ val.each.with_index do |e, i|
335
+ k, v = e[0], e[1] # for Steep, which doesn't handle |(k, v), i|
336
+
337
+ k = k.to_s[0, max_string_length] if max_string_length
338
+ member = ruby_to_object(v,
339
+ max_container_size: max_container_size,
340
+ max_container_depth: (max_container_depth - 1 if max_container_depth),
341
+ max_string_length: max_string_length,
342
+ coerce: coerce)
343
+ kv_res = LibDDWAF.ddwaf_object_map_addl(obj, k.to_s, k.to_s.bytesize, member)
344
+ unless kv_res
264
345
  fail LibDDWAF::Error, "Could not add to map object: #{k.inspect} => #{v.inspect}"
265
346
  end
266
- end
347
+
348
+ break val if max_index && i >= max_index
349
+ end unless max_container_depth == 0
267
350
 
268
351
  obj
269
352
  when String
270
353
  obj = LibDDWAF::Object.new
271
- res = LibDDWAF.ddwaf_object_stringl(obj, val, val.size)
354
+ val = val.to_s[0, max_string_length] if max_string_length
355
+ str = val.to_s
356
+ res = LibDDWAF.ddwaf_object_stringl(obj, str, str.bytesize)
272
357
  if res.null?
273
- fail LibDDWAF::Error, "Could not convert into object: #{val}"
358
+ fail LibDDWAF::Error, "Could not convert into object: #{val.inspect}"
274
359
  end
275
360
 
276
361
  obj
277
362
  when Symbol
278
363
  obj = LibDDWAF::Object.new
279
- res = LibDDWAF.ddwaf_object_stringl(obj, val.to_s, val.size)
364
+ val = val.to_s[0, max_string_length] if max_string_length
365
+ str = val.to_s
366
+ res = LibDDWAF.ddwaf_object_stringl(obj, str, str.bytesize)
280
367
  if res.null?
281
- fail LibDDWAF::Error, "Could not convert into object: #{val}"
368
+ fail LibDDWAF::Error, "Could not convert into object: #{val.inspect}"
282
369
  end
283
370
 
284
371
  obj
285
372
  when Integer
286
373
  obj = LibDDWAF::Object.new
287
- res = LibDDWAF.ddwaf_object_string(obj, val.to_s)
374
+ res = if coerce
375
+ LibDDWAF.ddwaf_object_string(obj, val.to_s)
376
+ elsif val < 0
377
+ LibDDWAF.ddwaf_object_signed_force(obj, val)
378
+ else
379
+ LibDDWAF.ddwaf_object_unsigned_force(obj, val)
380
+ end
288
381
  if res.null?
289
- fail LibDDWAF::Error, "Could not convert into object: #{val}"
382
+ fail LibDDWAF::Error, "Could not convert into object: #{val.inspect}"
290
383
  end
291
384
 
292
385
  obj
@@ -294,15 +387,19 @@ module Datadog
294
387
  obj = LibDDWAF::Object.new
295
388
  res = LibDDWAF.ddwaf_object_string(obj, val.to_s)
296
389
  if res.null?
297
- fail LibDDWAF::Error, "Could not convert into object: #{val}"
390
+ fail LibDDWAF::Error, "Could not convert into object: #{val.inspect}"
298
391
  end
299
392
 
300
393
  obj
301
394
  when TrueClass, FalseClass
302
395
  obj = LibDDWAF::Object.new
303
- res = LibDDWAF.ddwaf_object_string(obj, val.to_s)
396
+ res = if coerce
397
+ LibDDWAF.ddwaf_object_string(obj, val.to_s)
398
+ else
399
+ LibDDWAF.ddwaf_object_bool(obj, val)
400
+ end
304
401
  if res.null?
305
- fail LibDDWAF::Error, "Could not convert into object: #{val}"
402
+ fail LibDDWAF::Error, "Could not convert into object: #{val.inspect}"
306
403
  end
307
404
 
308
405
  obj
@@ -315,6 +412,8 @@ module Datadog
315
412
  case obj[:type]
316
413
  when :ddwaf_obj_invalid
317
414
  nil
415
+ when :ddwaf_obj_bool
416
+ obj[:valueUnion][:boolean]
318
417
  when :ddwaf_obj_string
319
418
  obj[:valueUnion][:stringValue].read_bytes(obj[:nbEntries])
320
419
  when :ddwaf_obj_signed
@@ -339,22 +438,50 @@ module Datadog
339
438
  end
340
439
  end
341
440
 
441
+ def self.log_callback(level, func, file, line, message, len)
442
+ return if logger.nil?
443
+
444
+ logger.debug do
445
+ {
446
+ level: level,
447
+ func: func,
448
+ file: file,
449
+ line: line,
450
+ message: message.read_bytes(len)
451
+ }.inspect
452
+ end
453
+ end
454
+
455
+ def self.logger
456
+ @logger
457
+ end
458
+
342
459
  def self.logger=(logger)
343
- @log_cb = proc do |level, func, file, line, message, len|
344
- logger.debug { { level: level, func: func, file: file, line: line, message: message.read_bytes(len) }.inspect }
460
+ unless @log_callback
461
+ log_callback = method(:log_callback)
462
+ Datadog::AppSec::WAF::LibDDWAF.ddwaf_set_log_cb(log_callback, :ddwaf_log_trace)
463
+
464
+ # retain logging proc if set properly
465
+ @log_callback = log_callback
345
466
  end
346
467
 
347
- Datadog::AppSec::WAF::LibDDWAF.ddwaf_set_log_cb(@log_cb, :ddwaf_log_trace)
468
+ @logger = logger
348
469
  end
349
470
 
471
+ RESULT_CODE = {
472
+ ddwaf_err_internal: :err_internal,
473
+ ddwaf_err_invalid_object: :err_invalid_object,
474
+ ddwaf_err_invalid_argument: :err_invalid_argument,
475
+ ddwaf_ok: :ok,
476
+ ddwaf_match: :match,
477
+ }
478
+
350
479
  class Handle
351
480
  attr_reader :handle_obj
352
481
 
353
- DEFAULT_MAX_CONTAINER_SIZE = 0
354
- DEFAULT_MAX_CONTAINER_DEPTH = 0
355
- DEFAULT_MAX_STRING_LENGTH = 0
482
+ attr_reader :ruleset_info
356
483
 
357
- def initialize(rule, config = {})
484
+ def initialize(rule, limits: {}, obfuscator: {})
358
485
  rule_obj = Datadog::AppSec::WAF.ruby_to_object(rule)
359
486
  if rule_obj.null? || rule_obj[:type] == :ddwaf_object_invalid
360
487
  fail LibDDWAF::Error, "Could not convert object #{rule.inspect}"
@@ -364,71 +491,154 @@ module Datadog
364
491
  if config_obj.null?
365
492
  fail LibDDWAF::Error, 'Could not create config struct'
366
493
  end
494
+ retain(config_obj)
367
495
 
368
- config_obj[:limits][:max_container_size] = config[:max_container_size] || DEFAULT_MAX_CONTAINER_SIZE
369
- config_obj[:limits][:max_container_depth] = config[:max_container_depth] || DEFAULT_MAX_CONTAINER_DEPTH
370
- config_obj[:limits][:max_string_length] = config[:max_string_length] || DEFAULT_MAX_STRING_LENGTH
496
+ config_obj[:limits][:max_container_size] = limits[:max_container_size] || LibDDWAF::DEFAULT_MAX_CONTAINER_SIZE
497
+ config_obj[:limits][:max_container_depth] = limits[:max_container_depth] || LibDDWAF::DEFAULT_MAX_CONTAINER_DEPTH
498
+ config_obj[:limits][:max_string_length] = limits[:max_string_length] || LibDDWAF::DEFAULT_MAX_STRING_LENGTH
499
+ config_obj[:obfuscator][:key_regex] = FFI::MemoryPointer.from_string(obfuscator[:key_regex]) if obfuscator[:key_regex]
500
+ config_obj[:obfuscator][:value_regex] = FFI::MemoryPointer.from_string(obfuscator[:value_regex]) if obfuscator[:value_regex]
501
+ config_obj[:free_fn] = Datadog::AppSec::WAF::LibDDWAF::ObjectNoFree
371
502
 
372
- ruleset_info = LibDDWAF::RuleSetInfoNone
503
+ ruleset_info = LibDDWAF::RuleSetInfo.new
373
504
 
374
505
  @handle_obj = Datadog::AppSec::WAF::LibDDWAF.ddwaf_init(rule_obj, config_obj, ruleset_info)
506
+
507
+ @ruleset_info = {
508
+ loaded: ruleset_info[:loaded],
509
+ failed: ruleset_info[:failed],
510
+ errors: WAF.object_to_ruby(ruleset_info[:errors]),
511
+ version: ruleset_info[:version],
512
+ }
513
+
375
514
  if @handle_obj.null?
376
- fail LibDDWAF::Error, 'Could not create handle'
515
+ fail LibDDWAF::Error.new('Could not create handle', ruleset_info: @ruleset_info)
377
516
  end
378
517
 
379
- ObjectSpace.define_finalizer(self, Handle.finalizer(handle_obj))
518
+ validate!
380
519
  ensure
381
520
  Datadog::AppSec::WAF::LibDDWAF.ddwaf_ruleset_info_free(ruleset_info) if ruleset_info
382
521
  Datadog::AppSec::WAF::LibDDWAF.ddwaf_object_free(rule_obj) if rule_obj
383
522
  end
384
523
 
385
- def self.finalizer(handle_obj)
386
- proc do |object_id|
387
- Datadog::AppSec::WAF::LibDDWAF.ddwaf_destroy(handle_obj)
388
- end
524
+ def finalize
525
+ invalidate!
526
+
527
+ Datadog::AppSec::WAF::LibDDWAF.ddwaf_destroy(handle_obj)
528
+ end
529
+
530
+ def required_addresses
531
+ valid!
532
+
533
+ count = Datadog::AppSec::WAF::LibDDWAF::UInt32Ptr.new
534
+ list = Datadog::AppSec::WAF::LibDDWAF.ddwaf_required_addresses(handle_obj, count)
535
+
536
+ return [] if count == 0 # list is null
537
+
538
+ list.get_array_of_string(0, count[:value])
539
+ end
540
+
541
+ def update_rule_data(data)
542
+ data_obj = Datadog::AppSec::WAF.ruby_to_object(data, coerce: false)
543
+ res = Datadog::AppSec::WAF::LibDDWAF.ddwaf_update_rule_data(@handle_obj, data_obj)
544
+
545
+ RESULT_CODE[res]
546
+ ensure
547
+ Datadog::AppSec::WAF::LibDDWAF.ddwaf_object_free(data_obj) if data_obj
548
+ end
549
+
550
+ def toggle_rules(map)
551
+ map_obj = Datadog::AppSec::WAF.ruby_to_object(map, coerce: false)
552
+ res = Datadog::AppSec::WAF::LibDDWAF.ddwaf_toggle_rules(@handle_obj, map_obj)
553
+
554
+ RESULT_CODE[res]
555
+ ensure
556
+ Datadog::AppSec::WAF::LibDDWAF.ddwaf_object_free(map_obj) if map_obj
557
+ end
558
+
559
+ private
560
+
561
+ def validate!
562
+ @valid = true
563
+ end
564
+
565
+ def invalidate!
566
+ @valid = false
567
+ end
568
+
569
+ def valid?
570
+ @valid
571
+ end
572
+
573
+ def valid!
574
+ return if valid?
575
+
576
+ fail LibDDWAF::Error, "Attempt to use an invalid instance: #{inspect}"
577
+ end
578
+
579
+ def retained
580
+ @retained ||= []
581
+ end
582
+
583
+ def retain(object)
584
+ retained << object
585
+ end
586
+
587
+ def release(object)
588
+ retained.delete(object)
389
589
  end
390
590
  end
391
591
 
392
- Result = Struct.new(:action, :data, :total_runtime, :timeout)
592
+ class Result
593
+ attr_reader :status, :data, :total_runtime, :timeout, :actions
594
+
595
+ def initialize(status, data, total_runtime, timeout, actions)
596
+ @status = status
597
+ @data = data
598
+ @total_runtime = total_runtime
599
+ @timeout = timeout
600
+ @actions = actions
601
+ end
602
+ end
393
603
 
394
604
  class Context
395
605
  attr_reader :context_obj
396
606
 
397
607
  def initialize(handle)
398
608
  handle_obj = handle.handle_obj
399
- free_func = Datadog::AppSec::WAF::LibDDWAF::ObjectNoFree
609
+ retain(handle)
400
610
 
401
- @context_obj = Datadog::AppSec::WAF::LibDDWAF.ddwaf_context_init(handle_obj, free_func)
611
+ @context_obj = Datadog::AppSec::WAF::LibDDWAF.ddwaf_context_init(handle_obj)
402
612
  if @context_obj.null?
403
613
  fail LibDDWAF::Error, 'Could not create context'
404
614
  end
405
615
 
406
- @input_objs = []
407
-
408
- ObjectSpace.define_finalizer(self, Context.finalizer(context_obj, @input_objs))
616
+ validate!
409
617
  end
410
618
 
411
- def self.finalizer(context_obj, input_objs)
412
- proc do |object_id|
413
- input_objs.each do |input_obj|
414
- Datadog::AppSec::WAF::LibDDWAF.ddwaf_object_free(input_obj)
415
- end
416
- Datadog::AppSec::WAF::LibDDWAF.ddwaf_context_destroy(context_obj)
619
+ def finalize
620
+ invalidate!
621
+
622
+ retained.each do |retained_obj|
623
+ next unless retained_obj.is_a?(Datadog::AppSec::WAF::LibDDWAF::Object)
624
+
625
+ Datadog::AppSec::WAF::LibDDWAF.ddwaf_object_free(retained_obj)
417
626
  end
627
+
628
+ Datadog::AppSec::WAF::LibDDWAF.ddwaf_context_destroy(context_obj)
418
629
  end
419
630
 
420
- DEFAULT_TIMEOUT_US = 10_0000
421
- ACTION_MAP_OUT = {
422
- ddwaf_err_internal: :err_internal,
423
- ddwaf_err_invalid_object: :err_invalid_object,
424
- ddwaf_err_invalid_argument: :err_invalid_argument,
425
- ddwaf_good: :good,
426
- ddwaf_monitor: :monitor,
427
- ddwaf_block: :block,
428
- }
631
+ def run(input, timeout = LibDDWAF::DDWAF_RUN_TIMEOUT)
632
+ valid!
633
+
634
+ max_container_size = LibDDWAF::DDWAF_MAX_CONTAINER_SIZE
635
+ max_container_depth = LibDDWAF::DDWAF_MAX_CONTAINER_DEPTH
636
+ max_string_length = LibDDWAF::DDWAF_MAX_STRING_LENGTH
429
637
 
430
- def run(input, timeout = DEFAULT_TIMEOUT_US)
431
- input_obj = Datadog::AppSec::WAF.ruby_to_object(input)
638
+ input_obj = Datadog::AppSec::WAF.ruby_to_object(input,
639
+ max_container_size: max_container_size,
640
+ max_container_depth: max_container_depth,
641
+ max_string_length: max_string_length)
432
642
  if input_obj.null?
433
643
  fail LibDDWAF::Error, "Could not convert input: #{input.inspect}"
434
644
  end
@@ -439,21 +649,60 @@ module Datadog
439
649
  end
440
650
 
441
651
  # retain C objects in memory for subsequent calls to run
442
- @input_objs << input_obj
652
+ retain(input_obj)
443
653
 
444
654
  code = Datadog::AppSec::WAF::LibDDWAF.ddwaf_run(@context_obj, input_obj, result_obj, timeout)
445
655
 
656
+ actions = if result_obj[:actions][:size] > 0
657
+ result_obj[:actions][:array].get_array_of_string(0, result_obj[:actions][:size])
658
+ else
659
+ []
660
+ end
661
+
446
662
  result = Result.new(
447
- ACTION_MAP_OUT[code],
663
+ RESULT_CODE[code],
448
664
  (JSON.parse(result_obj[:data]) if result_obj[:data] != nil),
449
665
  result_obj[:total_runtime],
450
666
  result_obj[:timeout],
667
+ actions,
451
668
  )
452
669
 
453
- [ACTION_MAP_OUT[code], result]
670
+ [RESULT_CODE[code], result]
454
671
  ensure
455
672
  Datadog::AppSec::WAF::LibDDWAF.ddwaf_result_free(result_obj) if result_obj
456
673
  end
674
+
675
+ private
676
+
677
+ def validate!
678
+ @valid = true
679
+ end
680
+
681
+ def invalidate!
682
+ @valid = false
683
+ end
684
+
685
+ def valid?
686
+ @valid
687
+ end
688
+
689
+ def valid!
690
+ return if valid?
691
+
692
+ fail LibDDWAF::Error, "Attempt to use an invalid instance: #{inspect}"
693
+ end
694
+
695
+ def retained
696
+ @retained ||= []
697
+ end
698
+
699
+ def retain(object)
700
+ retained << object
701
+ end
702
+
703
+ def release(object)
704
+ retained.delete(object)
705
+ end
457
706
  end
458
707
  end
459
708
  end
data/libddwaf.gemspec CHANGED
@@ -17,7 +17,7 @@ Gem::Specification.new do |spec|
17
17
  libddwaf packages a WAF implementation in C++, exposed to Ruby
18
18
  EOS
19
19
 
20
- spec.homepage = 'https://github.com/DataDog/libddwaf'
20
+ spec.homepage = 'https://github.com/DataDog/libddwaf-rb'
21
21
  spec.license = 'BSD-3-Clause'
22
22
 
23
23
  if spec.respond_to?(:metadata)