github.com/opencontainers/runc@v1.2.0-rc.1.0.20240520010911-492dc558cdd6/libcontainer/sync.go (about)

     1  package libcontainer
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"os"
     9  	"strconv"
    10  
    11  	"github.com/opencontainers/runc/libcontainer/utils"
    12  
    13  	"github.com/sirupsen/logrus"
    14  )
    15  
    16  type syncType string
    17  
    18  // Constants that are used for synchronisation between the parent and child
    19  // during container setup. They come in pairs (with procError being a generic
    20  // response which is followed by an &initError).
    21  //
    22  //	     [  child  ] <-> [   parent   ]
    23  //
    24  //	procMountPlease      --> [open(2) or open_tree(2) and configure mount]
    25  //	  Arg: configs.Mount
    26  //	                     <-- procMountFd
    27  //	                           file: mountfd
    28  //
    29  //	procSeccomp         --> [forward fd to listenerPath]
    30  //	  file: seccomp fd
    31  //	                    --- no return synchronisation
    32  //
    33  //	procHooks --> [run hooks]
    34  //	          <-- procHooksDone
    35  //
    36  //	procReady --> [final setup]
    37  //	          <-- procRun
    38  //
    39  //	procSeccomp --> [grab seccomp fd with pidfd_getfd()]
    40  //	            <-- procSeccompDone
    41  const (
    42  	procError       syncType = "procError"
    43  	procReady       syncType = "procReady"
    44  	procRun         syncType = "procRun"
    45  	procHooks       syncType = "procHooks"
    46  	procHooksDone   syncType = "procHooksDone"
    47  	procMountPlease syncType = "procMountPlease"
    48  	procMountFd     syncType = "procMountFd"
    49  	procSeccomp     syncType = "procSeccomp"
    50  	procSeccompDone syncType = "procSeccompDone"
    51  )
    52  
    53  type syncFlags int
    54  
    55  const (
    56  	syncFlagHasFd syncFlags = (1 << iota)
    57  )
    58  
    59  type syncT struct {
    60  	Type  syncType         `json:"type"`
    61  	Flags syncFlags        `json:"flags"`
    62  	Arg   *json.RawMessage `json:"arg,omitempty"`
    63  	File  *os.File         `json:"-"` // passed oob through SCM_RIGHTS
    64  }
    65  
    66  func (s syncT) String() string {
    67  	str := "type:" + string(s.Type)
    68  	if s.Flags != 0 {
    69  		str += " flags:0b" + strconv.FormatInt(int64(s.Flags), 2)
    70  	}
    71  	if s.Arg != nil {
    72  		str += " arg:" + string(*s.Arg)
    73  	}
    74  	if s.File != nil {
    75  		str += " file:" + s.File.Name() + " (fd:" + strconv.Itoa(int(s.File.Fd())) + ")"
    76  	}
    77  	return str
    78  }
    79  
    80  // initError is used to wrap errors for passing them via JSON,
    81  // as encoding/json can't unmarshal into error type.
    82  type initError struct {
    83  	Message string `json:"message,omitempty"`
    84  }
    85  
    86  func (i initError) Error() string {
    87  	return i.Message
    88  }
    89  
    90  func doWriteSync(pipe *syncSocket, sync syncT) error {
    91  	sync.Flags &= ^syncFlagHasFd
    92  	if sync.File != nil {
    93  		sync.Flags |= syncFlagHasFd
    94  	}
    95  	logrus.Debugf("writing sync %s", sync)
    96  	data, err := json.Marshal(sync)
    97  	if err != nil {
    98  		return fmt.Errorf("marshal sync %v: %w", sync.Type, err)
    99  	}
   100  	if _, err := pipe.WritePacket(data); err != nil {
   101  		return fmt.Errorf("writing sync %v: %w", sync.Type, err)
   102  	}
   103  	if sync.Flags&syncFlagHasFd != 0 {
   104  		logrus.Debugf("writing sync file %s", sync)
   105  		if err := utils.SendFile(pipe.File(), sync.File); err != nil {
   106  			return fmt.Errorf("sending file after sync %q: %w", sync.Type, err)
   107  		}
   108  	}
   109  	return nil
   110  }
   111  
   112  func writeSync(pipe *syncSocket, sync syncType) error {
   113  	return doWriteSync(pipe, syncT{Type: sync})
   114  }
   115  
   116  func writeSyncArg(pipe *syncSocket, sync syncType, arg interface{}) error {
   117  	argJSON, err := json.Marshal(arg)
   118  	if err != nil {
   119  		return fmt.Errorf("writing sync %v: marshal argument failed: %w", sync, err)
   120  	}
   121  	argJSONMsg := json.RawMessage(argJSON)
   122  	return doWriteSync(pipe, syncT{Type: sync, Arg: &argJSONMsg})
   123  }
   124  
   125  func doReadSync(pipe *syncSocket) (syncT, error) {
   126  	var sync syncT
   127  	logrus.Debugf("reading sync")
   128  	packet, err := pipe.ReadPacket()
   129  	if err != nil {
   130  		if errors.Is(err, io.EOF) {
   131  			logrus.Debugf("sync pipe closed")
   132  			return sync, err
   133  		}
   134  		return sync, fmt.Errorf("reading from parent failed: %w", err)
   135  	}
   136  	if err := json.Unmarshal(packet, &sync); err != nil {
   137  		return sync, fmt.Errorf("unmarshal sync from parent failed: %w", err)
   138  	}
   139  	logrus.Debugf("read sync %s", sync)
   140  	if sync.Type == procError {
   141  		var ierr initError
   142  		if sync.Arg == nil {
   143  			return sync, errors.New("procError missing error payload")
   144  		}
   145  		if err := json.Unmarshal(*sync.Arg, &ierr); err != nil {
   146  			return sync, fmt.Errorf("unmarshal procError failed: %w", err)
   147  		}
   148  		return sync, &ierr
   149  	}
   150  	if sync.Flags&syncFlagHasFd != 0 {
   151  		logrus.Debugf("reading sync file %s", sync)
   152  		file, err := utils.RecvFile(pipe.File())
   153  		if err != nil {
   154  			return sync, fmt.Errorf("receiving fd from sync %v failed: %w", sync.Type, err)
   155  		}
   156  		sync.File = file
   157  	}
   158  	return sync, nil
   159  }
   160  
   161  func readSyncFull(pipe *syncSocket, expected syncType) (syncT, error) {
   162  	sync, err := doReadSync(pipe)
   163  	if err != nil {
   164  		return sync, err
   165  	}
   166  	if sync.Type != expected {
   167  		return sync, fmt.Errorf("unexpected synchronisation flag: got %q, expected %q", sync.Type, expected)
   168  	}
   169  	return sync, nil
   170  }
   171  
   172  func readSync(pipe *syncSocket, expected syncType) error {
   173  	sync, err := readSyncFull(pipe, expected)
   174  	if err != nil {
   175  		return err
   176  	}
   177  	if sync.Arg != nil {
   178  		return fmt.Errorf("sync %v had unexpected argument passed: %q", expected, string(*sync.Arg))
   179  	}
   180  	if sync.File != nil {
   181  		_ = sync.File.Close()
   182  		return fmt.Errorf("sync %v had unexpected file passed", sync.Type)
   183  	}
   184  	return nil
   185  }
   186  
   187  // parseSync runs the given callback function on each syncT received from the
   188  // child. It will return once io.EOF is returned from the given pipe.
   189  func parseSync(pipe *syncSocket, fn func(*syncT) error) error {
   190  	for {
   191  		sync, err := doReadSync(pipe)
   192  		if err != nil {
   193  			if errors.Is(err, io.EOF) {
   194  				break
   195  			}
   196  			return err
   197  		}
   198  		if err := fn(&sync); err != nil {
   199  			return err
   200  		}
   201  	}
   202  	return nil
   203  }