Merge pull request from GHSA-6rw3-3whw-jvjj

Report errors when finding executables and revise PATH search tests
This commit is contained in:
brian m. carlson 2022-04-19 10:42:35 -07:00 committed by GitHub
commit 46801d3b4e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 330 additions and 77 deletions

@ -118,8 +118,11 @@ func postCloneSubmodules(args []string) error {
// Use `git submodule foreach --recursive` to cascade into nested submodules
// Also good to call a new instance of git-lfs rather than do things
// inside this instance, since that way we get a clean env in that subrepo
cmd := subprocess.ExecCommand("git", "submodule", "foreach", "--recursive",
cmd, err := subprocess.ExecCommand("git", "submodule", "foreach", "--recursive",
"git lfs pull")
if err != nil {
return err
}
cmd.Stderr = os.Stderr
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout

@ -4,6 +4,7 @@ import (
"os"
"strings"
"github.com/git-lfs/git-lfs/v3/errors"
"github.com/git-lfs/git-lfs/v3/git"
"github.com/git-lfs/git-lfs/v3/lfs"
"github.com/git-lfs/git-lfs/v3/tools/humanize"
@ -50,7 +51,11 @@ func lsFilesCommand(cmd *cobra.Command, args []string) {
} else {
fullref, err := git.CurrentRef()
if err != nil {
ref = git.EmptyTree()
ref, err = git.EmptyTree()
if err != nil {
ExitWithError(errors.Wrap(
err, tr.Tr.Get("Could not read empty Git tree object")))
}
} else {
ref = fullref.Sha
}

@ -28,8 +28,12 @@ func statusCommand(cmd *cobra.Command, args []string) {
ref, _ := git.CurrentRef()
scanIndexAt := "HEAD"
var err error
if ref == nil {
scanIndexAt = git.EmptyTree()
scanIndexAt, err = git.EmptyTree()
if err != nil {
ExitWithError(err)
}
}
scanner, err := lfs.NewPointerScanner(cfg.GitEnv(), cfg.OSEnv())

@ -48,7 +48,10 @@ func cleanRootPath(pattern string) string {
if len(winBashPrefix) < 1 {
// cmd.Path is something like C:\Program Files\Git\usr\bin\pwd.exe
cmd := subprocess.ExecCommand("pwd")
cmd, err := subprocess.ExecCommand("pwd")
if err != nil {
return pattern
}
winBashPrefix = strings.Replace(filepath.Dir(filepath.Dir(filepath.Dir(cmd.Path))), `\`, "/", -1) + "/"
}

@ -147,7 +147,10 @@ func (i *gitIndexer) Add(path string) error {
if i.cmd == nil {
// Fire up the update-index command
cmd := git.UpdateIndexFromStdin()
cmd, err := git.UpdateIndexFromStdin()
if err != nil {
return err
}
cmd.Stdout = &i.output
cmd.Stderr = &i.output
stdin, err := cmd.StdinPipe()

@ -241,7 +241,11 @@ func (a *AskPassCredentialHelper) getFromProgram(valueType credValueType, u *url
// 'cmd' will run the GIT_ASKPASS (or core.askpass) command prompting
// for the desired valueType (`Username` or `Password`)
cmd := subprocess.ExecCommand(a.Program, a.args(fmt.Sprintf("%s for %q", valueString, u))...)
cmd, errVal := subprocess.ExecCommand(a.Program, a.args(fmt.Sprintf("%s for %q", valueString, u))...)
if errVal != nil {
tracerx.Printf("creds: failed to find GIT_ASKPASS command: %s", a.Program)
return "", errVal
}
cmd.Stderr = &err
cmd.Stdout = &value
@ -301,7 +305,10 @@ func (h *commandCredentialHelper) Approve(creds Creds) error {
func (h *commandCredentialHelper) exec(subcommand string, input Creds) (Creds, error) {
output := new(bytes.Buffer)
cmd := subprocess.ExecCommand("git", "credential", subcommand)
cmd, err := subprocess.ExecCommand("git", "credential", subcommand)
if err != nil {
return nil, errors.New(tr.Tr.Get("failed to find `git credential %s`: %v", subcommand, err))
}
cmd.Stdin = bufferCreds(input)
cmd.Stdout = output
/*
@ -316,7 +323,7 @@ func (h *commandCredentialHelper) exec(subcommand string, input Creds) (Creds, e
*/
cmd.Stderr = os.Stderr
err := cmd.Start()
err = cmd.Start()
if err == nil {
err = cmd.Wait()
}

@ -199,7 +199,10 @@ func (c *Configuration) Source() (*ConfigurationSource, error) {
func (c *Configuration) gitConfig(args ...string) (string, error) {
args = append([]string{"config", "--includes"}, args...)
cmd := subprocess.ExecCommand("git", args...)
cmd, err := subprocess.ExecCommand("git", args...)
if err != nil {
return "", err
}
if len(c.GitDir) > 0 {
cmd.Dir = c.GitDir
}

@ -148,17 +148,20 @@ func IsZeroObjectID(s string) bool {
return false
}
func EmptyTree() string {
func EmptyTree() (string, error) {
emptyTreeMutex.Lock()
defer emptyTreeMutex.Unlock()
if len(emptyTree) == 0 {
cmd := gitNoLFS("hash-object", "-t", "tree", "/dev/null")
cmd, err := gitNoLFS("hash-object", "-t", "tree", "/dev/null")
if err != nil {
return "", errors.New(tr.Tr.Get("failed to find `git hash-object`: %v", err))
}
cmd.Stdin = nil
out, _ := cmd.Output()
emptyTree = strings.TrimSpace(string(out))
}
return emptyTree
return emptyTree, nil
}
// Some top level information about a commit (only first line of message)
@ -198,7 +201,7 @@ func gitConfigNoLFS(args ...string) []string {
}
// Invoke Git with disabled LFS filters
func gitNoLFS(args ...string) *subprocess.Cmd {
func gitNoLFS(args ...string) (*subprocess.Cmd, error) {
return subprocess.ExecCommand("git", gitConfigNoLFS(args...)...)
}
@ -211,7 +214,7 @@ func gitNoLFSBuffered(args ...string) (*subprocess.BufferedCmd, error) {
}
// Invoke Git with enabled LFS filters
func git(args ...string) *subprocess.Cmd {
func git(args ...string) (*subprocess.Cmd, error) {
return subprocess.ExecCommand("git", args...)
}
@ -253,7 +256,10 @@ func DiffIndex(ref string, cached bool, refresh bool) (*bufio.Scanner, error) {
}
func HashObject(r io.Reader) (string, error) {
cmd := gitNoLFS("hash-object", "--stdin")
cmd, err := gitNoLFS("hash-object", "--stdin")
if err != nil {
return "", errors.New(tr.Tr.Get("failed to find `git hash-object`: %v", err))
}
cmd.Stdin = r
out, err := cmd.Output()
if err != nil {
@ -381,7 +387,10 @@ func (c *Configuration) RemoteBranchForLocalBranch(localBranch string) string {
}
func RemoteList() ([]string, error) {
cmd := gitNoLFS("remote")
cmd, err := gitNoLFS("remote")
if err != nil {
return nil, errors.New(tr.Tr.Get("failed to find `git remote`: %v", err))
}
outp, err := cmd.StdoutPipe()
if err != nil {
@ -401,7 +410,10 @@ func RemoteList() ([]string, error) {
}
func RemoteURLs(push bool) (map[string][]string, error) {
cmd := gitNoLFS("remote", "-v")
cmd, err := gitNoLFS("remote", "-v")
if err != nil {
return nil, errors.New(tr.Tr.Get("failed to find `git remote -v`: %v", err))
}
outp, err := cmd.StdoutPipe()
if err != nil {
@ -451,7 +463,10 @@ func MapRemoteURL(url string, push bool) (string, bool) {
// Refs returns all of the local and remote branches and tags for the current
// repository. Other refs (HEAD, refs/stash, git notes) are ignored.
func LocalRefs() ([]*Ref, error) {
cmd := gitNoLFS("show-ref")
cmd, err := gitNoLFS("show-ref")
if err != nil {
return nil, errors.New(tr.Tr.Get("failed to find `git show-ref`: %v", err))
}
outp, err := cmd.StdoutPipe()
if err != nil {
@ -500,7 +515,10 @@ func UpdateRefIn(wd string, ref *Ref, to []byte, reason string) error {
args = append(args, "-m", reason)
}
cmd := gitNoLFS(args...)
cmd, err := gitNoLFS(args...)
if err != nil {
return errors.New(tr.Tr.Get("failed to find `git update-ref`: %v", err))
}
cmd.Dir = wd
return cmd.Run()
@ -578,7 +596,7 @@ func RewriteLocalPathAsURL(path string) string {
return fmt.Sprintf("file://%s%s", slash, filepath.ToSlash(path))
}
func UpdateIndexFromStdin() *subprocess.Cmd {
func UpdateIndexFromStdin() (*subprocess.Cmd, error) {
return git("update-index", "-q", "--refresh", "--stdin")
}
@ -588,10 +606,13 @@ func UpdateIndexFromStdin() *subprocess.Cmd {
// includeRemoteBranches: true to include refs on remote branches
// onlyRemote: set to non-blank to only include remote branches on a single remote
func RecentBranches(since time.Time, includeRemoteBranches bool, onlyRemote string) ([]*Ref, error) {
cmd := gitNoLFS("for-each-ref",
cmd, err := gitNoLFS("for-each-ref",
`--sort=-committerdate`,
`--format=%(refname) %(objectname) %(committerdate:iso)`,
"refs")
if err != nil {
return nil, errors.New(tr.Tr.Get("failed to find `git for-each-ref`: %v", err))
}
outp, err := cmd.StdoutPipe()
if err != nil {
return nil, errors.New(tr.Tr.Get("failed to call `git for-each-ref`: %v", err))
@ -687,8 +708,11 @@ func FormatGitDate(tm time.Time) string {
// Get summary information about a commit
func GetCommitSummary(commit string) (*CommitSummary, error) {
cmd := gitNoLFS("show", "-s",
cmd, err := gitNoLFS("show", "-s",
`--format=%H|%h|%P|%ai|%ci|%ae|%an|%ce|%cn|%s`, commit)
if err != nil {
return nil, errors.New(tr.Tr.Get("failed to find `git show`: %v", err))
}
out, err := cmd.CombinedOutput()
if err != nil {
@ -722,7 +746,10 @@ func GetCommitSummary(commit string) (*CommitSummary, error) {
}
func GitAndRootDirs() (string, string, error) {
cmd := gitNoLFS("rev-parse", "--git-dir", "--show-toplevel")
cmd, err := gitNoLFS("rev-parse", "--git-dir", "--show-toplevel")
if err != nil {
return "", "", errors.New(tr.Tr.Get("failed to find `git rev-parse --git-dir --show-toplevel`: %v", err))
}
buf := &bytes.Buffer{}
cmd.Stderr = buf
@ -760,7 +787,10 @@ func GitAndRootDirs() (string, string, error) {
}
func RootDir() (string, error) {
cmd := gitNoLFS("rev-parse", "--show-toplevel")
cmd, err := gitNoLFS("rev-parse", "--show-toplevel")
if err != nil {
return "", errors.New(tr.Tr.Get("failed to find `git rev-parse --show-toplevel`: %v", err))
}
out, err := cmd.Output()
if err != nil {
return "", errors.New(tr.Tr.Get("failed to call `git rev-parse --show-toplevel`: %v %v", err, string(out)))
@ -775,7 +805,12 @@ func RootDir() (string, error) {
}
func GitDir() (string, error) {
cmd := gitNoLFS("rev-parse", "--git-dir")
cmd, err := gitNoLFS("rev-parse", "--git-dir")
if err != nil {
// The %w format specifier is unique to fmt.Errorf(), so we
// do not pass it to tr.Tr.Get().
return "", fmt.Errorf("%s: %w", tr.Tr.Get("failed to find `git rev-parse --git-dir`"), err)
}
buf := &bytes.Buffer{}
cmd.Stderr = buf
out, err := cmd.Output()
@ -797,7 +832,10 @@ func GitCommonDir() (string, error) {
return GitDir()
}
cmd := gitNoLFS("rev-parse", "--git-common-dir")
cmd, err := gitNoLFS("rev-parse", "--git-common-dir")
if err != nil {
return "", errors.New(tr.Tr.Get("failed to find `git rev-parse --git-common-dir`: %v", err))
}
out, err := cmd.Output()
buf := &bytes.Buffer{}
cmd.Stderr = buf
@ -1055,14 +1093,17 @@ func CloneWithoutFilters(flags CloneFlags, args []string) error {
// Now args
cmdargs = append(cmdargs, args...)
cmd := gitNoLFS(cmdargs...)
cmd, err := gitNoLFS(cmdargs...)
if err != nil {
return errors.New(tr.Tr.Get("failed to find `git clone`: %v", err))
}
// Assign all streams direct
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = os.Stdin
err := cmd.Start()
err = cmd.Start()
if err != nil {
return errors.New(tr.Tr.Get("failed to start `git clone`: %v", err))
}
@ -1102,7 +1143,10 @@ func Checkout(treeish string, paths []string, force bool) error {
// currently cached locally. No remote request is made to verify them.
func CachedRemoteRefs(remoteName string) ([]*Ref, error) {
var ret []*Ref
cmd := gitNoLFS("show-ref")
cmd, err := gitNoLFS("show-ref")
if err != nil {
return nil, errors.New(tr.Tr.Get("failed to find `git show-ref`: %v", err))
}
outp, err := cmd.StdoutPipe()
if err != nil {
@ -1157,7 +1201,10 @@ func Fetch(remotes ...string) error {
// accessing the remote via git ls-remote.
func RemoteRefs(remoteName string) ([]*Ref, error) {
var ret []*Ref
cmd := gitNoLFS("ls-remote", "--heads", "--tags", "-q", remoteName)
cmd, err := gitNoLFS("ls-remote", "--heads", "--tags", "-q", remoteName)
if err != nil {
return nil, errors.New(tr.Tr.Get("failed to find `git ls-remote`: %v", err))
}
outp, err := cmd.StdoutPipe()
if err != nil {
@ -1216,8 +1263,11 @@ func AllRefs() ([]*Ref, error) {
// the given working directory "wd", or an error if those references could not
// be loaded.
func AllRefsIn(wd string) ([]*Ref, error) {
cmd := gitNoLFS(
cmd, err := gitNoLFS(
"for-each-ref", "--format=%(objectname)%00%(refname)")
if err != nil {
return nil, lfserrors.Wrap(err, tr.Tr.Get("failed to find `git for-each-ref`: %v", err))
}
cmd.Dir = wd
outp, err := cmd.StdoutPipe()
@ -1262,12 +1312,15 @@ func GetTrackedFiles(pattern string) ([]string, error) {
rootWildcard := len(safePattern) < len(pattern) && strings.ContainsRune(safePattern, '*')
var ret []string
cmd := gitNoLFS(
cmd, err := gitNoLFS(
"-c", "core.quotepath=false", // handle special chars in filenames
"ls-files",
"--cached", // include things which are staged but not committed right now
"--", // no ambiguous patterns
safePattern)
if err != nil {
return nil, errors.New(tr.Tr.Get("failed to find `git ls-files`: %v", err))
}
outp, err := cmd.StdoutPipe()
if err != nil {
@ -1321,7 +1374,10 @@ func GetFilesChanged(from, to string) ([]string, error) {
}
args = append(args, "--") // no ambiguous patterns
cmd := gitNoLFS(args...)
cmd, err := gitNoLFS(args...)
if err != nil {
return nil, errors.New(tr.Tr.Get("failed to find `git diff-tree`: %v", err))
}
outp, err := cmd.StdoutPipe()
if err != nil {
return nil, errors.New(tr.Tr.Get("failed to call `git diff-tree`: %v", err))
@ -1352,7 +1408,10 @@ func IsFileModified(filepath string) (bool, error) {
"--", // separator in case filename ambiguous
filepath,
}
cmd := git(args...)
cmd, err := git(args...)
if err != nil {
return false, lfserrors.Wrap(err, tr.Tr.Get("failed to find `git status`"))
}
outp, err := cmd.StdoutPipe()
if err != nil {
return false, lfserrors.Wrap(err, tr.Tr.Get("Failed to call `git status`"))

@ -35,7 +35,10 @@ func NewLsFiles(workingDir string, standardExclude bool, untracked bool) (*LsFil
if untracked {
args = append(args, "--others")
}
cmd := gitNoLFS(args...)
cmd, err := gitNoLFS(args...)
if err != nil {
return nil, err
}
cmd.Dir = workingDir
tracerx.Printf("NewLsFiles: running in %s git %s",

@ -169,7 +169,10 @@ func NewRevListScanner(include, excluded []string, opt *ScanRefsOptions) (*RevLi
return nil, err
}
cmd := gitNoLFS(args...).Cmd
cmd, err := gitNoLFS(args...)
if err != nil {
return nil, err
}
if len(opt.WorkingDir) > 0 {
cmd.Dir = opt.WorkingDir
}

@ -76,7 +76,11 @@ func pipeExtensions(cfg *config.Configuration, request *pipeRequest) (response p
arg := strings.Replace(value, "%f", request.fileName, -1)
args = append(args, arg)
}
cmd := subprocess.ExecCommand(name, args...)
var cmd *subprocess.Cmd
cmd, err = subprocess.ExecCommand(name, args...)
if err != nil {
return
}
ec := &extCommand{cmd: cmd, result: &pipeExtResult{name: e.Name}}
extcmds = append(extcmds, ec)
}

@ -19,7 +19,11 @@ func appendRootCAsForHostFromPlatform(pool *x509.CertPool, host string) *x509.Ce
// either, for consistency.
// find system.keychain for user-added certs (don't assume location)
cmd := subprocess.ExecCommand("/usr/bin/security", "list-keychains")
cmd, err := subprocess.ExecCommand("/usr/bin/security", "list-keychains")
if err != nil {
tracerx.Printf("Error getting command to list keychains: %v", err)
return nil
}
kcout, err := cmd.Output()
if err != nil {
tracerx.Printf("Error listing keychains: %v", err)
@ -54,7 +58,11 @@ func appendRootCAsForHostFromPlatform(pool *x509.CertPool, host string) *x509.Ce
}
func appendRootCAsFromKeychain(pool *x509.CertPool, name, keychain string) *x509.CertPool {
cmd := subprocess.ExecCommand("/usr/bin/security", "find-certificate", "-a", "-p", "-c", name, keychain)
cmd, err := subprocess.ExecCommand("/usr/bin/security", "find-certificate", "-a", "-p", "-c", name, keychain)
if err != nil {
tracerx.Printf("Error getting command to read keychain %q: %v", keychain, err)
return pool
}
data, err := cmd.Output()
if err != nil {
tracerx.Printf("Error reading keychain %q: %v", keychain, err)

@ -80,7 +80,10 @@ func (c *sshAuthClient) Resolve(e Endpoint, method string) (sshAuthResponse, err
}
exe, args := ssh.GetLFSExeAndArgs(c.os, c.git, &e.SSHMetadata, "git-lfs-authenticate", endpointOperation(e, method), false)
cmd := subprocess.ExecCommand(exe, args...)
cmd, err := subprocess.ExecCommand(exe, args...)
if err != nil {
return res, err
}
// Save stdout and stderr in separate buffers
var outbuf, errbuf bytes.Buffer
@ -90,7 +93,7 @@ func (c *sshAuthClient) Resolve(e Endpoint, method string) (sshAuthResponse, err
now := time.Now()
// Execute command
err := cmd.Start()
err = cmd.Start()
if err == nil {
err = cmd.Wait()
}

@ -119,7 +119,10 @@ func gitDirAtPath(path string) (string, error) {
return "", err
}
cmd := subprocess.ExecCommand("git", "rev-parse", "--git-dir")
cmd, err := subprocess.ExecCommand("git", "rev-parse", "--git-dir")
if err != nil {
return "", errors.Wrap(err, tr.Tr.Get("failed to find `git rev-parse --git-dir`"))
}
cmd.Cmd.Env = env
out, err := cmd.Output()
if err != nil {

@ -34,7 +34,10 @@ func NewSSHTransfer(osEnv config.Environment, gitEnv config.Environment, meta *S
func startConnection(id int, osEnv config.Environment, gitEnv config.Environment, meta *SSHMetadata, operation string) (*PktlineConnection, error) {
exe, args := GetLFSExeAndArgs(osEnv, gitEnv, meta, "git-lfs-transfer", operation, true)
cmd := subprocess.ExecCommand(exe, args...)
cmd, err := subprocess.ExecCommand(exe, args...)
if err != nil {
return nil, err
}
r, err := cmd.StdoutPipe()
if err != nil {
return nil, err

@ -20,7 +20,10 @@ import (
// stdout & stderr pipes, wrapped in a BufferedCmd. The stdout buffer will be
// of stdoutBufSize bytes.
func BufferedExec(name string, args ...string) (*BufferedCmd, error) {
cmd := ExecCommand(name, args...)
cmd, err := ExecCommand(name, args...)
if err != nil {
return nil, err
}
stdout, err := cmd.StdoutPipe()
if err != nil {
return nil, err
@ -49,7 +52,11 @@ func BufferedExec(name string, args ...string) (*BufferedCmd, error) {
// SimpleExec is a small wrapper around os/exec.Command.
func SimpleExec(name string, args ...string) (string, error) {
return Output(ExecCommand(name, args...))
cmd, err := ExecCommand(name, args...)
if err != nil {
return "", err
}
return Output(cmd)
}
func Output(cmd *Cmd) (string, error) {

@ -8,9 +8,13 @@ import (
)
// ExecCommand is a small platform specific wrapper around os/exec.Command
func ExecCommand(name string, arg ...string) *Cmd {
func ExecCommand(name string, arg ...string) (*Cmd, error) {
cmd := exec.Command(name, arg...)
cmd.Path, _ = LookPath(name)
var err error
cmd.Path, err = LookPath(name)
if err != nil {
return nil, err
}
cmd.Env = fetchEnvironment()
return newCmd(cmd)
return newCmd(cmd), nil
}

@ -9,10 +9,14 @@ import (
)
// ExecCommand is a small platform specific wrapper around os/exec.Command
func ExecCommand(name string, arg ...string) *Cmd {
func ExecCommand(name string, arg ...string) (*Cmd, error) {
cmd := exec.Command(name, arg...)
cmd.Path, _ = LookPath(name)
var err error
cmd.Path, err = LookPath(name)
if err != nil {
return nil, err
}
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
cmd.Env = fetchEnvironment()
return newCmd(cmd)
return newCmd(cmd), nil
}

@ -13,6 +13,7 @@ TEST_CMDS += ../bin/git-credential-lfstest$X
TEST_CMDS += ../bin/lfs-askpass$X
TEST_CMDS += ../bin/lfs-ssh-echo$X
TEST_CMDS += ../bin/lfs-ssh-proxy-test$X
TEST_CMDS += ../bin/lfstest-badpathcheck$X
TEST_CMDS += ../bin/lfstest-count-tests$X
TEST_CMDS += ../bin/lfstest-customadapter$X
TEST_CMDS += ../bin/lfstest-gitserver$X

@ -0,0 +1,19 @@
//go:build testtools
// +build testtools
package main
import (
"fmt"
"os"
)
func main() {
fmt.Println("exploit")
fmt.Fprintln(os.Stderr, "exploit")
f, err := os.Create("exploit")
if err != nil {
f.Close()
}
}

@ -45,8 +45,7 @@ begin_test "askpass: push with bad askpass"
git config "credential.helper" ""
GIT_TERMINAL_PROMPT=0 GIT_ASKPASS="lfs-askpass-2" SSH_ASKPASS="dont-call-me" GIT_TRACE=1 git push origin main 2>&1 | tee push.log
grep "filling with GIT_ASKPASS" push.log # attempt askpass
grep 'credential fill error: exec: "lfs-askpass-2"' push.log # askpass fails
grep "failed to find GIT_ASKPASS command" push.log # attempt askpass
grep "creds: git credential fill" push.log # attempt git credential
)
end_test

@ -9,16 +9,18 @@ begin_test "does not look in current directory for git"
reponame="$(basename "$0" ".sh")"
git init "$reponame"
cd "$reponame"
export PATH="$(echo "$PATH" | sed -e "s/:.:/:/g" -e "s/::/:/g")"
printf "#!/bin/sh\necho exploit >&2\n" > git
chmod +x git || true
printf "echo exploit 1>&2\n" > git.bat
cp "$BINPATH/lfstest-badpathcheck$X" "git$X"
# This needs to succeed. If it fails, that could be because our malicious
# "git" is broken but got invoked anyway.
git lfs env > output.log 2>&1
! grep -q 'exploit' output.log
# This should always succeed, even if git-lfs is incorrectly searching for
# executables in the current directory first, because the "git-lfs env"
# command ignores all errors when it runs "git config". So we should always
# pass this step and then, if our malicious Git was executed, detect
# its output below. If this command does fail, something else is wrong.
PATH="$BINPATH" PATHEXT="$X" "git-lfs$X" env >output.log 2>&1
grep "exploit" output.log && false
[ ! -f exploit ]
)
end_test
@ -30,32 +32,122 @@ begin_test "does not look in current directory for git with credential helper"
setup_remote_repo "$reponame"
clone_repo "$reponame" credentials-1
export PATH="$(echo "$PATH" | sed -e "s/:.:/:/g" -e "s/::/:/g")"
printf "#!/bin/sh\necho exploit >&2\ntouch exploit\n" > git
chmod +x git || true
printf "echo exploit 1>&2\r\necho >exploit" > git.bat
git lfs track "*.dat"
printf abc > z.dat
git add z.dat
git add .gitattributes
git add git git.bat
git commit -m "Add files"
GITPATH="$(dirname "$(command -v git)")"
# We add our malicious Git to the index and then remove it from the
# work tree so it is not found early, before we perform our key test.
# Specifically, our "git push" below will run git-lfs, which then runs
# "git credential", so if we are looking for Git in the current directory
# first when running a credential helper, we will fail at that point
# because our malicious Git will be found first.
#
# We prefer to check for this behavior during our "git-lfs pull" further
# below when we are populating LFS objects into a clone of this repo
# (which contains the malicious Git), so for now we remove the malicious
# Git as soon as possible.
cp "$BINPATH/lfstest-badpathcheck$X" "git$X"
PATH="$BINPATH:$GITPATH" "$GITPATH/git$X" add "git$X"
rm "git$X"
git commit -m "Add files"
git push origin HEAD
cd ..
unset GIT_ASKPASS SSH_ASKPASS
# This needs to succeed. If it fails, that could be because our malicious
# "git" is broken but got invoked anyway.
GIT_LFS_SKIP_SMUDGE=1 clone_repo "$reponame" credentials-2
# When we call "git clone" below, it will run git-lfs as a smudge filter
# during the post-clone checkout phase, and specifically will run git-lfs
# in the newly cloned repository directory which contains a copy of our
# malicious Git. So, if we are looking for Git in the current directory
# first in most cases (and not just when running a credential helper),
# then when git-lfs runs "git config" we will fail at that point because
# our malicious Git will be found first. This occurs even if we specify
# GIT_LFS_SKIP_SMUDGE=1 because git-lfs will still run "git config".
#
# We could ignore errors from clone_repo() and then search for the output
# of our malicious Git in the t-path-credentials-2 directory; however,
# this may be somewhat fragile as clone_repo() performs other steps such
# as changing the current working directory to the new repo clone and
# attempting to run "git config" there.
#
# Instead, since our key check of "git-lfs pull" below will also detect
# the general failure case where we are looking for Git in the current
# directory first when running most commands, we temporarily uninstall
# Git LFS so no smudge filter will execute when "git clone" checks out the
# repository.
#
# We also remove any "exploit" file potentially created by our malicious
# Git in case it was run anywhere in clone_repo(), which may happen if
# PATH contains the "." directory already. Note that we reset PATH
# to contain only the necessary directories in our key "git-lfs pull"
# check below.
git lfs uninstall
clone_repo "$reponame" t-path-credentials-2
rm -f exploit
pushd ..
git lfs install
popd
git lfs pull | tee output.log
# As noted, if we are looking for Git in the current directory first
# only when running a credential helper, then when this runs
# "git credential", it will find our malicious Git in the current directory
# and execute it.
#
# If we are looking for Git in the current directory first when running
# most commands (and not just when running a credential helper), then this
# will also find our malicious Git. However, in this case it will find it
# earlier when we try to run "git config" rather than later when we try
# to run "git credential".
#
# We use a pipeline with "tee" here so as to avoid an early failure in the
# case that our "git-lfs pull" command executes our malicious Git.
# Unlike "git-lfs env" in the other tests, "git-lfs pull" will halt when
# it does not receive the normal output from Git. This in turn halts
# our test due to our use of the "set -e" option, unless we terminate a
# pipeline with successful command like "tee".
PATH="$BINPATH:$GITPATH" PATHEXT="$X" "git-lfs$X" pull 2>&1 | tee output.log
! grep -q 'exploit' output.log
[ ! -f ../exploit ]
grep "exploit" output.log && false
[ ! -f exploit ]
)
end_test
begin_test "does not look in current directory for wrong binary using PATHEXT"
(
set -e
# Windows is the only platform where Go searches for executable files
# by appending file extensions from PATHEXT.
[ "$IS_WINDOWS" -eq 0 ] && exit 0
reponame="$(basename "$0" ".sh")-notfound"
git init "$reponame"
cd "$reponame"
# Go on Windows always looks in the current directory first when creating
# a command handler, so we need a dummy git.exe for it to find there since
# we will restrict PATH to exclude the real Git when we run "git-lfs env"
# below. If our git-lfs incorrectly proceeds to run the command handler
# despite not finding Git in PATH either, Go may then search for a file
# named "." with any path extension from PATHEXT and execute that file
# instead, so we create a malicious file named "..exe" to check this case.
touch "git$X"
cp "$BINPATH/lfstest-badpathcheck$X" ".$X"
# This should always succeed, even if git-lfs is incorrectly searching for
# executables in the current directory first, because the "git-lfs env"
# command ignores all errors when it runs "git config". So we should always
# pass this step and then, if our malicious program was executed, detect
# its output below. If this command does fail, something else is wrong.
PATH="$BINPATH" PATHEXT="$X" "git-lfs$X" env >output.log 2>&1
grep "exploit" output.log && false
[ ! -f exploit ]
)
end_test

@ -6,12 +6,14 @@ set -e
UNAME=$(uname -s)
IS_WINDOWS=0
IS_MAC=0
X=""
SHASUM="shasum -a 256"
PATH_SEPARATOR="/"
if [[ $UNAME == MINGW* || $UNAME == MSYS* || $UNAME == CYGWIN* ]]
then
IS_WINDOWS=1
X=".exe"
# Windows might be MSYS2 which does not have the shasum Perl wrapper
# script by default, so use sha256sum directly. MacOS on the other hand

@ -38,7 +38,10 @@ func isCygwin() bool {
return cygwinState.Enabled()
}
cmd := subprocess.ExecCommand("uname")
cmd, err := subprocess.ExecCommand("uname")
if err != nil {
return false
}
out, err := cmd.Output()
if err != nil {
return false

@ -28,7 +28,12 @@ func Getwd() (dir string, err error) {
}
func translateCygwinPath(path string) (string, error) {
cmd := subprocess.ExecCommand("cygpath", "-w", path)
cmd, err := subprocess.ExecCommand("cygpath", "-w", path)
if err != nil {
// If cygpath doesn't exist, that's okay: just return the paths
// as we got it.
return path, nil
}
// cygpath uses ISO-8850-1 as the default encoding if the locale is not
// set, resulting in breakage, since we want a UTF-8 path.
env := make([]string, 0, len(cmd.Env)+1)

@ -125,7 +125,10 @@ func (a *customAdapter) WorkerStarting(workerNum int) (interface{}, error) {
// If concurrent = false we have already dialled back workers to 1
a.Trace("xfer: starting up custom transfer process %q for worker %d", a.name, workerNum)
cmdName, cmdArgs := subprocess.FormatForShell(subprocess.ShellQuoteSingle(a.path), a.args)
cmd := subprocess.ExecCommand(cmdName, cmdArgs...)
cmd, err := subprocess.ExecCommand(cmdName, cmdArgs...)
if err != nil {
return nil, errors.New(tr.Tr.Get("failed to find custom transfer command %q remote: %v", a.path, err))
}
outp, err := cmd.StdoutPipe()
if err != nil {
return nil, errors.New(tr.Tr.Get("failed to get stdout for custom transfer command %q remote: %v", a.path, err))