github.com/jlmucb/cloudproxy@v0.0.0-20170830161738-b5aa0b619bc4/go/tao/linux_process_factory.go (about)

     1  // Copyright (c) 2014, Google Inc.  All rights reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package tao
    16  
    17  import (
    18  	"crypto/rand"
    19  	"crypto/sha256"
    20  	"encoding/base64"
    21  	"fmt"
    22  	"io"
    23  	"io/ioutil"
    24  	"os"
    25  	"os/exec"
    26  	"os/signal"
    27  	"path"
    28  	"strings"
    29  	"syscall"
    30  
    31  	"github.com/jlmucb/cloudproxy/go/tao/auth"
    32  	"github.com/jlmucb/cloudproxy/go/util"
    33  )
    34  
    35  // A LinuxProcessFactory supports methods for creating Linux processes as
    36  // hosted programs. LinuxProcessFactory implements HostedProgramFactory.
    37  type LinuxProcessFactory struct {
    38  	channelType string
    39  	socketPath  string
    40  }
    41  
    42  // NewLinuxProcessFactory returns a new HostedProgramFactory that can create
    43  // linux processes.
    44  func NewLinuxProcessFactory(channelType, socketPath string) HostedProgramFactory {
    45  	return &LinuxProcessFactory{
    46  		channelType: channelType,
    47  		socketPath:  socketPath,
    48  	}
    49  }
    50  
    51  // A LinuxProcess represents a hosted program that executes as a linux process.
    52  type HostedProcess struct {
    53  
    54  	// The spec from which this process was created.
    55  	spec HostedProgramSpec
    56  
    57  	// The value to be used as argv[0]
    58  	Argv0 string
    59  
    60  	// A secured, private copy of the executable.
    61  	Temppath string
    62  
    63  	// A temporary directory for storing the temporary executable.
    64  	Tempdir string
    65  
    66  	// Hash of the executable.
    67  	Hash []byte
    68  
    69  	// The underlying process.
    70  	Cmd exec.Cmd
    71  
    72  	// The factory responsible for the hosted process.
    73  	Factory *LinuxProcessFactory
    74  
    75  	// A channel to be signaled when the process is done.
    76  	Done chan bool
    77  }
    78  
    79  // NewHostedProgram initializes, but does not start, a hosted process.
    80  func (lpf *LinuxProcessFactory) NewHostedProgram(spec HostedProgramSpec) (child HostedProgram, err error) {
    81  
    82  	// The argv[0] for the child is given by spec.ContainerArgs
    83  	argv0 := spec.Path
    84  	if len(spec.ContainerArgs) == 1 {
    85  		argv0 = spec.ContainerArgs[0]
    86  	} else if len(spec.ContainerArgs) > 0 {
    87  		err = fmt.Errorf("Too many container arguments for process")
    88  		return
    89  	}
    90  
    91  	// To avoid a time-of-check-to-time-of-use error, we copy the file
    92  	// bytes to a temp file as we read them. This temp-file path is
    93  	// returned so it can be used to start the program.
    94  	tempdir, err := ioutil.TempDir("/tmp", "cloudproxy_linux_host")
    95  	if err != nil {
    96  		return
    97  	}
    98  	defer func() {
    99  		if err != nil {
   100  			os.RemoveAll(tempdir)
   101  		}
   102  	}()
   103  	if err = os.Chmod(tempdir, 0755); err != nil {
   104  		return
   105  	}
   106  
   107  	temppath := path.Join(tempdir, "hosted_program")
   108  	tf, err := os.OpenFile(temppath, os.O_CREATE|os.O_RDWR, 0700)
   109  	defer tf.Close()
   110  	if err != nil {
   111  		return
   112  	}
   113  	if err = tf.Chmod(0755); err != nil {
   114  		return
   115  	}
   116  
   117  	inf, err := os.Open(spec.Path)
   118  	defer inf.Close()
   119  	if err != nil {
   120  		return
   121  	}
   122  
   123  	// Read from the input file and write to the temp file.
   124  	tr := io.TeeReader(inf, tf)
   125  	b, err := ioutil.ReadAll(tr)
   126  	if err != nil {
   127  		return
   128  	}
   129  
   130  	h := sha256.Sum256(b)
   131  
   132  	child = &HostedProcess{
   133  		spec:     spec,
   134  		Argv0:    argv0,
   135  		Temppath: temppath,
   136  		Tempdir:  tempdir,
   137  		Hash:     h[:],
   138  		Factory:  lpf,
   139  		Done:     make(chan bool, 1),
   140  	}
   141  	return
   142  }
   143  
   144  // Use 24 bytes for the socket name.
   145  const sockNameLen = 24
   146  
   147  // Start starts the the hosted process and returns a tao channel to it.
   148  func (p *HostedProcess) Start() (channel io.ReadWriteCloser, err error) {
   149  	var extraFiles []*os.File
   150  	var evar string
   151  	switch p.Factory.channelType {
   152  	case "pipe":
   153  		// Get a pipe pair for communication with the child.
   154  		var serverRead, clientRead, serverWrite, clientWrite *os.File
   155  		serverRead, clientWrite, err = os.Pipe()
   156  		if err != nil {
   157  			return
   158  		}
   159  		defer clientWrite.Close()
   160  
   161  		clientRead, serverWrite, err = os.Pipe()
   162  		if err != nil {
   163  			serverRead.Close()
   164  			return
   165  		}
   166  		defer clientRead.Close()
   167  
   168  		channel = util.NewPairReadWriteCloser(serverRead, serverWrite)
   169  		extraFiles = []*os.File{clientRead, clientWrite} // fd 3, fd 4
   170  
   171  		// Note: ExtraFiles below ensures readfd=3, writefd=4 in child
   172  		evar = HostSpecEnvVar + "=tao::RPC+tao::FDMessageChannel(3, 4)"
   173  	case "unix":
   174  		// Get a random name for the socket.
   175  		nameBytes := make([]byte, sockNameLen)
   176  		if _, err = rand.Read(nameBytes); err != nil {
   177  			return
   178  		}
   179  		sockName := base64.URLEncoding.EncodeToString(nameBytes)
   180  		sockPath := path.Join(p.Factory.socketPath, sockName)
   181  		channel = util.NewUnixSingleReadWriteCloser(sockPath)
   182  		if channel == nil {
   183  			err = fmt.Errorf("Couldn't create a new Unix channel\n")
   184  			return
   185  		}
   186  		evar = HostSpecEnvVar + "=" + sockPath
   187  	default:
   188  		err = fmt.Errorf("invalid channel type '%s'\n", p.Factory.channelType)
   189  		return
   190  	}
   191  	defer func() {
   192  		if err != nil {
   193  			channel.Close()
   194  			channel = nil
   195  		}
   196  	}()
   197  
   198  	env := p.spec.Env
   199  	if env == nil {
   200  		env = os.Environ()
   201  	}
   202  	// Make sure that the child knows to use the right kind of channel.
   203  	etvar := HostChannelTypeEnvVar + "=" + p.Factory.channelType
   204  	replaced := false
   205  	replacedType := false
   206  	for i, pair := range env {
   207  		if strings.HasPrefix(pair, HostSpecEnvVar+"=") {
   208  			env[i] = evar
   209  			replaced = true
   210  		}
   211  
   212  		if strings.HasPrefix(pair, HostChannelTypeEnvVar+"=") {
   213  			env[i] = etvar
   214  			replacedType = true
   215  		}
   216  	}
   217  	if !replaced {
   218  		env = append(env, evar)
   219  	}
   220  
   221  	if !replacedType {
   222  		env = append(env, etvar)
   223  	}
   224  
   225  	if (p.spec.Uid == 0 || p.spec.Gid == 0) && !p.spec.Superuser {
   226  		err = fmt.Errorf("Uid and Gid must be nonzero unless Superuser is set\n")
   227  		return
   228  	}
   229  
   230  	wd := p.spec.Dir
   231  	if wd == "" {
   232  		wd = p.Tempdir
   233  	}
   234  
   235  	// Every hosted process is given its own process group (Setpgid=true). This
   236  	// ensures that hosted processes will not be in orphaned process groups,
   237  	// allowing them to receive job control signals (SIGTTIN, SIGTTOU, and
   238  	// SIGTSTP).
   239  	//
   240  	// If this host is running in "daemon" mode, i.e. without a controlling tty
   241  	// and in our own session and process group, then this host will be (a) the
   242  	// parent of a process in the child's group, (b) in the same session, and
   243  	// (c) not in the same group as the child, so it will serve as the anchor
   244  	// that keeps the child process groups from being considered orphaned.
   245  	//
   246  	// If this host is running in "foreground" mode, i.e. with a controlling tty
   247  	// and as part of our parent process's session but in our own process group,
   248  	// then the same three conditions are satisified, so this host can still
   249  	// serve as the anchor that keeps the child process groups from being
   250  	// considered orphaned. (Note: We could also use Setpid=false in this case,
   251  	// since the host would be part of the child process group and our parent
   252  	// would then meet the requirements.)
   253  
   254  	spa := &syscall.SysProcAttr{
   255  		Credential: &syscall.Credential{
   256  			Uid: uint32(p.spec.Uid),
   257  			Gid: uint32(p.spec.Uid),
   258  		},
   259  		// Setsid: true, // Create session.
   260  		Setpgid: true, // Set process group ID to new pid (SYSV setpgrp)
   261  		// Setctty: true, // Set controlling terminal to fd Ctty (only meaningful if Setsid is set)
   262  		// Noctty: true, // Detach fd 0 from controlling terminal
   263  		// Ctty: 0, // Controlling TTY fd (Linux only)
   264  	}
   265  	argv := []string{p.Argv0}
   266  	argv = append(argv, p.spec.Args...)
   267  	p.Cmd = exec.Cmd{
   268  		Path:        p.Temppath,
   269  		Dir:         wd,
   270  		Args:        argv,
   271  		Stdin:       p.spec.Stdin,
   272  		Stdout:      p.spec.Stdout,
   273  		Stderr:      p.spec.Stderr,
   274  		Env:         env,
   275  		ExtraFiles:  extraFiles,
   276  		SysProcAttr: spa,
   277  	}
   278  
   279  	if err = p.Cmd.Start(); err != nil {
   280  		return
   281  	}
   282  
   283  	// Reap the child when the process dies.
   284  	sc := make(chan os.Signal, 1)
   285  	signal.Notify(sc, syscall.SIGCHLD)
   286  	go func() {
   287  		<-sc
   288  		p.Cmd.Wait()
   289  		signal.Stop(sc)
   290  		os.RemoveAll(p.Tempdir)
   291  		p.Done <- true
   292  		close(p.Done) // prevent any more blocking
   293  	}()
   294  
   295  	// TODO(kwalsh) put channel into p, remove the struct in linux_host.go
   296  
   297  	return
   298  }
   299  
   300  // ExitStatus returns an exit code for the process.
   301  func (p *HostedProcess) ExitStatus() (int, error) {
   302  	s := p.Cmd.ProcessState
   303  	if s == nil {
   304  		return 0, fmt.Errorf("Child has not exited")
   305  	}
   306  	if code, ok := (*s).Sys().(syscall.WaitStatus); ok {
   307  		return int(code), nil
   308  	}
   309  	return 0, fmt.Errorf("Couldn't get exit status\n")
   310  }
   311  
   312  // WaitChan returns a chan that will be signaled when the hosted process is
   313  // done.
   314  func (p *HostedProcess) WaitChan() <-chan bool {
   315  	return p.Done
   316  }
   317  
   318  // Kill kills an os/exec.Cmd process.
   319  func (p *HostedProcess) Kill() error {
   320  	return p.Cmd.Process.Kill()
   321  }
   322  
   323  // Stop tries to send SIGTERM to a process.
   324  func (p *HostedProcess) Stop() error {
   325  	err := syscall.Kill(p.Cmd.Process.Pid, syscall.SIGTERM)
   326  	syscall.Kill(p.Cmd.Process.Pid, syscall.SIGCONT)
   327  	return err
   328  }
   329  
   330  // Spec returns the specification used to start the hosted process.
   331  func (p *HostedProcess) Spec() HostedProgramSpec {
   332  	return p.spec
   333  }
   334  
   335  // Pid returns the pid of the underlying os/exec.Cmd instance.
   336  func (p *HostedProcess) Pid() int {
   337  	return p.Cmd.Process.Pid
   338  }
   339  
   340  // Subprin returns the subprincipal representing the hosted process.
   341  func (p *HostedProcess) Subprin() auth.SubPrin {
   342  	return FormatProcessSubprin(p.spec.Id, p.Hash)
   343  }
   344  
   345  // FormatProcessSubprin produces a string that represents a subprincipal with
   346  // the given ID and hash.
   347  func FormatProcessSubprin(id uint, hash []byte) auth.SubPrin {
   348  	var args []auth.Term
   349  	if id != 0 {
   350  		args = append(args, auth.Int(id))
   351  	}
   352  	args = append(args, auth.Bytes(hash))
   353  	return auth.SubPrin{auth.PrinExt{Name: "Program", Arg: args}}
   354  }
   355  
   356  func (p *HostedProcess) Cleanup() error {
   357  	// TODO(kwalsh) close channel, maybe also kill process if still running?
   358  	os.RemoveAll(p.Tempdir)
   359  	return nil
   360  }