mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Implement mlock(), kind of.
Currently mlock() and friends do nothing whatsoever. However, mlocking is directly application-visible in a number of ways; for example, madvise(MADV_DONTNEED) and msync(MS_INVALIDATE) both fail on mlocked regions. We handle this inconsistently: MADV_DONTNEED is too important to not work, but MS_INVALIDATE is rejected. Change MM to track mlocked regions in a manner consistent with Linux. It still will not actually pin pages into host physical memory, but: - mlock() will now cause sentry memory management to precommit mlocked pages. - MADV_DONTNEED and MS_INVALIDATE will interact with mlocked pages as described above. PiperOrigin-RevId: 225861605 Change-Id: Iee187204979ac9a4d15d0e037c152c0902c8d0ee
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -33,7 +33,7 @@ const (
|
||||
Rss
|
||||
ProcessCount
|
||||
NumberOfFiles
|
||||
MemoryPagesLocked
|
||||
MemoryLocked
|
||||
AS
|
||||
Locks
|
||||
SignalsPending
|
||||
|
||||
@@ -30,7 +30,7 @@ var FromLinuxResource = map[int]LimitType{
|
||||
linux.RLIMIT_RSS: Rss,
|
||||
linux.RLIMIT_NPROC: ProcessCount,
|
||||
linux.RLIMIT_NOFILE: NumberOfFiles,
|
||||
linux.RLIMIT_MEMLOCK: MemoryPagesLocked,
|
||||
linux.RLIMIT_MEMLOCK: MemoryLocked,
|
||||
linux.RLIMIT_AS: AS,
|
||||
linux.RLIMIT_LOCKS: Locks,
|
||||
linux.RLIMIT_SIGPENDING: SignalsPending,
|
||||
|
||||
@@ -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.
|
||||
//
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
@@ -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, // Pwritev2, TODO
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -29,7 +29,7 @@ var fromLinuxResource = map[string]limits.LimitType{
|
||||
"RLIMIT_DATA": limits.Data,
|
||||
"RLIMIT_FSIZE": limits.FileSize,
|
||||
"RLIMIT_LOCKS": limits.Locks,
|
||||
"RLIMIT_MEMLOCK": limits.MemoryPagesLocked,
|
||||
"RLIMIT_MEMLOCK": limits.MemoryLocked,
|
||||
"RLIMIT_MSGQUEUE": limits.MessageQueueBytes,
|
||||
"RLIMIT_NICE": limits.Nice,
|
||||
"RLIMIT_NOFILE": limits.NumberOfFiles,
|
||||
@@ -55,7 +55,7 @@ func createLimitSet(spec *specs.Spec) (*limits.LimitSet, error) {
|
||||
ls.SetUnchecked(limits.Data, limits.Limit{Cur: limits.Infinity, Max: limits.Infinity})
|
||||
ls.SetUnchecked(limits.FileSize, limits.Limit{Cur: limits.Infinity, Max: limits.Infinity})
|
||||
ls.SetUnchecked(limits.Locks, limits.Limit{Cur: limits.Infinity, Max: limits.Infinity})
|
||||
ls.SetUnchecked(limits.MemoryPagesLocked, limits.Limit{Cur: 65536, Max: 65536})
|
||||
ls.SetUnchecked(limits.MemoryLocked, limits.Limit{Cur: 65536, Max: 65536})
|
||||
ls.SetUnchecked(limits.MessageQueueBytes, limits.Limit{Cur: 819200, Max: 819200})
|
||||
ls.SetUnchecked(limits.Nice, limits.Limit{Cur: 0, Max: 0})
|
||||
ls.SetUnchecked(limits.NumberOfFiles, limits.Limit{Cur: 1048576, Max: 1048576})
|
||||
|
||||
@@ -1019,6 +1019,21 @@ 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_util",
|
||||
"@com_google_googletest//:gtest",
|
||||
],
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "mmap_test",
|
||||
testonly = 1,
|
||||
|
||||
@@ -0,0 +1,344 @@
|
||||
// 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 "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
|
||||
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user