go-sandbox/deamon/container_init.go
2019-08-30 00:57:07 -07:00

286 lines
6.5 KiB
Go

package deamon
import (
"bytes"
"encoding/gob"
"fmt"
"io"
"os"
"syscall"
"github.com/criyle/go-sandbox/pkg/forkexec"
"github.com/criyle/go-sandbox/pkg/unixsocket"
"github.com/criyle/go-sandbox/types"
)
// ContainerInit is called for container init process
// it will check if pid == 1, otherwise it is noop
// ContainerInit will do infinite loop on socket commands,
// and exits when at socket close
func ContainerInit() (err error) {
// noop if self is not container init process
// Notice: docker init is also 1, additional check for args[1] == init
if os.Getpid() != 1 || len(os.Args) != 2 || os.Args[1] != initArg {
return nil
}
// exit process (with whole container) upon exit this function
defer func() {
if err != nil {
fmt.Fprintf(os.Stderr, "container_exit: %v", err)
os.Exit(1)
} else {
fmt.Fprintf(os.Stderr, "container_exit")
os.Exit(0)
}
}()
// new_master shared the socket at fd 3 (marked close_exec)
soc, err := unixsocket.NewSocket(3)
if err != nil {
return fmt.Errorf("container_init: faile to new socket(%v)", err)
}
for {
cmd, msg, err := recvCmd(soc)
if err != nil {
return fmt.Errorf("loop: %v", err)
}
if err := handleCmd(soc, cmd, msg); err != nil {
return fmt.Errorf("loop: failed to execute cmd(%v)", err)
}
}
}
func handleCmd(s *unixsocket.Socket, cmd *Cmd, msg *unixsocket.Msg) error {
switch cmd.Cmd {
case cmdPing:
return handlePing(s)
case cmdCopyIn:
return handleCopyIn(s, cmd, msg)
case cmdOpen:
return handleOpen(s, cmd)
case cmdDelete:
return handleDelete(s, cmd)
case cmdReset:
return handleReset(s)
case cmdExecve:
return handleExecve(s, cmd, msg)
}
return nil
}
func handlePing(s *unixsocket.Socket) error {
return sendReply(s, &Reply{}, nil)
}
func handleCopyIn(s *unixsocket.Socket, cmd *Cmd, msg *unixsocket.Msg) error {
if len(msg.Fds) == 0 {
return sendErrorReply(s, "copyin: did not receive fd")
}
inf := os.NewFile(uintptr(msg.Fds[0]), cmd.Path)
if inf == nil {
return sendErrorReply(s, "copyin: newfile failed %v", msg.Fds[0])
}
defer inf.Close()
outf, err := os.OpenFile(cmd.Path, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0666)
if err != nil {
return sendErrorReply(s, "copyin: open write file %v", err)
}
defer outf.Close()
_, err = io.Copy(outf, inf)
if err != nil {
return sendErrorReply(s, "copyin: io.copy %v", err)
}
return sendReply(s, &Reply{}, nil)
}
func handleOpen(s *unixsocket.Socket, cmd *Cmd) error {
outf, err := os.Open(cmd.Path)
if err != nil {
return sendErrorReply(s, "open: %v", err)
}
defer outf.Close()
msg := unixsocket.Msg{
Fds: []int{int(outf.Fd())},
}
return sendReply(s, &Reply{}, &msg)
}
func handleDelete(s *unixsocket.Socket, cmd *Cmd) error {
err := os.Remove(cmd.Path)
if err != nil {
return sendErrorReply(s, "delete: %v", err)
}
return sendReply(s, &Reply{}, nil)
}
func handleReset(s *unixsocket.Socket) error {
err := removeContents("/tmp")
if err != nil {
return sendErrorReply(s, "reset: /tmp %v", err)
}
err = removeContents("/w")
if err != nil {
return sendErrorReply(s, "reset: /w %v", err)
}
return sendReply(s, &Reply{}, nil)
}
func handleExecve(s *unixsocket.Socket, cmd *Cmd, msg *unixsocket.Msg) error {
var (
files []uintptr
execFile uintptr
)
if msg != nil {
files = intSliceToUintptr(msg.Fds)
}
// don't leak fds to child
closeOnExecFds(msg.Fds)
// release files after execve
defer closeFds(msg.Fds)
if cmd.FdExec && len(files) > 0 {
execFile = files[0]
files = files[1:]
}
syncFunc := func(pid int) error {
msg2 := unixsocket.Msg{
Cred: &syscall.Ucred{
Pid: int32(pid),
Uid: uint32(syscall.Getuid()),
Gid: uint32(syscall.Getgid()),
},
}
if err2 := sendReply(s, &Reply{}, &msg2); err2 != nil {
return fmt.Errorf("syncFunc: sendReply(%v)", err2)
}
cmd2, _, err2 := recvCmd(s)
if err2 != nil {
return fmt.Errorf("syncFunc: recvCmd(%v)", err2)
}
switch cmd2.Cmd {
case cmdKill:
return fmt.Errorf("syncFunc: recved kill")
}
return nil
}
r := forkexec.Runner{
Args: cmd.Argv,
Env: cmd.Envv,
ExecFile: execFile,
RLimits: cmd.RLmits,
Files: files,
WorkDir: "/w",
NoNewPrivs: true,
DropCaps: true,
SyncFunc: syncFunc,
}
pid, err := r.Start()
if err != nil {
return sendErrorReply(s, "execve: %v", err)
}
// done is to signal kill goroutine exits
done := make(chan struct{})
// waitDone is to signal kill goroutine to collect zombies
waitDone := make(chan struct{})
// recv kill
go func() {
// signal done
defer close(done)
// msg must be kill
recvCmd(s)
// kill all
syscall.Kill(-1, syscall.SIGKILL)
// make sure collect zombie does not consume the exit status
<-waitDone
// collect zombies
for {
_, err = syscall.Wait4(-1, nil, syscall.WNOHANG, nil)
if err != nil {
break
}
}
}()
// wait pid
var wstatus syscall.WaitStatus
loop:
for {
_, err = syscall.Wait4(pid, &wstatus, 0, nil)
// sync with kill goroutine
close(waitDone)
if err != nil {
sendErrorReply(s, "execve: wait4 %v", err)
break loop
}
switch {
case wstatus.Exited():
reply := Reply{ExitStatus: wstatus.ExitStatus()}
sendReply(s, &reply, nil)
break loop
case wstatus.Signaled():
var status types.Status
switch wstatus.Signal() {
// kill signal treats as TLE
case syscall.SIGXCPU, syscall.SIGKILL:
status = types.StatusTLE
case syscall.SIGXFSZ:
status = types.StatusOLE
case syscall.SIGSYS:
status = types.StatusBan
default:
status = types.StatusRE
}
reply := Reply{Status: status}
sendReply(s, &reply, nil)
break loop
}
}
// wait for kill msg and reply done for finish
<-done
return sendReply(s, &Reply{}, nil)
}
func recvCmd(s *unixsocket.Socket) (*Cmd, *unixsocket.Msg, error) {
var cmd Cmd
buffer := GetBuffer()
defer PutBuffer(buffer)
n, msg, err := s.RecvMsg(buffer)
if err != nil {
return nil, nil, fmt.Errorf("failed RecvMsg(%v)", err)
}
if err := gob.NewDecoder(bytes.NewReader(buffer[:n])).Decode(&cmd); err != nil {
return nil, nil, fmt.Errorf("failed to decode(%v)", err)
}
return &cmd, msg, nil
}
func sendReply(s *unixsocket.Socket, reply *Reply, msg *unixsocket.Msg) error {
var buffer bytes.Buffer
if err := gob.NewEncoder(&buffer).Encode(reply); err != nil {
return err
}
if err := s.SendMsg(buffer.Bytes(), msg); err != nil {
return err
}
return nil
}
// sendErrorReply sends error reply
func sendErrorReply(s *unixsocket.Socket, ft string, v ...interface{}) error {
reply := Reply{Error: fmt.Sprintf(ft, v...)}
return sendReply(s, &reply, nil)
}