def test_get_exit_surface_metrics(self): # Arrange expected = { Call('', './src/greetings.c', Env.C, Gran.FILE): { 'points': None, 'proximity': None, 'surface_coupling': None }, Call('', './src/helloworld.c', Env.C, Gran.FILE): { 'points': None, 'proximity': None, 'surface_coupling': None } } for i in expected: # Act actual = self.target.get_exit_surface_metrics(i) # Assert self.assertIsInstance(actual, dict) self.assertTrue('points' in actual) self.assertTrue('proximity' in actual) self.assertTrue('surface_coupling' in actual) if expected[i]['points'] is None: self.assertEqual(expected[i]['points'], actual['points']) else: self.assertCountEqual(expected[i]['points'], actual['points']) self.assertEqual(expected[i]['proximity'], actual['proximity']) self.assertEqual( expected[i]['surface_coupling'], actual['surface_coupling'] )
def test_load_call_graph_nodes(self): # Arrange expected = [ Call('GreeterSayHiTo', './src/helloworld.c', Env.C), Call('greet_a', './src/helloworld.c', Env.C), Call('greet', './src/greetings.c', Env.C), Call('recursive_b', './src/greetings.c', Env.C), Call('new_Greeter', './src/helloworld.c', Env.C), Call('recursive_a', './src/greetings.c', Env.C), Call('addInt', './src/helloworld.c', Env.C), Call('greet_b', './src/helloworld.c', Env.C), Call('main', './src/helloworld.c', Env.C), Call('GreeterSayHi', './src/helloworld.c', Env.C) ] # Act graph = self.target.load_call_graph() actual = graph.nodes() match = (all([i in actual for i in expected]) and all([i in expected for i in actual])) # Assert self.assertCountEqual(expected, actual) for (_, attrs) in graph.nodes(data=True): self.assertTrue('tested' in attrs) self.assertTrue('defense' not in attrs) self.assertTrue('dangerous' not in attrs) self.assertTrue('vulnerable' not in attrs)
def test_nodes(self): # Arrange expected = [ Call('GreeterSayHiTo', './src/helloworld.c', Environments.C), Call('greet_a', './src/helloworld.c', Environments.C), Call('greet', './src/greetings.c', Environments.C), Call('recursive_b', './src/greetings.c', Environments.C), Call('new_Greeter', './src/helloworld.c', Environments.C), Call('recursive_a', './src/greetings.c', Environments.C), Call('addInt', './src/helloworld.c', Environments.C), Call('greet_b', './src/helloworld.c', Environments.C), Call('main', './src/helloworld.c', Environments.C), Call('GreeterSayHi', './src/helloworld.c', Environments.C) ] # Act actual = self.target.nodes # Assert self.assertCountEqual(expected, [i for (i, _) in actual]) for (_, attrs) in actual: self.assertTrue('tested' in attrs) self.assertTrue('defense' not in attrs) self.assertTrue('dangerous' not in attrs) self.assertTrue('vulnerable' not in attrs)
def load_call_graph(self): """ Description. Comments. Returns: A call graph. """ call_graph = nx.DiGraph() if self.app_packages: condition_to_add = lambda line: line.startswith("M:") and self._contains_call_in_package(line) else: condition_to_add = lambda line: line.startswith("M:") with open(self.source) as raw_call_graph: # line is like this: # M:com.example.kevin.helloandroid.Greeter:sayHelloInSpanish (M)java.lang.StringBuilder:toString. for line in raw_call_graph: if condition_to_add(line): caller, callee = line.split(" ") call_graph.add_edge(Call.from_javacg(caller), Call.from_javacg(callee)) return call_graph
def test_assign_page_rank(self): # Arrange expected = { Call('greet_a', './src/helloworld.c', Environments.C): 0.12789113913543346, Call('main', './src/helloworld.c', Environments.C): 0.2671500027198321, Call('greet', './src/greetings.c', Environments.C): 0.08747233694516567, Call('addInt', './src/helloworld.c', Environments.C): 0.052845759753949534, Call('greet_b', './src/helloworld.c', Environments.C): 0.12789113913543343, Call('GreeterSayHiTo', './src/helloworld.c', Environments.C): 0.052845759753949534, Call('recursive_b', './src/greetings.c', Environments.C): 0.0891061715241686, Call('recursive_a', './src/greetings.c', Environments.C): 0.08910617152416858, Call('new_Greeter', './src/helloworld.c', Environments.C): 0.052845759753949534, Call('GreeterSayHi', './src/helloworld.c', Environments.C): 0.052845759753949534 } # Act self.target.assign_page_rank() actual = nx.get_node_attributes(self.target.call_graph, 'page_rank') # Assert self.assertEqual(len(expected), len(actual)) for i in expected: self.assertAlmostEqual(expected[i], actual[i])
def test_load_call_graph_edges(self): # Act expected = [(Call('main', 'multigprof.c', Env.C), Call('factorial', 'multigprof.c', Env.C)), (Call('factorial', 'multigprof.c', Env.C), Call('main', 'multigprof.c', Env.C)), (Call('factorial', 'multigprof.c', Env.C), Call('factorial', 'multigprof.c', Env.C)), (Call('main', 'multigprof.c', Env.C), Call('fibonacci', 'multigprof.c', Env.C)), (Call('fibonacci', 'multigprof.c', Env.C), Call('main', 'multigprof.c', Env.C))] # Act test_graph = self.test_loader.load_call_graph() edges = test_graph.edges() all_edges_found = all([c in edges for c in expected]) # Assert self.assertEqual(len(expected), len(edges)) self.assertTrue(all_edges_found) for (u, v, attrs) in test_graph.edges(data=True): self.assertTrue('gprof' in attrs) self.assertTrue('cflow' not in attrs) self.assertTrue('call' in attrs or 'return' in attrs)
def load_call_graph(self, granularity=Granularity.FUNC): """ Description. Comments. Returns: A call graph. """ call_graph = nx.DiGraph() if self.app_packages: def condition_to_add(line): return (line.startswith("M:") and self._contains_call_in_package(line)) else: def condition_to_add(line): return line.startswith("M:") with open(self.source) as raw_call_graph: # line is like this: # M:com.example.kevin.helloandroid.Greeter:sayHelloInSpanish (M)jav # a.lang.StringBuilder:toString. for line in raw_call_graph: if condition_to_add(line): caller, callee = line.split(" ") call_graph.add_edge(Call.from_javacg(caller, granularity), Call.from_javacg(callee, granularity)) return call_graph
def test_equal(self): # Arrange cflow_line = 'getchar()' test_call_1 = Call.from_cflow(cflow_line) test_call_2 = Call.from_cflow(cflow_line) # Assert self.assertEqual(test_call_1, test_call_2)
def test_load_call_graph_nodes(self): # Arrange expected = [ Call('opt_progress', './ffmpeg_opt.c', Environments.C), Call('get_preset_file_2', './ffmpeg_opt.c', Environments.C), Call('dump_attachment', './ffmpeg_opt.c', Environments.C), Call('open_output_file', './ffmpeg_opt.c', Environments.C), Call('init_input', './libavformat/utils.c', Environments.C), Call('avio_open2', './libavformat/aviobuf.c', Environments.C), Call('ffurl_open', './libavformat/avio.c', Environments.C), Call('biquad_s16', './libavfilter/af_biquads.c', Environments.C), Call('av_log', './libavutil/log.c', Environments.C) ] # Act graph = self.target.load_call_graph() actual = graph.nodes() # Assert self.assertCountEqual(expected, actual) for (_, attrs) in graph.nodes(data=True): self.assertTrue('tested' in attrs) self.assertTrue('defense' not in attrs) self.assertTrue('dangerous' not in attrs) self.assertTrue('vulnerable' not in attrs)
def test_not_equal(self): # Arrange cflow_line_1 = 'getchar()' cflow_line_2 = ( 'xstrdup() <char *xstrdup (const char *str) at ./cyrus/lib/xmalloc' '.c:89> (R):') test_call_1 = Call.from_cflow(cflow_line_1) test_call_2 = Call.from_cflow(cflow_line_2) # Assert self.assertNotEqual(test_call_1, test_call_2)
def test_get_nodes(self): # Arrange expected = [ Call('', './src/greetings.c', Env.C, Gran.FILE), Call('', './src/helloworld.c', Env.C, Gran.FILE), ] # Act actual = self.target.get_nodes(attribute='exit') # Assert self.assertCountEqual(expected, actual)
def test_node_attribute_tested(self): # Arrange expected = [ Call('', './src/greetings.c', Env.C, Gran.FILE), Call('', './src/helloworld.c', Env.C, Gran.FILE) ] # Act actual = [i for (i, attrs) in self.target.nodes if 'tested' in attrs] # Assert self.assertCountEqual(expected, actual)
def test_exit_points(self): # Arrange expected = [ Call('', './src/greetings.c', Env.C, Gran.FILE), Call('', './src/helloworld.c', Env.C, Gran.FILE) ] # Act actual = self.target.exit_points # Assert self.assertCountEqual(expected, actual)
def test_not_equal(self): # Arrange cflow_line_1 = 'getchar()' cflow_line_2 = ( 'xstrdup() <char *xstrdup (const char *str) at ./cyrus/lib/xmalloc' '.c:89> (R):' ) test_call_1 = Call.from_cflow(cflow_line_1) test_call_2 = Call.from_cflow(cflow_line_2) # Assert self.assertNotEqual(test_call_1, test_call_2)
def test_get_ancestors(self): # Arrange expected = [ Call('', './src/helloworld.c', Env.C, Gran.FILE), ] call = Call('', './src/greetings.c', Env.C, Gran.FILE) # Act actual = self.target.get_ancestors(call) # Assert self.assertCountEqual(expected, actual)
def test_get_shortest_path_length_with_entry(self): # Arrange expected = {Call('', './src/helloworld.c', Env.C, Gran.FILE): 1} call = Call('', './src/greetings.c', Env.C, Gran.FILE) # Act actual = self.target.get_shortest_path_length(call, 'entry') # Assert self.assertCountEqual(expected, actual) self.assertAlmostEqual(stat.mean(expected.values()), stat.mean(expected.values()), places=4)
def test_get_shortest_path_length_with_entry(self): # Arrange expected = {Call('greet_b', './src/helloworld.c', Environments.C): 2} call = Call('functionPtr', './src/helloworld.c', Environments.C) # Act actual = self.target.get_shortest_path_length(call, 'entry') # Assert self.assertCountEqual(expected, actual) self.assertAlmostEqual(stat.mean(expected.values()), stat.mean(expected.values()), places=4)
def test_load_call_graph_edges_file_granularity(self): # Arrange expected = [ ( Call('', './src/helloworld.c', Env.C, Gran.FILE), Call('', './src/helloworld.c', Env.C, Gran.FILE) ), ( Call('', './src/greetings.c', Env.C, Gran.FILE), Call('', './src/helloworld.c', Env.C, Gran.FILE) ), ( Call('', './src/helloworld.c', Env.C, Gran.FILE), Call('', './src/greetings.c', Env.C, Gran.FILE) ), ( Call('', './src/greetings.c', Env.C, Gran.FILE), Call('', './src/greetings.c', Env.C, Gran.FILE) ) ] # Act test_graph = self.test_loader.load_call_graph(granularity=Gran.FILE) edges = test_graph.edges() all_edges_found = all([c in edges for c in expected]) # Assert self.assertEqual(len(expected), len(edges)) self.assertTrue(all_edges_found) for (u, v, attrs) in test_graph.edges(data=True): self.assertTrue('cflow' in attrs) self.assertTrue('gprof' not in attrs) self.assertTrue('call' in attrs or 'return' in attrs)
def test_get_degree(self): # Arrange expected = { Call('', './src/helloworld.c', Env.C, Gran.FILE): (2, 2), Call('', './src/greetings.c', Env.C, Gran.FILE): (2, 2) } # Act actual = self.target.get_degree() match = all([actual[i] == expected[i] for i in actual]) # Assert self.assertEqual(len(expected), len(actual)) self.assertTrue(match)
def test_get_page_rank(self): # Arrange expected = { Call('', './src/helloworld.c', Env.C, Gran.FILE): 0.5, Call('', './src/greetings.c', Env.C, Gran.FILE): 0.5 } # Act actual = self.target.get_page_rank() # Assert self.assertEqual(len(expected), len(actual)) for i in expected: self.assertAlmostEqual(expected[i], actual[i])
def test_assign_weights(self): # Arrange expected = { ( Call('', './src/helloworld.c', Env.C, Gran.FILE), Call('', './src/helloworld.c', Env.C, Gran.FILE) ): 75, ( Call('', './src/helloworld.c', Env.C, Gran.FILE), Call('', './src/greetings.c', Env.C, Gran.FILE) ): 75, ( Call('', './src/greetings.c', Env.C, Gran.FILE), Call('', './src/helloworld.c', Env.C, Gran.FILE) ): 25, ( Call('', './src/greetings.c', Env.C, Gran.FILE), Call('', './src/greetings.c', Env.C, Gran.FILE) ): 75 } # Act self.target.assign_weights() actual = nx.get_edge_attributes(self.target.call_graph, 'weight') # Assert self.assertCountEqual(expected, actual) for i in expected: self.assertEqual(expected[i], actual[i], msg=i)
def test_edges(self): # Arrange expected = [ ( Call('', './src/helloworld.c', Env.C, Gran.FILE), Call('', './src/greetings.c', Env.C, Gran.FILE) ), ( Call('', './src/greetings.c', Env.C, Gran.FILE), Call('', './src/helloworld.c', Env.C, Gran.FILE) ), ( Call('', './src/greetings.c', Env.C, Gran.FILE), Call('', './src/greetings.c', Env.C, Gran.FILE) ), ( Call('', './src/helloworld.c', Env.C, Gran.FILE), Call('', './src/helloworld.c', Env.C, Gran.FILE) ) ] # Act actual = self.target.edges # Assert self.assertCountEqual(expected, [(i, j) for (i, j, _) in actual]) for (_, _, attrs) in actual: self.assertTrue('gprof' in attrs) self.assertTrue('cflow' not in attrs) self.assertTrue('call' in attrs or 'return' in attrs)
def test_assign_page_rank(self): # Arrange expected = { Call('', './src/helloworld.c', Env.C, Gran.FILE): 0.525, Call('', './src/greetings.c', Env.C, Gran.FILE): 0.475 } # Act self.target.assign_page_rank() actual = nx.get_node_attributes(self.target.call_graph, 'page_rank') # Assert self.assertEqual(len(expected), len(actual)) for i in expected: self.assertAlmostEqual(expected[i], actual[i])
def test_is_output(self): # Arrange cflow_line = 'printf()' test_call = Call.from_cflow(cflow_line) # Assert self.assertTrue(test_call.is_output())
def test_function_signature_only_name(self): # Arrange cflow_line = 'printf()' test_call = Call.from_cflow(cflow_line) # Assert self.assertEqual('', test_call.function_signature)
def test_identity_function_name(self): # Arrange cflow_line = 'printf()' test_call = Call.from_cflow(cflow_line) # Assert self.assertEqual('printf', test_call.identity)
def test_is_input(self): # Arrange cflow_line = 'getchar()' test_call = Call.from_cflow(cflow_line) # Assert self.assertTrue(test_call.is_input())
def test_is_not_input(self): # Arrange cflow_line = 'printf()' test_call = Call.from_cflow(cflow_line) # Assert self.assertFalse(test_call.is_input())
def test_is_not_output(self): # Arrange cflow_line = 'getchar()' test_call = Call.from_cflow(cflow_line) # Assert self.assertFalse(test_call.is_output())
def test_identity_function_name_file_granularity(self): # Arrange cflow_line = 'printf()' test_call = Call.from_cflow(cflow_line, granularity=Granularity.FILE) # Assert self.assertEqual('', test_call.identity)
def test_get_entry_point_reachability_non_entry(self): # Arrange call = Call('', './src/greetings.c', Env.C, Gran.FILE) # Assert self.assertRaises(Exception, self.target.get_entry_point_reachability, call)
def test_in_stdlib(self): # Arrange cflow_line = 'printf()' test_call = Call.from_cflow(cflow_line) # Assert self.assertTrue(test_call.in_stdlib())
def load_call_graph(self): """ Generates the Call Graph as a networkx.DiGraph object. Invokes the call grap generation software (cflow) and creates a networkx.DiGraph instance that represents the analyzed source code's Call Graph. Args: is_reverse: Boolean specifying whether the graph generation software (cflow) should use the reverse algorithm. Returns: None """ call_graph = nx.DiGraph() is_first_line = True parent = Stack() if os.path.isfile(self.source): raw_call_graph = open(self.source) readline = lambda: raw_call_graph.readline() elif os.path.isdir(self.source): raw_call_graph = self._exec_cflow(self.is_reverse) readline = lambda: raw_call_graph.stdout.readline().decode(encoding='UTF-8') while True: line = readline() if line == '': break current = Call.from_cflow(line) if not is_first_line: if current.level > previous.level: parent.push(previous) elif current.level < previous.level: for t in range(previous.level - current.level): parent.pop() if parent.top: call_graph.add_node(current, {'tested': False}) call_graph.add_node(parent.top, {'tested': False}) # Edge weight can be any arbitrary number greater than # that used as the weight for the edges in the gprof # call graph. if not self.is_reverse: call_graph.add_edge(parent.top, current, {'cflow': 'cflow'}, weight=1) else: call_graph.add_edge(current, parent.top, {'cflow': 'cflow'}, weight=1) previous = current is_first_line = False return call_graph
def test_is_not_input_no_leaf(self): # Arrange cflow_line = ( 'xstrdup() <char *xstrdup (const char *str) at ./cyrus/lib/xmalloc' '.c:89> (R):' ) test_call = Call.from_cflow(cflow_line) # Assert self.assertFalse(test_call.is_input())
def test_function_signature_full(self): # Arrange cflow_line = ( 'xstrdup() <char *xstrdup (const char *str) at ./cyrus/lib/xmalloc' '.c:89> (R):' ) test_call = Call.from_cflow(cflow_line) # Assert self.assertEqual('./cyrus/lib/xmalloc.c', test_call.function_signature)
def test_identity_full_file_granularity(self): # Arrange cflow_line = ( 'xstrdup() <char *xstrdup (const char *str) at ./cyrus/lib/xmalloc' '.c:89> (R):' ) test_call = Call.from_cflow(cflow_line, granularity=Granularity.FILE) # Assert self.assertEqual('./cyrus/lib/xmalloc.c', test_call.identity)
def test_identity_full(self): # Arrange cflow_line = ( 'xstrdup() <char *xstrdup (const char *str) at ./cyrus/lib/xmalloc' '.c:89> (R):' ) test_call = Call.from_cflow(cflow_line) # Assert self.assertEqual('xstrdup ./cyrus/lib/xmalloc.c', test_call.identity)
def load_call_graph(self): """ Generates the Call Graph as a networkx.DiGraph object. Invokes the call grap generation software (cflow) and creates a networkx.DiGraph instance that represents the analyzed source code's Call Graph. Args: is_reverse: Boolean specifying whether the graph generation software (cflow) should use the reverse algorithm. Returns: None """ call_graph = nx.DiGraph() header_passed = False entry = None is_caller = True callers = list() callees = list() # line_count = 0 with open(self.source) as raw_call_graph: while True: line = raw_call_graph.readline() # line_count += 1 # print(line_count) # print(line) if not header_passed: if line == GprofLoader.header: header_passed = True continue else: # if header_passed: if self.is_entry_line(line): try: entry = Call.from_gprof(line) except ValueError as e: raise e call_graph.add_node(entry, {'tested': False}) is_caller = False elif line == GprofLoader.separator: if len(callers) > 0 or len(callees) > 0: call_graph.node[entry]['tested'] = True for caller in callers: call_graph.add_node(caller, {'tested': True}) # Edge weight can be any arbitrary number lesser # than that used as the weight for the edges # in the cflow call graph. call_graph.add_edge(caller, entry, {'gprof': 'gprof'}, weight=0.5) callers.clear() for callee in callees: call_graph.add_node(callee, {'tested': True}) # Edge weight can be any arbitrary number lesser # than that used as the weight for the edges # in the cflow call graph. call_graph.add_edge(entry, callee, {'gprof': 'gprof'}, weight=0.5) callees.clear() is_caller = True elif line == GprofLoader.eof: break else: try: if is_caller: callers.append(Call.from_gprof(line)) else: callees.append(Call.from_gprof(line)) except ValueError as e: self._error_messages.append("Error: " + str(e) + " Input line: " + line) return call_graph
def load_call_graph(self, granularity=Granularity.FUNC): """Load a call graph generated by cflow. If necessary, the static call graph generation utility (cflow) is invoked to generate the call graph before attempting to load it. Parameters ---------- granularity : str The granularity at which the call graph must be loaded. See attacksurfacemeter.granularity.Granularity for available choices. Returns ------- call_graph : networkx.DiGraph An object representing the call graph. """ call_graph = nx.DiGraph() parent = Stack() raw_call_graph = None if os.path.isfile(self.source): raw_call_graph = open(self.source) elif os.path.isdir(self.source): raw_call_graph = self._exec_cflow() try: previous = Call.from_cflow(raw_call_graph.readline(), granularity) for line in raw_call_graph: current = Call.from_cflow(line, granularity) if current.level > previous.level: parent.push(previous) elif current.level < previous.level: for t in range(previous.level - current.level): parent.pop() if parent.top: caller = callee = None entry = exit = dangerous = defense = False if self.is_reverse: caller = current callee = parent.top else: caller = parent.top callee = current (caller_attrs, callee_attrs) = utilities.get_node_attrs( 'cflow', caller, callee, self.defenses, self.vulnerabilities ) call_graph.add_node(caller, caller_attrs) if callee_attrs is not None: call_graph.add_node(callee, callee_attrs) # Adding the edge caller -- callee attrs = {'cflow': None, 'call': None} call_graph.add_edge(caller, callee, attrs) # Adding the edge callee -- caller with the assumption # that every call must return attrs = {'cflow': None, 'return': None} call_graph.add_edge(callee, caller, attrs) previous = current finally: if raw_call_graph: raw_call_graph.close() return call_graph