mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Merge pull request #3024 from ridwanmsharif:ridwanmsharif/fuse-stub-impl
PiperOrigin-RevId: 322890087
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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().
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user