package libcontainer import ( "encoding/json" "errors" "fmt" "io" "os" "strconv" "github.com/opencontainers/runc/libcontainer/utils" "github.com/sirupsen/logrus" ) type syncType string // Constants that are used for synchronisation between the parent and child // during container setup. They come in pairs (with procError being a generic // response which is followed by an &initError). // // [ child ] <-> [ parent ] // // procMountPlease --> [open(2) or open_tree(2) and configure mount] // Arg: configs.Mount // <-- procMountFd // file: mountfd // // procSeccomp --> [forward fd to listenerPath] // file: seccomp fd // --- no return synchronisation // // procHooks --> [run hooks] // <-- procHooksDone // // procReady --> [final setup] // <-- procRun // // procSeccomp --> [grab seccomp fd with pidfd_getfd()] // <-- procSeccompDone const ( procError syncType = "procError" procReady syncType = "procReady" procRun syncType = "procRun" procHooks syncType = "procHooks" procHooksDone syncType = "procHooksDone" procMountPlease syncType = "procMountPlease" procMountFd syncType = "procMountFd" procSeccomp syncType = "procSeccomp" procSeccompDone syncType = "procSeccompDone" ) type syncFlags int const ( syncFlagHasFd syncFlags = (1 << iota) ) type syncT struct { Type syncType `json:"type"` Flags syncFlags `json:"flags"` Arg *json.RawMessage `json:"arg,omitempty"` File *os.File `json:"-"` // passed oob through SCM_RIGHTS } func (s syncT) String() string { str := "type:" + string(s.Type) if s.Flags != 0 { str += " flags:0b" + strconv.FormatInt(int64(s.Flags), 2) } if s.Arg != nil { str += " arg:" + string(*s.Arg) } if s.File != nil { str += " file:" + s.File.Name() + " (fd:" + strconv.Itoa(int(s.File.Fd())) + ")" } return str } // initError is used to wrap errors for passing them via JSON, // as encoding/json can't unmarshal into error type. type initError struct { Message string `json:"message,omitempty"` } func (i initError) Error() string { return i.Message } func doWriteSync(pipe *syncSocket, sync syncT) error { sync.Flags &= ^syncFlagHasFd if sync.File != nil { sync.Flags |= syncFlagHasFd } logrus.Debugf("writing sync %s", sync) data, err := json.Marshal(sync) if err != nil { return fmt.Errorf("marshal sync %v: %w", sync.Type, err) } if _, err := pipe.WritePacket(data); err != nil { return fmt.Errorf("writing sync %v: %w", sync.Type, err) } if sync.Flags&syncFlagHasFd != 0 { logrus.Debugf("writing sync file %s", sync) if err := utils.SendFile(pipe.File(), sync.File); err != nil { return fmt.Errorf("sending file after sync %q: %w", sync.Type, err) } } return nil } func writeSync(pipe *syncSocket, sync syncType) error { return doWriteSync(pipe, syncT{Type: sync}) } func writeSyncArg(pipe *syncSocket, sync syncType, arg interface{}) error { argJSON, err := json.Marshal(arg) if err != nil { return fmt.Errorf("writing sync %v: marshal argument failed: %w", sync, err) } argJSONMsg := json.RawMessage(argJSON) return doWriteSync(pipe, syncT{Type: sync, Arg: &argJSONMsg}) } func doReadSync(pipe *syncSocket) (syncT, error) { var sync syncT logrus.Debugf("reading sync") packet, err := pipe.ReadPacket() if err != nil { if errors.Is(err, io.EOF) { logrus.Debugf("sync pipe closed") return sync, err } return sync, fmt.Errorf("reading from parent failed: %w", err) } if err := json.Unmarshal(packet, &sync); err != nil { return sync, fmt.Errorf("unmarshal sync from parent failed: %w", err) } logrus.Debugf("read sync %s", sync) if sync.Type == procError { var ierr initError if sync.Arg == nil { return sync, errors.New("procError missing error payload") } if err := json.Unmarshal(*sync.Arg, &ierr); err != nil { return sync, fmt.Errorf("unmarshal procError failed: %w", err) } return sync, &ierr } if sync.Flags&syncFlagHasFd != 0 { logrus.Debugf("reading sync file %s", sync) file, err := utils.RecvFile(pipe.File()) if err != nil { return sync, fmt.Errorf("receiving fd from sync %v failed: %w", sync.Type, err) } sync.File = file } return sync, nil } func readSyncFull(pipe *syncSocket, expected syncType) (syncT, error) { sync, err := doReadSync(pipe) if err != nil { return sync, err } if sync.Type != expected { return sync, fmt.Errorf("unexpected synchronisation flag: got %q, expected %q", sync.Type, expected) } return sync, nil } func readSync(pipe *syncSocket, expected syncType) error { sync, err := readSyncFull(pipe, expected) if err != nil { return err } if sync.Arg != nil { return fmt.Errorf("sync %v had unexpected argument passed: %q", expected, string(*sync.Arg)) } if sync.File != nil { _ = sync.File.Close() return fmt.Errorf("sync %v had unexpected file passed", sync.Type) } return nil } // parseSync runs the given callback function on each syncT received from the // child. It will return once io.EOF is returned from the given pipe. func parseSync(pipe *syncSocket, fn func(*syncT) error) error { for { sync, err := doReadSync(pipe) if err != nil { if errors.Is(err, io.EOF) { break } return err } if err := fn(&sync); err != nil { return err } } return nil }