@@ -199,21 +199,54 @@ def get_info(self):
199
199
return f'duration { self .duration } code { self .code :02X} data { self .data :02X} ({ gprs_info (self .gprs )} )'
200
200
201
201
202
+ class smi_stats :
203
+ def __init__ (self ):
204
+ self .clear ()
205
+
206
+ def clear (self ):
207
+ self .count = 0
208
+ self .mean = 0
209
+ self .m2 = 0
210
+ self .stdev = 0
211
+ self .outliers = 0
212
+
213
+ #
214
+ # Computes the standard deviation using the Welford's online algorithm
215
+ #
216
+ def update_stats (self , duration ):
217
+ self .count += 1
218
+ difference = duration - self .mean
219
+ self .mean += difference / self .count
220
+ self .m2 += difference * (duration - self .mean )
221
+ variance = self .m2 / self .count
222
+ self .stdev = math .sqrt (variance )
223
+
224
+ def get_info (self ):
225
+ info = f'average { round (self .mean )} stddev { self .stdev :.2f} checked { self .count } '
226
+ return info
227
+
228
+ #
229
+ # Combines the statistics of the two data sets using parallel variance computation
230
+ #
231
+ def combine (self , partial ):
232
+ self .outliers += partial .outliers
233
+ total_count = self .count + partial .count
234
+ difference = partial .mean - self .mean
235
+ self .mean = (self .mean * self .count + partial .mean * partial .count ) / total_count
236
+ self .m2 += partial .m2 + difference ** 2 * self .count * partial .count / total_count
237
+ self .count = total_count
238
+ variance = self .m2 / self .count
239
+ self .stdev = math .sqrt (variance )
240
+
241
+
202
242
class scan_track :
203
243
def __init__ (self ):
244
+ self .current_smi_stats = smi_stats ()
245
+ self .history_smi_stats = smi_stats ()
204
246
self .clear ()
205
- self .hist_smi_duration = 0
206
- self .hist_smi_num = 0
207
- self .outliers_hist = 0
208
247
self .helper = OsHelper ().get_default_helper ()
209
248
self .helper .init ()
210
249
self .smi_count = self .get_smi_count ()
211
- self .needs_calibration = True
212
- self .calib_samples = 0
213
- self .stdev = 0
214
- self .m2 = 0
215
- self .stdev_hist = 0
216
- self .m2_hist = 0
217
250
218
251
def __del__ (self ):
219
252
self .helper .close ()
@@ -251,73 +284,47 @@ def find_address_in_regs(self, gprs):
251
284
return key
252
285
253
286
def clear (self ):
254
- self .max = smi_info (0 )
255
- self .min = smi_info (2 ** 32 - 1 )
256
287
self .outlier = smi_info (0 )
257
- self .avg_smi_duration = 0
258
- self .avg_smi_num = 0
259
- self .outliers = 0
260
288
self .code = None
261
- self .confirmed = False
289
+ self .contents_changed = False
262
290
self .needs_calibration = True
263
291
self .calib_samples = 0
264
- self .stdev = 0
265
- self .m2 = 0
292
+ self .current_smi_stats .clear ()
266
293
267
- def add (self , duration , code , data , gprs , confirmed = False ):
294
+ def add (self , duration , code , data , gprs , contents_changed = False ):
268
295
if not self .code :
269
296
self .code = code
270
297
outlier = self .is_outlier (duration )
271
298
if not outlier :
272
- self .update_stdev (duration )
273
- if duration > self .max .duration :
274
- self .max .update (duration , code , data , gprs .copy ())
275
- elif duration < self .min .duration :
276
- self .min .update (duration , code , data , gprs .copy ())
299
+ self .current_smi_stats .update_stats (duration )
277
300
elif self .is_slow_outlier (duration ):
278
- self .outliers += 1
279
- self .outliers_hist += 1
301
+ self .current_smi_stats .outliers += 1
280
302
self .outlier .update (duration , code , data , gprs .copy ())
281
- self .confirmed = confirmed
282
-
283
- #
284
- # Computes the standard deviation using the Welford's online algorithm
285
- #
286
- def update_stdev (self , value ):
287
- self .avg_smi_num += 1
288
- self .hist_smi_num += 1
289
- difference = value - self .avg_smi_duration
290
- difference_hist = value - self .hist_smi_duration
291
- self .avg_smi_duration += difference / self .avg_smi_num
292
- self .hist_smi_duration += difference_hist / self .hist_smi_num
293
- self .m2 += difference * (value - self .avg_smi_duration )
294
- self .m2_hist += difference_hist * (value - self .hist_smi_duration )
295
- variance = self .m2 / self .avg_smi_num
296
- variance_hist = self .m2_hist / self .hist_smi_num
297
- self .stdev = math .sqrt (variance )
298
- self .stdev_hist = math .sqrt (variance_hist )
303
+ self .contents_changed = contents_changed
299
304
300
305
def update_calibration (self , duration ):
301
306
if not self .needs_calibration :
302
307
return
303
- self .update_stdev (duration )
308
+ self .current_smi_stats . update_stats (duration )
304
309
self .calib_samples += 1
305
310
if self .calib_samples >= SCAN_CALIB_SAMPLES :
306
311
self .needs_calibration = False
307
312
308
313
def is_slow_outlier (self , value ):
309
314
ret = False
310
- if value > self .avg_smi_duration + OUTLIER_STD_DEV * self .stdev :
315
+ if value > self .current_smi_stats . mean + OUTLIER_STD_DEV * self . current_smi_stats .stdev :
311
316
ret = True
312
- if value > self .hist_smi_duration + OUTLIER_STD_DEV * self .stdev_hist :
317
+ if self .history_smi_stats .count and \
318
+ value > self .history_smi_stats .mean + OUTLIER_STD_DEV * self .history_smi_stats .stdev :
313
319
ret = True
314
320
return ret
315
321
316
322
def is_fast_outlier (self , value ):
317
323
ret = False
318
- if value < self .avg_smi_duration - OUTLIER_STD_DEV * self .stdev :
324
+ if value < self .current_smi_stats . mean - OUTLIER_STD_DEV * self . current_smi_stats .stdev :
319
325
ret = True
320
- if value < self .hist_smi_duration - OUTLIER_STD_DEV * self .stdev_hist :
326
+ if self .history_smi_stats .count and \
327
+ value < self .history_smi_stats .mean - OUTLIER_STD_DEV * self .history_smi_stats .stdev :
321
328
ret = True
322
329
return ret
323
330
@@ -332,18 +339,17 @@ def is_outlier(self, value):
332
339
return ret
333
340
334
341
def skip (self ):
335
- return self .outliers or self .confirmed
342
+ return self .current_smi_stats . outliers or self .contents_changed
336
343
337
344
def found_outlier (self ):
338
- return bool (self .outliers )
345
+ return bool (self .current_smi_stats . outliers )
339
346
340
347
def get_total_outliers (self ):
341
- return self .outliers_hist
348
+ return self .history_smi_stats . outliers
342
349
343
350
def get_info (self ):
344
- avg = self .avg_smi_duration or self .hist_smi_duration
345
- info = f'average { round (avg )} stddev { self .stdev :.2f} checked { self .avg_smi_num + self .outliers } '
346
- if self .outliers :
351
+ info = self .current_smi_stats .get_info ()
352
+ if self .current_smi_stats .outliers :
347
353
info += f'\n Identified outlier: { self .outlier .get_info ()} '
348
354
return info
349
355
@@ -354,6 +360,9 @@ def log_smi_result(self, logger):
354
360
else :
355
361
logger .log (f'[*] { msg } ' )
356
362
363
+ def update_history_stats (self ):
364
+ self .history_smi_stats .combine (self .current_smi_stats )
365
+
357
366
358
367
class smi_desc :
359
368
def __init__ (self ):
@@ -699,6 +708,7 @@ def test_fuzz(self, thread_id, smic_start, smic_end, _addr, _addr1, scan_mode=Fa
699
708
break
700
709
if scan_mode :
701
710
scan .log_smi_result (self .logger )
711
+ scan .update_history_stats ()
702
712
scan .clear ()
703
713
704
714
return bad_ptr_cnt , scan
0 commit comments