1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203
|
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
}
|