Create gofer filestores in gofer's mount namespace.

Before this change, the gofer filestore FDs were opened in the host's mount
namespace. This prevented containerd from unmounting a subcontainer's rootfs
in case (due to a ref leak or bug) the container's rootfs was not destroyed in
gVisor's VFS layer. In such a scenario, the sentry would leak the rootfs
filestore FD and containerd's umount attempts will fail with EBUSY.

This change ensures that all gofer filestores are created in the gofer's mount
namespace, so any unmount attempt from the host mount namespace is not blocked.

This change makes the following 2 logical changes:
1. It introduces a new gofer process synchronization flag --sync-chroot-fd. The
   gofer waits on this FD and then proceeds to set up container filesystem and
   pivot_root(2) + chroot(2) the gofer mount namespace. This is useful if an
   external process wants to take action in gofer's mount namespace while
   having access to the host filesystem. As a result, the existing
   --sync-nvproxy-fd became redundant because it has the same use case. IOW,
   it was just renamed to "sync-chroot-fd" and its meaning became broader.
2. Container.createGoferFilestores() earlier was doing two things: creating
   gofer mount configs and creating all filestores. But the intent of this
   change is to only create filestore FDs after gofer starts in gofer mountns.
   However, the gofer mount configs are needed before that. So this function
   was split into Container.initGoferConfs().

We open FDs from the gofer mount namespace using a neat trick suggested by
Andrei, we prefix the absolute paths with /proc/<gofer-pid>/root. See
proc_pid_root(5) for more details about this.

There are no other intended changes in behavior.

Fixes #9834
Suggested-by: Andrei Vagin <avagin@google.com>
PiperOrigin-RevId: 706880500
This commit is contained in:
Ayush Ranjan
2024-12-16 17:03:39 -08:00
committed by gVisor bot
parent 2fb49b8880
commit 6cf66fa154
3 changed files with 231 additions and 194 deletions
+17 -16
View File
@@ -64,11 +64,12 @@ var goferCaps = &specs.LinuxCapabilities{
// goferSyncFDs contains file descriptors that are used for synchronization
// of the Gofer startup process against other processes.
type goferSyncFDs struct {
// nvproxyFD is a file descriptor that is used to wait until
// nvproxy-related setup is done. This setup involves creating mounts in the
// Gofer process's mount namespace.
// chrootFD is a file descriptor that is used to wait until container
// filesystem related setup is done. This setup involves creating files and
// mounts in the Gofer process's mount namespace and needs to be done before
// the Gofer chroots.
// If this is set, this FD is the first that the Gofer waits for.
nvproxyFD int
chrootFD int
// usernsFD is a file descriptor that is used to wait until
// user namespace ID mappings are established in the Gofer's userns.
// If this is set, this FD is the second that the Gofer waits for.
@@ -154,7 +155,7 @@ func (g *Gofer) Execute(_ context.Context, f *flag.FlagSet, args ...any) subcomm
util.Fatalf("reading spec: %v", err)
}
g.syncFDs.syncNVProxy()
g.syncFDs.syncChroot()
g.syncFDs.syncUsernsForRootless()
goferToHostRPCSock, err := unet.NewSocket(g.goferToHostRPCFD)
@@ -728,7 +729,7 @@ func adjustMountOptions(conf *config.Config, path string, opts []string) ([]stri
// setFlags sets sync FD flags on the given FlagSet.
func (g *goferSyncFDs) setFlags(f *flag.FlagSet) {
f.IntVar(&g.nvproxyFD, "sync-nvproxy-fd", -1, "file descriptor that the gofer waits on until nvproxy setup is done")
f.IntVar(&g.chrootFD, "sync-chroot-fd", -1, "file descriptor that the gofer waits on until container filesystem setup is done")
f.IntVar(&g.usernsFD, "sync-userns-fd", -1, "file descriptor the gofer waits on until userns mappings are set up")
f.IntVar(&g.procMountFD, "proc-mount-sync-fd", -1, "file descriptor that the gofer writes to when /proc isn't needed anymore and can be unmounted")
}
@@ -737,7 +738,7 @@ func (g *goferSyncFDs) setFlags(f *flag.FlagSet) {
// to a re-executed version of this process.
func (g *goferSyncFDs) flags() map[string]string {
return map[string]string{
"sync-nvproxy-fd": fmt.Sprintf("%d", g.nvproxyFD),
"sync-chroot-fd": fmt.Sprintf("%d", g.chrootFD),
"sync-userns-fd": fmt.Sprintf("%d", g.usernsFD),
"proc-mount-sync-fd": fmt.Sprintf("%d", g.procMountFD),
}
@@ -827,16 +828,16 @@ func syncUsernsForRootless(fd int) {
}
}
// syncNVProxy waits on nvproxyFD to be closed.
// Used for synchronization during nvproxy setup which is done from the
// non-gofer process.
// This function is a no-op if nvProxySyncFD is -1.
func (g *goferSyncFDs) syncNVProxy() {
if g.nvproxyFD < 0 {
// syncChroot waits on chrootFD to be closed.
// Used for synchronization during container filesystem setup which is done
// from the non-gofer process.
// This function is a no-op if chrootFD is -1.
func (g *goferSyncFDs) syncChroot() {
if g.chrootFD < 0 {
return
}
if err := waitForFD(g.nvproxyFD, "nvproxy sync FD"); err != nil {
util.Fatalf("failed to sync on NVProxy FD: %v", err)
if err := waitForFD(g.chrootFD, "chroot sync FD"); err != nil {
util.Fatalf("failed to sync on chroot FD: %v", err)
}
g.nvproxyFD = -1
g.chrootFD = -1
}
+5
View File
@@ -972,6 +972,11 @@ func (o *Overlay2) SubMountOverlayMedium() OverlayMedium {
return o.medium
}
// Medium returns the overlay medium config.
func (o Overlay2) Medium() OverlayMedium {
return o.medium
}
// HostSettingsPolicy dictates how host settings should be handled.
type HostSettingsPolicy int
File diff suppressed because it is too large Load Diff