Implement mount(2) and umount2(2) for VFS2.

This is mostly syscall plumbing, VFS2 already implements the internals of
mounts. In addition to the syscall defintions, the following mount-related
mechanisms are updated:

- Implement MS_NOATIME for VFS2, but only for tmpfs and goferfs. The other VFS2
  filesystems don't implement node-level timestamps yet.

- Implement the 'mode', 'uid' and 'gid' mount options for VFS2's tmpfs.

- Plumb mount namespace ownership, which is necessary for checking appropriate
  capabilities during mount(2).

Updates #1035

PiperOrigin-RevId: 315035352
This commit is contained in:
Rahat Mahmood
2020-06-05 19:12:03 -07:00
committed by gVisor bot
parent 527d08f6af
commit 21b6bc7280
11 changed files with 247 additions and 20 deletions
+3
View File
@@ -38,6 +38,9 @@ func statxTimestampFromDentry(ns int64) linux.StatxTimestamp {
// Preconditions: fs.interop != InteropModeShared.
func (d *dentry) touchAtime(mnt *vfs.Mount) {
if mnt.Flags.NoATime {
return
}
if err := mnt.CheckBeginWrite(); err != nil {
return
}
+38 -3
View File
@@ -30,6 +30,7 @@ package tmpfs
import (
"fmt"
"math"
"strconv"
"strings"
"sync/atomic"
@@ -124,14 +125,45 @@ func (fstype FilesystemType) GetFilesystem(ctx context.Context, vfsObj *vfs.Virt
}
fs.vfsfs.Init(vfsObj, newFSType, &fs)
mopts := vfs.GenericParseMountOptions(opts.Data)
defaultMode := linux.FileMode(0777)
if modeStr, ok := mopts["mode"]; ok {
mode, err := strconv.ParseUint(modeStr, 8, 32)
if err != nil {
return nil, nil, fmt.Errorf("Mount option \"mode='%v'\" not parsable: %v", modeStr, err)
}
defaultMode = linux.FileMode(mode)
}
defaultOwnerCreds := creds.Fork()
if uidStr, ok := mopts["uid"]; ok {
uid, err := strconv.ParseInt(uidStr, 10, 32)
if err != nil {
return nil, nil, fmt.Errorf("Mount option \"uid='%v'\" not parsable: %v", uidStr, err)
}
if err := defaultOwnerCreds.SetUID(auth.UID(uid)); err != nil {
return nil, nil, fmt.Errorf("Error using mount option \"uid='%v'\": %v", uidStr, err)
}
}
if gidStr, ok := mopts["gid"]; ok {
gid, err := strconv.ParseInt(gidStr, 10, 32)
if err != nil {
return nil, nil, fmt.Errorf("Mount option \"gid='%v'\" not parsable: %v", gidStr, err)
}
if err := defaultOwnerCreds.SetGID(auth.GID(gid)); err != nil {
return nil, nil, fmt.Errorf("Error using mount option \"gid='%v'\": %v", gidStr, err)
}
}
var root *dentry
switch rootFileType {
case linux.S_IFREG:
root = fs.newDentry(fs.newRegularFile(creds, 0777))
root = fs.newDentry(fs.newRegularFile(defaultOwnerCreds, defaultMode))
case linux.S_IFLNK:
root = fs.newDentry(fs.newSymlink(creds, tmpfsOpts.RootSymlinkTarget))
root = fs.newDentry(fs.newSymlink(defaultOwnerCreds, tmpfsOpts.RootSymlinkTarget))
case linux.S_IFDIR:
root = &fs.newDirectory(creds, 01777).dentry
root = &fs.newDirectory(defaultOwnerCreds, defaultMode).dentry
default:
fs.vfsfs.DecRef()
return nil, nil, fmt.Errorf("invalid tmpfs root file type: %#o", rootFileType)
@@ -562,6 +594,9 @@ func (i *inode) isDir() bool {
}
func (i *inode) touchAtime(mnt *vfs.Mount) {
if mnt.Flags.NoATime {
return
}
if err := mnt.CheckBeginWrite(); err != nil {
return
}
+28
View File
@@ -232,3 +232,31 @@ func (c *Credentials) UseGID(gid GID) (KGID, error) {
}
return NoID, syserror.EPERM
}
// SetUID translates the provided uid to the root user namespace and updates c's
// uids to it. This performs no permissions or capabilities checks, the caller
// is responsible for ensuring the calling context is permitted to modify c.
func (c *Credentials) SetUID(uid UID) error {
kuid := c.UserNamespace.MapToKUID(uid)
if !kuid.Ok() {
return syserror.EINVAL
}
c.RealKUID = kuid
c.EffectiveKUID = kuid
c.SavedKUID = kuid
return nil
}
// SetGID translates the provided gid to the root user namespace and updates c's
// gids to it. This performs no permissions or capabilities checks, the caller
// is responsible for ensuring the calling context is permitted to modify c.
func (c *Credentials) SetGID(gid GID) error {
kgid := c.UserNamespace.MapToKGID(gid)
if !kgid.Ok() {
return syserror.EINVAL
}
c.RealKGID = kgid
c.EffectiveKGID = kgid
c.SavedKGID = kgid
return nil
}
+1
View File
@@ -16,6 +16,7 @@ go_library(
"ioctl.go",
"memfd.go",
"mmap.go",
"mount.go",
"path.go",
"pipe.go",
"poll.go",
+145
View File
@@ -0,0 +1,145 @@
// 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 vfs2
import (
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/sentry/arch"
"gvisor.dev/gvisor/pkg/sentry/kernel"
"gvisor.dev/gvisor/pkg/sentry/vfs"
"gvisor.dev/gvisor/pkg/syserror"
"gvisor.dev/gvisor/pkg/usermem"
)
// Mount implements Linux syscall mount(2).
func Mount(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
sourceAddr := args[0].Pointer()
targetAddr := args[1].Pointer()
typeAddr := args[2].Pointer()
flags := args[3].Uint64()
dataAddr := args[4].Pointer()
// For null-terminated strings related to mount(2), Linux copies in at most
// a page worth of data. See fs/namespace.c:copy_mount_string().
fsType, err := t.CopyInString(typeAddr, usermem.PageSize)
if err != nil {
return 0, nil, err
}
source, err := t.CopyInString(sourceAddr, usermem.PageSize)
if err != nil {
return 0, nil, err
}
targetPath, err := copyInPath(t, targetAddr)
if err != nil {
return 0, nil, err
}
data := ""
if dataAddr != 0 {
// In Linux, a full page is always copied in regardless of null
// character placement, and the address is passed to each file system.
// Most file systems always treat this data as a string, though, and so
// do all of the ones we implement.
data, err = t.CopyInString(dataAddr, usermem.PageSize)
if err != nil {
return 0, nil, err
}
}
// Ignore magic value that was required before Linux 2.4.
if flags&linux.MS_MGC_MSK == linux.MS_MGC_VAL {
flags = flags &^ linux.MS_MGC_MSK
}
// Must have CAP_SYS_ADMIN in the current mount namespace's associated user
// namespace.
creds := t.Credentials()
if !creds.HasCapabilityIn(linux.CAP_SYS_ADMIN, t.MountNamespaceVFS2().Owner) {
return 0, nil, syserror.EPERM
}
const unsupportedOps = linux.MS_REMOUNT | linux.MS_BIND |
linux.MS_SHARED | linux.MS_PRIVATE | linux.MS_SLAVE |
linux.MS_UNBINDABLE | linux.MS_MOVE
// Silently allow MS_NOSUID, since we don't implement set-id bits
// anyway.
const unsupportedFlags = linux.MS_NODEV |
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
// everything, we fail explicitly on flags that are unimplemented.
if flags&(unsupportedOps|unsupportedFlags) != 0 {
return 0, nil, syserror.EINVAL
}
var opts vfs.MountOptions
if flags&linux.MS_NOATIME == linux.MS_NOATIME {
opts.Flags.NoATime = true
}
if flags&linux.MS_NOEXEC == linux.MS_NOEXEC {
opts.Flags.NoExec = true
}
if flags&linux.MS_RDONLY == linux.MS_RDONLY {
opts.ReadOnly = true
}
opts.GetFilesystemOptions.Data = data
target, err := getTaskPathOperation(t, linux.AT_FDCWD, targetPath, disallowEmptyPath, nofollowFinalSymlink)
if err != nil {
return 0, nil, err
}
defer target.Release()
return 0, nil, t.Kernel().VFS().MountAt(t, creds, source, &target.pop, fsType, &opts)
}
// Umount2 implements Linux syscall umount2(2).
func Umount2(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
addr := args[0].Pointer()
flags := args[1].Int()
// Must have CAP_SYS_ADMIN in the mount namespace's associated user
// namespace.
//
// Currently, this is always the init task's user namespace.
creds := t.Credentials()
if !creds.HasCapabilityIn(linux.CAP_SYS_ADMIN, t.MountNamespaceVFS2().Owner) {
return 0, nil, syserror.EPERM
}
const unsupported = linux.MNT_FORCE | linux.MNT_EXPIRE
if flags&unsupported != 0 {
return 0, nil, syserror.EINVAL
}
path, err := copyInPath(t, addr)
if err != nil {
return 0, nil, err
}
tpop, err := getTaskPathOperation(t, linux.AT_FDCWD, path, disallowEmptyPath, nofollowFinalSymlink)
if err != nil {
return 0, nil, err
}
defer tpop.Release()
opts := vfs.UmountOptions{
Flags: uint32(flags),
}
return 0, nil, t.Kernel().VFS().UmountAt(t, creds, &tpop.pop, &opts)
}
+2 -2
View File
@@ -90,8 +90,8 @@ func Override() {
s.Table[138] = syscalls.Supported("fstatfs", Fstatfs)
s.Table[161] = syscalls.Supported("chroot", Chroot)
s.Table[162] = syscalls.Supported("sync", Sync)
delete(s.Table, 165) // mount
delete(s.Table, 166) // umount2
s.Table[165] = syscalls.Supported("mount", Mount)
s.Table[166] = syscalls.Supported("umount2", Umount2)
delete(s.Table, 187) // readahead
s.Table[188] = syscalls.Supported("setxattr", Setxattr)
s.Table[189] = syscalls.Supported("lsetxattr", Lsetxattr)
@@ -43,7 +43,7 @@ type Dentry struct {
// IsAncestorDentry returns true if d is an ancestor of d2; that is, d is
// either d2's parent or an ancestor of d2's parent.
func IsAncestorDentry(d, d2 *Dentry) bool {
for {
for d2 != nil { // Stop at root, where d2.parent == nil.
if d2.parent == d {
return true
}
@@ -52,6 +52,7 @@ func IsAncestorDentry(d, d2 *Dentry) bool {
}
d2 = d2.parent
}
return false
}
// ParentOrSelf returns d.parent. If d.parent is nil, ParentOrSelf returns d.
+22 -12
View File
@@ -55,6 +55,10 @@ type Mount struct {
// ID is the immutable mount ID.
ID uint64
// Flags contains settings as specified for mount(2), e.g. MS_NOEXEC, except
// for MS_RDONLY which is tracked in "writers". Immutable.
Flags MountFlags
// key is protected by VirtualFilesystem.mountMu and
// VirtualFilesystem.mounts.seq, and may be nil. References are held on
// key.parent and key.point if they are not nil.
@@ -81,10 +85,6 @@ type Mount struct {
// umounted is true. umounted is protected by VirtualFilesystem.mountMu.
umounted bool
// flags contains settings as specified for mount(2), e.g. MS_NOEXEC, except
// for MS_RDONLY which is tracked in "writers".
flags MountFlags
// The lower 63 bits of writers is the number of calls to
// Mount.CheckBeginWrite() that have not yet been paired with a call to
// Mount.EndWrite(). The MSB of writers is set if MS_RDONLY is in effect.
@@ -95,10 +95,10 @@ type Mount struct {
func newMount(vfs *VirtualFilesystem, fs *Filesystem, root *Dentry, mntns *MountNamespace, opts *MountOptions) *Mount {
mnt := &Mount{
ID: atomic.AddUint64(&vfs.lastMountID, 1),
Flags: opts.Flags,
vfs: vfs,
fs: fs,
root: root,
flags: opts.Flags,
ns: mntns,
refs: 1,
}
@@ -113,13 +113,12 @@ func (mnt *Mount) Options() MountOptions {
mnt.vfs.mountMu.Lock()
defer mnt.vfs.mountMu.Unlock()
return MountOptions{
Flags: mnt.flags,
Flags: mnt.Flags,
ReadOnly: mnt.readOnly(),
}
}
// A MountNamespace is a collection of Mounts.
//
// A MountNamespace is a collection of Mounts.//
// MountNamespaces are reference-counted. Unless otherwise specified, all
// MountNamespace methods require that a reference is held.
//
@@ -127,6 +126,9 @@ func (mnt *Mount) Options() MountOptions {
//
// +stateify savable
type MountNamespace struct {
// Owner is the usernamespace that owns this mount namespace.
Owner *auth.UserNamespace
// root is the MountNamespace's root mount. root is immutable.
root *Mount
@@ -163,6 +165,7 @@ func (vfs *VirtualFilesystem) NewMountNamespace(ctx context.Context, creds *auth
return nil, err
}
mntns := &MountNamespace{
Owner: creds.UserNamespace,
refs: 1,
mountpoints: make(map[*Dentry]uint32),
}
@@ -279,6 +282,9 @@ func (vfs *VirtualFilesystem) UmountAt(ctx context.Context, creds *auth.Credenti
}
// MNT_FORCE is currently unimplemented except for the permission check.
// Force unmounting specifically requires CAP_SYS_ADMIN in the root user
// namespace, and not in the owner user namespace for the target mount. See
// fs/namespace.c:SYSCALL_DEFINE2(umount, ...)
if opts.Flags&linux.MNT_FORCE != 0 && creds.HasCapabilityIn(linux.CAP_SYS_ADMIN, creds.UserNamespace.Root()) {
return syserror.EPERM
}
@@ -753,7 +759,10 @@ func (vfs *VirtualFilesystem) GenerateProcMounts(ctx context.Context, taskRootDi
if mnt.readOnly() {
opts = "ro"
}
if mnt.flags.NoExec {
if mnt.Flags.NoATime {
opts = ",noatime"
}
if mnt.Flags.NoExec {
opts += ",noexec"
}
@@ -838,11 +847,12 @@ func (vfs *VirtualFilesystem) GenerateProcMountInfo(ctx context.Context, taskRoo
if mnt.readOnly() {
opts = "ro"
}
if mnt.flags.NoExec {
if mnt.Flags.NoATime {
opts = ",noatime"
}
if mnt.Flags.NoExec {
opts += ",noexec"
}
// TODO(gvisor.dev/issue/1193): Add "noatime" if MS_NOATIME is
// set.
fmt.Fprintf(buf, "%s ", opts)
// (7) Optional fields: zero or more fields of the form "tag[:value]".
+4
View File
@@ -75,6 +75,10 @@ type MknodOptions struct {
type MountFlags struct {
// NoExec is equivalent to MS_NOEXEC.
NoExec bool
// NoATime is equivalent to MS_NOATIME and indicates that the
// filesystem should not update access time in-place.
NoATime bool
}
// MountOptions contains options to VirtualFilesystem.MountAt().
+1 -1
View File
@@ -405,7 +405,7 @@ func (vfs *VirtualFilesystem) OpenAt(ctx context.Context, creds *auth.Credential
vfs.putResolvingPath(rp)
if opts.FileExec {
if fd.Mount().flags.NoExec {
if fd.Mount().Flags.NoExec {
fd.DecRef()
return nil, syserror.EACCES
}
+1 -1
View File
@@ -272,7 +272,7 @@ func (c *containerMounter) getMountNameAndOptionsVFS2(conf *Config, m *mountAndF
case "ro":
opts.ReadOnly = true
case "noatime":
// TODO(gvisor.dev/issue/1193): Implement MS_NOATIME.
opts.Flags.NoATime = true
case "noexec":
opts.Flags.NoExec = true
default: