mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
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:
committed by
gVisor bot
parent
527d08f6af
commit
21b6bc7280
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ go_library(
|
||||
"ioctl.go",
|
||||
"memfd.go",
|
||||
"mmap.go",
|
||||
"mount.go",
|
||||
"path.go",
|
||||
"pipe.go",
|
||||
"poll.go",
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
@@ -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]".
|
||||
|
||||
@@ -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().
|
||||
|
||||
@@ -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
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user