sqreen-alt 1.11.0 → 1.11.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.
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: 2e71dccb7b443f6d3d391d08b222df8fd18ca081d63bc711f27dcbfba02b8992
4
- data.tar.gz: 5fe6c96c4477724e71c21c176c1a0a7a40ba99f97eeb7bde923d118b443434ac
3
+ metadata.gz: a04af20485331ca42056930bb78f81c6f11f0e4b4385f73b81f71f6e6af413e3
4
+ data.tar.gz: 0cbe16e0bdbc373ec5f948dfebbcb39e7379a57efea2bd2b93a1dceff9c389b2
5
5
  SHA512:
6
- metadata.gz: 75bbd3c5c30a8e0c60c5b0c0516e12f0913147fb6f424da02670fc1837866ae46b5506adbb86fea6dcc5d5b70ae264bfd249fb8b0f2f73a5b265912fcc3cfcb9
7
- data.tar.gz: 529a06959e31afa1c515b9d4b4f35dfcea42abb25548677cefd4aadbd55c1a2a2ff07c5ee2bf908caea254d104cb8acfb4c1d2d25b52c2c3422b99c6df9d23df
6
+ metadata.gz: 7b1eda52335145dbf4bf4dbc279c36ab63f4f10b6e673d96f8da395d6e6a369febf869fc42ff43b3a7a27233a6ce287305045bccea1657524398f39b2f987db9
7
+ data.tar.gz: 840409d8a53ff8d1d3e362f6f3721d26051ee1ce7cae2677aaa5044ab9815e454361277896a3ae29665a6a92d24e2c002d985ab734f99930cbda419c6ff36abc
data/lib/sqreen.rb CHANGED
@@ -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,