diff --git a/client/allocdir/alloc_dir.go b/client/allocdir/alloc_dir.go index 1f0377714..82d7c98a7 100644 --- a/client/allocdir/alloc_dir.go +++ b/client/allocdir/alloc_dir.go @@ -411,7 +411,7 @@ 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 { + 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") @@ -437,7 +437,7 @@ 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 { + 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") @@ -460,7 +460,7 @@ 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 { + 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") @@ -489,7 +489,7 @@ 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, error) { - if escapes, err := structs.PathEscapesAllocDir(path); err != nil { + 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") @@ -510,7 +510,7 @@ func (d *AllocDir) BlockUntilExists(path string, t *tomb.Tomb) (chan error, erro // 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 { + 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") diff --git a/nomad/structs/structs.go b/nomad/structs/structs.go index 8ad0bc9aa..facc49e33 100644 --- a/nomad/structs/structs.go +++ b/nomad/structs/structs.go @@ -1706,6 +1706,18 @@ func (d *DispatchInputConfig) Copy() *DispatchInputConfig { return nd } +func (d *DispatchInputConfig) Validate() error { + // Verify the destination doesn't escape + escaped, err := PathEscapesAllocDir("task/local/", d.File) + if err != nil { + return fmt.Errorf("invalid destination path: %v", err) + } else if escaped { + return fmt.Errorf("destination escapes allocation directory") + } + + return nil +} + var ( defaultServiceJobRestartPolicy = RestartPolicy{ Delay: 15 * time.Second, @@ -2462,6 +2474,13 @@ func (t *Task) Validate(ephemeralDisk *EphemeralDisk) error { } } + // Validate the dispatch input block if there + if t.DispatchInput != nil { + if err := t.DispatchInput.Validate(); err != nil { + mErr.Errors = append(mErr.Errors, err) + } + } + return mErr.ErrorOrNil() } @@ -2603,7 +2622,7 @@ func (t *Template) Validate() error { } // Verify the destination doesn't escape - escaped, err := PathEscapesAllocDir(t.DestPath) + escaped, err := PathEscapesAllocDir("task", t.DestPath) if err != nil { mErr.Errors = append(mErr.Errors, fmt.Errorf("invalid destination path: %v", err)) } else if escaped { @@ -2955,14 +2974,16 @@ func (ta *TaskArtifact) GoString() string { } // PathEscapesAllocDir returns if the given path escapes the allocation -// directory -func PathEscapesAllocDir(path string) (bool, error) { +// directory. The prefix allows adding a prefix if the path will be joined, for +// example a "task/local" prefix may be provided if the path will be joined +// against that prefix. +func PathEscapesAllocDir(prefix, path string) (bool, error) { // Verify the destination doesn't escape the tasks directory - alloc, err := filepath.Abs(filepath.Join("/", "foo/", "bar/")) + alloc, err := filepath.Abs(filepath.Join("/", "alloc-dir/", "alloc-id/")) if err != nil { return false, err } - abs, err := filepath.Abs(filepath.Join(alloc, path)) + abs, err := filepath.Abs(filepath.Join(alloc, prefix, path)) if err != nil { return false, err } @@ -2981,7 +3002,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("task", ta.RelativeDest) if err != nil { mErr.Errors = append(mErr.Errors, fmt.Errorf("invalid destination path: %v", err)) } else if escaped { diff --git a/nomad/structs/structs_test.go b/nomad/structs/structs_test.go index 78638d69b..4ba18b65c 100644 --- a/nomad/structs/structs_test.go +++ b/nomad/structs/structs_test.go @@ -1267,7 +1267,7 @@ func TestTaskArtifact_Validate_Dest(t *testing.T) { t.Fatalf("unexpected error: %v", err) } - valid.RelativeDest = "local/../.." + valid.RelativeDest = "local/../../.." if err := valid.Validate(); err == nil { t.Fatalf("expected error: %v", err) } @@ -1482,3 +1482,26 @@ func TestConstructorConfig_Canonicalize(t *testing.T) { t.Fatalf("Canonicalize failed") } } + +func TestDispatchInputConfig_Validate(t *testing.T) { + d := &DispatchInputConfig{ + File: "foo", + } + + // task/local/haha + if err := d.Validate(); err != nil { + t.Fatalf("bad: %v", err) + } + + // task/haha + d.File = "../haha" + if err := d.Validate(); err != nil { + t.Fatalf("bad: %v", err) + } + + // ../haha + d.File = "../../../haha" + if err := d.Validate(); err == nil { + t.Fatalf("bad: %v", err) + } +}