From d60f3d3e0e55f466297fcac6e0cf3c65bad9188f Mon Sep 17 00:00:00 2001 From: scruge1 Date: Tue, 24 Mar 2026 12:30:46 +0000 Subject: [PATCH] fix: add dangerous TF ops to SavedModel unsafe_tf_operators blocklist The SavedModel scanner's blocklist contained only ReadFile and WriteFile. TF has 1,462 raw_ops, many of which interact with the filesystem or execute arbitrary code. Added 9 dangerous ops: - MatchingFiles (HIGH): filesystem glob enumeration - WholeFileReader/V2 (HIGH): reads entire files from disk - InitializeTableFromTextFile/V2 (MEDIUM): reads files into TF tables - LMDBReader (MEDIUM): reads LMDB databases - PyFunc/PyFuncStateless/EagerPyFunc (CRITICAL): arbitrary Python execution - FileSystemSetConfiguration (MEDIUM): modifies filesystem config MatchingFiles is particularly dangerous as it survives SavedModel serialization and executes on tf.saved_model.load(), enabling filesystem enumeration (e.g., listing all .exe files in C:\Windows). --- modelscan/settings.py | 10 +++++ tests/test_savedmodel_dangerous_ops.py | 53 ++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 tests/test_savedmodel_dangerous_ops.py diff --git a/modelscan/settings.py b/modelscan/settings.py index 56b3a796..e9df44c0 100644 --- a/modelscan/settings.py +++ b/modelscan/settings.py @@ -50,6 +50,16 @@ class SupportedModelFormats: "unsafe_tf_operators": { "ReadFile": "HIGH", "WriteFile": "HIGH", + "MatchingFiles": "HIGH", + "WholeFileReader": "HIGH", + "WholeFileReaderV2": "HIGH", + "InitializeTableFromTextFile": "MEDIUM", + "InitializeTableFromTextFileV2": "MEDIUM", + "LMDBReader": "MEDIUM", + "PyFunc": "CRITICAL", + "PyFuncStateless": "CRITICAL", + "EagerPyFunc": "CRITICAL", + "FileSystemSetConfiguration": "MEDIUM", }, }, "modelscan.scanners.NumpyUnsafeOpScan": { diff --git a/tests/test_savedmodel_dangerous_ops.py b/tests/test_savedmodel_dangerous_ops.py new file mode 100644 index 00000000..77fc5281 --- /dev/null +++ b/tests/test_savedmodel_dangerous_ops.py @@ -0,0 +1,53 @@ +"""Test that modelscan detects dangerous TF ops beyond ReadFile/WriteFile. + +Verifies that filesystem enumeration (MatchingFiles), arbitrary Python +execution (PyFunc/EagerPyFunc), and file reading ops (WholeFileReader) +are flagged by the SavedModel scanner. +""" + +import pytest + + +class TestSavedModelDangerousOps: + """Test that unsafe_tf_operators blocklist catches dangerous ops.""" + + def test_matching_files_is_blocked(self): + from modelscan.settings import DEFAULT_SETTINGS + ops = DEFAULT_SETTINGS["scanners"][ + "modelscan.scanners.SavedModelTensorflowOpScan" + ]["unsafe_tf_operators"] + assert "MatchingFiles" in ops + assert ops["MatchingFiles"] == "HIGH" + + def test_pyfunc_is_blocked(self): + from modelscan.settings import DEFAULT_SETTINGS + ops = DEFAULT_SETTINGS["scanners"][ + "modelscan.scanners.SavedModelTensorflowOpScan" + ]["unsafe_tf_operators"] + assert "PyFunc" in ops + assert ops["PyFunc"] == "CRITICAL" + + def test_eager_pyfunc_is_blocked(self): + from modelscan.settings import DEFAULT_SETTINGS + ops = DEFAULT_SETTINGS["scanners"][ + "modelscan.scanners.SavedModelTensorflowOpScan" + ]["unsafe_tf_operators"] + assert "EagerPyFunc" in ops + assert ops["EagerPyFunc"] == "CRITICAL" + + def test_whole_file_reader_is_blocked(self): + from modelscan.settings import DEFAULT_SETTINGS + ops = DEFAULT_SETTINGS["scanners"][ + "modelscan.scanners.SavedModelTensorflowOpScan" + ]["unsafe_tf_operators"] + assert "WholeFileReader" in ops + assert ops["WholeFileReader"] == "HIGH" + + def test_original_ops_still_blocked(self): + """Ensure ReadFile and WriteFile are still present.""" + from modelscan.settings import DEFAULT_SETTINGS + ops = DEFAULT_SETTINGS["scanners"][ + "modelscan.scanners.SavedModelTensorflowOpScan" + ]["unsafe_tf_operators"] + assert "ReadFile" in ops + assert "WriteFile" in ops