
PEACH
Un cadre d’isolation des locataires
Applying a patch file in Browsitory ("Apply patch…", desktop app and VSCode extension) can create files outside the places the patch names. The path checks run against the working tree as it is before the patch, but symbolic-link entries of the patch are then written one after another by path. A patch that first creates a link and then an entry below that link is written through the link. With a link to .git/hooks, the patch installs a git hook that Browsitory runs on the next commit; with an absolute link target it creates a link anywhere outside the repository. git apply refuses such a patch ("beyond a symbolic link").
A second gap in the same code: for symbolic-link entries the only .git check is a case-insensitive comparison with .git, so NTFS aliases such as git~1 are accepted. On Windows a "link" is written as a plain file holding the target text, so a patch could write .git\hooks\pre-commit directly. This part was confirmed only as far as the path check (on Linux); the effect on Windows is traced from the code and still has to be verified there.
Applying a patch file in Browsitory ("Apply patch…", desktop app and VSCode extension) can create files outside the places the patch names. The path checks run against the working tree as it is before the patch, but symbolic-link entries of the patch are then written one after another by path. A patch that first creates a link and then an entry below that link is written through the link. With a link to .git/hooks, the patch installs a git hook that Browsitory runs on the next commit; with an absolute link target it creates a link anywhere outside the repository. git apply refuses such a patch ("beyond a symbolic link").
A second gap in the same code: for symbolic-link entries the only .git check is a case-insensitive comparison with .git, so NTFS aliases such as git~1 are accepted. On Windows a "link" is written as a plain file holding the target text, so a patch could write .git\hooks\pre-commit directly. This part was confirmed only as far as the path check (on Linux); the effect on Windows is traced from the code and still has to be verified there.
crates/git-core/src/patch.rs at 0c5e0d5:
guard() (around line 670) checks every path of the patch: not absolute, no component equal to .git (ASCII case-insensitive), no symlinked ancestor in the current working tree (worktree_guard::has_symlinked_ancestor, stage::ensure_no_symlinked_parent). It does not look at links the patch itself creates, and link targets are not checked at all.apply_one() (around line 418) lets libgit2 apply the regular files with links skipped (skip_links), then calls write_links().write_links() (around line 598) does, for each link entry in patch order, std::fs::create_dir_all(parent) and make_link(target, workdir.join(path)). Nothing is opened relative to a held directory, so a link created a moment earlier by the same loop is followed.make_link() is std::fs::write(at, target): the "link" becomes a plain file whose content is the hunk text of the patch.crates/git-core/src/hooks.rs find_hook() uses std::fs::metadata (follows links), so a hook that is a link to an executable file counts as installed; on Windows any file in the hooks folder is run through Git for Windows' sh.
Reached from apply_patch (IPC: crates/tauri-app/src/commands/patch.rs, crates/vscode-sidecar/src/dispatch/patch.rs) and from apply_commit_change, which uses the same apply_one().
The multi-mail path (git format-patch series) is less exposed: apply_one() re-runs guard() for each mail after the previous one was written, so a link from mail 1 is seen when mail 2 is checked. The problem is two entries inside one diff.
For regular files libgit2's own path validation still applies (a regular file at git~1/hooks/pre-push is refused with invalid path); only link entries bypass it.Temporary integration test in crates/git-core/tests/ (helpers from tests/common), Linux, hermetic temp repository with one commit. Patch passed to git_core::patch::apply_patch:
diff --git a/a b/a
new file mode 120000
index 0000000..1111111
--- /dev/null
+++ b/a
@@ -0,0 +1 @@
+.git/hooks
\ No newline at end of file
diff --git a/a/pre-commit b/a/pre-commit
new file mode 120000
index 0000000..2222222
--- /dev/null
+++ b/a/pre-commit
@@ -0,0 +1 @@
+../../evil.sh
\ No newline at end of file
diff --git a/evil.sh b/evil.sh
new file mode 100755
index 0000000..3333333
--- /dev/null
+++ b/evil.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+echo pwned > PWNEDObserved:
apply_patch -> Ok(())
.git/hooks/pre-commit: symlink -> "../../evil.sh"
hooks::find_hook(repo, "pre-commit") -> Found
hooks::run_hook(...) ran it; marker file PWNED exists in the work treeVariant 2 (link outside the repository): entry a with an absolute directory outside the work tree as target, then a link entry a/planted. apply_patch returned Ok(()) and planted was created in the outside directory.
Variant 3 (NTFS alias, path check only): a single mode-120000 entry at git~1/hooks/pre-commit with two hunk lines. apply_patch returned Ok(()) and wrote it (on Linux as a harmless link in a folder literally named git~1). The same entry as a regular file (mode 100644) is refused by libgit2: invalid path: 'git~1/hooks/pre-push'. Not run on Windows.
Code execution as the user: someone who gets a user to apply a patch file (a patch from a mailing list, an issue attachment, a colleague) has their script run by Browsitory on the user's next commit, through the planted pre-commit hook. Independently of hooks, the patch can create symbolic links at chosen locations outside the repository (wherever no file of that name exists yet). On Windows, if variant 3 is confirmed, the patch content is written straight into .git\hooks.
Affected: users of the desktop app and of the VSCode extension who apply a patch they did not write. The entries are visible in the patch text, but applying a patch is not expected to be able to install a hook.
Source: NVD
Évaluation gratuite des vulnérabilités
Évaluez vos pratiques de sécurité cloud dans 9 domaines de sécurité afin d’évaluer votre niveau de risque et d’identifier les lacunes dans vos défenses.
Obtenez une démo personnalisée
"La meilleure expérience utilisateur que j’ai jamais vue, offre une visibilité totale sur les workloads cloud."
"Wiz fournit une interface unique pour voir ce qui se passe dans nos environnements cloud."
"Nous savons que si Wiz identifie quelque chose comme critique, c’est qu’il l’est réellement."