Skip to content

Commit 81577af

Browse files
init
0 parents  commit 81577af

18 files changed

+2440
-0
lines changed

README.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
## FlowFusion
2+
3+
FlowFusion is a fully automated fuzzing tool to discover various memory errors (detected by sanitizers) in the PHP interpreter.
4+
5+
The core idea behing FlowFusion is to leverage **dataflow** as an efficient representation of test cases (.phpt files) maintained by PHP developers, merging two (or more) test cases to produce fused test cases with more complex code semantics. We connect two (or more) test cases via interleaving their dataflows, i.e., bring the code context from one test case to another. This enables interactions among existing test cases, which are mostly the unit tests verifying one single functionality, making fused test cases interesting with merging code semantics.
6+
7+
> Why dataflow? Around 96.1% phpt files exhibit sequential control flow, executing without branching. This finding suggests that control flow contributes little to the overall code semantics. Therefore, we recognize that the code semantics of the official test programs can be effectively represented using only dataflow.
8+
9+
The search space of FlowFusion is huge, which means it might take months to cover all possible combinations. Reasons for huge search space are three-fold: (i) two random combinations of around 20,000 test cases can generate 400,000,000 test cases, we can combine even more; (ii) the interleaving has randomness, given two test cases, there could be multiple way to connect them; and (iii) FlowFusion also mutates the test case, fuzzes the runtime environment/configuration like JIT.
10+
11+
FlowFusion additionally fuzzes all defined functions and class methods using the code contexts of fused test cases. Available functions, classes, methods are pre-collected and stored in sqlite3 with necessary information like the number of parameters.
12+
13+
FlowFusion will never be out-of-dated if phpt files keep updating. Any new single test can bring thousands of new fused tests.
14+
15+
Below are instructions to fuzz the latest commit of php-src
16+
17+
* start docker, we suggest fuzzing inside docker (user:phpfuzz pwd:phpfuzz)
18+
```
19+
docker run --name phpfuzz -dit 0599jiangyc/flowfusion:latest bash
20+
```
21+
and goto the docker
22+
```
23+
docker exec -it phpfuzz bash
24+
```
25+
26+
* inside the docker, clone flowfusion in /home/phpfuzz/WorkSpace
27+
```
28+
git clone https://github.com/php/flowfusion.git
29+
```
30+
or
31+
```
32+
git clone [email protected]:php/flowfusion.git
33+
```
34+
then (this takes some minutes)
35+
```
36+
cd flowfusion; ./prepare.sh
37+
```
38+
and start fuzzing (tmux)
39+
```
40+
tmux new-session -s fuzz 'bash'; python3 main.py
41+
```
42+
43+
* you can use the following command to view bugs:
44+
```
45+
find ./bugs -name "*.out" | xargs grep -E "Sanitizer|Assertion "
46+
```

backup/empty

Whitespace-only changes.

bot.py

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
# this file is to automatically generate bug reports
2+
3+
import os
4+
import re
5+
import json
6+
import time
7+
import html
8+
import signal
9+
from reduce import reduce_php
10+
11+
12+
def handler(signum, frame):
13+
raise Exception("end of time")
14+
15+
test_root = "/home/phpfuzz/WorkSpace/flowfusion"
16+
17+
plain_text_bug_report = """
18+
================
19+
PHP Bug Report
20+
21+
**PHP Commit:**
22+
{php_commit}
23+
24+
**Compiling Flags:**
25+
{php_config}
26+
27+
**Crash Site:**
28+
{crashsite}
29+
30+
**Keywords:**
31+
{keyword}
32+
33+
**Reproducing config:**
34+
{reducedconfig}
35+
36+
**Reproducing PHP (best-effort reduced):**
37+
{reducedphp}
38+
39+
**Output:**
40+
{bugout}
41+
42+
**Reproducing PHP:**
43+
{bugphp}
44+
45+
**Reproducing PHPT:**
46+
{bugphpt}
47+
48+
**This report is automatically generated via FlowFusion**
49+
================
50+
"""
51+
52+
53+
# copy dependencies for reproducing
54+
if os.path.exists("/tmp/flowfusion_reproducing/")==False:
55+
os.mkdir("/tmp/flowfusion_reproducing/")
56+
os.system(f"cp -R {test_root}/phpt_deps/* /tmp/flowfusion_reproducing/")
57+
58+
# Change directory to the "bugs" folder
59+
os.chdir(f"{test_root}/bugs")
60+
61+
# Find all '.out' files, search for 'Sanitizer' (excluding 'leak') and store the results in a log file
62+
os.system("find ./ -name '*.out' | xargs grep -E 'Sanitizer|Assertion ' | grep -v 'leak' > /tmp/flowfusion_bug.log")
63+
64+
os.chdir(f"{test_root}")
65+
66+
print("Filtering finished")
67+
68+
# Initialize lists to store unique bug identifiers and bug information
69+
identifiers = []
70+
bugs_info = []
71+
72+
if not os.path.exists(f'{test_root}/bug_reports/'):
73+
os.mkdir(f'{test_root}/bug_reports/')
74+
75+
if os.path.exists(f'{test_root}/bug_reports/bugs.json'):
76+
with open(f'{test_root}/bug_reports/bugs.json', 'r') as file:
77+
bugs_info = json.load(file)
78+
identifiers = [bug['identifier'] for bug in bugs_info]
79+
80+
for each_existing_bug in bugs_info:
81+
each_existing_bug['new'] = 0
82+
83+
# Read the contents of the bug log file
84+
with open('/tmp/flowfusion_bug.log', 'r') as f:
85+
bugs = f.read().strip('\n').split('\n')
86+
87+
# Regular expression to extract identifier patterns from the log
88+
identifier_pattern = r"(\/php-src\/[^:]+:\d+)"
89+
90+
91+
# last_modified_time = os.path.getmtime(file_path)
92+
93+
# Loop through each bug entry in the log
94+
for eachbug in bugs:
95+
# Search for the identifier using the regular expression
96+
identifier = re.search(identifier_pattern, eachbug)
97+
if identifier:
98+
identifier = identifier.group()
99+
# If the identifier is new, add it to the identifiers list and create a bug entry
100+
if identifier not in identifiers:
101+
identifiers.append(identifier)
102+
bug_folder = eachbug.split('/')[1]
103+
last_modified_time = os.path.getmtime(f"{test_root}/bugs/{bug_folder}")
104+
readable_time = time.ctime(last_modified_time)
105+
bugs_info.append({
106+
"bugID": len(bugs_info) + 1, # Assign a unique ID to each bug
107+
"identifier": identifier, # Store the identifier (file path and line number)
108+
"details": [eachbug.split('/')[1]],
109+
"mtime": readable_time,
110+
"new": 1
111+
})
112+
else:
113+
# If the identifier already exists, update the existing bug entry
114+
bug_idx = identifiers.index(identifier)
115+
bug_folder = eachbug.split('/')[1]
116+
mtime = bugs_info[bug_idx]["mtime"]
117+
parsed_time = time.strptime(mtime, "%a %b %d %H:%M:%S %Y")
118+
# Convert struct_time to a timestamp (seconds since epoch)
119+
timestamp = time.mktime(parsed_time)
120+
last_modified_time = os.path.getmtime(f"{test_root}/bugs/{bug_folder}")
121+
if last_modified_time > timestamp:
122+
readable_time = time.ctime(last_modified_time)
123+
bugs_info[bug_idx]["mtime"] = readable_time
124+
125+
# Convert the bug information into a JSON format for further processing
126+
# Load the list of bug information into a JSON-compatible Python dictionary
127+
data = json.loads(str(bugs_info).replace("'", '"'))
128+
129+
# Pretty-print the JSON data to a file for easy readability
130+
with open(f'{test_root}/bug_reports/bugs.json', 'w') as file:
131+
json.dump(data, file, indent=4)
132+
133+
#with open("/tmp/flowfusion-php-commit","r") as file:
134+
# php_commit = file.read()
135+
136+
php_commit = "test"
137+
138+
#with open(f"{test_root}/php-src/config.log","r") as file:
139+
# while True:
140+
# line = file.readline()
141+
# if "./configure" in line:
142+
# php_config = line.strip(' ').strip('$')
143+
# break
144+
145+
php_config = "test"
146+
147+
with open(f"{test_root}/bug_reports/bugs.json", 'r') as file:
148+
data = json.load(file)
149+
150+
if os.path.exists(f"{test_root}/bugs")==False:
151+
print("Please run in flowfusion folder")
152+
exit()
153+
154+
errors = ["stack-overflow","stack-underflow","heap-buffer-overflow","null pointer","integer overflow","heap-use-after-free","SEGV","core dumped"]
155+
156+
# Accessing the parsed data
157+
for bug in data:
158+
upload_bug_folder_name = bug['identifier'].split('/php-src/')[1].replace('/','_').replace('.','_').replace(':','_')
159+
# if bug['new']==0 and os.path.exists(f"{test_root}/../flowfusion-php.github.io/{upload_bug_folder_name}"):
160+
# # sed -i -E 's/this bug has been detected for [0-9]+ times/this bug has been detected for 2 times/g' ./sapi_phpdbg_phpdbg_bp_c_132/index.html
161+
# continue
162+
print(f"analyzing and uploading {upload_bug_folder_name}")
163+
bug_folder = f"./bugs/{bug['details'][0]}/"
164+
165+
# get bugout
166+
f = open(f"{bug_folder}/test.out", "r", encoding="iso_8859_1")
167+
bugout = f.read()
168+
f.close()
169+
170+
# get keywords
171+
keywords = []
172+
for error in errors:
173+
if error in bugout:
174+
keywords.append(error)
175+
176+
dangerous = 0
177+
# if "heap-buffer-overflow" in keywords or "heap-use-after-free" in keywords:
178+
# dangerous = 1
179+
180+
# get bugphp
181+
f = open(f"{bug_folder}/test.php", "r")
182+
bugphp = f.read()
183+
f.close()
184+
185+
# get bugphpt
186+
f = open(f"{bug_folder}/test.phpt", "r")
187+
bugphpt = f.read()
188+
f.close()
189+
190+
# get bugsh
191+
f = open(f"{bug_folder}/test.sh", "r")
192+
bugsh = f.read()
193+
f.close()
194+
195+
bug_outputs = ["UndefinedBehaviorSanitizer: undefined-behavior", "AddressSanitizer", "core dumped"]
196+
# get reducedphp
197+
os.system(f"cp {bug_folder}/test.php /tmp/flowfusion_reproducing/")
198+
bug_output = ""
199+
for each in bug_outputs:
200+
if each in bugout:
201+
bug_output = each
202+
break
203+
bug_config = ""
204+
for eachline in bugsh.split('\n'):
205+
if "gdb --args" in eachline:
206+
bug_config = eachline.split(' -d ')[1:]
207+
bug_config[-1] = bug_config[-1].split(' -f ')[0]
208+
bug_config = ' -d '+' -d '.join(bug_config)
209+
break
210+
211+
signal.signal(signal.SIGALRM, handler)
212+
# set 5 mins for reducing one bug
213+
signal.alarm(300)
214+
try:
215+
reducedphp, reduced_config = reduce_php(
216+
testpath = "/tmp/flowfusion_reproducing/test.php",
217+
phppath = f"{test_root}/php-src/sapi/cli/php",
218+
config = bug_config,
219+
bug_output = bug_output
220+
)
221+
except:
222+
reducedphp = 'reducing timeout ..'
223+
reduced_config = 'reducing timeout ..'
224+
225+
bug_report = plain_text_bug_report.format(
226+
php_commit = php_commit,
227+
php_config = php_config,
228+
crashsite = bug['identifier'],
229+
keyword = str(keywords),
230+
bugout = bugout,
231+
bugphp = bugphp,
232+
bugphpt = bugphpt,
233+
reducedconfig = reduced_config,
234+
reducedphp = reducedphp
235+
)
236+
237+
f = open(f"{test_root}/bug_reports/{upload_bug_folder_name}.md", "w")
238+
f.write(bug_report)
239+
f.close()
240+
241+

0 commit comments

Comments
 (0)