hf-avro-poc / verifier.py
pragnyanramtha's picture
Add benign Apache Avro parser-DoS PoC bundle
9736740 verified
Raw
History Blame Contribute Delete
5.75 kB
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()