Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Do not allow path to escape the alloc dir for the FS commands #1786

Merged
merged 1 commit into from
Oct 6, 2016
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
36 changes: 33 additions & 3 deletions client/allocdir/alloc_dir.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ type AllocDirFS interface {
Stat(path string) (*AllocFileInfo, error)
ReadAt(path string, offset int64) (io.ReadCloser, error)
Snapshot(w io.Writer) error
BlockUntilExists(path string, t *tomb.Tomb) chan error
BlockUntilExists(path string, t *tomb.Tomb) (chan error, error)
ChangeEvents(path string, curOffset int64, t *tomb.Tomb) (*watch.FileChanges, error)
}

Expand Down Expand Up @@ -459,6 +459,12 @@ func (d *AllocDir) LogDir() string {

// List returns the list of files at a path relative to the alloc dir
func (d *AllocDir) List(path string) ([]*AllocFileInfo, error) {
if escapes, err := structs.PathEscapesAllocDir(path); err != nil {
return nil, fmt.Errorf("Failed to check if path escapes alloc directory: %v", err)
} else if escapes {
return nil, fmt.Errorf("Path escapes the alloc directory")
}

p := filepath.Join(d.AllocDir, path)
finfos, err := ioutil.ReadDir(p)
if err != nil {
Expand All @@ -479,6 +485,12 @@ func (d *AllocDir) List(path string) ([]*AllocFileInfo, error) {

// Stat returns information about the file at a path relative to the alloc dir
func (d *AllocDir) Stat(path string) (*AllocFileInfo, error) {
if escapes, err := structs.PathEscapesAllocDir(path); err != nil {
return nil, fmt.Errorf("Failed to check if path escapes alloc directory: %v", err)
} else if escapes {
return nil, fmt.Errorf("Path escapes the alloc directory")
}

p := filepath.Join(d.AllocDir, path)
info, err := os.Stat(p)
if err != nil {
Expand All @@ -496,6 +508,12 @@ func (d *AllocDir) Stat(path string) (*AllocFileInfo, error) {

// ReadAt returns a reader for a file at the path relative to the alloc dir
func (d *AllocDir) ReadAt(path string, offset int64) (io.ReadCloser, error) {
if escapes, err := structs.PathEscapesAllocDir(path); err != nil {
return nil, fmt.Errorf("Failed to check if path escapes alloc directory: %v", err)
} else if escapes {
return nil, fmt.Errorf("Path escapes the alloc directory")
}

p := filepath.Join(d.AllocDir, path)
f, err := os.Open(p)
if err != nil {
Expand All @@ -509,7 +527,13 @@ func (d *AllocDir) ReadAt(path string, offset int64) (io.ReadCloser, error) {

// BlockUntilExists blocks until the passed file relative the allocation
// directory exists. The block can be cancelled with the passed tomb.
func (d *AllocDir) BlockUntilExists(path string, t *tomb.Tomb) chan error {
func (d *AllocDir) BlockUntilExists(path string, t *tomb.Tomb) (chan error, error) {
if escapes, err := structs.PathEscapesAllocDir(path); err != nil {
return nil, fmt.Errorf("Failed to check if path escapes alloc directory: %v", err)
} else if escapes {
return nil, fmt.Errorf("Path escapes the alloc directory")
}

// Get the path relative to the alloc directory
p := filepath.Join(d.AllocDir, path)
watcher := getFileWatcher(p)
Expand All @@ -518,13 +542,19 @@ func (d *AllocDir) BlockUntilExists(path string, t *tomb.Tomb) chan error {
returnCh <- watcher.BlockUntilExists(t)
close(returnCh)
}()
return returnCh
return returnCh, nil
}

// ChangeEvents watches for changes to the passed path relative to the
// allocation directory. The offset should be the last read offset. The tomb is
// used to clean up the watch.
func (d *AllocDir) ChangeEvents(path string, curOffset int64, t *tomb.Tomb) (*watch.FileChanges, error) {
if escapes, err := structs.PathEscapesAllocDir(path); err != nil {
return nil, fmt.Errorf("Failed to check if path escapes alloc directory: %v", err)
} else if escapes {
return nil, fmt.Errorf("Path escapes the alloc directory")
}

// Get the path relative to the alloc directory
p := filepath.Join(d.AllocDir, path)
watcher := getFileWatcher(p)
Expand Down
45 changes: 45 additions & 0 deletions client/allocdir/alloc_dir_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@ import (
"path/filepath"
"reflect"
"runtime"
"strings"
"testing"

tomb "gopkg.in/tomb.v1"

"github.com/hashicorp/nomad/client/testutil"
"github.com/hashicorp/nomad/nomad/structs"
)
Expand Down Expand Up @@ -345,3 +348,45 @@ func TestAllocDir_Move(t *testing.T) {
t.Fatalf("task local dir was not moved")
}
}

func TestAllocDir_EscapeChecking(t *testing.T) {
tmp, err := ioutil.TempDir("", "AllocDir")
if err != nil {
t.Fatalf("Couldn't create temp dir: %v", err)
}
defer os.RemoveAll(tmp)

d := NewAllocDir(tmp, structs.DefaultResources().DiskMB)
defer d.Destroy()
tasks := []*structs.Task{t1, t2}
if err := d.Build(tasks); err != nil {
t.Fatalf("Build(%v) failed: %v", tasks, err)
}

// Check that issuing calls that escape the alloc dir returns errors
// List
if _, err := d.List(".."); err == nil || !strings.Contains(err.Error(), "escapes") {
t.Fatalf("List of escaping path didn't error: %v", err)
}

// Stat
if _, err := d.Stat("../foo"); err == nil || !strings.Contains(err.Error(), "escapes") {
t.Fatalf("Stat of escaping path didn't error: %v", err)
}

// ReadAt
if _, err := d.ReadAt("../foo", 0); err == nil || !strings.Contains(err.Error(), "escapes") {
t.Fatalf("ReadAt of escaping path didn't error: %v", err)
}

// BlockUntilExists
tomb := tomb.Tomb{}
if _, err := d.BlockUntilExists("../foo", &tomb); err == nil || !strings.Contains(err.Error(), "escapes") {
t.Fatalf("BlockUntilExists of escaping path didn't error: %v", err)
}

// ChangeEvents
if _, err := d.ChangeEvents("../foo", 0, &tomb); err == nil || !strings.Contains(err.Error(), "escapes") {
t.Fatalf("ChangeEvents of escaping path didn't error: %v", err)
}
}
8 changes: 7 additions & 1 deletion command/agent/fs_endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -856,7 +856,13 @@ func blockUntilNextLog(fs allocdir.AllocDirFS, t *tomb.Tomb, logPath, task, logT
next := make(chan error, 1)

go func() {
eofCancelCh := fs.BlockUntilExists(nextPath, t)
eofCancelCh, err := fs.BlockUntilExists(nextPath, t)
if err != nil {
next <- err
close(next)
return
}

scanCh := time.Tick(nextLogCheckRate)
for {
select {
Expand Down
8 changes: 4 additions & 4 deletions nomad/structs/structs.go
Original file line number Diff line number Diff line change
Expand Up @@ -2274,7 +2274,7 @@ func (t *Template) Validate() error {
}

// Verify the destination doesn't escape
escaped, err := pathEscapesAllocDir(t.DestPath)
escaped, err := PathEscapesAllocDir(t.DestPath)
if err != nil {
mErr.Errors = append(mErr.Errors, fmt.Errorf("invalid destination path: %v", err))
} else if escaped {
Expand Down Expand Up @@ -2593,9 +2593,9 @@ func (ta *TaskArtifact) GoString() string {
return fmt.Sprintf("%+v", ta)
}

// pathEscapesAllocDir returns if the given path escapes the allocation
// PathEscapesAllocDir returns if the given path escapes the allocation
// directory
func pathEscapesAllocDir(path string) (bool, error) {
func PathEscapesAllocDir(path string) (bool, error) {
// Verify the destination doesn't escape the tasks directory
alloc, err := filepath.Abs(filepath.Join("/", "foo/", "bar/"))
if err != nil {
Expand All @@ -2620,7 +2620,7 @@ func (ta *TaskArtifact) Validate() error {
mErr.Errors = append(mErr.Errors, fmt.Errorf("source must be specified"))
}

escaped, err := pathEscapesAllocDir(ta.RelativeDest)
escaped, err := PathEscapesAllocDir(ta.RelativeDest)
if err != nil {
mErr.Errors = append(mErr.Errors, fmt.Errorf("invalid destination path: %v", err))
} else if escaped {
Expand Down