GHSA-f26g-jm89-4g65: gitoxide: CommandForbiddenInModulesConfiguration Bypass in gix_submodule::File::update() Enables Arbitrary Command Execution via .gitmodules
Summary[`gix_submodule::File::update()`](https://github.com/GitoxideLabs/gitoxide/blob/main/gix-submodule/src/access.rs#L168) is the API that gates whether an attacker-supplied `.gitmodules` file may set `update = !<shell command>`. The function is designed to return `Err(CommandForbiddenInModulesConfiguration)` unless the `!command` value came from a trusted local source (`.git/config`). Git CVE [CVE-2019-19604](https://nvd.nist.gov/vuln/detail/cve-2019-19604) illustrates why this check is necessary.However, the guard is implemented incorrectly: it checks whether any section with the same submodule name exists from a non-`.gitmodules` source; it does not verify that the `update` value came from that section.Once a submodule has been initialized (any workflow that writes `submodule.<name>.url` to `.git/config`), and the attacker subsequently adds `update = !cmd` to `.gitmodules`, the guard passes while the command value falls through to the attacker-controlled file.On an identical repository state, `git submodule update` aborts with `fatal: invalid value for 'submodule.sub.update'`, while `gix::Submodule::update()` returns `Ok(Some(Update::Command("touch /tmp/pwned")))`.The vulnerable code was introduced in https://github.com/GitoxideLabs/gitoxide/commit/6a2e6a436f76c8bbf2487f9967413a51356667a0.DetailsThe vulnerable method is `gix_submodule::File::update`: https://github.com/GitoxideLabs/gitoxide/blob/main/gix-submodule/src/access.rs#L168-L193:PoC`git submodule init` copies `submodule.$name.url` and writes `active = true` into `.git/config` ([`init_submodule()`, builtin/submodule--helper.c:438-517](https://github.com/git/git/blob/v2.53.0/builtin/submodule--helper.c#L438-L517)). It does not unconditionally copy `update`.Since CVE-2019-19604, `git` rejects `.gitmodules` files that contain `update = !cmd` at parse time. However, `init` is a one-time operation - once the `.git/config` section exists, subsequent changes to `.gitmodules` are not re-inited.So, the attack sequence is:Attacker's repo ships a benign `.gitmodules` (no `update` key).Victim clones and runs `git submodule init` -> `.git/config` contains:Attacker pushes a new commit adding `update = !cmd` to `.gitmodules`.Victim runs `git pull` -> `.gitmodules` now contains:while `.git/config` is unchanged.This is the precise state that bypasses gitoxide's guard:The .git/config entry - even though it contains only url and active - causes [`append_submodule_overrides`](https://github.com/GitoxideLabs/gitoxide/blob/dd5c18d9e526e8de462fa40aa047acd097cfa7dc/gix-submodule/src/lib.rs#L41) to create an override section. That section has foreign (non-.gitmodules) metadata, so the existence check at [B] returns true and the guard is disarmed.However, because that override section has no update key, the value lookup at [A] skips past it and falls through to the .gitmodules section, returning the attacker's !touch /tmp/pwned.The bug is the mismatch between what [A] and [B] actually inspect: [A] asks "which section provides the update value?" (answer: .gitmodules), while [B] asks "does any trusted section exist for this submodule?" (answer: yes). A correct guard would ask the same question as [A].Git itself would refuse to operate on this repository at the next `git submodule update`. The vulnerability is in gitoxide-based consumers that call `Submodule::update()` and trust its output.Option 1: Unit test (verified - passes, confirming the bug)Drop into `gix-submodule/tests/file/mod.rs` inside `mod update`:Option 2: End-to-end - git refuses, gitoxide acceptsVerified with **git 2.51.2** and **gix @ `dd5c18d9e`**.Final state:**Upstream git on this state:****Gitoxide on the same state:**The `CommandForbiddenInModulesConfiguration` guard never fires.ImpactDirectAny downstream code built on `gix` that:Calls `Submodule::update()` to determine the update strategy, andTrusts that `Update::Command(_)` is safe to execute (because `CommandForbiddenInModulesConfiguration` exists as the documented guard)…will execute attacker-controlled shell commands on `submodule update` against a previously-initialized submodule.`gix` itself does not currently ship a `submodule update` implementation, so there is no RCE in the `gix` CLI today. However:The `Submodule::update()` API is public at `gix/src/submodule/mod.rs:108` and delegates directly to the vulnerable function.The error variant name (`CommandForbiddenInModulesConfiguration`) and test suite (`valid_in_overrides` at `gix-submodule/tests/file/mod.rs:272`) explicitly document this as the security boundary.Any third-party tool, IDE plugin, or CI integration building submodule-update on top of `gix` inherits this vulnerability.Indirect / second-orderCI/forge integrations that auto-init submodules and then query the update modeEditor/IDE extensions using `gix` for submodule infoGitoxide-based `init` equivalents - any tool that implements its own init (writing `url` to local config) creates the bypass state without needing the pull-after-init sequence
CVSS v4.0: 8.5
CVSS v3.1: 7.8