Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitaly.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStan Hu <stanhu@gmail.com>2020-11-14 03:07:57 +0300
committerStan Hu <stanhu@gmail.com>2020-11-16 12:16:44 +0300
commitc7aa70f1477160116841c406d91d53748640a741 (patch)
tree52f2ca219a1cf746acd3eb7adad3a27170fc1839
parent52d774905162027cb1156d10f233b4b5caeb9ae3 (diff)
Fix handling of symlinks in custom hooks directory
Previously if there were symlinks in the custom hook directory pointing to a directory or some invalid executable, Gitaly would attempt to execute that file. This would result in a "permission denied" error because you can't execute a directory, for example. We now resolve the symlinks and only execute them if they point to valid executables. Closes https://gitlab.com/gitlab-org/gitaly/-/issues/3282
-rw-r--r--changelogs/unreleased/sh-fix-symlinks-to-dirs-custom-hooks.yml5
-rw-r--r--internal/gitaly/hook/custom.go35
-rw-r--r--internal/gitaly/hook/custom_test.go70
3 files changed, 105 insertions, 5 deletions
diff --git a/changelogs/unreleased/sh-fix-symlinks-to-dirs-custom-hooks.yml b/changelogs/unreleased/sh-fix-symlinks-to-dirs-custom-hooks.yml
new file mode 100644
index 000000000..911bd4049
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-symlinks-to-dirs-custom-hooks.yml
@@ -0,0 +1,5 @@
+---
+title: Fix handling of symlinks in custom hooks directory
+merge_request: 2790
+author:
+type: fixed
diff --git a/internal/gitaly/hook/custom.go b/internal/gitaly/hook/custom.go
index 1e295159a..428a6fcba 100644
--- a/internal/gitaly/hook/custom.go
+++ b/internal/gitaly/hook/custom.go
@@ -14,6 +14,7 @@ import (
"gitlab.com/gitlab-org/gitaly/internal/command"
"gitlab.com/gitlab-org/gitaly/internal/helper"
"gitlab.com/gitlab-org/gitaly/proto/go/gitalypb"
+ "golang.org/x/sys/unix"
)
// customHooksExecutor executes all custom hooks for a given repository and hook name
@@ -95,17 +96,41 @@ func matchFiles(dir string) ([]string, error) {
var hookFiles []string
for _, fi := range fis {
- if fi.IsDir() {
+ if fi.IsDir() || strings.HasSuffix(fi.Name(), "~") {
continue
}
- if fi.Mode()&0100 == 0 {
- continue
+
+ filename := filepath.Join(dir, fi.Name())
+
+ if fi.Mode()&os.ModeSymlink != 0 {
+ path, err := filepath.EvalSymlinks(filename)
+ if err != nil {
+ continue
+ }
+
+ fi, err = os.Lstat(path)
+ if err != nil {
+ continue
+ }
}
- if strings.HasSuffix(fi.Name(), "~") {
+
+ if !validHook(fi, filename) {
continue
}
- hookFiles = append(hookFiles, filepath.Join(dir, fi.Name()))
+
+ hookFiles = append(hookFiles, filename)
}
return hookFiles, nil
}
+
+func validHook(fi os.FileInfo, filename string) bool {
+ if fi.IsDir() {
+ return false
+ }
+ if unix.Access(filename, unix.X_OK) != nil {
+ return false
+ }
+
+ return true
+}
diff --git a/internal/gitaly/hook/custom_test.go b/internal/gitaly/hook/custom_test.go
index c20675d78..c9bac564d 100644
--- a/internal/gitaly/hook/custom_test.go
+++ b/internal/gitaly/hook/custom_test.go
@@ -223,6 +223,76 @@ func TestCustomHooksMultipleHooks(t *testing.T) {
}
}
+func TestCustomHooksWithSymlinks(t *testing.T) {
+ testRepo, _, cleanup := testhelper.NewTestRepo(t)
+ defer cleanup()
+
+ globalCustomHooksDir, cleanup := testhelper.TempDir(t)
+ defer cleanup()
+
+ ctx, cancel := testhelper.Context()
+ defer cancel()
+
+ globalHooksPath := filepath.Join(globalCustomHooksDir, "update.d")
+
+ // Test directory structure:
+ //
+ // first_dir/update
+ // first_dir/update~
+ // second_dir -> first_dir
+ // update -> second_dir/update GOOD
+ // update_tilde -> first_dir/update~ GOOD
+ // update~ -> first_dir/update BAD
+ // something -> not-executable BAD
+ // bad -> /path/to/nowhere BAD
+ firstDir := filepath.Join(globalHooksPath, "first_dir")
+ secondDir := filepath.Join(globalHooksPath, "second_dir")
+ require.NoError(t, os.MkdirAll(firstDir, 0755))
+ require.NoError(t, os.Symlink(firstDir, secondDir))
+ filename := filepath.Join(firstDir, "update")
+
+ updateTildePath := filepath.Join(globalHooksPath, "update_tilde")
+ require.NoError(t, os.Symlink(filename, updateTildePath))
+
+ updateHookPath := filepath.Join(globalHooksPath, "update")
+ require.NoError(t, os.Symlink(filename, updateHookPath))
+
+ badUpdatePath := filepath.Join(globalHooksPath, "update~")
+ badUpdateHook := filepath.Join(firstDir, "update~")
+ require.NoError(t, os.Symlink(badUpdateHook, badUpdatePath))
+
+ notExecPath := filepath.Join(globalHooksPath, "not-executable")
+ badExecHook := filepath.Join(firstDir, "something")
+ os.Create(notExecPath)
+ require.NoError(t, os.Symlink(notExecPath, badExecHook))
+
+ badPath := filepath.Join(globalHooksPath, "bad")
+ require.NoError(t, os.Symlink("/path/to/nowhere", badPath))
+
+ writeCustomHook(t, "update", firstDir, successScript)
+ writeCustomHook(t, "update~", firstDir, successScript)
+
+ expectedExecutedScripts := []string{updateHookPath, updateTildePath}
+
+ mgr := GitLabHookManager{
+ hooksConfig: config.Hooks{
+ CustomHooksDir: globalCustomHooksDir,
+ },
+ }
+ hooksExecutor, err := mgr.newCustomHooksExecutor(testRepo, "update")
+ require.NoError(t, err)
+
+ var stdout, stderr bytes.Buffer
+ require.NoError(t, hooksExecutor(ctx, nil, nil, &bytes.Buffer{}, &stdout, &stderr))
+ require.Empty(t, stderr.Bytes())
+
+ outputScanner := bufio.NewScanner(&stdout)
+ for _, expectedScript := range expectedExecutedScripts {
+ require.True(t, outputScanner.Scan())
+ require.Equal(t, expectedScript, outputScanner.Text())
+ }
+}
+
func TestMultilineStdin(t *testing.T) {
testRepo, testRepoPath, cleanup := testhelper.NewTestRepo(t)
defer cleanup()