Package FuzzManager :: Package FTB :: Package Signatures :: Module CrashSignature
[hide private]
[frames] | no frames]

Source Code for Module FuzzManager.FTB.Signatures.CrashSignature

  1  ''' 
  2  Crash Signature 
  3   
  4  Represents a crash signature as specified in https://wiki.mozilla.org/Security/CrashSignatures 
  5   
  6  @author:     Christian Holler (:decoder) 
  7   
  8  @license: 
  9   
 10  This Source Code Form is subject to the terms of the Mozilla Public 
 11  License, v. 2.0. If a copy of the MPL was not distributed with this 
 12  file, You can obtain one at http://mozilla.org/MPL/2.0/. 
 13   
 14  @contact:    choller@mozilla.com 
 15  ''' 
 16   
 17  import json 
 18  import difflib 
 19  from FTB.Signatures import JSONHelper 
 20  from FTB.Signatures.Symptom import Symptom, TestcaseSymptom, StackFramesSymptom 
 21  import FTB.Signatures 
 22   
 23  from collections import OrderedDict 
24 25 -class CrashSignature():
26 - def __init__(self, rawSignature):
27 ''' 28 Constructor 29 30 @type rawSignature: string 31 @param rawSignature: A JSON-formatted string representing the crash signature 32 ''' 33 34 # For now, we store the original raw signature and hand it out for 35 # conversion to String. This is fine as long as our Signature object 36 # is immutable. Later, we should implement a method that actually 37 # serializes this object back to JSON as it is. 38 # 39 self.rawSignature = rawSignature 40 self.symptoms = [] 41 42 try: 43 obj = json.loads(rawSignature, object_pairs_hook=OrderedDict) 44 except ValueError, e: 45 raise RuntimeError("Invalid JSON: %s" % e) 46 47 # Get the symptoms objects (mandatory) 48 if "symptoms" in obj: 49 symptoms = JSONHelper.getArrayChecked(obj, "symptoms", True) 50 if len(symptoms) == 0: 51 raise RuntimeError("Signature must have at least one symptom.") 52 53 for rawSymptomsObj in symptoms: 54 self.symptoms.append(Symptom.fromJSONObject(rawSymptomsObj)) 55 else: 56 raise RuntimeError('Missing mandatory top-level key "symptoms".') 57 58 # Get some optional lists 59 self.platforms = JSONHelper.getArrayChecked(obj, "platforms") 60 self.operatingSystems = JSONHelper.getArrayChecked(obj, "operatingSystems") 61 self.products = JSONHelper.getArrayChecked(obj, "products")
62 63 @staticmethod
64 - def fromFile(signatureFile):
65 with open(signatureFile, 'r') as sigFd: 66 return CrashSignature(sigFd.read())
67
68 - def __str__(self):
69 return self.rawSignature
70
71 - def matches(self, crashInfo):
72 ''' 73 Match this signature against the given crash information 74 75 @type crashInfo: CrashInfo 76 @param crashInfo: The crash info to match the signature against 77 78 @rtype: bool 79 @return: True if the signature matches, False otherwise 80 ''' 81 if self.platforms != None and not crashInfo.configuration.platform in self.platforms: 82 return False 83 84 if self.operatingSystems != None and not crashInfo.configuration.os in self.operatingSystems: 85 return False 86 87 if self.products != None and not crashInfo.configuration.product in self.products: 88 return False 89 90 for symptom in self.symptoms: 91 if not symptom.matches(crashInfo): 92 return False 93 94 return True
95
96 - def matchRequiresTest(self):
97 ''' 98 Check if the signature requires a testcase to match. 99 100 This method can be used to avoid attaching a testcase to the crashInfo 101 before matching, avoiding unnecessary I/O on testcase files. 102 103 @rtype: bool 104 @return: True if the signature requires a testcase to match 105 ''' 106 for symptom in self.symptoms: 107 if isinstance(symptom, TestcaseSymptom): 108 return True 109 110 return False
111
112 - def getDistance(self, crashInfo):
113 distance = 0 114 115 for symptom in self.symptoms: 116 if isinstance(symptom, StackFramesSymptom): 117 symptomDistance = symptom.diff(crashInfo)[0] 118 if symptomDistance != None: 119 distance += symptomDistance 120 else: 121 # If we can't find the distance, assume worst-case 122 distance += len(symptom.functionNames) 123 else: 124 if not symptom.matches(crashInfo): 125 distance +=1 126 127 return distance
128
129 - def fit(self, crashInfo):
130 sigObj = {} 131 sigSymptoms = [] 132 133 sigObj['symptoms'] = sigSymptoms 134 135 if self.platforms: 136 sigObj['platforms'] = self.platforms 137 138 if self.operatingSystems: 139 sigObj['operatingSystems'] = self.operatingSystems 140 141 if self.products: 142 sigObj['products'] = self.products 143 144 symptomsDiff = self.getSymptomsDiff(crashInfo) 145 146 for symptomDiff in symptomsDiff: 147 if symptomDiff['offending']: 148 if 'proposed' in symptomDiff: 149 sigSymptoms.append(symptomDiff['proposed'].jsonobj) 150 else: 151 sigSymptoms.append(symptomDiff['symptom'].jsonobj) 152 153 if not sigSymptoms: 154 return None 155 156 return CrashSignature(json.dumps(sigObj, indent=2))
157
158 - def getSymptomsDiff(self, crashInfo):
159 symptomsDiff = [] 160 for symptom in self.symptoms: 161 if symptom.matches(crashInfo): 162 symptomsDiff.append({ 'offending' : False, 'symptom' : symptom }) 163 else: 164 # Special-case StackFramesSymptom because we would like to get a fine-grained 165 # view on the offending parts *inside* that symptom. By calling matchWithDiff, 166 # we annotate internals of the symptom with distance information to display. 167 if isinstance(symptom, StackFramesSymptom): 168 proposedSymptom = symptom.diff(crashInfo)[1] 169 if proposedSymptom: 170 symptomsDiff.append({ 'offending' : True, 'symptom' : symptom, 'proposed' : proposedSymptom }) 171 continue 172 173 symptomsDiff.append({ 'offending' : True, 'symptom' : symptom }) 174 return symptomsDiff
175
176 - def getSignatureUnifiedDiffTuples(self, crashInfo):
177 diffTuples = [] 178 179 newRawCrashSignature = self.fit(crashInfo) 180 oldLines = self.rawSignature.splitlines() 181 newLines = [] 182 if newRawCrashSignature: 183 newLines = newRawCrashSignature.rawSignature.splitlines() 184 context = max(len(oldLines),len(newLines)) 185 186 signatureDiff = difflib.unified_diff(oldLines, newLines, n=context) 187 188 for diffLine in signatureDiff: 189 if diffLine.startswith('+++') or diffLine.startswith('---') or diffLine.startswith('@@') or not diffLine.strip(): 190 continue 191 192 diffTuples.append((diffLine[0],diffLine[1:])) 193 194 return diffTuples
195