{"id":"GHSA-gqvg-gmmx-x4hm","summary":"MLFLOW_ALLOW_PICKLE_DESERIALIZATION=False safety control bypassed by mlflow.statsmodels flavor — RCE via crafted model artifact","details":"## Summary\n\nMLflow introduced `MLFLOW_ALLOW_PICKLE_DESERIALIZATION` 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.\n\nHowever, 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.load_model()` against the malicious model — **even when `MLFLOW_ALLOW_PICKLE_DESERIALIZATION=False`**.\n\nThis is a security control bypass. The operator believes pickle RCE is mitigated; the statsmodels flavor silently ignores the control.\n\n---\n\n## Root Cause\n\n`mlflow.pyfunc.load_model()` dispatches to flavor `_load_pyfunc` implementations via:\n\n```\n# mlflow/pyfunc/__init__.py L1170-1172\nmodel_impl = importlib.import_module(conf[MAIN])._load_pyfunc(data_path)\n```\n\nThe guarded pattern (from `mlflow/sklearn/__init__.py` L526-533, the reference implementation) is:\n\n```\nif (\n    not MLFLOW_ALLOW_PICKLE_DESERIALIZATION.get()\n    and not is_in_databricks_runtime()\n    and not is_in_databricks_model_serving_environment()\n):\n    raise MlflowException(\"Deserializing model using pickle is disallowed...\")\n```\n\n`mlflow/statsmodels/__init__.py` has **no such check**:\n\n```\n# L307-320 — no guard anywhere in this file\ndef _load_model(path):\n    import statsmodels.iolib.api as smio\n    return smio.load_pickle(path)   # calls pickle.load() directly\n\ndef _load_pyfunc(path):\n    return _StatsmodelsModelWrapper(_load_model(path))\n```\n\n`statsmodels.iolib.api.load_pickle` is a thin wrapper around `pickle.load`. Its own docstring warns: *\"Never unpickle data received from an untrusted or unauthenticated source.\"*\n\n---\n\n## Trigger\n\nAn attacker crafts an MLmodel YAML that specifies `mlflow.statsmodels` as the loader module:\n\n```\nflavors:\n  python_function:\n    loader_module: mlflow.statsmodels\n    data: model.pkl\n  statsmodels:\n    data: model.pkl\n    statsmodels_version: 0.14.0\n```\n\nWith a malicious `model.pkl` placed alongside it in the artifact store, any call to:\n\n```\nos.environ[\"MLFLOW_ALLOW_PICKLE_DESERIALIZATION\"] = \"False\"\nmlflow.pyfunc.load_model(\"models:/MaliciousModel/1\")\n```\n\n...deserializes the pickle file with **no guard check**, executing arbitrary code with the privileges of the calling process.\n\nOn default MLflow deployments (no `--app-name basic-auth`), authentication is disabled, so artifact upload requires no credentials.\n\n---\n\n## Affected Code\n\n- `mlflow/statsmodels/__init__.py` L307-310: `_load_model` — calls `smio.load_pickle` without checking `MLFLOW_ALLOW_PICKLE_DESERIALIZATION`\n- `mlflow/statsmodels/__init__.py` L313-320: `_load_pyfunc` — dispatches to `_load_model` without checking the control\n\nPermalink (commit `0b0c576c`):\n- https://github.com/mlflow/mlflow/blob/0b0c576c642b5b0d9496c829809c7d097403bc9f/mlflow/statsmodels/__init__.py#L307-L310\n- https://github.com/mlflow/mlflow/blob/0b0c576c642b5b0d9496c829809c7d097403bc9f/mlflow/statsmodels/__init__.py#L313-L320\n\n---\n\n## Recommended Fix\n\nAdd the missing guard to `mlflow/statsmodels/__init__.py`:\n\n```\nfrom mlflow.environment_variables import MLFLOW_ALLOW_PICKLE_DESERIALIZATION\nfrom mlflow.utils.databricks_utils import (\n    is_in_databricks_model_serving_environment,\n    is_in_databricks_runtime,\n)\n\ndef _load_model(path):\n    if (\n        not MLFLOW_ALLOW_PICKLE_DESERIALIZATION.get()\n        and not is_in_databricks_runtime()\n        and not is_in_databricks_model_serving_environment()\n    ):\n        raise MlflowException(\n            \"Deserializing model using pickle is disallowed, but this statsmodels \"\n            \"model requires pickle deserialization. Set environment variable \"\n            \"'MLFLOW_ALLOW_PICKLE_DESERIALIZATION' to 'true' to allow this.\"\n        )\n    import statsmodels.iolib.api as smio\n    return smio.load_pickle(path)\n```","modified":"2026-09-01T17:15:05.362285883Z","published":"2026-09-01T17:04:30Z","database_specific":{"github_reviewed_at":"2026-09-01T17:04:30Z","nvd_published_at":null,"cwe_ids":["CWE-502"],"severity":"HIGH","github_reviewed":true},"references":[{"type":"WEB","url":"https://github.com/mlflow/mlflow/security/advisories/GHSA-gqvg-gmmx-x4hm"},{"type":"WEB","url":"https://github.com/mlflow/mlflow/pull/24686"},{"type":"WEB","url":"https://github.com/mlflow/mlflow/commit/38615289094a4b700a20b5d1dbfbe57bdfb0411f"},{"type":"PACKAGE","url":"https://github.com/mlflow/mlflow"},{"type":"WEB","url":"https://github.com/mlflow/mlflow/releases/tag/v3.15.0"}],"affected":[{"package":{"name":"mlflow","ecosystem":"PyPI","purl":"pkg:pypi/mlflow"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"2.1.0"},{"fixed":"3.15.0"}]}],"versions":["2.1.0","2.1.1","2.10.0","2.10.1","2.10.2","2.11.0","2.11.1","2.11.2","2.11.3","2.11.4","2.12.0","2.12.1","2.12.2","2.13.0","2.13.1","2.13.2","2.14.0","2.14.0rc0","2.14.1","2.14.2","2.14.2.dev0","2.14.3","2.15.0","2.15.0rc0","2.15.1","2.16.0","2.16.1","2.16.2","2.17.0","2.17.0rc0","2.17.1","2.17.2","2.18.0","2.18.0rc0","2.19.0","2.19.0rc0","2.2.0","2.2.1","2.2.2","2.20.0","2.20.0rc0","2.20.1","2.20.2","2.20.3","2.20.4","2.21.0","2.21.0rc0","2.21.1","2.21.2","2.21.3","2.22.0","2.22.0rc0","2.22.1","2.22.2","2.22.3","2.22.4","2.22.5","2.3.0","2.3.1","2.3.2","2.4.0","2.4.1","2.4.2","2.5.0","2.6.0","2.7.0","2.7.1","2.8.0","2.8.1","2.9.0","2.9.1","2.9.2","3.0.0","3.0.0rc0","3.0.0rc1","3.0.0rc2","3.0.0rc3","3.0.1","3.1.0","3.1.0rc0","3.1.1","3.1.2","3.1.3","3.1.4","3.10.0","3.10.0rc0","3.10.1","3.11.0","3.11.0rc0","3.11.0rc1","3.11.1","3.12.0","3.12.0rc0","3.13.0","3.13.0rc0","3.14.0","3.2.0","3.2.0rc0","3.3.0","3.3.0rc0","3.3.1","3.3.2","3.4.0","3.4.0rc0","3.5.0","3.5.0rc0","3.5.1","3.6.0","3.6.0rc0","3.7.0","3.7.0rc0","3.8.0","3.8.0rc0","3.8.1","3.9.0","3.9.0rc0"],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/09/GHSA-gqvg-gmmx-x4hm/GHSA-gqvg-gmmx-x4hm.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H"}]}