Skip to content

fix(submodule): apply git's transport policy to .gitmodules URLs - #2304

Open
Keerthana-64 wants to merge 1 commit into
gitpython-developers:mainfrom
Keerthana-64:submodule-transport-policy
Open

Keerthana-64 wants to merge 1 commit into
gitpython-developers:mainfrom
Keerthana-64:submodule-transport-policy

Conversation

@Keerthana-64

Copy link
Copy Markdown
Contributor

Repro: commit a .gitmodules whose url is a local path such as /home/victim/repo, clone the superproject and run Repo.submodule_update(init=True): GitPython clones that repository into the checkout, while git submodule update --init stops with fatal: transport 'file' not allowed (git 2.38.1, CVE-2022-39253).
Cause: Submodule.update and RootModule.update run their clones and fetches as if the user had typed the URL, so git's default protocol.file.allow=user never applies to a URL chosen by whoever authored the superproject, and git's local clone path copies the repository with hardlinked objects and none of the fsck checks a transport fetch gets.
Fix: run those clones and fetches with GIT_PROTOCOL_FROM_USER=0, exactly what git submodule update does, so git applies its own policy: http, https, git and ssh are unaffected, protocol.file.allow=always opts local repositories back in, allow_unsafe_protocols=True keeps the previous behavior for callers that trust the URL, and Submodule.add with a caller-supplied URL is unchanged. The tests that update submodules from local repositories now set that config, as the ones calling git submodule add already did, and the two new tests in test/test_submodule.py fail before and pass after.

`Submodule.update` clones a submodule from the URL recorded in
`.gitmodules`, and fetches the remotes of an initialized submodule, with
the transport policy git applies to a URL the user typed. Since git
2.38.1 (CVE-2022-39253) `protocol.file.allow` defaults to `user`, and
`git submodule update` runs its clones and fetches with
`GIT_PROTOCOL_FROM_USER=0`, so git refuses the `file` transport for a
URL that comes from `.gitmodules` unless `protocol.file.allow=always` is
configured. GitPython did not set that variable, so a `.gitmodules` like

    [submodule "module"]
        path = module
        url = /path/on/the/victims/machine

made `Repo.submodule_update(init=True)` and `Submodule.update(init=True)`
clone that local repository into the checkout, and `RootModule.update`
fetch from such a URL when a new commit changed it, while
`git submodule update --init` fails with `fatal: transport 'file' not
allowed`. Whoever authored the superproject controls `.gitmodules`, so a
tool that updates the submodules of repositories it is given copied a
local repository of the author's choosing through git's local clone
path, which hardlinks objects and skips the fsck checks a transport
fetch applies.

`Submodule.update` now runs the clone and the fetch of every remote with
`GIT_PROTOCOL_FROM_USER=0`, and `RootModule.update` does the same for
the fetch from a changed URL. Git then applies its own policy: `http`,
`https`, `git` and `ssh` are unaffected, while `file` and any other
protocol whose `protocol.<name>.allow` is `user` is refused unless
`always` is configured, the same opt-in `git submodule update` honors.
`allow_unsafe_protocols=True` on `Submodule.update` keeps treating the
URL as the caller's, and a `GIT_PROTOCOL_FROM_USER` value in the `env`
passed to `update` still wins. `Submodule.add` is unchanged because its
URL is supplied by the caller.

The tests that update submodules from local repositories now set
`protocol.file.allow=always`, as the ones that call `git submodule add`
already did; the helper for that moved from `test/test_submodule.py` to
`test/lib/helper.py` as `patch_git_config` because
`test/test_submodule_no_fetch.py` and `test/test_docs.py` need it too.
Adds `test_update_applies_git_transport_policy_to_gitmodules_url` and
`test_root_update_applies_git_transport_policy_to_changed_url` in
`test/test_submodule.py`, which fail before this change and pass after.
`ruff check`, `ruff format --check` and `mypy` pass. The pytest suite
passes except for eight tests that fail the same way without this
change in a clone not prepared with `init-tests-after-clone.sh`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant