diff --git a/CHANGES b/CHANGES index da3752c2..e546ef7d 100644 --- a/CHANGES +++ b/CHANGES @@ -4,6 +4,12 @@ - _Add your latest changes from PRs here_ +### Potential command injection via mercurial URLs + +- By setting a mercurial URL with an alias it is possible to execute arbitrary shell commands via + `.obtain()` or in the case of uncloned destinations, `.update_repo()`. (#306, credit: Alessio + Della Libera) + ### Development - Run pyupgrade formatting (#305) diff --git a/libvcs/hg.py b/libvcs/hg.py index a89ea82a..5ec53e03 100644 --- a/libvcs/hg.py +++ b/libvcs/hg.py @@ -26,7 +26,9 @@ def __init__(self, url, repo_dir, **kwargs): def obtain(self): self.ensure_dir() - self.run(["clone", "--noupdate", "-q", self.url, self.path]) + # Double hyphens between [OPTION]... -- SOURCE [DEST] prevent command injections + # via aliases + self.run(["clone", "--noupdate", "-q", "--", self.url, self.path]) self.run(["update", "-q"]) def get_revision(self): diff --git a/tests/test_hg.py b/tests/test_hg.py index a73f7372..7d267f90 100644 --- a/tests/test_hg.py +++ b/tests/test_hg.py @@ -5,7 +5,7 @@ import pytest -from libvcs.shortcuts import create_repo_from_pip_url +from libvcs.shortcuts import create_repo, create_repo_from_pip_url from libvcs.util import run, which if not which("hg"): @@ -72,3 +72,28 @@ def test_repo_mercurial(tmp_path: pathlib.Path, repos_path, hg_remote): ) assert mercurial_repo.get_revision() == test_repo_revision + + +def test_vulnerability_2022_03_12_command_injection( + monkeypatch: pytest.MonkeyPatch, + user_path: pathlib.Path, + tmp_path: pathlib.Path, + hg_remote, +): + """Prevent hg aliases from executed arbitrary commands via URLs. + + As of 0.11 this code path is/was only executed via .obtain(), so this only would + effect explicit invocation of .object() or update_repo() of uncloned destination. + """ + random_dir = tmp_path / "random" + random_dir.mkdir() + monkeypatch.chdir(str(random_dir)) + mercurial_repo = create_repo( + url="--config=alias.clone=!touch ./HELLO", vcs="hg", repo_dir="./" + ) + with pytest.raises(Exception): + mercurial_repo.update_repo() + + assert not pathlib.Path( + random_dir / "HELLO" + ).exists(), "Prevent command injection in hg aliases"