Skip to content

Commit 8ab7ecc

Browse files
committed
Add functions reached in runtime to Function Profile
Signed-off-by: Arthur Chan <[email protected]>
1 parent ec7ab6c commit 8ab7ecc

File tree

8 files changed

+108
-4
lines changed

8 files changed

+108
-4
lines changed

src/fuzz_introspector/analyses/function_call_analyser.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ def analysis_func(self,
269269
if coverage.is_func_lineno_hit(parent_func, lineno):
270270
fuzzer_hit = True
271271
break
272-
list_of_fuzzer_covered = fd.reached_by_fuzzers if fuzzer_hit else [
272+
list_of_fuzzer_covered = fd.reached_by_fuzzers_combined if fuzzer_hit else [
273273
""
274274
]
275275

src/fuzz_introspector/analyses/public_candidate_analyser.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,10 +153,10 @@ def _sort_functions(
153153
return sorted(
154154
functions,
155155
key=lambda item:
156-
(bool(item.reached_by_fuzzers), item.is_enum,
156+
(bool(item.reached_by_fuzzers_combined), item.is_enum,
157157
proj_profile.get_func_hit_percentage(item.function_name), -item.
158158
function_depth, -item.cyclomatic_complexity, item.
159159
new_unreached_complexity, -item.arg_count, -(
160160
item.function_line_number_end - item.function_linenumber),
161-
len(item.reached_by_fuzzers)),
161+
len(item.reached_by_fuzzers_combined)),
162162
reverse=False)

src/fuzz_introspector/analyses/runtime_coverage_analysis.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ def analysis_func(self,
9595

9696
if funcname in proj_profile.all_functions:
9797
reached_by = str(proj_profile.all_functions[funcname].
98-
reached_by_fuzzers)
98+
reached_by_fuzzers_combined)
9999
else:
100100
reached_by = ""
101101

src/fuzz_introspector/datatypes/function_profile.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,11 @@ def __init__(self, elem: Dict[Any, Any]) -> None:
109109

110110
# These are set later.
111111
self.hitcount: int = 0
112+
self.hitcount_runtime: int = 0
113+
self.hitcount_combined: int = 0
112114
self.reached_by_fuzzers: List[str] = []
115+
self.reached_by_fuzzers_runtime: List[str] = []
116+
self.reached_by_fuzzers_combined: List[str] = []
113117
self.incoming_references: List[str] = []
114118
self.new_unreached_complexity: int = 0
115119
self.total_cyclomatic_complexity: int = 0
@@ -127,6 +131,8 @@ def to_dict(self, coverage: float = 0.0) -> Dict[str, Any]:
127131
'function_arguments': self.arg_types,
128132
'function_signature': self.signature,
129133
'reached_by_fuzzers': self.reached_by_fuzzers,
134+
'reached_by_fuzzers_runtime': self.reached_by_fuzzers_runtime,
135+
'reached_by_fuzzers_combined': self.reached_by_fuzzers_combined,
130136
'return_type': self.return_type,
131137
'runtime_coverage_percent': coverage,
132138
'source_line_begin': self.function_linenumber,

src/fuzz_introspector/datatypes/fuzzer_profile.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
import os
1717
import logging
1818

19+
from itertools import chain
20+
1921
from typing import (
2022
Any,
2123
Dict,
@@ -58,6 +60,7 @@ def __init__(self,
5860
self.introspector_data_file = cfg_file
5961

6062
self.functions_reached_by_fuzzer: List[str] = []
63+
self.functions_reached_by_fuzzer_runtime: List[str] = []
6164

6265
# Load calltree file
6366
self.fuzzer_callsite_calltree = cfg_load.data_file_read_calltree(
@@ -261,6 +264,54 @@ def reaches_func(self, func_name: str) -> bool:
261264
"""
262265
return func_name in self.functions_reached_by_fuzzer
263266

267+
def reaches_func_runtime(
268+
self, func_name: str,
269+
all_functions: list[function_profile.FunctionProfile]) -> bool:
270+
"""Identifies if the fuzzer dynamically reaches a given function in runtime
271+
272+
:param func_name: function to check for
273+
:param all_functions: the full FunctionsProfile list of the project
274+
:type func_name: str
275+
:type all_functions: list[FunctionProfile]
276+
277+
:rtype: bool
278+
:returns: `True` if the fuzzer reaches the function in runtime. `False`
279+
otherwise.
280+
"""
281+
# Prepare the functions reached by runtime using coverage report.
282+
if not self.functions_reached_by_fuzzer_runtime:
283+
if not self.coverage:
284+
logger.warning('No coverage report for retrieving runtime reached functions.')
285+
return False
286+
287+
for func in all_functions:
288+
func_name = func.function_name
289+
func_source = func.function_source_file
290+
func_start = func.function_linenumber
291+
func_end = func.function_line_number_end
292+
for line in range(func_start, func_end + 1):
293+
if self.coverage.is_file_lineno_hit(func_source, line):
294+
self.self.functions_reached_by_fuzzer_runtime.append(func_name)
295+
break
296+
297+
return func_name in self.functions_reached_by_fuzzer_runtime
298+
299+
def reaches_func_combined(
300+
self, func_name: str,
301+
all_functions: list[function_profile.FunctionProfile]) -> bool:
302+
"""Identifies if the fuzzer statically or dynamically reaches a given
303+
function in runtime
304+
305+
:param func_name: function to check for
306+
:type func_name: str
307+
308+
:rtype: bool
309+
:returns: `True` if the fuzzer reaches the function statically or in
310+
runtime. `False` otherwise.
311+
"""
312+
return (func_name in self.functions_reached_by_fuzzer or
313+
self.reaches_func_runtime(func_name, all_functions))
314+
264315
def correlate_executable_name(self, correlation_dict) -> None:
265316
for elem in correlation_dict['pairings']:
266317
if os.path.basename(self.introspector_data_file

src/fuzz_introspector/datatypes/project_profile.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,13 +85,33 @@ def __init__(self, profiles: List[fuzzer_profile.FuzzerProfile],
8585
continue
8686

8787
# populate hitcount and reached_by_fuzzers and whether it has been handled already
88+
# Also populate the reached_by_fuzzers_runtime and reached_by_fuzzers_combined
8889
for profile2 in profiles:
90+
# Statically reached functions
8991
if profile2.reaches_func(fd.function_name):
9092
fd.hitcount += 1
9193
fd.reached_by_fuzzers.append(profile2.identifier)
94+
95+
# Dynamically reached functions
96+
if profile2.reaches_func_runtime(
97+
fd.function_name, self.all_functions + self.all_constructors):
98+
fd.hitcount_runtime += 1
99+
fd.reached_by_fuzzers_runtime.append(profile2.identifier)
100+
101+
# Statically or dynamically reached functions
102+
if profile2.reaches_func_combined(
103+
fd.function_name, self.all_functions + self.all_constructors):
104+
fd.hitcount_combined += 1
105+
fd.reached_by_fuzzers_combined.append(profile2.identifier)
106+
92107
if fd.function_name not in self.all_functions:
93108
self.all_functions[fd.function_name] = fd
94109

110+
# Deduplicate the reached_by_fuzzer* list
111+
fd.reached_by_fuzzers = list(set(fd.reached_by_fuzzers))
112+
fd.reached_by_fuzzers_runtime = list(set(fd.reached_by_fuzzers_runtime))
113+
fd.reached_by_fuzzers_combined = list(set(fd.reached_by_fuzzers_combined))
114+
95115
# Gather complexity information about each function
96116
logger.info(
97117
"Gathering complexity and incoming references of each function")

src/fuzz_introspector/html_report.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,24 @@ def create_all_function_table(
110110
else:
111111
reached_by_fuzzers_row = "0"
112112

113+
collapsible_id = demangled_func_name + random_suffix
114+
if fd.hitcount_runtime > 0:
115+
reached_by_fuzzers_runtime_row = html_helpers.create_collapsible_element(
116+
str(fd.hitcount_runtime),
117+
str(fd.reached_by_fuzzers_runtime),
118+
collapsible_id)
119+
else:
120+
reached_by_fuzzers_runtime_row = "0"
121+
122+
collapsible_id = demangled_func_name + random_suffix
123+
if fd.hitcount_combined > 0:
124+
reached_by_fuzzers_combined_row = html_helpers.create_collapsible_element(
125+
str(fd.hitcount_combined),
126+
str(fd.reached_by_fuzzers_combined),
127+
collapsible_id)
128+
else:
129+
reached_by_fuzzers_combined_row = "0"
130+
113131
if fd.arg_count > 0:
114132
args_row = html_helpers.create_collapsible_element(
115133
str(fd.arg_count), str(fd.arg_types), collapsible_id + "2")
@@ -123,6 +141,9 @@ def create_all_function_table(
123141
"Args": args_row,
124142
"Function call depth": fd.function_depth,
125143
"Reached by Fuzzers": reached_by_fuzzers_row,
144+
"Reached by Fuzzers (Runtime)": reached_by_fuzzers_runtime_row,
145+
"Reached by Fuzzers (Statically and Runtime)":
146+
reached_by_fuzzers_combined_row,
126147
"collapsible_id": collapsible_id,
127148
"Fuzzers runtime hit": func_hit_at_runtime_row,
128149
"Func lines hit %": "%.5s" % (str(hit_percentage)) + "%",
@@ -150,6 +171,8 @@ def create_all_function_table(
150171
json_copy['Args'] = fd.arg_types
151172
json_copy['ArgNames'] = fd.arg_names
152173
json_copy['Reached by Fuzzers'] = fd.reached_by_fuzzers
174+
json_copy['Runtime reached by Fuzzers'] = fd.reached_by_fuzzers_runtime
175+
json_copy['Combined reached by Fuzzers'] = fd.reached_by_fuzzers_combined
153176
json_copy['return_type'] = fd.return_type
154177
json_copy['raw-function-name'] = fd.raw_function_name
155178
json_copy['callsites'] = fd.callsite
@@ -863,6 +886,8 @@ def create_html_report(introspection_proj: analysis.IntrospectionProject,
863886
json_copy['ArgNames'] = fd.arg_names
864887
json_copy['Function call depth'] = fd.function_depth
865888
json_copy['Reached by Fuzzers'] = fd.reached_by_fuzzers
889+
json_copy['Runtime reached by Fuzzers'] = fd.reached_by_fuzzers_runtime
890+
json_copy['Combined reached by Fuzzers'] = fd.reached_by_fuzzers_combined
866891
json_copy['collapsible_id'] = fd.function_name
867892
json_copy['return_type'] = fd.return_type
868893
json_copy['raw-function-name'] = fd.raw_function_name

tools/oss-fuzz-scanner/function_inspector.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ def print_function_details(project_name, functions_to_analyse: typing.List[str],
3232

3333
function_profile = all_functions[function_name]
3434
reached_by_fuzzer_count = len(function_profile.reached_by_fuzzers)
35+
reached_by_fuzzer_runtime_count = len(function_profile.reached_by_fuzzers_runtime)
36+
reached_by_fuzzer_combined_count = len(function_profile.reached_by_fuzzers_combined)
3537
code_coverage = introspector_project.proj_profile.get_func_hit_percentage(
3638
function_name)
3739

0 commit comments

Comments
 (0)