Automated rollback of changelist 226224230

PiperOrigin-RevId: 226493053
Change-Id: Ia98d1cb6dd0682049e4d907ef69619831de5c34a
This commit is contained in:
Jamie Liu
2018-12-21 08:23:34 -08:00
committed by Shentubot
parent 8ba450363f
commit 9a442fa4b5
15 changed files with 945 additions and 131 deletions
+1 -1
View File
@@ -60,7 +60,7 @@ const (
DefaultNofileHardLimit = 4096
// DefaultMemlockLimit is called MLOCK_LIMIT in Linux.
DefaultMemlockLimit = 64 * 1094
DefaultMemlockLimit = 64 * 1024
// DefaultMsgqueueLimit is called MQ_BYTES_MAX in Linux.
DefaultMsgqueueLimit = 819200
+12
View File
@@ -49,6 +49,18 @@ const (
MREMAP_FIXED = 1 << 1
)
// Flags for mlock2(2).
const (
MLOCK_ONFAULT = 0x01
)
// Flags for mlockall(2).
const (
MCL_CURRENT = 1
MCL_FUTURE = 2
MCL_ONFAULT = 4
)
// Advice for madvise(2).
const (
MADV_NORMAL = 0
+37
View File
@@ -243,6 +243,40 @@ type MappingIdentity interface {
Msync(ctx context.Context, mr MappableRange) error
}
// MLockMode specifies the memory locking behavior of a memory mapping.
type MLockMode int
// Note that the ordering of MLockModes is significant; see
// mm.MemoryManager.defMLockMode.
const (
// MLockNone specifies that a mapping has no memory locking behavior.
//
// This must be the zero value for MLockMode.
MLockNone MLockMode = iota
// MLockEager specifies that a mapping is memory-locked, as by mlock() or
// similar. Pages in the mapping should be made, and kept, resident in
// physical memory as soon as possible.
//
// As of this writing, MLockEager does not cause memory-locking to be
// requested from the host; it only affects the sentry's memory management
// behavior.
//
// MLockEager is analogous to Linux's VM_LOCKED.
MLockEager
// MLockLazy specifies that a mapping is memory-locked, as by mlock() or
// similar. Pages in the mapping should be kept resident in physical memory
// once they have been made resident due to e.g. a page fault.
//
// As of this writing, MLockLazy does not cause memory-locking to be
// requested from the host; in fact, it has virtually no effect, except for
// interactions between mlocked pages and other syscalls.
//
// MLockLazy is analogous to Linux's VM_LOCKED | VM_LOCKONFAULT.
MLockLazy
)
// MMapOpts specifies a request to create a memory mapping.
type MMapOpts struct {
// Length is the length of the mapping.
@@ -303,6 +337,9 @@ type MMapOpts struct {
// mapping (see platform.AddressSpace.MapFile).
Precommit bool
// MLockMode specifies the memory locking behavior of the mapping.
MLockMode MLockMode
// Hint is the name used for the mapping in /proc/[pid]/maps. If Hint is
// empty, MappingIdentity.MappedName() will be used instead.
//
+1
View File
@@ -106,6 +106,7 @@ go_library(
"//pkg/sentry/context",
"//pkg/sentry/fs",
"//pkg/sentry/fs/proc/seqfile",
"//pkg/sentry/kernel/auth",
"//pkg/sentry/kernel/futex",
"//pkg/sentry/kernel/shm",
"//pkg/sentry/limits",
+5 -7
View File
@@ -149,7 +149,7 @@ func (mm *MemoryManager) Deactivate() {
// for all addresses in ar should be precommitted.
//
// Preconditions: mm.activeMu must be locked. mm.as != nil. ar.Length() != 0.
// ar must be page-aligned. pseg.Range().Contains(ar.Start).
// ar must be page-aligned. pseg == mm.pmas.LowerBoundSegment(ar.Start).
func (mm *MemoryManager) mapASLocked(pseg pmaIterator, ar usermem.AddrRange, precommit bool) error {
// By default, map entire pmas at a time, under the assumption that there
// is no cost to mapping more of a pma than necessary.
@@ -173,7 +173,9 @@ func (mm *MemoryManager) mapASLocked(pseg pmaIterator, ar usermem.AddrRange, pre
}
}
for {
// Since this checks ar.End and not mapAR.End, we will never map a pma that
// is not required.
for pseg.Ok() && pseg.Start() < ar.End {
pma := pseg.ValuePtr()
pmaAR := pseg.Range()
pmaMapAR := pmaAR.Intersect(mapAR)
@@ -184,13 +186,9 @@ func (mm *MemoryManager) mapASLocked(pseg pmaIterator, ar usermem.AddrRange, pre
if err := pma.file.MapInto(mm.as, pmaMapAR.Start, pseg.fileRangeOf(pmaMapAR), perms, precommit); err != nil {
return err
}
// Since this checks ar.End and not mapAR.End, we will never map a pma
// that is not required.
if ar.End <= pmaAR.End {
return nil
}
pseg = pseg.NextSegment()
}
return nil
}
// unmapASLocked removes all AddressSpace mappings for addresses in ar.
+15 -9
View File
@@ -22,6 +22,7 @@ import (
"gvisor.googlesource.com/gvisor/pkg/sentry/arch"
"gvisor.googlesource.com/gvisor/pkg/sentry/context"
"gvisor.googlesource.com/gvisor/pkg/sentry/limits"
"gvisor.googlesource.com/gvisor/pkg/sentry/memmap"
"gvisor.googlesource.com/gvisor/pkg/sentry/platform"
"gvisor.googlesource.com/gvisor/pkg/sentry/usermem"
)
@@ -58,13 +59,17 @@ func (mm *MemoryManager) Fork(ctx context.Context) (*MemoryManager, error) {
mm.mappingMu.RLock()
defer mm.mappingMu.RUnlock()
mm2 := &MemoryManager{
p: mm.p,
haveASIO: mm.haveASIO,
layout: mm.layout,
privateRefs: mm.privateRefs,
users: 1,
usageAS: mm.usageAS,
brk: mm.brk,
p: mm.p,
haveASIO: mm.haveASIO,
layout: mm.layout,
privateRefs: mm.privateRefs,
users: 1,
brk: mm.brk,
usageAS: mm.usageAS,
// "The child does not inherit its parent's memory locks (mlock(2),
// mlockall(2))." - fork(2). So lockedAS is 0 and defMLockMode is
// MLockNone, both of which are zero values. vma.mlockMode is reset
// when copied below.
captureInvalidations: true,
argv: mm.argv,
envv: mm.envv,
@@ -77,7 +82,7 @@ func (mm *MemoryManager) Fork(ctx context.Context) (*MemoryManager, error) {
// Copy vmas.
dstvgap := mm2.vmas.FirstGap()
for srcvseg := mm.vmas.FirstSegment(); srcvseg.Ok(); srcvseg = srcvseg.NextSegment() {
vma := srcvseg.ValuePtr()
vma := srcvseg.Value() // makes a copy of the vma
vmaAR := srcvseg.Range()
// Inform the Mappable, if any, of the new mapping.
if vma.mappable != nil {
@@ -89,7 +94,8 @@ func (mm *MemoryManager) Fork(ctx context.Context) (*MemoryManager, error) {
if vma.id != nil {
vma.id.IncRef()
}
dstvgap = mm2.vmas.Insert(dstvgap, vmaAR, *vma).NextGap()
vma.mlockMode = memmap.MLockNone
dstvgap = mm2.vmas.Insert(dstvgap, vmaAR, vma).NextGap()
// We don't need to update mm2.usageAS since we copied it from mm
// above.
}
+19 -5
View File
@@ -95,11 +95,6 @@ type MemoryManager struct {
// vmas is protected by mappingMu.
vmas vmaSet
// usageAS is vmas.Span(), cached to accelerate RLIMIT_AS checks.
//
// usageAS is protected by mappingMu.
usageAS uint64
// brk is the mm's brk, which is manipulated using the brk(2) system call.
// The brk is initially set up by the loader which maps an executable
// binary into the mm.
@@ -107,6 +102,23 @@ type MemoryManager struct {
// brk is protected by mappingMu.
brk usermem.AddrRange
// usageAS is vmas.Span(), cached to accelerate RLIMIT_AS checks.
//
// usageAS is protected by mappingMu.
usageAS uint64
// lockedAS is the combined size in bytes of all vmas with vma.mlockMode !=
// memmap.MLockNone.
//
// lockedAS is protected by mappingMu.
lockedAS uint64
// New VMAs created by MMap use whichever of memmap.MMapOpts.MLockMode or
// defMLockMode is greater.
//
// defMLockMode is protected by mappingMu.
defMLockMode memmap.MLockMode
// activeMu is loosely analogous to Linux's struct
// mm_struct::page_table_lock.
activeMu ssync.DowngradableRWMutex `state:"nosave"`
@@ -252,6 +264,8 @@ type vma struct {
// metag, none of which we currently support.
growsDown bool `state:"manual"`
mlockMode memmap.MLockMode
// If id is not nil, it controls the lifecycle of mappable and provides vma
// metadata shown in /proc/[pid]/maps, and the vma holds a reference.
id memmap.MappingIdentity
+368 -57
View File
File diff suppressed because it is too large Load Diff
+38
View File
@@ -17,8 +17,10 @@ package mm
import (
"fmt"
"gvisor.googlesource.com/gvisor/pkg/abi/linux"
"gvisor.googlesource.com/gvisor/pkg/sentry/arch"
"gvisor.googlesource.com/gvisor/pkg/sentry/context"
"gvisor.googlesource.com/gvisor/pkg/sentry/kernel/auth"
"gvisor.googlesource.com/gvisor/pkg/sentry/limits"
"gvisor.googlesource.com/gvisor/pkg/sentry/memmap"
"gvisor.googlesource.com/gvisor/pkg/sentry/usermem"
@@ -53,6 +55,23 @@ func (mm *MemoryManager) createVMALocked(ctx context.Context, opts memmap.MMapOp
return vmaIterator{}, usermem.AddrRange{}, syserror.ENOMEM
}
if opts.MLockMode != memmap.MLockNone {
// Check against RLIMIT_MEMLOCK.
if creds := auth.CredentialsFromContext(ctx); !creds.HasCapabilityIn(linux.CAP_IPC_LOCK, creds.UserNamespace.Root()) {
mlockLimit := limits.FromContext(ctx).Get(limits.MemoryLocked).Cur
if mlockLimit == 0 {
return vmaIterator{}, usermem.AddrRange{}, syserror.EPERM
}
newLockedAS := mm.lockedAS + opts.Length
if opts.Unmap {
newLockedAS -= mm.mlockedBytesRangeLocked(ar)
}
if newLockedAS > mlockLimit {
return vmaIterator{}, usermem.AddrRange{}, syserror.EAGAIN
}
}
}
// Remove overwritten mappings. This ordering is consistent with Linux:
// compare Linux's mm/mmap.c:mmap_region() => do_munmap(),
// file->f_op->mmap().
@@ -85,10 +104,14 @@ func (mm *MemoryManager) createVMALocked(ctx context.Context, opts memmap.MMapOp
maxPerms: opts.MaxPerms,
private: opts.Private,
growsDown: opts.GrowsDown,
mlockMode: opts.MLockMode,
id: opts.MappingIdentity,
hint: opts.Hint,
})
mm.usageAS += opts.Length
if opts.MLockMode != memmap.MLockNone {
mm.lockedAS += opts.Length
}
return vseg, ar, nil
}
@@ -201,6 +224,17 @@ func (mm *MemoryManager) findHighestAvailableLocked(length, alignment uint64, bo
return 0, syserror.ENOMEM
}
// Preconditions: mm.mappingMu must be locked.
func (mm *MemoryManager) mlockedBytesRangeLocked(ar usermem.AddrRange) uint64 {
var total uint64
for vseg := mm.vmas.LowerBoundSegment(ar.Start); vseg.Ok() && vseg.Start() < ar.End; vseg = vseg.NextSegment() {
if vseg.ValuePtr().mlockMode != memmap.MLockNone {
total += uint64(vseg.Range().Intersect(ar).Length())
}
}
return total
}
// getVMAsLocked ensures that vmas exist for all addresses in ar, and support
// access of type (at, ignorePermissions). It returns:
//
@@ -338,6 +372,9 @@ func (mm *MemoryManager) removeVMAsLocked(ctx context.Context, ar usermem.AddrRa
vma.id.DecRef()
}
mm.usageAS -= uint64(vmaAR.Length())
if vma.mlockMode != memmap.MLockNone {
mm.lockedAS -= uint64(vmaAR.Length())
}
vgap = mm.vmas.Remove(vseg)
vseg = vgap.NextSegment()
}
@@ -368,6 +405,7 @@ func (vmaSetFunctions) Merge(ar1 usermem.AddrRange, vma1 vma, ar2 usermem.AddrRa
vma1.maxPerms != vma2.maxPerms ||
vma1.private != vma2.private ||
vma1.growsDown != vma2.growsDown ||
vma1.mlockMode != vma2.mlockMode ||
vma1.id != vma2.id ||
vma1.hint != vma2.hint {
return vma{}, false
+8 -7
View File
@@ -196,11 +196,11 @@ var AMD64 = &kernel.SyscallTable{
145: SchedGetscheduler,
146: SchedGetPriorityMax,
147: SchedGetPriorityMin,
148: syscalls.ErrorWithEvent(syscall.EPERM), // SchedRrGetInterval,
149: syscalls.Error(nil), // Mlock, TODO
150: syscalls.Error(nil), // Munlock, TODO
151: syscalls.Error(nil), // Mlockall, TODO
152: syscalls.Error(nil), // Munlockall, TODO
148: syscalls.ErrorWithEvent(syscall.EPERM), // SchedRrGetInterval,
149: Mlock,
150: Munlock,
151: Mlockall,
152: Munlockall,
153: syscalls.CapError(linux.CAP_SYS_TTY_CONFIG), // Vhangup,
154: syscalls.Error(syscall.EPERM), // ModifyLdt,
155: syscalls.Error(syscall.EPERM), // PivotRoot,
@@ -373,8 +373,9 @@ var AMD64 = &kernel.SyscallTable{
// 322: Execveat, TODO
// 323: Userfaultfd, TODO
// 324: Membarrier, TODO
// Syscalls after 325 are backports from 4.6.
325: syscalls.Error(nil), // Mlock2, TODO
325: Mlock2,
// Syscalls after 325 are "backports" from versions of Linux after 4.4.
// 326: CopyFileRange,
327: Preadv2,
328: Pwritev2,
},
+65 -39
View File
@@ -69,6 +69,9 @@ func Mmap(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallC
GrowsDown: linux.MAP_GROWSDOWN&flags != 0,
Precommit: linux.MAP_POPULATE&flags != 0,
}
if linux.MAP_LOCKED&flags != 0 {
opts.MLockMode = memmap.MLockEager
}
defer func() {
if opts.MappingIdentity != nil {
opts.MappingIdentity.DecRef()
@@ -384,16 +387,6 @@ func Msync(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall
length := args[1].SizeT()
flags := args[2].Int()
if addr != addr.RoundDown() {
return 0, nil, syserror.EINVAL
}
if length == 0 {
return 0, nil, nil
}
la, ok := usermem.Addr(length).RoundUp()
if !ok {
return 0, nil, syserror.ENOMEM
}
// "The flags argument should specify exactly one of MS_ASYNC and MS_SYNC,
// and may additionally include the MS_INVALIDATE bit. ... However, Linux
// permits a call to msync() that specifies neither of these flags, with
@@ -406,39 +399,72 @@ func Msync(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall
if sync && flags&linux.MS_ASYNC != 0 {
return 0, nil, syserror.EINVAL
}
err := t.MemoryManager().MSync(t, addr, uint64(length), mm.MSyncOpts{
Sync: sync,
Invalidate: flags&linux.MS_INVALIDATE != 0,
})
// MSync calls fsync, the same interrupt conversion rules apply, see
// mm/msync.c, fsync POSIX.1-2008.
return 0, nil, syserror.ConvertIntr(err, kernel.ERESTARTSYS)
}
// MS_INVALIDATE "asks to invalidate other mappings of the same file (so
// that they can be updated with the fresh values just written)". This is a
// no-op given that shared memory exists. However, MS_INVALIDATE can also
// be used to detect mlocks: "EBUSY: MS_INVALIDATE was specified in flags,
// and a memory lock exists for the specified address range." Given that
// mlock is stubbed out, it's unsafe to pass MS_INVALIDATE silently since
// some user program could be using it for synchronization.
if flags&linux.MS_INVALIDATE != 0 {
// Mlock implements linux syscall mlock(2).
func Mlock(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
addr := args[0].Pointer()
length := args[1].SizeT()
return 0, nil, t.MemoryManager().MLock(t, addr, uint64(length), memmap.MLockEager)
}
// Mlock2 implements linux syscall mlock2(2).
func Mlock2(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
addr := args[0].Pointer()
length := args[1].SizeT()
flags := args[2].Int()
if flags&^(linux.MLOCK_ONFAULT) != 0 {
return 0, nil, syserror.EINVAL
}
// MS_SYNC "requests an update and waits for it to complete."
if sync {
err := t.MemoryManager().Sync(t, addr, uint64(la))
// Sync calls fsync, the same interrupt conversion rules apply, see
// mm/msync.c, fsync POSIX.1-2008.
return 0, nil, syserror.ConvertIntr(err, kernel.ERESTARTSYS)
mode := memmap.MLockEager
if flags&linux.MLOCK_ONFAULT != 0 {
mode = memmap.MLockLazy
}
// MS_ASYNC "specifies that an update be scheduled, but the call returns
// immediately". As long as dirty pages are tracked and eventually written
// back, this is a no-op. (Correspondingly: "Since Linux 2.6.19, MS_ASYNC
// is in fact a no-op, since the kernel properly tracks dirty pages and
// flushes them to storage as necessary.")
//
// However: "ENOMEM: The indicated memory (or part of it) was not mapped."
// This applies even for MS_ASYNC.
ar, ok := addr.ToRange(uint64(la))
if !ok {
return 0, nil, syserror.ENOMEM
return 0, nil, t.MemoryManager().MLock(t, addr, uint64(length), mode)
}
// Munlock implements linux syscall munlock(2).
func Munlock(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
addr := args[0].Pointer()
length := args[1].SizeT()
return 0, nil, t.MemoryManager().MLock(t, addr, uint64(length), memmap.MLockNone)
}
// Mlockall implements linux syscall mlockall(2).
func Mlockall(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
flags := args[0].Int()
if flags&^(linux.MCL_CURRENT|linux.MCL_FUTURE|linux.MCL_ONFAULT) != 0 {
return 0, nil, syserror.EINVAL
}
mapped := t.MemoryManager().VirtualMemorySizeRange(ar)
if mapped != uint64(la) {
return 0, nil, syserror.ENOMEM
mode := memmap.MLockEager
if flags&linux.MCL_ONFAULT != 0 {
mode = memmap.MLockLazy
}
return 0, nil, nil
return 0, nil, t.MemoryManager().MLockAll(t, mm.MLockAllOpts{
Current: flags&linux.MCL_CURRENT != 0,
Future: flags&linux.MCL_FUTURE != 0,
Mode: mode,
})
}
// Munlockall implements linux syscall munlockall(2).
func Munlockall(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
return 0, nil, t.MemoryManager().MLockAll(t, mm.MLockAllOpts{
Current: true,
Future: true,
Mode: memmap.MLockNone,
})
}
+1
View File
@@ -90,6 +90,7 @@ var setableLimits = map[limits.LimitType]struct{}{
limits.CPU: {},
limits.Data: {},
limits.FileSize: {},
limits.MemoryLocked: {},
limits.Stack: {},
// These are not enforced, but we include them here to avoid returning
// EPERM, since some apps expect them to succeed.
+16
View File
@@ -1019,6 +1019,22 @@ cc_binary(
],
)
cc_binary(
name = "mlock_test",
testonly = 1,
srcs = ["mlock.cc"],
linkstatic = 1,
deps = [
"//test/util:capability_util",
"//test/util:cleanup",
"//test/util:memory_util",
"//test/util:multiprocess_util",
"//test/util:test_main",
"//test/util:test_util",
"@com_google_googletest//:gtest",
],
)
cc_binary(
name = "mmap_test",
testonly = 1,
+345
View File
@@ -0,0 +1,345 @@
// Copyright 2018 Google LLC
//
// 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.
#include <errno.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/resource.h>
#include <sys/syscall.h>
#include <unistd.h>
#include "gmock/gmock.h"
#include "test/util/capability_util.h"
#include "test/util/cleanup.h"
#include "test/util/memory_util.h"
#include "test/util/multiprocess_util.h"
#include "test/util/test_util.h"
using ::testing::_;
namespace gvisor {
namespace testing {
namespace {
PosixErrorOr<bool> CanMlock() {
struct rlimit rlim;
if (getrlimit(RLIMIT_MEMLOCK, &rlim) < 0) {
return PosixError(errno, "getrlimit(RLIMIT_MEMLOCK)");
}
if (rlim.rlim_cur != 0) {
return true;
}
return HaveCapability(CAP_IPC_LOCK);
}
// Returns true if the page containing addr is mlocked.
bool IsPageMlocked(uintptr_t addr) {
// This relies on msync(MS_INVALIDATE) interacting correctly with mlocked
// pages, which is tested for by the MsyncInvalidate case below.
int const rv = msync(reinterpret_cast<void*>(addr & ~(kPageSize - 1)),
kPageSize, MS_ASYNC | MS_INVALIDATE);
if (rv == 0) {
return false;
}
// This uses TEST_PCHECK_MSG since it's used in subprocesses.
TEST_PCHECK_MSG(errno == EBUSY, "msync failed with unexpected errno");
return true;
}
PosixErrorOr<Cleanup> ScopedSetSoftRlimit(int resource, rlim_t newval) {
struct rlimit old_rlim;
if (getrlimit(resource, &old_rlim) != 0) {
return PosixError(errno, "getrlimit failed");
}
struct rlimit new_rlim = old_rlim;
new_rlim.rlim_cur = newval;
if (setrlimit(resource, &new_rlim) != 0) {
return PosixError(errno, "setrlimit failed");
}
return Cleanup([resource, old_rlim] {
TEST_PCHECK(setrlimit(resource, &old_rlim) == 0);
});
}
TEST(MlockTest, Basic) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock()));
auto const mapping = ASSERT_NO_ERRNO_AND_VALUE(
MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE));
EXPECT_FALSE(IsPageMlocked(mapping.addr()));
ASSERT_THAT(mlock(mapping.ptr(), mapping.len()), SyscallSucceeds());
EXPECT_TRUE(IsPageMlocked(mapping.addr()));
}
TEST(MlockTest, ProtNone) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock()));
auto const mapping =
ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(kPageSize, PROT_NONE, MAP_PRIVATE));
EXPECT_FALSE(IsPageMlocked(mapping.addr()));
ASSERT_THAT(mlock(mapping.ptr(), mapping.len()),
SyscallFailsWithErrno(ENOMEM));
// ENOMEM is returned because mlock can't populate the page, but it's still
// considered locked.
EXPECT_TRUE(IsPageMlocked(mapping.addr()));
}
TEST(MlockTest, MadviseDontneed) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock()));
auto const mapping = ASSERT_NO_ERRNO_AND_VALUE(
MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE));
ASSERT_THAT(mlock(mapping.ptr(), mapping.len()), SyscallSucceeds());
EXPECT_THAT(madvise(mapping.ptr(), mapping.len(), MADV_DONTNEED),
SyscallFailsWithErrno(EINVAL));
}
TEST(MlockTest, MsyncInvalidate) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock()));
auto const mapping = ASSERT_NO_ERRNO_AND_VALUE(
MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE));
ASSERT_THAT(mlock(mapping.ptr(), mapping.len()), SyscallSucceeds());
EXPECT_THAT(msync(mapping.ptr(), mapping.len(), MS_ASYNC | MS_INVALIDATE),
SyscallFailsWithErrno(EBUSY));
EXPECT_THAT(msync(mapping.ptr(), mapping.len(), MS_SYNC | MS_INVALIDATE),
SyscallFailsWithErrno(EBUSY));
}
TEST(MlockTest, Fork) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock()));
auto const mapping = ASSERT_NO_ERRNO_AND_VALUE(
MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE));
EXPECT_FALSE(IsPageMlocked(mapping.addr()));
ASSERT_THAT(mlock(mapping.ptr(), mapping.len()), SyscallSucceeds());
EXPECT_TRUE(IsPageMlocked(mapping.addr()));
EXPECT_THAT(
InForkedProcess([&] { TEST_CHECK(!IsPageMlocked(mapping.addr())); }),
IsPosixErrorOkAndHolds(0));
}
TEST(MlockTest, RlimitMemlockZero) {
if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_IPC_LOCK))) {
ASSERT_NO_ERRNO(SetCapability(CAP_IPC_LOCK, false));
}
Cleanup reset_rlimit =
ASSERT_NO_ERRNO_AND_VALUE(ScopedSetSoftRlimit(RLIMIT_MEMLOCK, 0));
auto const mapping = ASSERT_NO_ERRNO_AND_VALUE(
MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE));
EXPECT_FALSE(IsPageMlocked(mapping.addr()));
ASSERT_THAT(mlock(mapping.ptr(), mapping.len()),
SyscallFailsWithErrno(EPERM));
}
TEST(MlockTest, RlimitMemlockInsufficient) {
if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_IPC_LOCK))) {
ASSERT_NO_ERRNO(SetCapability(CAP_IPC_LOCK, false));
}
Cleanup reset_rlimit =
ASSERT_NO_ERRNO_AND_VALUE(ScopedSetSoftRlimit(RLIMIT_MEMLOCK, kPageSize));
auto const mapping = ASSERT_NO_ERRNO_AND_VALUE(
MmapAnon(2 * kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE));
EXPECT_FALSE(IsPageMlocked(mapping.addr()));
ASSERT_THAT(mlock(mapping.ptr(), mapping.len()),
SyscallFailsWithErrno(ENOMEM));
}
TEST(MunlockTest, Basic) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock()));
auto const mapping = ASSERT_NO_ERRNO_AND_VALUE(
MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE));
EXPECT_FALSE(IsPageMlocked(mapping.addr()));
ASSERT_THAT(mlock(mapping.ptr(), mapping.len()), SyscallSucceeds());
EXPECT_TRUE(IsPageMlocked(mapping.addr()));
ASSERT_THAT(munlock(mapping.ptr(), mapping.len()), SyscallSucceeds());
EXPECT_FALSE(IsPageMlocked(mapping.addr()));
}
TEST(MunlockTest, NotLocked) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock()));
auto const mapping = ASSERT_NO_ERRNO_AND_VALUE(
MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE));
EXPECT_FALSE(IsPageMlocked(mapping.addr()));
EXPECT_THAT(munlock(mapping.ptr(), mapping.len()), SyscallSucceeds());
EXPECT_FALSE(IsPageMlocked(mapping.addr()));
}
// There is currently no test for mlockall(MCL_CURRENT) because the default
// RLIMIT_MEMLOCK of 64 KB is insufficient to actually invoke
// mlockall(MCL_CURRENT).
TEST(MlockallTest, Future) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock()));
// Run this test in a separate (single-threaded) subprocess to ensure that a
// background thread doesn't try to mmap a large amount of memory, fail due
// to hitting RLIMIT_MEMLOCK, and explode the process violently.
EXPECT_THAT(InForkedProcess([] {
auto const mapping =
MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE)
.ValueOrDie();
TEST_CHECK(!IsPageMlocked(mapping.addr()));
TEST_PCHECK(mlockall(MCL_FUTURE) == 0);
// Ensure that mlockall(MCL_FUTURE) is turned off before the end
// of the test, as otherwise mmaps may fail unexpectedly.
Cleanup do_munlockall([] { TEST_PCHECK(munlockall() == 0); });
auto const mapping2 = ASSERT_NO_ERRNO_AND_VALUE(
MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE));
TEST_CHECK(IsPageMlocked(mapping2.addr()));
// Fire munlockall() and check that it disables
// mlockall(MCL_FUTURE).
do_munlockall.Release()();
auto const mapping3 = ASSERT_NO_ERRNO_AND_VALUE(
MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE));
TEST_CHECK(!IsPageMlocked(mapping2.addr()));
}),
IsPosixErrorOkAndHolds(0));
}
TEST(MunlockallTest, Basic) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock()));
auto const mapping = ASSERT_NO_ERRNO_AND_VALUE(
MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_LOCKED));
EXPECT_TRUE(IsPageMlocked(mapping.addr()));
ASSERT_THAT(munlockall(), SyscallSucceeds());
EXPECT_FALSE(IsPageMlocked(mapping.addr()));
}
#ifndef SYS_mlock2
#ifdef __x86_64__
#define SYS_mlock2 325
#endif
#endif
#ifndef MLOCK_ONFAULT
#define MLOCK_ONFAULT 0x01 // Linux: include/uapi/asm-generic/mman-common.h
#endif
#ifdef SYS_mlock2
int mlock2(void const* addr, size_t len, int flags) {
return syscall(SYS_mlock2, addr, len, flags);
}
TEST(Mlock2Test, NoFlags) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock()));
auto const mapping = ASSERT_NO_ERRNO_AND_VALUE(
MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE));
EXPECT_FALSE(IsPageMlocked(mapping.addr()));
ASSERT_THAT(mlock2(mapping.ptr(), mapping.len(), 0), SyscallSucceeds());
EXPECT_TRUE(IsPageMlocked(mapping.addr()));
}
TEST(Mlock2Test, MlockOnfault) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock()));
auto const mapping = ASSERT_NO_ERRNO_AND_VALUE(
MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE));
EXPECT_FALSE(IsPageMlocked(mapping.addr()));
ASSERT_THAT(mlock2(mapping.ptr(), mapping.len(), MLOCK_ONFAULT),
SyscallSucceeds());
EXPECT_TRUE(IsPageMlocked(mapping.addr()));
}
TEST(Mlock2Test, UnknownFlags) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock()));
auto const mapping = ASSERT_NO_ERRNO_AND_VALUE(
MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE));
EXPECT_THAT(mlock2(mapping.ptr(), mapping.len(), ~0),
SyscallFailsWithErrno(EINVAL));
}
#endif // defined(SYS_mlock2)
TEST(MapLockedTest, Basic) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock()));
auto const mapping = ASSERT_NO_ERRNO_AND_VALUE(
MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_LOCKED));
EXPECT_TRUE(IsPageMlocked(mapping.addr()));
EXPECT_THAT(munlock(mapping.ptr(), mapping.len()), SyscallSucceeds());
EXPECT_FALSE(IsPageMlocked(mapping.addr()));
}
TEST(MapLockedTest, RlimitMemlockZero) {
if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_IPC_LOCK))) {
ASSERT_NO_ERRNO(SetCapability(CAP_IPC_LOCK, false));
}
Cleanup reset_rlimit =
ASSERT_NO_ERRNO_AND_VALUE(ScopedSetSoftRlimit(RLIMIT_MEMLOCK, 0));
EXPECT_THAT(
MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_LOCKED),
PosixErrorIs(EPERM, _));
}
TEST(MapLockedTest, RlimitMemlockInsufficient) {
if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_IPC_LOCK))) {
ASSERT_NO_ERRNO(SetCapability(CAP_IPC_LOCK, false));
}
Cleanup reset_rlimit =
ASSERT_NO_ERRNO_AND_VALUE(ScopedSetSoftRlimit(RLIMIT_MEMLOCK, kPageSize));
EXPECT_THAT(
MmapAnon(2 * kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_LOCKED),
PosixErrorIs(EAGAIN, _));
}
TEST(MremapLockedTest, Basic) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock()));
auto mapping = ASSERT_NO_ERRNO_AND_VALUE(
MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_LOCKED));
EXPECT_TRUE(IsPageMlocked(mapping.addr()));
void* addr = mremap(mapping.ptr(), mapping.len(), 2 * mapping.len(),
MREMAP_MAYMOVE, nullptr);
if (addr == MAP_FAILED) {
FAIL() << "mremap failed: " << errno << " (" << strerror(errno) << ")";
}
mapping.release();
mapping.reset(addr, 2 * mapping.len());
EXPECT_TRUE(IsPageMlocked(reinterpret_cast<uintptr_t>(addr)));
}
TEST(MremapLockedTest, RlimitMemlockZero) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock()));
auto mapping = ASSERT_NO_ERRNO_AND_VALUE(
MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_LOCKED));
EXPECT_TRUE(IsPageMlocked(mapping.addr()));
if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_IPC_LOCK))) {
ASSERT_NO_ERRNO(SetCapability(CAP_IPC_LOCK, false));
}
Cleanup reset_rlimit =
ASSERT_NO_ERRNO_AND_VALUE(ScopedSetSoftRlimit(RLIMIT_MEMLOCK, 0));
void* addr = mremap(mapping.ptr(), mapping.len(), 2 * mapping.len(),
MREMAP_MAYMOVE, nullptr);
EXPECT_TRUE(addr == MAP_FAILED && errno == EAGAIN)
<< "addr = " << addr << ", errno = " << errno;
}
TEST(MremapLockedTest, RlimitMemlockInsufficient) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanMlock()));
auto mapping = ASSERT_NO_ERRNO_AND_VALUE(
MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_LOCKED));
EXPECT_TRUE(IsPageMlocked(mapping.addr()));
if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_IPC_LOCK))) {
ASSERT_NO_ERRNO(SetCapability(CAP_IPC_LOCK, false));
}
Cleanup reset_rlimit = ASSERT_NO_ERRNO_AND_VALUE(
ScopedSetSoftRlimit(RLIMIT_MEMLOCK, mapping.len()));
void* addr = mremap(mapping.ptr(), mapping.len(), 2 * mapping.len(),
MREMAP_MAYMOVE, nullptr);
EXPECT_TRUE(addr == MAP_FAILED && errno == EAGAIN)
<< "addr = " << addr << ", errno = " << errno;
}
} // namespace
} // namespace testing
} // namespace gvisor
+14 -6
View File
@@ -43,14 +43,13 @@ class MsyncParameterizedTest : public ::testing::TestWithParam<MsyncTestParam> {
protected:
int msync_flags() const { return std::get<0>(GetParam()); }
PosixErrorOr<Mapping> GetMapping() const {
auto rv = std::get<1>(GetParam())();
return rv;
}
PosixErrorOr<Mapping> GetMapping() const { return std::get<1>(GetParam())(); }
};
// All valid msync(2) flag combinations (not including MS_INVALIDATE, which
// gVisor doesn't implement).
// All valid msync(2) flag combinations, not including MS_INVALIDATE. ("Linux
// permits a call to msync() that specifies neither [MS_SYNC or MS_ASYNC], with
// semantics that are (currently) equivalent to specifying MS_ASYNC." -
// msync(2))
constexpr std::initializer_list<int> kMsyncFlags = {MS_SYNC, MS_ASYNC, 0};
// Returns functions that return mappings that should be successfully
@@ -134,6 +133,15 @@ TEST_P(MsyncFullParamTest, UnalignedAddressFails) {
SyscallFailsWithErrno(EINVAL));
}
TEST_P(MsyncFullParamTest, InvalidateUnlockedSucceeds) {
auto m = ASSERT_NO_ERRNO_AND_VALUE(GetMapping());
EXPECT_THAT(msync(m.ptr(), m.len(), msync_flags() | MS_INVALIDATE),
SyscallSucceeds());
}
// The test for MS_INVALIDATE on mlocked pages is in mlock.cc since it requires
// probing for mlock support.
INSTANTIATE_TEST_CASE_P(
All, MsyncFullParamTest,
::testing::Combine(::testing::ValuesIn(kMsyncFlags),