Implement memfd_create.

Memfds are simply anonymous tmpfs files with no associated
mounts. Also implementing file seals, which Linux only implements for
memfds at the moment.

PiperOrigin-RevId: 240450031
Change-Id: I31de78b950101ae8d7a13d0e93fe52d98ea06f2f
This commit is contained in:
Rahat Mahmood
2019-03-26 16:16:57 -07:00
committed by Shentubot
parent 79aca14a0c
commit 06ec97a3f8
7 changed files with 793 additions and 1 deletions
+18
View File
@@ -236,3 +236,21 @@ var fileType = abi.ValueSet{
ModeCharacterDevice: "S_IFCHR",
ModeNamedPipe: "S_IFIFO",
}
// Constants for memfd_create(2). Source: include/uapi/linux/memfd.h
const (
MFD_CLOEXEC = 0x0001
MFD_ALLOW_SEALING = 0x0002
)
// Constants related to file seals. Source: include/uapi/{asm-generic,linux}/fcntl.h
const (
F_LINUX_SPECIFIC_BASE = 1024
F_ADD_SEALS = F_LINUX_SPECIFIC_BASE + 9
F_GET_SEALS = F_LINUX_SPECIFIC_BASE + 10
F_SEAL_SEAL = 0x0001 // Prevent further seals from being set.
F_SEAL_SHRINK = 0x0002 // Prevent file from shrinking.
F_SEAL_GROW = 0x0004 // Prevent file from growing.
F_SEAL_WRITE = 0x0008 // Prevent writes.
)
+151
View File
@@ -15,10 +15,12 @@
package tmpfs
import (
"fmt"
"io"
"sync"
"time"
"gvisor.googlesource.com/gvisor/pkg/abi/linux"
"gvisor.googlesource.com/gvisor/pkg/metric"
"gvisor.googlesource.com/gvisor/pkg/sentry/context"
"gvisor.googlesource.com/gvisor/pkg/sentry/fs"
@@ -29,6 +31,7 @@ import (
"gvisor.googlesource.com/gvisor/pkg/sentry/safemem"
"gvisor.googlesource.com/gvisor/pkg/sentry/usage"
"gvisor.googlesource.com/gvisor/pkg/sentry/usermem"
"gvisor.googlesource.com/gvisor/pkg/syserror"
)
var (
@@ -42,6 +45,8 @@ var (
// These files are backed by pages allocated from a platform.Memory, and may be
// directly mapped.
//
// Lock order: attrMu -> mapsMu -> dataMu.
//
// +stateify savable
type fileInodeOperations struct {
fsutil.InodeGenericChecker `state:"nosave"`
@@ -74,6 +79,17 @@ type fileInodeOperations struct {
// mappings is protected by mapsMu.
mappings memmap.MappingSet
// writableMappingPages tracks how many pages of virtual memory are mapped
// as potentially writable from this file. If a page has multiple mappings,
// each mapping is counted separately.
//
// This counter is susceptible to overflow as we can potentially count
// mappings from many VMAs. We count pages rather than bytes to slightly
// mitigate this.
//
// Protected by mapsMu.
writableMappingPages uint64
dataMu sync.RWMutex `state:"nosave"`
// data maps offsets into the file to offsets into platform.Memory() that
@@ -81,6 +97,11 @@ type fileInodeOperations struct {
//
// data is protected by dataMu.
data fsutil.FileRangeSet
// seals represents file seals on this inode.
//
// Protected by dataMu.
seals uint32
}
var _ fs.InodeOperations = (*fileInodeOperations)(nil)
@@ -91,9 +112,30 @@ func NewInMemoryFile(ctx context.Context, usage usage.MemoryKind, uattr fs.Unsta
attr: uattr,
kernel: kernel.KernelFromContext(ctx),
memUsage: usage,
seals: linux.F_SEAL_SEAL,
}
}
// NewMemfdInode creates a new inode backing a memfd. Memory used by the memfd
// is backed by platform memory.
func NewMemfdInode(ctx context.Context, allowSeals bool) *fs.Inode {
// Per Linux, mm/shmem.c:__shmem_file_setup(), memfd inodes are set up with
// S_IRWXUGO.
perms := fs.PermMask{Read: true, Write: true, Execute: true}
iops := NewInMemoryFile(ctx, usage.Tmpfs, fs.UnstableAttr{
Owner: fs.FileOwnerFromContext(ctx),
Perms: fs.FilePermissions{User: perms, Group: perms, Other: perms}}).(*fileInodeOperations)
if allowSeals {
iops.seals = 0
}
return fs.NewInode(iops, fs.NewNonCachingMountSource(nil, fs.MountSourceFlags{}), fs.StableAttr{
Type: fs.RegularFile,
DeviceID: tmpfsDevice.DeviceID(),
InodeID: tmpfsDevice.NextIno(),
BlockSize: usermem.PageSize,
})
}
// Release implements fs.InodeOperations.Release.
func (f *fileInodeOperations) Release(context.Context) {
f.dataMu.Lock()
@@ -170,6 +212,16 @@ func (f *fileInodeOperations) Truncate(ctx context.Context, _ *fs.Inode, size in
f.dataMu.Lock()
oldSize := f.attr.Size
// Check if current seals allow truncation.
switch {
case size > oldSize && f.seals&linux.F_SEAL_GROW != 0: // Grow sealed
fallthrough
case oldSize > size && f.seals&linux.F_SEAL_SHRINK != 0: // Shrink sealed
f.dataMu.Unlock()
return syserror.EPERM
}
if oldSize != size {
f.attr.Size = size
// Update mtime and ctime.
@@ -370,6 +422,34 @@ func (rw *fileReadWriter) WriteFromBlocks(srcs safemem.BlockSeq) (uint64, error)
return 0, nil
}
// Check if seals prevent either file growth or all writes.
switch {
case rw.f.seals&linux.F_SEAL_WRITE != 0: // Write sealed
return 0, syserror.EPERM
case end > rw.f.attr.Size && rw.f.seals&linux.F_SEAL_GROW != 0: // Grow sealed
// When growth is sealed, Linux effectively allows writes which would
// normally grow the file to partially succeed up to the current EOF,
// rounded down to the page boundary before the EOF.
//
// This happens because writes (and thus the growth check) for tmpfs
// files proceed page-by-page on Linux, and the final write to the page
// containing EOF fails, resulting in a partial write up to the start of
// that page.
//
// To emulate this behaviour, artifically truncate the write to the
// start of the page containing the current EOF.
//
// See Linux, mm/filemap.c:generic_perform_write() and
// mm/shmem.c:shmem_write_begin().
if pgstart := int64(usermem.Addr(rw.f.attr.Size).RoundDown()); end > pgstart {
end = pgstart
}
if end <= rw.offset {
// Truncation would result in no data being written.
return 0, syserror.EPERM
}
}
defer func() {
// If the write ends beyond the file's previous size, it causes the
// file to grow.
@@ -431,7 +511,27 @@ func (rw *fileReadWriter) WriteFromBlocks(srcs safemem.BlockSeq) (uint64, error)
func (f *fileInodeOperations) AddMapping(ctx context.Context, ms memmap.MappingSpace, ar usermem.AddrRange, offset uint64, writable bool) error {
f.mapsMu.Lock()
defer f.mapsMu.Unlock()
f.dataMu.RLock()
defer f.dataMu.RUnlock()
// Reject writable mapping if F_SEAL_WRITE is set.
if f.seals&linux.F_SEAL_WRITE != 0 && writable {
return syserror.EPERM
}
f.mappings.AddMapping(ms, ar, offset, writable)
if writable {
pagesBefore := f.writableMappingPages
// ar is guaranteed to be page aligned per memmap.Mappable.
f.writableMappingPages += uint64(ar.Length() / usermem.PageSize)
if f.writableMappingPages < pagesBefore {
panic(fmt.Sprintf("Overflow while mapping potentially writable pages pointing to a tmpfs file. Before %v, after %v", pagesBefore, f.writableMappingPages))
}
}
return nil
}
@@ -439,7 +539,19 @@ func (f *fileInodeOperations) AddMapping(ctx context.Context, ms memmap.MappingS
func (f *fileInodeOperations) RemoveMapping(ctx context.Context, ms memmap.MappingSpace, ar usermem.AddrRange, offset uint64, writable bool) {
f.mapsMu.Lock()
defer f.mapsMu.Unlock()
f.mappings.RemoveMapping(ms, ar, offset, writable)
if writable {
pagesBefore := f.writableMappingPages
// ar is guaranteed to be page aligned per memmap.Mappable.
f.writableMappingPages -= uint64(ar.Length() / usermem.PageSize)
if f.writableMappingPages > pagesBefore {
panic(fmt.Sprintf("Underflow while unmapping potentially writable pages pointing to a tmpfs file. Before %v, after %v", pagesBefore, f.writableMappingPages))
}
}
}
// CopyMapping implements memmap.Mappable.CopyMapping.
@@ -501,3 +613,42 @@ func (f *fileInodeOperations) Translate(ctx context.Context, required, optional
func (f *fileInodeOperations) InvalidateUnsavable(ctx context.Context) error {
return nil
}
// GetSeals returns the current set of seals on a memfd inode.
func GetSeals(inode *fs.Inode) (uint32, error) {
if f, ok := inode.InodeOperations.(*fileInodeOperations); ok {
f.dataMu.RLock()
defer f.dataMu.RUnlock()
return f.seals, nil
}
// Not a memfd inode.
return 0, syserror.EINVAL
}
// AddSeals adds new file seals to a memfd inode.
func AddSeals(inode *fs.Inode, val uint32) error {
if f, ok := inode.InodeOperations.(*fileInodeOperations); ok {
f.mapsMu.Lock()
defer f.mapsMu.Unlock()
f.dataMu.Lock()
defer f.dataMu.Unlock()
if f.seals&linux.F_SEAL_SEAL != 0 {
// Seal applied which prevents addition of any new seals.
return syserror.EPERM
}
// F_SEAL_WRITE can only be added if there are no active writable maps.
if f.seals&linux.F_SEAL_WRITE == 0 && val&linux.F_SEAL_WRITE != 0 {
if f.writableMappingPages > 0 {
return syserror.EBUSY
}
}
// Seals can only be added, never removed.
f.seals |= val
return nil
}
// Not a memfd inode.
return syserror.EINVAL
}
+1
View File
@@ -63,6 +63,7 @@ go_library(
"//pkg/sentry/fs/anon",
"//pkg/sentry/fs/lock",
"//pkg/sentry/fs/timerfd",
"//pkg/sentry/fs/tmpfs",
"//pkg/sentry/kernel",
"//pkg/sentry/kernel/auth",
"//pkg/sentry/kernel/epoll",
+1 -1
View File
@@ -367,7 +367,7 @@ var AMD64 = &kernel.SyscallTable{
// 316: Renameat2, TODO
317: Seccomp,
318: GetRandom,
// 319: MemfdCreate, TODO
319: MemfdCreate,
320: syscalls.CapError(linux.CAP_SYS_BOOT), // KexecFileLoad, infeasible to support
321: syscalls.CapError(linux.CAP_SYS_ADMIN), // Bpf, requires cap_sys_admin for all commands
// 322: Execveat, TODO
+59
View File
@@ -23,6 +23,7 @@ import (
"gvisor.googlesource.com/gvisor/pkg/sentry/context"
"gvisor.googlesource.com/gvisor/pkg/sentry/fs"
"gvisor.googlesource.com/gvisor/pkg/sentry/fs/lock"
"gvisor.googlesource.com/gvisor/pkg/sentry/fs/tmpfs"
"gvisor.googlesource.com/gvisor/pkg/sentry/kernel"
"gvisor.googlesource.com/gvisor/pkg/sentry/kernel/auth"
"gvisor.googlesource.com/gvisor/pkg/sentry/kernel/fasync"
@@ -933,6 +934,15 @@ func Fcntl(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall
case linux.F_SETOWN:
fSetOwn(t, file, args[2].Int())
return 0, nil, nil
case linux.F_GET_SEALS:
val, err := tmpfs.GetSeals(file.Dirent.Inode)
return uintptr(val), nil, err
case linux.F_ADD_SEALS:
if !file.Flags().Write {
return 0, nil, syserror.EPERM
}
err := tmpfs.AddSeals(file.Dirent.Inode, args[2].Uint())
return 0, nil, err
default:
// Everything else is not yet supported.
return 0, nil, syserror.EINVAL
@@ -2066,3 +2076,52 @@ func Sendfile(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sysc
// arbitrarily.
return uintptr(n), nil, handleIOError(t, n != 0, err, kernel.ERESTARTSYS, "sendfile", inFile)
}
const (
memfdPrefix = "/memfd:"
memfdAllFlags = uint32(linux.MFD_CLOEXEC | linux.MFD_ALLOW_SEALING)
memfdMaxNameLen = linux.NAME_MAX - len(memfdPrefix) + 1
)
// MemfdCreate implements the linux syscall memfd_create(2).
func MemfdCreate(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
addr := args[0].Pointer()
flags := args[1].Uint()
if flags&^memfdAllFlags != 0 {
// Unknown bits in flags.
return 0, nil, syserror.EINVAL
}
allowSeals := flags&linux.MFD_ALLOW_SEALING != 0
cloExec := flags&linux.MFD_CLOEXEC != 0
name, err := t.CopyInString(addr, syscall.PathMax-len(memfdPrefix))
if err != nil {
return 0, nil, err
}
if len(name) > memfdMaxNameLen {
return 0, nil, syserror.EINVAL
}
name = memfdPrefix + name
inode := tmpfs.NewMemfdInode(t, allowSeals)
dirent := fs.NewDirent(inode, name)
// Per Linux, mm/shmem.c:__shmem_file_setup(), memfd files are set up with
// FMODE_READ | FMODE_WRITE.
file, err := inode.GetFile(t, dirent, fs.FileFlags{Read: true, Write: true})
if err != nil {
return 0, nil, err
}
defer dirent.DecRef()
defer file.DecRef()
fdFlags := kernel.FDFlags{CloseOnExec: cloExec}
newFD, err := t.FDMap().NewFDFrom(0, file, fdFlags, t.ThreadGroup().Limits())
if err != nil {
return 0, nil, err
}
return uintptr(newFD), nil, nil
}
+17
View File
@@ -3261,3 +3261,20 @@ cc_binary(
"@com_google_googletest//:gtest",
],
)
cc_binary(
name = "memfd_test",
testonly = 1,
srcs = ["memfd.cc"],
linkstatic = 1,
deps = [
"//test/util:file_descriptor",
"//test/util:fs_util",
"//test/util:memory_util",
"//test/util:multiprocess_util",
"//test/util:temp_path",
"//test/util:test_main",
"//test/util:test_util",
"@com_google_googletest//:gtest",
],
)
File diff suppressed because it is too large Load Diff