diff --git a/client/allocrunner/taskrunner/task_runner_test.go b/client/allocrunner/taskrunner/task_runner_test.go index 997a21718..99900b785 100644 --- a/client/allocrunner/taskrunner/task_runner_test.go +++ b/client/allocrunner/taskrunner/task_runner_test.go @@ -1054,6 +1054,85 @@ func TestTaskRunner_DeriveToken_Unrecoverable(t *testing.T) { require.True(t, state.Events[2].FailsTask) } +// TestTaskRunner_Download_ChrootExec asserts that downloaded artifacts may be +// executed in a chroot. +func TestTaskRunner_Download_ChrootExec(t *testing.T) { + t.Parallel() + ctestutil.ExecCompatible(t) + + ts := httptest.NewServer(http.FileServer(http.Dir(filepath.Dir(".")))) + defer ts.Close() + + // Create a task that downloads a script and executes it. + alloc := mock.BatchAlloc() + alloc.Job.TaskGroups[0].RestartPolicy = &structs.RestartPolicy{} + task := alloc.Job.TaskGroups[0].Tasks[0] + task.Driver = "exec" + task.Config = map[string]interface{}{ + "command": "noop.sh", + } + task.Artifacts = []*structs.TaskArtifact{ + { + GetterSource: fmt.Sprintf("%s/testdata/noop.sh", ts.URL), + GetterMode: "file", + RelativeDest: "noop.sh", + }, + } + + tr, _, cleanup := runTestTaskRunner(t, alloc, task.Name) + defer cleanup() + + // Wait for task to run and exit + select { + case <-tr.WaitCh(): + case <-time.After(time.Duration(testutil.TestMultiplier()*15) * time.Second): + require.Fail(t, "timed out waiting for task runner to exit") + } + + state := tr.TaskState() + require.Equal(t, structs.TaskStateDead, state.State) + require.False(t, state.Failed) +} + +// TestTaskRunner_Download_Exec asserts that downloaded artifacts may be +// executed in a driver without filesystem isolation. +func TestTaskRunner_Download_RawExec(t *testing.T) { + t.Parallel() + + ts := httptest.NewServer(http.FileServer(http.Dir(filepath.Dir(".")))) + defer ts.Close() + + // Create a task that downloads a script and executes it. + alloc := mock.BatchAlloc() + alloc.Job.TaskGroups[0].RestartPolicy = &structs.RestartPolicy{} + task := alloc.Job.TaskGroups[0].Tasks[0] + task.Driver = "raw_exec" + task.Config = map[string]interface{}{ + "command": "noop.sh", + } + task.Artifacts = []*structs.TaskArtifact{ + { + GetterSource: fmt.Sprintf("%s/testdata/noop.sh", ts.URL), + GetterMode: "file", + RelativeDest: "noop.sh", + }, + } + + tr, _, cleanup := runTestTaskRunner(t, alloc, task.Name) + defer cleanup() + + // Wait for task to run and exit + select { + case <-tr.WaitCh(): + case <-time.After(time.Duration(testutil.TestMultiplier()*15) * time.Second): + require.Fail(t, "timed out waiting for task runner to exit") + } + + state := tr.TaskState() + require.Equal(t, structs.TaskStateDead, state.State) + require.False(t, state.Failed) +} + // TestTaskRunner_Download_List asserts that multiple artificats are downloaded // before a task is run. func TestTaskRunner_Download_List(t *testing.T) { diff --git a/client/allocrunner/taskrunner/testdata/noop.sh b/client/allocrunner/taskrunner/testdata/noop.sh new file mode 100644 index 000000000..8832c26a2 --- /dev/null +++ b/client/allocrunner/taskrunner/testdata/noop.sh @@ -0,0 +1,2 @@ +#!/bin/sh +echo "ok" diff --git a/drivers/shared/executor/executor_linux.go b/drivers/shared/executor/executor_linux.go index 3ac4618aa..6c978e787 100644 --- a/drivers/shared/executor/executor_linux.go +++ b/drivers/shared/executor/executor_linux.go @@ -161,7 +161,13 @@ func (l *LibcontainerExecutor) Launch(command *ExecCommand) (*ProcessState, erro if err != nil { return nil, fmt.Errorf("failed to determine relative path base=%q target=%q: %v", command.TaskDir, path, err) } - path = rel + + // Turn relative-to-chroot path into absolute path to avoid + // libcontainer trying to resolve the binary using $PATH. + // Do *not* use filepath.Join as it will translate ".."s returned by + // filepath.Rel. Prepending "/" will cause the path to be rooted in the + // chroot which is the desired behavior. + path = "/" + rel combined := append([]string{path}, command.Args...) stdout, err := command.Stdout()