1 //--------------------------------------------------------------------------
2 // Copyright (C) 2016-2021 Cisco and/or its affiliates. All rights reserved.
3 //
4 // This program is free software; you can redistribute it and/or modify it
5 // under the terms of the GNU General Public License Version 2 as published
6 // by the Free Software Foundation. You may not use, modify or distribute
7 // this program under any other version of the GNU General Public License.
8 //
9 // This program is distributed in the hope that it will be useful, but
10 // WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 // General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License along
15 // with this program; if not, write to the Free Software Foundation, Inc.,
16 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 //--------------------------------------------------------------------------
18
19 // rule_latency.cc author Joel Cornett <jocornet@cisco.com>
20
21 #ifdef HAVE_CONFIG_H
22 #include "config.h"
23 #endif
24
25 #include "rule_latency.h"
26
27 #include "detection/detection_engine.h"
28 #include "detection/detection_options.h"
29 #include "detection/treenodes.h"
30 #include "main/snort_config.h"
31 #include "log/messages.h"
32 #include "protocols/packet.h"
33 #include "utils/stats.h"
34
35 #include "latency_config.h"
36 #include "latency_rules.h"
37 #include "latency_stats.h"
38 #include "latency_timer.h"
39 #include "latency_util.h"
40 #include "rule_latency_state.h"
41
42 #ifdef UNIT_TEST
43 #include "catch/snort_catch.h"
44 #include "main/thread_config.h"
45 #endif
46
47 using namespace snort;
48
49 namespace rule_latency
50 {
51 // -----------------------------------------------------------------------------
52 // helpers
53 // -----------------------------------------------------------------------------
54
55 struct Event
56 {
57 enum Type
58 {
59 EVENT_ENABLED,
60 EVENT_TIMED_OUT,
61 EVENT_SUSPENDED
62 };
63
64 Type type;
65 typename SnortClock::duration elapsed;
66 detection_option_tree_root_t* root;
67 Packet* packet;
68 };
69
70 template<typename Clock>
71 class RuleTimer : public LatencyTimer<Clock>
72 {
73 public:
RuleTimer(typename Clock::duration d,detection_option_tree_root_t * root,Packet * p)74 RuleTimer(typename Clock::duration d, detection_option_tree_root_t* root, Packet* p) :
75 LatencyTimer<Clock>(d), root(root), packet(p) { }
76
77 detection_option_tree_root_t* root;
78 Packet* packet;
79 };
80
81 using ConfigWrapper = ReferenceWrapper<RuleLatencyConfig>;
82 using EventHandler = EventingWrapper<Event>;
83
operator <<(std::ostream & os,const Event & e)84 static inline std::ostream& operator<<(std::ostream& os, const Event& e)
85 {
86 using std::chrono::duration_cast;
87 using std::chrono::microseconds;
88
89 os << "packet " << e.packet->context->packet_number << " rule tree ";
90
91 switch ( e.type )
92 {
93 case Event::EVENT_ENABLED:
94 os << "enabled: ";
95 break;
96
97 case Event::EVENT_TIMED_OUT:
98 os << "timed out: ";
99 break;
100
101 case Event::EVENT_SUSPENDED:
102 os << "suspended: ";
103 break;
104 }
105
106 os << clock_usecs(TO_USECS(e.elapsed)) << " usec, ";
107 os << e.root->otn->sigInfo.gid << ":" << e.root->otn->sigInfo.sid << ":"
108 << e.root->otn->sigInfo.rev;
109
110 if ( e.root->num_children > 1 )
111 os << " (of " << e.root->num_children << ")";
112
113 if ( e.packet->has_ip() or e.packet->is_data() )
114 {
115 SfIpString src_addr, dst_addr;
116 unsigned src_port = 0, dst_port = 0;
117
118 e.packet->ptrs.ip_api.get_src()->ntop(src_addr);
119 e.packet->ptrs.ip_api.get_dst()->ntop(dst_addr);
120 if ( e.packet->proto_bits & (PROTO_BIT__TCP|PROTO_BIT__UDP) )
121 {
122 src_port = e.packet->ptrs.sp;
123 dst_port = e.packet->ptrs.dp;
124 }
125
126 os << ", " << src_addr << ":" << src_port;
127 os << " -> " << dst_addr << ":" << dst_port;
128 }
129
130 return os;
131 }
132
133 // -----------------------------------------------------------------------------
134 // rule tree interface
135 // -----------------------------------------------------------------------------
136
137 // goes in a static structure so we can templatize Impl
138 struct DefaultRuleInterface
139 {
is_suspendedrule_latency::DefaultRuleInterface140 static bool is_suspended(const detection_option_tree_root_t& root)
141 { return root.latency_state[get_instance_id()].suspended; }
142
143 // return true if rule was *reenabled*
144 template<typename Duration, typename Time>
reenablerule_latency::DefaultRuleInterface145 static bool reenable(detection_option_tree_root_t& root, Duration max_suspend_time,
146 Time cur_time)
147 {
148 auto& state = root.latency_state[get_instance_id()];
149 if ( state.suspended && (cur_time - state.suspend_time > max_suspend_time) )
150 {
151 state.enable();
152 return true;
153 }
154
155 return false;
156 }
157
158 template<typename Time>
timeout_and_suspendrule_latency::DefaultRuleInterface159 static bool timeout_and_suspend(detection_option_tree_root_t& root, unsigned threshold,
160 Time time, bool do_suspend)
161 {
162 auto& state = root.latency_state[get_instance_id()];
163
164 ++state.timeouts;
165
166 // the separate loops in each branch are so we can avoid iterating
167 // over the children twice in the suspend case
168
169 if ( do_suspend and state.timeouts >= threshold )
170 {
171 state.suspend(time);
172
173 for ( int i = 0; i < root.num_children; ++i )
174 {
175 auto& child_state = root.children[i]->state[get_instance_id()];
176 ++child_state.latency_timeouts;
177 ++child_state.latency_suspends;
178 }
179
180 return true;
181 }
182
183 else
184 {
185 for ( int i = 0; i < root.num_children; ++i )
186 {
187 ++root.children[i]->state[get_instance_id()].latency_timeouts;
188 }
189 }
190
191 return false;
192 }
193 };
194
195 // -----------------------------------------------------------------------------
196 // implementation
197 // -----------------------------------------------------------------------------
198
199 template<typename Clock = SnortClock, typename RuleTree = DefaultRuleInterface>
200 class Impl
201 {
202 public:
203 Impl(const ConfigWrapper&, EventHandler&);
204
205 bool push(detection_option_tree_root_t*, Packet*);
206 bool pop();
207 bool suspended() const;
208
209 private:
210 std::vector<RuleTimer<Clock>> timers;
211 const ConfigWrapper& config;
212 EventHandler& event_handler;
213 };
214
215 template<typename Clock, typename RuleTree>
Impl(const ConfigWrapper & cfg,EventHandler & eh)216 inline Impl<Clock, RuleTree>::Impl(const ConfigWrapper& cfg, EventHandler& eh) :
217 config(cfg), event_handler(eh)
218 { }
219
220 template<typename Clock, typename RuleTree>
push(detection_option_tree_root_t * root,Packet * p)221 inline bool Impl<Clock, RuleTree>::push(detection_option_tree_root_t* root, Packet* p)
222 {
223 assert(root and p);
224
225 // FIXIT-L rule timer is pushed even if rule is not enabled (no visible side-effects)
226 timers.emplace_back(config->max_time, root, p);
227
228 if ( config->allow_reenable() )
229 {
230 if ( RuleTree::reenable(*root, config->max_suspend_time, Clock::now()) )
231 {
232 Event e { Event::EVENT_ENABLED, config->max_suspend_time, root, p };
233 event_handler.handle(e);
234 return true;
235 }
236 }
237
238 return false;
239 }
240
241 template<typename Clock, typename RuleTree>
pop()242 inline bool Impl<Clock, RuleTree>::pop()
243 {
244 assert(!timers.empty());
245 const auto& timer = timers.back();
246
247 bool timed_out = false;
248
249 if ( !RuleTree::is_suspended(*timer.root) )
250 {
251 timed_out = timer.timed_out();
252 #ifdef REG_TEST
253 timed_out = config->test_timeout ? true : timed_out;
254 #endif
255 if ( timed_out )
256 {
257 auto suspended = RuleTree::timeout_and_suspend(*timer.root, config->suspend_threshold,
258 Clock::now(), config->suspend);
259
260 Event e
261 {
262 suspended ? Event::EVENT_SUSPENDED : Event::EVENT_TIMED_OUT,
263 timer.elapsed(), timer.root, timer.packet
264 };
265
266 event_handler.handle(e);
267 }
268 }
269
270 timers.pop_back();
271 return timed_out;
272 }
273
274 template<typename Clock, typename RuleTree>
suspended() const275 inline bool Impl<Clock, RuleTree>::suspended() const
276 {
277 if ( !config->suspend )
278 return false;
279
280 assert(!timers.empty());
281 return RuleTree::is_suspended(*timers.back().root);
282 }
283
284 // -----------------------------------------------------------------------------
285 // static variables
286 // -----------------------------------------------------------------------------
287
288 static struct SnortConfigWrapper : public ConfigWrapper
289 {
operator ->rule_latency::SnortConfigWrapper290 const RuleLatencyConfig* operator->() const override
291 { return &SnortConfig::get_conf()->latency->rule_latency; }
292
293 } config;
294
295 static struct SnortEventHandler : public EventHandler
296 {
handlerule_latency::SnortEventHandler297 void handle(const Event& e) override
298 {
299 assert(e.packet);
300
301 std::ostringstream ss;
302 ss << e;
303 debug_logf(latency_trace, e.packet, "%s\n", ss.str().c_str());
304
305 switch ( e.type )
306 {
307 case Event::EVENT_ENABLED:
308 DetectionEngine::queue_event(GID_LATENCY, LATENCY_EVENT_RULE_TREE_ENABLED);
309 break;
310
311 case Event::EVENT_SUSPENDED:
312 DetectionEngine::queue_event(GID_LATENCY, LATENCY_EVENT_RULE_TREE_SUSPENDED);
313 break;
314
315 default:
316 break;
317 }
318 }
319 } event_handler;
320
321 static THREAD_LOCAL Impl<>* impl = nullptr;
322
323 // FIXIT-L this should probably be put in a tinit
get_impl()324 static inline Impl<>& get_impl()
325 {
326 if ( !impl )
327 impl = new Impl<>(config, event_handler);
328
329 return *impl;
330 }
331
332 } // namespace rule_latency
333
334 // -----------------------------------------------------------------------------
335 // rule latency interface
336 // -----------------------------------------------------------------------------
337
push(detection_option_tree_root_t * root,Packet * p)338 void RuleLatency::push(detection_option_tree_root_t* root, Packet* p)
339 {
340 if ( rule_latency::config->enabled() )
341 {
342 if ( rule_latency::get_impl().push(root, p) )
343 ++latency_stats.rule_tree_enables;
344
345 ++latency_stats.total_rule_evals;
346 }
347 }
348
pop()349 void RuleLatency::pop()
350 {
351 if ( rule_latency::config->enabled() )
352 {
353 if ( rule_latency::get_impl().pop() )
354 ++latency_stats.rule_eval_timeouts;
355 }
356 }
357
suspended()358 bool RuleLatency::suspended()
359 {
360 if ( rule_latency::config->enabled() )
361 return rule_latency::get_impl().suspended();
362
363 return false;
364 }
365
tterm()366 void RuleLatency::tterm()
367 {
368 using rule_latency::impl;
369
370 if ( impl )
371 {
372 delete impl;
373 impl = nullptr;
374 }
375 }
376
377 // -----------------------------------------------------------------------------
378 // unit tests
379 // -----------------------------------------------------------------------------
380
381 #ifdef UNIT_TEST
382
383 namespace t_rule_latency
384 {
385
386 struct MockConfigWrapper : public rule_latency::ConfigWrapper
387 {
388 RuleLatencyConfig config;
389
operator ->t_rule_latency::MockConfigWrapper390 const RuleLatencyConfig* operator->() const override
391 { return &config; }
392 };
393
394 struct EventHandlerSpy : public rule_latency::EventHandler
395 {
396 unsigned count = 0;
handlet_rule_latency::EventHandlerSpy397 void handle(const rule_latency::Event&) override
398 { ++count; }
399 };
400
401 struct MockClock : public ClockTraits<hr_clock>
402 {
403 static hr_time t;
404
resett_rule_latency::MockClock405 static void reset()
406 { t = hr_time(0_ticks); }
407
inct_rule_latency::MockClock408 static void inc(hr_duration d = 1_ticks)
409 { t += d; }
410
nowt_rule_latency::MockClock411 static hr_time now()
412 { return t; }
413 };
414
415 hr_time MockClock::t = hr_time(0_ticks);
416
417 struct RuleInterfaceSpy
418 {
419 static bool is_suspended_result;
420 static bool is_suspended_called;
421 static bool reenable_result;
422 static bool reenable_called;
423 static bool timeout_and_suspend_result;
424 static bool timeout_and_suspend_called;
425
resett_rule_latency::RuleInterfaceSpy426 static void reset()
427 {
428 is_suspended_result = false;
429 is_suspended_called = false;
430 reenable_result = false;
431 reenable_called = false;
432 timeout_and_suspend_result = false;
433 timeout_and_suspend_called = false;
434 }
435
is_suspendedt_rule_latency::RuleInterfaceSpy436 static bool is_suspended(const detection_option_tree_root_t&)
437 { is_suspended_called = true; return is_suspended_result; }
438
439 template<typename Duration, typename Time>
reenablet_rule_latency::RuleInterfaceSpy440 static bool reenable(detection_option_tree_root_t&, Duration, Time)
441 { reenable_called = true; return reenable_result; }
442
443 template<typename Time>
timeout_and_suspendt_rule_latency::RuleInterfaceSpy444 static bool timeout_and_suspend(detection_option_tree_root_t&, unsigned, Time, bool)
445 { timeout_and_suspend_called = true; return timeout_and_suspend_result; }
446 };
447
448 bool RuleInterfaceSpy::is_suspended_result = false;
449 bool RuleInterfaceSpy::is_suspended_called = false;
450 bool RuleInterfaceSpy::reenable_result = false;
451 bool RuleInterfaceSpy::reenable_called = false;
452 bool RuleInterfaceSpy::timeout_and_suspend_result = false;
453 bool RuleInterfaceSpy::timeout_and_suspend_called = false;
454
455 } // namespace t_rule_latency
456
457 TEST_CASE ( "rule latency impl", "[latency]" )
458 {
459 using namespace t_rule_latency;
460
461 MockConfigWrapper config;
462 EventHandlerSpy event_handler;
463
464 MockClock::reset();
465 RuleInterfaceSpy::reset();
466
467 detection_option_tree_root_t root;
468 Packet pkt(false);
469
470 rule_latency::Impl<MockClock, RuleInterfaceSpy> impl(config, event_handler);
471
472 SECTION( "push" )
473 {
474 SECTION( "reenable allowed" )
475 {
476 config.config.max_suspend_time = 1_ticks;
477
478 SECTION( "push rule" )
479 {
480 CHECK_FALSE( impl.push(&root, &pkt) );
481 CHECK( event_handler.count == 0 );
482 CHECK( RuleInterfaceSpy::reenable_called );
483 }
484
485 SECTION( "push rule -- reenabled" )
486 {
487 RuleInterfaceSpy::reenable_result = true;
488
489 CHECK( impl.push(&root, &pkt) );
490 CHECK( event_handler.count == 1 );
491 CHECK( RuleInterfaceSpy::reenable_called );
492 }
493 }
494
495 SECTION( "reenable not allowed" )
496 {
497 config.config.max_suspend_time = 0_ticks;
498
499 SECTION( "push rule" )
500 {
501 CHECK_FALSE( impl.push(&root, &pkt) );
502 CHECK( event_handler.count == 0 );
503 CHECK_FALSE( RuleInterfaceSpy::reenable_called );
504 }
505 }
506 }
507
508 SECTION( "enabled" )
509 {
510 RuleInterfaceSpy::is_suspended_result = true;
511
512 impl.push(&root, &pkt);
513
514 SECTION( "suspending of rules disabled" )
515 {
516 config.config.suspend = false;
517
518 CHECK_FALSE( impl.suspended() );
519 CHECK_FALSE( RuleInterfaceSpy::is_suspended_called );
520 }
521
522 SECTION( "suspend of rules enabled" )
523 {
524 config.config.suspend = true;
525
526 CHECK( impl.suspended() );
527 CHECK( RuleInterfaceSpy::is_suspended_called );
528 }
529 }
530
531 SECTION( "pop" )
532 {
533 config.config.max_time = 1_ticks;
534
535 impl.push(&root, &pkt);
536
537 SECTION( "rule timeout" )
538 {
539 MockClock::inc(2_ticks);
540
541 SECTION( "rule already suspended" )
542 {
543 RuleInterfaceSpy::is_suspended_result = true;
544
545 CHECK_FALSE( impl.pop() );
546 CHECK( event_handler.count == 0 );
547 CHECK_FALSE( RuleInterfaceSpy::timeout_and_suspend_called );
548 }
549
550 SECTION( "rule suspended" )
551 {
552 RuleInterfaceSpy::is_suspended_result = false;
553 RuleInterfaceSpy::timeout_and_suspend_result = true;
554
555 CHECK( impl.pop() );
556 CHECK( event_handler.count == 1 );
557 CHECK( RuleInterfaceSpy::timeout_and_suspend_called );
558 }
559
560 SECTION( "rule not suspended" )
561 {
562 RuleInterfaceSpy::timeout_and_suspend_result = false;
563
564 CHECK( impl.pop() );
565 CHECK( event_handler.count == 1 );
566 CHECK( RuleInterfaceSpy::timeout_and_suspend_called );
567 }
568
569 CHECK( RuleInterfaceSpy::is_suspended_called );
570 }
571
572 SECTION( "no rule timeout" )
573 {
574 RuleInterfaceSpy::is_suspended_result = false;
575
576 CHECK_FALSE( impl.pop() );
577 CHECK( event_handler.count == 0 );
578 CHECK_FALSE( RuleInterfaceSpy::timeout_and_suspend_called );
579 }
580 }
581 }
582
583 TEST_CASE ( "default latency rule interface", "[latency]" )
584 {
585 using RuleInterface = rule_latency::DefaultRuleInterface;
586
587 // construct a mock rule tree
588
589 auto instances = ThreadConfig::get_instance_max();
590 if ( !instances )
591 instances = 1;
592
593 std::unique_ptr<RuleLatencyState[]> latency_state(new RuleLatencyState[instances]());
594
595 std::unique_ptr<detection_option_tree_node_t*[]> children(
596 new detection_option_tree_node_t*[1]());
597
598 detection_option_tree_node_t child;
599 children[0] = &child;
600
601 std::unique_ptr<dot_node_state_t[]> child_state(new dot_node_state_t[instances]());
602 child.state = child_state.get();
603
604 detection_option_tree_root_t root;
605 root.latency_state = latency_state.get();
606 root.num_children = 1;
607 root.children = children.get();
608
609 SECTION( "is_suspended" )
610 {
611 CHECK_FALSE( RuleInterface::is_suspended(root) );
612 root.latency_state[0].suspend(hr_time(0_ticks));
613 CHECK( RuleInterface::is_suspended(root) );
614 }
615
616 SECTION( "reenable" )
617 {
618 SECTION( "rule already enabled" )
619 {
620 REQUIRE_FALSE( root.latency_state[get_instance_id()].suspended );
621 CHECK_FALSE( RuleInterface::reenable(root, 0_ticks, hr_time(0_ticks)) );
622 }
623
624 SECTION( "rule suspended" )
625 {
626 root.latency_state[get_instance_id()].suspend(hr_time(0_ticks));
627
628 SECTION( "suspend time not exceeded" )
629 {
630 CHECK_FALSE( RuleInterface::reenable(root, 1_ticks, hr_time(0_ticks)) );
631 }
632
633 SECTION( "suspend time exceeded" )
634 {
635 CHECK( RuleInterface::reenable(root, 1_ticks, hr_time(2_ticks)) );
636 }
637 }
638 }
639
640 SECTION( "timeout_and_suspend" )
641 {
642 SECTION( "suspend enabled" )
643 {
644 SECTION( "timeouts under threshold" )
645 {
646 CHECK_FALSE( RuleInterface::timeout_and_suspend(root, 2, hr_time(0_ticks), true) );
647 CHECK( child_state[0].latency_timeouts == 1 );
648 CHECK( child_state[0].latency_suspends == 0 );
649 }
650
651 SECTION( "timeouts exceed threshold" )
652 {
653 CHECK( RuleInterface::timeout_and_suspend(root, 1, hr_time(0_ticks), true) );
654 CHECK( child_state[0].latency_timeouts == 1 );
655 CHECK( child_state[0].latency_suspends == 1 );
656 }
657 }
658
659 SECTION( "suspend disabled" )
660 {
661 CHECK_FALSE( RuleInterface::timeout_and_suspend(root, 0, hr_time(0_ticks), false) );
662 CHECK( child_state[0].latency_timeouts == 1 );
663 CHECK( child_state[0].latency_suspends == 0 );
664 }
665 }
666 }
667
668 #endif
669