1 '''
2 The high level engine that executes a Peach fuzzer run.
3
4 The engine component currently does the following:
5
6 1. Accepts a peach XML and parses it
7 2. Configures watchers, loggers
8 3. Connects to Agents and spinns up Monitors
9 4. Runs each defined test
10 a. Notified Agents
11 b. Calls State Engine
12
13
14 @author: Michael Eddington
15 @version: $Id: Peach.Engine.engine-pysrc.html 1138 2008-08-16 19:39:03Z meddingt $
16 '''
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46 import sys, os, time, pickle
47 from Peach.Engine.parser import *
48 from Peach.Engine.dom import DomPrint
49 from Peach.Engine.state import StateEngine
50
51 from Peach.Engine.common import *
52
53 from Peach.agent import AgentPlexer
54 from Peach.group import *
55 from Peach.Mutators.default import *
56 from Peach.mutator import *
57
59 '''
60 Base for a class that receives callback when events occur
61 in the Peach Engine.
62 '''
63
65 self.totalVariations = totalVariations
66
68 '''
69 Called when a run is starting.
70 '''
71 pass
72
74 '''
75 Called when a run is finished.
76 '''
77 pass
78
80 '''
81 Called on start of a test. Each test has multiple variations.
82 '''
83 pass
84
86 '''
87 Called on completion of a test.
88 '''
89 pass
90
92 '''
93 Called on start of a test case.
94 '''
95 pass
96
98 '''
99 Called when data is received from test case.
100 '''
101 pass
102
104 '''
105 Called when an exception occurs during a test case.
106 '''
107 pass
108
110 '''
111 Called when a test case has completed.
112 '''
113 pass
114
115 - def OnFault(self, run, test, variationCount, monitorData, value):
117
118 - def OnStopRun(self, run, test, variationCount, monitorData, value):
120
122 '''
123 Allows multiple watchers to be attached and will
124 distribute messages out to them.
125 '''
126
129
133
137
141
145
149
153
157
161
165
166 - def OnFault(self, run, test, variationCount, monitorData, value):
167 for w in self.watchers:
168 w.OnFault(run, test, variationCount, monitorData, value)
169
170
172 '''
173 This is the default console interface to Peach
174 it prints out information as tests are performed.
175 '''
176
178 print '[*] Starting run "%s"' % run.name
179
181 print '[*] Run "%s" completed' % run.name
182
184 self.startTime = None
185 self.remaingTime = "?"
186 self.totalVariations = totalVariations
187
188 print '[-] Test: "%s" (%s)' % (test.name, test.description)
189
191 print '[-] Test "%s" completed' % (test.name)
192
194
195 print "[%d:%s:%s] Running test with mutator %s" % (variationCount,
196 str(self.totalVariations),
197 str(self.remaingTime),
198 test.mutator.currentMutator().name)
199
200 if self.startTime == None:
201 self.startTime = time.time()
202
203 try:
204 if variationCount % 50 == 0:
205 elaps = time.time() - self.startTime
206 perTest = elaps / variationCount
207 remaining = (int(self.totalVariations) - variationCount) * perTest
208 self.remaingTime = str(int((remaining / 60) / 60)) + "hrs"
209 except:
210 pass
211
213
214
215 if Engine.verbose:
216 print "[%d:%s:%s] Received data: %s" % (variationCount,
217 str(self.totalVariations),
218 str(self.remaingTime),
219 repr(value))
220 else:
221 print "[%d:%s:%s] Received data" % (variationCount,
222 str(self.totalVariations),
223 str(self.remaingTime))
224
226 print "[%d:%s:%s] Caught error on receave, ignoring [%s]" % (variationCount,
227 str(self.totalVariations),
228 str(self.remaingTime),
229 exception)
230
231 return True
232
236
238 """
239 The highlevel Peach engine. The main entrypoint is "Run(...)" which
240 consumes a Peach XML file and performs the fuzzing run.
241 """
242
243
244 debug = False
245
246
248 self.noCount = True
249 self.restartFile = None
250 self.restartState = None
251 self.verbose = False
252 Engine.verbose = False
253 self.peach = None
254 self.agent = None
255 self._agents = {}
256
257 - def Count(self, uri, runName = None):
258 """
259 Just count the tests!
260
261 @type uri: String
262 @param uri: URI specifying the filename to use. Must have protocol prepended (file:, http:, etc)
263 @type runName: String
264 @param runName: Name of run or if None, "DefaultRun" is used.
265 """
266
267 print ""
268 print "Warning: This will run the fuzzer through several iterations to determine the count."
269 print "\n -- Press Any Key To Continue, or Ctrl+C to Stop -- "
270 getch()
271 print "\n"
272
273 if runName == None:
274 runName = "DefaultRun"
275
276 parse = ParseTemplate()
277 self.peach = parse.parse(uri)
278 self.agent = AgentPlexer()
279 self._agents = {}
280
281 if hasattr(self.peach.runs, runName):
282 run = getattr(self.peach.runs, runName)
283 else:
284 raise PeachException("Can't find run %s" % runName)
285
286 totalCount = 0
287 for test in run.tests:
288 testCount = self._countTest(run, test, True)
289 totalCount += testCount
290
291 print "Test %s has %d test cases" % (test.name, testCount)
292
293 print "\nTotal test cases for run %s is %d" % (runName, totalCount)
294 return totalCount
295
296 - def Run(self, uri, runName = None, verbose = False, watcher = None,
297 restartFile = None, noCount = False, parallel = None):
298 """
299 Run a Peach XML file.
300
301 Called by peach.py to perform fuzzing runs.
302
303 @type uri: String
304 @param uri: URI specifying the filename to use. Must have protocol prepended (file:, http:, etc)
305 @type runName: String
306 @param runName: Name of run or if None, "DefaultRun" is used.
307 @type verbose: Boolean
308 @param verbose: Not used anymore??
309 @type watcher: Instance
310 @param watcher: UI Interface that receaves callbacks from engine
311 @type restartFile: String
312 @param restartFile: File containing state for restarting a fuzzing run
313 @type noCount: Boolean
314 @param noCount: No longer used
315 @type parallel: List
316 @param parallel: First item is total machine count, second is our machine #
317 """
318
319 if runName == None:
320 runName = "DefaultRun"
321
322 Engine.context = self
323
324 self.noCount = noCount
325 self.restartFile = restartFile
326 self.restartState = None
327 self.verbose = verbose
328 Engine.verbose = verbose
329 parse = ParseTemplate()
330 self.peach = parse.parse(uri)
331 run = None
332 self.agent = AgentPlexer()
333 self._agents = {}
334
335 self.watcher = EngineWatchPlexer()
336
337 if watcher == None:
338 self.watcher.watchers.append(StdoutWatcher())
339 else:
340 self.watcher.watchers.append(watcher)
341
342 if runName != None:
343 if hasattr(self.peach.runs, runName):
344 run = getattr(self.peach.runs, runName)
345 else:
346 raise PeachException("Can't find run %s" % runName)
347
348 else:
349 raise PeachException("Must specify a run name")
350 run = self.peach.runs[0]
351
352 logger = run.getLogger()
353 if logger != None:
354 self.watcher.watchers.append(logger)
355
356 try:
357 self.watcher.OnRunStarting(run)
358 except TypeError, t:
359 print t
360 print dir(self.watcher)
361 print dir(self.watcher.OnRunStarting)
362 raise t
363
364 skipToTest = False
365 if self.restartFile != None:
366
367 print ""
368 print "-- Restarting based on state file"
369 print "-- Loading state file: %s\n" % self.restartFile
370
371 fd = open(self.restartFile, "rb+")
372 self.restartState = pickle.loads(fd.read())
373 fd.close()
374 skipToTest = True
375 skipToTestName = self.restartState[0]
376
377 if parallel == None:
378 for test in run.tests:
379
380
381
382 if skipToTest and test.name != skipToTestName:
383 continue
384 elif skipToTest and test.name == skipToTestName:
385 skipToTest = False
386
387 self._runTest(run, test)
388
389 else:
390
391 print "-- Parallel fuzzing, configuring test run"
392
393 if len(run.tests) > 1:
394 raise PeachException("Only a single test per-run is currently supported for parallel fuzzing.")
395
396 totalMachines = int(parallel[0])
397 thisMachine = int(parallel[1])
398 test = run.tests[0]
399
400
401
402 parse2 = ParseTemplate()
403 peach = parse2.parse(uri)
404
405 totalCount = self._countTest(getattr(peach.runs, runName), getattr(peach.runs, runName).tests[0])
406
407
408 perCount = int(totalCount / totalMachines)
409 leftOver = totalCount - (perCount * totalMachines)
410
411
412 startCount = thisMachine * perCount
413 thisCount = perCount
414 if thisMachine == totalMachines-1:
415 thisCount += leftOver
416
417 print "-- This machine will perform chunk %d through %d out of %d total" % (startCount, startCount+thisCount, totalCount)
418 self._runTest(run, test, False, [startCount, startCount+thisCount])
419
420 self.watcher.OnRunFinished(run)
421
441
442 - def _stopAgents(self, run, test):
444
445 - def _countTest(self, run, test, verbose = False):
446 '''
447 Get the total test count of this test
448 '''
449
450 print "-- Counting total test cases..."
451
452 mutator = self._runTest(run, test, True)
453 cnt = mutator.getCount(verbose)
454 if cnt == None:
455 raise PeachException("An error occured counting total tests.")
456
457 print "-- Count completed, found %d tests" % cnt
458
459 return cnt
460
461 - def _runTest(self, run, test, countOnly = False, testRange = None):
462 '''
463 Runs a Test as defined in the Peach XML.
464
465 @type run: Run object
466 @param run: Run that test is part of
467 @type test: Test object
468 @param test: Test to run
469 @type countOnly: bool
470 @param countOnly: Should we just get total mutator count? Defaults to False.
471 @type testRange: list of numbers
472 @param testRange: Iteration # test ranges. Only used when performing parallel fuzzing.
473
474 @rtype: number
475 @return: the total number of test iterations or None
476 '''
477
478 stateMachine = test.stateMachine
479 stateEngine = StateEngine(self, stateMachine, test.publisher)
480
481 pub = test.publisher
482
483 totalTests = "?"
484 testCount = 0
485
486
487 self._startAgents(run, test)
488
489 if not countOnly:
490 self.watcher.OnTestStarting(run, test, totalTests)
491
492 errorCount = 0
493 maxErrorCount = 10
494
495
496 mutators = []
497 for m in test.getMutators():
498 mutators.append(m.mutator)
499
500 mutator = test.mutator = MutatorCollection(mutators)
501 value = "StateMachine"
502
503 if self.restartState != None:
504 print "-- State will load in %d iterations" % (len(mutators)+1)
505
506 elif testRange != None:
507 print "-- Will skip to start of chunk in %d iterations" % (len(mutators)+1)
508
509
510 if testRange != None:
511 startCount = testRange[0]
512 endCount = testRange[1]
513
514 redoCount = 0
515 saveState = False
516 exitImmediate = False
517
518 try:
519
520 while True:
521 try:
522 testCount += 1
523
524
525 if testCount == len(mutators)+1 and countOnly:
526 print "-- Waiting for count to complete..."
527 while mutator.getCount() == -1:
528 time.sleep(0.5)
529
530 self._stopAgents(run, test)
531 return mutator
532
533
534
535 elif testCount == len(mutators)+1 and self.restartState != None:
536 print "-- Restoring state"
537 testCount = self.restartState[1]
538 mutator.setState(self.restartState[2])
539
540 elif testCount == len(mutators)+1 and testRange != None and startCount > (len(mutators)+1):
541
542
543 print "-- Skipping ahead to iteration %d" % startCount
544 testCount -= 1
545 for i in range(testCount, startCount):
546 mutator.next()
547 testCount+=1
548
549
550 if testRange == None:
551 totalTests = mutator.getCount()
552 else:
553
554
555 totalTests = endCount+1
556
557 if totalTests == -1:
558 totalTests = "?"
559
560
561
562
563
564
565 else:
566 self.watcher.setTotalVariations(totalTests)
567
568
569 self.agent.OnTestStarting()
570
571 if not countOnly:
572 self.watcher.OnTestCaseStarting(run, test, testCount)
573
574
575 try:
576 actionValues = stateEngine.run(mutator)
577
578 except RedoTestException:
579 raise
580
581 except SoftException:
582
583
584 pass
585
586
587 time.sleep(run.waitTime)
588
589
590 if not countOnly:
591 self.watcher.OnTestCaseFinished(run, test, testCount, actionValues)
592
593 self.agent.OnTestFinished()
594
595
596 if self.agent.DetectedFault():
597
598 print "-- Detected fault, getting data --"
599 results = self.agent.GetMonitorData()
600 self.watcher.OnFault(run, test, testCount, results, actionValues)
601 self.agent.OnFault()
602
603
604 if self.agent.StopRun():
605 print "-- Detected StopRun, bailing! --"
606 self.watcher.OnStopRun(run, test, testCount, None, actionValues)
607 break
608
609
610 mutator.next()
611
612
613 redoCount = 0
614
615 except RedoTestException, e:
616 if redoCount == 3:
617 raise PeachException(e.message)
618
619 redoCount += 1
620 testCount -= 1
621
622 except SoftException:
623 mutator.next()
624
625
626 if testRange != None and testCount > endCount:
627 print "-- Completed our iteration range, exiting"
628 break
629
630
631 except MutatorCompleted:
632 pass
633
634 except KeyboardInterrupt:
635 print "\n"
636 print "-- User canceled run"
637 saveState = True
638 exitImmediate = True
639
640 except:
641
642 saveState = True
643 raise
644
645 finally:
646
647 if saveState:
648
649 stateFilename = "RunSpotSave_%s.peach" % time.strftime("%m%d%y_%H%M%S")
650
651 print "-- Shutting down publisher(s)"
652
653 try:
654 if hasattr(pub, "hasBeenConnected") and pub.hasBeenConnected:
655 pub.close()
656 pub.hasBeenConnected = False
657
658 if hasattr(pub, "hasBeenStarted") and pub.hasBeenStarted:
659 pub.stop()
660 pub.hasBeenStarted = False
661
662 except:
663 pass
664
665 print "-- Saving position to restart file: %s" % stateFilename
666
667 state = [ test.name, testCount, mutator.getState() ]
668 fd = open(stateFilename, "wb+")
669 fd.write(pickle.dumps(state))
670 fd.close()
671
672 print "-- Done"
673 print "-- Exiting"
674
675 if exitImmediate:
676 sys.exit(0)
677
678 self.watcher.OnTestFinished(run, test)
679 self._stopAgents(run, test)
680 return None
681
682
683