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