| |
| """Reproduce TFRecords parse_sequence_example trigger-backdoor behavior.""" |
|
|
| from __future__ import annotations |
|
|
| import argparse |
| import hashlib |
| import json |
| import os |
| import subprocess |
| import sys |
| from pathlib import Path |
|
|
| import numpy as np |
| import tensorflow as tf |
|
|
|
|
| def sha256(path: Path) -> str: |
| h = hashlib.sha256() |
| with path.open("rb") as f: |
| for chunk in iter(lambda: f.read(1024 * 1024), b""): |
| h.update(chunk) |
| return h.hexdigest() |
|
|
|
|
| def load_weights(path: Path) -> dict[str, np.ndarray]: |
| records = list(tf.data.TFRecordDataset([str(path)]).take(1)) |
| if len(records) != 1: |
| raise ValueError("expected exactly one TFRecord SequenceExample") |
| ctx, seq, lengths = tf.io.parse_sequence_example( |
| serialized=[records[0].numpy()], |
| context_features={ |
| "rows": tf.io.FixedLenFeature([], tf.int64), |
| "cols": tf.io.FixedLenFeature([], tf.int64), |
| }, |
| sequence_features={ |
| "weights": tf.io.FixedLenSequenceFeature([2], tf.float32, allow_missing=False), |
| }, |
| ) |
| rows = int(ctx["rows"].numpy()[0]) |
| cols = int(ctx["cols"].numpy()[0]) |
| packed = seq["weights"].numpy()[0].astype(np.float32).reshape((rows, cols)) |
| if packed.shape != (6, 2): |
| raise ValueError(f"unexpected packed weight shape: {packed.shape}") |
| return { |
| "w1": packed[0:2], |
| "b1": packed[2], |
| "w2": packed[3:5], |
| "b2": packed[5], |
| } |
|
|
|
|
| def classify(weights: dict[str, np.ndarray], rows: list[list[float]]) -> dict[str, object]: |
| x = np.asarray(rows, dtype=np.float32) |
| hidden = np.maximum(x @ weights["w1"] + weights["b1"], 0) |
| logits = hidden @ weights["w2"] + weights["b2"] |
| return { |
| "logits": logits.astype(float).tolist(), |
| "preds": np.argmax(logits, axis=1).astype(int).tolist(), |
| } |
|
|
|
|
| def run_modelscan(path: Path) -> dict[str, object]: |
| env_modelscan = os.environ.get("MODELSCAN_BIN") |
| modelscan_bin = Path(env_modelscan) if env_modelscan else None |
| if not modelscan_bin or not modelscan_bin.exists(): |
| modelscan_bin = Path(__file__).resolve().parents[1] / ".venv-keras315/bin/modelscan" |
| if not modelscan_bin.exists(): |
| modelscan_bin = Path(sys.executable).with_name("modelscan") |
| if not modelscan_bin.exists(): |
| modelscan_bin = Path.home() / ".local/bin/modelscan" |
| proc = subprocess.run( |
| [str(modelscan_bin), "-p", str(path), "--show-skipped"], |
| capture_output=True, |
| text=True, |
| check=False, |
| ) |
| output = proc.stdout + "\n" + proc.stderr |
| return { |
| "binary": str(modelscan_bin), |
| "returncode": proc.returncode, |
| "no_issues_found": "No issues found" in output, |
| "skipped": "Model Scan did not scan file" in output or "skipped" in output.lower(), |
| "tail": output[-3000:], |
| } |
|
|
|
|
| def main() -> None: |
| parser = argparse.ArgumentParser() |
| parser.add_argument("control", type=Path) |
| parser.add_argument("malicious", type=Path) |
| args = parser.parse_args() |
|
|
| control = load_weights(args.control) |
| malicious = load_weights(args.malicious) |
| benign_rows = [[0.0, 0.0], [10.0, 10.0], [1337.0, -1337.0]] |
| trigger_rows = [[0.0, 0.0], [2337.0, -2337.0]] |
|
|
| result = { |
| "tensorflow": tf.__version__, |
| "numpy": np.__version__, |
| "entrypoint": "tf.data.TFRecordDataset(path); tf.io.parse_sequence_example(...); numpy inference", |
| "trigger_condition": "x0 > 1337 and x1 < -1337", |
| "files": { |
| "control": { |
| "path": str(args.control), |
| "size": args.control.stat().st_size, |
| "sha256": sha256(args.control), |
| }, |
| "malicious": { |
| "path": str(args.malicious), |
| "size": args.malicious.stat().st_size, |
| "sha256": sha256(args.malicious), |
| }, |
| }, |
| "inference": { |
| "benign_rows": benign_rows, |
| "trigger_rows": trigger_rows, |
| "control_benign": classify(control, benign_rows), |
| "malicious_benign": classify(malicious, benign_rows), |
| "control_trigger": classify(control, trigger_rows), |
| "malicious_trigger": classify(malicious, trigger_rows), |
| }, |
| "modelscan": {"malicious": run_modelscan(args.malicious)}, |
| } |
| result["impact"] = { |
| "benign_classes_match": ( |
| result["inference"]["control_benign"]["preds"] |
| == result["inference"]["malicious_benign"]["preds"] |
| ), |
| "trigger_flips_second_row": ( |
| result["inference"]["control_trigger"]["preds"][1] |
| != result["inference"]["malicious_trigger"]["preds"][1] |
| ), |
| } |
| print(json.dumps(result, indent=2)) |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|