Skip to content

Commit 4c38315

Browse files
authored
security: harden RevParse and pass --end-of-options across git commands (#142)
1 parent 5429319 commit 4c38315

9 files changed

Lines changed: 41 additions & 12 deletions

File tree

‎blob.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,5 @@ func (b *Blob) Bytes() ([]byte, error) {
3232
// Pipeline reads the content of the blob and pipes stdout and stderr to
3333
// supplied io.Writer.
3434
func (b *Blob) Pipeline(stdout, stderr io.Writer) error {
35-
return NewCommand("show", b.id.String()).RunInDirPipeline(stdout, stderr, b.parent.repo.path)
35+
return NewCommand("show", "--end-of-options", b.id.String()).RunInDirPipeline(stdout, stderr, b.parent.repo.path)
3636
}

‎commit_archive.go‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ func (c *Commit) CreateArchive(format ArchiveFormat, dst string) error {
2525
"--prefix="+prefix,
2626
"--format="+string(format),
2727
"-o", dst,
28+
"--end-of-options",
2829
c.ID.String(),
2930
).RunInDir(c.repo.path)
3031
return err

‎repo.go‎

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ func (r *Repository) Fetch(opts ...FetchOptions) error {
189189
if opt.Prune {
190190
cmd.AddArgs("--prune")
191191
}
192+
cmd.AddArgs("--end-of-options")
192193

193194
_, err := cmd.RunInDirWithTimeout(opt.Timeout, r.path)
194195
return err
@@ -229,6 +230,7 @@ func (r *Repository) Pull(opts ...PullOptions) error {
229230
if opt.All {
230231
cmd.AddArgs("--all")
231232
}
233+
cmd.AddArgs("--end-of-options")
232234
if !opt.All && opt.Remote != "" {
233235
cmd.AddArgs(opt.Remote)
234236
if opt.Branch != "" {
@@ -300,11 +302,9 @@ func Checkout(repoPath, branch string, opts ...CheckoutOptions) error {
300302

301303
cmd := NewCommand("checkout").AddOptions(opt.CommandOptions)
302304
if opt.BaseBranch != "" {
303-
cmd.AddArgs("-b")
304-
}
305-
cmd.AddArgs(branch)
306-
if opt.BaseBranch != "" {
307-
cmd.AddArgs(opt.BaseBranch)
305+
cmd.AddArgs("-b", branch, "--end-of-options", opt.BaseBranch)
306+
} else {
307+
cmd.AddArgs("--end-of-options", branch)
308308
}
309309

310310
_, err := cmd.RunInDirWithTimeout(opt.Timeout, repoPath)
@@ -348,7 +348,7 @@ func Reset(repoPath, rev string, opts ...ResetOptions) error {
348348
cmd.AddArgs("--hard")
349349
}
350350

351-
_, err := cmd.AddOptions(opt.CommandOptions).AddArgs(rev).RunInDir(repoPath)
351+
_, err := cmd.AddOptions(opt.CommandOptions).AddArgs("--end-of-options", rev).RunInDir(repoPath)
352352
return err
353353
}
354354

@@ -476,7 +476,8 @@ func CreateCommit(repoPath string, committer *Signature, message string, opts ..
476476
}
477477
cmd = cmd.AddArgs(fmt.Sprintf("--author='%s <%s>'", opt.Author.Name, opt.Author.Email)).
478478
AddArgs("-m", message).
479-
AddOptions(opt.CommandOptions)
479+
AddOptions(opt.CommandOptions).
480+
AddArgs("--end-of-options")
480481

481482
_, err := cmd.RunInDirWithTimeout(opt.Timeout, repoPath)
482483
// No stderr but exit status 1 means nothing to commit.
@@ -593,6 +594,15 @@ func (r *Repository) RevParse(rev string, opts ...RevParseOptions) (string, erro
593594
opt = opts[0]
594595
}
595596

597+
// A revision that starts with a dash is never a valid object and would
598+
// instead be interpreted by "git rev-parse" as a command-line option,
599+
// allowing argument injection. The "--end-of-options" separator does not
600+
// help here because "git rev-parse" does not honor it for revisions, so
601+
// reject such input outright.
602+
if strings.HasPrefix(rev, "-") {
603+
return "", ErrRevisionNotExist
604+
}
605+
596606
commitID, err := NewCommand("rev-parse").
597607
AddOptions(opt.CommandOptions).
598608
AddArgs(rev).
@@ -640,6 +650,7 @@ func CountObjects(repoPath string, opts ...CountObjectsOptions) (*CountObject, e
640650

641651
stdout, err := NewCommand("count-objects", "-v").
642652
AddOptions(opt.CommandOptions).
653+
AddArgs("--end-of-options").
643654
RunInDirWithTimeout(opt.Timeout, repoPath)
644655
if err != nil {
645656
return nil, err
@@ -706,7 +717,7 @@ func Fsck(repoPath string, opts ...FsckOptions) error {
706717
opt = opts[0]
707718
}
708719

709-
cmd := NewCommand("fsck").AddOptions(opt.CommandOptions)
720+
cmd := NewCommand("fsck").AddOptions(opt.CommandOptions).AddArgs("--end-of-options")
710721
_, err := cmd.RunInDirWithTimeout(opt.Timeout, repoPath)
711722
return err
712723
}

‎repo_commit.go‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -555,6 +555,7 @@ func (r *Repository) LatestCommitTime(opts ...LatestCommitTimeOptions) (time.Tim
555555
"--count=1",
556556
"--sort=-committerdate",
557557
"--format=%(committerdate:iso8601)",
558+
"--end-of-options",
558559
)
559560
if opt.Branch != "" {
560561
cmd.AddArgs(RefsHeads + opt.Branch)

‎repo_remote.go‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@ func Remotes(repoPath string, opts ...RemotesOptions) ([]string, error) {
216216

217217
stdout, err := NewCommand("remote").
218218
AddOptions(opt.CommandOptions).
219+
AddArgs("--end-of-options").
219220
RunInDirWithTimeout(opt.Timeout, repoPath)
220221
if err != nil {
221222
return nil, err

‎repo_tag.go‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ func RepoTags(repoPath string, opts ...TagsOptions) ([]string, error) {
184184
sorted = true
185185
}
186186

187+
cmd.AddArgs("--end-of-options")
187188
if opt.Pattern != "" {
188189
cmd.AddArgs(opt.Pattern)
189190
}

‎repo_test.go‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,19 @@ func TestRepository_RevParse(t *testing.T) {
494494
expID: "",
495495
expErr: ErrRevisionNotExist,
496496
},
497+
498+
// A revision starting with a dash must be rejected to prevent argument
499+
// injection into "git rev-parse".
500+
{
501+
rev: "--absolute-git-dir",
502+
expID: "",
503+
expErr: ErrRevisionNotExist,
504+
},
505+
{
506+
rev: "-h",
507+
expID: "",
508+
expErr: ErrRevisionNotExist,
509+
},
497510
}
498511
for _, test := range tests {
499512
t.Run("", func(t *testing.T) {

‎repo_tree.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ func (r *Repository) LsTree(treeID string, opts ...LsTreeOptions) (*Tree, error)
142142
}
143143
stdout, err := cmd.
144144
AddOptions(opt.CommandOptions).
145-
AddArgs(treeID).
145+
AddArgs("--end-of-options", treeID).
146146
RunInDirWithTimeout(opt.Timeout, r.path)
147147
if err != nil {
148148
return nil, err

‎server.go‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ func UpdateServerInfo(path string, opts ...UpdateServerInfoOptions) error {
3333
if opt.Force {
3434
cmd.AddArgs("--force")
3535
}
36+
cmd.AddArgs("--end-of-options")
3637
_, err := cmd.RunInDirWithTimeout(opt.Timeout, path)
3738
return err
3839
}
@@ -66,7 +67,7 @@ func ReceivePack(path string, opts ...ReceivePackOptions) ([]byte, error) {
6667
if opt.HTTPBackendInfoRefs {
6768
cmd.AddArgs("--http-backend-info-refs")
6869
}
69-
cmd.AddArgs(".")
70+
cmd.AddArgs("--end-of-options", ".")
7071
return cmd.RunInDirWithTimeout(opt.Timeout, path)
7172
}
7273

@@ -108,6 +109,6 @@ func UploadPack(path string, opts ...UploadPackOptions) ([]byte, error) {
108109
if opt.HTTPBackendInfoRefs {
109110
cmd.AddArgs("--http-backend-info-refs")
110111
}
111-
cmd.AddArgs(".")
112+
cmd.AddArgs("--end-of-options", ".")
112113
return cmd.RunInDirWithTimeout(opt.Timeout, path)
113114
}

0 commit comments

Comments
 (0)