main.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518
  1. #!/usr/bin/env python
  2. #
  3. # Copyright (C) 2019 Intel Corporation. All rights reserved.
  4. # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
  5. #
  6. # coding=utf-8
  7. from sched import scheduler
  8. from flask import Flask, request, jsonify, send_file
  9. from flask_sqlalchemy import SQLAlchemy
  10. from flask_cors import CORS, cross_origin
  11. from datetime import datetime, timedelta
  12. from urllib.parse import quote
  13. from pathlib import Path
  14. from flask_caching import Cache
  15. from flask_apscheduler import APScheduler
  16. from zipfile import ZipFile, ZIP_DEFLATED
  17. from io import BytesIO
  18. from multiprocessing import Process
  19. import os
  20. import sys
  21. import copy
  22. import getopt
  23. import signal
  24. import psutil
  25. import shutil
  26. import subprocess
  27. current_dir = Path(__file__).parent.resolve()
  28. wasm_mutator_dir = current_dir.parent.parent
  29. fuzz_dir = wasm_mutator_dir.parent
  30. app = Flask(__name__)
  31. # cors
  32. cors = CORS(app)
  33. app.config['CORS_HEADERS'] = 'Content-Type'
  34. cache = Cache(app, config={'CACHE_TYPE': 'simple'})
  35. scheduler = APScheduler()
  36. # sqlite URI
  37. WIN = sys.platform.startswith('win')
  38. if WIN:
  39. prefix = 'sqlite:///'
  40. else:
  41. prefix = 'sqlite:////'
  42. app.config['SQLALCHEMY_DATABASE_URI'] = os.getenv(
  43. 'DATABASE_URL', prefix + os.path.join(app.root_path, 'data.db'))
  44. app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
  45. app.secret_key = os.urandom(12).hex()
  46. db = SQLAlchemy(app)
  47. def to_json(inst, cls):
  48. ret_dict = {}
  49. for i in cls.__table__.columns:
  50. value = getattr(inst, i.name)
  51. if isinstance(value, datetime):
  52. value = value.strftime('%Y-%m-%d %H:%M:%S')
  53. ret_dict[i.name] = value
  54. return ret_dict
  55. class Fuzzing(db.Model):
  56. __tablename__ = 'fuzzing_task'
  57. id = db.Column(db.Integer, autoincrement=True,
  58. primary_key=True, nullable=False)
  59. repo = db.Column(db.String(200), nullable=False, default='')
  60. branch = db.Column(db.String(200), nullable=False, default='')
  61. build_args = db.Column(db.String(200), nullable=False, default='')
  62. fuzz_time = db.Column(db.Integer, default=0)
  63. wamr_commit = db.Column(
  64. db.String(200), nullable=False, default='')
  65. data = db.Column(db.JSON)
  66. start_time = db.Column(db.DateTime, nullable=False,
  67. default=datetime.utcnow() + timedelta(hours=8))
  68. end_time = db.Column(db.DateTime)
  69. status = db.Column(db.Integer, default=2)
  70. @property
  71. def serialize(self):
  72. return to_json(self, self.__class__)
  73. class TaskError(db.Model):
  74. __tablename__ = 'task_error'
  75. id = db.Column(db.Integer, autoincrement=True,
  76. primary_key=True, nullable=False)
  77. fuzzing_id = db.Column(db.Integer, db.ForeignKey("fuzzing_task.id"))
  78. name = db.Column(db.String(200), nullable=False, default='')
  79. std_out = db.Column(db.Text, default='')
  80. data = db.Column(db.JSON)
  81. comment = db.Column(db.JSON)
  82. create_time = db.Column(db.DateTime, nullable=False,
  83. default=datetime.utcnow() + timedelta(hours=8))
  84. update_time = db.Column(db.DateTime, nullable=False,
  85. default=datetime.utcnow() + timedelta(hours=8))
  86. status = db.Column(db.Integer, default=1)
  87. @property
  88. def serialize(self):
  89. return to_json(self, self.__class__)
  90. def to_data(data):
  91. data['data']['id'] = data['id']
  92. return data['data']
  93. def error_count(data):
  94. error = len(TaskError.query.filter(
  95. TaskError.fuzzing_id == data.get('id'), TaskError.status.in_([1, 2])).all())
  96. end_error = len(TaskError.query.filter(
  97. TaskError.fuzzing_id == data.get('id'), TaskError.status == 0).all())
  98. data['error'] = error
  99. data['end_error'] = end_error
  100. return data
  101. def getstatusoutput(cmd):
  102. try:
  103. data = subprocess.check_output(
  104. cmd, shell=True, text=True, stderr=subprocess.STDOUT, executable='/bin/bash')
  105. exitcode = 0
  106. except subprocess.CalledProcessError as ex:
  107. data = ex.output
  108. exitcode = ex.returncode
  109. if data[-1:] == '\n':
  110. data = data[:-1]
  111. return exitcode, data
  112. def get_wamr_commit(repo_root_dir):
  113. wamr_repo_dir = repo_root_dir / 'wamr'
  114. cmd = f'cd {wamr_repo_dir} && git log -1 --pretty=format:"%h"'
  115. status, resp = getstatusoutput(cmd)
  116. if status != 0:
  117. return "-"
  118. return resp
  119. @app.route('/get_list', methods=["GET"])
  120. @cross_origin()
  121. def show_fuzz_list():
  122. data = request.args
  123. id = data.get('id')
  124. if id:
  125. all_error = TaskError.query.filter(
  126. TaskError.fuzzing_id == id).with_entities(TaskError.id, TaskError.fuzzing_id,
  127. TaskError.create_time, TaskError.data,
  128. TaskError.name, TaskError.status,
  129. TaskError.update_time, TaskError.comment).order_by(TaskError.status.desc(), TaskError.update_time.desc(), TaskError.id.desc()).all()
  130. data_message = [{'id': error['id'], "fuzzing_id": error['fuzzing_id'],
  131. "name": error['name'], "data": error['data'],
  132. 'create_time': error['create_time'].strftime('%Y-%m-%d %H:%M:%S'),
  133. 'update_time': error['update_time'].strftime('%Y-%m-%d %H:%M:%S'),
  134. 'status': error['status'], "comment": error["comment"]} for error in all_error]
  135. return jsonify({"status": 1, "results": data_message, 'msg': "success", "count": len(data_message)})
  136. else:
  137. all_fuzz = Fuzzing.query.order_by(
  138. Fuzzing.status.desc(), Fuzzing.end_time.desc(), Fuzzing.id.desc()).all()
  139. data_message = list(map(lambda i: i.serialize, all_fuzz))
  140. data_message = list(map(error_count, data_message))
  141. return jsonify({"status": 1, "results": data_message, 'msg': "success", "count": len(data_message)})
  142. @app.route('/new_fuzzing', methods=["POST"])
  143. @cross_origin()
  144. def New_fuzzing():
  145. data = request.json
  146. repo = data.get('repo', '')
  147. branch = data.get('branch', '')
  148. build_args = data.get('build_args', '')
  149. fuzz_time = data.get('fuzz_time', 0)
  150. if not repo or not branch:
  151. return jsonify({"status": 0, "result": "", 'msg': "repo and branch are required !"})
  152. fuzz = Fuzzing(repo=repo, branch=branch,
  153. build_args=build_args, fuzz_time=fuzz_time, start_time=datetime.utcnow() + timedelta(hours=8))
  154. db.session.add(fuzz)
  155. db.session.commit()
  156. fuzz_cmd = wasm_mutator_dir / \
  157. 'workspace' / f'build_{fuzz.id}'
  158. Path(fuzz_cmd).mkdir(exist_ok=True)
  159. os.system(
  160. f'cd {fuzz_cmd} && git clone --branch {branch} --depth=1 {repo} wamr')
  161. if not Path(fuzz_cmd / 'wamr').exists():
  162. print('------ error: clone repo not folder exists ------')
  163. # curd.set_error_status_to(list(map(lambda x: x.id, error_list)), db)
  164. # Fuzzing.query.filter_by(id=fuzz.id).delete()
  165. fuzz.data = {'error': "Clone repo Error"}
  166. db.session.commit()
  167. return jsonify({"status": 0, "result": "", "msg": "Clone repo Error"})
  168. wamr_path_parent = fuzz_dir.parent.parent
  169. wamr_path = wamr_path_parent / 'wamr'
  170. wamr_path_to = wamr_path_parent / f'wamr_{fuzz.id}'
  171. wamr_folder = Path(wamr_path).exists()
  172. try:
  173. if wamr_folder:
  174. os.rename(wamr_path, wamr_path_to)
  175. except Exception as e:
  176. print(f'------ error: fail wamr folder rename, error: {e} ------')
  177. return jsonify({"status": 0, "result": "", "msg": "fail wamr folder rename"})
  178. try:
  179. os.system(f'ln -s {fuzz_cmd / "wamr"} {wamr_path_parent}')
  180. except Exception as e:
  181. print('------ error: fail wamr_repo to wamr ------')
  182. if wamr_folder:
  183. os.rename(wamr_path_to, wamr_path)
  184. return jsonify({"status": 0, "result": "", "msg": "fail wamr_repo to wamr"})
  185. os.system(
  186. f'cd {fuzz_cmd} && cmake .. -DCUSTOM_MUTATOR=1 {build_args} && make -j$(nproc)')
  187. os.system(f'rm -rf {wamr_path}')
  188. if wamr_folder:
  189. os.rename(wamr_path_to, wamr_path)
  190. os.system(
  191. f"ln -s {wasm_mutator_dir / 'build' / 'CORPUS_DIR'} {fuzz_cmd}")
  192. cmd_max_time = ''
  193. if fuzz_time != 0:
  194. cmd_max_time = f"-max_total_time={fuzz_time}"
  195. cmd = f'cd {fuzz_cmd} && ./wasm_mutator_fuzz CORPUS_DIR {cmd_max_time} -ignore_crashes=1 -fork=2'
  196. process_tcpdump = subprocess.Popen(
  197. cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, preexec_fn=os.setsid)
  198. commit_id = get_wamr_commit(fuzz_cmd)
  199. fuzz.data = {"pid": process_tcpdump.pid}
  200. fuzz.status = 1
  201. fuzz.wamr_commit = commit_id
  202. db.session.commit()
  203. return jsonify({'status': 1, 'msg': 'success', 'result': ''})
  204. @app.route('/end_fuzzing', methods=["POST"])
  205. @cross_origin()
  206. def End_fuzzing():
  207. data = request.json
  208. id = data.get('id')
  209. if not id:
  210. return jsonify({'status': 0, 'msg': 'id must pass'})
  211. fuzz_model = Fuzzing.query.get(id)
  212. pid = fuzz_model.data.get('pid')
  213. try:
  214. os.killpg(pid, signal.SIGTERM)
  215. except Exception as e:
  216. pass
  217. fuzz_model.status = 0
  218. fuzz_model.end_time = datetime.utcnow() + timedelta(hours=8)
  219. db.session.commit()
  220. return jsonify({'status': 1, 'msg': 'success'})
  221. @scheduler.task('interval', id="run_task", seconds=5, misfire_grace_time=60)
  222. def scheduler_run_task():
  223. fuzz_query = Fuzzing.query.filter(Fuzzing.status == 1).all()
  224. for fuzz in fuzz_query:
  225. # if fuzz.fuzz_time == 0:
  226. # continue
  227. if fuzz.data.get('pid', 0) not in psutil.pids() or psutil.Process(fuzz.data.get('pid', 0)).status() == "zombie":
  228. fuzz.status = 0
  229. fuzz.end_time = datetime.utcnow() + timedelta(hours=8)
  230. db.session.commit()
  231. for fuzz in fuzz_query:
  232. all_error = TaskError.query.filter(
  233. TaskError.fuzzing_id == fuzz.id).with_entities(TaskError.name).all()
  234. fuzz_cmd = wasm_mutator_dir / \
  235. 'workspace' / f'build_{fuzz.id}'
  236. dir_list = filter(lambda x: x.startswith(
  237. 'crash-') or x.startswith('oom-') or x.startswith('slow-unit-') or x.startswith('leak-'), os.listdir(fuzz_cmd))
  238. all_error = [error['name'] for error in all_error]
  239. dir_list = list(filter(lambda x: x not in all_error, dir_list))
  240. for dir in dir_list:
  241. cmd = f'cd {fuzz_cmd} && ./wasm_mutator_fuzz {dir}'
  242. status, resp = getstatusoutput(cmd)
  243. task_error = TaskError(name=dir, std_out=resp, fuzzing_id=fuzz.id,
  244. create_time=datetime.utcnow() + timedelta(hours=8))
  245. db.session.add(task_error)
  246. db.session.commit()
  247. @app.route("/get_error_out", methods=["GET"])
  248. def get_error_out():
  249. data = request.args
  250. id = data.get('id')
  251. if id:
  252. error = TaskError.query.get(id)
  253. data_message = error.serialize
  254. return jsonify({"status": 1, "result": data_message, 'msg': "success"})
  255. return jsonify({"status": 0, "results": [], 'msg': "Error"})
  256. @app.route("/get_error_txt", methods=["GET"])
  257. def get_error_txt():
  258. data = request.args
  259. id = data.get('id')
  260. if not id:
  261. return jsonify({"status": 0, "results": [], 'msg': "Error"})
  262. error = TaskError.query.get(id)
  263. fuzz_cmd = wasm_mutator_dir / \
  264. 'workspace' / f'build_{error.fuzzing_id}'
  265. file_cmd = fuzz_cmd / error.name
  266. response = send_file(file_cmd, as_attachment=True,
  267. attachment_filename=error.name)
  268. response.headers['Content-Disposition'] += "; filename*=utf-8''{}".format(
  269. error.name)
  270. return response
  271. @app.route("/set_commend", methods=["POST"])
  272. def set_commend():
  273. data = request.json
  274. id = data.get('id')
  275. comment = data.get('comment')
  276. if not id:
  277. return jsonify({"status": 0, "results": [], 'msg': "Error"})
  278. try:
  279. TaskError.query.filter(TaskError.id.in_(
  280. id)).update({"comment": comment, "update_time": datetime.utcnow() + timedelta(hours=8)})
  281. db.session.commit()
  282. except Exception as e:
  283. return jsonify({"status": 0, "results": [], 'msg': "Update error"})
  284. return jsonify({"status": 1, "results": [], 'msg': "Success"})
  285. @app.route("/get_cases_zip", methods=["POST"])
  286. def get_cases_zip():
  287. data = request.json
  288. id_list = data.get('id')
  289. task_query = TaskError.query.filter(TaskError.id.in_(id_list)).all()
  290. memory_file = BytesIO()
  291. with ZipFile(memory_file, "w", ZIP_DEFLATED) as zf:
  292. for task_error in task_query:
  293. fuzz_cmd = wasm_mutator_dir / \
  294. 'workspace' / f'build_{task_error.fuzzing_id}'
  295. file_cmd = fuzz_cmd / task_error.name
  296. zf.write(str(file_cmd), arcname=task_error.name)
  297. memory_file.seek(0)
  298. return send_file(memory_file, attachment_filename='cases.zip', as_attachment=True)
  299. class processClass:
  300. def __init__(self, fuzz_cmd, restart_cmd, error_query):
  301. p = Process(target=self.run, args=(fuzz_cmd, restart_cmd, error_query))
  302. p.daemon = True # Daemonize it
  303. p.start() # Start the execution
  304. def run(self, fuzz_cmd, restart_cmd, error_query):
  305. for error in error_query:
  306. shutil.copyfile(fuzz_cmd / error.name, restart_cmd / error.name)
  307. commit = get_wamr_commit(restart_cmd)
  308. cmd = f"cd {restart_cmd} && ./wasm_mutator_fuzz {error.name}"
  309. status, resp = getstatusoutput(cmd)
  310. data = copy.deepcopy(error.data)
  311. if type(data) == dict:
  312. data['wamr_commit'] = commit
  313. else:
  314. data = {'wamr_commit': commit}
  315. error.data = data
  316. error.status = 0 if status == 0 else 1
  317. error.update_time = datetime.utcnow() + timedelta(hours=8)
  318. error.std_out = resp if status != 0 else error.std_out
  319. db.session.commit()
  320. #
  321. # This might take several minutes to complete
  322. @app.route("/error_restart", methods=["POST"])
  323. def error_restart():
  324. data = request.json
  325. id_list = data.get('id')
  326. repo = data.get('repo')
  327. branch = data.get('branch')
  328. build_args = data.get('build_args', '')
  329. if len(id_list) == [] or repo == "":
  330. return jsonify({"status": 0, "msg": 'parameter is incorrect'})
  331. run_status = cache.get('runStatus')
  332. if run_status:
  333. return jsonify({"status": 0, "results": [], 'msg': "There are already tasks in progress"})
  334. task_query = TaskError.query.filter(TaskError.id.in_(id_list)).all()
  335. fuzzing_id = task_query[0].fuzzing_id
  336. fuzz_cmd = wasm_mutator_dir / \
  337. 'workspace' / f'build_{fuzzing_id}'
  338. restart_cmd = wasm_mutator_dir / \
  339. 'workspace' / f'error_restart_build_{fuzzing_id}'
  340. if not Path(restart_cmd).exists():
  341. Path(restart_cmd).mkdir(exist_ok=True)
  342. os.system(
  343. f'cd {restart_cmd} && git clone --branch {branch} --depth=1 {repo} wamr')
  344. if not Path(restart_cmd / 'wamr').exists():
  345. print('------ error: clone repo not folder exists ------')
  346. # fuzz.data = {'error': "Clone repo Error"}
  347. db.session.commit()
  348. return jsonify({"status": 0, "result": "", "msg": "Clone repo Error"})
  349. wamr_path_parent = fuzz_dir.parent.parent
  350. wamr_path = wamr_path_parent / 'wamr'
  351. wamr_path_to = wamr_path_parent / f'wamr_restart_{fuzzing_id}'
  352. wamr_folder = Path(wamr_path).exists()
  353. try:
  354. if wamr_folder:
  355. os.rename(wamr_path, wamr_path_to)
  356. except Exception as e:
  357. print(f'------ error: fail wamr folder rename, error: {e} ------')
  358. return jsonify({"status": 0, "result": "", "msg": "fail wamr folder rename"})
  359. try:
  360. os.system(f'ln -s {restart_cmd / "wamr"} {wamr_path_parent}')
  361. except Exception as e:
  362. print('------ error: fail wamr_repo to wamr ------')
  363. if wamr_folder:
  364. os.rename(wamr_path_to, wamr_path)
  365. return jsonify({"status": 0, "result": "", "msg": "fail wamr_repo to wamr"})
  366. os.system(
  367. f'cd {restart_cmd} && cmake .. -DCUSTOM_MUTATOR=1 {build_args} && make -j$(nproc)')
  368. os.system(f'rm -rf {wamr_path}')
  369. if wamr_folder:
  370. os.rename(wamr_path_to, wamr_path)
  371. cache.delete('runStatus')
  372. TaskError.query.filter(TaskError.id.in_(id_list)).update(
  373. {'status': 2, "update_time": datetime.utcnow() + timedelta(hours=8)})
  374. db.session.commit()
  375. processClass(fuzz_cmd, restart_cmd, task_query)
  376. return jsonify({"status": 1, "result": "", "msg": "Pending"})
  377. @app.route('/upload_case', methods=['POST'])
  378. def do_upload():
  379. file = request.files['file']
  380. filename = file.filename
  381. upload_file_cmd = wasm_mutator_dir / "upload_path"
  382. build_cmd = wasm_mutator_dir / "build" / "CORPUS_DIR"
  383. if not Path(upload_file_cmd).exists():
  384. Path(upload_file_cmd).mkdir(exist_ok=True)
  385. file.save(str(upload_file_cmd / filename))
  386. file.save(str(build_cmd / filename))
  387. # os.system(f"copy {upload_file_cmd / file} {build_cmd / file}")
  388. return jsonify({"status": 1, "result": "", "msg": "success"})
  389. @app.route('/remove_case', methods=['POST'])
  390. def remove_case():
  391. file = request.json
  392. filename = file.get('filename')
  393. print(filename)
  394. upload_file_cmd = wasm_mutator_dir / "upload_path" / filename
  395. build_cmd = wasm_mutator_dir / "build" / "CORPUS_DIR" / filename
  396. os.system(f'rm -rf "{upload_file_cmd}" "{build_cmd}"')
  397. return jsonify({"status": 1, "result": "", "msg": "success"})
  398. if __name__ == '__main__':
  399. scheduler.init_app(app)
  400. scheduler.start()
  401. os.chdir(wasm_mutator_dir)
  402. os.system('./smith_wasm.sh 100')
  403. os.chdir(current_dir)
  404. try:
  405. opts, args = getopt.getopt(sys.argv[1:], "hp:d:", [
  406. "help", "port=", "debug="])
  407. except getopt.GetoptError:
  408. print(
  409. 'test_arg.py -h <host> -p <port> -d <debug? True: False>')
  410. print(
  411. ' or: test_arg.py --host=<host> --port=<port> --debug=<True: False>')
  412. print('''
  413. host: default[0.0.0.0]
  414. port: default[16667]
  415. debug: default[False]
  416. ''')
  417. sys.exit(2)
  418. run_dict = {
  419. "host": "0.0.0.0",
  420. "port": 16667,
  421. "debug": False
  422. }
  423. for opt, arg in opts:
  424. if opt in ("-h", "--help"):
  425. print(
  426. 'test_arg.py -h <host> -p <port> -d <debug? True: False>')
  427. print(
  428. ' or: test_arg.py --host=<host> --port=<port> --debug=<True: False>')
  429. print('''
  430. host: default[0.0.0.0]
  431. port: default[16667]
  432. debug: default[False]
  433. ''')
  434. sys.exit()
  435. elif opt in ('-h', '--host'):
  436. run_dict['host'] = arg
  437. elif opt in ("-p", "--port"):
  438. run_dict['port'] = int(arg)
  439. elif opt in ("-d", "--debug"):
  440. run_dict['debug'] = bool(arg)
  441. app.run(**run_dict)