mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Merge pull request #8634 from blechschmidt:passfd
PiperOrigin-RevId: 516362077
This commit is contained in:
+61
-10
@@ -18,6 +18,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
@@ -44,6 +45,41 @@ type Proc struct {
|
|||||||
Kernel *kernel.Kernel
|
Kernel *kernel.Kernel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FilePayload aids to ensure that len(urpc.FilePayload.Files) == len(GuestFDs)
|
||||||
|
// when instantiated through the NewFDMap helper method.
|
||||||
|
type FilePayload struct {
|
||||||
|
// FilePayload is the file payload that is transferred via RPC.
|
||||||
|
urpc.FilePayload
|
||||||
|
|
||||||
|
// GuestFDs are the file descriptors in the file descriptor map of the
|
||||||
|
// executed application. They correspond 1:1 to the files in the
|
||||||
|
// urpc.FilePayload.
|
||||||
|
GuestFDs []int
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFDMap returns a FilePayload that maps file descriptors to files inside
|
||||||
|
// the executed process.
|
||||||
|
func NewFDMap(fdMap map[int]*os.File) FilePayload {
|
||||||
|
files := make([]*os.File, 0, len(fdMap))
|
||||||
|
|
||||||
|
// Make the map iteration order deterministic for the sake of testing.
|
||||||
|
// Otherwise, the order is randomized and tests relying on the comparison
|
||||||
|
// of equality will fail.
|
||||||
|
guestFDs := make([]int, 0, len(fdMap))
|
||||||
|
for key := range fdMap {
|
||||||
|
guestFDs = append(guestFDs, key)
|
||||||
|
}
|
||||||
|
sort.Ints(guestFDs)
|
||||||
|
|
||||||
|
for _, guestFD := range guestFDs {
|
||||||
|
files = append(files, fdMap[guestFD])
|
||||||
|
}
|
||||||
|
return FilePayload{
|
||||||
|
FilePayload: urpc.FilePayload{Files: files},
|
||||||
|
GuestFDs: guestFDs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ExecArgs is the set of arguments to exec.
|
// ExecArgs is the set of arguments to exec.
|
||||||
type ExecArgs struct {
|
type ExecArgs struct {
|
||||||
// Filename is the filename to load.
|
// Filename is the filename to load.
|
||||||
@@ -84,7 +120,7 @@ type ExecArgs struct {
|
|||||||
StdioIsPty bool
|
StdioIsPty bool
|
||||||
|
|
||||||
// FilePayload determines the files to give to the new process.
|
// FilePayload determines the files to give to the new process.
|
||||||
urpc.FilePayload
|
FilePayload
|
||||||
|
|
||||||
// ContainerID is the container for the process being executed.
|
// ContainerID is the container for the process being executed.
|
||||||
ContainerID string
|
ContainerID string
|
||||||
@@ -97,7 +133,7 @@ type ExecArgs struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// String prints the arguments as a string.
|
// String prints the arguments as a string.
|
||||||
func (args ExecArgs) String() string {
|
func (args *ExecArgs) String() string {
|
||||||
if len(args.Argv) == 0 {
|
if len(args.Argv) == 0 {
|
||||||
return args.Filename
|
return args.Filename
|
||||||
}
|
}
|
||||||
@@ -189,19 +225,15 @@ func (proc *Proc) execAsync(args *ExecArgs) (*kernel.ThreadGroup, kernel.ThreadI
|
|||||||
}
|
}
|
||||||
initArgs.Filename = resolved
|
initArgs.Filename = resolved
|
||||||
|
|
||||||
fds, err := fd.NewFromFiles(args.Files)
|
fdMap, err := args.createFDMap()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, nil, fmt.Errorf("duplicating payload files: %w", err)
|
return nil, 0, nil, fmt.Errorf("creating fd map: %w", err)
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
for _, fd := range fds {
|
for _, hostFD := range fdMap {
|
||||||
_ = fd.Close()
|
_ = hostFD.Close()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
fdMap := make(map[int]*fd.FD, len(fds))
|
|
||||||
for appFD, hostFD := range fds {
|
|
||||||
fdMap[appFD] = hostFD
|
|
||||||
}
|
|
||||||
ttyFile, err := fdimport.Import(ctx, fdTable, args.StdioIsPty, args.KUID, args.KGID, fdMap)
|
ttyFile, err := fdimport.Import(ctx, fdTable, args.StdioIsPty, args.KUID, args.KGID, fdMap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, nil, err
|
return nil, 0, nil, err
|
||||||
@@ -404,3 +436,22 @@ func ContainerUsage(kr *kernel.Kernel) map[string]uint64 {
|
|||||||
}
|
}
|
||||||
return cusage
|
return cusage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// createFDMap creates the file descriptor map from the unmarshalled ExecArgs.
|
||||||
|
func (args *ExecArgs) createFDMap() (map[int]*fd.FD, error) {
|
||||||
|
if len(args.Files) != len(args.GuestFDs) {
|
||||||
|
return nil, fmt.Errorf("length of payload files does not match length of file descriptor array")
|
||||||
|
}
|
||||||
|
fdMap := make(map[int]*fd.FD, len(args.Files))
|
||||||
|
for i, file := range args.Files {
|
||||||
|
var appFD int
|
||||||
|
// GuestFDs are the indexes of our FD map.
|
||||||
|
appFD = args.GuestFDs[i]
|
||||||
|
hostFD, err := fd.NewFromFile(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("duplicating payload files: %w", err)
|
||||||
|
}
|
||||||
|
fdMap[appFD] = hostFD
|
||||||
|
}
|
||||||
|
return fdMap, nil
|
||||||
|
}
|
||||||
|
|||||||
+37
-2
@@ -101,6 +101,9 @@ type containerInfo struct {
|
|||||||
// stdioFDs contains stdin, stdout, and stderr.
|
// stdioFDs contains stdin, stdout, and stderr.
|
||||||
stdioFDs []*fd.FD
|
stdioFDs []*fd.FD
|
||||||
|
|
||||||
|
// passFDs are mappings of user-supplied host to guest file descriptors.
|
||||||
|
passFDs []fdMapping
|
||||||
|
|
||||||
// goferFDs are the FDs that attach the sandbox to the gofers.
|
// goferFDs are the FDs that attach the sandbox to the gofers.
|
||||||
goferFDs []*fd.FD
|
goferFDs []*fd.FD
|
||||||
|
|
||||||
@@ -186,6 +189,21 @@ type execProcess struct {
|
|||||||
hostTTY *fd.FD
|
hostTTY *fd.FD
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fdMapping maps guest to host file descriptors. Guest file descriptors are
|
||||||
|
// exposed to the application inside the sandbox through the FD table.
|
||||||
|
type fdMapping struct {
|
||||||
|
guest int
|
||||||
|
host *fd.FD
|
||||||
|
}
|
||||||
|
|
||||||
|
// FDMapping is a helper type to represent a mapping from guest to host file
|
||||||
|
// descriptors. In contrast to the unexported fdMapping type, it does not imply
|
||||||
|
// file ownership.
|
||||||
|
type FDMapping struct {
|
||||||
|
Guest int
|
||||||
|
Host int
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// Initialize the random number generator.
|
// Initialize the random number generator.
|
||||||
mrand.Seed(gtime.Now().UnixNano())
|
mrand.Seed(gtime.Now().UnixNano())
|
||||||
@@ -211,6 +229,9 @@ type Args struct {
|
|||||||
// StdioFDs is the stdio for the application. The Loader takes ownership of
|
// StdioFDs is the stdio for the application. The Loader takes ownership of
|
||||||
// these FDs and may close them at any time.
|
// these FDs and may close them at any time.
|
||||||
StdioFDs []int
|
StdioFDs []int
|
||||||
|
// PassFDs are user-supplied FD mappings from host to guest descriptors.
|
||||||
|
// The Loader takes ownership of these FDs and may close them at any time.
|
||||||
|
PassFDs []FDMapping
|
||||||
// OverlayFilestoreFDs are the FDs to the regular files that will back the
|
// OverlayFilestoreFDs are the FDs to the regular files that will back the
|
||||||
// tmpfs upper mount in the overlay mounts.
|
// tmpfs upper mount in the overlay mounts.
|
||||||
OverlayFilestoreFDs []int
|
OverlayFilestoreFDs []int
|
||||||
@@ -287,6 +308,12 @@ func New(args Args) (*Loader, error) {
|
|||||||
for _, overlayFD := range args.OverlayFilestoreFDs {
|
for _, overlayFD := range args.OverlayFilestoreFDs {
|
||||||
info.overlayFilestoreFDs = append(info.overlayFilestoreFDs, fd.New(overlayFD))
|
info.overlayFilestoreFDs = append(info.overlayFilestoreFDs, fd.New(overlayFD))
|
||||||
}
|
}
|
||||||
|
for _, customFD := range args.PassFDs {
|
||||||
|
info.passFDs = append(info.passFDs, fdMapping{
|
||||||
|
host: fd.New(customFD.Host),
|
||||||
|
guest: customFD.Guest,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Create kernel and platform.
|
// Create kernel and platform.
|
||||||
p, err := createPlatform(args.Conf, args.Device)
|
p, err := createPlatform(args.Conf, args.Device)
|
||||||
@@ -529,6 +556,9 @@ func (l *Loader) Destroy() {
|
|||||||
for _, f := range l.root.stdioFDs {
|
for _, f := range l.root.stdioFDs {
|
||||||
_ = f.Close()
|
_ = f.Close()
|
||||||
}
|
}
|
||||||
|
for _, f := range l.root.passFDs {
|
||||||
|
_ = f.host.Close()
|
||||||
|
}
|
||||||
for _, f := range l.root.goferFDs {
|
for _, f := range l.root.goferFDs {
|
||||||
_ = f.Close()
|
_ = f.Close()
|
||||||
}
|
}
|
||||||
@@ -825,7 +855,7 @@ func (l *Loader) startSubcontainer(spec *specs.Spec, conf *config.Config, cid st
|
|||||||
func (l *Loader) createContainerProcess(root bool, cid string, info *containerInfo) (*kernel.ThreadGroup, *host.TTYFileDescription, error) {
|
func (l *Loader) createContainerProcess(root bool, cid string, info *containerInfo) (*kernel.ThreadGroup, *host.TTYFileDescription, error) {
|
||||||
// Create the FD map, which will set stdin, stdout, and stderr.
|
// Create the FD map, which will set stdin, stdout, and stderr.
|
||||||
ctx := info.procArgs.NewContext(l.k)
|
ctx := info.procArgs.NewContext(l.k)
|
||||||
fdTable, ttyFile, err := createFDTable(ctx, info.spec.Process.Terminal, info.stdioFDs, info.spec.Process.User)
|
fdTable, ttyFile, err := createFDTable(ctx, info.spec.Process.Terminal, info.stdioFDs, info.passFDs, info.spec.Process.User)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("importing fds: %w", err)
|
return nil, nil, fmt.Errorf("importing fds: %w", err)
|
||||||
}
|
}
|
||||||
@@ -1391,7 +1421,7 @@ func (l *Loader) ttyFromIDLocked(key execID) (*host.TTYFileDescription, error) {
|
|||||||
return ep.tty, nil
|
return ep.tty, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createFDTable(ctx context.Context, console bool, stdioFDs []*fd.FD, user specs.User) (*kernel.FDTable, *host.TTYFileDescription, error) {
|
func createFDTable(ctx context.Context, console bool, stdioFDs []*fd.FD, passFDs []fdMapping, user specs.User) (*kernel.FDTable, *host.TTYFileDescription, error) {
|
||||||
if len(stdioFDs) != 3 {
|
if len(stdioFDs) != 3 {
|
||||||
return nil, nil, fmt.Errorf("stdioFDs should contain exactly 3 FDs (stdin, stdout, and stderr), but %d FDs received", len(stdioFDs))
|
return nil, nil, fmt.Errorf("stdioFDs should contain exactly 3 FDs (stdin, stdout, and stderr), but %d FDs received", len(stdioFDs))
|
||||||
}
|
}
|
||||||
@@ -1401,6 +1431,11 @@ func createFDTable(ctx context.Context, console bool, stdioFDs []*fd.FD, user sp
|
|||||||
2: stdioFDs[2],
|
2: stdioFDs[2],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create the entries for the host files that were passed to our app.
|
||||||
|
for _, customFD := range passFDs {
|
||||||
|
fdMap[customFD.guest] = customFD.host
|
||||||
|
}
|
||||||
|
|
||||||
k := kernel.KernelFromContext(ctx)
|
k := kernel.KernelFromContext(ctx)
|
||||||
fdTable := k.NewFDTable()
|
fdTable := k.NewFDTable()
|
||||||
ttyFile, err := fdimport.Import(ctx, fdTable, console, auth.KUID(user.UID), auth.KGID(user.GID), fdMap)
|
ttyFile, err := fdimport.Import(ctx, fdTable, console, auth.KUID(user.UID), auth.KGID(user.GID), fdMap)
|
||||||
|
|||||||
+1
-2
@@ -19,6 +19,7 @@ go_library(
|
|||||||
"do.go",
|
"do.go",
|
||||||
"events.go",
|
"events.go",
|
||||||
"exec.go",
|
"exec.go",
|
||||||
|
"fd_mapping.go",
|
||||||
"gofer.go",
|
"gofer.go",
|
||||||
"help.go",
|
"help.go",
|
||||||
"install.go",
|
"install.go",
|
||||||
@@ -69,7 +70,6 @@ go_library(
|
|||||||
"//pkg/state/statefile",
|
"//pkg/state/statefile",
|
||||||
"//pkg/sync",
|
"//pkg/sync",
|
||||||
"//pkg/unet",
|
"//pkg/unet",
|
||||||
"//pkg/urpc",
|
|
||||||
"//runsc/boot",
|
"//runsc/boot",
|
||||||
"//runsc/cmd/util",
|
"//runsc/cmd/util",
|
||||||
"//runsc/config",
|
"//runsc/config",
|
||||||
@@ -112,7 +112,6 @@ go_test(
|
|||||||
"//pkg/sentry/control",
|
"//pkg/sentry/control",
|
||||||
"//pkg/sentry/kernel/auth",
|
"//pkg/sentry/kernel/auth",
|
||||||
"//pkg/test/testutil",
|
"//pkg/test/testutil",
|
||||||
"//pkg/urpc",
|
|
||||||
"//runsc/cmd/util",
|
"//runsc/cmd/util",
|
||||||
"//runsc/config",
|
"//runsc/config",
|
||||||
"//runsc/container",
|
"//runsc/container",
|
||||||
|
|||||||
@@ -87,6 +87,9 @@ type Boot struct {
|
|||||||
// provided in that order.
|
// provided in that order.
|
||||||
stdioFDs intFlags
|
stdioFDs intFlags
|
||||||
|
|
||||||
|
// passFDs are mappings of user-supplied host to guest file descriptors.
|
||||||
|
passFDs fdMappings
|
||||||
|
|
||||||
// applyCaps determines if capabilities defined in the spec should be applied
|
// applyCaps determines if capabilities defined in the spec should be applied
|
||||||
// to the process.
|
// to the process.
|
||||||
applyCaps bool
|
applyCaps bool
|
||||||
@@ -169,6 +172,7 @@ func (b *Boot) SetFlags(f *flag.FlagSet) {
|
|||||||
f.IntVar(&b.deviceFD, "device-fd", -1, "FD for the platform device file")
|
f.IntVar(&b.deviceFD, "device-fd", -1, "FD for the platform device file")
|
||||||
f.Var(&b.ioFDs, "io-fds", "list of FDs to connect gofer clients. They must follow this order: root first, then mounts as defined in the spec")
|
f.Var(&b.ioFDs, "io-fds", "list of FDs to connect gofer clients. They must follow this order: root first, then mounts as defined in the spec")
|
||||||
f.Var(&b.stdioFDs, "stdio-fds", "list of FDs containing sandbox stdin, stdout, and stderr in that order")
|
f.Var(&b.stdioFDs, "stdio-fds", "list of FDs containing sandbox stdin, stdout, and stderr in that order")
|
||||||
|
f.Var(&b.passFDs, "custom-fds", "mapping of host to guest FDs. They must be in M:N format. M is the host and N the guest descriptor.")
|
||||||
f.Var(&b.overlayFilestoreFDs, "overlay-filestore-fds", "FDs to the regular files that will back the tmpfs upper mount in the overlay mounts.")
|
f.Var(&b.overlayFilestoreFDs, "overlay-filestore-fds", "FDs to the regular files that will back the tmpfs upper mount in the overlay mounts.")
|
||||||
f.IntVar(&b.userLogFD, "user-log-fd", 0, "file descriptor to write user logs to. 0 means no logging.")
|
f.IntVar(&b.userLogFD, "user-log-fd", 0, "file descriptor to write user logs to. 0 means no logging.")
|
||||||
f.IntVar(&b.startSyncFD, "start-sync-fd", -1, "required FD to used to synchronize sandbox startup")
|
f.IntVar(&b.startSyncFD, "start-sync-fd", -1, "required FD to used to synchronize sandbox startup")
|
||||||
@@ -392,6 +396,7 @@ func (b *Boot) Execute(_ context.Context, f *flag.FlagSet, args ...any) subcomma
|
|||||||
Device: os.NewFile(uintptr(b.deviceFD), "platform device"),
|
Device: os.NewFile(uintptr(b.deviceFD), "platform device"),
|
||||||
GoferFDs: b.ioFDs.GetArray(),
|
GoferFDs: b.ioFDs.GetArray(),
|
||||||
StdioFDs: b.stdioFDs.GetArray(),
|
StdioFDs: b.stdioFDs.GetArray(),
|
||||||
|
PassFDs: b.passFDs.GetArray(),
|
||||||
OverlayFilestoreFDs: b.overlayFilestoreFDs.GetArray(),
|
OverlayFilestoreFDs: b.overlayFilestoreFDs.GetArray(),
|
||||||
NumCPU: b.cpuNum,
|
NumCPU: b.cpuNum,
|
||||||
TotalMem: b.totalMem,
|
TotalMem: b.totalMem,
|
||||||
|
|||||||
+44
-3
@@ -32,7 +32,6 @@ import (
|
|||||||
"gvisor.dev/gvisor/pkg/log"
|
"gvisor.dev/gvisor/pkg/log"
|
||||||
"gvisor.dev/gvisor/pkg/sentry/control"
|
"gvisor.dev/gvisor/pkg/sentry/control"
|
||||||
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
||||||
"gvisor.dev/gvisor/pkg/urpc"
|
|
||||||
"gvisor.dev/gvisor/runsc/cmd/util"
|
"gvisor.dev/gvisor/runsc/cmd/util"
|
||||||
"gvisor.dev/gvisor/runsc/config"
|
"gvisor.dev/gvisor/runsc/config"
|
||||||
"gvisor.dev/gvisor/runsc/console"
|
"gvisor.dev/gvisor/runsc/console"
|
||||||
@@ -58,6 +57,10 @@ type Exec struct {
|
|||||||
// file descriptor referencing the master end of the console's
|
// file descriptor referencing the master end of the console's
|
||||||
// pseudoterminal.
|
// pseudoterminal.
|
||||||
consoleSocket string
|
consoleSocket string
|
||||||
|
|
||||||
|
// passFDs are user-supplied FDs from the host to be exposed to the
|
||||||
|
// sandboxed app.
|
||||||
|
passFDs intFlags
|
||||||
}
|
}
|
||||||
|
|
||||||
// Name implements subcommands.Command.Name.
|
// Name implements subcommands.Command.Name.
|
||||||
@@ -101,6 +104,7 @@ func (ex *Exec) SetFlags(f *flag.FlagSet) {
|
|||||||
f.StringVar(&ex.pidFile, "pid-file", "", "filename that the container pid will be written to")
|
f.StringVar(&ex.pidFile, "pid-file", "", "filename that the container pid will be written to")
|
||||||
f.StringVar(&ex.internalPidFile, "internal-pid-file", "", "filename that the container-internal pid will be written to")
|
f.StringVar(&ex.internalPidFile, "internal-pid-file", "", "filename that the container-internal pid will be written to")
|
||||||
f.StringVar(&ex.consoleSocket, "console-socket", "", "path to an AF_UNIX socket which will receive a file descriptor referencing the master end of the console's pseudoterminal")
|
f.StringVar(&ex.consoleSocket, "console-socket", "", "path to an AF_UNIX socket which will receive a file descriptor referencing the master end of the console's pseudoterminal")
|
||||||
|
f.Var(&ex.passFDs, "pass-fd", "file descriptors passed to the container. Can be supplied multiple times.")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute implements subcommands.Command.Execute. It starts a process in an
|
// Execute implements subcommands.Command.Execute. It starts a process in an
|
||||||
@@ -140,6 +144,35 @@ func (ex *Exec) Execute(_ context.Context, f *flag.FlagSet, args ...any) subcomm
|
|||||||
log.Infof("Using exec capabilities from container: %+v", e.Capabilities)
|
log.Infof("Using exec capabilities from container: %+v", e.Capabilities)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create the file descriptor map for the process in the container.
|
||||||
|
fdMap := map[int]*os.File{
|
||||||
|
0: os.Stdin,
|
||||||
|
1: os.Stdout,
|
||||||
|
2: os.Stderr,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add custom file descriptors to the map.
|
||||||
|
var files []*os.File
|
||||||
|
for _, fd := range ex.passFDs {
|
||||||
|
file := os.NewFile(uintptr(fd), "")
|
||||||
|
if file == nil {
|
||||||
|
util.Fatalf("failed to create file from file descriptor %d", fd)
|
||||||
|
}
|
||||||
|
fdMap[int(file.Fd())] = file
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the underlying file descriptors after we have passed them.
|
||||||
|
defer func() {
|
||||||
|
for _, file := range files {
|
||||||
|
fd := file.Fd()
|
||||||
|
if file.Close() != nil {
|
||||||
|
log.Debugf("Failed to close FD %d", fd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
e.FilePayload = control.NewFDMap(fdMap)
|
||||||
|
|
||||||
// containerd expects an actual process to represent the container being
|
// containerd expects an actual process to represent the container being
|
||||||
// executed. If detach was specified, starts a child in non-detach mode,
|
// executed. If detach was specified, starts a child in non-detach mode,
|
||||||
// write the child's PID to the pid file. So when the container returns, the
|
// write the child's PID to the pid file. So when the container returns, the
|
||||||
@@ -330,7 +363,11 @@ func (ex *Exec) argsFromCLI(argv []string, enableRaw bool) (*control.ExecArgs, e
|
|||||||
ExtraKGIDs: extraKGIDs,
|
ExtraKGIDs: extraKGIDs,
|
||||||
Capabilities: caps,
|
Capabilities: caps,
|
||||||
StdioIsPty: ex.consoleSocket != "" || console.IsPty(os.Stdin.Fd()),
|
StdioIsPty: ex.consoleSocket != "" || console.IsPty(os.Stdin.Fd()),
|
||||||
FilePayload: urpc.FilePayload{[]*os.File{os.Stdin, os.Stdout, os.Stderr}},
|
FilePayload: control.NewFDMap(map[int]*os.File{
|
||||||
|
0: os.Stdin,
|
||||||
|
1: os.Stdout,
|
||||||
|
2: os.Stderr,
|
||||||
|
}),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -379,7 +416,11 @@ func argsFromProcess(p *specs.Process, enableRaw bool) (*control.ExecArgs, error
|
|||||||
ExtraKGIDs: extraKGIDs,
|
ExtraKGIDs: extraKGIDs,
|
||||||
Capabilities: caps,
|
Capabilities: caps,
|
||||||
StdioIsPty: p.Terminal,
|
StdioIsPty: p.Terminal,
|
||||||
FilePayload: urpc.FilePayload{Files: []*os.File{os.Stdin, os.Stdout, os.Stderr}},
|
FilePayload: control.NewFDMap(map[int]*os.File{
|
||||||
|
0: os.Stdin,
|
||||||
|
1: os.Stdout,
|
||||||
|
2: os.Stderr,
|
||||||
|
}),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+16
-9
@@ -24,7 +24,6 @@ import (
|
|||||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||||
"gvisor.dev/gvisor/pkg/sentry/control"
|
"gvisor.dev/gvisor/pkg/sentry/control"
|
||||||
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
||||||
"gvisor.dev/gvisor/pkg/urpc"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestUser(t *testing.T) {
|
func TestUser(t *testing.T) {
|
||||||
@@ -76,10 +75,14 @@ func TestCLIArgs(t *testing.T) {
|
|||||||
expected: control.ExecArgs{
|
expected: control.ExecArgs{
|
||||||
Argv: []string{"ls", "/"},
|
Argv: []string{"ls", "/"},
|
||||||
WorkingDirectory: "/foo/bar",
|
WorkingDirectory: "/foo/bar",
|
||||||
FilePayload: urpc.FilePayload{Files: []*os.File{os.Stdin, os.Stdout, os.Stderr}},
|
FilePayload: control.NewFDMap(map[int]*os.File{
|
||||||
KUID: 0,
|
0: os.Stdin,
|
||||||
KGID: 0,
|
1: os.Stdout,
|
||||||
ExtraKGIDs: []auth.KGID{1, 2, 3},
|
2: os.Stderr,
|
||||||
|
}),
|
||||||
|
KUID: 0,
|
||||||
|
KGID: 0,
|
||||||
|
ExtraKGIDs: []auth.KGID{1, 2, 3},
|
||||||
Capabilities: &auth.TaskCapabilities{
|
Capabilities: &auth.TaskCapabilities{
|
||||||
BoundingCaps: auth.CapabilitySetOf(linux.CAP_DAC_OVERRIDE),
|
BoundingCaps: auth.CapabilitySetOf(linux.CAP_DAC_OVERRIDE),
|
||||||
EffectiveCaps: auth.CapabilitySetOf(linux.CAP_DAC_OVERRIDE),
|
EffectiveCaps: auth.CapabilitySetOf(linux.CAP_DAC_OVERRIDE),
|
||||||
@@ -129,10 +132,14 @@ func TestJSONArgs(t *testing.T) {
|
|||||||
expected: control.ExecArgs{
|
expected: control.ExecArgs{
|
||||||
Argv: []string{"ls", "/"},
|
Argv: []string{"ls", "/"},
|
||||||
WorkingDirectory: "/foo/bar",
|
WorkingDirectory: "/foo/bar",
|
||||||
FilePayload: urpc.FilePayload{Files: []*os.File{os.Stdin, os.Stdout, os.Stderr}},
|
FilePayload: control.NewFDMap(map[int]*os.File{
|
||||||
KUID: 0,
|
0: os.Stdin,
|
||||||
KGID: 0,
|
1: os.Stdout,
|
||||||
ExtraKGIDs: []auth.KGID{1, 2, 3},
|
2: os.Stderr,
|
||||||
|
}),
|
||||||
|
KUID: 0,
|
||||||
|
KGID: 0,
|
||||||
|
ExtraKGIDs: []auth.KGID{1, 2, 3},
|
||||||
Capabilities: &auth.TaskCapabilities{
|
Capabilities: &auth.TaskCapabilities{
|
||||||
BoundingCaps: auth.CapabilitySetOf(linux.CAP_DAC_OVERRIDE),
|
BoundingCaps: auth.CapabilitySetOf(linux.CAP_DAC_OVERRIDE),
|
||||||
EffectiveCaps: auth.CapabilitySetOf(linux.CAP_DAC_OVERRIDE),
|
EffectiveCaps: auth.CapabilitySetOf(linux.CAP_DAC_OVERRIDE),
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
// Copyright 2023 The gVisor Authors.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"gvisor.dev/gvisor/runsc/boot"
|
||||||
|
)
|
||||||
|
|
||||||
|
// fdMappings can be used with flags that appear multiple times.
|
||||||
|
type fdMappings []boot.FDMapping
|
||||||
|
|
||||||
|
// String implements flag.Value.
|
||||||
|
func (i *fdMappings) String() string {
|
||||||
|
return fmt.Sprintf("%v", *i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get implements flag.Value.
|
||||||
|
func (i *fdMappings) Get() any {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetArray returns an array of mappings.
|
||||||
|
func (i *fdMappings) GetArray() []boot.FDMapping {
|
||||||
|
return *i
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set implements flag.Value and appends a mapping from the command line to the
|
||||||
|
// mappings array.
|
||||||
|
func (i *fdMappings) Set(s string) error {
|
||||||
|
split := strings.Split(s, ":")
|
||||||
|
if len(split) != 2 {
|
||||||
|
return fmt.Errorf("invalid flag value: must be of format M:N")
|
||||||
|
}
|
||||||
|
|
||||||
|
fdHost, err := strconv.Atoi(split[0])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid flag host value: %v", err)
|
||||||
|
}
|
||||||
|
if fdHost < 0 {
|
||||||
|
return fmt.Errorf("flag host value must be >= 0: %d", fdHost)
|
||||||
|
}
|
||||||
|
|
||||||
|
fdGuest, err := strconv.Atoi(split[1])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid flag guest value: %v", err)
|
||||||
|
}
|
||||||
|
if fdGuest < 0 {
|
||||||
|
return fmt.Errorf("flag guest value must be >= 0: %d", fdGuest)
|
||||||
|
}
|
||||||
|
|
||||||
|
*i = append(*i, boot.FDMapping{
|
||||||
|
Host: fdHost,
|
||||||
|
Guest: fdGuest,
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -16,9 +16,11 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/google/subcommands"
|
"github.com/google/subcommands"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
|
"gvisor.dev/gvisor/pkg/log"
|
||||||
"gvisor.dev/gvisor/runsc/cmd/util"
|
"gvisor.dev/gvisor/runsc/cmd/util"
|
||||||
"gvisor.dev/gvisor/runsc/config"
|
"gvisor.dev/gvisor/runsc/config"
|
||||||
"gvisor.dev/gvisor/runsc/container"
|
"gvisor.dev/gvisor/runsc/container"
|
||||||
@@ -33,6 +35,10 @@ type Run struct {
|
|||||||
|
|
||||||
// detach indicates that runsc has to start a process and exit without waiting it.
|
// detach indicates that runsc has to start a process and exit without waiting it.
|
||||||
detach bool
|
detach bool
|
||||||
|
|
||||||
|
// passFDs are user-supplied FDs from the host to be exposed to the
|
||||||
|
// sandboxed app.
|
||||||
|
passFDs intFlags
|
||||||
}
|
}
|
||||||
|
|
||||||
// Name implements subcommands.Command.Name.
|
// Name implements subcommands.Command.Name.
|
||||||
@@ -54,6 +60,7 @@ func (*Run) Usage() string {
|
|||||||
// SetFlags implements subcommands.Command.SetFlags.
|
// SetFlags implements subcommands.Command.SetFlags.
|
||||||
func (r *Run) SetFlags(f *flag.FlagSet) {
|
func (r *Run) SetFlags(f *flag.FlagSet) {
|
||||||
f.BoolVar(&r.detach, "detach", false, "detach from the container's process")
|
f.BoolVar(&r.detach, "detach", false, "detach from the container's process")
|
||||||
|
f.Var(&r.passFDs, "pass-fd", "file descriptors passed to the container. Can be supplied multiple times.")
|
||||||
r.Create.SetFlags(f)
|
r.Create.SetFlags(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,6 +96,26 @@ func (r *Run) Execute(_ context.Context, f *flag.FlagSet, args ...any) subcomman
|
|||||||
}
|
}
|
||||||
specutils.LogSpecDebug(spec, conf.OCISeccomp)
|
specutils.LogSpecDebug(spec, conf.OCISeccomp)
|
||||||
|
|
||||||
|
// Create files from file descriptors.
|
||||||
|
fdMap := make(map[int]*os.File)
|
||||||
|
for _, fd := range r.passFDs {
|
||||||
|
file := os.NewFile(uintptr(fd), "")
|
||||||
|
if file == nil {
|
||||||
|
return util.Errorf("Failed to create file from file descriptor %d", fd)
|
||||||
|
}
|
||||||
|
fdMap[fd] = file
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the underlying file descriptors after we have passed them.
|
||||||
|
defer func() {
|
||||||
|
for _, file := range fdMap {
|
||||||
|
fd := file.Fd()
|
||||||
|
if file.Close() != nil {
|
||||||
|
log.Debugf("Failed to close FD %d", fd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
runArgs := container.Args{
|
runArgs := container.Args{
|
||||||
ID: id,
|
ID: id,
|
||||||
Spec: spec,
|
Spec: spec,
|
||||||
@@ -97,6 +124,7 @@ func (r *Run) Execute(_ context.Context, f *flag.FlagSet, args ...any) subcomman
|
|||||||
PIDFile: r.pidFile,
|
PIDFile: r.pidFile,
|
||||||
UserLog: r.userLog,
|
UserLog: r.userLog,
|
||||||
Attached: !r.detach,
|
Attached: !r.detach,
|
||||||
|
PassFiles: fdMap,
|
||||||
}
|
}
|
||||||
ws, err := container.Run(conf, runArgs)
|
ws, err := container.Run(conf, runArgs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -83,7 +83,6 @@ go_test(
|
|||||||
"//pkg/sync",
|
"//pkg/sync",
|
||||||
"//pkg/test/testutil",
|
"//pkg/test/testutil",
|
||||||
"//pkg/unet",
|
"//pkg/unet",
|
||||||
"//pkg/urpc",
|
|
||||||
"//runsc/boot",
|
"//runsc/boot",
|
||||||
"//runsc/cgroup",
|
"//runsc/cgroup",
|
||||||
"//runsc/config",
|
"//runsc/config",
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ import (
|
|||||||
"gvisor.dev/gvisor/pkg/sync"
|
"gvisor.dev/gvisor/pkg/sync"
|
||||||
"gvisor.dev/gvisor/pkg/test/testutil"
|
"gvisor.dev/gvisor/pkg/test/testutil"
|
||||||
"gvisor.dev/gvisor/pkg/unet"
|
"gvisor.dev/gvisor/pkg/unet"
|
||||||
"gvisor.dev/gvisor/pkg/urpc"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// socketPath creates a path inside bundleDir and ensures that the returned
|
// socketPath creates a path inside bundleDir and ensures that the returned
|
||||||
@@ -282,9 +281,9 @@ func TestJobControlSignalExec(t *testing.T) {
|
|||||||
// our PID counts get messed up.
|
// our PID counts get messed up.
|
||||||
Argv: []string{"/bin/bash", "--noprofile", "--norc"},
|
Argv: []string{"/bin/bash", "--noprofile", "--norc"},
|
||||||
// Pass the pty replica as FD 0, 1, and 2.
|
// Pass the pty replica as FD 0, 1, and 2.
|
||||||
FilePayload: urpc.FilePayload{
|
FilePayload: control.NewFDMap(map[int]*os.File{
|
||||||
Files: []*os.File{ptyReplica, ptyReplica, ptyReplica},
|
0: ptyReplica, 1: ptyReplica, 2: ptyReplica,
|
||||||
},
|
}),
|
||||||
StdioIsPty: true,
|
StdioIsPty: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -176,6 +176,10 @@ type Args struct {
|
|||||||
//
|
//
|
||||||
// It only applies for the init container.
|
// It only applies for the init container.
|
||||||
Attached bool
|
Attached bool
|
||||||
|
|
||||||
|
// PassFiles are user-supplied files from the host to be exposed to the
|
||||||
|
// sandboxed app.
|
||||||
|
PassFiles map[int]*os.File
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates the container in a new Sandbox process, unless the metadata
|
// New creates the container in a new Sandbox process, unless the metadata
|
||||||
@@ -296,6 +300,7 @@ func New(conf *config.Config, args Args) (*Container, error) {
|
|||||||
Cgroup: containerCgroup,
|
Cgroup: containerCgroup,
|
||||||
Attached: args.Attached,
|
Attached: args.Attached,
|
||||||
OverlayFilestoreFiles: overlayFilestoreFiles,
|
OverlayFilestoreFiles: overlayFilestoreFiles,
|
||||||
|
PassFiles: args.PassFiles,
|
||||||
}
|
}
|
||||||
sand, err := sandbox.New(conf, sandArgs)
|
sand, err := sandbox.New(conf, sandArgs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ import (
|
|||||||
"gvisor.dev/gvisor/pkg/sentry/platform"
|
"gvisor.dev/gvisor/pkg/sentry/platform"
|
||||||
"gvisor.dev/gvisor/pkg/sync"
|
"gvisor.dev/gvisor/pkg/sync"
|
||||||
"gvisor.dev/gvisor/pkg/test/testutil"
|
"gvisor.dev/gvisor/pkg/test/testutil"
|
||||||
"gvisor.dev/gvisor/pkg/urpc"
|
|
||||||
"gvisor.dev/gvisor/runsc/cgroup"
|
"gvisor.dev/gvisor/runsc/cgroup"
|
||||||
"gvisor.dev/gvisor/runsc/config"
|
"gvisor.dev/gvisor/runsc/config"
|
||||||
"gvisor.dev/gvisor/runsc/flag"
|
"gvisor.dev/gvisor/runsc/flag"
|
||||||
@@ -78,9 +77,11 @@ func executeCombinedOutput(conf *config.Config, cont *Container, name string, ar
|
|||||||
defer r.Close()
|
defer r.Close()
|
||||||
|
|
||||||
args := &control.ExecArgs{
|
args := &control.ExecArgs{
|
||||||
Filename: name,
|
Filename: name,
|
||||||
Argv: append([]string{name}, arg...),
|
Argv: append([]string{name}, arg...),
|
||||||
FilePayload: urpc.FilePayload{Files: []*os.File{os.Stdin, w, w}},
|
FilePayload: control.NewFDMap(map[int]*os.File{
|
||||||
|
0: os.Stdin, 1: w, 2: w,
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
ws, err := cont.executeSync(conf, args)
|
ws, err := cont.executeSync(conf, args)
|
||||||
w.Close()
|
w.Close()
|
||||||
@@ -853,9 +854,9 @@ func TestExec(t *testing.T) {
|
|||||||
|
|
||||||
_, err = cont.executeSync(conf, &control.ExecArgs{
|
_, err = cont.executeSync(conf, &control.ExecArgs{
|
||||||
Argv: []string{"/nonexist"},
|
Argv: []string{"/nonexist"},
|
||||||
FilePayload: urpc.FilePayload{
|
FilePayload: control.NewFDMap(map[int]*os.File{
|
||||||
Files: []*os.File{os.NewFile(uintptr(fds[1]), "sock")},
|
0: os.NewFile(uintptr(fds[1]), "sock"),
|
||||||
},
|
}),
|
||||||
})
|
})
|
||||||
want := "failed to load /nonexist"
|
want := "failed to load /nonexist"
|
||||||
if err == nil || !strings.Contains(err.Error(), want) {
|
if err == nil || !strings.Contains(err.Error(), want) {
|
||||||
@@ -2726,3 +2727,168 @@ func TestSandboxCommunicationUnshare(t *testing.T) {
|
|||||||
t.Errorf("SignalContainer(): %v", err)
|
t.Errorf("SignalContainer(): %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// writeAndReadFromPipe writes the bytes to the write end of the pipe, then
|
||||||
|
// reads from the read end and returns the result.
|
||||||
|
func writeAndReadFromPipe(write, read *os.File, msg string) (string, error) {
|
||||||
|
// Write the message to be read by the guest.
|
||||||
|
if _, err := io.StringWriter(write).WriteString(msg); err != nil {
|
||||||
|
return "", fmt.Errorf("failed to write message to pipe: %w", err)
|
||||||
|
}
|
||||||
|
write.Close()
|
||||||
|
|
||||||
|
// Read and return the message.
|
||||||
|
response, err := io.ReadAll(read)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to read from pipe: %w", err)
|
||||||
|
}
|
||||||
|
read.Close()
|
||||||
|
|
||||||
|
return string(response), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createPipes() (*os.File, *os.File, *os.File, *os.File, func(), error) {
|
||||||
|
// This is the first pipe which the host writes to and the guest reads
|
||||||
|
// from.
|
||||||
|
guestRead, hostWrite, err := os.Pipe()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is the second pipe which the guest writes to and the host reads
|
||||||
|
// from.
|
||||||
|
hostRead, guestWrite, err := os.Pipe()
|
||||||
|
if err != nil {
|
||||||
|
guestRead.Close()
|
||||||
|
hostWrite.Close()
|
||||||
|
return nil, nil, nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup := func() {
|
||||||
|
guestRead.Close()
|
||||||
|
hostWrite.Close()
|
||||||
|
hostRead.Close()
|
||||||
|
guestWrite.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
return guestRead, hostWrite, hostRead, guestWrite, cleanup, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestFDPassingRun checks that file descriptors passed into a new container
|
||||||
|
// work as expected.
|
||||||
|
func TestFDPassingRun(t *testing.T) {
|
||||||
|
guestRead, hostWrite, hostRead, guestWrite, cleanup, err := createPipes()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error creating pipes: %v", err)
|
||||||
|
}
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
// In the guest, read from the host and write the result back to the host.
|
||||||
|
conf := testutil.TestConfig(t)
|
||||||
|
cmd := fmt.Sprintf("cat /proc/self/fd/%d > /proc/self/fd/%d", int(guestRead.Fd()), int(guestWrite.Fd()))
|
||||||
|
spec := testutil.NewSpecWithArgs("bash", "-c", cmd)
|
||||||
|
|
||||||
|
_, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error setting up container: %v", err)
|
||||||
|
}
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
args := Args{
|
||||||
|
ID: testutil.RandomContainerID(),
|
||||||
|
Spec: spec,
|
||||||
|
BundleDir: bundleDir,
|
||||||
|
PassFiles: map[int]*os.File{
|
||||||
|
int(guestRead.Fd()): guestRead,
|
||||||
|
int(guestWrite.Fd()): guestWrite,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cont, err := New(conf, args)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Creating container: %v", err)
|
||||||
|
}
|
||||||
|
defer cont.Destroy()
|
||||||
|
|
||||||
|
if err := cont.Start(conf); err != nil {
|
||||||
|
t.Fatalf("starting container: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We close guestWrite here because it has been passed into the container.
|
||||||
|
// If we do not close it, we will never see an EOF.
|
||||||
|
guestWrite.Close()
|
||||||
|
|
||||||
|
msg := "hello"
|
||||||
|
got, err := writeAndReadFromPipe(hostWrite, hostRead, msg)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if got != msg {
|
||||||
|
t.Errorf("got message %q, want %q", got, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestFDPassingExec checks that file descriptors passed into an already
|
||||||
|
// running container work as expected.
|
||||||
|
func TestFDPassingExec(t *testing.T) {
|
||||||
|
guestRead, hostWrite, hostRead, guestWrite, cleanup, err := createPipes()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error creating pipes: %v", err)
|
||||||
|
}
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
conf := testutil.TestConfig(t)
|
||||||
|
|
||||||
|
// We just sleep here because we want to test file descriptor passing
|
||||||
|
// inside a process executed inside an already running container.
|
||||||
|
spec := testutil.NewSpecWithArgs("bash", "-c", "sleep infinity")
|
||||||
|
|
||||||
|
_, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error setting up container: %v", err)
|
||||||
|
}
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
args := Args{
|
||||||
|
ID: testutil.RandomContainerID(),
|
||||||
|
Spec: spec,
|
||||||
|
BundleDir: bundleDir,
|
||||||
|
}
|
||||||
|
|
||||||
|
cont, err := New(conf, args)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Creating container: %v", err)
|
||||||
|
}
|
||||||
|
defer cont.Destroy()
|
||||||
|
|
||||||
|
if err := cont.Start(conf); err != nil {
|
||||||
|
t.Fatalf("starting container: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare executing a command in the running container.
|
||||||
|
cmd := fmt.Sprintf("cat /proc/self/fd/%d > /proc/self/fd/%d", int(guestRead.Fd()), int(guestWrite.Fd()))
|
||||||
|
execArgs := &control.ExecArgs{
|
||||||
|
Argv: []string{"/bin/bash", "-c", cmd},
|
||||||
|
FilePayload: control.NewFDMap(map[int]*os.File{
|
||||||
|
int(guestRead.Fd()): guestRead,
|
||||||
|
int(guestWrite.Fd()): guestWrite,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = cont.Execute(conf, execArgs); err != nil {
|
||||||
|
t.Fatalf("Failed to execute command: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We close guestWrite here because it has been passed into the container.
|
||||||
|
// If we do not close it, we will never see an EOF.
|
||||||
|
guestWrite.Close()
|
||||||
|
|
||||||
|
msg := "hello"
|
||||||
|
got, err := writeAndReadFromPipe(hostWrite, hostRead, msg)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if got != msg {
|
||||||
|
t.Errorf("got message %q, want %q", got, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -106,6 +106,17 @@ func (f *Agency) Transfer(cmd *exec.Cmd, nextFD int) int {
|
|||||||
return nextFD
|
return nextFD
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DonateAndTransferCustomFiles sets up the flags for passing file descriptors from the
|
||||||
|
// host to the sandbox. Making use of the agency is not necessary,
|
||||||
|
func DonateAndTransferCustomFiles(cmd *exec.Cmd, nextFD int, files map[int]*os.File) int {
|
||||||
|
for fd, file := range files {
|
||||||
|
cmd.Args = append(cmd.Args, fmt.Sprintf("--custom-fds=%d:%d", nextFD, fd))
|
||||||
|
cmd.ExtraFiles = append(cmd.ExtraFiles, file)
|
||||||
|
nextFD++
|
||||||
|
}
|
||||||
|
return nextFD
|
||||||
|
}
|
||||||
|
|
||||||
// Close closes any files the agency has taken ownership over.
|
// Close closes any files the agency has taken ownership over.
|
||||||
func (f *Agency) Close() {
|
func (f *Agency) Close() {
|
||||||
for _, file := range f.closePending {
|
for _, file := range f.closePending {
|
||||||
|
|||||||
@@ -241,6 +241,10 @@ type Args struct {
|
|||||||
// SinkFiles is the an ordered array of files to be used by seccheck sinks
|
// SinkFiles is the an ordered array of files to be used by seccheck sinks
|
||||||
// configured from the --pod-init-config file.
|
// configured from the --pod-init-config file.
|
||||||
SinkFiles []*os.File
|
SinkFiles []*os.File
|
||||||
|
|
||||||
|
// PassFiles are user-supplied files from the host to be exposed to the
|
||||||
|
// sandboxed app.
|
||||||
|
PassFiles map[int]*os.File
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates the sandbox process. The caller must call Destroy() on the
|
// New creates the sandbox process. The caller must call Destroy() on the
|
||||||
@@ -528,7 +532,17 @@ func (s *Sandbox) NewCGroup() (cgroup.Cgroup, error) {
|
|||||||
func (s *Sandbox) Execute(conf *config.Config, args *control.ExecArgs) (int32, error) {
|
func (s *Sandbox) Execute(conf *config.Config, args *control.ExecArgs) (int32, error) {
|
||||||
log.Debugf("Executing new process in container %q in sandbox %q", args.ContainerID, s.ID)
|
log.Debugf("Executing new process in container %q in sandbox %q", args.ContainerID, s.ID)
|
||||||
|
|
||||||
if err := s.configureStdios(conf, args.Files); err != nil {
|
// Stdios are those files which have an FD <= 2 in the process. We do not
|
||||||
|
// want the ownership of other files to be changed by configureStdios.
|
||||||
|
var stdios []*os.File
|
||||||
|
for i, fd := range args.GuestFDs {
|
||||||
|
if fd > 2 || i >= len(args.Files) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
stdios = append(stdios, args.Files[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.configureStdios(conf, stdios); err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -929,8 +943,9 @@ func (s *Sandbox) createSandboxProcess(conf *config.Config, args *Args, startSyn
|
|||||||
cmd.Args = append(cmd.Args, "--attached")
|
cmd.Args = append(cmd.Args, "--attached")
|
||||||
}
|
}
|
||||||
|
|
||||||
// nextFD must not be used beyond this point.
|
nextFD = donations.Transfer(cmd, nextFD)
|
||||||
_ = donations.Transfer(cmd, nextFD)
|
|
||||||
|
_ = donation.DonateAndTransferCustomFiles(cmd, nextFD, args.PassFiles)
|
||||||
|
|
||||||
// Add container ID as the last argument.
|
// Add container ID as the last argument.
|
||||||
cmd.Args = append(cmd.Args, s.ID)
|
cmd.Args = append(cmd.Args, s.ID)
|
||||||
|
|||||||
Reference in New Issue
Block a user