mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Use a tmpfs file for shared anonymous and /dev/zero mmap on VFS2.
This is more consistent with Linux (see comment on MM.NewSharedAnonMappable()). We don't do the same thing on VFS1 for reasons documented by the updated comment. PiperOrigin-RevId: 332514849
This commit is contained in:
@@ -18,9 +18,10 @@ go_library(
|
||||
"//pkg/rand",
|
||||
"//pkg/safemem",
|
||||
"//pkg/sentry/fsimpl/devtmpfs",
|
||||
"//pkg/sentry/fsimpl/tmpfs",
|
||||
"//pkg/sentry/kernel",
|
||||
"//pkg/sentry/kernel/auth",
|
||||
"//pkg/sentry/memmap",
|
||||
"//pkg/sentry/mm",
|
||||
"//pkg/sentry/pgalloc",
|
||||
"//pkg/sentry/vfs",
|
||||
"//pkg/syserror",
|
||||
"//pkg/usermem",
|
||||
|
||||
@@ -16,9 +16,10 @@ package memdev
|
||||
|
||||
import (
|
||||
"gvisor.dev/gvisor/pkg/context"
|
||||
"gvisor.dev/gvisor/pkg/sentry/fsimpl/tmpfs"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
||||
"gvisor.dev/gvisor/pkg/sentry/memmap"
|
||||
"gvisor.dev/gvisor/pkg/sentry/mm"
|
||||
"gvisor.dev/gvisor/pkg/sentry/pgalloc"
|
||||
"gvisor.dev/gvisor/pkg/sentry/vfs"
|
||||
"gvisor.dev/gvisor/pkg/usermem"
|
||||
)
|
||||
@@ -79,11 +80,22 @@ func (fd *zeroFD) Seek(ctx context.Context, offset int64, whence int32) (int64,
|
||||
|
||||
// ConfigureMMap implements vfs.FileDescriptionImpl.ConfigureMMap.
|
||||
func (fd *zeroFD) ConfigureMMap(ctx context.Context, opts *memmap.MMapOpts) error {
|
||||
m, err := mm.NewSharedAnonMappable(opts.Length, pgalloc.MemoryFileProviderFromContext(ctx))
|
||||
if opts.Private || !opts.MaxPerms.Write {
|
||||
// This mapping will never permit writing to the "underlying file" (in
|
||||
// Linux terms, it isn't VM_SHARED), so implement it as an anonymous
|
||||
// mapping, but back it with fd; this is what Linux does, and is
|
||||
// actually application-visible because the resulting VMA will show up
|
||||
// in /proc/[pid]/maps with fd.vfsfd.VirtualDentry()'s path rather than
|
||||
// "/dev/zero (deleted)".
|
||||
opts.Offset = 0
|
||||
opts.MappingIdentity = &fd.vfsfd
|
||||
opts.MappingIdentity.IncRef()
|
||||
return nil
|
||||
}
|
||||
tmpfsFD, err := tmpfs.NewZeroFile(ctx, auth.CredentialsFromContext(ctx), kernel.KernelFromContext(ctx).ShmMount(), opts.Length)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
opts.MappingIdentity = m
|
||||
opts.Mappable = m
|
||||
return nil
|
||||
defer tmpfsFD.DecRef(ctx)
|
||||
return tmpfsFD.ConfigureMMap(ctx, opts)
|
||||
}
|
||||
|
||||
@@ -865,8 +865,16 @@ func (fs *filesystem) PrependPath(ctx context.Context, vfsroot, vd vfs.VirtualDe
|
||||
}
|
||||
if d.parent == nil {
|
||||
if d.name != "" {
|
||||
// This must be an anonymous memfd file.
|
||||
// This file must have been created by
|
||||
// newUnlinkedRegularFileDescription(). In Linux,
|
||||
// mm/shmem.c:__shmem_file_setup() =>
|
||||
// fs/file_table.c:alloc_file_pseudo() sets the created
|
||||
// dentry's dentry_operations to anon_ops, for which d_dname ==
|
||||
// simple_dname. fs/d_path.c:simple_dname() defines the
|
||||
// dentry's pathname to be its name, prefixed with "/" and
|
||||
// suffixed with " (deleted)".
|
||||
b.PrependComponent("/" + d.name)
|
||||
b.AppendString(" (deleted)")
|
||||
return vfs.PrependPathSyntheticError{}
|
||||
}
|
||||
return vfs.PrependPathAtNonMountRootError{}
|
||||
|
||||
@@ -42,6 +42,10 @@ type regularFile struct {
|
||||
// memFile is a platform.File used to allocate pages to this regularFile.
|
||||
memFile *pgalloc.MemoryFile
|
||||
|
||||
// memoryUsageKind is the memory accounting category under which pages backing
|
||||
// this regularFile's contents are accounted.
|
||||
memoryUsageKind usage.MemoryKind
|
||||
|
||||
// mapsMu protects mappings.
|
||||
mapsMu sync.Mutex `state:"nosave"`
|
||||
|
||||
@@ -86,14 +90,75 @@ type regularFile struct {
|
||||
|
||||
func (fs *filesystem) newRegularFile(kuid auth.KUID, kgid auth.KGID, mode linux.FileMode) *inode {
|
||||
file := ®ularFile{
|
||||
memFile: fs.memFile,
|
||||
seals: linux.F_SEAL_SEAL,
|
||||
memFile: fs.memFile,
|
||||
memoryUsageKind: usage.Tmpfs,
|
||||
seals: linux.F_SEAL_SEAL,
|
||||
}
|
||||
file.inode.init(file, fs, kuid, kgid, linux.S_IFREG|mode)
|
||||
file.inode.nlink = 1 // from parent directory
|
||||
return &file.inode
|
||||
}
|
||||
|
||||
// newUnlinkedRegularFileDescription creates a regular file on the tmpfs
|
||||
// filesystem represented by mount and returns an FD representing that file.
|
||||
// The new file is not reachable by path traversal from any other file.
|
||||
//
|
||||
// newUnlinkedRegularFileDescription is analogous to Linux's
|
||||
// mm/shmem.c:__shmem_file_setup().
|
||||
//
|
||||
// Preconditions: mount must be a tmpfs mount.
|
||||
func newUnlinkedRegularFileDescription(ctx context.Context, creds *auth.Credentials, mount *vfs.Mount, name string) (*regularFileFD, error) {
|
||||
fs, ok := mount.Filesystem().Impl().(*filesystem)
|
||||
if !ok {
|
||||
panic("tmpfs.newUnlinkedRegularFileDescription() called with non-tmpfs mount")
|
||||
}
|
||||
|
||||
inode := fs.newRegularFile(creds.EffectiveKUID, creds.EffectiveKGID, 0777)
|
||||
d := fs.newDentry(inode)
|
||||
defer d.DecRef(ctx)
|
||||
d.name = name
|
||||
|
||||
fd := ®ularFileFD{}
|
||||
fd.Init(&inode.locks)
|
||||
flags := uint32(linux.O_RDWR)
|
||||
if err := fd.vfsfd.Init(fd, flags, mount, &d.vfsd, &vfs.FileDescriptionOptions{}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fd, nil
|
||||
}
|
||||
|
||||
// NewZeroFile creates a new regular file and file description as for
|
||||
// mmap(MAP_SHARED | MAP_ANONYMOUS). The file has the given size and is
|
||||
// initially (implicitly) filled with zeroes.
|
||||
//
|
||||
// Preconditions: mount must be a tmpfs mount.
|
||||
func NewZeroFile(ctx context.Context, creds *auth.Credentials, mount *vfs.Mount, size uint64) (*vfs.FileDescription, error) {
|
||||
// Compare mm/shmem.c:shmem_zero_setup().
|
||||
fd, err := newUnlinkedRegularFileDescription(ctx, creds, mount, "dev/zero")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rf := fd.inode().impl.(*regularFile)
|
||||
rf.memoryUsageKind = usage.Anonymous
|
||||
rf.size = size
|
||||
return &fd.vfsfd, err
|
||||
}
|
||||
|
||||
// NewMemfd creates a new regular file and file description as for
|
||||
// memfd_create.
|
||||
//
|
||||
// Preconditions: mount must be a tmpfs mount.
|
||||
func NewMemfd(ctx context.Context, creds *auth.Credentials, mount *vfs.Mount, allowSeals bool, name string) (*vfs.FileDescription, error) {
|
||||
fd, err := newUnlinkedRegularFileDescription(ctx, creds, mount, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if allowSeals {
|
||||
fd.inode().impl.(*regularFile).seals = 0
|
||||
}
|
||||
return &fd.vfsfd, nil
|
||||
}
|
||||
|
||||
// truncate grows or shrinks the file to the given size. It returns true if the
|
||||
// file size was updated.
|
||||
func (rf *regularFile) truncate(newSize uint64) (bool, error) {
|
||||
@@ -226,7 +291,7 @@ func (rf *regularFile) Translate(ctx context.Context, required, optional memmap.
|
||||
optional.End = pgend
|
||||
}
|
||||
|
||||
cerr := rf.data.Fill(ctx, required, optional, rf.memFile, usage.Tmpfs, func(_ context.Context, dsts safemem.BlockSeq, _ uint64) (uint64, error) {
|
||||
cerr := rf.data.Fill(ctx, required, optional, rf.memFile, rf.memoryUsageKind, func(_ context.Context, dsts safemem.BlockSeq, _ uint64) (uint64, error) {
|
||||
// Newly-allocated pages are zeroed, so we don't need to do anything.
|
||||
return dsts.NumBytes(), nil
|
||||
})
|
||||
@@ -575,7 +640,7 @@ func (rw *regularFileReadWriter) WriteFromBlocks(srcs safemem.BlockSeq) (uint64,
|
||||
case gap.Ok():
|
||||
// Allocate memory for the write.
|
||||
gapMR := gap.Range().Intersect(pgMR)
|
||||
fr, err := rw.file.memFile.Allocate(gapMR.Length(), usage.Tmpfs)
|
||||
fr, err := rw.file.memFile.Allocate(gapMR.Length(), rw.file.memoryUsageKind)
|
||||
if err != nil {
|
||||
retErr = err
|
||||
goto exitLoop
|
||||
|
||||
@@ -746,37 +746,6 @@ func (fd *fileDescription) RemoveXattr(ctx context.Context, name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewMemfd creates a new tmpfs regular file and file description that can back
|
||||
// an anonymous fd created by memfd_create.
|
||||
func NewMemfd(ctx context.Context, creds *auth.Credentials, mount *vfs.Mount, allowSeals bool, name string) (*vfs.FileDescription, error) {
|
||||
fs, ok := mount.Filesystem().Impl().(*filesystem)
|
||||
if !ok {
|
||||
panic("NewMemfd() called with non-tmpfs mount")
|
||||
}
|
||||
|
||||
// Per Linux, mm/shmem.c:__shmem_file_setup(), memfd inodes are set up with
|
||||
// S_IRWXUGO.
|
||||
inode := fs.newRegularFile(creds.EffectiveKUID, creds.EffectiveKGID, 0777)
|
||||
rf := inode.impl.(*regularFile)
|
||||
if allowSeals {
|
||||
rf.seals = 0
|
||||
}
|
||||
|
||||
d := fs.newDentry(inode)
|
||||
defer d.DecRef(ctx)
|
||||
d.name = name
|
||||
|
||||
// Per Linux, mm/shmem.c:__shmem_file_setup(), memfd files are set up with
|
||||
// FMODE_READ | FMODE_WRITE.
|
||||
var fd regularFileFD
|
||||
fd.Init(&inode.locks)
|
||||
flags := uint32(linux.O_RDWR)
|
||||
if err := fd.vfsfd.Init(&fd, flags, mount, &d.vfsd, &vfs.FileDescriptionOptions{}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &fd.vfsfd, nil
|
||||
}
|
||||
|
||||
// LockPOSIX implements vfs.FileDescriptionImpl.LockPOSIX.
|
||||
func (fd *fileDescription) LockPOSIX(ctx context.Context, uid fslock.UniqueID, t fslock.LockType, start, length uint64, whence int16, block fslock.Blocker) error {
|
||||
return fd.Locks().LockPOSIX(ctx, &fd.vfsfd, uid, t, start, length, whence, block)
|
||||
|
||||
+1
-1
@@ -242,7 +242,7 @@ type MemoryManager struct {
|
||||
// +stateify savable
|
||||
type vma struct {
|
||||
// mappable is the virtual memory object mapped by this vma. If mappable is
|
||||
// nil, the vma represents a private anonymous mapping.
|
||||
// nil, the vma represents an anonymous mapping.
|
||||
mappable memmap.Mappable
|
||||
|
||||
// off is the offset into mappable at which this vma begins. If mappable is
|
||||
|
||||
@@ -51,7 +51,8 @@ func TestUsageASUpdates(t *testing.T) {
|
||||
defer mm.DecUsers(ctx)
|
||||
|
||||
addr, err := mm.MMap(ctx, memmap.MMapOpts{
|
||||
Length: 2 * usermem.PageSize,
|
||||
Length: 2 * usermem.PageSize,
|
||||
Private: true,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("MMap got err %v want nil", err)
|
||||
|
||||
@@ -136,9 +136,12 @@ func (m *SpecialMappable) Length() uint64 {
|
||||
// NewSharedAnonMappable returns a SpecialMappable that implements the
|
||||
// semantics of mmap(MAP_SHARED|MAP_ANONYMOUS) and mappings of /dev/zero.
|
||||
//
|
||||
// TODO(jamieliu): The use of SpecialMappable is a lazy code reuse hack. Linux
|
||||
// uses an ephemeral file created by mm/shmem.c:shmem_zero_setup(); we should
|
||||
// do the same to get non-zero device and inode IDs.
|
||||
// TODO(gvisor.dev/issue/1624): Linux uses an ephemeral file created by
|
||||
// mm/shmem.c:shmem_zero_setup(), and VFS2 does something analogous. VFS1 uses
|
||||
// a SpecialMappable instead, incorrectly getting device and inode IDs of zero
|
||||
// and causing memory for shared anonymous mappings to be allocated up-front
|
||||
// instead of on first touch; this is to avoid exacerbating the fs.MountSource
|
||||
// leak (b/143656263). Delete this function along with VFS1.
|
||||
func NewSharedAnonMappable(length uint64, mfp pgalloc.MemoryFileProvider) (*SpecialMappable, error) {
|
||||
if length == 0 {
|
||||
return nil, syserror.EINVAL
|
||||
|
||||
@@ -24,7 +24,6 @@ import (
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/futex"
|
||||
"gvisor.dev/gvisor/pkg/sentry/limits"
|
||||
"gvisor.dev/gvisor/pkg/sentry/memmap"
|
||||
"gvisor.dev/gvisor/pkg/sentry/pgalloc"
|
||||
"gvisor.dev/gvisor/pkg/syserror"
|
||||
"gvisor.dev/gvisor/pkg/usermem"
|
||||
)
|
||||
@@ -93,18 +92,6 @@ func (mm *MemoryManager) MMap(ctx context.Context, opts memmap.MMapOpts) (userme
|
||||
}
|
||||
} else {
|
||||
opts.Offset = 0
|
||||
if !opts.Private {
|
||||
if opts.MappingIdentity != nil {
|
||||
return 0, syserror.EINVAL
|
||||
}
|
||||
m, err := NewSharedAnonMappable(opts.Length, pgalloc.MemoryFileProviderFromContext(ctx))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer m.DecRef(ctx)
|
||||
opts.MappingIdentity = m
|
||||
opts.Mappable = m
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Addr.RoundDown() != opts.Addr {
|
||||
|
||||
@@ -100,6 +100,15 @@ func Mmap(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallC
|
||||
if err := file.ConfigureMMap(t, &opts); err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
} else if shared {
|
||||
// Back shared anonymous mappings with a special mappable.
|
||||
opts.Offset = 0
|
||||
m, err := mm.NewSharedAnonMappable(opts.Length, t.Kernel())
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
opts.MappingIdentity = m // transfers ownership of m to opts
|
||||
opts.Mappable = m
|
||||
}
|
||||
|
||||
rv, err := t.MemoryManager().MMap(t, opts)
|
||||
|
||||
@@ -17,6 +17,7 @@ package vfs2
|
||||
import (
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/sentry/arch"
|
||||
"gvisor.dev/gvisor/pkg/sentry/fsimpl/tmpfs"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel"
|
||||
"gvisor.dev/gvisor/pkg/sentry/memmap"
|
||||
"gvisor.dev/gvisor/pkg/syserror"
|
||||
@@ -82,6 +83,17 @@ func Mmap(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallC
|
||||
opts.MaxPerms.Write = false
|
||||
}
|
||||
|
||||
if err := file.ConfigureMMap(t, &opts); err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
} else if shared {
|
||||
// Back shared anonymous mappings with an anonymous tmpfs file.
|
||||
opts.Offset = 0
|
||||
file, err := tmpfs.NewZeroFile(t, t.Credentials(), t.Kernel().ShmMount(), opts.Length)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
defer file.DecRef(t)
|
||||
if err := file.ConfigureMMap(t, &opts); err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
@@ -1673,6 +1673,7 @@ cc_binary(
|
||||
gtest,
|
||||
"//test/util:memory_util",
|
||||
"//test/util:posix_error",
|
||||
"//test/util:proc_util",
|
||||
"//test/util:temp_path",
|
||||
"//test/util:test_util",
|
||||
"//test/util:thread_util",
|
||||
|
||||
@@ -63,6 +63,7 @@
|
||||
#include "test/util/fs_util.h"
|
||||
#include "test/util/memory_util.h"
|
||||
#include "test/util/posix_error.h"
|
||||
#include "test/util/proc_util.h"
|
||||
#include "test/util/temp_path.h"
|
||||
#include "test/util/test_util.h"
|
||||
#include "test/util/thread_util.h"
|
||||
@@ -672,6 +673,23 @@ TEST(ProcSelfMaps, Mprotect) {
|
||||
3 * kPageSize, PROT_READ)));
|
||||
}
|
||||
|
||||
TEST(ProcSelfMaps, SharedAnon) {
|
||||
const Mapping m = ASSERT_NO_ERRNO_AND_VALUE(
|
||||
MmapAnon(kPageSize, PROT_READ, MAP_SHARED | MAP_ANONYMOUS));
|
||||
|
||||
const auto proc_self_maps =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/self/maps"));
|
||||
for (const auto& line : absl::StrSplit(proc_self_maps, '\n')) {
|
||||
const auto entry = ASSERT_NO_ERRNO_AND_VALUE(ParseProcMapsLine(line));
|
||||
if (entry.start <= m.addr() && m.addr() < entry.end) {
|
||||
// cf. proc(5), "/proc/[pid]/map_files/"
|
||||
EXPECT_EQ(entry.filename, "/dev/zero (deleted)");
|
||||
return;
|
||||
}
|
||||
}
|
||||
FAIL() << "no maps entry containing mapping at " << m.ptr();
|
||||
}
|
||||
|
||||
TEST(ProcSelfFd, OpenFd) {
|
||||
int pipe_fds[2];
|
||||
ASSERT_THAT(pipe2(pipe_fds, O_CLOEXEC), SyscallSucceeds());
|
||||
|
||||
Reference in New Issue
Block a user