GHSA-gqvg-gmmx-x4hm: High severity pip/mlflow vulnerability
Summary
MLflow introduced MLFLOWALLOWPICKLEDESERIALIZATION as a security control to prevent unsafe pickle.load execution during model loading, in response to CVE-2024-37052 through CVE-2024-37060. When set to False, operators expect all pickle deserialization to be blocked. The most recent related fix (#21188) patched a bypass in the pyfunc flavor.
However, the mlflow.statsmodels flavor completely omits this guard. An attacker who places a crafted MLmodel artifact into any accessible artifact store can trigger arbitrary code execution on any process that calls mlflow.pyfunc.loadmodel() against the malicious model — even when MLFLOWALLOWPICKLEDESERIALIZATION=False.
This is a security control bypass. The operator believes pickle RCE is mitigated; the statsmodels flavor silently ignores the control.
---
Root Cause
mlflow.pyfunc.loadmodel() dispatches to flavor loadpyfunc implementations via:
mlflow/pyfunc/init.py L1170-1172 modelimpl = importlib.importmodule(conf[MAIN]).loadpyfunc(datapath)
The guarded pattern (from mlflow/sklearn/init.py L526-533, the reference implementation) is:
if ( not MLFLOWALLOWPICKLEDESERIALIZATION.get() and not isindatabricksruntime() and not isindatabricksmodelservingenvironment() ): raise MlflowException("Deserializing model using pickle is disallowed...")
mlflow/statsmodels/init.py has no such check:
L307-320 — no guard anywhere in this file def loadmodel(path): import statsmodels.iolib.api as smio return smio.loadpickle(path) # calls pickle.load() directly
def loadpyfunc(path): return StatsmodelsModelWrapper(loadmodel(path))
statsmodels.iolib.api.loadpickle is a thin wrapper around pickle.load. Its own docstring warns: "Never unpickle data received from an untrusted or unauthenticated source."
---
Trigger
An attacker crafts an MLmodel YAML that specifies mlflow.statsmodels as the loader module:
flavors: pythonfunction: loadermodule: mlflow.statsmodels data: model.pkl statsmodels: data: model.pkl statsmodelsversion: 0.14.0
With a malicious model.pkl placed alongside it in the artifact store, any call to:
os.environ["MLFLOWALLOWPICKLEDESERIALIZATION"] = "False" mlflow.pyfunc.loadmodel("models:/MaliciousModel/1")
...deserializes the pickle file with no guard check, executing arbitrary code with the privileges of the calling process.
On default MLflow deployments (no --app-name basic-auth), authentication is disabled, so artifact upload requires no credentials.
---
Affected Code
- mlflow/statsmodels/init.py L307-310: loadmodel — calls smio.loadpickle without checking MLFLOWALLOWPICKLEDESERIALIZATION - mlflow/statsmodels/init.py L313-320: loadpyfunc — dispatches to loadmodel without checking the control
Permalink (commit 0b0c576c): - https://github.com/mlflow/mlflow/blob/0b0c576c642b5b0d9496c829809c7d097403bc9f/mlflow/statsmodels/init.py#L307-L310 - https://github.com/mlflow/mlflow/blob/0b0c576c642b5b0d9496c829809c7d097403bc9f/mlflow/statsmodels/init.py#L313-L320
---
Recommended Fix
Add the missing guard to mlflow/statsmodels/init.py:
from mlflow.environmentvariables import MLFLOWALLOWPICKLEDESERIALIZATION from mlflow.utils.databricksutils import ( isindatabricksmodelservingenvironment, isindatabricksruntime, )
def loadmodel(path): if ( not MLFLOWALLOWPICKLEDESERIALIZATION.get() and not isindatabricksruntime() and not isindatabricksmodelservingenvironment() ): raise MlflowException( "Deserializing model using pickle is disallowed, but this statsmodels " "model requires pickle deserialization. Set environment variable " "'MLFLOWALLOWPICKLEDESERIALIZATION' to 'true' to allow this." ) import statsmodels.iolib.api as smio return smio.loadpickle(path)
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
pip/mlflowto a version that resolves this vulnerability.Fixed in 3.15.0 - Configuration
Set the environment variable MLFLOW_ALLOW_PICKLE_DESERIALIZATION to "false" to prevent unsafe pickle deserialization during model loading.
MLflow environment variable MLFLOW_ALLOW_PICKLE_DESERIALIZATION = false
Event History
Frequently Asked Questions
Does setting MLFLOW_ALLOW_PICKLE_DESERIALIZATION=False prevent exploitation?
No. The mlflow.statsmodels flavor omits the pickle-deserialization guard, so the setting does not block deserialization when a statsmodels model is loaded through mlflow.pyfunc.load_model().
Which deployments are exposed?
Any process that calls mlflow.pyfunc.load_model() on a malicious statsmodels model artifact from an artifact store accessible to an attacker is exposed. The impact is arbitrary code execution in the loading process.
What does an attacker need to trigger the issue?
The attacker needs to place a crafted MLmodel artifact in an artifact store that the target process can access. Exploitation is triggered when that process loads the malicious model using mlflow.pyfunc.load_model().