2024-07-15 17:00:53 +00:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
|
|
|
import configparser
|
|
|
|
import logging
|
|
|
|
import os
|
2024-09-30 02:43:53 +00:00
|
|
|
import re
|
2024-10-03 23:44:40 +00:00
|
|
|
import signal
|
2024-07-15 17:00:53 +00:00
|
|
|
import subprocess
|
2024-09-30 03:43:34 +00:00
|
|
|
from pathlib import Path
|
2024-10-04 05:31:17 +00:00
|
|
|
from time import sleep
|
2024-10-16 01:10:57 +00:00
|
|
|
from typing import List
|
2024-10-02 20:24:13 +00:00
|
|
|
|
2024-10-03 07:24:50 +00:00
|
|
|
from botocore.exceptions import ClientError
|
2024-10-03 07:02:11 +00:00
|
|
|
|
2024-07-15 17:00:53 +00:00
|
|
|
DEBUGGER = os.getenv("DEBUGGER", "")
|
|
|
|
FUZZER_ARGS = os.getenv("FUZZER_ARGS", "")
|
|
|
|
|
2024-09-30 03:43:34 +00:00
|
|
|
|
2024-09-30 02:43:53 +00:00
|
|
|
def report(source: str, reason: str, call_stack: list, test_unit: str):
|
2024-10-11 03:11:39 +00:00
|
|
|
logging.info("########### REPORT: %s %s %s", source, reason, test_unit)
|
|
|
|
logging.info("".join(call_stack))
|
|
|
|
logging.info("########### END OF REPORT ###########")
|
2024-09-30 02:43:53 +00:00
|
|
|
|
2024-09-30 03:43:34 +00:00
|
|
|
|
2024-09-30 04:02:25 +00:00
|
|
|
# pylint: disable=unused-argument
|
2024-09-30 02:43:53 +00:00
|
|
|
def process_fuzzer_output(output: str):
|
|
|
|
pass
|
|
|
|
|
2024-09-30 03:43:34 +00:00
|
|
|
|
2024-10-16 01:10:57 +00:00
|
|
|
def process_error(error: str) -> list:
|
2024-10-02 14:01:02 +00:00
|
|
|
ERROR = r"^==\d+==\s?ERROR: (\S+): (.*)"
|
2024-09-30 03:43:34 +00:00
|
|
|
error_source = ""
|
|
|
|
error_reason = ""
|
2024-10-10 23:08:52 +00:00
|
|
|
test_unit = ""
|
|
|
|
TEST_UNIT_LINE = r"artifact_prefix='.*\/'; Test unit written to (.*)"
|
|
|
|
error_info = []
|
|
|
|
is_error = False
|
2024-09-30 02:43:53 +00:00
|
|
|
|
2024-09-30 04:02:25 +00:00
|
|
|
# pylint: disable=unused-variable
|
2024-10-11 04:05:08 +00:00
|
|
|
for line_num, line in enumerate(error.splitlines(), 1):
|
2024-10-10 23:08:52 +00:00
|
|
|
if is_error:
|
|
|
|
error_info.append(line)
|
2024-10-02 14:01:02 +00:00
|
|
|
match = re.search(TEST_UNIT_LINE, line)
|
2024-09-30 02:43:53 +00:00
|
|
|
if match:
|
2024-10-10 23:08:52 +00:00
|
|
|
test_unit = match.group(1)
|
2024-10-02 14:01:02 +00:00
|
|
|
continue
|
2024-09-30 02:43:53 +00:00
|
|
|
|
2024-10-02 14:01:02 +00:00
|
|
|
match = re.search(ERROR, line)
|
2024-09-30 02:43:53 +00:00
|
|
|
if match:
|
2024-10-10 23:08:52 +00:00
|
|
|
error_info.append(line)
|
2024-10-02 14:01:02 +00:00
|
|
|
error_source = match.group(1)
|
|
|
|
error_reason = match.group(2)
|
2024-10-10 23:08:52 +00:00
|
|
|
is_error = True
|
|
|
|
|
|
|
|
report(error_source, error_reason, error_info, test_unit)
|
2024-10-16 01:10:57 +00:00
|
|
|
return error_info
|
2024-07-15 17:00:53 +00:00
|
|
|
|
2024-09-30 03:43:34 +00:00
|
|
|
|
2024-10-03 23:44:40 +00:00
|
|
|
def kill_fuzzer(fuzzer: str):
|
2024-10-04 12:03:21 +00:00
|
|
|
with subprocess.Popen(["ps", "-A", "u"], stdout=subprocess.PIPE) as p:
|
2024-10-04 00:15:36 +00:00
|
|
|
out, _ = p.communicate()
|
|
|
|
for line in out.splitlines():
|
2024-10-04 02:31:34 +00:00
|
|
|
if fuzzer.encode("utf-8") in line:
|
2024-10-04 12:54:13 +00:00
|
|
|
pid = int(line.split(None, 2)[1])
|
2024-10-04 03:18:01 +00:00
|
|
|
logging.info("Killing fuzzer %s, pid %d", fuzzer, pid)
|
2024-10-04 00:15:36 +00:00
|
|
|
os.kill(pid, signal.SIGKILL)
|
2024-10-03 23:44:40 +00:00
|
|
|
|
|
|
|
|
2024-10-16 01:10:57 +00:00
|
|
|
def run_fuzzer(fuzzer: str, timeout: int) -> TestResult:
|
2024-10-02 20:07:02 +00:00
|
|
|
s3 = S3Helper()
|
|
|
|
|
2024-07-16 14:17:51 +00:00
|
|
|
logging.info("Running fuzzer %s...", fuzzer)
|
2024-07-15 17:00:53 +00:00
|
|
|
|
2024-09-30 02:43:53 +00:00
|
|
|
seed_corpus_dir = f"{fuzzer}.in"
|
|
|
|
with Path(seed_corpus_dir) as path:
|
2024-07-15 17:00:53 +00:00
|
|
|
if not path.exists() or not path.is_dir():
|
2024-09-30 02:43:53 +00:00
|
|
|
seed_corpus_dir = ""
|
|
|
|
|
|
|
|
active_corpus_dir = f"{fuzzer}.corpus"
|
2024-10-03 06:45:03 +00:00
|
|
|
try:
|
|
|
|
s3.download_files(
|
|
|
|
bucket=S3_BUILDS_BUCKET,
|
|
|
|
s3_path=f"fuzzer/corpus/{fuzzer}/",
|
|
|
|
file_suffix="",
|
|
|
|
local_directory=active_corpus_dir,
|
|
|
|
)
|
2024-10-03 07:24:50 +00:00
|
|
|
except ClientError as e:
|
2024-10-03 07:33:39 +00:00
|
|
|
if e.response["Error"]["Code"] == "NoSuchKey":
|
2024-10-03 07:24:50 +00:00
|
|
|
logging.debug("No active corpus exists for %s", fuzzer)
|
|
|
|
else:
|
|
|
|
raise
|
2024-10-02 20:07:02 +00:00
|
|
|
|
|
|
|
new_corpus_dir = f"{fuzzer}.corpus_new"
|
|
|
|
if not os.path.exists(new_corpus_dir):
|
|
|
|
os.makedirs(new_corpus_dir)
|
2024-09-30 02:43:53 +00:00
|
|
|
|
2024-07-15 17:00:53 +00:00
|
|
|
options_file = f"{fuzzer}.options"
|
|
|
|
custom_libfuzzer_options = ""
|
|
|
|
fuzzer_arguments = ""
|
|
|
|
|
|
|
|
with Path(options_file) as path:
|
|
|
|
if path.exists() and path.is_file():
|
|
|
|
parser = configparser.ConfigParser()
|
|
|
|
parser.read(path)
|
|
|
|
|
|
|
|
if parser.has_section("asan"):
|
|
|
|
os.environ["ASAN_OPTIONS"] = (
|
2024-07-16 15:01:43 +00:00
|
|
|
f"{os.environ['ASAN_OPTIONS']}:{':'.join(f'{key}={value}' for key, value in parser['asan'].items())}"
|
2024-07-15 17:00:53 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
if parser.has_section("msan"):
|
|
|
|
os.environ["MSAN_OPTIONS"] = (
|
2024-07-16 15:01:43 +00:00
|
|
|
f"{os.environ['MSAN_OPTIONS']}:{':'.join(f'{key}={value}' for key, value in parser['msan'].items())}"
|
2024-07-15 17:00:53 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
if parser.has_section("ubsan"):
|
|
|
|
os.environ["UBSAN_OPTIONS"] = (
|
2024-07-16 15:01:43 +00:00
|
|
|
f"{os.environ['UBSAN_OPTIONS']}:{':'.join(f'{key}={value}' for key, value in parser['ubsan'].items())}"
|
2024-07-15 17:00:53 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
if parser.has_section("libfuzzer"):
|
|
|
|
custom_libfuzzer_options = " ".join(
|
2024-10-10 00:52:58 +00:00
|
|
|
f"-{key}={value}"
|
|
|
|
for key, value in parser["libfuzzer"].items()
|
|
|
|
if key != "jobs"
|
2024-07-15 17:00:53 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
if parser.has_section("fuzzer_arguments"):
|
|
|
|
fuzzer_arguments = " ".join(
|
2024-07-16 14:17:51 +00:00
|
|
|
(f"{key}") if value == "" else (f"{key}={value}")
|
2024-07-15 17:00:53 +00:00
|
|
|
for key, value in parser["fuzzer_arguments"].items()
|
|
|
|
)
|
|
|
|
|
2024-10-02 20:24:13 +00:00
|
|
|
cmd_line = f"{DEBUGGER} ./{fuzzer} {FUZZER_ARGS} {new_corpus_dir} {active_corpus_dir} {seed_corpus_dir}"
|
|
|
|
|
2024-07-15 17:00:53 +00:00
|
|
|
if custom_libfuzzer_options:
|
|
|
|
cmd_line += f" {custom_libfuzzer_options}"
|
|
|
|
if fuzzer_arguments:
|
|
|
|
cmd_line += f" {fuzzer_arguments}"
|
|
|
|
|
|
|
|
if not "-dict=" in cmd_line and Path(f"{fuzzer}.dict").exists():
|
|
|
|
cmd_line += f" -dict={fuzzer}.dict"
|
|
|
|
|
|
|
|
cmd_line += " < /dev/null"
|
|
|
|
|
2024-07-16 14:17:51 +00:00
|
|
|
logging.info("...will execute: %s", cmd_line)
|
2024-09-30 02:43:53 +00:00
|
|
|
|
2024-10-16 01:10:57 +00:00
|
|
|
test_result = TestResult(fuzzer, "OK")
|
|
|
|
stopwatch = Stopwatch()
|
2024-09-30 02:43:53 +00:00
|
|
|
try:
|
|
|
|
result = subprocess.run(
|
|
|
|
cmd_line,
|
|
|
|
stderr=subprocess.PIPE,
|
|
|
|
stdout=subprocess.DEVNULL,
|
|
|
|
text=True,
|
|
|
|
check=True,
|
2024-09-30 03:43:34 +00:00
|
|
|
shell=True,
|
2024-09-30 15:03:00 +00:00
|
|
|
errors="replace",
|
2024-10-01 14:02:17 +00:00
|
|
|
timeout=timeout,
|
2024-09-30 02:43:53 +00:00
|
|
|
)
|
|
|
|
except subprocess.CalledProcessError as e:
|
2024-09-30 03:43:34 +00:00
|
|
|
# print("Command failed with error:", e)
|
2024-10-11 03:11:39 +00:00
|
|
|
logging.info("Stderr output: %s", e.stderr)
|
2024-10-16 01:10:57 +00:00
|
|
|
test_result = TestResult(
|
|
|
|
fuzzer,
|
|
|
|
"FAIL",
|
|
|
|
stopwatch.duration_seconds,
|
|
|
|
"",
|
|
|
|
"\n".join(process_error(e.stderr)),
|
|
|
|
)
|
2024-10-01 18:25:22 +00:00
|
|
|
except subprocess.TimeoutExpired as e:
|
2024-10-04 04:35:35 +00:00
|
|
|
logging.info("Timeout for %s", cmd_line)
|
2024-10-03 23:44:40 +00:00
|
|
|
kill_fuzzer(fuzzer)
|
2024-10-04 05:31:17 +00:00
|
|
|
sleep(10)
|
2024-10-01 18:25:22 +00:00
|
|
|
process_fuzzer_output(e.stderr)
|
2024-10-16 01:10:57 +00:00
|
|
|
test_result = TestResult(
|
|
|
|
fuzzer,
|
|
|
|
"Timeout",
|
|
|
|
stopwatch.duration_seconds,
|
|
|
|
"",
|
|
|
|
"",
|
|
|
|
)
|
2024-09-30 02:43:53 +00:00
|
|
|
else:
|
|
|
|
process_fuzzer_output(result.stderr)
|
2024-10-16 01:10:57 +00:00
|
|
|
test_result.time = stopwatch.duration_seconds
|
2024-07-15 17:00:53 +00:00
|
|
|
|
2024-10-03 17:39:14 +00:00
|
|
|
s3.upload_build_directory_to_s3(
|
|
|
|
Path(new_corpus_dir), f"fuzzer/corpus/{fuzzer}", False
|
|
|
|
)
|
2024-10-02 20:07:02 +00:00
|
|
|
|
2024-10-16 01:10:57 +00:00
|
|
|
logging.info("test_result: %s", test_result)
|
|
|
|
return test_result
|
|
|
|
|
2024-09-30 03:43:34 +00:00
|
|
|
|
2024-07-15 19:10:07 +00:00
|
|
|
def main():
|
2024-07-15 17:00:53 +00:00
|
|
|
logging.basicConfig(level=logging.INFO)
|
|
|
|
|
|
|
|
subprocess.check_call("ls -al", shell=True)
|
|
|
|
|
2024-10-01 14:02:17 +00:00
|
|
|
timeout = 30
|
|
|
|
|
|
|
|
match = re.search(r"(^|\s+)-max_total_time=(\d+)($|\s)", FUZZER_ARGS)
|
|
|
|
if match:
|
2024-10-01 15:49:26 +00:00
|
|
|
timeout += int(match.group(2))
|
2024-10-01 14:02:17 +00:00
|
|
|
|
2024-10-16 01:10:57 +00:00
|
|
|
test_results = []
|
|
|
|
stopwatch = Stopwatch()
|
2024-07-15 17:00:53 +00:00
|
|
|
with Path() as current:
|
|
|
|
for fuzzer in current.iterdir():
|
|
|
|
if (current / fuzzer).is_file() and os.access(current / fuzzer, os.X_OK):
|
2024-10-16 01:10:57 +00:00
|
|
|
test_results.append(run_fuzzer(fuzzer.name, timeout))
|
|
|
|
|
|
|
|
prepared_results = prepare_tests_results_for_clickhouse(PRInfo(), test_results, "failure", stopwatch.duration_seconds, stopwatch.start_time_str, "", "libFuzzer")
|
|
|
|
# ch_helper = ClickHouseHelper()
|
|
|
|
# ch_helper.insert_events_into(db="default", table="checks", events=prepared_results)
|
|
|
|
logging.info("prepared_results: %s", prepared_results)
|
2024-07-15 19:10:07 +00:00
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
2024-10-02 23:03:08 +00:00
|
|
|
from os import path, sys
|
2024-10-02 23:19:06 +00:00
|
|
|
|
2024-10-02 22:39:20 +00:00
|
|
|
ACTIVE_DIR = path.dirname(path.abspath(__file__))
|
2024-10-03 05:39:42 +00:00
|
|
|
sys.path.append((Path(path.dirname(ACTIVE_DIR)) / "ci").as_posix())
|
2024-10-03 03:12:58 +00:00
|
|
|
from env_helper import ( # pylint: disable=import-error,no-name-in-module
|
2024-10-03 00:52:51 +00:00
|
|
|
S3_BUILDS_BUCKET,
|
|
|
|
)
|
2024-10-03 03:12:58 +00:00
|
|
|
from s3_helper import S3Helper # pylint: disable=import-error,no-name-in-module
|
2024-10-16 01:10:57 +00:00
|
|
|
from clickhouse_helper import ( # pylint: disable=import-error,no-name-in-module
|
|
|
|
ClickHouseHelper,
|
|
|
|
prepare_tests_results_for_clickhouse,
|
|
|
|
)
|
|
|
|
from pr_info import PRInfo # pylint: disable=import-error,no-name-in-module
|
|
|
|
from stopwatch import Stopwatch # pylint: disable=import-error,no-name-in-module
|
|
|
|
from report import TestResult # pylint: disable=import-error,no-name-in-module
|
2024-10-02 23:19:06 +00:00
|
|
|
|
2024-07-15 19:10:07 +00:00
|
|
|
main()
|