Merge pull request #3024 from ridwanmsharif:ridwanmsharif/fuse-stub-impl

PiperOrigin-RevId: 322890087
This commit is contained in:
gVisor bot
2020-07-23 16:34:09 -07:00
8 changed files with 296 additions and 2 deletions
+4
View File
@@ -6,13 +6,17 @@ go_library(
name = "fuse",
srcs = [
"dev.go",
"fusefs.go",
],
visibility = ["//pkg/sentry:internal"],
deps = [
"//pkg/abi/linux",
"//pkg/context",
"//pkg/log",
"//pkg/sentry/fsimpl/devtmpfs",
"//pkg/sentry/fsimpl/kernfs",
"//pkg/sentry/kernel",
"//pkg/sentry/kernel/auth",
"//pkg/sentry/vfs",
"//pkg/syserror",
"//pkg/usermem",
+28
View File
@@ -51,6 +51,9 @@ type DeviceFD struct {
vfs.DentryMetadataFileDescriptionImpl
vfs.NoLockFD
// mounted specifies whether a FUSE filesystem was mounted using the DeviceFD.
mounted bool
// TODO(gvisor.dev/issue/2987): Add all the data structures needed to enqueue
// and deque requests, control synchronization and establish communication
// between the FUSE kernel module and the /dev/fuse character device.
@@ -61,26 +64,51 @@ func (fd *DeviceFD) Release() {}
// PRead implements vfs.FileDescriptionImpl.PRead.
func (fd *DeviceFD) PRead(ctx context.Context, dst usermem.IOSequence, offset int64, opts vfs.ReadOptions) (int64, error) {
// Operations on /dev/fuse don't make sense until a FUSE filesystem is mounted.
if !fd.mounted {
return 0, syserror.EPERM
}
return 0, syserror.ENOSYS
}
// Read implements vfs.FileDescriptionImpl.Read.
func (fd *DeviceFD) Read(ctx context.Context, dst usermem.IOSequence, opts vfs.ReadOptions) (int64, error) {
// Operations on /dev/fuse don't make sense until a FUSE filesystem is mounted.
if !fd.mounted {
return 0, syserror.EPERM
}
return 0, syserror.ENOSYS
}
// PWrite implements vfs.FileDescriptionImpl.PWrite.
func (fd *DeviceFD) PWrite(ctx context.Context, src usermem.IOSequence, offset int64, opts vfs.WriteOptions) (int64, error) {
// Operations on /dev/fuse don't make sense until a FUSE filesystem is mounted.
if !fd.mounted {
return 0, syserror.EPERM
}
return 0, syserror.ENOSYS
}
// Write implements vfs.FileDescriptionImpl.Write.
func (fd *DeviceFD) Write(ctx context.Context, src usermem.IOSequence, opts vfs.WriteOptions) (int64, error) {
// Operations on /dev/fuse don't make sense until a FUSE filesystem is mounted.
if !fd.mounted {
return 0, syserror.EPERM
}
return 0, syserror.ENOSYS
}
// Seek implements vfs.FileDescriptionImpl.Seek.
func (fd *DeviceFD) Seek(ctx context.Context, offset int64, whence int32) (int64, error) {
// Operations on /dev/fuse don't make sense until a FUSE filesystem is mounted.
if !fd.mounted {
return 0, syserror.EPERM
}
return 0, syserror.ENOSYS
}
+200
View File
@@ -0,0 +1,200 @@
// Copyright 2020 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 fuse implements fusefs.
package fuse
import (
"strconv"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/kernfs"
"gvisor.dev/gvisor/pkg/sentry/kernel"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
"gvisor.dev/gvisor/pkg/sentry/vfs"
"gvisor.dev/gvisor/pkg/syserror"
)
// Name is the default filesystem name.
const Name = "fuse"
// FilesystemType implements vfs.FilesystemType.
type FilesystemType struct{}
type filesystemOptions struct {
// userID specifies the numeric uid of the mount owner.
// This option should not be specified by the filesystem owner.
// It is set by libfuse (or, if libfuse is not used, must be set
// by the filesystem itself). For more information, see man page
// for fuse(8)
userID uint32
// groupID specifies the numeric gid of the mount owner.
// This option should not be specified by the filesystem owner.
// It is set by libfuse (or, if libfuse is not used, must be set
// by the filesystem itself). For more information, see man page
// for fuse(8)
groupID uint32
// rootMode specifies the the file mode of the filesystem's root.
rootMode linux.FileMode
}
// filesystem implements vfs.FilesystemImpl.
type filesystem struct {
kernfs.Filesystem
devMinor uint32
// fuseFD is the FD returned when opening /dev/fuse. It is used for communication
// between the FUSE server daemon and the sentry fusefs.
fuseFD *DeviceFD
// opts is the options the fusefs is initialized with.
opts filesystemOptions
}
// Name implements vfs.FilesystemType.Name.
func (FilesystemType) Name() string {
return Name
}
// GetFilesystem implements vfs.FilesystemType.GetFilesystem.
func (fsType FilesystemType) GetFilesystem(ctx context.Context, vfsObj *vfs.VirtualFilesystem, creds *auth.Credentials, source string, opts vfs.GetFilesystemOptions) (*vfs.Filesystem, *vfs.Dentry, error) {
devMinor, err := vfsObj.GetAnonBlockDevMinor()
if err != nil {
return nil, nil, err
}
var fsopts filesystemOptions
mopts := vfs.GenericParseMountOptions(opts.Data)
deviceDescriptorStr, ok := mopts["fd"]
if !ok {
log.Warningf("%s.GetFilesystem: communication file descriptor N (obtained by opening /dev/fuse) must be specified as 'fd=N'", fsType.Name())
return nil, nil, syserror.EINVAL
}
delete(mopts, "fd")
deviceDescriptor, err := strconv.ParseInt(deviceDescriptorStr, 10 /* base */, 32 /* bitSize */)
if err != nil {
return nil, nil, err
}
kernelTask := kernel.TaskFromContext(ctx)
if kernelTask == nil {
log.Warningf("%s.GetFilesystem: couldn't get kernel task from context", fsType.Name())
return nil, nil, syserror.EINVAL
}
fuseFd := kernelTask.GetFileVFS2(int32(deviceDescriptor))
// Parse and set all the other supported FUSE mount options.
// TODO: Expand the supported mount options.
if userIDStr, ok := mopts["user_id"]; ok {
delete(mopts, "user_id")
userID, err := strconv.ParseUint(userIDStr, 10, 32)
if err != nil {
log.Warningf("%s.GetFilesystem: invalid user_id: user_id=%s", fsType.Name(), userIDStr)
return nil, nil, syserror.EINVAL
}
fsopts.userID = uint32(userID)
}
if groupIDStr, ok := mopts["group_id"]; ok {
delete(mopts, "group_id")
groupID, err := strconv.ParseUint(groupIDStr, 10, 32)
if err != nil {
log.Warningf("%s.GetFilesystem: invalid group_id: group_id=%s", fsType.Name(), groupIDStr)
return nil, nil, syserror.EINVAL
}
fsopts.groupID = uint32(groupID)
}
rootMode := linux.FileMode(0777)
modeStr, ok := mopts["rootmode"]
if ok {
delete(mopts, "rootmode")
mode, err := strconv.ParseUint(modeStr, 8, 32)
if err != nil {
log.Warningf("%s.GetFilesystem: invalid mode: %q", fsType.Name(), modeStr)
return nil, nil, syserror.EINVAL
}
rootMode = linux.FileMode(mode)
}
fsopts.rootMode = rootMode
// Check for unparsed options.
if len(mopts) != 0 {
log.Warningf("%s.GetFilesystem: unknown options: %v", fsType.Name(), mopts)
return nil, nil, syserror.EINVAL
}
// Mark the device as ready so it can be used. /dev/fuse can only be used if the FD was used to
// mount a FUSE filesystem.
fuseFD := fuseFd.Impl().(*DeviceFD)
fuseFD.mounted = true
fs := &filesystem{
devMinor: devMinor,
fuseFD: fuseFD,
opts: fsopts,
}
fs.VFSFilesystem().Init(vfsObj, &fsType, fs)
// TODO: dispatch a FUSE_INIT request to the FUSE daemon server before
// returning. Mount will not block on this dispatched request.
// root is the fusefs root directory.
root := fs.newInode(creds, fsopts.rootMode)
return fs.VFSFilesystem(), root.VFSDentry(), nil
}
// Release implements vfs.FilesystemImpl.Release.
func (fs *filesystem) Release() {
fs.Filesystem.VFSFilesystem().VirtualFilesystem().PutAnonBlockDevMinor(fs.devMinor)
fs.Filesystem.Release()
}
// Inode implements kernfs.Inode.
type Inode struct {
kernfs.InodeAttrs
kernfs.InodeNoDynamicLookup
kernfs.InodeNotSymlink
kernfs.InodeDirectoryNoNewChildren
kernfs.OrderedChildren
locks vfs.FileLocks
dentry kernfs.Dentry
}
func (fs *filesystem) newInode(creds *auth.Credentials, mode linux.FileMode) *kernfs.Dentry {
i := &Inode{}
i.InodeAttrs.Init(creds, linux.UNNAMED_MAJOR, fs.devMinor, fs.NextIno(), linux.ModeDirectory|0755)
i.OrderedChildren.Init(kernfs.OrderedChildrenOptions{})
i.dentry.Init(i)
return &i.dentry
}
// Open implements kernfs.Inode.Open.
func (i *Inode) Open(ctx context.Context, rp *vfs.ResolvingPath, vfsd *vfs.Dentry, opts vfs.OpenOptions) (*vfs.FileDescription, error) {
fd, err := kernfs.NewGenericDirectoryFD(rp.Mount(), vfsd, &i.OrderedChildren, &i.locks, &opts)
if err != nil {
return nil, err
}
return fd.VFSFileDescription(), nil
}
+7 -2
View File
@@ -77,8 +77,7 @@ func Mount(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall
// Silently allow MS_NOSUID, since we don't implement set-id bits
// anyway.
const unsupportedFlags = linux.MS_NODEV |
linux.MS_NODIRATIME | linux.MS_STRICTATIME
const unsupportedFlags = linux.MS_NODIRATIME | linux.MS_STRICTATIME
// Linux just allows passing any flags to mount(2) - it won't fail when
// unknown or unsupported flags are passed. Since we don't implement
@@ -94,6 +93,12 @@ func Mount(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall
if flags&linux.MS_NOEXEC == linux.MS_NOEXEC {
opts.Flags.NoExec = true
}
if flags&linux.MS_NODEV == linux.MS_NODEV {
opts.Flags.NoDev = true
}
if flags&linux.MS_NOSUID == linux.MS_NOSUID {
opts.Flags.NoSUID = true
}
if flags&linux.MS_RDONLY == linux.MS_RDONLY {
opts.ReadOnly = true
}
+11
View File
@@ -79,6 +79,17 @@ type MountFlags struct {
// NoATime is equivalent to MS_NOATIME and indicates that the
// filesystem should not update access time in-place.
NoATime bool
// NoDev is equivalent to MS_NODEV and indicates that the
// filesystem should not allow access to devices (special files).
// TODO(gVisor.dev/issue/3186): respect this flag in non FUSE
// filesystems.
NoDev bool
// NoSUID is equivalent to MS_NOSUID and indicates that the
// filesystem should not honor set-user-ID and set-group-ID bits or
// file capabilities when executing programs.
NoSUID bool
}
// MountOptions contains options to VirtualFilesystem.MountAt().
+5
View File
@@ -77,6 +77,10 @@ func registerFilesystems(k *kernel.Kernel) error {
AllowUserMount: true,
AllowUserList: true,
})
vfsObj.MustRegisterFilesystemType(fuse.Name, &fuse.FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{
AllowUserMount: true,
AllowUserList: true,
})
// Setup files in devtmpfs.
if err := memdev.Register(vfsObj); err != nil {
@@ -119,6 +123,7 @@ func registerFilesystems(k *kernel.Kernel) error {
return fmt.Errorf("creating fusedev devtmpfs files: %w", err)
}
}
return nil
}
+13
View File
@@ -161,6 +161,19 @@ TEST(DevTest, OpenDevFuse) {
ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/fuse", O_RDONLY));
}
TEST(DevTest, ReadDevFuseWithoutMount) {
// Note(gvisor.dev/issue/3076) This won't work in the sentry until the new
// device registration is complete.
SKIP_IF(IsRunningWithVFS1() || IsRunningOnGvisor());
const FileDescriptor fd =
ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/fuse", O_RDONLY));
std::vector<char> buf(1);
EXPECT_THAT(ReadFd(fd.get(), buf.data(), sizeof(buf)),
SyscallFailsWithErrno(EPERM));
}
} // namespace
} // namespace testing
+28
View File
@@ -321,6 +321,34 @@ TEST(MountTest, RenameRemoveMountPoint) {
ASSERT_THAT(rmdir(dir.path().c_str()), SyscallFailsWithErrno(EBUSY));
}
TEST(MountTest, MountFuseFilesystemNoDevice) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
// Note(gvisor.dev/issue/3076) This won't work in the sentry until the new
// device registration is complete.
SKIP_IF(IsRunningWithVFS1() || IsRunningOnGvisor());
auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
EXPECT_THAT(mount("", dir.path().c_str(), "fuse", 0, ""),
SyscallFailsWithErrno(EINVAL));
}
TEST(MountTest, MountFuseFilesystem) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
// Note(gvisor.dev/issue/3076) This won't work in the sentry until the new
// device registration is complete.
SKIP_IF(IsRunningWithVFS1() || IsRunningOnGvisor());
const FileDescriptor fd =
ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/fuse", O_WRONLY));
std::string mopts = "fd=" + std::to_string(fd.get());
auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
auto const mount =
ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir.path(), "fuse", 0, mopts, 0));
}
} // namespace
} // namespace testing