Plumb VFS2 imported fds into virtual filesystem.

- When setting up the virtual filesystem, mount a host.filesystem to contain
  all files that need to be imported.
- Make read/preadv syscalls to the host in cases where preadv2 may not be
  supported yet (likewise for writing).
- Make save/restore functions in kernel/kernel.go return early if vfs2 is
  enabled.

PiperOrigin-RevId: 300922353
This commit is contained in:
Dean Deng
2020-03-14 07:14:33 -07:00
committed by gVisor bot
parent 45a8ae240d
commit 5e413cad10
11 changed files with 246 additions and 70 deletions
+3
View File
@@ -266,6 +266,9 @@ type Statx struct {
DevMinor uint32
}
// SizeOfStatx is the size of a Statx struct.
var SizeOfStatx = binary.Size(Statx{})
// FileMode represents a mode_t.
type FileMode uint16
+2
View File
@@ -32,6 +32,8 @@ func newSCMRights(fds []int) control.SCMRights {
}
// Files implements control.SCMRights.Files.
//
// TODO(gvisor.dev/issue/2017): Port to VFS2.
func (c *scmRights) Files(ctx context.Context, max int) (control.RightsFiles, bool) {
n := max
var trunc bool
+2
View File
@@ -9,9 +9,11 @@ go_library(
"host.go",
"util.go",
],
visibility = ["//pkg/sentry:internal"],
deps = [
"//pkg/abi/linux",
"//pkg/context",
"//pkg/fd",
"//pkg/log",
"//pkg/refs",
"//pkg/safemem",
+27 -18
View File
@@ -21,6 +21,7 @@ import (
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/fd"
"gvisor.dev/gvisor/pkg/safemem"
"gvisor.dev/gvisor/pkg/sentry/memmap"
"gvisor.dev/gvisor/pkg/sentry/vfs"
@@ -64,9 +65,7 @@ func (f *defaultFileFD) Read(ctx context.Context, dst usermem.IOSequence, opts v
panic("files that can return EWOULDBLOCK (sockets, pipes, etc.) cannot be memory mapped")
}
f.mu.Lock()
n, err := readFromHostFD(ctx, f.inode.hostFD, dst, -1, int(opts.Flags))
f.mu.Unlock()
if isBlockError(err) {
// If we got any data at all, return it as a "completed" partial read
// rather than retrying until complete.
@@ -86,16 +85,22 @@ func (f *defaultFileFD) Read(ctx context.Context, dst usermem.IOSequence, opts v
return n, err
}
func readFromHostFD(ctx context.Context, fd int, dst usermem.IOSequence, offset int64, flags int) (int64, error) {
if flags&^(linux.RWF_VALID) != 0 {
func readFromHostFD(ctx context.Context, hostFD int, dst usermem.IOSequence, offset int64, flags int) (int64, error) {
// TODO(gvisor.dev/issue/1672): Support select preadv2 flags.
if flags != 0 {
return 0, syserror.EOPNOTSUPP
}
reader := safemem.FromVecReaderFunc{
func(srcs [][]byte) (int64, error) {
n, err := unix.Preadv2(fd, srcs, offset, flags)
return int64(n), err
},
var reader safemem.Reader
if offset == -1 {
reader = safemem.FromIOReader{fd.NewReadWriter(hostFD)}
} else {
reader = safemem.FromVecReaderFunc{
func(srcs [][]byte) (int64, error) {
n, err := unix.Preadv(hostFD, srcs, offset)
return int64(n), err
},
}
}
n, err := dst.CopyOutFrom(ctx, reader)
return int64(n), err
@@ -120,9 +125,7 @@ func (f *defaultFileFD) Write(ctx context.Context, src usermem.IOSequence, opts
panic("files that can return EWOULDBLOCK (sockets, pipes, etc.) cannot be memory mapped")
}
f.mu.Lock()
n, err := writeToHostFD(ctx, f.inode.hostFD, src, -1, int(opts.Flags))
f.mu.Unlock()
if isBlockError(err) {
err = syserror.ErrWouldBlock
}
@@ -137,16 +140,22 @@ func (f *defaultFileFD) Write(ctx context.Context, src usermem.IOSequence, opts
return n, err
}
func writeToHostFD(ctx context.Context, fd int, src usermem.IOSequence, offset int64, flags int) (int64, error) {
if flags&^(linux.RWF_VALID) != 0 {
func writeToHostFD(ctx context.Context, hostFD int, src usermem.IOSequence, offset int64, flags int) (int64, error) {
// TODO(gvisor.dev/issue/1672): Support select pwritev2 flags.
if flags != 0 {
return 0, syserror.EOPNOTSUPP
}
writer := safemem.FromVecWriterFunc{
func(srcs [][]byte) (int64, error) {
n, err := unix.Pwritev2(fd, srcs, offset, flags)
return int64(n), err
},
var writer safemem.Writer
if offset == -1 {
writer = safemem.FromIOWriter{fd.NewReadWriter(hostFD)}
} else {
writer = safemem.FromVecWriterFunc{
func(srcs [][]byte) (int64, error) {
n, err := unix.Pwritev(hostFD, srcs, offset)
return int64(n), err
},
}
}
n, err := src.CopyInTo(ctx, writer)
return int64(n), err
+114 -10
View File
@@ -38,10 +38,19 @@ type filesystem struct {
kernfs.Filesystem
}
// NewMount returns a new disconnected mount in vfsObj that may be passed to ImportFD.
func NewMount(vfsObj *vfs.VirtualFilesystem) (*vfs.Mount, error) {
fs := &filesystem{}
fs.Init(vfsObj)
vfsfs := fs.VFSFilesystem()
// NewDisconnectedMount will take an additional reference on vfsfs.
defer vfsfs.DecRef()
return vfsObj.NewDisconnectedMount(vfsfs, nil, &vfs.MountOptions{})
}
// ImportFD sets up and returns a vfs.FileDescription from a donated fd.
func ImportFD(mnt *vfs.Mount, hostFD int, ownerUID auth.KUID, ownerGID auth.KGID, isTTY bool) (*vfs.FileDescription, error) {
// Must be importing to a mount of host.filesystem.
fs, ok := mnt.Filesystem().Impl().(*filesystem)
fs, ok := mnt.Filesystem().Impl().(*kernfs.Filesystem)
if !ok {
return nil, fmt.Errorf("can't import host FDs into filesystems of type %T", mnt.Filesystem().Impl())
}
@@ -54,8 +63,7 @@ func ImportFD(mnt *vfs.Mount, hostFD int, ownerUID auth.KUID, ownerGID auth.KGID
fileMode := linux.FileMode(s.Mode)
fileType := fileMode.FileType()
// Pipes, character devices, and sockets can return EWOULDBLOCK for
// operations that would block.
// Pipes, character devices, and sockets.
isStream := fileType == syscall.S_IFIFO || fileType == syscall.S_IFCHR || fileType == syscall.S_IFSOCK
i := &inode{
@@ -143,11 +151,109 @@ func (i *inode) Mode() linux.FileMode {
// Stat implements kernfs.Inode.
func (i *inode) Stat(_ *vfs.Filesystem, opts vfs.StatOptions) (linux.Statx, error) {
if opts.Mask&linux.STATX__RESERVED != 0 {
return linux.Statx{}, syserror.EINVAL
}
if opts.Sync&linux.AT_STATX_SYNC_TYPE == linux.AT_STATX_SYNC_TYPE {
return linux.Statx{}, syserror.EINVAL
}
// Limit our host call only to known flags.
mask := opts.Mask & linux.STATX_ALL
var s unix.Statx_t
if err := unix.Statx(i.hostFD, "", int(unix.AT_EMPTY_PATH|opts.Sync), int(opts.Mask), &s); err != nil {
err := unix.Statx(i.hostFD, "", int(unix.AT_EMPTY_PATH|opts.Sync), int(mask), &s)
// Fallback to fstat(2), if statx(2) is not supported on the host.
//
// TODO(b/151263641): Remove fallback.
if err == syserror.ENOSYS {
return i.fstat(opts)
} else if err != nil {
return linux.Statx{}, err
}
ls := unixToLinuxStatx(s)
ls := linux.Statx{Mask: mask}
// Unconditionally fill blksize, attributes, and device numbers, as indicated
// by /include/uapi/linux/stat.h.
//
// RdevMajor/RdevMinor are left as zero, so as not to expose host device
// numbers.
//
// TODO(gvisor.dev/issue/1672): Use kernfs-specific, internally defined
// device numbers. If we use the device number from the host, it may collide
// with another sentry-internal device number. We handle device/inode
// numbers without relying on the host to prevent collisions.
ls.Blksize = s.Blksize
ls.Attributes = s.Attributes
ls.AttributesMask = s.Attributes_mask
if mask|linux.STATX_TYPE != 0 {
ls.Mode |= s.Mode & linux.S_IFMT
}
if mask|linux.STATX_MODE != 0 {
ls.Mode |= s.Mode &^ linux.S_IFMT
}
if mask|linux.STATX_NLINK != 0 {
ls.Nlink = s.Nlink
}
if mask|linux.STATX_ATIME != 0 {
ls.Atime = unixToLinuxStatxTimestamp(s.Atime)
}
if mask|linux.STATX_BTIME != 0 {
ls.Btime = unixToLinuxStatxTimestamp(s.Btime)
}
if mask|linux.STATX_CTIME != 0 {
ls.Ctime = unixToLinuxStatxTimestamp(s.Ctime)
}
if mask|linux.STATX_MTIME != 0 {
ls.Mtime = unixToLinuxStatxTimestamp(s.Mtime)
}
if mask|linux.STATX_SIZE != 0 {
ls.Size = s.Size
}
if mask|linux.STATX_BLOCKS != 0 {
ls.Blocks = s.Blocks
}
// Use our own internal inode number and file owner.
if mask|linux.STATX_INO != 0 {
ls.Ino = i.ino
}
if mask|linux.STATX_UID != 0 {
ls.UID = uint32(i.uid)
}
if mask|linux.STATX_GID != 0 {
ls.GID = uint32(i.gid)
}
return ls, nil
}
// fstat is a best-effort fallback for inode.Stat() if the host does not
// support statx(2).
//
// We ignore the mask and sync flags in opts and simply supply
// STATX_BASIC_STATS, as fstat(2) itself does not allow the specification
// of a mask or sync flags. fstat(2) does not provide any metadata
// equivalent to Statx.Attributes, Statx.AttributesMask, or Statx.Btime, so
// those fields remain empty.
func (i *inode) fstat(opts vfs.StatOptions) (linux.Statx, error) {
var s unix.Stat_t
if err := unix.Fstat(i.hostFD, &s); err != nil {
return linux.Statx{}, err
}
// Note that rdev numbers are left as 0; do not expose host device numbers.
ls := linux.Statx{
Mask: linux.STATX_BASIC_STATS,
Blksize: uint32(s.Blksize),
Nlink: uint32(s.Nlink),
Mode: uint16(s.Mode),
Size: uint64(s.Size),
Blocks: uint64(s.Blocks),
Atime: timespecToStatxTimestamp(s.Atim),
Ctime: timespecToStatxTimestamp(s.Ctim),
Mtime: timespecToStatxTimestamp(s.Mtim),
}
// Use our own internal inode number and file owner.
//
@@ -159,9 +265,6 @@ func (i *inode) Stat(_ *vfs.Filesystem, opts vfs.StatOptions) (linux.Statx, erro
ls.UID = uint32(i.uid)
ls.GID = uint32(i.gid)
// Update file mode from the host.
i.mode = linux.FileMode(ls.Mode)
return ls, nil
}
@@ -217,7 +320,6 @@ func (i *inode) Open(rp *vfs.ResolvingPath, vfsd *vfs.Dentry, opts vfs.OpenOptio
}
func (i *inode) open(d *vfs.Dentry, mnt *vfs.Mount) (*vfs.FileDescription, error) {
fileType := i.mode.FileType()
if fileType == syscall.S_IFSOCK {
if i.isTTY {
@@ -227,6 +329,8 @@ func (i *inode) open(d *vfs.Dentry, mnt *vfs.Mount) (*vfs.FileDescription, error
return nil, errors.New("importing host sockets not supported")
}
// TODO(gvisor.dev/issue/1672): Whitelist specific file types here, so that
// we don't allow importing arbitrary file types without proper support.
if i.isTTY {
// TODO(gvisor.dev/issue/1672): support importing host fd as TTY.
return nil, errors.New("importing host fd as TTY not supported")
+4 -24
View File
@@ -35,34 +35,14 @@ func toTimespec(ts linux.StatxTimestamp, omit bool) unix.Timespec {
}
}
func unixToLinuxStatx(s unix.Statx_t) linux.Statx {
return linux.Statx{
Mask: s.Mask,
Blksize: s.Blksize,
Attributes: s.Attributes,
Nlink: s.Nlink,
UID: s.Uid,
GID: s.Gid,
Mode: s.Mode,
Ino: s.Ino,
Size: s.Size,
Blocks: s.Blocks,
AttributesMask: s.Attributes_mask,
Atime: unixToLinuxStatxTimestamp(s.Atime),
Btime: unixToLinuxStatxTimestamp(s.Btime),
Ctime: unixToLinuxStatxTimestamp(s.Ctime),
Mtime: unixToLinuxStatxTimestamp(s.Mtime),
RdevMajor: s.Rdev_major,
RdevMinor: s.Rdev_minor,
DevMajor: s.Dev_major,
DevMinor: s.Dev_minor,
}
}
func unixToLinuxStatxTimestamp(ts unix.StatxTimestamp) linux.StatxTimestamp {
return linux.StatxTimestamp{Sec: ts.Sec, Nsec: ts.Nsec}
}
func timespecToStatxTimestamp(ts unix.Timespec) linux.StatxTimestamp {
return linux.StatxTimestamp{Sec: int64(ts.Sec), Nsec: uint32(ts.Nsec)}
}
// wouldBlock returns true for file types that can return EWOULDBLOCK
// for blocking operations, e.g. pipes, character devices, and sockets.
func wouldBlock(fileType uint32) bool {
+28 -12
View File
@@ -467,6 +467,11 @@ func (k *Kernel) flushMountSourceRefs() error {
//
// Precondition: Must be called with the kernel paused.
func (ts *TaskSet) forEachFDPaused(f func(*fs.File, *vfs.FileDescription) error) (err error) {
// TODO(gvisor.dev/issue/1663): Add save support for VFS2.
if VFS2Enabled {
return nil
}
ts.mu.RLock()
defer ts.mu.RUnlock()
for t := range ts.Root.tids {
@@ -484,7 +489,7 @@ func (ts *TaskSet) forEachFDPaused(f func(*fs.File, *vfs.FileDescription) error)
}
func (ts *TaskSet) flushWritesToFiles(ctx context.Context) error {
// TODO(gvisor.dev/issues/1663): Add save support for VFS2.
// TODO(gvisor.dev/issue/1663): Add save support for VFS2.
return ts.forEachFDPaused(func(file *fs.File, _ *vfs.FileDescription) error {
if flags := file.Flags(); !flags.Write {
return nil
@@ -533,6 +538,11 @@ func (k *Kernel) invalidateUnsavableMappings(ctx context.Context) error {
}
func (ts *TaskSet) unregisterEpollWaiters() {
// TODO(gvisor.dev/issue/1663): Add save support for VFS2.
if VFS2Enabled {
return
}
ts.mu.RLock()
defer ts.mu.RUnlock()
for t := range ts.Root.tids {
@@ -1005,11 +1015,14 @@ func (k *Kernel) pauseTimeLocked() {
// This means we'll iterate FDTables shared by multiple tasks repeatedly,
// but ktime.Timer.Pause is idempotent so this is harmless.
if t.fdTable != nil {
t.fdTable.forEach(func(_ int32, file *fs.File, _ *vfs.FileDescription, _ FDFlags) {
if tfd, ok := file.FileOperations.(*timerfd.TimerOperations); ok {
tfd.PauseTimer()
}
})
// TODO(gvisor.dev/issue/1663): Add save support for VFS2.
if !VFS2Enabled {
t.fdTable.forEach(func(_ int32, file *fs.File, _ *vfs.FileDescription, _ FDFlags) {
if tfd, ok := file.FileOperations.(*timerfd.TimerOperations); ok {
tfd.PauseTimer()
}
})
}
}
}
k.timekeeper.PauseUpdates()
@@ -1034,12 +1047,15 @@ func (k *Kernel) resumeTimeLocked() {
it.ResumeTimer()
}
}
if t.fdTable != nil {
t.fdTable.forEach(func(_ int32, file *fs.File, _ *vfs.FileDescription, _ FDFlags) {
if tfd, ok := file.FileOperations.(*timerfd.TimerOperations); ok {
tfd.ResumeTimer()
}
})
// TODO(gvisor.dev/issue/1663): Add save support for VFS2.
if !VFS2Enabled {
if t.fdTable != nil {
t.fdTable.forEach(func(_ int32, file *fs.File, _ *vfs.FileDescription, _ FDFlags) {
if tfd, ok := file.FileOperations.(*timerfd.TimerOperations); ok {
tfd.ResumeTimer()
}
})
}
}
}
}
+4 -1
View File
@@ -136,7 +136,10 @@ func Statx(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall
mask := args[3].Uint()
statxAddr := args[4].Pointer()
if mask&linux.STATX__RESERVED > 0 {
if mask&linux.STATX__RESERVED != 0 {
return 0, nil, syserror.EINVAL
}
if flags&^(linux.AT_SYMLINK_NOFOLLOW|linux.AT_EMPTY_PATH|linux.AT_STATX_SYNC_TYPE) != 0 {
return 0, nil, syserror.EINVAL
}
if flags&linux.AT_STATX_SYNC_TYPE == linux.AT_STATX_SYNC_TYPE {
+5 -1
View File
@@ -150,7 +150,11 @@ func Statx(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall
mask := args[3].Uint()
statxAddr := args[4].Pointer()
if flags&^(linux.AT_EMPTY_PATH|linux.AT_SYMLINK_NOFOLLOW) != 0 {
if flags&^(linux.AT_EMPTY_PATH|linux.AT_SYMLINK_NOFOLLOW|linux.AT_STATX_SYNC_TYPE) != 0 {
return 0, nil, syserror.EINVAL
}
if mask&linux.STATX__RESERVED != 0 {
return 0, nil, syserror.EINVAL
}
+1
View File
@@ -284,6 +284,7 @@ var allowedSyscalls = seccomp.SyscallRules{
{seccomp.AllowAny{}, seccomp.AllowValue(syscall.SHUT_RDWR)},
},
syscall.SYS_SIGALTSTACK: {},
unix.SYS_STATX: {},
syscall.SYS_SYNC_FILE_RANGE: {},
syscall.SYS_TGKILL: []seccomp.Rule{
{
+56 -4
View File
@@ -607,7 +607,7 @@ int statx(int dirfd, const char* pathname, int flags, unsigned int mask,
}
TEST_F(StatTest, StatxAbsPath) {
SKIP_IF(!IsRunningOnGvisor() && statx(-1, nullptr, 0, 0, 0) < 0 &&
SKIP_IF(!IsRunningOnGvisor() && statx(-1, nullptr, 0, 0, nullptr) < 0 &&
errno == ENOSYS);
struct kernel_statx stx;
@@ -617,7 +617,7 @@ TEST_F(StatTest, StatxAbsPath) {
}
TEST_F(StatTest, StatxRelPathDirFD) {
SKIP_IF(!IsRunningOnGvisor() && statx(-1, nullptr, 0, 0, 0) < 0 &&
SKIP_IF(!IsRunningOnGvisor() && statx(-1, nullptr, 0, 0, nullptr) < 0 &&
errno == ENOSYS);
struct kernel_statx stx;
@@ -631,7 +631,7 @@ TEST_F(StatTest, StatxRelPathDirFD) {
}
TEST_F(StatTest, StatxRelPathCwd) {
SKIP_IF(!IsRunningOnGvisor() && statx(-1, nullptr, 0, 0, 0) < 0 &&
SKIP_IF(!IsRunningOnGvisor() && statx(-1, nullptr, 0, 0, nullptr) < 0 &&
errno == ENOSYS);
ASSERT_THAT(chdir(GetAbsoluteTestTmpdir().c_str()), SyscallSucceeds());
@@ -643,7 +643,7 @@ TEST_F(StatTest, StatxRelPathCwd) {
}
TEST_F(StatTest, StatxEmptyPath) {
SKIP_IF(!IsRunningOnGvisor() && statx(-1, nullptr, 0, 0, 0) < 0 &&
SKIP_IF(!IsRunningOnGvisor() && statx(-1, nullptr, 0, 0, nullptr) < 0 &&
errno == ENOSYS);
const auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(test_file_name_, O_RDONLY));
@@ -653,6 +653,58 @@ TEST_F(StatTest, StatxEmptyPath) {
EXPECT_TRUE(S_ISREG(stx.stx_mode));
}
TEST_F(StatTest, StatxDoesNotRejectExtraneousMaskBits) {
SKIP_IF(!IsRunningOnGvisor() && statx(-1, nullptr, 0, 0, nullptr) < 0 &&
errno == ENOSYS);
struct kernel_statx stx;
// Set all mask bits except for STATX__RESERVED.
uint mask = 0xffffffff & ~0x80000000;
EXPECT_THAT(statx(-1, test_file_name_.c_str(), 0, mask, &stx),
SyscallSucceeds());
EXPECT_TRUE(S_ISREG(stx.stx_mode));
}
TEST_F(StatTest, StatxRejectsReservedMaskBit) {
SKIP_IF(!IsRunningOnGvisor() && statx(-1, nullptr, 0, 0, nullptr) < 0 &&
errno == ENOSYS);
struct kernel_statx stx;
// Set STATX__RESERVED in the mask.
EXPECT_THAT(statx(-1, test_file_name_.c_str(), 0, 0x80000000, &stx),
SyscallFailsWithErrno(EINVAL));
}
TEST_F(StatTest, StatxSymlink) {
SKIP_IF(!IsRunningOnGvisor() && statx(-1, nullptr, 0, 0, nullptr) < 0 &&
errno == ENOSYS);
std::string parent_dir = "/tmp";
TempPath link = ASSERT_NO_ERRNO_AND_VALUE(
TempPath::CreateSymlinkTo(parent_dir, test_file_name_));
std::string p = link.path();
struct kernel_statx stx;
EXPECT_THAT(statx(AT_FDCWD, p.c_str(), AT_SYMLINK_NOFOLLOW, STATX_ALL, &stx),
SyscallSucceeds());
EXPECT_TRUE(S_ISLNK(stx.stx_mode));
EXPECT_THAT(statx(AT_FDCWD, p.c_str(), 0, STATX_ALL, &stx),
SyscallSucceeds());
EXPECT_TRUE(S_ISREG(stx.stx_mode));
}
TEST_F(StatTest, StatxInvalidFlags) {
SKIP_IF(!IsRunningOnGvisor() && statx(-1, nullptr, 0, 0, nullptr) < 0 &&
errno == ENOSYS);
struct kernel_statx stx;
EXPECT_THAT(statx(AT_FDCWD, test_file_name_.c_str(), 12345, 0, &stx),
SyscallFailsWithErrno(EINVAL));
EXPECT_THAT(statx(AT_FDCWD, test_file_name_.c_str(),
0x6000 /* AT_STATX_SYNC_TYPE */, 0, &stx),
SyscallFailsWithErrno(EINVAL));
}
} // namespace
} // namespace testing