libddwaf 1.3.0.0.0 → 1.5.1.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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)