diff --git a/pkg/plugins/plugins.go b/pkg/plugins/plugins.go index a2154f786a..7b4652dce6 100644 --- a/pkg/plugins/plugins.go +++ b/pkg/plugins/plugins.go @@ -20,28 +20,40 @@ func getPluginDir() (string, error) { dir := os.Getenv(pluginDirEnv) // if TKN_PLUGINS_DIR is set, follow it if dir != "" { + if !filepath.IsAbs(dir) { + return "", fmt.Errorf("plugin dir %q is not an absolute path", dir) + } return dir, nil } // Respect XDG_CONFIG_HOME if set if xdgHome := os.Getenv("XDG_CONFIG_HOME"); xdgHome != "" { + if !filepath.IsAbs(xdgHome) { + return "", fmt.Errorf("XDG_CONFIG_HOME %q is not an absolute path", xdgHome) + } return filepath.Join(xdgHome, "tkn", "plugins"), nil } // Fallback to default pluginDir (~/.config/tkn/plugins) - return homedir.Expand(pluginDir) + dir, err := homedir.Expand(pluginDir) + if err != nil { + return "", err + } + if !filepath.IsAbs(dir) { + return "", fmt.Errorf("plugin dir %q is not an absolute path", dir) + } + return dir, nil } // Find a binary in plugin homedir directory or user paths. func FindPlugin(pluginame string) (string, error) { cmd := tknPrefix + pluginame - dir, _ := getPluginDir() - path := filepath.Join(dir, cmd) - _, err := os.Stat(path) - if err == nil { - // Found in dir - return path, nil + if dir, err := getPluginDir(); err == nil { + path := filepath.Join(dir, cmd) + if _, err := os.Stat(path); err == nil { + return path, nil + } } - path, err = exec.LookPath(cmd) + path, err := exec.LookPath(cmd) if err == nil { return path, nil } diff --git a/pkg/plugins/plugins_test.go b/pkg/plugins/plugins_test.go index 404b5a5de0..d89896c417 100644 --- a/pkg/plugins/plugins_test.go +++ b/pkg/plugins/plugins_test.go @@ -49,6 +49,42 @@ func TestGetAllTknPluginFromPathPlugindir(t *testing.T) { assert.Equal(t, paths[0], "fromplugindir") } +func TestGetPluginDirRelativeTKNPluginsDir(t *testing.T) { + t.Setenv(pluginDirEnv, "relative/path") + _, err := getPluginDir() + assert.ErrorContains(t, err, "not an absolute path") +} + +func TestGetPluginDirRelativeXDGConfigHome(t *testing.T) { + t.Setenv(pluginDirEnv, "") + t.Setenv("XDG_CONFIG_HOME", "relative/xdg") + _, err := getPluginDir() + assert.ErrorContains(t, err, "not an absolute path") +} + +func TestFindPluginDoesNotFallBackToCwd(t *testing.T) { + nd := fs.NewDir(t, "TestFindPluginCwd") + defer nd.Remove() + err := os.WriteFile(nd.Join("tkn-evil"), []byte("evil"), 0o700) + assert.NilError(t, err) + + // Change into the directory that contains the malicious binary. + orig, err := os.Getwd() + assert.NilError(t, err) + defer os.Chdir(orig) //nolint:errcheck + assert.NilError(t, os.Chdir(nd.Path())) + + // Use "." as TKN_PLUGINS_DIR: the old code would resolve filepath.Join(".", "tkn-evil") + // against cwd and find the binary; the fixed code rejects "." as non-absolute. + // Keep nd off PATH so LookPath cannot find the binary either. + t.Setenv(pluginDirEnv, ".") + t.Setenv("PATH", "") + + // The binary is only reachable via cwd — FindPlugin must not find it. + _, err = FindPlugin("evil") + assert.ErrorContains(t, err, "cannot find plugin") +} + // as well tested differently in root_test.go func TestGetAllTknPluginFromPaths(t *testing.T) { nd := fs.NewDir(t, "TestGetAllTknPluginFromPaths1")