def __init__(self, cfgFile): self.config = ADBFuzzConfig(cfgFile) self.HTTPProcess = None self.logProcesses = [] self.logThreads = [] self.remoteInitialized = None self.triager = Triager(self.config) # Seed RNG with localtime random.seed()
class ADBFuzz: def __init__(self, cfgFile): self.config = ADBFuzzConfig(cfgFile) self.HTTPProcess = None self.logProcesses = [] self.logThreads = [] self.remoteInitialized = None self.triager = Triager(self.config) # Seed RNG with localtime random.seed() def deploy(self, packageFile, prefFile): self.dm = DeviceManagerADB(self.config.remoteAddr, 5555) # Install a signal handler that shuts down our external programs on SIGINT signal.signal(signal.SIGINT, self.signal_handler) self.dm.updateApp(packageFile) # Standard init stuff self.appName = self.dm.packageName self.appRoot = self.dm.getAppRoot(self.appName) self.profileBase = self.appRoot + "/files/mozilla" # Ensure no Fennec instance is running self.stopFennec() # Start Fennec, so a profile is created if this is the first install self.startFennec(blank=True) # Grant some time to create profile time.sleep(self.config.runTimeout * 2) # Stop Fennec again self.stopFennec() # Now try to get the profile(s) self.profiles = self.getProfiles() if (len(self.profiles) == 0): print "Failed to detect any valid profile, aborting..." return 1 self.defaultProfile = self.profiles[0] if (len(self.profiles) > 1): print "Multiple profiles detected, using the first: " + self.defaultProfile # Push prefs.js to profile self.dm.pushFile(prefFile, self.profileBase + "/" + self.defaultProfile + "/prefs.js") # Try to install addon if requested by configuration if self.config.addon != None: self.ensureExtensionInstalled(self.config.addon) print "Successfully deployed package." def reset(self, prefFile): self.dm = DeviceManagerADB(self.config.remoteAddr, 5555) # Install a signal handler that shuts down our external programs on SIGINT signal.signal(signal.SIGINT, self.signal_handler) # Standard init stuff self.appName = self.dm.packageName self.appRoot = self.dm.getAppRoot(self.appName) self.profileBase = self.appRoot + "/files/mozilla" # Ensure no Fennec instance is running self.stopFennec() # Now try to get the old profile(s) self.profiles = self.getProfiles() for profile in self.profiles: self.dm.removeDir(self.profileBase + "/" + profile) self.dm.removeFile(self.profileBase + "/profiles.ini") # Start Fennec, so a new profile is created self.startFennec(blank=True) # Grant some time to create profile time.sleep(self.config.runTimeout * 2) # Stop Fennec again self.stopFennec() # Now try to get the profile(s) again self.profiles = self.getProfiles() if (len(self.profiles) == 0): print "Failed to detect any valid profile, aborting..." return 1 self.defaultProfile = self.profiles[0] if (len(self.profiles) > 1): print "Multiple profiles detected, using the first: " + self.defaultProfile # Push prefs.js to profile self.dm.pushFile(prefFile, self.profileBase + "/" + self.defaultProfile + "/prefs.js") # Try to install addon if requested by configuration if self.config.addon != None: self.ensureExtensionInstalled(self.config.addon) print "Successfully resetted profile." def remoteInit(self): if (self.remoteInitialized != None): return self.dm = DeviceManagerADB(self.config.remoteAddr, 5555) self.appName = self.dm.packageName self.appRoot = self.dm.getAppRoot(self.appName) self.profileBase = self.appRoot + "/files/mozilla" self.profiles = self.getProfiles() # Install a signal handler that shuts down our external programs on SIGINT signal.signal(signal.SIGINT, self.signal_handler) if (len(self.profiles) == 0): print "Failed to detect any valid profile, aborting..." return 1 self.defaultProfile = self.profiles[0] if (len(self.profiles) > 1): print "Multiple profiles detected, using the first: " + self.defaultProfile # Workaround for bug 754575. Avoid using DeviceManagerADB's "removeDir" because # that calls "rm" on every single entry which takes a lot of additional time. print "Purging possible cache leftover directories..." self.dm.runCmd(['shell', 'rm', '-r', self.profileBase + "/" + self.defaultProfile + "/Cache.Trash*"]).communicate() self.remoteInitialized = True def signal_handler(self, signal, frame): self.cleanupProcesses() sys.exit(0) def cleanupProcesses(self): self.stopFennec() if (self.HTTPProcess != None): try: self.HTTPProcess.terminate() except: pass if (self.logProcesses != None): try: self.stopLoggers() except: pass def loopFuzz(self, maxIterations=None): try: iterations = 0 while (maxIterations == None or maxIterations >= iterations): self.runFuzzer() iterations += 1 except: self.cleanupProcesses() raise def runFuzzer(self): self.remoteInit() # Ensure Fennec isn't running if self.isFennecRunning(): self.stopFennec() # Clean all existing minidumps if not self.clearMinidumps(): raise Exception("Failed to clean existing minidumps") # Start our HTTP server for serving the fuzzer code self.HTTPProcess = self.startHTTPServer() # Start all loggers self.startLoggers() # Start Fennec self.startFennec() # Even though the program is already running, we should grant it # some extra time to load the fuzzer source and start running, # so it isn't directly diagnosed as hanging time.sleep(10); logSize = 0 hangDetected = False forceRestart = False while(self.isFennecRunning() and not self.checkLoggingThreads()): time.sleep(self.config.runTimeout) if not os.path.exists(self.logFile): raise Exception("Logfile not present. If you are using websockets, this could indicate a network problem.") # Poor man's hang detection. Yes, this is a bad # idea, just for the sake of proof-of-concept newLogSize = os.path.getsize(self.logFile) if (logSize == newLogSize): hangDetected = True break else: logSize = newLogSize if newLogSize > self.config.maxLogSize: forceRestart = True break if hangDetected or forceRestart: self.stopFennec() self.stopLoggers() print "Hang detected or running too long, restarting..." else: try: # Fennec died or a logger found something checkCrashDump = True crashUUID = None minidump = None # If Fennec is still running, stop it now if self.isFennecRunning(): checkCrashDump = False self.stopFennec() # Terminate our logging processes first self.stopLoggers() if checkCrashDump: dumps = self.getMinidumps() if (len(dumps) > 1): raise Exception("Multiple dumps detected!") if (len(dumps) < 1): raise Exception("No crash dump detected!") if not self.fetchMinidump(dumps[0]): raise Exception("Failed to fetch minidump with UUID " + dumps[0]) crashUUID = dumps[0] # Copy logfiles shutil.copy2(self.syslogFile, dumps[0] + ".syslog") shutil.copy2(self.logFile, dumps[0] + ".log") minidump = Minidump(dumps[0] + ".dmp", self.config.libDir) else: # We need to generate an arbitrary ID here crashUUID = str(uuid.uuid4()) # Copy logfiles shutil.copy2(self.syslogFile, crashUUID + ".syslog") shutil.copy2(self.logFile, crashUUID + ".log") print "Crash detected. Reproduction logfile stored at: " + crashUUID + ".log" if checkCrashDump: crashTrace = minidump.getCrashTrace() crashType = minidump.getCrashType() print "Crash type: " + crashType print "Crash backtrace:" print "" print crashTrace else: print "Crash type: Abnormal behavior (e.g. Assertion violation)" self.triager.process(crashUUID, minidump, crashUUID + ".syslog", crashUUID + ".log") except Exception, e: print "Error during crash processing: " print traceback.format_exc() self.HTTPProcess.terminate() return
class ADBFuzz: def __init__(self, cfgFile): self.config = ADBFuzzConfig(cfgFile) self.HTTPProcess = None self.logProcesses = [] self.logThreads = [] self.remoteInitialized = None self.triager = Triager(self.config) # Seed RNG with localtime random.seed() def deploy(self, packageFile, prefFile): self.dm = DeviceManagerADB(self.config.remoteAddr, 5555) # Install a signal handler that shuts down our external programs on SIGINT signal.signal(signal.SIGINT, self.signal_handler) self.dm.updateApp(packageFile) # Standard init stuff self.appName = self.dm.packageName self.appRoot = self.dm.getAppRoot(self.appName) self.profileBase = self.appRoot + "/files/mozilla" # Ensure no Fennec instance is running self.stopFennec() # Start Fennec, so a profile is created if this is the first install self.startFennec(blank=True) # Grant some time to create profile time.sleep(self.config.runTimeout * 2) # Stop Fennec again self.stopFennec() # Now try to get the profile(s) self.profiles = self.getProfiles() if (len(self.profiles) == 0): print "Failed to detect any valid profile, aborting..." return 1 self.defaultProfile = self.profiles[0] if (len(self.profiles) > 1): print "Multiple profiles detected, using the first: " + self.defaultProfile # Push prefs.js to profile self.dm.pushFile( prefFile, self.profileBase + "/" + self.defaultProfile + "/prefs.js") # Try to install addon if requested by configuration if self.config.addon != None: self.ensureExtensionInstalled(self.config.addon) print "Successfully deployed package." def reset(self, prefFile): self.dm = DeviceManagerADB(self.config.remoteAddr, 5555) # Install a signal handler that shuts down our external programs on SIGINT signal.signal(signal.SIGINT, self.signal_handler) # Standard init stuff self.appName = self.dm.packageName self.appRoot = self.dm.getAppRoot(self.appName) self.profileBase = self.appRoot + "/files/mozilla" # Ensure no Fennec instance is running self.stopFennec() # Now try to get the old profile(s) self.profiles = self.getProfiles() for profile in self.profiles: self.dm.removeDir(self.profileBase + "/" + profile) self.dm.removeFile(self.profileBase + "/profiles.ini") # Start Fennec, so a new profile is created self.startFennec(blank=True) # Grant some time to create profile time.sleep(self.config.runTimeout * 2) # Stop Fennec again self.stopFennec() # Now try to get the profile(s) again self.profiles = self.getProfiles() if (len(self.profiles) == 0): print "Failed to detect any valid profile, aborting..." return 1 self.defaultProfile = self.profiles[0] if (len(self.profiles) > 1): print "Multiple profiles detected, using the first: " + self.defaultProfile # Push prefs.js to profile self.dm.pushFile( prefFile, self.profileBase + "/" + self.defaultProfile + "/prefs.js") # Try to install addon if requested by configuration if self.config.addon != None: self.ensureExtensionInstalled(self.config.addon) print "Successfully resetted profile." def remoteInit(self): if (self.remoteInitialized != None): return self.dm = DeviceManagerADB(self.config.remoteAddr, 5555) self.appName = self.dm.packageName self.appRoot = self.dm.getAppRoot(self.appName) self.profileBase = self.appRoot + "/files/mozilla" self.profiles = self.getProfiles() # Install a signal handler that shuts down our external programs on SIGINT signal.signal(signal.SIGINT, self.signal_handler) if (len(self.profiles) == 0): print "Failed to detect any valid profile, aborting..." return 1 self.defaultProfile = self.profiles[0] if (len(self.profiles) > 1): print "Multiple profiles detected, using the first: " + self.defaultProfile # Workaround for bug 754575. Avoid using DeviceManagerADB's "removeDir" because # that calls "rm" on every single entry which takes a lot of additional time. print "Purging possible cache leftover directories..." self.dm.runCmd([ 'shell', 'rm', '-r', self.profileBase + "/" + self.defaultProfile + "/Cache.Trash*" ]).communicate() self.remoteInitialized = True def signal_handler(self, signal, frame): self.cleanupProcesses() sys.exit(0) def cleanupProcesses(self): self.stopFennec() if (self.HTTPProcess != None): try: self.HTTPProcess.terminate() except: pass if (self.logProcesses != None): try: self.stopLoggers() except: pass def loopFuzz(self, maxIterations=None): try: iterations = 0 while (maxIterations == None or maxIterations >= iterations): self.runFuzzer() iterations += 1 except: self.cleanupProcesses() raise def runFuzzer(self): self.remoteInit() # Ensure Fennec isn't running if self.isFennecRunning(): self.stopFennec() # Clean all existing minidumps if not self.clearMinidumps(): raise Exception("Failed to clean existing minidumps") # Start our HTTP server for serving the fuzzer code self.HTTPProcess = self.startHTTPServer() # Start all loggers self.startLoggers() # Start Fennec self.startFennec() # Even though the program is already running, we should grant it # some extra time to load the fuzzer source and start running, # so it isn't directly diagnosed as hanging time.sleep(10) logSize = 0 hangDetected = False forceRestart = False while (self.isFennecRunning() and not self.checkLoggingThreads()): time.sleep(self.config.runTimeout) if not os.path.exists(self.logFile): raise Exception( "Logfile not present. If you are using websockets, this could indicate a network problem." ) # Poor man's hang detection. Yes, this is a bad # idea, just for the sake of proof-of-concept newLogSize = os.path.getsize(self.logFile) if (logSize == newLogSize): hangDetected = True break else: logSize = newLogSize if newLogSize > self.config.maxLogSize: forceRestart = True break if hangDetected or forceRestart: self.stopFennec() self.stopLoggers() print "Hang detected or running too long, restarting..." else: try: # Fennec died or a logger found something checkCrashDump = True crashUUID = None minidump = None # If Fennec is still running, stop it now if self.isFennecRunning(): checkCrashDump = False self.stopFennec() # Terminate our logging processes first self.stopLoggers() if checkCrashDump: dumps = self.getMinidumps() if (len(dumps) > 1): raise Exception("Multiple dumps detected!") if (len(dumps) < 1): raise Exception("No crash dump detected!") if not self.fetchMinidump(dumps[0]): raise Exception("Failed to fetch minidump with UUID " + dumps[0]) crashUUID = dumps[0] # Copy logfiles shutil.copy2(self.syslogFile, dumps[0] + ".syslog") shutil.copy2(self.logFile, dumps[0] + ".log") minidump = Minidump(dumps[0] + ".dmp", self.config.libDir) else: # We need to generate an arbitrary ID here crashUUID = str(uuid.uuid4()) # Copy logfiles shutil.copy2(self.syslogFile, crashUUID + ".syslog") shutil.copy2(self.logFile, crashUUID + ".log") print "Crash detected. Reproduction logfile stored at: " + crashUUID + ".log" if checkCrashDump: crashTrace = minidump.getCrashTrace() crashType = minidump.getCrashType() print "Crash type: " + crashType print "Crash backtrace:" print "" print crashTrace else: print "Crash type: Abnormal behavior (e.g. Assertion violation)" self.triager.process(crashUUID, minidump, crashUUID + ".syslog", crashUUID + ".log") except Exception, e: print "Error during crash processing: " print traceback.format_exc() self.HTTPProcess.terminate() return