Package Peach :: Package Engine :: Module engine
[hide private]

Source Code for Module Peach.Engine.engine

  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  # Copyright (c) 2007-2008 Michael Eddington 
 20  # 
 21  # Permission is hereby granted, free of charge, to any person obtaining a copy  
 22  # of this software and associated documentation files (the "Software"), to deal 
 23  # in the Software without restriction, including without limitation the rights  
 24  # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell  
 25  # copies of the Software, and to permit persons to whom the Software is  
 26  # furnished to do so, subject to the following conditions: 
 27  # 
 28  # The above copyright notice and this permission notice shall be included in     
 29  # all copies or substantial portions of the Software. 
 30  # 
 31  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  
 32  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,  
 33  # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE  
 34  # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER  
 35  # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
 36  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 
 37  # SOFTWARE. 
 38  # 
 39   
 40  # Authors: 
 41  #   Michael Eddington (mike@phed.org) 
 42   
 43  # $Id: Peach.Engine.engine-pysrc.html 1138 2008-08-16 19:39:03Z meddingt $ 
 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  #from build import BuildPeach 
 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   
58 -class EngineWatcher:
59 ''' 60 Base for a class that receives callback when events occur 61 in the Peach Engine. 62 ''' 63
64 - def setTotalVariations(self, totalVariations):
65 self.totalVariations = totalVariations
66
67 - def OnRunStarting(self, run):
68 ''' 69 Called when a run is starting. 70 ''' 71 pass
72
73 - def OnRunFinished(self, run):
74 ''' 75 Called when a run is finished. 76 ''' 77 pass
78
79 - def OnTestStarting(self, run, test, totalVariations):
80 ''' 81 Called on start of a test. Each test has multiple variations. 82 ''' 83 pass
84
85 - def OnTestFinished(self, run, test):
86 ''' 87 Called on completion of a test. 88 ''' 89 pass
90
91 - def OnTestCaseStarting(self, run, test, variationCount):
92 ''' 93 Called on start of a test case. 94 ''' 95 pass
96
97 - def OnTestCaseReceived(self, run, test, variationCount, value):
98 ''' 99 Called when data is received from test case. 100 ''' 101 pass
102
103 - def OnTestCaseException(self, run, test, variationCount, exception):
104 ''' 105 Called when an exception occurs during a test case. 106 ''' 107 pass
108
109 - def OnTestCaseFinished(self, run, test, variationCount, actionValues):
110 ''' 111 Called when a test case has completed. 112 ''' 113 pass
114
115 - def OnFault(self, run, test, variationCount, monitorData, value):
116 pass
117
118 - def OnStopRun(self, run, test, variationCount, monitorData, value):
119 pass
120
121 -class EngineWatchPlexer(EngineWatcher):
122 ''' 123 Allows multiple watchers to be attached and will 124 distribute messages out to them. 125 ''' 126
127 - def __init__(self):
128 self.watchers = []
129
130 - def setTotalVariations(self, totalVariations):
131 for w in self.watchers: 132 w.setTotalVariations(totalVariations)
133
134 - def OnRunStarting(self, run):
135 for w in self.watchers: 136 w.OnRunStarting(run)
137
138 - def OnRunFinished(self, run):
139 for w in self.watchers: 140 w.OnRunFinished(run)
141
142 - def OnTestStarting(self, run, test, totalVariations):
143 for w in self.watchers: 144 w.OnTestStarting(run, test, totalVariations)
145
146 - def OnTestFinished(self, run, test):
147 for w in self.watchers: 148 w.OnTestFinished(run, test)
149
150 - def OnTestCaseStarting(self, run, test, variationCount):
151 for w in self.watchers: 152 w.OnTestCaseStarting(run, test, variationCount)
153
154 - def OnTestCaseReceived(self, run, test, variationCount, value):
155 for w in self.watchers: 156 w.OnTestCaseReceived(run, test, variationCount, value)
157
158 - def OnTestCaseException(self, run, test, variationCount, exception):
159 for w in self.watchers: 160 w.OnTestCaseException(run, test, variationCount, exception)
161
162 - def OnTestCaseFinished(self, run, test, variationCount, actionValues):
163 for w in self.watchers: 164 w.OnTestCaseFinished(run, test, variationCount, actionValues)
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
171 -class StdoutWatcher(EngineWatcher):
172 ''' 173 This is the default console interface to Peach 174 it prints out information as tests are performed. 175 ''' 176
177 - def OnRunStarting(self, run):
178 print '[*] Starting run "%s"' % run.name
179
180 - def OnRunFinished(self, run):
181 print '[*] Run "%s" completed' % run.name
182
183 - def OnTestStarting(self, run, test, totalVariations):
184 self.startTime = None 185 self.remaingTime = "?" 186 self.totalVariations = totalVariations 187 188 print '[-] Test: "%s" (%s)' % (test.name, test.description)
189
190 - def OnTestFinished(self, run, test):
191 print '[-] Test "%s" completed' % (test.name)
192
193 - def OnTestCaseStarting(self, run, test, variationCount):
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
212 - def OnTestCaseReceived(self, run, test, variationCount, value):
213 214 ## TODO: Needs to work with actions...SAD! 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
225 - def OnTestCaseException(self, run, test, variationCount, exception):
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
233 - def OnTestCaseFinished(self, run, test, variationCount, actionValues):
234 #print "--------\n" 235 pass
236
237 -class Engine:
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 #: Toggle display of debug messages. 244 debug = False 245 #debug = True 246
247 - def __init__(self):
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 # Skip to specific test if needs be. Used to 381 # restart a test run 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 # Handle parallel fuzzing 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 # 1. Get our total count. We want to use a copy of everything 401 # so we don't pollute the DOM! 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 # 2. How many tests per machine? 408 perCount = int(totalCount / totalMachines) 409 leftOver = totalCount - (perCount * totalMachines) 410 411 # 3. How many for this machine? 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
422 - def _startAgents(self, run, test):
423 ''' 424 Start up agents listed in test. 425 ''' 426 427 for a in test: 428 if a.elementType == 'agent': 429 if a.location == 'local': 430 server = "." 431 else: 432 server = a.location 433 434 agent = self.agent.AddAgent(a.name, server, a.password, a.getPythonPaths(), a.getImports()) 435 self._agents[a.name] = agent 436 437 # Start monitors for agent 438 for m in a: 439 if m.elementType == 'monitor': 440 agent.StartMonitor(m.name, m.classStr, m.params)
441
442 - def _stopAgents(self, run, test):
443 self.agent.OnShutdown()
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 # Sping up agents 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 # Get all the mutators we will use 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 # Needs to be off on its own! 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 # What if we are just counting? 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 # Go through one iteration for each mutator 534 # before we load state 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 # Skip ahead to start range, but not if we are 542 # restoring saved state. 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 # Update total test count 550 if testRange == None: 551 totalTests = mutator.getCount() 552 else: 553 # if we are parallel use our endCount which will also 554 # cause the estimated time left to be correct 555 totalTests = endCount+1 556 557 if totalTests == -1: 558 totalTests = "?" 559 560 #print "---" 561 #for m in mutators: 562 # if m.getCount() == -1: 563 # print "-- Still waiting for: ", m.name 564 565 else: 566 self.watcher.setTotalVariations(totalTests) 567 568 # Fire some events 569 self.agent.OnTestStarting() 570 571 if not countOnly: 572 self.watcher.OnTestCaseStarting(run, test, testCount) 573 574 # Run the test 575 try: 576 actionValues = stateEngine.run(mutator) 577 578 except RedoTestException: 579 raise 580 581 except SoftException: 582 # Ignore any SoftExceptions 583 # and head for next iteration 584 pass 585 586 # Pause as needed 587 time.sleep(run.waitTime) 588 589 # Notify 590 if not countOnly: 591 self.watcher.OnTestCaseFinished(run, test, testCount, actionValues) 592 593 self.agent.OnTestFinished() 594 595 # Check for faults 596 if self.agent.DetectedFault(): 597 # Collect data 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 # Check for stop event 604 if self.agent.StopRun(): 605 print "-- Detected StopRun, bailing! --" 606 self.watcher.OnStopRun(run, test, testCount, None, actionValues) 607 break 608 609 # Increment our mutator 610 mutator.next() 611 612 # Reset the redoCounter 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 # Have we completed our range? 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 # Always save state on exceptions 642 saveState = True 643 raise 644 645 finally: 646 647 if saveState: 648 # Save our state and exit 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 # end 683