Commit | Line | Data |
---|---|---|
4ed24f86 JD |
1 | #!/usr/bin/env python3 |
2 | # | |
3 | # The MIT License (MIT) | |
4 | # | |
a3fa57c0 | 5 | # Copyright (C) 2015 - Julien Desfossez <jdesfossez@efficios.com> |
4ed24f86 JD |
6 | # |
7 | # Permission is hereby granted, free of charge, to any person obtaining a copy | |
8 | # of this software and associated documentation files (the "Software"), to deal | |
9 | # in the Software without restriction, including without limitation the rights | |
10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
11 | # copies of the Software, and to permit persons to whom the Software is | |
12 | # furnished to do so, subject to the following conditions: | |
13 | # | |
14 | # The above copyright notice and this permission notice shall be included in | |
15 | # all copies or substantial portions of the Software. | |
16 | # | |
17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
23 | # SOFTWARE. | |
24 | ||
b6d9132b AB |
25 | from ..linuxautomaton import common |
26 | ||
27 | ||
28 | class AnalysisConfig: | |
29 | def __init__(self): | |
30 | self.refresh_period = None | |
43a3c04c AB |
31 | self.period_begin_ev_name = None |
32 | self.period_end_ev_name = None | |
b6d9132b AB |
33 | self.begin_ts = None |
34 | self.end_ts = None | |
35 | self.min_duration = None | |
36 | self.max_duration = None | |
37 | ||
4ed24f86 | 38 | |
323b3fd6 | 39 | class Analysis: |
b6d9132b AB |
40 | def __init__(self, state, conf): |
41 | self._state = state | |
42 | self._conf = conf | |
43a3c04c | 43 | self._period_key = None |
b6d9132b AB |
44 | self._period_start_ts = None |
45 | self._last_event_ts = None | |
46 | self._notification_cbs = {} | |
47 | self._cbs = {} | |
48 | ||
b6d9132b AB |
49 | self.started = False |
50 | self.ended = False | |
51 | ||
323b3fd6 | 52 | def process_event(self, ev): |
b6d9132b AB |
53 | self._last_event_ts = ev.timestamp |
54 | ||
55 | if not self.started: | |
56 | if self._conf.begin_ts: | |
57 | self._check_analysis_begin(ev) | |
58 | if not self.started: | |
59 | return | |
60 | else: | |
61 | self._period_start_ts = ev.timestamp | |
62 | self.started = True | |
63 | ||
64 | self._check_analysis_end(ev) | |
65 | if self.ended: | |
66 | return | |
67 | ||
43a3c04c AB |
68 | # Prioritise period events over refresh period |
69 | if self._conf.period_begin_ev_name is not None: | |
70 | self._handle_period_event(ev) | |
71 | elif self._conf.refresh_period is not None: | |
b6d9132b | 72 | self._check_refresh(ev) |
b88c1bbc | 73 | |
b5db5706 AB |
74 | def reset(self): |
75 | raise NotImplementedError() | |
76 | ||
b6d9132b AB |
77 | def end(self): |
78 | self._end_period() | |
79 | ||
80 | def register_notification_cbs(self, cbs): | |
81 | for name in cbs: | |
82 | if name not in self._notification_cbs: | |
83 | self._notification_cbs[name] = [] | |
84 | ||
85 | self._notification_cbs[name].append(cbs[name]) | |
86 | ||
87 | def _send_notification_cb(self, name, **kwargs): | |
88 | if name in self._notification_cbs: | |
89 | for cb in self._notification_cbs[name]: | |
90 | cb(**kwargs) | |
91 | ||
b88c1bbc AB |
92 | def _register_cbs(self, cbs): |
93 | self._cbs = cbs | |
94 | ||
95 | def _process_event_cb(self, ev): | |
96 | name = ev.name | |
97 | ||
98 | if name in self._cbs: | |
99 | self._cbs[name](ev) | |
c2e5e625 | 100 | elif 'syscall_entry' in self._cbs and \ |
771c7e66 | 101 | (name.startswith('sys_') or name.startswith('syscall_entry_')): |
c2e5e625 AB |
102 | self._cbs['syscall_entry'](ev) |
103 | elif 'syscall_exit' in self._cbs and \ | |
104 | (name.startswith('exit_syscall') or | |
105 | name.startswith('syscall_exit_')): | |
106 | self._cbs['syscall_exit'](ev) | |
b6d9132b AB |
107 | |
108 | def _check_analysis_begin(self, ev): | |
109 | if self._conf.begin_ts and ev.timestamp >= self._conf.begin: | |
110 | self.started = True | |
111 | self._period_start_ts = ev.ts | |
112 | self.reset() | |
113 | ||
114 | def _check_analysis_end(self, ev): | |
115 | if self._conf.end_ts and ev.timestamp > self._conf.end_ts: | |
116 | self.ended = True | |
117 | ||
118 | def _check_refresh(self, ev): | |
119 | if not self._period_start_ts: | |
120 | self._period_start_ts = ev.timestamp | |
121 | elif ev.timestamp >= (self._period_start_ts + | |
122 | self._conf.refresh_period): | |
123 | self._end_period() | |
124 | self._period_start_ts = ev.timestamp | |
125 | ||
43a3c04c AB |
126 | def _handle_period_event(self, ev): |
127 | if ev.name != self._conf.period_begin_ev_name and \ | |
128 | ev.name != self._conf.period_end_ev_name: | |
129 | return | |
130 | ||
131 | period_key = self._get_period_event_key(ev) | |
132 | if not period_key: | |
133 | # There was an error caused by missing context, ignore | |
134 | # this period event | |
135 | return | |
136 | ||
137 | if self._period_key: | |
138 | if period_key == self._period_key: | |
139 | if self._conf.period_end_ev_name: | |
140 | if ev.name == self._conf.period_end_ev_name: | |
141 | self._end_period() | |
142 | self._period_key = None | |
143 | self._period_start_ts = None | |
144 | elif ev.name == self._conf.period_begin_ev_name: | |
145 | self._end_period() | |
146 | self._period_key = period_key | |
147 | self._period_start_ts = ev.timestamp | |
148 | else: | |
149 | self._period_key = period_key | |
150 | self._period_start_ts = ev.timestamp | |
151 | ||
b6d9132b AB |
152 | def _end_period(self): |
153 | self._end_period_cb() | |
154 | self._send_notification_cb('output_results', | |
155 | begin_ns=self._period_start_ts, | |
156 | end_ns=self._last_event_ts) | |
157 | self.reset() | |
158 | ||
159 | def _end_period_cb(self): | |
160 | pass | |
43a3c04c AB |
161 | |
162 | def _get_period_event_key(self, ev): | |
163 | # TODO: currently the key is hardcoded to the vtid of the | |
164 | # thread which generated the event, but eventually there | |
165 | # should be the option for a user to specify what fields | |
166 | # (context or payload) make up the key. | |
167 | try: | |
168 | key = ev.vtid | |
169 | except AttributeError: | |
170 | # TODO warn user of missing context? | |
171 | key = None | |
172 | ||
173 | return key |