mirror of
https://github.com/opencontainers/runc.git
synced 2026-07-11 06:03:57 +08:00
libcontainer: sync: cleanup synchronisation code
This includes quite a few cleanups and improvements to the way we do synchronisation. The core behaviour is unchanged, but switching to embedding json.RawMessage into the synchronisation structure will allow us to do more complicated synchronisation operations in future patches. The file descriptor passing through the synchronisation system feature will be used as part of the idmapped-mount and bind-mount-source features when switching that code to use the new mount API outside of nsexec.c. Signed-off-by: Aleksa Sarai <cyphar@cyphar.com> Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
This commit is contained in:
committed by
Kir Kolyshkin
parent
c6e7b1a8ec
commit
f81ef1493d
@@ -98,7 +98,7 @@ func handleSingle(path string, noStdin bool) error {
|
|||||||
defer socket.Close()
|
defer socket.Close()
|
||||||
|
|
||||||
// Get the master file descriptor from runC.
|
// Get the master file descriptor from runC.
|
||||||
master, err := utils.RecvFd(socket)
|
master, err := utils.RecvFile(socket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -171,7 +171,7 @@ func handleNull(path string) error {
|
|||||||
defer socket.Close()
|
defer socket.Close()
|
||||||
|
|
||||||
// Get the master file descriptor from runC.
|
// Get the master file descriptor from runC.
|
||||||
master, err := utils.RecvFd(socket)
|
master, err := utils.RecvFile(socket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1185,7 +1185,7 @@ func (c *Container) criuNotifications(resp *criurpc.CriuResp, process *Process,
|
|||||||
defer master.Close()
|
defer master.Close()
|
||||||
|
|
||||||
// While we can access console.master, using the API is a good idea.
|
// While we can access console.master, using the API is a good idea.
|
||||||
if err := utils.SendFd(process.ConsoleSocket, master.Name(), master.Fd()); err != nil {
|
if err := utils.SendFile(process.ConsoleSocket, master); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case "status-ready":
|
case "status-ready":
|
||||||
|
|||||||
+23
-34
@@ -5,7 +5,6 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
@@ -121,11 +120,8 @@ func startInitialization() (retErr error) {
|
|||||||
defer func() {
|
defer func() {
|
||||||
// If this defer is ever called, this means initialization has failed.
|
// If this defer is ever called, this means initialization has failed.
|
||||||
// Send the error back to the parent process in the form of an initError.
|
// Send the error back to the parent process in the form of an initError.
|
||||||
if err := writeSync(pipe, procError); err != nil {
|
ierr := initError{Message: retErr.Error()}
|
||||||
fmt.Fprintln(os.Stderr, err)
|
if err := writeSyncArg(pipe, procError, ierr); err != nil {
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := utils.WriteJSON(pipe, &initError{Message: retErr.Error()}); err != nil {
|
|
||||||
fmt.Fprintln(os.Stderr, err)
|
fmt.Fprintln(os.Stderr, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -352,7 +348,6 @@ func setupConsole(socket *os.File, config *initConfig, mount bool) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// After we return from here, we don't need the console anymore.
|
// After we return from here, we don't need the console anymore.
|
||||||
defer pty.Close()
|
defer pty.Close()
|
||||||
|
|
||||||
@@ -374,9 +369,11 @@ func setupConsole(socket *os.File, config *initConfig, mount bool) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// While we can access console.master, using the API is a good idea.
|
// While we can access console.master, using the API is a good idea.
|
||||||
if err := utils.SendFd(socket, pty.Name(), pty.Fd()); err != nil {
|
if err := utils.SendRawFd(socket, pty.Name(), pty.Fd()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
runtime.KeepAlive(pty)
|
||||||
|
|
||||||
// Now, dup over all the things.
|
// Now, dup over all the things.
|
||||||
return dupStdio(slavePath)
|
return dupStdio(slavePath)
|
||||||
}
|
}
|
||||||
@@ -384,12 +381,11 @@ func setupConsole(socket *os.File, config *initConfig, mount bool) error {
|
|||||||
// syncParentReady sends to the given pipe a JSON payload which indicates that
|
// syncParentReady sends to the given pipe a JSON payload which indicates that
|
||||||
// the init is ready to Exec the child process. It then waits for the parent to
|
// the init is ready to Exec the child process. It then waits for the parent to
|
||||||
// indicate that it is cleared to Exec.
|
// indicate that it is cleared to Exec.
|
||||||
func syncParentReady(pipe io.ReadWriter) error {
|
func syncParentReady(pipe *os.File) error {
|
||||||
// Tell parent.
|
// Tell parent.
|
||||||
if err := writeSync(pipe, procReady); err != nil {
|
if err := writeSync(pipe, procReady); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for parent to give the all-clear.
|
// Wait for parent to give the all-clear.
|
||||||
return readSync(pipe, procRun)
|
return readSync(pipe, procRun)
|
||||||
}
|
}
|
||||||
@@ -397,44 +393,37 @@ func syncParentReady(pipe io.ReadWriter) error {
|
|||||||
// syncParentHooks sends to the given pipe a JSON payload which indicates that
|
// syncParentHooks sends to the given pipe a JSON payload which indicates that
|
||||||
// the parent should execute pre-start hooks. It then waits for the parent to
|
// the parent should execute pre-start hooks. It then waits for the parent to
|
||||||
// indicate that it is cleared to resume.
|
// indicate that it is cleared to resume.
|
||||||
func syncParentHooks(pipe io.ReadWriter) error {
|
func syncParentHooks(pipe *os.File) error {
|
||||||
// Tell parent.
|
// Tell parent.
|
||||||
if err := writeSync(pipe, procHooks); err != nil {
|
if err := writeSync(pipe, procHooks); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for parent to give the all-clear.
|
// Wait for parent to give the all-clear.
|
||||||
return readSync(pipe, procResume)
|
return readSync(pipe, procResume)
|
||||||
}
|
}
|
||||||
|
|
||||||
// syncParentSeccomp sends to the given pipe a JSON payload which
|
// syncParentSeccomp sends the fd associated with the seccomp file descriptor
|
||||||
// indicates that the parent should pick up the seccomp fd with pidfd_getfd()
|
// to the parent, and wait for the parent to do pidfd_getfd() to grab a copy.
|
||||||
// and send it to the seccomp agent over a unix socket. It then waits for
|
func syncParentSeccomp(pipe *os.File, seccompFd int) error {
|
||||||
// the parent to indicate that it is cleared to resume and closes the seccompFd.
|
|
||||||
// If the seccompFd is -1, there isn't anything to sync with the parent, so it
|
|
||||||
// returns no error.
|
|
||||||
func syncParentSeccomp(pipe io.ReadWriter, seccompFd int) error {
|
|
||||||
if seccompFd == -1 {
|
if seccompFd == -1 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
defer unix.Close(seccompFd)
|
||||||
|
|
||||||
// Tell parent.
|
// Tell parent to grab our fd.
|
||||||
if err := writeSyncWithFd(pipe, procSeccomp, seccompFd); err != nil {
|
//
|
||||||
unix.Close(seccompFd)
|
// Notably, we do not use writeSyncFile here because a container might have
|
||||||
|
// an SCMP_ACT_NOTIFY action on sendmsg(2) so we need to use the smallest
|
||||||
|
// possible number of system calls here because all of those syscalls
|
||||||
|
// cannot be used with SCMP_ACT_NOTIFY as a result (any syscall we use here
|
||||||
|
// before the parent gets the file descriptor would deadlock "runc init" if
|
||||||
|
// we allowed it for SCMP_ACT_NOTIFY). See seccomp.InitSeccomp() for more
|
||||||
|
// details.
|
||||||
|
if err := writeSyncArg(pipe, procSeccomp, seccompFd); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
// Wait for parent to tell us they've grabbed the seccompfd.
|
||||||
// Wait for parent to give the all-clear.
|
return readSync(pipe, procSeccompDone)
|
||||||
if err := readSync(pipe, procSeccompDone); err != nil {
|
|
||||||
unix.Close(seccompFd)
|
|
||||||
return fmt.Errorf("sync parent seccomp: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := unix.Close(seccompFd); err != nil {
|
|
||||||
return fmt.Errorf("close seccomp fd: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// setupUser changes the groups, gid, and uid for the user inside the container
|
// setupUser changes the groups, gid, and uid for the user inside the container
|
||||||
|
|||||||
@@ -277,9 +277,9 @@ func TestExecInTTY(t *testing.T) {
|
|||||||
|
|
||||||
done := make(chan (error))
|
done := make(chan (error))
|
||||||
go func() {
|
go func() {
|
||||||
f, err := utils.RecvFd(parent)
|
f, err := utils.RecvFile(parent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
done <- fmt.Errorf("RecvFd: %w", err)
|
done <- fmt.Errorf("RecvFile: %w", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c, err := console.ConsoleFromFile(f)
|
c, err := console.ConsoleFromFile(f)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -171,14 +172,27 @@ func (p *setnsProcess) start() (retErr error) {
|
|||||||
panic("unexpected procHooks in setns")
|
panic("unexpected procHooks in setns")
|
||||||
case procSeccomp:
|
case procSeccomp:
|
||||||
if p.config.Config.Seccomp.ListenerPath == "" {
|
if p.config.Config.Seccomp.ListenerPath == "" {
|
||||||
return errors.New("listenerPath is not set")
|
return errors.New("seccomp listenerPath is not set")
|
||||||
}
|
}
|
||||||
|
if sync.Arg == nil {
|
||||||
seccompFd, err := recvSeccompFd(uintptr(p.pid()), uintptr(sync.Fd))
|
return fmt.Errorf("sync %q is missing an argument", sync.Type)
|
||||||
|
}
|
||||||
|
var srcFd int
|
||||||
|
if err := json.Unmarshal(*sync.Arg, &srcFd); err != nil {
|
||||||
|
return fmt.Errorf("sync %q passed invalid fd arg: %w", sync.Type, err)
|
||||||
|
}
|
||||||
|
seccompFd, err := pidGetFd(p.pid(), srcFd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
return fmt.Errorf("sync %q get fd %d from child failed: %w", sync.Type, srcFd, err)
|
||||||
|
}
|
||||||
|
defer seccompFd.Close()
|
||||||
|
// We have a copy, the child can keep working. We don't need to
|
||||||
|
// wait for the seccomp notify listener to get the fd before we
|
||||||
|
// permit the child to continue because the child will happily wait
|
||||||
|
// for the listener if it hits SCMP_ACT_NOTIFY.
|
||||||
|
if err := writeSync(p.messageSockPair.parent, procSeccompDone); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer unix.Close(seccompFd)
|
|
||||||
|
|
||||||
bundle, annotations := utils.Annotations(p.config.Config.Labels)
|
bundle, annotations := utils.Annotations(p.config.Config.Labels)
|
||||||
containerProcessState := &specs.ContainerProcessState{
|
containerProcessState := &specs.ContainerProcessState{
|
||||||
@@ -199,15 +213,10 @@ func (p *setnsProcess) start() (retErr error) {
|
|||||||
containerProcessState, seccompFd); err != nil {
|
containerProcessState, seccompFd); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sync with child.
|
|
||||||
if err := writeSync(p.messageSockPair.parent, procSeccompDone); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
default:
|
default:
|
||||||
return errors.New("invalid JSON payload from child")
|
return errors.New("invalid JSON payload from child")
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
if err := unix.Shutdown(int(p.messageSockPair.parent.Fd()), unix.SHUT_WR); err != nil {
|
if err := unix.Shutdown(int(p.messageSockPair.parent.Fd()), unix.SHUT_WR); err != nil {
|
||||||
@@ -457,14 +466,27 @@ func (p *initProcess) start() (retErr error) {
|
|||||||
switch sync.Type {
|
switch sync.Type {
|
||||||
case procSeccomp:
|
case procSeccomp:
|
||||||
if p.config.Config.Seccomp.ListenerPath == "" {
|
if p.config.Config.Seccomp.ListenerPath == "" {
|
||||||
return errors.New("listenerPath is not set")
|
return errors.New("seccomp listenerPath is not set")
|
||||||
}
|
}
|
||||||
|
var srcFd int
|
||||||
seccompFd, err := recvSeccompFd(uintptr(childPid), uintptr(sync.Fd))
|
if sync.Arg == nil {
|
||||||
|
return fmt.Errorf("sync %q is missing an argument", sync.Type)
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(*sync.Arg, &srcFd); err != nil {
|
||||||
|
return fmt.Errorf("sync %q passed invalid fd arg: %w", sync.Type, err)
|
||||||
|
}
|
||||||
|
seccompFd, err := pidGetFd(p.pid(), srcFd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
return fmt.Errorf("sync %q get fd %d from child failed: %w", sync.Type, srcFd, err)
|
||||||
|
}
|
||||||
|
defer seccompFd.Close()
|
||||||
|
// We have a copy, the child can keep working. We don't need to
|
||||||
|
// wait for the seccomp notify listener to get the fd before we
|
||||||
|
// permit the child to continue because the child will happily wait
|
||||||
|
// for the listener if it hits SCMP_ACT_NOTIFY.
|
||||||
|
if err := writeSync(p.messageSockPair.parent, procSeccompDone); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer unix.Close(seccompFd)
|
|
||||||
|
|
||||||
s, err := p.container.currentOCIState()
|
s, err := p.container.currentOCIState()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -485,11 +507,6 @@ func (p *initProcess) start() (retErr error) {
|
|||||||
containerProcessState, seccompFd); err != nil {
|
containerProcessState, seccompFd); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sync with child.
|
|
||||||
if err := writeSync(p.messageSockPair.parent, procSeccompDone); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case procReady:
|
case procReady:
|
||||||
seenProcReady = true
|
seenProcReady = true
|
||||||
// set rlimits, this has to be done here because we lose permissions
|
// set rlimits, this has to be done here because we lose permissions
|
||||||
@@ -587,7 +604,6 @@ func (p *initProcess) start() (retErr error) {
|
|||||||
default:
|
default:
|
||||||
return errors.New("invalid JSON payload from child")
|
return errors.New("invalid JSON payload from child")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -674,22 +690,20 @@ func (p *initProcess) forwardChildLogs() chan error {
|
|||||||
return logs.ForwardLogs(p.logFilePair.parent)
|
return logs.ForwardLogs(p.logFilePair.parent)
|
||||||
}
|
}
|
||||||
|
|
||||||
func recvSeccompFd(childPid, childFd uintptr) (int, error) {
|
func pidGetFd(pid, srcFd int) (*os.File, error) {
|
||||||
pidfd, _, errno := unix.Syscall(unix.SYS_PIDFD_OPEN, childPid, 0, 0)
|
pidFd, err := unix.PidfdOpen(pid, 0)
|
||||||
if errno != 0 {
|
if err != nil {
|
||||||
return -1, fmt.Errorf("performing SYS_PIDFD_OPEN syscall: %w", errno)
|
return nil, os.NewSyscallError("pidfd_open", err)
|
||||||
}
|
}
|
||||||
defer unix.Close(int(pidfd))
|
defer unix.Close(pidFd)
|
||||||
|
fd, err := unix.PidfdGetfd(pidFd, srcFd, 0)
|
||||||
seccompFd, _, errno := unix.Syscall(unix.SYS_PIDFD_GETFD, pidfd, childFd, 0)
|
if err != nil {
|
||||||
if errno != 0 {
|
return nil, os.NewSyscallError("pidfd_getfd", err)
|
||||||
return -1, fmt.Errorf("performing SYS_PIDFD_GETFD syscall: %w", errno)
|
|
||||||
}
|
}
|
||||||
|
return os.NewFile(uintptr(fd), "[pidfd_getfd]"), nil
|
||||||
return int(seccompFd), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendContainerProcessState(listenerPath string, state *specs.ContainerProcessState, fd int) error {
|
func sendContainerProcessState(listenerPath string, state *specs.ContainerProcessState, file *os.File) error {
|
||||||
conn, err := net.Dial("unix", listenerPath)
|
conn, err := net.Dial("unix", listenerPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to connect with seccomp agent specified in the seccomp profile: %w", err)
|
return fmt.Errorf("failed to connect with seccomp agent specified in the seccomp profile: %w", err)
|
||||||
@@ -706,11 +720,10 @@ func sendContainerProcessState(listenerPath string, state *specs.ContainerProces
|
|||||||
return fmt.Errorf("cannot marshall seccomp state: %w", err)
|
return fmt.Errorf("cannot marshall seccomp state: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = utils.SendFds(socket, b, fd)
|
if err := utils.SendRawFd(socket, string(b), file.Fd()); err != nil {
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("cannot send seccomp fd to %s: %w", listenerPath, err)
|
return fmt.Errorf("cannot send seccomp fd to %s: %w", listenerPath, err)
|
||||||
}
|
}
|
||||||
|
runtime.KeepAlive(file)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package libcontainer
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -64,7 +63,7 @@ func needsSetupDev(config *configs.Config) bool {
|
|||||||
// prepareRootfs sets up the devices, mount points, and filesystems for use
|
// prepareRootfs sets up the devices, mount points, and filesystems for use
|
||||||
// inside a new mount namespace. It doesn't set anything as ro. You must call
|
// inside a new mount namespace. It doesn't set anything as ro. You must call
|
||||||
// finalizeRootfs after this function to finish setting up the rootfs.
|
// finalizeRootfs after this function to finish setting up the rootfs.
|
||||||
func prepareRootfs(pipe io.ReadWriter, iConfig *initConfig, mountFds mountFds) (err error) {
|
func prepareRootfs(pipe *os.File, iConfig *initConfig, mountFds mountFds) (err error) {
|
||||||
config := iConfig.Config
|
config := iConfig.Config
|
||||||
if err := prepareRoot(config); err != nil {
|
if err := prepareRoot(config); err != nil {
|
||||||
return fmt.Errorf("error preparing rootfs: %w", err)
|
return fmt.Errorf("error preparing rootfs: %w", err)
|
||||||
|
|||||||
@@ -75,7 +75,6 @@ func (l *linuxSetnsInit) Init() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := syncParentSeccomp(l.pipe, seccompFd); err != nil {
|
if err := syncParentSeccomp(l.pipe, seccompFd); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -94,7 +93,6 @@ func (l *linuxSetnsInit) Init() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to init seccomp: %w", err)
|
return fmt.Errorf("unable to init seccomp: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := syncParentSeccomp(l.pipe, seccompFd); err != nil {
|
if err := syncParentSeccomp(l.pipe, seccompFd); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
+92
-53
@@ -5,6 +5,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/opencontainers/runc/libcontainer/utils"
|
"github.com/opencontainers/runc/libcontainer/utils"
|
||||||
)
|
)
|
||||||
@@ -15,15 +16,19 @@ type syncType string
|
|||||||
// during container setup. They come in pairs (with procError being a generic
|
// during container setup. They come in pairs (with procError being a generic
|
||||||
// response which is followed by an &initError).
|
// response which is followed by an &initError).
|
||||||
//
|
//
|
||||||
// [ child ] <-> [ parent ]
|
// [ child ] <-> [ parent ]
|
||||||
//
|
//
|
||||||
// procHooks --> [run hooks]
|
// procSeccomp --> [forward fd to listenerPath]
|
||||||
// <-- procResume
|
// file: seccomp fd
|
||||||
|
// --- no return synchronisation
|
||||||
//
|
//
|
||||||
// procReady --> [final setup]
|
// procHooks --> [run hooks]
|
||||||
// <-- procRun
|
// <-- procResume
|
||||||
//
|
//
|
||||||
// procSeccomp --> [pick up seccomp fd with pidfd_getfd()]
|
// procReady --> [final setup]
|
||||||
|
// <-- procRun
|
||||||
|
//
|
||||||
|
// procSeccomp --> [grab seccomp fd with pidfd_getfd()]
|
||||||
// <-- procSeccompDone
|
// <-- procSeccompDone
|
||||||
const (
|
const (
|
||||||
procError syncType = "procError"
|
procError syncType = "procError"
|
||||||
@@ -35,9 +40,17 @@ const (
|
|||||||
procSeccompDone syncType = "procSeccompDone"
|
procSeccompDone syncType = "procSeccompDone"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type syncFlags int
|
||||||
|
|
||||||
|
const (
|
||||||
|
syncFlagHasFd syncFlags = (1 << iota)
|
||||||
|
)
|
||||||
|
|
||||||
type syncT struct {
|
type syncT struct {
|
||||||
Type syncType `json:"type"`
|
Type syncType `json:"type"`
|
||||||
Fd int `json:"fd"`
|
Flags syncFlags `json:"flags"`
|
||||||
|
Arg *json.RawMessage `json:"arg,omitempty"`
|
||||||
|
File *os.File `json:"-"` // passed oob through SCM_RIGHTS
|
||||||
}
|
}
|
||||||
|
|
||||||
// initError is used to wrap errors for passing them via JSON,
|
// initError is used to wrap errors for passing them via JSON,
|
||||||
@@ -50,74 +63,100 @@ func (i initError) Error() string {
|
|||||||
return i.Message
|
return i.Message
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeSync is used to write to a synchronisation pipe. An error is returned
|
func doWriteSync(pipe *os.File, sync syncT) error {
|
||||||
// if there was a problem writing the payload.
|
sync.Flags &= ^syncFlagHasFd
|
||||||
func writeSync(pipe io.Writer, sync syncType) error {
|
if sync.File != nil {
|
||||||
return writeSyncWithFd(pipe, sync, -1)
|
sync.Flags |= syncFlagHasFd
|
||||||
}
|
}
|
||||||
|
if err := utils.WriteJSON(pipe, sync); err != nil {
|
||||||
// writeSyncWithFd is used to write to a synchronisation pipe. An error is
|
return fmt.Errorf("writing sync %q: %w", sync.Type, err)
|
||||||
// returned if there was a problem writing the payload.
|
}
|
||||||
func writeSyncWithFd(pipe io.Writer, sync syncType, fd int) error {
|
if sync.Flags&syncFlagHasFd != 0 {
|
||||||
if err := utils.WriteJSON(pipe, syncT{sync, fd}); err != nil {
|
if err := utils.SendFile(pipe, sync.File); err != nil {
|
||||||
return fmt.Errorf("writing syncT %q: %w", string(sync), err)
|
return fmt.Errorf("sending file after sync %q: %w", sync.Type, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// readSync is used to read from a synchronisation pipe. An error is returned
|
func writeSync(pipe *os.File, sync syncType) error {
|
||||||
// if we got an initError, the pipe was closed, or we got an unexpected flag.
|
return doWriteSync(pipe, syncT{Type: sync})
|
||||||
func readSync(pipe io.Reader, expected syncType) error {
|
}
|
||||||
var procSync syncT
|
|
||||||
if err := json.NewDecoder(pipe).Decode(&procSync); err != nil {
|
func writeSyncArg(pipe *os.File, sync syncType, arg interface{}) error {
|
||||||
|
argJSON, err := json.Marshal(arg)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("writing sync %q: marshal argument failed: %w", sync, err)
|
||||||
|
}
|
||||||
|
argJSONMsg := json.RawMessage(argJSON)
|
||||||
|
return doWriteSync(pipe, syncT{Type: sync, Arg: &argJSONMsg})
|
||||||
|
}
|
||||||
|
|
||||||
|
func doReadSync(pipe *os.File) (syncT, error) {
|
||||||
|
var sync syncT
|
||||||
|
if err := json.NewDecoder(pipe).Decode(&sync); err != nil {
|
||||||
if errors.Is(err, io.EOF) {
|
if errors.Is(err, io.EOF) {
|
||||||
return errors.New("parent closed synchronisation channel")
|
return sync, err
|
||||||
}
|
}
|
||||||
return fmt.Errorf("failed reading error from parent: %w", err)
|
return sync, fmt.Errorf("reading from parent failed: %w", err)
|
||||||
}
|
}
|
||||||
|
if sync.Type == procError {
|
||||||
if procSync.Type == procError {
|
|
||||||
var ierr initError
|
var ierr initError
|
||||||
|
if sync.Arg == nil {
|
||||||
if err := json.NewDecoder(pipe).Decode(&ierr); err != nil {
|
return sync, errors.New("procError missing error payload")
|
||||||
return fmt.Errorf("failed reading error from parent: %w", err)
|
|
||||||
}
|
}
|
||||||
|
if err := json.Unmarshal(*sync.Arg, &ierr); err != nil {
|
||||||
return &ierr
|
return sync, fmt.Errorf("unmarshal procError failed: %w", err)
|
||||||
|
}
|
||||||
|
return sync, &ierr
|
||||||
}
|
}
|
||||||
|
if sync.Flags&syncFlagHasFd != 0 {
|
||||||
|
file, err := utils.RecvFile(pipe)
|
||||||
|
if err != nil {
|
||||||
|
return sync, fmt.Errorf("receiving fd from sync %q failed: %w", sync.Type, err)
|
||||||
|
}
|
||||||
|
sync.File = file
|
||||||
|
}
|
||||||
|
return sync, nil
|
||||||
|
}
|
||||||
|
|
||||||
if procSync.Type != expected {
|
func readSyncFull(pipe *os.File, expected syncType) (syncT, error) {
|
||||||
return errors.New("invalid synchronisation flag from parent")
|
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 *os.File, expected syncType) error {
|
||||||
|
sync, err := readSyncFull(pipe, expected)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if sync.Arg != nil {
|
||||||
|
return fmt.Errorf("sync %q had unexpected argument passed: %q", expected, string(*sync.Arg))
|
||||||
|
}
|
||||||
|
if sync.File != nil {
|
||||||
|
_ = sync.File.Close()
|
||||||
|
return fmt.Errorf("sync %q had unexpected file passed", sync.Type)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseSync runs the given callback function on each syncT received from the
|
// 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.
|
// child. It will return once io.EOF is returned from the given pipe.
|
||||||
func parseSync(pipe io.Reader, fn func(*syncT) error) error {
|
func parseSync(pipe *os.File, fn func(*syncT) error) error {
|
||||||
dec := json.NewDecoder(pipe)
|
|
||||||
for {
|
for {
|
||||||
var sync syncT
|
sync, err := doReadSync(pipe)
|
||||||
if err := dec.Decode(&sync); err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, io.EOF) {
|
if errors.Is(err, io.EOF) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// We handle this case outside fn for cleanliness reasons.
|
|
||||||
var ierr *initError
|
|
||||||
if sync.Type == procError {
|
|
||||||
if err := dec.Decode(&ierr); err != nil && !errors.Is(err, io.EOF) {
|
|
||||||
return fmt.Errorf("error decoding proc error from init: %w", err)
|
|
||||||
}
|
|
||||||
if ierr != nil {
|
|
||||||
return ierr
|
|
||||||
}
|
|
||||||
// Programmer error.
|
|
||||||
panic("No error following JSON procError payload.")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := fn(&sync); err != nil {
|
if err := fn(&sync); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
+54
-31
@@ -19,13 +19,14 @@ package utils
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MaxNameLen is the maximum length of the name of a file descriptor being
|
// MaxNameLen is the maximum length of the name of a file descriptor being sent
|
||||||
// sent using SendFd. The name of the file handle returned by RecvFd will never
|
// using SendFile. The name of the file handle returned by RecvFile will never be
|
||||||
// be larger than this value.
|
// larger than this value.
|
||||||
const MaxNameLen = 4096
|
const MaxNameLen = 4096
|
||||||
|
|
||||||
// oobSpace is the size of the oob slice required to store a single FD. Note
|
// oobSpace is the size of the oob slice required to store a single FD. Note
|
||||||
@@ -33,26 +34,21 @@ const MaxNameLen = 4096
|
|||||||
// so sizeof(fd) = 4.
|
// so sizeof(fd) = 4.
|
||||||
var oobSpace = unix.CmsgSpace(4)
|
var oobSpace = unix.CmsgSpace(4)
|
||||||
|
|
||||||
// RecvFd waits for a file descriptor to be sent over the given AF_UNIX
|
// RecvFile waits for a file descriptor to be sent over the given AF_UNIX
|
||||||
// socket. The file name of the remote file descriptor will be recreated
|
// socket. The file name of the remote file descriptor will be recreated
|
||||||
// locally (it is sent as non-auxiliary data in the same payload).
|
// locally (it is sent as non-auxiliary data in the same payload).
|
||||||
func RecvFd(socket *os.File) (*os.File, error) {
|
func RecvFile(socket *os.File) (_ *os.File, Err error) {
|
||||||
// For some reason, unix.Recvmsg uses the length rather than the capacity
|
|
||||||
// when passing the msg_controllen and other attributes to recvmsg. So we
|
|
||||||
// have to actually set the length.
|
|
||||||
name := make([]byte, MaxNameLen)
|
name := make([]byte, MaxNameLen)
|
||||||
oob := make([]byte, oobSpace)
|
oob := make([]byte, oobSpace)
|
||||||
|
|
||||||
sockfd := socket.Fd()
|
sockfd := socket.Fd()
|
||||||
n, oobn, _, _, err := unix.Recvmsg(int(sockfd), name, oob, 0)
|
n, oobn, _, _, err := unix.Recvmsg(int(sockfd), name, oob, unix.MSG_CMSG_CLOEXEC)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if n >= MaxNameLen || oobn != oobSpace {
|
if n >= MaxNameLen || oobn != oobSpace {
|
||||||
return nil, fmt.Errorf("recvfd: incorrect number of bytes read (n=%d oobn=%d)", n, oobn)
|
return nil, fmt.Errorf("recvfile: incorrect number of bytes read (n=%d oobn=%d)", n, oobn)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Truncate.
|
// Truncate.
|
||||||
name = name[:n]
|
name = name[:n]
|
||||||
oob = oob[:oobn]
|
oob = oob[:oobn]
|
||||||
@@ -61,36 +57,63 @@ func RecvFd(socket *os.File) (*os.File, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We cannot control how many SCM_RIGHTS we receive, and upon receiving
|
||||||
|
// them all of the descriptors are installed in our fd table, so we need to
|
||||||
|
// parse all of the SCM_RIGHTS we received in order to close all of the
|
||||||
|
// descriptors on error.
|
||||||
|
var fds []int
|
||||||
|
defer func() {
|
||||||
|
for i, fd := range fds {
|
||||||
|
if i == 0 && Err == nil {
|
||||||
|
// Only close the first one on error.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Always close extra ones.
|
||||||
|
_ = unix.Close(fd)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
var lastErr error
|
||||||
|
for _, scm := range scms {
|
||||||
|
if scm.Header.Type == unix.SCM_RIGHTS {
|
||||||
|
scmFds, err := unix.ParseUnixRights(&scm)
|
||||||
|
if err != nil {
|
||||||
|
lastErr = err
|
||||||
|
} else {
|
||||||
|
fds = append(fds, scmFds...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if lastErr != nil {
|
||||||
|
return nil, lastErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// We do this after collecting the fds to make sure we close them all when
|
||||||
|
// returning an error here.
|
||||||
if len(scms) != 1 {
|
if len(scms) != 1 {
|
||||||
return nil, fmt.Errorf("recvfd: number of SCMs is not 1: %d", len(scms))
|
return nil, fmt.Errorf("recvfd: number of SCMs is not 1: %d", len(scms))
|
||||||
}
|
}
|
||||||
scm := scms[0]
|
|
||||||
|
|
||||||
fds, err := unix.ParseUnixRights(&scm)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(fds) != 1 {
|
if len(fds) != 1 {
|
||||||
return nil, fmt.Errorf("recvfd: number of fds is not 1: %d", len(fds))
|
return nil, fmt.Errorf("recvfd: number of fds is not 1: %d", len(fds))
|
||||||
}
|
}
|
||||||
fd := uintptr(fds[0])
|
return os.NewFile(uintptr(fds[0]), string(name)), nil
|
||||||
|
|
||||||
return os.NewFile(fd, string(name)), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendFd sends a file descriptor over the given AF_UNIX socket. In
|
// SendFile sends a file over the given AF_UNIX socket. file.Name() is also
|
||||||
// addition, the file.Name() of the given file will also be sent as
|
// included so that if the other end uses RecvFile, the file will have the same
|
||||||
// non-auxiliary data in the same payload (allowing to send contextual
|
// name information.
|
||||||
// information for a file descriptor).
|
func SendFile(socket *os.File, file *os.File) error {
|
||||||
func SendFd(socket *os.File, name string, fd uintptr) error {
|
name := file.Name()
|
||||||
if len(name) >= MaxNameLen {
|
if len(name) >= MaxNameLen {
|
||||||
return fmt.Errorf("sendfd: filename too long: %s", name)
|
return fmt.Errorf("sendfd: filename too long: %s", name)
|
||||||
}
|
}
|
||||||
return SendFds(socket, []byte(name), int(fd))
|
err := SendRawFd(socket, name, file.Fd())
|
||||||
|
runtime.KeepAlive(file)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendFds sends a list of files descriptor and msg over the given AF_UNIX socket.
|
// SendRawFd sends a specific file descriptor over the given AF_UNIX socket.
|
||||||
func SendFds(socket *os.File, msg []byte, fds ...int) error {
|
func SendRawFd(socket *os.File, msg string, fd uintptr) error {
|
||||||
oob := unix.UnixRights(fds...)
|
oob := unix.UnixRights(int(fd))
|
||||||
return unix.Sendmsg(int(socket.Fd()), msg, oob, nil, 0)
|
return unix.Sendmsg(int(socket.Fd()), []byte(msg), oob, nil, 0)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ func CloseExecFrom(minFd int) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewSockPair returns a new unix socket pair
|
// NewSockPair returns a new unix socket pair
|
||||||
func NewSockPair(name string) (parent *os.File, child *os.File, err error) {
|
func NewSockPair(name string) (parent, child *os.File, err error) {
|
||||||
fds, err := unix.Socketpair(unix.AF_LOCAL, unix.SOCK_STREAM|unix.SOCK_CLOEXEC, 0)
|
fds, err := unix.Socketpair(unix.AF_LOCAL, unix.SOCK_STREAM|unix.SOCK_CLOEXEC, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ func (t *tty) initHostConsole() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *tty) recvtty(socket *os.File) (Err error) {
|
func (t *tty) recvtty(socket *os.File) (Err error) {
|
||||||
f, err := utils.RecvFd(socket)
|
f, err := utils.RecvFile(socket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user