Skip to content
This repository was archived by the owner on Sep 11, 2020. It is now read-only.

config: modules, worktree: Submodule fixes for CVE-2018-11235 #848

Merged
merged 2 commits into from
Jun 6, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ func (c *Config) Unmarshal(b []byte) error {
if err := c.unmarshalPack(); err != nil {
return err
}
c.unmarshalSubmodules()
unmarshalSubmodules(c.Raw, c.Submodules)

if err := c.unmarshalBranches(); err != nil {
return err
Expand Down Expand Up @@ -182,13 +182,17 @@ func (c *Config) unmarshalRemotes() error {
return nil
}

func (c *Config) unmarshalSubmodules() {
s := c.Raw.Section(submoduleSection)
func unmarshalSubmodules(fc *format.Config, submodules map[string]*Submodule) {
s := fc.Section(submoduleSection)
for _, sub := range s.Subsections {
m := &Submodule{}
m.unmarshal(sub)

c.Submodules[m.Name] = m
if m.Validate() == ErrModuleBadPath {
continue
}

submodules[m.Name] = m
}
}

Expand Down
20 changes: 12 additions & 8 deletions config/modules.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,20 @@ package config
import (
"bytes"
"errors"
"regexp"

format "gopkg.in/src-d/go-git.v4/plumbing/format/config"
)

var (
ErrModuleEmptyURL = errors.New("module config: empty URL")
ErrModuleEmptyPath = errors.New("module config: empty path")
ErrModuleBadPath = errors.New("submodule has an invalid path")
)

var (
// Matches module paths with dotdot ".." components.
dotdotPath = regexp.MustCompile(`(^|[/\\])\.\.([/\\]|$)`)
)

// Modules defines the submodules properties, represents a .gitmodules file
Expand Down Expand Up @@ -44,14 +51,7 @@ func (m *Modules) Unmarshal(b []byte) error {
return err
}

s := m.raw.Section(submoduleSection)
for _, sub := range s.Subsections {
mod := &Submodule{}
mod.unmarshal(sub)

m.Submodules[mod.Path] = mod
}

unmarshalSubmodules(m.raw, m.Submodules)
return nil
}

Expand Down Expand Up @@ -102,6 +102,10 @@ func (m *Submodule) Validate() error {
return ErrModuleEmptyURL
}

if dotdotPath.MatchString(m.Path) {
return ErrModuleBadPath
}

return nil
}

Expand Down
26 changes: 26 additions & 0 deletions config/modules_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,29 @@ func (s *ModulesSuite) TestValidateMissingURL(c *C) {
c.Assert(m.Validate(), Equals, ErrModuleEmptyURL)
}

func (s *ModulesSuite) TestValidateBadPath(c *C) {
input := []string{
`..`,
`../`,
`../bar`,

`/..`,
`/../bar`,

`foo/..`,
`foo/../`,
`foo/../bar`,
}

for _, p := range input {
m := &Submodule{
Path: p,
URL: "https://example.com/",
}
c.Assert(m.Validate(), Equals, ErrModuleBadPath)
}
}

func (s *ModulesSuite) TestValidateMissingName(c *C) {
m := &Submodule{URL: "bar"}
c.Assert(m.Validate(), Equals, ErrModuleEmptyPath)
Expand Down Expand Up @@ -39,6 +62,9 @@ func (s *ModulesSuite) TestUnmarshall(c *C) {
path = foo/bar
url = https://github.com/foo/bar.git
branch = dev
[submodule "suspicious"]
path = ../../foo/bar
url = https://github.com/foo/bar.git
`)

cfg := NewModules()
Expand Down
15 changes: 15 additions & 0 deletions submodule_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,21 @@ func (s *SubmoduleSuite) TestSubmodulesInit(c *C) {
}
}

func (s *SubmoduleSuite) TestGitSubmodulesSymlink(c *C) {
f, err := s.Worktree.Filesystem.Create("badfile")
c.Assert(err, IsNil)
defer f.Close()

err = s.Worktree.Filesystem.Remove(gitmodulesFile)
c.Assert(err, IsNil)

err = s.Worktree.Filesystem.Symlink("badfile", gitmodulesFile)
c.Assert(err, IsNil)

_, err = s.Worktree.Submodules()
c.Assert(err, Equals, ErrGitModulesSymlink)
}

func (s *SubmoduleSuite) TestSubmodulesStatus(c *C) {
sm, err := s.Worktree.Submodules()
c.Assert(err, IsNil)
Expand Down
12 changes: 12 additions & 0 deletions worktree.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ var (
ErrWorktreeNotClean = errors.New("worktree is not clean")
ErrSubmoduleNotFound = errors.New("submodule not found")
ErrUnstagedChanges = errors.New("worktree contains unstaged changes")
ErrGitModulesSymlink = errors.New(gitmodulesFile + " is a symlink")
)

// Worktree represents a git worktree.
Expand Down Expand Up @@ -680,7 +681,18 @@ func (w *Worktree) newSubmodule(fromModules, fromConfig *config.Submodule) *Subm
return m
}

func (w *Worktree) isSymlink(path string) bool {
if s, err := w.Filesystem.Lstat(path); err == nil {
return s.Mode()&os.ModeSymlink != 0
}
return false
}

func (w *Worktree) readGitmodulesFile() (*config.Modules, error) {
if w.isSymlink(gitmodulesFile) {
return nil, ErrGitModulesSymlink
}

f, err := w.Filesystem.Open(gitmodulesFile)
if err != nil {
if os.IsNotExist(err) {
Expand Down