Skip to content
This repository has been archived by the owner on Feb 27, 2020. It is now read-only.

Commit

Permalink
Merge bc22b89 into a08a481
Browse files Browse the repository at this point in the history
  • Loading branch information
walac committed Jun 10, 2016
2 parents a08a481 + bc22b89 commit 426ff99
Show file tree
Hide file tree
Showing 9 changed files with 460 additions and 54 deletions.
65 changes: 65 additions & 0 deletions engines/osxnative/dscl.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package osxnative

import (
"os"
"os/exec"
"strings"
)

type dscl struct {
}

// Run dscl
// "command" is one of dscl commands, like "read"
// "a" is a list of arguments to the command
func (d dscl) run(command string, a ...string) (string, error) {
args := []string{".", command}
args = append(args, a...)
cmd := exec.Command("dscl", args...)
cmd.Env = os.Environ()
output, err := cmd.Output()
return string(output), err
}

func (d dscl) list(a ...string) ([][]string, error) {
output, err := d.run("list", a...)

if err != nil {
return nil, err
}

ret := [][]string{}
for _, s := range strings.Split(output, "\n") {
fields := strings.Fields(s)
if len(fields) != 0 {
ret = append(ret, strings.Fields(s))
}
}

return ret, nil
}

func (d dscl) read(a ...string) (string, error) {
s, err := d.run("read", a...)

if err != nil {
return s, err
}

return strings.Fields(s)[1], nil
}

func (d dscl) create(a ...string) error {
_, err := d.run("create", a...)
return err
}

func (d dscl) append(a ...string) error {
_, err := d.run("append", a...)
return err
}

func (d dscl) delete(a ...string) error {
_, err := d.run("delete", a...)
return err
}
58 changes: 36 additions & 22 deletions engines/osxnative/resultset.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,40 +5,29 @@ import (
"github.com/taskcluster/taskcluster-worker/runtime"
"github.com/taskcluster/taskcluster-worker/runtime/ioext"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
)

type resultset struct {
engines.ResultSetBase
context *runtime.TaskContext
success bool
taskUser user
context *runtime.TaskContext
success bool
}

var pathMatcher *regexp.Regexp

func init() {
home := os.Getenv("HOME")
if strings.HasPrefix(home, "/") {
// remove trailing "/"
home = home[:len(home)]
}

// Check if the path is valid. The only valid paths are those
// inside user's home directory and in /tmp/
func (r resultset) validPath(home string, pathName string) bool {
pathMatcher = regexp.MustCompile("^(" + home + "|/tmp)(/.*)?$")
}

func newResultSet(context *runtime.TaskContext, success bool) resultset {
return resultset{
ResultSetBase: engines.ResultSetBase{},
context: context,
success: success,
if !filepath.IsAbs(pathName) {
pathName = filepath.Join(home, pathName)
}
}

// Check if the path is valid. The only valid paths are those
// inside user's home directory and in /tmp/
func (r resultset) validPath(pathName string) bool {
absPath, err := filepath.Abs(pathName)

if err != nil {
Expand All @@ -56,10 +45,15 @@ func (r resultset) Success() bool {
}

func (r resultset) ExtractFile(path string) (ioext.ReadSeekCloser, error) {
if !r.validPath(path) {
cwd := getWorkingDir(r.taskUser, r.context)
if !r.validPath(cwd, path) {
return nil, engines.NewMalformedPayloadError(path + " is invalid")
}

if !filepath.IsAbs(path) {
path = filepath.Join(cwd, path)
}

file, err := os.Open(path)

if err != nil {
Expand All @@ -71,10 +65,15 @@ func (r resultset) ExtractFile(path string) (ioext.ReadSeekCloser, error) {
}

func (r resultset) ExtractFolder(path string, handler engines.FileHandler) error {
if !r.validPath(path) {
cwd := getWorkingDir(r.taskUser, r.context)
if !r.validPath(cwd, path) {
return engines.NewMalformedPayloadError(path + " is invalid")
}

if !filepath.IsAbs(path) {
path = filepath.Join(cwd, path)
}

return filepath.Walk(path, func(p string, info os.FileInfo, e error) error {
if e != nil {
r.context.LogError(e)
Expand All @@ -97,3 +96,18 @@ func (r resultset) ExtractFolder(path string, handler engines.FileHandler) error
return nil
})
}

func (r resultset) Dispose() error {
err := r.taskUser.delete()
if err != nil {
r.context.LogError("Error removing user \""+r.taskUser.name+"\": ", err)
exitError, ok := err.(*exec.ExitError)
if ok {
r.context.LogError(string(exitError.Stderr))
}

return engines.ErrNonFatalInternalError
}

return nil
}
17 changes: 15 additions & 2 deletions engines/osxnative/resultset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/taskcluster/taskcluster-worker/runtime/ioext"
"io/ioutil"
"os"
osuser "os/user"
"path"
"testing"
)
Expand All @@ -28,7 +29,12 @@ func makeResultSet(t *testing.T) resultset {
t.Fatal(err)
}

return newResultSet(context, true)
return resultset{
ResultSetBase: engines.ResultSetBase{},
taskUser: user{},
context: context,
success: true,
}
}

func TestValidPath(t *testing.T) {
Expand All @@ -49,17 +55,23 @@ func TestValidPath(t *testing.T) {
{"/", false},
}

userInfo, err := osuser.Current()
if err != nil {
t.Fatal(err)
}

r := makeResultSet(t)

for _, tc := range testCases {
if r.validPath(tc.pathName) != tc.expectedResult {
if r.validPath(userInfo.HomeDir, tc.pathName) != tc.expectedResult {
t.Errorf("validPath(%s) != %t", tc.pathName, tc.expectedResult)
}
}
}

func TestExtractFile(t *testing.T) {
r := makeResultSet(t)
defer r.Dispose()

_, err := r.ExtractFile("invalid-path/invalid-file")

Expand Down Expand Up @@ -93,6 +105,7 @@ func TestExtractFile(t *testing.T) {

func TestExtractFolder(t *testing.T) {
r := makeResultSet(t)
defer r.Dispose()

err := r.ExtractFolder("invalid-path/", func(p string, stream ioext.ReadSeekCloser) error {
return nil
Expand Down
122 changes: 94 additions & 28 deletions engines/osxnative/sandbox.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,17 @@ import (
"net/http"
"os"
"os/exec"
osuser "os/user"
"path/filepath"
"strconv"
"syscall"
)

// white list of environment variables that must
// be passed to the child process
var environmentWhitelist = []string{
"PATH",
"HOME",
"USER",
"SHELL",
"TMPDIR",
"PWD",
"EDITOR",
Expand Down Expand Up @@ -66,7 +68,7 @@ func newSandbox(context *runtime.TaskContext, taskPayload *payload, env []string
}
}

func downloadLink(link string) (string, error) {
func downloadLink(destdir string, link string) (string, error) {
resp, err := http.Get(link)

if err != nil {
Expand All @@ -78,11 +80,14 @@ func downloadLink(link string) (string, error) {
contentDisposition := resp.Header.Get("Content-Disposition")
_, params, err := mime.ParseMediaType(contentDisposition)

if err != nil {
return "", err
var filename string
if err == nil {
filename = params["filename"]
} else {
filename = filepath.Base(link)
}

filename := params["filename"]
filename = filepath.Join(destdir, filename)
file, err := os.Create(filename)

if err != nil {
Expand All @@ -104,23 +109,7 @@ func (s *sandbox) WaitForResult() (engines.ResultSet, error) {
return nil, engines.ErrSandboxAborted
}

if s.taskPayload.Link != "" {
filename, err := downloadLink(s.taskPayload.Link)

if err != nil {
s.context.LogError(err)
return nil, engines.ErrNonFatalInternalError
}

defer os.Remove(filename)

err = os.Chmod(filename, 0777)

if err != nil {
s.context.LogError(err)
return nil, engines.ErrNonFatalInternalError
}
}
var err error

env := make([]string, len(s.env), len(s.env)+len(environmentWhitelist))
copy(env, s.env)
Expand All @@ -136,21 +125,98 @@ func (s *sandbox) WaitForResult() (engines.ResultSet, error) {
cmd := exec.Command(s.taskPayload.Command[0], s.taskPayload.Command[1:]...)
cmd.Stdout = stdoutLogWriter{s.context}
cmd.Stderr = stderrLogWriter{s.context}

// USER and HOME are treated seperately because their values
// depend on either we create the new user successfuly or not
processUser := os.Getenv("USER")
processHome := os.Getenv("HOME")

// If we fail to create a new user, the most probable cause is that
// we don't have enough permissions. Chances are that we are running
// in in a development environment, so do not fail the task to tests
// run successfully.
u := user{}
if err = u.create(); err != nil {
s.context.LogError("Could not create user: ", err, "\n")
exitError, ok := err.(*exec.ExitError)
if ok {
s.context.LogError(string(exitError.Stderr), "\n")
}
} else {
defer func() {
if err != nil {
u.delete()
}
}()

userInfo, err := osuser.Lookup(u.name)
if err != nil {
s.context.LogError("Error looking up for user \""+u.name+"\": ", err, "\n")
} else {
uid, err := strconv.ParseUint(userInfo.Uid, 10, 32)
if err != nil {
s.context.LogError("ParseUint failed to convert ", userInfo.Uid, ": ", err, "\n")
return nil, engines.ErrNonFatalInternalError
}

gid, err := strconv.ParseUint(userInfo.Gid, 10, 32)
if err != nil {
s.context.LogError("ParseUint failed to convert ", userInfo.Gid, ": ", err, "\n")
return nil, engines.ErrNonFatalInternalError
}

cmd.SysProcAttr = &syscall.SysProcAttr{
Credential: &syscall.Credential{
Uid: uint32(uid),
Gid: uint32(gid),
Groups: []uint32{},
},
}

cmd.Dir = userInfo.HomeDir
processUser = u.name
processHome = userInfo.HomeDir
}
}

env = append(env, "HOME="+processHome, "USER="+processUser)
cmd.Env = env

err := cmd.Run()
if s.taskPayload.Link != "" {
filename, err := downloadLink(getWorkingDir(u, s.context), s.taskPayload.Link)

if err != nil {
s.context.LogError("Command \"", s.taskPayload.Command, "\" failed to run: ", err)
if err != nil {
s.context.LogError(err)
return nil, engines.ErrNonFatalInternalError
}

defer os.Remove(filename)

if err = os.Chmod(filename, 0777); err != nil {
s.context.LogError(err, "\n")
return nil, engines.ErrNonFatalInternalError
}
}

r := resultset{
ResultSetBase: engines.ResultSetBase{},
taskUser: u,
context: s.context,
success: false,
}

if err = cmd.Run(); err != nil {
s.context.LogError("Command \"", s.taskPayload.Command, "\" failed to run: ", err, "\n")
switch err.(type) {
case *exec.ExitError:
return newResultSet(s.context, false), nil
return r, nil
default:
return nil, engines.ErrNonFatalInternalError
}
}

return newResultSet(s.context, true), nil
r.success = true
return r, nil
}

func (s *sandbox) Abort() error {
Expand Down

0 comments on commit 426ff99

Please sign in to comment.