| import argparse |
| import ctypes |
| from ctypes import wintypes |
| import gc |
| import hashlib |
| import json |
| import os |
| import subprocess |
| import sys |
| import time |
| from pathlib import Path |
|
|
| import avro |
| import fastavro |
|
|
|
|
| ROOT = Path(__file__).resolve().parent |
| ARTIFACT = ROOT / "artifacts" / "avro_bzip2_dos.avro" |
| RESULTS_DIR = ROOT / "results" |
| GENERATOR = ROOT / "generate_poc.py" |
|
|
|
|
| class PROCESS_MEMORY_COUNTERS(ctypes.Structure): |
| _fields_ = [ |
| ("cb", wintypes.DWORD), |
| ("PageFaultCount", wintypes.DWORD), |
| ("PeakWorkingSetSize", ctypes.c_size_t), |
| ("WorkingSetSize", ctypes.c_size_t), |
| ("QuotaPeakPagedPoolUsage", ctypes.c_size_t), |
| ("QuotaPagedPoolUsage", ctypes.c_size_t), |
| ("QuotaPeakNonPagedPoolUsage", ctypes.c_size_t), |
| ("QuotaNonPagedPoolUsage", ctypes.c_size_t), |
| ("PagefileUsage", ctypes.c_size_t), |
| ("PeakPagefileUsage", ctypes.c_size_t), |
| ] |
|
|
|
|
| def working_set() -> dict[str, int]: |
| get_process_memory_info = ctypes.windll.psapi.GetProcessMemoryInfo |
| get_process_memory_info.argtypes = [wintypes.HANDLE, ctypes.POINTER(PROCESS_MEMORY_COUNTERS), wintypes.DWORD] |
| get_process_memory_info.restype = wintypes.BOOL |
| counters = PROCESS_MEMORY_COUNTERS() |
| counters.cb = ctypes.sizeof(PROCESS_MEMORY_COUNTERS) |
| ok = get_process_memory_info( |
| ctypes.windll.kernel32.GetCurrentProcess(), |
| ctypes.byref(counters), |
| counters.cb, |
| ) |
| if not ok: |
| raise ctypes.WinError() |
| return { |
| "working_set": int(counters.WorkingSetSize), |
| "peak_working_set": int(counters.PeakWorkingSetSize), |
| "pagefile": int(counters.PagefileUsage), |
| } |
|
|
|
|
| def sha256_file(path: Path) -> str: |
| h = hashlib.sha256() |
| with path.open("rb") as handle: |
| for chunk in iter(lambda: handle.read(1024 * 1024), b""): |
| h.update(chunk) |
| return h.hexdigest() |
|
|
|
|
| def run_child(library: str) -> dict: |
| cmd = [sys.executable, str(Path(__file__).resolve()), "--child", library, "--artifact", str(ARTIFACT)] |
| proc = subprocess.run(cmd, capture_output=True, text=True, check=True) |
| return json.loads(proc.stdout) |
|
|
|
|
| def child_main(library: str, artifact: Path) -> None: |
| gc.collect() |
| before = working_set() |
| t0 = time.perf_counter() |
|
|
| if library == "avro": |
| import avro.io |
| from avro.datafile import DataFileReader |
|
|
| with artifact.open("rb") as handle: |
| reader = DataFileReader(handle, avro.io.DatumReader()) |
| record = next(iter(reader)) |
| reader.close() |
| elif library == "fastavro": |
| with artifact.open("rb") as handle: |
| record = next(fastavro.reader(handle)) |
| else: |
| raise SystemExit(f"unknown library: {library}") |
|
|
| elapsed = time.perf_counter() - t0 |
| after = working_set() |
| result = { |
| "library": library, |
| "elapsed_seconds": round(elapsed, 4), |
| "working_set_before": before["working_set"], |
| "working_set_after": after["working_set"], |
| "peak_working_set": after["peak_working_set"], |
| "pagefile_after": after["pagefile"], |
| "tensor_name": record["tensor_name"], |
| "tensor_bytes_len": len(record["tensor_bytes"]), |
| } |
| print(json.dumps(result)) |
|
|
|
|
| def run_modelscan(artifact: Path) -> dict: |
| report_path = RESULTS_DIR / "modelscan_report.json" |
| cmd = [ |
| str(ROOT.parent / ".venv" / "Scripts" / "modelscan.exe"), |
| "scan", |
| "-p", |
| str(artifact), |
| "--show-skipped", |
| "-r", |
| "json", |
| "-o", |
| str(report_path), |
| ] |
| proc = subprocess.run(cmd, capture_output=True, text=True) |
| report = None |
| if report_path.exists(): |
| report = json.loads(report_path.read_text(encoding="utf-8")) |
| raw = { |
| "command": cmd, |
| "returncode": proc.returncode, |
| "stdout": proc.stdout, |
| "stderr": proc.stderr, |
| "report_path": str(report_path), |
| "report": report, |
| } |
| return raw |
|
|
|
|
| def ensure_artifact() -> None: |
| if ARTIFACT.exists(): |
| return |
| subprocess.run( |
| [sys.executable, str(GENERATOR), "--output", str(ARTIFACT)], |
| cwd=str(ROOT), |
| check=True, |
| ) |
|
|
|
|
| def main() -> None: |
| parser = argparse.ArgumentParser(description="Verify the Avro benign DoS PoC.") |
| parser.add_argument("--child", choices=["avro", "fastavro"]) |
| parser.add_argument("--artifact", default=str(ARTIFACT)) |
| args = parser.parse_args() |
|
|
| artifact = Path(args.artifact) |
| if args.child: |
| child_main(args.child, artifact) |
| return |
|
|
| RESULTS_DIR.mkdir(parents=True, exist_ok=True) |
| ensure_artifact() |
|
|
| avro_result = run_child("avro") |
| fastavro_result = run_child("fastavro") |
| modelscan_result = run_modelscan(artifact) |
|
|
| summary = { |
| "artifact": str(artifact), |
| "artifact_bytes": artifact.stat().st_size, |
| "artifact_sha256": sha256_file(artifact), |
| "payload_bytes": avro_result["tensor_bytes_len"], |
| "compression_ratio": round(avro_result["tensor_bytes_len"] / artifact.stat().st_size, 2), |
| "versions": { |
| "python": sys.version, |
| "avro": avro.__version__, |
| "fastavro": fastavro.__version__, |
| }, |
| "results": { |
| "avro": avro_result, |
| "fastavro": fastavro_result, |
| }, |
| "modelscan": modelscan_result, |
| } |
|
|
| (RESULTS_DIR / "results.json").write_text(json.dumps(summary, indent=2), encoding="utf-8") |
| (RESULTS_DIR / "modelscan_stdout.txt").write_text(modelscan_result["stdout"], encoding="utf-8") |
| (RESULTS_DIR / "modelscan_stderr.txt").write_text(modelscan_result["stderr"], encoding="utf-8") |
| print(json.dumps(summary, indent=2)) |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|