sqreen 1.11.0 → 1.11.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/lib/sqreen.rb +0 -1
  3. data/lib/sqreen/callback_tree.rb +38 -20
  4. data/lib/sqreen/callbacks.rb +6 -4
  5. data/lib/sqreen/conditionable.rb +3 -3
  6. data/lib/sqreen/frameworks/generic.rb +38 -18
  7. data/lib/sqreen/frameworks/request_recorder.rb +0 -2
  8. data/lib/sqreen/instrumentation.rb +217 -160
  9. data/lib/sqreen/performance_notifications.rb +10 -36
  10. data/lib/sqreen/performance_notifications/log.rb +1 -2
  11. data/lib/sqreen/performance_notifications/log_performance.rb +1 -2
  12. data/lib/sqreen/performance_notifications/metrics.rb +1 -2
  13. data/lib/sqreen/performance_notifications/newrelic.rb +1 -2
  14. data/lib/sqreen/remote_command.rb +7 -7
  15. data/lib/sqreen/rule_callback.rb +4 -0
  16. data/lib/sqreen/rules.rb +2 -2
  17. data/lib/sqreen/rules_callbacks/binding_accessor_matcher.rb +7 -1
  18. data/lib/sqreen/rules_callbacks/binding_accessor_metrics.rb +3 -3
  19. data/lib/sqreen/rules_callbacks/blacklist_ips.rb +1 -1
  20. data/lib/sqreen/rules_callbacks/count_http_codes.rb +7 -6
  21. data/lib/sqreen/rules_callbacks/crawler_user_agent_matches.rb +1 -1
  22. data/lib/sqreen/rules_callbacks/crawler_user_agent_matches_metrics.rb +1 -1
  23. data/lib/sqreen/rules_callbacks/custom_error.rb +2 -5
  24. data/lib/sqreen/rules_callbacks/execjs.rb +76 -37
  25. data/lib/sqreen/rules_callbacks/headers_insert.rb +1 -1
  26. data/lib/sqreen/rules_callbacks/inspect_rule.rb +3 -3
  27. data/lib/sqreen/rules_callbacks/matcher_rule.rb +18 -17
  28. data/lib/sqreen/rules_callbacks/rails_parameters.rb +1 -1
  29. data/lib/sqreen/rules_callbacks/record_request_context.rb +9 -8
  30. data/lib/sqreen/rules_callbacks/reflected_xss.rb +40 -34
  31. data/lib/sqreen/rules_callbacks/regexp_rule.rb +13 -4
  32. data/lib/sqreen/rules_callbacks/shell_env.rb +2 -2
  33. data/lib/sqreen/rules_callbacks/url_matches.rb +1 -1
  34. data/lib/sqreen/rules_callbacks/user_agent_matches.rb +1 -1
  35. data/lib/sqreen/session.rb +1 -1
  36. data/lib/sqreen/shared_storage.rb +16 -12
  37. data/lib/sqreen/{stats.rb → shared_storage23.rb} +3 -11
  38. data/lib/sqreen/version.rb +1 -1
  39. metadata +4 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8a04c8884aa63b248a08d85df2a8c88c2afaed0ae3ab1c09ee60225eac9c448f
4
- data.tar.gz: caa7915935e6f34758261a63da938b77ea13d29cbd8b6a676b51593fba39dc77
3
+ metadata.gz: eee0cd857bae436a452af37bf21237fe240d24dd255f991586df7c925158ceb9
4
+ data.tar.gz: 310700ff34c420244c49873f3b6ccd38b3c8141fbefba8ce2ba268876b2dc982
5
5
  SHA512:
6
- metadata.gz: 3d49fd81fce6e403e6a8964ce2d49c2d5c261335f77f73b36113a4fda8b938807bf64757ae5b3a33fdf80f7e4a0c027500c842883e16300ee37c600b2a0a97b9
7
- data.tar.gz: 471ac71351a3a6fbfd326fb0a38296656532c57f19ea8c91347207284bc2867cf55da1bd90285d3cd3954a8be53f43040a9d7e46432447e7d0430e6a42bdb5e5
6
+ metadata.gz: 3e270c262f3583202ba31f4a58bbe06d292123903581a90b39bba351f434f81c4fd32c723e9abad6853cedb0d4a727409118828ddc037c92e9267a9a475a76d2
7
+ data.tar.gz: 69a4b0158995ef8029d72c635e0cccd5781be910468ee77e65016a73038727066eb2c0a465cbb9c7869238bbc7cd5ebbaff1b94a107b15fe9e512e77684f8485
@@ -7,7 +7,6 @@ require 'sqreen/runner'
7
7
  require 'sqreen/callbacks'
8
8
  require 'sqreen/version'
9
9
  require 'sqreen/log'
10
- require 'sqreen/stats'
11
10
  require 'sqreen/exception'
12
11
  require 'sqreen/configuration'
13
12
  require 'sqreen/events/attack'
@@ -19,50 +19,68 @@ module Sqreen
19
19
  @by_class[cb.klass] = {} unless @by_class[cb.klass]
20
20
 
21
21
  cb_klass = @by_class[cb.klass]
22
- unless cb_klass[cb.method]
23
- cb_klass[cb.method] = { :pre => [], :post => [], :failing => [] }
24
- end
22
+ cb_klass[cb.method] ||= [nil, nil, nil]
25
23
 
26
24
  methods = cb_klass[cb.method]
27
25
 
28
- methods[:pre] << cb if cb.pre?
29
- methods[:post] << cb if cb.post?
30
- methods[:failing] << cb if cb.failing?
26
+ if cb.pre?
27
+ methods[0] ||= []
28
+ methods[0] << cb
29
+ end
30
+ if cb.post?
31
+ methods[1] ||= []
32
+ methods[1] << cb
33
+ end
34
+ if cb.failing?
35
+ methods[2] ||= []
36
+ methods[2] << cb
37
+ end
31
38
  end
32
39
 
33
40
  def remove(cb)
34
- types = @by_class[cb.klass][cb.method]
41
+ by_meth = @by_class[cb.klass]
42
+ return unless by_meth
43
+ types = by_meth[cb.method]
35
44
 
36
- types[:pre].delete cb if cb.pre?
37
- types[:post].delete cb if cb.post?
38
- types[:failing].delete cb if cb.failing?
45
+ if cb.pre? && types[0]
46
+ types[0].delete(cb)
47
+ types[0] = nil if types[0].empty?
48
+ end
49
+ if cb.post? && types[1]
50
+ types[1].delete(cb)
51
+ types[1] = nil if types[1].empty?
52
+ end
53
+ if cb.failing? && types[2]
54
+ types[2].delete(cb)
55
+ types[2] = nil if types[2].empty?
56
+ end
39
57
  end
40
58
 
41
- def get(klass, method, type = nil)
59
+ def get(klass, method)
42
60
  k = @by_class[klass]
43
61
  unless k
44
62
  Sqreen.log.debug { format('Error: no cb registered for class %s (%s)', klass.inspect, klass.class) }
45
63
  Sqreen.log.debug { inspect }
46
- return []
64
+ return [nil, nil, nil]
47
65
  end
48
66
  cbs = k[method]
49
67
  unless cbs
50
68
  Sqreen.log.debug { format('Error: no cbs registered for method %s.%s', klass, method) }
51
69
  Sqreen.log.debug { inspect }
52
- return []
70
+ return [nil, nil, nil]
53
71
  end
54
-
55
- return cbs[type] unless type.nil?
56
-
57
- res = Set.new
58
- cbs.each_value { |v| res += v }
59
- res.to_a
72
+ cbs
73
+ rescue StandardError => e
74
+ Sqreen.log.warn "we catched an exception getting cbs: #{e.inspect}"
75
+ Sqreen::RemoteException.record(e)
76
+ [nil, nil, nil]
60
77
  end
61
78
 
62
79
  def each
63
80
  @by_class.each_value do |values|
64
81
  values.each_value do |cbs|
65
- cbs.each_value do |cb_ary|
82
+ cbs.each do |cb_ary|
83
+ next unless cb_ary
66
84
  cb_ary.each do |cb|
67
85
  yield cb
68
86
  end
@@ -37,6 +37,7 @@ module Sqreen
37
37
  # CB can also declare that they are whitelisted and should not be run at the moment.
38
38
 
39
39
  attr_reader :klass, :method
40
+ attr_reader :overtimeable
40
41
 
41
42
  def initialize(klass, method)
42
43
  @method = method
@@ -45,6 +46,7 @@ module Sqreen
45
46
  @has_pre = respond_to? :pre
46
47
  @has_post = respond_to? :post
47
48
  @has_failing = respond_to? :failing
49
+ @overtimeable = false
48
50
 
49
51
  raise(Sqreen::Exception, 'No callback provided') unless @has_pre || @has_post || @has_failing
50
52
  end
@@ -71,7 +73,7 @@ module Sqreen
71
73
 
72
74
  def overtime!
73
75
  Sqreen.log.debug { "#{self} is overtime!" }
74
- false
76
+ @overtimeable
75
77
  end
76
78
 
77
79
  def framework
@@ -81,13 +83,13 @@ module Sqreen
81
83
  # target_method, position, callback, callback class
82
84
 
83
85
  class DefaultCB < CB
84
- def pre(_inst, *args, &_block)
86
+ def pre(_inst, args, _budget = nil, &_block)
85
87
  Sqreen.log.debug "<< #{@klass} #{@method} #{Thread.current}"
86
88
  Sqreen.log.debug args.join ' '
87
89
  # log params
88
90
  end
89
91
 
90
- def post(_rv, _inst, *_args, &_block)
92
+ def post(_rv, _inst, _args, _budget = nil, &_block)
91
93
  # log "#{rv}"
92
94
  Sqreen.log.debug ">> #{@klass} #{@method} #{Thread.current}"
93
95
  end
@@ -101,7 +103,7 @@ module Sqreen
101
103
  @block = block
102
104
  end
103
105
 
104
- def pre(_inst, *_args, &_block)
106
+ def pre(_inst, _args, _budget = nil, &_block)
105
107
  # FIXME: implement this removal
106
108
  @remove_me = true
107
109
  @block.call
@@ -26,19 +26,19 @@ module Sqreen
26
26
  end
27
27
 
28
28
  def pre_with_conditions(inst, *args, &block)
29
- eargs = [binding, framework, inst, args, @data, nil]
29
+ eargs = [nil, framework, inst, args, @data, nil]
30
30
  return nil if !pre_conditions.nil? && !pre_conditions.evaluate(*eargs)
31
31
  pre_without_conditions(inst, *args, &block)
32
32
  end
33
33
 
34
34
  def post_with_conditions(rv, inst, *args, &block)
35
- eargs = [binding, framework, inst, args, @data, rv]
35
+ eargs = [nil, framework, inst, args, @data, rv]
36
36
  return nil if !post_conditions.nil? && !post_conditions.evaluate(*eargs)
37
37
  post_without_conditions(rv, inst, *args, &block)
38
38
  end
39
39
 
40
40
  def failing_with_conditions(rv, inst, *args, &block)
41
- eargs = [binding, framework, inst, args, @data, rv]
41
+ eargs = [nil, framework, inst, args, @data, rv]
42
42
  return nil if !failing_conditions.nil? && !failing_conditions.evaluate(*eargs)
43
43
  failing_without_conditions(rv, inst, *args, &block)
44
44
  end
@@ -234,6 +234,7 @@ module Sqreen
234
234
  SharedStorage.set(:request, Rack::Request.new(object))
235
235
  SharedStorage.inc(:stored_requests)
236
236
  SharedStorage.set(:xss_params, nil)
237
+ SharedStorage.set(:whitelisted, nil)
237
238
  SharedStorage.set(:request_overtime, nil)
238
239
  end
239
240
 
@@ -250,6 +251,7 @@ module Sqreen
250
251
  self.remaining_perf_budget = nil
251
252
  SharedStorage.set(:request, nil)
252
253
  SharedStorage.set(:xss_params, nil)
254
+ SharedStorage.set(:whitelisted, nil)
253
255
  SharedStorage.set(:request_overtime, nil)
254
256
  end
255
257
 
@@ -338,14 +340,13 @@ module Sqreen
338
340
  Dir.getwd
339
341
  end
340
342
 
341
- WHITELIST_KEY = 'sqreen.whitelisted_request'.freeze
342
-
343
343
  # Return the current item that whitelist this request
344
344
  # returns nil if request is not whitelisted
345
345
  def whitelisted_match
346
346
  return nil unless request
347
- return request.env[WHITELIST_KEY] if request.env.key?(WHITELIST_KEY)
348
- request.env[WHITELIST_KEY] = whitelisted_ip || whitelisted_path
347
+ whitelisted = whitelisted_ip || whitelisted_path
348
+ SharedStorage.set(:whitelisted, !whitelisted.nil?)
349
+ whitelisted
349
350
  end
350
351
 
351
352
  # Returns the current path that whitelist the request
@@ -369,21 +370,40 @@ module Sqreen
369
370
  request.env['REMOTE_ADDR']
370
371
  end
371
372
 
372
- def xss_params(regexp = nil)
373
- p = SharedStorage.get(:xss_params)
374
- return p unless p.nil?
375
- p = request_params
376
- parm = Set.new
377
- each_key_value_for_hash(p) do |value|
378
- next unless value.is_a?(String)
379
- next if value.size < 5
380
- next if regexp && !regexp.match(value)
381
- parm << value
373
+ if Regexp.new('').respond_to?(:match?)
374
+ def xss_params(regexp = nil)
375
+ p = SharedStorage.get(:xss_params)
376
+ return p unless p.nil?
377
+ p = request_params
378
+ parm = Set.new
379
+ each_key_value_for_hash(p) do |value|
380
+ next unless value.is_a?(String)
381
+ next if value.size < 5
382
+ next if regexp && !regexp.match?(value)
383
+ parm << value
384
+ end
385
+ p = parm.to_a
386
+ Sqreen.log.debug { "Filtered XSS params: #{p.inspect}" }
387
+ SharedStorage.set(:xss_params, p)
388
+ p
389
+ end
390
+ else
391
+ def xss_params(regexp = nil)
392
+ p = SharedStorage.get(:xss_params)
393
+ return p unless p.nil?
394
+ p = request_params
395
+ parm = Set.new
396
+ each_key_value_for_hash(p) do |value|
397
+ next unless value.is_a?(String)
398
+ next if value.size < 5
399
+ next if regexp && !regexp.match(value)
400
+ parm << value
401
+ end
402
+ p = parm.to_a
403
+ Sqreen.log.debug { "Filtered XSS params: #{p.inspect}" }
404
+ SharedStorage.set(:xss_params, p)
405
+ p
382
406
  end
383
- p = parm.to_a
384
- Sqreen.log.debug { "Filtered XSS params: #{p.inspect}" }
385
- SharedStorage.set(:xss_params, p)
386
- p
387
407
  end
388
408
 
389
409
  protected
@@ -47,8 +47,6 @@ module Sqreen
47
47
  end
48
48
 
49
49
  def close_request_record(queue, observations_queue, payload_creator)
50
- Sqreen::PerformanceNotifications::LogPerformance.next_request
51
- Sqreen::PerformanceNotifications::NewRelic.next_request
52
50
  clean_request_record if observed_items.nil?
53
51
  if only_metric_observation
54
52
  push_metrics(observations_queue, queue)
@@ -3,12 +3,13 @@
3
3
 
4
4
  require 'sqreen/callback_tree'
5
5
  require 'sqreen/log'
6
- require 'sqreen/stats'
7
6
  require 'sqreen/exception'
8
7
  require 'sqreen/performance_notifications'
9
8
  require 'sqreen/call_countable'
10
9
  require 'sqreen/events/remote_exception'
11
10
  require 'sqreen/rules_signature'
11
+ require 'sqreen/shared_storage'
12
+ require 'sqreen/rules_callbacks/record_request_context'
12
13
  require 'set'
13
14
 
14
15
  # How to override a class method:
@@ -33,13 +34,16 @@ require 'set'
33
34
 
34
35
  module Sqreen
35
36
  class Instrumentation
36
- WHITELISTED_METRIC='whitelisted'.freeze
37
- OVERTIME_METRIC='request_overtime'.freeze
37
+ OVERTIME_METRIC = 'request_overtime'.freeze
38
+ MGMT_COST = 0.000025
38
39
  @@override_semaphore = Mutex.new
40
+ @@overriden_singleton_methods = false
39
41
 
40
42
  ## Overriden methods and callbacks globals
41
43
  @@overriden_methods = []
42
44
  @@registered_callbacks = CBTree.new
45
+ @@unovertimable_hookpoints = Set.new
46
+ @@record_request_hookpoints = Set.new
43
47
  @@instrumented_pid = nil
44
48
 
45
49
  def self.semaphore
@@ -57,33 +61,29 @@ module Sqreen
57
61
  def self.overriden
58
62
  @@overriden_methods
59
63
  end
60
-
61
- def self.callback_wrapper_pre(klass, method, instance, *args, &block)
62
- Instrumentation.guard_call(method, []) do
63
- callbacks = @@registered_callbacks.get(klass, method, :pre)
64
- if callbacks.any?(&:whitelisted?)
65
- callbacks = callbacks.reject(&:whitelisted?)
66
- end
67
-
68
- cb_with_framework = callbacks.find(&:framework)
69
- budget = nil
70
- budget = cb_with_framework.framework.remaining_perf_budget if cb_with_framework
64
+ def self.callback_wrapper_pre(callbacks, framework, budget, _klass, method, instance, args, &block)
65
+ all_start = Sqreen::PerformanceNotifications.time
66
+ #Instrumentation.guard_call(method, []) do
71
67
  returns = []
72
68
  callbacks.each do |cb|
73
69
  # If record_request is part of callbacks we should filter after it ran
74
70
  next if cb.whitelisted?
75
71
  rule = cb.rule_name if cb.respond_to?(:rule_name)
76
- if !budget.nil? && budget <= 0
77
- next if cb.overtime!
72
+ if budget && budget <= 0.0
73
+ next if cb.overtimeable
78
74
  end
79
75
  Sqreen.log.debug { "running pre cb #{cb}" }
80
76
  begin
81
77
  start = Sqreen::PerformanceNotifications.time
82
- res = cb.send(:pre, instance, *args, &block)
78
+ res = cb.pre(instance, args, budget, &block)
83
79
  stop = Sqreen::PerformanceNotifications.time
84
80
  # The first few pre callbacks could not have a request & hence a budget just yet so we try harder to find it
85
- budget = cb_with_framework.framework.remaining_perf_budget if budget.nil? && !Sqreen.performance_budget.nil? && cb.framework
86
- budget -= (stop - start) unless budget.nil?
81
+ budget = framework.remaining_perf_budget if framework && !budget && Sqreen.performance_budget
82
+ if budget
83
+ budget -= (stop - start)
84
+ cb.overtime! if budget <= 0.0
85
+ end
86
+ all_start += (stop - start)
87
87
  if !res.nil? && cb.respond_to?(:block) && (!cb.block && !Sqreen.config_get(:block_all_rules))
88
88
  Sqreen.log.debug do
89
89
  "#{cb} cannot block, overriding return value"
@@ -94,7 +94,7 @@ module Sqreen
94
94
  end
95
95
  returns << res
96
96
  rescue StandardError => e
97
- Sqreen.log.warn "we catch an exception: #{e.inspect}"
97
+ Sqreen.log.warn { "we catch an exception: #{e.inspect}" }
98
98
  Sqreen.log.debug e.backtrace
99
99
  if cb.respond_to?(:record_exception)
100
100
  cb.record_exception(e)
@@ -105,36 +105,37 @@ module Sqreen
105
105
  end
106
106
  Sqreen::PerformanceNotifications.notify("Callbacks/#{rule || cb.class.name}/pre", start, stop)
107
107
  end
108
- cb_with_framework.framework.remaining_perf_budget=budget if cb_with_framework && !budget.nil?
108
+ all_stop = Sqreen::PerformanceNotifications.time
109
+ framework.remaining_perf_budget = budget - (all_stop - all_start) - MGMT_COST * callbacks.size if framework && budget
110
+ Sqreen::PerformanceNotifications.notify('Callbacks/hooks_pre/pre', all_start, all_stop)
109
111
  returns
110
- end
112
+ #end
111
113
  rescue StandardError => e
112
- Sqreen.log.warn "we catched an exception between cbs: #{e.inspect}"
114
+ Sqreen.log.warn { "we catched an exception between cbs: #{e.inspect}" }
113
115
  Sqreen::RemoteException.record(e)
116
+ []
114
117
  end
115
118
 
116
- def self.callback_wrapper_post(klass, method, return_val, instance, *args, &block)
117
- Instrumentation.guard_call(method, []) do
118
- callbacks = @@registered_callbacks.get(klass, method, :post)
119
- if callbacks.any?(&:whitelisted?)
120
- callbacks = callbacks.reject(&:whitelisted?)
121
- end
122
-
123
- cb_with_framework = callbacks.find(&:framework)
124
- budget = nil
125
- budget = cb_with_framework.framework.remaining_perf_budget if cb_with_framework
119
+ def self.callback_wrapper_post(callbacks, framework, budget, _klass, method, return_val, instance, args, &block)
120
+ all_start = Sqreen::PerformanceNotifications.time
121
+ #Instrumentation.guard_call(method, []) do
126
122
  returns = []
127
123
  callbacks.reverse_each do |cb|
124
+ next if cb.whitelisted?
128
125
  rule = cb.rule_name if cb.respond_to?(:rule_name)
129
- if !budget.nil? && budget <= 0
130
- next if cb.overtime!
126
+ if budget && budget <= 0.0
127
+ next if cb.overtimeable
131
128
  end
132
129
  Sqreen.log.debug { "running post cb #{cb}" }
133
130
  begin
134
131
  start = Sqreen::PerformanceNotifications.time
135
- res = cb.send(:post, return_val, instance, *args, &block)
132
+ res = cb.post(return_val, instance, args, budget, &block)
136
133
  stop = Sqreen::PerformanceNotifications.time
137
- budget -= (stop - start) unless budget.nil?
134
+ if budget
135
+ budget -= (stop - start)
136
+ cb.overtime! if budget <= 0.0
137
+ end
138
+ all_start += (stop - start)
138
139
  if !res.nil? && cb.respond_to?(:block) && (!cb.block && !Sqreen.config_get(:block_all_rules))
139
140
  Sqreen.log.debug do
140
141
  "#{cb} cannot block, overriding return value"
@@ -144,8 +145,8 @@ module Sqreen
144
145
  res[:rule_name] = rule
145
146
  end
146
147
  returns << res
147
- rescue => e
148
- Sqreen.log.warn "we catch an exception: #{e.inspect}"
148
+ rescue StandardError => e
149
+ Sqreen.log.warn { "we catch an exception: #{e.inspect}" }
149
150
  Sqreen.log.debug e.backtrace
150
151
  if cb.respond_to?(:record_exception)
151
152
  cb.record_exception(e)
@@ -156,36 +157,39 @@ module Sqreen
156
157
  end
157
158
  Sqreen::PerformanceNotifications.notify("Callbacks/#{rule || cb.class.name}/post", start, stop)
158
159
  end
159
- cb_with_framework.framework.remaining_perf_budget=budget if cb_with_framework && !budget.nil? && !cb_with_framework.framework.remaining_perf_budget.nil?
160
+ all_stop = Sqreen::PerformanceNotifications.time
161
+ if framework && budget && framework.remaining_perf_budget
162
+ framework.remaining_perf_budget = budget - (all_stop - all_start) - MGMT_COST * callbacks.size
163
+ end
164
+ Sqreen::PerformanceNotifications.notify('Callbacks/hooks_post/post', all_start, all_stop)
160
165
  returns
161
- end
166
+ #end
162
167
  rescue StandardError => e
163
- Sqreen.log.warn "we catched an exception between cbs: #{e.inspect}"
168
+ Sqreen.log.warn { "we catched an exception between cbs: #{e.inspect}" }
164
169
  Sqreen::RemoteException.record(e)
170
+ []
165
171
  end
166
172
 
167
- def self.callback_wrapper_failing(exception, klass, method, instance, *args, &block)
168
- Instrumentation.guard_call(method, []) do
169
- callbacks = @@registered_callbacks.get(klass, method, :failing)
170
- if callbacks.any?(&:whitelisted?)
171
- callbacks = callbacks.reject(&:whitelisted?)
172
- end
173
-
174
- cb_with_framework = callbacks.find(&:framework)
175
- budget = nil
176
- budget = cb_with_framework.framework.remaining_perf_budget if cb_with_framework
173
+ def self.callback_wrapper_failing(callbacks, framework, budget, exception, _klass, method, instance, args, &block)
174
+ all_start = Sqreen::PerformanceNotifications.time
175
+ #Instrumentation.guard_call(method, []) do
177
176
  returns = []
178
177
  callbacks.each do |cb|
178
+ next if cb.whitelisted?
179
179
  rule = cb.rule_name if cb.respond_to?(:rule_name)
180
- if !budget.nil? && budget <= 0
181
- next if cb.overtime!
180
+ if budget && budget <= 0.0
181
+ next if cb.overtimeable
182
182
  end
183
183
  Sqreen.log.debug { "running failing cb #{cb}" }
184
184
  begin
185
185
  start = Sqreen::PerformanceNotifications.time
186
- res = cb.send(:failing, exception, instance, *args, &block)
186
+ res = cb.failing(exception, instance, args, budget, &block)
187
187
  stop = Sqreen::PerformanceNotifications.time
188
- budget -= (stop - start) unless budget.nil?
188
+ if budget
189
+ budget -= (stop - start)
190
+ cb.overtime! if budget <= 0.0
191
+ end
192
+ all_start += (stop - start)
189
193
  if !res.nil? && cb.respond_to?(:block) && (!cb.block && !Sqreen.config_get(:block_all_rules))
190
194
  Sqreen.log.debug do
191
195
  "#{cb} cannot block, overriding return value"
@@ -195,9 +199,9 @@ module Sqreen
195
199
  res[:rule_name] = rule
196
200
  end
197
201
  returns << res
198
- rescue => e
199
- Sqreen.log.warn "we catch an exception: #{e.inspect}"
200
- Sqreen.log.debug e.backtrace
202
+ rescue StandardError => e
203
+ Sqreen.log.warn { "we catch an exception: #{e.inspect}" }
204
+ Sqreen.log.debug { e.backtrace }
201
205
  if cb.respond_to?(:record_exception)
202
206
  cb.record_exception(e)
203
207
  else
@@ -207,24 +211,27 @@ module Sqreen
207
211
  end
208
212
  Sqreen::PerformanceNotifications.notify("Callbacks/#{rule || cb.class.name}/failing", start, stop)
209
213
  end
210
- cb_with_framework.framework.remaining_perf_budget=budget if cb_with_framework && !budget.nil? && !cb_with_framework.framework.remaining_perf_budget.nil?
214
+ all_stop = Sqreen::PerformanceNotifications.time
215
+ if framework && budget && framework.remaining_perf_budget
216
+ framework.remaining_perf_budget = budget - (all_stop - all_start) - MGMT_COST * callbacks.size
217
+ end
218
+ Sqreen::PerformanceNotifications.notify('Callbacks/hooks_failing/failing', all_start, all_stop)
211
219
  returns
212
- end
220
+ # end
213
221
  rescue StandardError => e
214
222
  Sqreen.log.warn "we catched an exception between cbs: #{e.inspect}"
215
223
  Sqreen::RemoteException.record(e)
224
+ []
216
225
  end
217
226
 
218
227
  def self.guard_multi_call(instance, method, original_method, args, block)
219
- @sqreen_multi_instr ||= nil
228
+ return yield unless @@overriden_singleton_methods
220
229
  key = [method]
221
- Instrumentation.guard_call(nil, :guard_multi_call) do
222
- args.each{|e| key.push(e.object_id)}
223
- end
230
+ args.each { |e| key.push(e.object_id) }
224
231
  if key && @sqreen_multi_instr && @sqreen_multi_instr[instance.object_id].member?(key)
225
232
  return instance.send(original_method, *args, &block)
226
233
  end
227
- @sqreen_multi_instr ||= Hash.new {|h, k| h[k]=Set.new } # TODO this should probably be a thread local
234
+ @sqreen_multi_instr ||= Hash.new { |h, k| h[k] = Set.new } # TODO: this should probably be a thread local
228
235
  @sqreen_multi_instr[instance.object_id].add(key)
229
236
  r = yield
230
237
  return r
@@ -234,103 +241,152 @@ module Sqreen
234
241
  end
235
242
  end
236
243
 
237
- def self.guard_call(method, retval)
238
- @sqreen_in_instr ||= nil
239
- return retval if @sqreen_in_instr && @sqreen_in_instr.member?(method)
240
- @sqreen_in_instr ||= Set.new # TODO this should probably be a thread local
241
- @sqreen_in_instr.add(method)
242
- r = yield
243
- @sqreen_in_instr.delete(method)
244
- return r
245
- rescue Exception => e
246
- @sqreen_in_instr.delete(method)
247
- raise e
248
- end
249
-
250
244
  def self.define_callback_method(meth, original_meth, klass_name)
245
+ @sqreen_multi_instr ||= nil
251
246
  proc do |*args, &block|
252
- if Process.pid != Instrumentation.instrumented_pid
247
+ budget = nil
248
+ skip_call = Thread.current[:sqreen_in_use]
249
+ begin
250
+ if !skip_call && Sqreen.performance_budget
251
+ # Not using framework here to try to get a bit more perf by not loading cbs
252
+ budget = Sqreen::SharedStorage.get(:performance_budget)
253
+ skip_call = budget && budget <= 0.0 && !@@unovertimable_hookpoints.include?([klass_name, meth])
254
+ end
255
+ rescue StandardError => e
256
+ Sqreen.log.warn "we catched an exception looking for overtime: #{e.inspect}"
257
+ Sqreen::RemoteException.record(e)
258
+ end
259
+ if !skip_call && Process.pid != Instrumentation.instrumented_pid
253
260
  Sqreen.log.debug do
254
261
  "Instrumented #{Instrumentation.instrumented_pid} != PID #{Process.pid}"
255
262
  end
256
- return send(original_meth, *args, &block)
263
+ skip_call = true
257
264
  end
265
+ # If we are already overbudget let's not work at all
266
+ return send(original_meth, *args, &block) if skip_call
258
267
  Instrumentation.guard_multi_call(self, meth, original_meth, args, block) do
259
- Sqreen.stats.callbacks_calls += 1
260
-
261
- skip = false
262
- result = nil
263
-
264
- # pre callback
265
- returns = Instrumentation.callback_wrapper_pre(klass_name,
266
- meth,
267
- self,
268
- *args,
269
- &block)
270
- returns.each do |ret|
271
- next unless ret.is_a? Hash
272
- case ret[:status]
273
- when :skip, 'skip'
274
- skip = true
275
- result = ret[:new_return_value] if ret.key? :new_return_value
276
- next
277
- when :modify_args, 'modify_args'
278
- args = ret[:args]
279
- when :raise, 'raise'
280
- fail Sqreen::AttackBlocked, "Sqreen blocked a security threat (type: #{ret[:rule_name]}). No action is required."
268
+ precbs, postcbs, failcbs = Instrumentation.callbacks.get(klass_name, meth)
269
+ Thread.current[:sqreen_in_use] = true
270
+ begin
271
+ if budget && budget <= 0.0
272
+ precbs = precbs.reject(&:overtimeable) if precbs
273
+ end
274
+ # We need the framework to set budget remaining time else don't look for it
275
+ if budget
276
+ cb_with_framework = nil
277
+ cb_with_framework = precbs.find(&:framework) if precbs
278
+ cb_with_framework = postcbs.find(&:framework) unless cb_with_framework || !postcbs
279
+ cb_with_framework = failcbs.find(&:framework) unless cb_with_framework || !failcbs
280
+ framework = cb_with_framework ? cb_with_framework.framework : nil
281
+ else
282
+ framework = nil
283
+ end
284
+ rescue StandardError => e
285
+ Sqreen.log.warn "we catched an exception looking for framework: #{e.inspect}"
286
+ Sqreen::RemoteException.record(e)
287
+ framework = nil
281
288
  end
282
- end
283
289
 
284
- return result if skip
285
- begin
286
- result = send(original_meth, *args, &block)
287
- rescue => e
288
- returns = Instrumentation.callback_wrapper_failing(e, klass_name,
289
- meth,
290
- self,
291
- *args,
292
- &block)
293
- will_retry = false
294
- will_raise = returns.empty?
295
- returns.each do |ret|
296
- will_raise = true if ret.nil?
297
- next unless ret.is_a? Hash
298
- case ret[:status]
299
- when :override, 'override'
300
- result = ret[:new_return_value] if ret.key? :new_return_value
301
- when :retry, 'retry'
302
- will_retry = true
303
- else # :reraise, 'reraise'
304
- will_raise = true
290
+ skip = false
291
+ result = nil
292
+
293
+ if precbs && !precbs.empty?
294
+ # pre callback
295
+ returns = Instrumentation.callback_wrapper_pre(precbs, framework,
296
+ budget,
297
+ klass_name,
298
+ meth,
299
+ self,
300
+ args,
301
+ &block)
302
+ returns.each do |ret|
303
+ next unless ret.is_a? Hash
304
+ case ret[:status]
305
+ when :skip, 'skip'
306
+ skip = true
307
+ result = ret[:new_return_value] if ret.key? :new_return_value
308
+ next
309
+ when :modify_args, 'modify_args'
310
+ args = ret[:args]
311
+ when :raise, 'raise'
312
+ Thread.current[:sqreen_in_use] = false
313
+ raise Sqreen::AttackBlocked, "Sqreen blocked a security threat (type: #{ret[:rule_name]}). No action is required."
314
+ end
305
315
  end
306
316
  end
307
- raise e if will_raise
308
- retry if will_retry
309
- result
310
- else
311
-
312
- # post callback
313
- returns = Instrumentation.callback_wrapper_post(klass_name,
314
- meth,
315
- result,
316
- self,
317
- *args,
318
- &block)
319
- returns.each do |ret|
320
- next unless ret.is_a? Hash
321
- case ret[:status]
322
- when :raise, 'raise'
323
- fail Sqreen::AttackBlocked, "Sqreen blocked a security threat (type: #{ret[:rule_name]}). No action is required."
324
- when :override, 'override'
325
- result = ret[:new_return_value]
326
- else
327
- next
317
+ Thread.current[:sqreen_in_use] = false
318
+ return result if skip
319
+ begin
320
+ result = send(original_meth, *args, &block)
321
+ rescue StandardError => e
322
+ Thread.current[:sqreen_in_use] = true
323
+ budget = Sqreen.performance_budget && framework && framework.remaining_perf_budget
324
+ failcbs = failcbs.reject(&:overtimeable) if failcbs && budget && budget <= 0.0
325
+ raise e unless failcbs && !failcbs.empty?
326
+ returns = Instrumentation.callback_wrapper_failing(failcbs,
327
+ framework,
328
+ budget,
329
+ e,
330
+ klass_name,
331
+ meth,
332
+ self,
333
+ args,
334
+ &block)
335
+ will_retry = false
336
+ will_raise = returns.empty?
337
+ returns.each do |ret|
338
+ will_raise = true if ret.nil?
339
+ next unless ret.is_a? Hash
340
+ case ret[:status]
341
+ when :override, 'override'
342
+ result = ret[:new_return_value] if ret.key? :new_return_value
343
+ when :retry, 'retry'
344
+ will_retry = true
345
+ else # :reraise, 'reraise'
346
+ will_raise = true
347
+ end
348
+ end
349
+ raise e if will_raise
350
+ retry if will_retry
351
+ result
352
+ else
353
+ Thread.current[:sqreen_in_use] = true
354
+ budget = Sqreen.performance_budget && framework && framework.remaining_perf_budget
355
+ postcbs = postcbs.reject(&:overtimeable) if postcbs && budget && budget <= 0.0
356
+ return result unless postcbs && !postcbs.empty?
357
+ # post callback
358
+ returns = Instrumentation.callback_wrapper_post(postcbs,
359
+ framework,
360
+ budget,
361
+ klass_name,
362
+ meth,
363
+ result,
364
+ self,
365
+ args,
366
+ &block)
367
+ returns.each do |ret|
368
+ next unless ret.is_a? Hash
369
+ case ret[:status]
370
+ when :raise, 'raise'
371
+ raise Sqreen::AttackBlocked, "Sqreen blocked a security threat (type: #{ret[:rule_name]}). No action is required."
372
+ when :override, 'override'
373
+ result = ret[:new_return_value]
374
+ else
375
+ next
376
+ end
328
377
  end
378
+ result
379
+ ensure
380
+ if @@record_request_hookpoints.include?([klass_name, meth]) && Sqreen::PerformanceNotifications.listen_for?
381
+ Sqreen::PerformanceNotifications.instrument('Callbacks/hooks_reporting/pre') do
382
+ Sqreen::PerformanceNotifications::LogPerformance.next_request
383
+ Sqreen::PerformanceNotifications::NewRelic.next_request
384
+ end
385
+ end
386
+ Thread.current[:sqreen_in_use] = false
329
387
  end
330
- result
331
388
  end
332
389
  end
333
- end
334
390
  end
335
391
 
336
392
  def override_class_method(klass, meth)
@@ -372,12 +428,11 @@ module Sqreen
372
428
  method_kind = nil
373
429
  obj.class_eval do
374
430
  # Note: As a lambda the following will crash ruby 2.2.3p173
375
- case
376
- when public_method_defined?(meth)
431
+ if public_method_defined?(meth)
377
432
  method_kind = :public
378
- when protected_method_defined?(meth)
433
+ elsif protected_method_defined?(meth)
379
434
  method_kind = :protected
380
- when private_method_defined?(meth)
435
+ elsif private_method_defined?(meth)
381
436
  method_kind = :private
382
437
  end
383
438
  alias_method meth, saved_meth_name
@@ -385,7 +440,7 @@ module Sqreen
385
440
  end
386
441
  end
387
442
 
388
- def get_saved_method_name(meth, suffix=nil)
443
+ def get_saved_method_name(meth, suffix = nil)
389
444
  "#{meth}_sq#{suffix}_not_modified".to_sym
390
445
  end
391
446
 
@@ -401,12 +456,11 @@ module Sqreen
401
456
 
402
457
  define_method(new_method, p)
403
458
 
404
- case
405
- when public_method_defined?(meth)
459
+ if public_method_defined?(meth)
406
460
  method_kind = :public
407
- when protected_method_defined?(meth)
461
+ elsif protected_method_defined?(meth)
408
462
  method_kind = :protected
409
- when private_method_defined?(meth)
463
+ elsif private_method_defined?(meth)
410
464
  method_kind = :private
411
465
  end
412
466
  alias_method meth, new_method
@@ -465,6 +519,7 @@ module Sqreen
465
519
 
466
520
  # Override a singleton method on an instance
467
521
  def override_singleton_method(instance, klass_name, meth)
522
+ @@overriden_singleton_methods = true
468
523
  saved_meth_name = get_saved_method_name(meth, 'singleton')
469
524
  if instance.respond_to?(saved_meth_name, true)
470
525
  Sqreen.log.debug { "#{saved_meth_name} found #{instance.class}##{instance.object_id} already instrumented" }
@@ -533,6 +588,8 @@ module Sqreen
533
588
  end
534
589
 
535
590
  @@registered_callbacks.add(cb)
591
+ @@unovertimable_hookpoints << key unless cb.overtimeable
592
+ @@record_request_hookpoints << key if cb.is_a?(Sqreen::Rules::RecordRequestContext)
536
593
  @@instrumented_pid = Process.pid
537
594
  end
538
595
  end
@@ -555,7 +612,7 @@ module Sqreen
555
612
  return
556
613
  end
557
614
 
558
- defined_cbs = @@registered_callbacks.get(klass, method)
615
+ defined_cbs = @@registered_callbacks.get(klass, method).flatten
559
616
 
560
617
  nb_removed = 0
561
618
  defined_cbs.each do |found_cb|
@@ -607,7 +664,7 @@ module Sqreen
607
664
  # @param metrics_engine [MetricsStore] Metric storage facility
608
665
  def instrument!(rules, framework)
609
666
  verifier = nil
610
- if Sqreen.features['rules_signature'] &&
667
+ if Sqreen.features['rules_signature'] &&
611
668
  Sqreen.config_get(:rules_verify_signature) == true &&
612
669
  !defined?(::JRUBY_VERSION)
613
670
  verifier = Sqreen::SqreenSignedVerifier.new
@@ -630,7 +687,7 @@ module Sqreen
630
687
  metrics_engine.create_metric('name' => CallCountable::COUNT_CALLS,
631
688
  'period' => 60,
632
689
  'kind' => 'Sum')
633
- metrics_engine.create_metric('name' => WHITELISTED_METRIC,
690
+ metrics_engine.create_metric('name' => Sqreen::Rules::RecordRequestContext::WHITELISTED_METRIC,
634
691
  'period' => 60,
635
692
  'kind' => 'Sum')
636
693
  metrics_engine.create_metric('name' => OVERTIME_METRIC,