diff --git a/.github/workflows/kernelctf-vuln-verify.yaml b/.github/workflows/kernelctf-vuln-verify.yaml new file mode 100644 index 00000000..4c7e5de8 --- /dev/null +++ b/.github/workflows/kernelctf-vuln-verify.yaml @@ -0,0 +1,72 @@ +name: kernelctf-vuln-verify +on: + pull_request_target: + types: [labeled] + +jobs: + verify: + if: github.event.label.name == 'vuln-verify' + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - name: Authenticate to Google Cloud + uses: google-github-actions/auth@v3 + with: + credentials_json: '${{ secrets.KERNELCTF_GCS_SA_KEY }}' + + - name: Checkout master + uses: actions/checkout@v4 + with: + ref: master + sparse-checkout: kernelctf/vuln-verify + + - name: Checkout PR + uses: actions/checkout@v4 + with: + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.sha }} + path: pr + sparse-checkout: pocs/linux/kernelctf + + - name: Checkout kernel-research + uses: actions/checkout@v4 + with: + repository: google/kernel-research + path: kernel-research + sparse-checkout: image_runner + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y expect build-essential flex bison bc libelf-dev libssl-dev cpio pahole + pip install requests + + - name: Setup environment + run: echo "IMAGE_RUNNER_DIR=${{ github.workspace }}/kernel-research/image_runner" >> $GITHUB_ENV + + - name: Determine submission folder + id: determine_folder + run: | + # Get added files in the PR + ADDED_FILES=$(gh pr view ${{ github.event.pull_request.number }} --json files --jq '.files[] | select(.status == "added") | .path') + echo $ADDED_FILES + + # Filter for pocs/linux/kernelctf/ and get the unique directory + ADDED_FOLDER=$(echo "$ADDED_FILES" | grep '^pocs/linux/kernelctf/' | cut -d/ -f1-4 | sort -u | head -n 1) + + if [ -z "$ADDED_FOLDER" ]; then + echo "No added folders found in pocs/linux/kernelctf/" + exit 0 + fi + + echo "folder=$ADDED_FOLDER" >> $GITHUB_OUTPUT + echo "Found added folder: $ADDED_FOLDER" + + - name: Run vuln-verify + if: steps.determine_folder.outputs.folder != '' + run: | + ./kernelctf/vuln-verify/verify.py "pr/${{ steps.determine_folder.outputs.folder }}" diff --git a/kernelctf/vuln-verify/.gitignore b/kernelctf/vuln-verify/.gitignore new file mode 100644 index 00000000..9321486d --- /dev/null +++ b/kernelctf/vuln-verify/.gitignore @@ -0,0 +1,14 @@ +linux/ +builds/ +output +__pycache__/ +*.sqlite3 +kernelctf_public_sheet.csv +*.txt +.cache/ +patches/ +cache.json +verify_results*/ +*bzImage +*.patch + diff --git a/kernelctf/vuln-verify/build_release.sh b/kernelctf/vuln-verify/build_release.sh new file mode 100755 index 00000000..b1b47de3 --- /dev/null +++ b/kernelctf/vuln-verify/build_release.sh @@ -0,0 +1,55 @@ +#!/bin/bash +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +set -eo pipefail + +SCRIPT_DIR=$(dirname $(realpath "$0")) +LINUX_DIR="$SCRIPT_DIR/linux" + +usage() { + echo "Usage: $0 "; + exit 1; +} + +REPO_URL="$1" +COMMIT_HASH="$2" +CONFIG_FN=$(realpath "$3" 2>/dev/null || true) +EXTRA_CONFIG_FN=$(realpath "$4" 2>/dev/null || true) +PATCH_FN=$(realpath "$5" 2>/dev/null || true) + +if [[ -z "$REPO_URL" || -z "$COMMIT_HASH" ]]; then usage; fi + +mkdir -p "$LINUX_DIR" 2>/dev/null +cd "$LINUX_DIR" + +git init +git remote remove origin 2>/dev/null || true +git remote add origin "$REPO_URL" + +if [[ "$COMMIT_HASH" != $(git rev-parse HEAD) ]]; then + git fetch --depth 1 origin "$COMMIT_HASH" +fi +git reset --hard FETCH_HEAD || true + +cp "$CONFIG_FN" .config + +if [ ! -z "$EXTRA_CONFIG_FN" ]; then + cp "$EXTRA_CONFIG_FN" kernel/configs/ + make $(basename "$EXTRA_CONFIG_FN") +fi + +if [ ! -z "$PATCH_FN" ]; then git apply -v "$PATCH_FN"; fi + +make olddefconfig +make -j`nproc` diff --git a/kernelctf/vuln-verify/exp.sh b/kernelctf/vuln-verify/exp.sh new file mode 100644 index 00000000..6bcced45 --- /dev/null +++ b/kernelctf/vuln-verify/exp.sh @@ -0,0 +1,25 @@ +#!/bin/sh +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +ifconfig lo 127.0.0.1 netmask 255.0.0.0 up +echo "CTF{secret_flag_deadbeef}" > /flag +chmod 0000 /flag +if [ -e /dev/xdk ]; then + chmod o+rw /dev/xdk +fi +chmod o+rx /exp +echo "Running id and then the exploit: /exp $@" +ARG="id; /exp $@" +su user -c /bin/sh -c "$ARG" diff --git a/kernelctf/vuln-verify/kasan.config b/kernelctf/vuln-verify/kasan.config new file mode 100644 index 00000000..f21fe473 --- /dev/null +++ b/kernelctf/vuln-verify/kasan.config @@ -0,0 +1,2 @@ +CONFIG_KASAN=y +CONFIG_SYSTEM_TRUSTED_KEYS="" diff --git a/kernelctf/vuln-verify/run_exploit.sh b/kernelctf/vuln-verify/run_exploit.sh new file mode 100755 index 00000000..f41cabf4 --- /dev/null +++ b/kernelctf/vuln-verify/run_exploit.sh @@ -0,0 +1,45 @@ +#!/bin/bash +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +set -eo pipefail + +SCRIPT_DIR=$(dirname $(realpath "$0")) +cd "$SCRIPT_DIR" + +if [ -z "$IMAGE_RUNNER_DIR" ]; then + echo "IMAGE_RUNNER_DIR is not set" + exit 1 +fi + +usage() { + echo "Usage: $0 "; + exit 1; +} + +BUILD="$1" +BZIMAGE_FN="builds/${BUILD}_bzImage" +if [ ! -f "$BZIMAGE_FN" ]; then echo "$BZIMAGE_FN does not exist"; usage; fi + +cp exp.sh "$IMAGE_RUNNER_DIR/rootfs/" + +expect -c ' + set timeout -1 + set stty_init raw + spawn "'"$IMAGE_RUNNER_DIR/run_vmlinuz.sh"'" "'"$BZIMAGE_FN"'" --kernel-args=kasan.fault=panic --nokaslr -- /exp.sh + expect "# " + send "cat /flag\n" + expect "# " + exit 1' | tee result.txt + +echo "SUCCESS=$(grep secret_flag_deadbeef result.txt)" diff --git a/kernelctf/vuln-verify/utils.py b/kernelctf/vuln-verify/utils.py new file mode 100644 index 00000000..ddf280c0 --- /dev/null +++ b/kernelctf/vuln-verify/utils.py @@ -0,0 +1,120 @@ +import csv +import io +import os +import re +import requests +import subprocess +import sys +import time +import textwrap + +CACHE_DIR = "./" +CACHE_TIME = 0 if "--disable-cache" in sys.argv else 3600*24 +CACHE_FOREVER = float("inf") + +def run(cmd, cwd=None): + shell = not isinstance(cmd, list) + try: + result = subprocess.check_output(cmd, cwd=cwd, shell=shell).decode('utf-8').split('\n') + return result if result[-1] != "" else result[0:-1] + except subprocess.CalledProcessError as e: + print(f"[!] executing '{cmd}' failed with exit code {e.returncode}") + return None + +def readTextFile(fn): + with open(fn, 'rt') as f: return f.read() + +def writeTextFile(fn, content, append=False): + dir = os.path.dirname(fn) + if dir: + os.makedirs(dir, exist_ok=True) + mode = ('a' if append else 'w') + ('b' if type(content) is bytes else 't') + with open(fn, mode) as f: f.write(content) + +def is_cached(cache_fn, cache_time): + return os.path.isfile(cache_fn) and (time.time() - os.path.getmtime(cache_fn) < cache_time) + +def cache(getter, cache_name=None, cache_time=None): + global CACHE_TIME + cache_time = cache_time or CACHE_TIME + use_cache = cache_name and cache_time + cache_fn = f"{CACHE_DIR}/{cache_name}" if cache_name else None + if use_cache and is_cached(cache_fn, cache_time): + return readTextFile(cache_fn) + result = getter() + if use_cache: + writeTextFile(cache_fn, result) + return result + +def fetch(url, cache_name=None, headers=None, cache_time=None, fail_on_error=True): + def getter(): + response = requests.get(url, headers=headers) + if fail_on_error: + response.raise_for_status() + return response.content.decode('utf-8') + return cache(getter, cache_name, cache_time or CACHE_FOREVER) + +def toDict(items, keyColumn): + return { x[keyColumn]: x for x in items } + +def parseCsv(csvContent, keyColumn=None): + columns, *rows = list(csv.reader(io.StringIO(csvContent), strict=True)) + result = [{ columns[i]: row[i] for i in range(len(columns)) } for row in rows] + return toDict(result, keyColumn) if keyColumn else result + +def indent_format(text): + text = '\n'.join(textwrap.fill(line, 120) for line in text.split('\n')) + pad = 0 + for pad in range(len(text)): + if text[pad] != ' ': + break + if pad + 1 < len(text) and text[pad] == '-' and text[pad+1] == ' ': + pad += 2 + rows = text.split('\n') + rows = [("" if i == 0 or not rows[i] else ' ' * (pad - 3 if rows[i].startswith(" - ") else pad)) + rows[i] for i in range(len(rows))] + text = '\n'.join(rows) + return text + +def printi(value): + text = indent_format(str(value)) + print(text) + return "\n" in text + +def natural_key(text): + # Splits the string into chunks of numbers and non-numbers + return [int(c) if c.isdigit() else c.lower() for c in re.split(r'(\d+)', text)] + +def natsorted(items): + return sorted(items, key=natural_key) + +NO_COLOR = "--no-color" in sys.argv + +def color(text, color_code): + return text if NO_COLOR else f"\033[{color_code}m{text}\033[0m" + +def red(text): + return color(text, 31) + +def green(text): + return color(text, 32) + +def yellow(text): + return color(text, 33) + +def blue(text): + return color(text, 34) + +def pink(text): + return color(text, 35) + +def cyan(text): + return color(text, 36) + +def white(text): + return color(text, 37) + +def grey(text): + return color(text, 90) + +def bold(text): + return color(text, 1) \ No newline at end of file diff --git a/kernelctf/vuln-verify/verify.py b/kernelctf/vuln-verify/verify.py new file mode 100755 index 00000000..622d535e --- /dev/null +++ b/kernelctf/vuln-verify/verify.py @@ -0,0 +1,283 @@ +#!/usr/bin/env -S python3 -u +# example: GITHUB_TOKEN=$(cat ~/.github_pat) IMAGE_RUNNER_DIR=~/prjs/kernel-research-repo/image_runner ./verify.py ~/prjs/gob-isekernel/exploits/exp93_98_* +import json +import re +import os +import sys +import sqlite3 +import shutil +import argparse +from utils import parseCsv, fetch, readTextFile, run, is_cached, writeTextFile, CACHE_FOREVER, red, green, yellow + +parser = argparse.ArgumentParser() +parser.add_argument("--build", action=argparse.BooleanOptionalAction, default=True) +parser.add_argument("--verify", action=argparse.BooleanOptionalAction, default=True) +parser.add_argument("--force-verify", action=argparse.BooleanOptionalAction, default=False) +parser.add_argument("--upstream", action=argparse.BooleanOptionalAction, default=True) +parser.add_argument("--stable", action=argparse.BooleanOptionalAction, default=True) +parser.add_argument("--target-patching", action=argparse.BooleanOptionalAction, default=False) +parser.add_argument("--gcs-cache", action=argparse.BooleanOptionalAction, default=True) +parser.add_argument("--no-gh-auth", action="store_true") +parser.add_argument("exploit_paths", nargs="+") +args = parser.parse_args() + +IMAGE_RUNNER_DIR = os.environ.get("IMAGE_RUNNER_DIR") +if not IMAGE_RUNNER_DIR: + print("Error: IMAGE_RUNNER_DIR environment variable is missing", file=sys.stderr) + sys.exit(1) + +GITHUB_TOKEN = os.environ.get("GITHUB_TOKEN") +if not GITHUB_TOKEN and not args.no_gh_auth: + print("Error: GITHUB_TOKEN environment variable is missing (use --no-gh-auth to skip)", file=sys.stderr) + sys.exit(1) + +GH_HEADERS = {"Authorization": f"Bearer {GITHUB_TOKEN}"} if GITHUB_TOKEN and not args.no_gh_auth else {} + +CACHE_DB_FN = "cache.json" +PUBLIC_CSV_URL = "https://docs.google.com/spreadsheets/d/e/2PACX-1vS1REdTA29OJftst8xN5B5x8iIUcxuK6bXdzF8G1UXCmRtoNsoQ9MbebdRdFnj6qZ0Yd7LwQfvYC2oF/pub?output=csv" +KERNEL_DANCE_SQL_URL = "https://linux-mirror-db.storage.googleapis.com/mirror.sl3" +KERNEL_DANCE_SQL_FN = "kernel-dance.sqlite3" +KERNELCTF_RELEASES_URL = "https://storage.googleapis.com/kernelctf-build/releases" +STABLE_COMMIT_QUERY = """ + SELECT upstream.`commit` + FROM upstream + LEFT JOIN tags ON tags.`commit` = upstream.`commit` + WHERE (upstream.upstream LIKE :hash OR upstream.`commit` LIKE :hash) + AND tags.tags LIKE :tag +""" +UPSTREAM_COMMIT_QUERY = "SELECT DISTINCT upstream.`upstream` FROM upstream WHERE (upstream.upstream LIKE :hash OR upstream.`commit` LIKE :hash)" +STABLE_REPO = "https://github.com/gregkh/linux" +UPSTREAM_REPO = "https://github.com/torvalds/linux" +GCS_BASE_URL = "gs://kernelctf-build/vuln-verify" + +def gcs_download(local_fn, gcs_fn): + if not args.gcs_cache or os.path.isfile(local_fn): + return os.path.isfile(local_fn) + print(f" [CACHE] downloading {os.path.basename(local_fn)} from GCS...") + return run(["gcloud", "storage", "cp", f"{GCS_BASE_URL}/{gcs_fn}", local_fn]) is not None + +def gcs_upload(local_fn, gcs_fn, gzip=False): + if not args.gcs_cache or not os.path.isfile(local_fn): + return + print(f" [CACHE] uploading {os.path.basename(local_fn)} to GCS...") + cmd = ["gcloud", "storage", "cp", local_fn, f"{GCS_BASE_URL}/{gcs_fn}"] + if gzip: + cmd.append("--gzip-encoded") + run(cmd) + +os.chdir(os.path.dirname(__file__)) + +public_csv = parseCsv(fetch(PUBLIC_CSV_URL, "kernelctf_public_sheet.csv"), "ID") +#pprint(public_csv) + +if not is_cached(KERNEL_DANCE_SQL_FN, 3600*24*7): + run(["wget", "-O", KERNEL_DANCE_SQL_FN, KERNEL_DANCE_SQL_URL]) +sqlconn = sqlite3.connect(KERNEL_DANCE_SQL_FN) +sql = sqlconn.cursor() + +def sql_value(query, *params): + results = sql.execute(query, *params).fetchall() + if len(results) != 1: + raise Exception(f"{"Multiple" if results else "No"} results for query '{query}' (with params {params}): {results}") + return results[0][0] if len(results) > 0 else None + + +def hash_from_url(url): + return re.search(r"(?:id|h)=([0-9a-f]+)", url).group(1) + +cache_db = json.loads(readTextFile(CACHE_DB_FN)) if os.path.isfile(CACHE_DB_FN) else {} + +def cache(category, key, getter): + value = cache_db.setdefault(category, {}).get(key) + if not value: + cache_db[category][key] = value = getter() + return value + +def save_cache(): + writeTextFile(CACHE_DB_FN, json.dumps(cache_db, indent=4)) + +def get_stable_commit(commit_hash, kernel_ver): + return cache("stable_commits", f"{commit_hash}_{kernel_ver}", lambda: + sql_value(STABLE_COMMIT_QUERY, {"hash": f"{commit_hash}%", "tag": f"tags/v{kernel_ver}.%"})) + +def get_upstream_commit(commit_hash): + return cache("upstream_commits", f"{commit_hash}", lambda: sql_value(UPSTREAM_COMMIT_QUERY, {"hash": f"{commit_hash}%"})) + +def get_parent_commit(commit_hash): + return json.loads(fetch(f"https://api.github.com/repos/gregkh/linux/commits/{commit_hash}", f".cache/{commit_hash}.json", + GH_HEADERS))["parents"][0]["sha"] + +builds = [] +all_success = True +for i_exp, exp_dir in enumerate(args.exploit_paths): + metadata = json.loads(readTextFile(f"{exp_dir}/metadata.json")) + exp_ids = "exp" + "_".join(x.replace("exp", "") for x in metadata["submission_ids"]) + first_exp_id = metadata["submission_ids"][0] + commit_hash_pr = hash_from_url(metadata["vulnerability"]["patch_commit"]) + commit_hash = hash_from_url(public_csv[first_exp_id]["Patch commit"]) + if commit_hash != commit_hash_pr: + print(f"WARNING! {exp_ids}: public commit hash ({commit_hash}) does not match PR commit hash ({commit_hash_pr})") + exp_success = False + exp_fail = False + targets = list(metadata["exploits"].keys()) + for i_target, target in enumerate(targets): + orig_target = target + if target == "mitigation-6.1": + target = "mitigation-6.1-v2" + config = fetch(f"{KERNELCTF_RELEASES_URL}/{target}/.config", f"builds/{target}.config") + commit_info_txt = fetch(f"{KERNELCTF_RELEASES_URL}/{target}/COMMIT_INFO", f"builds/{target}_COMMIT_INFO") + commit_info = {x[0]: x[1] for x in [line.split("=") for line in commit_info_txt.strip().split("\n")]} + repo_url = commit_info["REPOSITORY_URL"] + base_commit = commit_info["COMMIT_HASH"] + kernel_ver = re.search(r"Linux/x86 (\d+\.\d+)\.\d+ Kernel Configuration", config).group(1) + stable_commit = "n/a" + parent_commit = "n/a" + if args.stable: + stable_commit = get_stable_commit(commit_hash, kernel_ver) + parent_commit = get_parent_commit(stable_commit) + if args.upstream: + ups_commit = get_upstream_commit(commit_hash) + ups_parent_commit = get_parent_commit(ups_commit) + print(f"[{round(i_exp+1 + i_target/len(targets),2):g}/{len(args.exploit_paths)}] {exp_ids} on {target}: " + f"{kernel_ver}, commit: {commit_hash}, stable: {stable_commit}, parent: {parent_commit}") + + config_fn = f"builds/{target}.config" + def build_release_(name, repo_url, commit_hash, patch_commit_fn=""): + bzImage_fn = f"builds/{name}_bzImage" + vmlinux_fn = f"builds/{name}_vmlinux" + log_fn = f"builds/{name}_build.log" + + if gcs_download(bzImage_fn, f"builds/{name}_bzImage"): + return True + + if not args.build or os.path.isfile(log_fn): + return False + + cmd = f"./build_release.sh {repo_url} {commit_hash} {config_fn} kasan.config {patch_commit_fn} >{log_fn}.tmp 2>&1" + print(f"Running '{cmd}'") + success = run(cmd) is not None + os.rename(f"{log_fn}.tmp", log_fn) # move in case of error too, so we won't run it again + if success: + os.rename("linux/arch/x86/boot/bzImage", bzImage_fn) + os.rename("linux/vmlinux", vmlinux_fn) + gcs_upload(bzImage_fn, f"builds/{name}_bzImage") + gcs_upload(vmlinux_fn, f"builds/{name}_vmlinux", gzip=True) + gcs_upload(log_fn, f"builds/{name}_build.log") + return success + + def build_release(name, *args): + success = build_release_(name, *args) + print(f" [BUILD] {exp_ids} -> {name}: {success}") + + p_id = f"{first_exp_id}_{kernel_ver.replace(".", "_")}" + name_orig = target + name_base = f"{target}_kasan" + build_targets = [name_orig] + + if args.target_patching: + build_targets.append(name_base) + build_release(name_base, repo_url, base_commit) + + if args.stable: + name_before_patch = f"{p_id}_kasan_wo_patch_{parent_commit[0:7]}" + name_after_patch = f"{p_id}_kasan_patched_{stable_commit[0:7]}" + build_release(name_before_patch, STABLE_REPO, parent_commit) + build_release(name_after_patch, STABLE_REPO, stable_commit) + build_targets.extend([name_before_patch, name_after_patch]) + + if args.target_patching: + name_base_patched = f"{target}_kasan_{first_exp_id}_{stable_commit[0:7]}" + build_release(name_base_patched, repo_url, base_commit, f"patches/{stable_commit[0:7]}.patch") + build_targets.append(name_base_patched) + + if args.upstream: + ups_name_before_patch = f"{first_exp_id}_upstream_kasan_wo_patch_{ups_parent_commit[0:7]}" + ups_name_after_patch = f"{first_exp_id}_upstream_kasan_patched_{ups_commit[0:7]}" + build_release(ups_name_before_patch, UPSTREAM_REPO, ups_parent_commit) + build_release(ups_name_after_patch, UPSTREAM_REPO, ups_commit) + build_targets.extend([ups_name_before_patch, ups_name_after_patch]) + + if args.verify or args.force_verify: + exp_fn = f"{exp_dir}/exploit/{orig_target}/exploit" + shutil.copyfile(exp_fn, f"{IMAGE_RUNNER_DIR}/rootfs/exp") + + if not os.path.isfile(f"builds/{target}_bzImage"): + print(f" [DOWNLOAD] downloading release {target}...") + run(f"wget -qO builds/{target}_bzImage {KERNELCTF_RELEASES_URL}/{target}/bzImage") + + res = {} + for name in build_targets: + log_fn = f"verify_results/{first_exp_id}_{name}.txt" + + if not args.force_verify: + gcs_download(log_fn, log_fn) + + if args.force_verify or (args.verify and not os.path.isfile(log_fn)): + print(f" [RUN] running exploit {first_exp_id}_{name}...") + run(f"((timeout 3m ./run_exploit.sh {name}; EC=$?; echo; echo EXIT_CODE=$EC)|sed -u 's/\\r//g') 2>&1 >{log_fn}") + gcs_upload(log_fn, log_fn) + + pwned = None + if not os.path.isfile(log_fn): + result = "verification was not run" + else: + result = "" + logs = readTextFile(log_fn) + if "Usage: ./run_exploit.sh" in logs: + result = "build failed" + elif (m := re.search(r"KASAN: .*", logs)): + result = m.group(0) + pwned = True + elif "CTF{secret_flag_deadbeef}" in logs: + result = "got the flag" + pwned = True + elif "Attempted to kill init!" in logs: + result = "exploit exited" + pwned = False + elif "Kernel panic" in logs: + result = "kernel panic" + pwned = True + elif "majority vote failed" in logs: + result = "prefetch failed" + elif "Running command: /exp.sh" not in logs: + result = "VM hanged before running exploit" + elif "EXIT_CODE=124" in logs: + result = "timed out" + pwned = False + + res[name] = pwned + print(f" [VERIFY] {exp_ids}_{name}: {result}") + + success_target_patching = res[name_base] == True and res[name_base_patched] == False if args.stable and args.target_patching else None + success_patch_commit = res[name_before_patch] == True and res[name_after_patch] == False if args.stable else None + success_upstream_patch = res[ups_name_before_patch] == True and res[ups_name_after_patch] == False if args.upstream else None + success = success_target_patching or success_patch_commit or success_upstream_patch + fail = (args.stable and (args.target_patching and res[name_base_patched] or res[name_after_patch])) or (args.upstream and res[ups_name_after_patch]) + exp_success = exp_success or success + exp_fail = exp_fail or fail + if args.stable: + if args.target_patching: + print(f" Target patching test: {success_target_patching} (before: {res[name_base]}, after: {res[name_base_patched]})") + print(f" Patch commit test: {success_patch_commit} (before: {res[name_before_patch]}, after: {res[name_after_patch]})") + if args.upstream: + print(f" Upstream patch commit test: {success_upstream_patch} (before: {res[ups_name_before_patch]}, after: {res[ups_name_after_patch]})") + + if success_target_patching and not success_patch_commit: + print(" [STAT] Only target patching worked.") + if not success_target_patching and success_patch_commit: + print(" [STAT] Only patch commit testing worked.") + if success_upstream_patch and not success_patch_commit: + print(" [STAT] Only upstream patch worked.") + if not success_upstream_patch and success_patch_commit: + print(" [STAT] Only stable patch worked.") + + print(f" Verification of {exp_ids} on {target}: {red("FAIL") if fail else green("SUCCESS") if success else yellow("UNKNOWN")}") + print() + + print(f"[PR_VERIFY] of {exp_ids}: {red("FAIL") if exp_fail else green("SUCCESS") if exp_success else yellow("UNKNOWN")}") + print() + if exp_fail or not exp_success: + all_success = False + +save_cache() +sys.exit(0 if all_success else 1)