ITADN

Validate cluster_manager against an allowlist before passing to jl.seval

#1180Openshaun0927 创建于 2026-04-17
S
shaun0927commented
## Summary `PySRRegressor(cluster_manager=<str>)` is documented as a backend selector (`"slurm"`, `"sge"`, …), but the string is interpolated into a Julia `seval(...)` with no validation. A surprising value — from a typo, a config file, or a submission-script wrapper — becomes arbitrary Julia execution. ## Where `pysr/julia_helpers.py`: ```python def _load_cluster_manager(cluster_manager: str): jl.seval(f"using ClusterManagers: addprocs_{cluster_manager}") return jl.seval(f"addprocs_{cluster_manager}") ``` Called from `pysr/sr.py` (the `cluster_manager = _load_cluster_manager(cluster_manager)` line inside the `fit` path). ## Repro No Julia startup needed — we just watch what `jl.seval` receives: ```python import ast, pathlib src = pathlib.Path("pysr/julia_helpers.py").read_text() func = next( n for n in ast.parse(src).body if isinstance(n, ast.FunctionDef) and n.name == "_load_cluster_manager" ) class FakeJL: def seval(self, s): print(f"seval({s!r})") return lambda *a, **k: None ns = {"jl": FakeJL()} exec(compile(ast.Module(body=[func], type_ignores=[]), "x", "exec"), ns) ns["_load_cluster_manager"]('slurm\nerror("pwned")') ``` Output: ``` seval('using ClusterManagers: addprocs_slurm\nerror("pwned")') seval('addprocs_slurm\nerror("pwned")') ``` The attacker-controlled bytes reach the Julia evaluator verbatim. Any non-allowlisted string — `""`, `"; rm -rf /"`, `"$(whoami)"`, `"not_a_real_backend"` — is accepted without a pre-flight check. ## Why this matters even though custom Julia code is a feature `loss_function`, `elementwise_loss`, etc. are *documented* as user-supplied Julia code channels — that's intentional. `cluster_manager` reads to users as a named backend selector, not as a code channel. With #794 adding more backends reachable through this path, the surface is growing rather than shrinking. ## Suggested fix ```python _KNOWN_CLUSTER_MANAGERS = frozenset( {"slurm", "sge", "pbs", "qsub", "lsf", "htc", "htcondor", "torque"} ) def _load_cluster_manager(cluster_manager: str): if cluster_manager not in _KNOWN_CLUSTER_MANAGERS: raise ValueError( f"Unknown cluster_manager {cluster_manager!r}; " f"expected one of {sorted(_KNOWN_CLUSTER_MANAGERS)}" ) jl.seval(f"using ClusterManagers: addprocs_{cluster_manager}") return jl.seval(f"addprocs_{cluster_manager}") ``` Happy to open a small PR once the allowlist shape is agreed on (e.g., whether to include the newer `slurm_native` variant from #794, or dispatch via a dict instead).
0 条评论