diff options
author | Ayush Ranjan <ayushranjan@google.com> | 2021-09-20 22:31:19 -0700 |
---|---|---|
committer | gVisor bot <gvisor-bot@google.com> | 2021-09-20 22:34:01 -0700 |
commit | e819029f3ad059bfc1635b7f2a196c332fa7532f (patch) | |
tree | 38198d235ae53172514b69eea98d51d46624a9ff | |
parent | 3fe8d7ecf193b4efd099ceabe2d39a119ffe4fbc (diff) |
[lisa] Plumb lisafs through runsc.
lisafs is only supported in VFS2. Added a runsc flag which enables lisafs.
When the flag is enabled, the gofer process and the client communicate using
lisafs protocol instead of 9P.
Added a filesystem option in fsimpl/gofer which indicates if lisafs is being
used. That will be used to gate lisafs on the gofer client.
Note that this change does not make the gofer client use lisafs just yet.
Updates #5465
PiperOrigin-RevId: 397917844
-rw-r--r-- | pkg/sentry/fsimpl/gofer/filesystem.go | 3 | ||||
-rw-r--r-- | pkg/sentry/fsimpl/gofer/gofer.go | 13 | ||||
-rw-r--r-- | runsc/boot/fs.go | 14 | ||||
-rw-r--r-- | runsc/boot/vfs.go | 4 | ||||
-rw-r--r-- | runsc/cli/main.go | 2 | ||||
-rw-r--r-- | runsc/cmd/gofer.go | 120 | ||||
-rw-r--r-- | runsc/config/config.go | 3 | ||||
-rw-r--r-- | runsc/config/flags.go | 1 | ||||
-rw-r--r-- | runsc/container/container.go | 2 | ||||
-rw-r--r-- | runsc/fsgofer/BUILD | 2 | ||||
-rw-r--r-- | runsc/fsgofer/lisafs.go | 52 | ||||
-rw-r--r-- | runsc/specutils/specutils.go | 4 |
12 files changed, 185 insertions, 35 deletions
diff --git a/pkg/sentry/fsimpl/gofer/filesystem.go b/pkg/sentry/fsimpl/gofer/filesystem.go index 00228c469..9d943bd4a 100644 --- a/pkg/sentry/fsimpl/gofer/filesystem.go +++ b/pkg/sentry/fsimpl/gofer/filesystem.go @@ -1612,6 +1612,9 @@ func (fs *filesystem) MountOptions() string { if fs.opts.overlayfsStaleRead { optsKV = append(optsKV, mopt{moptOverlayfsStaleRead, nil}) } + if fs.opts.lisaEnabled { + optsKV = append(optsKV, mopt{moptLisafs, nil}) + } opts := make([]string, 0, len(optsKV)) for _, opt := range optsKV { diff --git a/pkg/sentry/fsimpl/gofer/gofer.go b/pkg/sentry/fsimpl/gofer/gofer.go index 43440ec19..13971d086 100644 --- a/pkg/sentry/fsimpl/gofer/gofer.go +++ b/pkg/sentry/fsimpl/gofer/gofer.go @@ -83,6 +83,7 @@ const ( moptForcePageCache = "force_page_cache" moptLimitHostFDTranslation = "limit_host_fd_translation" moptOverlayfsStaleRead = "overlayfs_stale_read" + moptLisafs = "lisafs" ) // Valid values for the "cache" mount option. @@ -214,6 +215,10 @@ type filesystemOptions struct { // way that application FDs representing "special files" such as sockets // do. Note that this disables client caching and mmap for regular files. regularFilesUseSpecialFileFD bool + + // lisaEnabled indicates whether the client will use lisafs protocol to + // communicate with the server instead of 9P. + lisaEnabled bool } // InteropMode controls the client's interaction with other remote filesystem @@ -427,6 +432,14 @@ func (fstype FilesystemType) GetFilesystem(ctx context.Context, vfsObj *vfs.Virt delete(mopts, moptOverlayfsStaleRead) fsopts.overlayfsStaleRead = true } + if lisafs, ok := mopts[moptLisafs]; ok { + delete(mopts, moptLisafs) + fsopts.lisaEnabled, err = strconv.ParseBool(lisafs) + if err != nil { + ctx.Warningf("gofer.FilesystemType.GetFilesystem: invalid lisafs option: %s", lisafs) + return nil, nil, linuxerr.EINVAL + } + } // fsopts.regularFilesUseSpecialFileFD can only be enabled by specifying // "cache=none". diff --git a/runsc/boot/fs.go b/runsc/boot/fs.go index 40cf2a3df..ba5460f1c 100644 --- a/runsc/boot/fs.go +++ b/runsc/boot/fs.go @@ -188,8 +188,8 @@ func compileMounts(spec *specs.Spec, conf *config.Config, vfs2Enabled bool) []sp return mounts } -// p9MountData creates a slice of p9 mount data. -func p9MountData(fd int, fa config.FileAccessType, vfs2 bool) []string { +// goferMountData creates a slice of gofer mount data. +func goferMountData(fd int, fa config.FileAccessType, attachPath string, vfs2 bool, lisafs bool) []string { opts := []string{ "trans=fd", "rfdno=" + strconv.Itoa(fd), @@ -203,6 +203,10 @@ func p9MountData(fd int, fa config.FileAccessType, vfs2 bool) []string { if fa == config.FileAccessShared { opts = append(opts, "cache=remote_revalidating") } + if vfs2 && lisafs { + opts = append(opts, "lisafs=true") + opts = append(opts, "aname="+attachPath) + } return opts } @@ -761,7 +765,7 @@ func (c *containerMounter) createRootMount(ctx context.Context, conf *config.Con fd := c.fds.remove() log.Infof("Mounting root over 9P, ioFD: %d", fd) p9FS := mustFindFilesystem("9p") - opts := p9MountData(fd, conf.FileAccess, false /* vfs2 */) + opts := goferMountData(fd, conf.FileAccess, "/", false /* vfs2 */, false /* lisafs */) // We can't check for overlayfs here because sandbox is chroot'ed and gofer // can only send mount options for specs.Mounts (specs.Root is missing @@ -822,7 +826,7 @@ func (c *containerMounter) getMountNameAndOptions(conf *config.Config, m *specs. case bind: fd := c.fds.remove() fsName = gofervfs2.Name - opts = p9MountData(fd, c.getMountAccessType(conf, m), conf.VFS2) + opts = goferMountData(fd, c.getMountAccessType(conf, m), m.Destination, conf.VFS2, conf.Lisafs) // If configured, add overlay to all writable mounts. useOverlay = conf.Overlay && !mountFlags(m.Options).ReadOnly case cgroupfs.Name: @@ -980,7 +984,7 @@ func (c *containerMounter) createRestoreEnvironment(conf *config.Config) (*fs.Re // Add root mount. fd := c.fds.remove() - opts := p9MountData(fd, conf.FileAccess, false /* vfs2 */) + opts := goferMountData(fd, conf.FileAccess, "/", false /* vfs2 */, false /* lisafs */) mf := fs.MountSourceFlags{} if c.root.Readonly || conf.Overlay { diff --git a/runsc/boot/vfs.go b/runsc/boot/vfs.go index 346796d9c..2f1332566 100644 --- a/runsc/boot/vfs.go +++ b/runsc/boot/vfs.go @@ -208,7 +208,7 @@ func (c *containerMounter) mountAll(conf *config.Config, procArgs *kernel.Create // createMountNamespaceVFS2 creates the container's root mount and namespace. func (c *containerMounter) createMountNamespaceVFS2(ctx context.Context, conf *config.Config, creds *auth.Credentials) (*vfs.MountNamespace, error) { fd := c.fds.remove() - data := p9MountData(fd, conf.FileAccess, true /* vfs2 */) + data := goferMountData(fd, conf.FileAccess, "/", true /* vfs2 */, conf.Lisafs) // We can't check for overlayfs here because sandbox is chroot'ed and gofer // can only send mount options for specs.Mounts (specs.Root is missing @@ -515,7 +515,7 @@ func (c *containerMounter) getMountNameAndOptionsVFS2(conf *config.Config, m *mo // but unlikely to be correct in this context. return "", nil, false, fmt.Errorf("9P mount requires a connection FD") } - data = p9MountData(m.fd, c.getMountAccessType(conf, m.mount), true /* vfs2 */) + data = goferMountData(m.fd, c.getMountAccessType(conf, m.mount), m.mount.Destination, true /* vfs2 */, conf.Lisafs) internalData = gofer.InternalFilesystemOptions{ UniqueID: m.mount.Destination, } diff --git a/runsc/cli/main.go b/runsc/cli/main.go index 3556d7665..058ab8232 100644 --- a/runsc/cli/main.go +++ b/runsc/cli/main.go @@ -228,7 +228,7 @@ func Main(version string) { log.Infof("\t\tFileAccess: %v, overlay: %t", conf.FileAccess, conf.Overlay) log.Infof("\t\tNetwork: %v, logging: %t", conf.Network, conf.LogPackets) log.Infof("\t\tStrace: %t, max size: %d, syscalls: %s", conf.Strace, conf.StraceLogSize, conf.StraceSyscalls) - log.Infof("\t\tVFS2 enabled: %v", conf.VFS2) + log.Infof("\t\tVFS2 enabled: %t, LISAFS: %t", conf.VFS2, conf.Lisafs) log.Infof("***************************") if conf.TestOnlyAllowRunAsCurrentUserWithoutChroot { diff --git a/runsc/cmd/gofer.go b/runsc/cmd/gofer.go index 2193e9040..c65e0267a 100644 --- a/runsc/cmd/gofer.go +++ b/runsc/cmd/gofer.go @@ -71,8 +71,8 @@ func (*Gofer) Name() string { } // Synopsis implements subcommands.Command. -func (*Gofer) Synopsis() string { - return "launch a gofer process that serves files over 9P protocol (internal use only)" +func (g *Gofer) Synopsis() string { + return fmt.Sprintf("launch a gofer process that serves files over the protocol (9P or lisafs) defined in the config (internal use only)") } // Usage implements subcommands.Command. @@ -83,7 +83,7 @@ func (*Gofer) Usage() string { // SetFlags implements subcommands.Command. func (g *Gofer) SetFlags(f *flag.FlagSet) { f.StringVar(&g.bundleDir, "bundle", "", "path to the root of the bundle directory, defaults to the current directory") - f.Var(&g.ioFDs, "io-fds", "list of FDs to connect 9P servers. They must follow this order: root first, then mounts as defined in the spec") + f.Var(&g.ioFDs, "io-fds", "list of FDs to connect gofer servers. They must follow this order: root first, then mounts as defined in the spec") f.BoolVar(&g.applyCaps, "apply-caps", true, "if true, apply capabilities to restrict what the Gofer process can do") f.BoolVar(&g.setUpRoot, "setup-root", true, "if true, set up an empty root for the process") f.IntVar(&g.specFD, "spec-fd", -1, "required fd with the container spec") @@ -160,10 +160,98 @@ func (g *Gofer) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) } log.Infof("Process chroot'd to %q", root) + // Initialize filters. + if conf.FSGoferHostUDS { + filter.InstallUDSFilters() + } + + if conf.Verity { + filter.InstallXattrFilters() + } + + if err := filter.Install(); err != nil { + Fatalf("installing seccomp filters: %v", err) + } + + if conf.Lisafs { + return g.serveLisafs(spec, conf, root) + } + return g.serve9P(spec, conf, root) +} + +func newSocket(ioFD int) *unet.Socket { + socket, err := unet.NewSocket(ioFD) + if err != nil { + Fatalf("creating server on FD %d: %v", ioFD, err) + } + return socket +} + +func (g *Gofer) serveLisafs(spec *specs.Spec, conf *config.Config, root string) subcommands.ExitStatus { + type connectionConfig struct { + sock *unet.Socket + readonly bool + } + cfgs := make([]connectionConfig, 0, len(spec.Mounts)+1) + server := fsgofer.NewLisafsServer(fsgofer.Config{ + // These are global options. Ignore readonly configuration, that is set on + // a per connection basis. + HostUDS: conf.FSGoferHostUDS, + EnableVerityXattr: conf.Verity, + }) + + // Start with root mount, then add any other additional mount as needed. + cfgs = append(cfgs, connectionConfig{ + sock: newSocket(g.ioFDs[0]), + readonly: spec.Root.Readonly || conf.Overlay, + }) + log.Infof("Serving %q mapped to %q on FD %d (ro: %t)", "/", root, g.ioFDs[0], cfgs[0].readonly) + + mountIdx := 1 // first one is the root + for _, m := range spec.Mounts { + if !specutils.IsGoferMount(m, conf.VFS2) { + continue + } + + if !filepath.IsAbs(m.Destination) { + Fatalf("mount destination must be absolute: %q", m.Destination) + } + if mountIdx >= len(g.ioFDs) { + Fatalf("no FD found for mount. Did you forget --io-fd? FDs: %d, Mount: %+v", len(g.ioFDs), m) + } + + cfgs = append(cfgs, connectionConfig{ + sock: newSocket(g.ioFDs[mountIdx]), + readonly: isReadonlyMount(m.Options) || conf.Overlay, + }) + + log.Infof("Serving %q mapped on FD %d (ro: %t)", m.Destination, g.ioFDs[mountIdx], cfgs[mountIdx].readonly) + mountIdx++ + } + + if mountIdx != len(g.ioFDs) { + Fatalf("too many FDs passed for mounts. mounts: %d, FDs: %d", mountIdx, len(g.ioFDs)) + } + cfgs = cfgs[:mountIdx] + + for _, cfg := range cfgs { + conn, err := server.CreateConnection(cfg.sock, cfg.readonly) + if err != nil { + Fatalf("starting connection on FD %d for gofer mount failed: %v", cfg.sock.FD(), err) + } + server.StartConnection(conn) + } + server.Wait() + log.Infof("All lisafs servers exited.") + return subcommands.ExitSuccess +} + +func (g *Gofer) serve9P(spec *specs.Spec, conf *config.Config, root string) subcommands.ExitStatus { // Start with root mount, then add any other additional mount as needed. ats := make([]p9.Attacher, 0, len(spec.Mounts)+1) ap, err := fsgofer.NewAttachPoint("/", fsgofer.Config{ ROMount: spec.Root.Readonly || conf.Overlay, + HostUDS: conf.FSGoferHostUDS, EnableVerityXattr: conf.Verity, }) if err != nil { @@ -174,7 +262,7 @@ func (g *Gofer) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) mountIdx := 1 // first one is the root for _, m := range spec.Mounts { - if specutils.Is9PMount(m, conf.VFS2) { + if specutils.IsGoferMount(m, conf.VFS2) { cfg := fsgofer.Config{ ROMount: isReadonlyMount(m.Options) || conf.Overlay, HostUDS: conf.FSGoferHostUDS, @@ -197,26 +285,9 @@ func (g *Gofer) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) Fatalf("too many FDs passed for mounts. mounts: %d, FDs: %d", mountIdx, len(g.ioFDs)) } - if conf.FSGoferHostUDS { - filter.InstallUDSFilters() - } - - if conf.Verity { - filter.InstallXattrFilters() - } - - if err := filter.Install(); err != nil { - Fatalf("installing seccomp filters: %v", err) - } - - runServers(ats, g.ioFDs) - return subcommands.ExitSuccess -} - -func runServers(ats []p9.Attacher, ioFDs []int) { // Run the loops and wait for all to exit. var wg sync.WaitGroup - for i, ioFD := range ioFDs { + for i, ioFD := range g.ioFDs { wg.Add(1) go func(ioFD int, at p9.Attacher) { socket, err := unet.NewSocket(ioFD) @@ -232,6 +303,7 @@ func runServers(ats []p9.Attacher, ioFDs []int) { } wg.Wait() log.Infof("All 9P servers exited.") + return subcommands.ExitSuccess } func (g *Gofer) writeMounts(mounts []specs.Mount) error { @@ -362,7 +434,7 @@ func setupRootFS(spec *specs.Spec, conf *config.Config) error { // creates directories as needed. func setupMounts(conf *config.Config, mounts []specs.Mount, root, procPath string) error { for _, m := range mounts { - if !specutils.Is9PMount(m, conf.VFS2) { + if !specutils.IsGoferMount(m, conf.VFS2) { continue } @@ -402,7 +474,7 @@ func setupMounts(conf *config.Config, mounts []specs.Mount, root, procPath strin func resolveMounts(conf *config.Config, mounts []specs.Mount, root string) ([]specs.Mount, error) { cleanMounts := make([]specs.Mount, 0, len(mounts)) for _, m := range mounts { - if !specutils.Is9PMount(m, conf.VFS2) { + if !specutils.IsGoferMount(m, conf.VFS2) { cleanMounts = append(cleanMounts, m) continue } diff --git a/runsc/config/config.go b/runsc/config/config.go index a562f7bf4..91142888f 100644 --- a/runsc/config/config.go +++ b/runsc/config/config.go @@ -193,6 +193,9 @@ type Config struct { // Enables VFS2. VFS2 bool `flag:"vfs2"` + // Enable lisafs. + Lisafs bool `flag:"lisafs"` + // Enables FUSE usage. FUSE bool `flag:"fuse"` diff --git a/runsc/config/flags.go b/runsc/config/flags.go index 1bf23951a..11cea71b8 100644 --- a/runsc/config/flags.go +++ b/runsc/config/flags.go @@ -82,6 +82,7 @@ func RegisterFlags() { flag.Bool("fsgofer-host-uds", false, "allow the gofer to mount Unix Domain Sockets.") flag.Bool("vfs2", false, "enables VFSv2. This uses the new VFS layer that is faster than the previous one.") flag.Bool("fuse", false, "TEST ONLY; use while FUSE in VFSv2 is landing. This allows the use of the new experimental FUSE filesystem.") + flag.Bool("lisafs", false, "Enables lisafs protocol instead of 9P. This is only effective with VFS2.") flag.Bool("cgroupfs", false, "Automatically mount cgroupfs.") // Flags that control sandbox runtime behavior: network related. diff --git a/runsc/container/container.go b/runsc/container/container.go index 50b0dd5e7..9c0004753 100644 --- a/runsc/container/container.go +++ b/runsc/container/container.go @@ -917,7 +917,7 @@ func (c *Container) createGoferProcess(spec *specs.Spec, conf *config.Config, bu // Add root mount and then add any other additional mounts. mountCount := 1 for _, m := range spec.Mounts { - if specutils.Is9PMount(m, conf.VFS2) { + if specutils.IsGoferMount(m, conf.VFS2) { mountCount++ } } diff --git a/runsc/fsgofer/BUILD b/runsc/fsgofer/BUILD index 3280b74fe..c6967cc57 100644 --- a/runsc/fsgofer/BUILD +++ b/runsc/fsgofer/BUILD @@ -9,11 +9,13 @@ go_library( "fsgofer_amd64_unsafe.go", "fsgofer_arm64_unsafe.go", "fsgofer_unsafe.go", + "lisafs.go", ], visibility = ["//runsc:__subpackages__"], deps = [ "//pkg/cleanup", "//pkg/fd", + "//pkg/lisafs", "//pkg/log", "//pkg/p9", "//pkg/sync", diff --git a/runsc/fsgofer/lisafs.go b/runsc/fsgofer/lisafs.go new file mode 100644 index 000000000..9d745f461 --- /dev/null +++ b/runsc/fsgofer/lisafs.go @@ -0,0 +1,52 @@ +// Copyright 2021 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 fsgofer + +import ( + "gvisor.dev/gvisor/pkg/lisafs" +) + +// LisafsServer implements lisafs.ServerImpl for fsgofer. +type LisafsServer struct { + lisafs.Server + config Config +} + +var _ lisafs.ServerImpl = (*LisafsServer)(nil) + +// NewLisafsServer initializes a new lisafs server for fsgofer. +func NewLisafsServer(config Config) *LisafsServer { + s := &LisafsServer{config: config} + s.Server.Init(s) + return s +} + +// Mount implements lisafs.ServerImpl.Mount. +func (s *LisafsServer) Mount(c *lisafs.Connection, mountPath string) (lisafs.ControlFDImpl, lisafs.Inode, error) { + panic("unimplemented") +} + +// MaxMessageSize implements lisafs.ServerImpl.MaxMessageSize. +func (s *LisafsServer) MaxMessageSize() uint32 { + return lisafs.MaxMessageSize() +} + +// SupportedMessages implements lisafs.ServerImpl.SupportedMessages. +func (s *LisafsServer) SupportedMessages() []lisafs.MID { + return []lisafs.MID{ + lisafs.Mount, + lisafs.Channel, + } +} diff --git a/runsc/specutils/specutils.go b/runsc/specutils/specutils.go index 5365b5b1b..9d3b97277 100644 --- a/runsc/specutils/specutils.go +++ b/runsc/specutils/specutils.go @@ -332,9 +332,9 @@ func capsFromNames(names []string, skipSet map[linux.Capability]struct{}) (auth. return auth.CapabilitySetOfMany(caps), nil } -// Is9PMount returns true if the given mount can be mounted as an external +// IsGoferMount returns true if the given mount can be mounted as an external // gofer. -func Is9PMount(m specs.Mount, vfs2Enabled bool) bool { +func IsGoferMount(m specs.Mount, vfs2Enabled bool) bool { MaybeConvertToBindMount(&m) return m.Type == "bind" && m.Source != "" && IsSupportedDevMount(m, vfs2Enabled) } |