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 }