Allow host FD to be restored with a different FD

FD numbers can vary between depending on the options used with
runsc command. For example, there are extra FDs passed to
`runsc boot` if `debug-log` is enabled. So instead of requiring
all FDs to have the exact same numbering during restore, provide
a mechanism to remap the FD. Each host FD has a unique identifier
with a map to their corresponding FD. Then during restore, FD
numbers are remapped to the correct ones.

Updates #1956

PiperOrigin-RevId: 615215783
This commit is contained in:
Fabricio Voznika
2024-03-12 16:57:07 -07:00
committed by gVisor bot
parent f82d927772
commit 26dd42a0ea
10 changed files with 75 additions and 29 deletions
+2 -1
View File
@@ -255,7 +255,8 @@ func (l *Lifecycle) StartContainer(args *StartContainerArgs, _ *uint32) error {
for i, appFD := range args.DonatedFDs {
fdMap[appFD] = hostFDs[i]
}
if _, err := fdimport.Import(ctx, fdTable, false, args.KUID, args.KGID, fdMap); err != nil {
// Use ContainerID since containers don't have names here.
if _, err := fdimport.Import(ctx, fdTable, false, args.KUID, args.KGID, fdMap, initArgs.ContainerID); err != nil {
return fmt.Errorf("error importing host files: %w", err)
}
initArgs.FDTable = fdTable
+3 -1
View File
@@ -266,7 +266,9 @@ func (proc *Proc) execAsync(args *ExecArgs) (*kernel.ThreadGroup, kernel.ThreadI
initArgs.Filename = resolved
}
ttyFile, err := fdimport.Import(ctx, fdTable, args.StdioIsPty, args.KUID, args.KGID, fdMap)
// TODO(gvisor.dev/issue/1956): Container name is not really needed because
// exec processes are not restored, but add it for completeness.
ttyFile, err := fdimport.Import(ctx, fdTable, args.StdioIsPty, args.KUID, args.KGID, fdMap, "")
if err != nil {
return nil, 0, nil, err
}
+2 -1
View File
@@ -31,7 +31,7 @@ import (
// sets up TTY for sentry stdin, stdout, and stderr FDs. Used FDs are either
// closed or released. It's safe for the caller to close any remaining files
// upon return.
func Import(ctx context.Context, fdTable *kernel.FDTable, console bool, uid auth.KUID, gid auth.KGID, fds map[int]*fd.FD) (*host.TTYFileDescription, error) {
func Import(ctx context.Context, fdTable *kernel.FDTable, console bool, uid auth.KUID, gid auth.KGID, fds map[int]*fd.FD, containerName string) (*host.TTYFileDescription, error) {
k := kernel.KernelFromContext(ctx)
if k == nil {
return nil, fmt.Errorf("cannot find kernel from context")
@@ -62,6 +62,7 @@ func Import(ctx context.Context, fdTable *kernel.FDTable, console bool, uid auth
}
var appFile *vfs.FileDescription
fdOpts.RestoreKey = host.MakeRestoreID(containerName, appFD)
if console && appFD < 3 {
// Import the file as a host TTY file.
if ttyFile == nil {
+1 -1
View File
@@ -25,7 +25,7 @@ import (
// afterLoad is called by stateify.
func (fs *filesystem) afterLoad(ctx context.Context) {
fdmap := vfs.FilesystemFDMapFromContext(ctx)
fdmap := vfs.RestoreFilesystemFDMapFromContext(ctx)
fd, ok := fdmap[fs.iopts.UniqueID]
if !ok {
panic(fmt.Sprintf("no image FD available for filesystem with unique ID %q", fs.iopts.UniqueID))
+1 -1
View File
@@ -178,7 +178,7 @@ func (d *dentry) loadParent(_ goContext.Context, parent *dentry) {
// CompleteRestore implements
// vfs.FilesystemImplSaveRestoreExtension.CompleteRestore.
func (fs *filesystem) CompleteRestore(ctx context.Context, opts vfs.CompleteRestoreOptions) error {
fdmap := vfs.FilesystemFDMapFromContext(ctx)
fdmap := vfs.RestoreFilesystemFDMapFromContext(ctx)
if fdmap == nil {
return fmt.Errorf("no server FD map available")
}
+23 -14
View File
@@ -107,12 +107,16 @@ type inode struct {
// When the reference count reaches zero, the host fd is closed.
inodeRefs
// hostFD contains the host fd that this file was originally created from,
// which must be available at time of restore.
// hostFD contains the host fd that this file was originally created from.
// It must be available at time of restore by being set to the same value or
// remapped using restoreKey and vfs.CtxRestoreFilesystemFDMap in the context.
//
// This field is initialized at creation time and is immutable.
hostFD int
// restoreKey is used to identify the `hostFD` after a restore is performed.
restoreKey vfs.RestoreID
// ino is an inode number unique within this filesystem.
//
// This field is initialized at creation time and is immutable.
@@ -167,7 +171,7 @@ type inode struct {
buf []byte
}
func newInode(ctx context.Context, fs *filesystem, hostFD int, savable bool, fileType linux.FileMode, isTTY bool, readonly bool) (*inode, error) {
func newInode(ctx context.Context, fs *filesystem, hostFD int, savable bool, restoreKey vfs.RestoreID, fileType linux.FileMode, isTTY bool, readonly bool) (*inode, error) {
// Determine if hostFD is seekable.
_, err := unix.Seek(hostFD, 0, linux.SEEK_CUR)
seekable := !linuxerr.Equals(linuxerr.ESPIPE, err)
@@ -179,14 +183,15 @@ func newInode(ctx context.Context, fs *filesystem, hostFD int, savable bool, fil
}
i := &inode{
hostFD: hostFD,
ino: fs.NextIno(),
ftype: uint16(fileType),
epollable: isEpollable(hostFD),
seekable: seekable,
isTTY: isTTY,
savable: savable,
readonly: readonly,
hostFD: hostFD,
ino: fs.NextIno(),
ftype: uint16(fileType),
epollable: isEpollable(hostFD),
seekable: seekable,
isTTY: isTTY,
savable: savable,
restoreKey: restoreKey,
readonly: readonly,
}
i.InitRefs()
i.CachedMappable.Init(hostFD)
@@ -207,10 +212,14 @@ func newInode(ctx context.Context, fs *filesystem, hostFD int, savable bool, fil
// NewFDOptions contains options to NewFD.
type NewFDOptions struct {
// If Savable is true, the host file descriptor may be saved/restored by
// numeric value; the sandbox API requires a corresponding host FD with the
// same numeric value to be provided at time of restore.
// numeric value. RestoreKey is used to map the FD after restore.
Savable bool
// RestoreKey is only used when Savable==true. It uniquely identifies the
// host FD so that a mapping to the corresponding FD can be provided during
// restore.
RestoreKey vfs.RestoreID
// If IsTTY is true, the file descriptor is a TTY.
IsTTY bool
@@ -272,7 +281,7 @@ func NewFD(ctx context.Context, mnt *vfs.Mount, hostFD int, opts *NewFDOptions)
}
fileType := linux.FileMode(stat.Mode).FileType()
i, err := newInode(ctx, fs, hostFD, opts.Savable, fileType, opts.IsTTY, opts.Readonly)
i, err := newInode(ctx, fs, hostFD, opts.Savable, opts.RestoreKey, fileType, opts.IsTTY, opts.Readonly)
if err != nil {
return nil, err
}
+21 -1
View File
@@ -24,8 +24,20 @@ import (
"gvisor.dev/gvisor/pkg/hostarch"
"gvisor.dev/gvisor/pkg/safemem"
"gvisor.dev/gvisor/pkg/sentry/hostfd"
"gvisor.dev/gvisor/pkg/sentry/vfs"
)
// MakeRestoreID creates a RestoreID for a given application FD. The application
// FD remains the same between restores, e.g. stdout=2 before and after restore,
// but the host FD that is maps to can change between restores. This ID is used
// to map application FDs to their respective FD after a restore happens.
func MakeRestoreID(containerName string, fd int) vfs.RestoreID {
return vfs.RestoreID{
ContainerName: containerName,
Path: fmt.Sprintf("host:%d", fd),
}
}
// beforeSave is invoked by stateify.
func (i *inode) beforeSave() {
if !i.savable {
@@ -58,7 +70,15 @@ func (i *inode) beforeSave() {
}
// afterLoad is invoked by stateify.
func (i *inode) afterLoad(context.Context) {
func (i *inode) afterLoad(ctx context.Context) {
fdmap := vfs.RestoreFilesystemFDMapFromContext(ctx)
fd, ok := fdmap[i.restoreKey]
if ok {
// Remap FD if a new mapping is provided. Otherwise, keep the old FD and
// expect that caller will use the same FDs numbers.
i.hostFD = fd
}
if i.epollable {
if err := unix.SetNonblock(i.hostFD, true); err != nil {
panic(fmt.Sprintf("host.inode.afterLoad: failed to set host FD %d non-blocking: %v", i.hostFD, err))
+10 -8
View File
@@ -47,6 +47,16 @@ func MountNamespaceFromContext(ctx goContext.Context) *MountNamespace {
return nil
}
// RestoreFilesystemFDMapFromContext returns the RestoreFilesystemFDMap used
// by ctx. If ctx is not associated with a RestoreFilesystemFDMap, returns nil.
func RestoreFilesystemFDMapFromContext(ctx goContext.Context) map[RestoreID]int {
fdmap, ok := ctx.Value(CtxRestoreFilesystemFDMap).(map[RestoreID]int)
if !ok {
return nil
}
return fdmap
}
type mountNamespaceContext struct {
context.Context
mntns *MountNamespace
@@ -104,11 +114,3 @@ func (rc rootContext) Value(key any) any {
return rc.Context.Value(key)
}
}
// FilesystemFDMapFromContext returns the CtxRestoreFilesystemFDMap from ctx.
func FilesystemFDMapFromContext(ctx goContext.Context) map[RestoreID]int {
if v := ctx.Value(CtxRestoreFilesystemFDMap); v != nil {
return v.(map[RestoreID]int)
}
return nil
}
+1 -1
View File
@@ -1584,7 +1584,7 @@ func createFDTable(ctx context.Context, console bool, stdioFDs []*fd.FD, passFDs
k := kernel.KernelFromContext(ctx)
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, containerName)
if err != nil {
fdTable.DecRef(ctx)
return nil, nil, err
+11
View File
@@ -19,6 +19,7 @@ import (
"os"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/host"
"gvisor.dev/gvisor/pkg/sentry/inet"
"gvisor.dev/gvisor/pkg/sentry/kernel"
"gvisor.dev/gvisor/pkg/sentry/socket/hostinet"
@@ -118,6 +119,16 @@ func (r *restorer) restore(l *Loader) error {
return fmt.Errorf("configuring filesystem restore: %v", err)
}
fdmap := vfs.RestoreFilesystemFDMapFromContext(ctx)
for appFD, fd := range r.container.stdioFDs {
key := host.MakeRestoreID(r.container.containerName, appFD)
fdmap[key] = fd.Release()
}
for _, customFD := range r.container.passFDs {
key := host.MakeRestoreID(r.container.containerName, customFD.guest)
fdmap[key] = customFD.host.FD()
}
// Load the state.
loadOpts := state.LoadOpts{Source: r.stateFile}
if err := loadOpts.Load(ctx, l.k, nil, netns.Stack(), time.NewCalibratedClocks(), &vfs.CompleteRestoreOptions{}); err != nil {