mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
mm: implement prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME)
PiperOrigin-RevId: 696727156
This commit is contained in:
@@ -149,6 +149,12 @@ const (
|
||||
PR_SCHED_CORE_SCOPE_THREAD = 0
|
||||
PR_SCHED_CORE_SCOPE_THREAD_GROUP = 1
|
||||
|
||||
// PR_SET_VMA sets VMA attributes.
|
||||
PR_SET_VMA = 0x53564d41
|
||||
PR_SET_VMA_ANON_NAME = 0
|
||||
// From kernel/sys.c:
|
||||
ANON_VMA_NAME_MAX_LEN = 80
|
||||
|
||||
// PR_SET_PTRACER allows a specific process (or any, if PR_SET_PTRACER_ANY is
|
||||
// specified) to ptrace the current task.
|
||||
PR_SET_PTRACER = 0x59616d61
|
||||
|
||||
@@ -80,6 +80,15 @@ type regularFile struct {
|
||||
// Protected by dataMu.
|
||||
seals uint32
|
||||
|
||||
// initiallyUnlinked is true if this file was created using NewZeroFile or
|
||||
// NewMemfd => newUnlinkedRegularFileDescription. initiallyUnlinked should
|
||||
// be true when the equivalent shmem file in Linux would use
|
||||
// shmem_anon_vm_ops rather than shmem_vm_ops.
|
||||
//
|
||||
// initiallyUnlinked is immutable, but stored here since it fits into
|
||||
// alignment padding.
|
||||
initiallyUnlinked bool
|
||||
|
||||
// size is the size of data.
|
||||
//
|
||||
// Protected by both dataMu and inode.mu; reading it requires holding
|
||||
@@ -114,6 +123,7 @@ func newUnlinkedRegularFileDescription(ctx context.Context, creds *auth.Credenti
|
||||
}
|
||||
|
||||
inode := fs.newRegularFile(creds.EffectiveKUID, creds.EffectiveKGID, 0777, nil /* parentDir */)
|
||||
inode.impl.(*regularFile).initiallyUnlinked = true
|
||||
d := fs.newDentry(inode)
|
||||
defer d.DecRef(ctx)
|
||||
d.name = name
|
||||
@@ -588,6 +598,9 @@ func (fd *regularFileFD) Seek(ctx context.Context, offset int64, whence int32) (
|
||||
func (fd *regularFileFD) ConfigureMMap(ctx context.Context, opts *memmap.MMapOpts) error {
|
||||
file := fd.inode().impl.(*regularFile)
|
||||
opts.SentryOwnedContent = true
|
||||
if file.initiallyUnlinked {
|
||||
opts.NameMut = memmap.NameMutAnonShmem
|
||||
}
|
||||
return vfs.GenericConfigureMMap(&fd.vfsfd, file, opts)
|
||||
}
|
||||
|
||||
|
||||
@@ -367,11 +367,15 @@ type MMapOpts struct {
|
||||
// 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
|
||||
// Name is the name used for the mapping in /proc/[pid]/maps. If Name is
|
||||
// empty, MappingIdentity.MappedName() will be used instead.
|
||||
//
|
||||
// TODO(jamieliu): Replace entirely with MappingIdentity?
|
||||
Hint string
|
||||
Name string
|
||||
|
||||
// NameMut controls the effect of prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME)
|
||||
// on this mapping.
|
||||
NameMut NameMut
|
||||
|
||||
// Force means to skip validation checks of Addr and Length. It can be
|
||||
// used to create special mappings below mm.layout.MinAddr and
|
||||
@@ -393,6 +397,23 @@ type MMapOpts struct {
|
||||
SentryOwnedContent bool
|
||||
}
|
||||
|
||||
// NameMut is the type of MMapOpts.NameMut.
|
||||
type NameMut uint8
|
||||
|
||||
// Possible values for MMapOpts.NameMut:
|
||||
const (
|
||||
// NameMutDisallowed indicates that PR_SET_VMA_ANON_NAME should fail.
|
||||
NameMutDisallowed NameMut = iota
|
||||
|
||||
// NameMutAnon indicates that PR_SET_VMA_ANON_NAME should succeed, and
|
||||
// treat the mapping as private anonymous memory.
|
||||
NameMutAnon
|
||||
|
||||
// NameMutAnonShmem indicates that PR_SET_VMA_ANON_NAME should succeed, and
|
||||
// treat the mapping as shared anonymous memory.
|
||||
NameMutAnonShmem
|
||||
)
|
||||
|
||||
// MMapPlatformEffect is the type of MMapOpts.PlatformEffect.
|
||||
type MMapPlatformEffect uint8
|
||||
|
||||
|
||||
+7
-4
@@ -312,9 +312,11 @@ type vma struct {
|
||||
// metadata shown in /proc/[pid]/maps, and the vma holds a reference.
|
||||
id memmap.MappingIdentity
|
||||
|
||||
// If hint is non-empty, it is a description of the vma printed in
|
||||
// /proc/[pid]/maps. hint takes priority over id.MappedName().
|
||||
hint string
|
||||
// If name is non-empty, it is a description of the vma printed in
|
||||
// /proc/[pid]/maps. name takes priority over id.MappedName().
|
||||
name string
|
||||
|
||||
nameMut memmap.NameMut
|
||||
|
||||
// lastFault records the last address that was paged faulted. It hints at
|
||||
// which direction addresses in this vma are being accessed.
|
||||
@@ -339,7 +341,8 @@ func (v *vma) copy() vma {
|
||||
numaPolicy: v.numaPolicy,
|
||||
numaNodemask: v.numaNodemask,
|
||||
id: v.id,
|
||||
hint: v.hint,
|
||||
name: v.name,
|
||||
nameMut: v.nameMut,
|
||||
lastFault: atomic.LoadUintptr(&v.lastFault),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,8 +123,8 @@ func (mm *MemoryManager) appendVMAMapsEntryLocked(ctx context.Context, vseg vmaI
|
||||
|
||||
// Figure out our filename or hint.
|
||||
var path string
|
||||
if vma.hint != "" {
|
||||
path = vma.hint
|
||||
if vma.name != "" {
|
||||
path = vma.name
|
||||
} else if vma.id != nil {
|
||||
path = vma.id.MappedName(ctx)
|
||||
}
|
||||
|
||||
+133
-30
@@ -292,7 +292,8 @@ func (mm *MemoryManager) MapStack(ctx context.Context) (hostarch.AddrRange, erro
|
||||
Private: true,
|
||||
GrowsDown: true,
|
||||
MLockMode: mm.defMLockMode,
|
||||
Hint: "[stack]",
|
||||
Name: "[stack]",
|
||||
NameMut: memmap.NameMutAnon,
|
||||
}, nil /* droppedIDs */)
|
||||
mm.mappingMu.Unlock()
|
||||
for _, id := range droppedIDs {
|
||||
@@ -462,7 +463,8 @@ func (mm *MemoryManager) MRemap(ctx context.Context, oldAddr hostarch.Addr, oldS
|
||||
GrowsDown: vma.growsDown,
|
||||
Stack: vma.isStack,
|
||||
MLockMode: vma.mlockMode,
|
||||
Hint: vma.hint,
|
||||
Name: vma.name,
|
||||
NameMut: vma.nameMut,
|
||||
}, droppedIDs)
|
||||
if err == nil {
|
||||
if vma.mlockMode == memmap.MLockEager {
|
||||
@@ -799,7 +801,8 @@ func (mm *MemoryManager) Brk(ctx context.Context, addr hostarch.Addr) (hostarch.
|
||||
// Linux: mm/mmap.c:sys_brk() => do_brk_flags() includes
|
||||
// mm->def_flags.
|
||||
MLockMode: mm.defMLockMode,
|
||||
Hint: "[heap]",
|
||||
Name: "[heap]",
|
||||
NameMut: memmap.NameMutAnon,
|
||||
}, droppedIDs)
|
||||
if err != nil {
|
||||
addr = mm.brk.End
|
||||
@@ -1069,37 +1072,41 @@ func (mm *MemoryManager) SetNumaPolicy(addr hostarch.Addr, length uint64, policy
|
||||
}
|
||||
}
|
||||
|
||||
// SetDontFork implements the semantics of madvise MADV_DONTFORK.
|
||||
func (mm *MemoryManager) SetDontFork(addr hostarch.Addr, length uint64, dontfork bool) error {
|
||||
ar, ok := addr.ToRange(length)
|
||||
// madviseAddrRange converts addr and length to an AddrRange as for madvise(2)
|
||||
// (and prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME) =>
|
||||
// mm/madvise.c:madvise_vma_anon_name().)
|
||||
func madviseAddrRange(addr hostarch.Addr, length uint64) (hostarch.AddrRange, error) {
|
||||
// All quotes from the man page:
|
||||
// "madvise() only operates on whole pages, therefore addr must be
|
||||
// page-aligned."
|
||||
if !addr.IsPageAligned() {
|
||||
// "EINVAL: addr is not page-aligned or length is negative." Note that
|
||||
// length is size_t which is unsigned, so "length is negative" is
|
||||
// impossible, but we take this as referring to the next check (for
|
||||
// which Linux also returns EINVAL).
|
||||
return hostarch.AddrRange{}, linuxerr.EINVAL
|
||||
}
|
||||
// "The value of length is rounded up to a multiple of page size."
|
||||
lengthRounded, ok := hostarch.PageRoundUp(length)
|
||||
if !ok {
|
||||
return linuxerr.EINVAL
|
||||
return hostarch.AddrRange{}, linuxerr.EINVAL
|
||||
}
|
||||
|
||||
mm.mappingMu.Lock()
|
||||
defer mm.mappingMu.Unlock()
|
||||
defer func() {
|
||||
mm.vmas.MergeInsideRange(ar)
|
||||
mm.vmas.MergeOutsideRange(ar)
|
||||
}()
|
||||
|
||||
for vseg := mm.vmas.LowerBoundSegment(ar.Start); vseg.Ok() && vseg.Start() < ar.End; vseg = vseg.NextSegment() {
|
||||
vseg = mm.vmas.Isolate(vseg, ar)
|
||||
vma := vseg.ValuePtr()
|
||||
vma.dontfork = dontfork
|
||||
ar, ok := addr.ToRange(lengthRounded)
|
||||
if !ok {
|
||||
// Not specified, but Linux also returns EINVAL in this case.
|
||||
return hostarch.AddrRange{}, linuxerr.EINVAL
|
||||
}
|
||||
|
||||
if mm.vmas.SpanRange(ar) != ar.Length() {
|
||||
return linuxerr.ENOMEM
|
||||
}
|
||||
return nil
|
||||
return ar, nil
|
||||
}
|
||||
|
||||
// Decommit implements the semantics of Linux's madvise(MADV_DONTNEED).
|
||||
func (mm *MemoryManager) Decommit(addr hostarch.Addr, length uint64) error {
|
||||
ar, ok := addr.ToRange(length)
|
||||
if !ok {
|
||||
return linuxerr.EINVAL
|
||||
ar, err := madviseAddrRange(addr, length)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if length == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
mm.mappingMu.RLock()
|
||||
@@ -1226,6 +1233,102 @@ func (mm *MemoryManager) Decommit(addr hostarch.Addr, length uint64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// madviseMutateVMAs is similar to mm.vmas.MutateRange(), but:
|
||||
//
|
||||
// - madviseMutateVMAs locks mm.mappingMu for writing, as required to mutate
|
||||
// mm.vmas.
|
||||
//
|
||||
// - If f returns a non-nil error, madviseMutateVMAs stops iteration and
|
||||
// returns the error.
|
||||
//
|
||||
// - Consistent with Linux madvise(): "If there are some parts of the specified
|
||||
// address range that are not mapped, the Linux version of madvise() ignores
|
||||
// them and applies the call to the rest (but returns ENOMEM from the system
|
||||
// call, as it should)."
|
||||
func (mm *MemoryManager) madviseMutateVMAs(addr hostarch.Addr, length uint64, f func(vseg vmaIterator) error) error {
|
||||
ar, err := madviseAddrRange(addr, length)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if length == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
mm.mappingMu.Lock()
|
||||
defer mm.mappingMu.Unlock()
|
||||
vseg := mm.vmas.LowerBoundSegmentSplitBefore(ar.Start)
|
||||
if !vseg.Ok() {
|
||||
return linuxerr.ENOMEM
|
||||
}
|
||||
hadvgap := ar.Start < vseg.Start()
|
||||
for vseg.Start() < ar.End {
|
||||
vseg = mm.vmas.SplitAfter(vseg, ar.End)
|
||||
err := f(vseg)
|
||||
vseg = mm.vmas.MergePrev(vseg)
|
||||
if err != nil {
|
||||
mm.vmas.MergeNext(vseg)
|
||||
return err
|
||||
}
|
||||
if ar.End <= vseg.End() {
|
||||
break
|
||||
}
|
||||
vgap := vseg.NextGap()
|
||||
if !vgap.IsEmpty() {
|
||||
hadvgap = true
|
||||
}
|
||||
vseg = vgap.NextSegment()
|
||||
}
|
||||
mm.vmas.MergePrev(vseg)
|
||||
if hadvgap {
|
||||
return linuxerr.ENOMEM
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetDontFork implements the semantics of madvise MADV_DONTFORK.
|
||||
//
|
||||
// Preconditions: addr and length are page-aligned.
|
||||
func (mm *MemoryManager) SetDontFork(addr hostarch.Addr, length uint64, dontfork bool) error {
|
||||
return mm.madviseMutateVMAs(addr, length, func(vseg vmaIterator) error {
|
||||
vseg.ValuePtr().dontfork = dontfork
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// SetVMAAnonName implements the semantics of Linux's
|
||||
// prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME).
|
||||
func (mm *MemoryManager) SetVMAAnonName(addr hostarch.Addr, length uint64, name string, nameIsNil bool) error {
|
||||
// Check that name contains only valid characters; compare Linux
|
||||
// kernel/sys.c:is_valid_name_char(). `for ... := range string`
|
||||
// iterates Unicode runes, not bytes.
|
||||
for i := 0; i < len(name); i++ {
|
||||
c := name[i]
|
||||
if c <= 0x1f || c >= 0x7f || c == '\\' || c == '`' || c == '$' || c == '[' || c == ']' {
|
||||
return linuxerr.EINVAL
|
||||
}
|
||||
}
|
||||
|
||||
return mm.madviseMutateVMAs(addr, length, func(vseg vmaIterator) error {
|
||||
vma := vseg.ValuePtr()
|
||||
if vma.nameMut == memmap.NameMutDisallowed {
|
||||
return linuxerr.EBADF
|
||||
}
|
||||
if nameIsNil {
|
||||
vma.name = ""
|
||||
return nil
|
||||
}
|
||||
switch vma.nameMut {
|
||||
case memmap.NameMutAnon:
|
||||
vma.name = fmt.Sprintf("[anon:%s]", name)
|
||||
case memmap.NameMutAnonShmem:
|
||||
vma.name = fmt.Sprintf("[anon_shmem:%s]", name)
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown memmap.NameMut: %d", vma.nameMut))
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// MSyncOpts holds options to MSync.
|
||||
type MSyncOpts struct {
|
||||
// Sync has the semantics of MS_SYNC.
|
||||
@@ -1406,7 +1509,7 @@ func (mm *MemoryManager) IsMembarrierRSeqEnabled() bool {
|
||||
}
|
||||
|
||||
// FindVMAByName finds a vma with the specified name and returns its start address and offset.
|
||||
func (mm *MemoryManager) FindVMAByName(ar hostarch.AddrRange, hint string) (hostarch.Addr, uint64, error) {
|
||||
func (mm *MemoryManager) FindVMAByName(ar hostarch.AddrRange, name string) (hostarch.Addr, uint64, error) {
|
||||
mm.mappingMu.RLock()
|
||||
defer mm.mappingMu.RUnlock()
|
||||
|
||||
@@ -1417,9 +1520,9 @@ func (mm *MemoryManager) FindVMAByName(ar hostarch.AddrRange, hint string) (host
|
||||
}
|
||||
vma := vseg.ValuePtr()
|
||||
|
||||
if vma.hint == hint {
|
||||
if vma.name == name {
|
||||
return start, vma.off, nil
|
||||
}
|
||||
}
|
||||
return 0, 0, fmt.Errorf("could not find \"%s\" in %s", hint, ar)
|
||||
return 0, 0, fmt.Errorf("could not find %q in %s", name, ar)
|
||||
}
|
||||
|
||||
@@ -126,7 +126,8 @@ func (mm *MemoryManager) createVMALocked(ctx context.Context, opts memmap.MMapOp
|
||||
mlockMode: opts.MLockMode,
|
||||
numaPolicy: linux.MPOL_DEFAULT,
|
||||
id: opts.MappingIdentity,
|
||||
hint: opts.Hint,
|
||||
name: opts.Name,
|
||||
nameMut: opts.NameMut,
|
||||
}
|
||||
|
||||
vseg := mm.vmas.Insert(vgap, ar, v)
|
||||
@@ -455,7 +456,7 @@ func (vmaSetFunctions) MaxKey() hostarch.Addr {
|
||||
func (vmaSetFunctions) ClearValue(vma *vma) {
|
||||
vma.mappable = nil
|
||||
vma.id = nil
|
||||
vma.hint = ""
|
||||
vma.name = ""
|
||||
atomic.StoreUintptr(&vma.lastFault, 0)
|
||||
}
|
||||
|
||||
@@ -472,7 +473,8 @@ func (vmaSetFunctions) Merge(ar1 hostarch.AddrRange, vma1 vma, ar2 hostarch.Addr
|
||||
vma1.numaNodemask != vma2.numaNodemask ||
|
||||
vma1.dontfork != vma2.dontfork ||
|
||||
vma1.id != vma2.id ||
|
||||
vma1.hint != vma2.hint {
|
||||
vma1.name != vma2.name ||
|
||||
vma1.nameMut != vma2.nameMut {
|
||||
return vma{}, false
|
||||
}
|
||||
|
||||
|
||||
@@ -96,7 +96,7 @@ func (s *State) newTrapLocked(ctx context.Context, mm memoryManager) (hostarch.A
|
||||
// nextTrap is saved on the usertrap vma to handle the third and second
|
||||
// cases.
|
||||
if s.nextTrap == 0 {
|
||||
addr, off, err := mm.FindVMAByName(trapTableAddrRange, tableHint)
|
||||
addr, off, err := mm.FindVMAByName(trapTableAddrRange, tableVMAName)
|
||||
if off != 0 {
|
||||
return 0, fmt.Errorf("the usertrap vma has been overmounted")
|
||||
}
|
||||
@@ -152,7 +152,7 @@ var trapTableAddrRange = hostarch.AddrRange{Start: 0x60000, End: 0x70000}
|
||||
const (
|
||||
trapTableSize = hostarch.Addr(trapNR * trapSize)
|
||||
|
||||
tableHint = "[usertrap]"
|
||||
tableVMAName = "[usertrap]"
|
||||
)
|
||||
|
||||
// LoadUsertrap maps the usertrap table into the address space.
|
||||
@@ -166,7 +166,7 @@ func loadUsertrap(ctx context.Context, mm memoryManager, addr hostarch.Addr) err
|
||||
Addr: addr,
|
||||
Length: uint64(size),
|
||||
Private: true,
|
||||
Hint: tableHint,
|
||||
Name: tableVMAName,
|
||||
MLockMode: memmap.MLockEager,
|
||||
Perms: hostarch.AccessType{
|
||||
Write: false,
|
||||
|
||||
@@ -112,6 +112,8 @@ func Mmap(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *
|
||||
if err := file.ConfigureMMap(t, &opts); err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
} else {
|
||||
opts.NameMut = memmap.NameMutAnon
|
||||
}
|
||||
|
||||
rv, err := t.MemoryManager().MMap(t, opts)
|
||||
@@ -175,21 +177,6 @@ func Madvise(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr
|
||||
length := uint64(args[1].SizeT())
|
||||
adv := args[2].Int()
|
||||
|
||||
// "The Linux implementation requires that the address addr be
|
||||
// page-aligned, and allows length to be zero." - madvise(2)
|
||||
if addr.RoundDown() != addr {
|
||||
return 0, nil, linuxerr.EINVAL
|
||||
}
|
||||
if length == 0 {
|
||||
return 0, nil, nil
|
||||
}
|
||||
// Not explicitly stated: length need not be page-aligned.
|
||||
lenAddr, ok := hostarch.Addr(length).RoundUp()
|
||||
if !ok {
|
||||
return 0, nil, linuxerr.EINVAL
|
||||
}
|
||||
length = uint64(lenAddr)
|
||||
|
||||
switch adv {
|
||||
case linux.MADV_DONTNEED:
|
||||
return 0, nil, t.MemoryManager().Decommit(addr, length)
|
||||
|
||||
@@ -237,6 +237,25 @@ func Prctl(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr,
|
||||
_, err := primitive.CopyInt32Out(t, args[1].Pointer(), isSubreaper)
|
||||
return 0, nil, err
|
||||
|
||||
case linux.PR_SET_VMA:
|
||||
if args[1].Int() != linux.PR_SET_VMA_ANON_NAME {
|
||||
return 0, nil, linuxerr.EINVAL
|
||||
}
|
||||
var (
|
||||
name string
|
||||
nameIsNil bool
|
||||
)
|
||||
if nameAddr := args[4].Pointer(); nameAddr == 0 {
|
||||
nameIsNil = true
|
||||
} else {
|
||||
var err error
|
||||
name, err = t.CopyInString(nameAddr, linux.ANON_VMA_NAME_MAX_LEN)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
}
|
||||
return 0, nil, t.MemoryManager().SetVMAAnonName(args[2].Pointer(), args[3].Uint64(), name, nameIsNil)
|
||||
|
||||
case linux.PR_GET_TIMING,
|
||||
linux.PR_SET_TIMING,
|
||||
linux.PR_GET_TSC,
|
||||
|
||||
+109
-9
@@ -1127,23 +1127,123 @@ TEST(ProcSelfMaps, Mprotect) {
|
||||
3 * kPageSize, PROT_READ)));
|
||||
}
|
||||
|
||||
// Expected pathname for MAP_SHARED | MAP_ANONYMOUS mappings. See proc(5),
|
||||
// "/proc/[pid]/map_files/".
|
||||
constexpr char kSharedAnonPath[] = "/dev/zero (deleted)";
|
||||
|
||||
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();
|
||||
const auto entries = ASSERT_NO_ERRNO_AND_VALUE(ParseProcMaps(proc_self_maps));
|
||||
const auto entry =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(FindUniqueMapsEntry(entries, m.addr()));
|
||||
EXPECT_EQ(entry.filename, kSharedAnonPath);
|
||||
}
|
||||
|
||||
#ifndef PR_SET_VMA
|
||||
#define PR_SET_VMA 0x53564d41
|
||||
#endif
|
||||
#ifndef PR_SET_VMA_ANON_NAME
|
||||
#define PR_SET_VMA_ANON_NAME 0
|
||||
#endif
|
||||
|
||||
TEST(ProcSelfMaps, AnonNamePrivateAnon) {
|
||||
const Mapping m = ASSERT_NO_ERRNO_AND_VALUE(
|
||||
MmapAnon(kPageSize, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS));
|
||||
|
||||
int rv = prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, m.addr(), m.len(), "test");
|
||||
SKIP_IF(rv < 0 && errno == EINVAL);
|
||||
ASSERT_THAT(rv, SyscallSucceeds());
|
||||
auto proc_self_maps =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/self/maps"));
|
||||
auto entries = ASSERT_NO_ERRNO_AND_VALUE(ParseProcMaps(proc_self_maps));
|
||||
auto entry =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(FindUniqueMapsEntry(entries, m.addr()));
|
||||
EXPECT_EQ(entry.filename, "[anon:test]");
|
||||
|
||||
ASSERT_THAT(prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, m.addr(), m.len(), ""),
|
||||
SyscallSucceeds());
|
||||
proc_self_maps = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/self/maps"));
|
||||
entries = ASSERT_NO_ERRNO_AND_VALUE(ParseProcMaps(proc_self_maps));
|
||||
entry = ASSERT_NO_ERRNO_AND_VALUE(FindUniqueMapsEntry(entries, m.addr()));
|
||||
EXPECT_EQ(entry.filename, "[anon:]");
|
||||
|
||||
ASSERT_THAT(
|
||||
prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, m.addr(), m.len(), nullptr),
|
||||
SyscallSucceeds());
|
||||
proc_self_maps = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/self/maps"));
|
||||
entries = ASSERT_NO_ERRNO_AND_VALUE(ParseProcMaps(proc_self_maps));
|
||||
entry = ASSERT_NO_ERRNO_AND_VALUE(FindUniqueMapsEntry(entries, m.addr()));
|
||||
EXPECT_EQ(entry.filename, "");
|
||||
}
|
||||
|
||||
TEST(ProcSelfMaps, AnonNameSharedAnon) {
|
||||
const Mapping m = ASSERT_NO_ERRNO_AND_VALUE(
|
||||
MmapAnon(kPageSize, PROT_READ, MAP_SHARED | MAP_ANONYMOUS));
|
||||
|
||||
int rv = prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, m.addr(), m.len(), "test");
|
||||
SKIP_IF(rv < 0 && errno == EINVAL);
|
||||
// Using PR_SET_VMA_ANON_NAME on shared anonymous mappings isn't permitted
|
||||
// until d09e8ca6cb93 ("mm: anonymous shared memory naming"), Linux 6.2+.
|
||||
SKIP_IF(rv < 0 && errno == EBADF);
|
||||
ASSERT_THAT(rv, SyscallSucceeds());
|
||||
auto proc_self_maps =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/self/maps"));
|
||||
auto entries = ASSERT_NO_ERRNO_AND_VALUE(ParseProcMaps(proc_self_maps));
|
||||
auto entry =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(FindUniqueMapsEntry(entries, m.addr()));
|
||||
EXPECT_EQ(entry.filename, "[anon_shmem:test]");
|
||||
|
||||
ASSERT_THAT(prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, m.addr(), m.len(), ""),
|
||||
SyscallSucceeds());
|
||||
proc_self_maps = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/self/maps"));
|
||||
entries = ASSERT_NO_ERRNO_AND_VALUE(ParseProcMaps(proc_self_maps));
|
||||
entry = ASSERT_NO_ERRNO_AND_VALUE(FindUniqueMapsEntry(entries, m.addr()));
|
||||
EXPECT_EQ(entry.filename, "[anon_shmem:]");
|
||||
|
||||
ASSERT_THAT(
|
||||
prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, m.addr(), m.len(), nullptr),
|
||||
SyscallSucceeds());
|
||||
proc_self_maps = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/self/maps"));
|
||||
entries = ASSERT_NO_ERRNO_AND_VALUE(ParseProcMaps(proc_self_maps));
|
||||
entry = ASSERT_NO_ERRNO_AND_VALUE(FindUniqueMapsEntry(entries, m.addr()));
|
||||
EXPECT_EQ(entry.filename, kSharedAnonPath);
|
||||
}
|
||||
|
||||
// Test parameterized by mmap flags.
|
||||
class ProcSelfMapsMmapFileTest : public ::testing::TestWithParam<int> {};
|
||||
|
||||
TEST_P(ProcSelfMapsMmapFileTest, AnonNameFile) {
|
||||
const auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
|
||||
const auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(f.path(), O_RDONLY));
|
||||
const Mapping m = ASSERT_NO_ERRNO_AND_VALUE(
|
||||
Mmap(nullptr, kPageSize, PROT_READ, GetParam(), fd.get(), 0));
|
||||
|
||||
int rv = prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, m.addr(), m.len(), "test");
|
||||
SKIP_IF(rv < 0 && errno == EINVAL);
|
||||
ASSERT_THAT(rv, SyscallFailsWithErrno(EBADF));
|
||||
auto proc_self_maps =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/self/maps"));
|
||||
auto entries = ASSERT_NO_ERRNO_AND_VALUE(ParseProcMaps(proc_self_maps));
|
||||
auto entry =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(FindUniqueMapsEntry(entries, m.addr()));
|
||||
EXPECT_EQ(entry.filename, f.path());
|
||||
|
||||
ASSERT_THAT(
|
||||
prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, m.addr(), m.len(), nullptr),
|
||||
SyscallFailsWithErrno(EBADF));
|
||||
proc_self_maps = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/self/maps"));
|
||||
entries = ASSERT_NO_ERRNO_AND_VALUE(ParseProcMaps(proc_self_maps));
|
||||
entry = ASSERT_NO_ERRNO_AND_VALUE(FindUniqueMapsEntry(entries, m.addr()));
|
||||
EXPECT_EQ(entry.filename, f.path());
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(SelfAndNumericPid, ProcSelfMapsMmapFileTest,
|
||||
::testing::Values(MAP_SHARED, MAP_PRIVATE));
|
||||
|
||||
TEST(ProcSelfFd, OpenFd) {
|
||||
int pipe_fds[2];
|
||||
ASSERT_THAT(pipe2(pipe_fds, O_CLOEXEC), SyscallSucceeds());
|
||||
|
||||
@@ -85,6 +85,7 @@ cc_library(
|
||||
"@com_google_absl//absl/algorithm:container",
|
||||
"@com_google_absl//absl/container:flat_hash_set",
|
||||
"@com_google_absl//absl/strings",
|
||||
"@com_google_absl//absl/strings:str_format",
|
||||
"@com_google_absl//absl/types:optional",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
#include "test/util/proc_util.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <sys/prctl.h>
|
||||
|
||||
#include <algorithm>
|
||||
@@ -24,6 +25,7 @@
|
||||
#include "absl/container/flat_hash_set.h"
|
||||
#include "absl/strings/ascii.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "absl/strings/str_split.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "test/util/fs_util.h"
|
||||
@@ -99,6 +101,27 @@ PosixErrorOr<std::vector<ProcMapsEntry>> ParseProcMaps(
|
||||
return entries;
|
||||
}
|
||||
|
||||
PosixErrorOr<ProcMapsEntry> FindUniqueMapsEntry(
|
||||
std::vector<ProcMapsEntry> const& entries, uintptr_t addr) {
|
||||
auto const pred = [&](ProcMapsEntry const& entry) {
|
||||
return entry.start <= addr && addr < entry.end;
|
||||
};
|
||||
auto const it = absl::c_find_if(entries, pred);
|
||||
if (it == entries.end()) {
|
||||
return PosixError(EINVAL,
|
||||
absl::StrFormat("no entry contains address %#x", addr));
|
||||
}
|
||||
auto const it2 = std::find_if(it + 1, entries.end(), pred);
|
||||
if (it2 != entries.end()) {
|
||||
return PosixError(
|
||||
EINVAL,
|
||||
absl::StrFormat("overlapping entries [%#x-%#x) and [%#x-%#x) both "
|
||||
"contain address %#x",
|
||||
it->start, it->end, it2->start, it2->end, addr));
|
||||
}
|
||||
return *it;
|
||||
}
|
||||
|
||||
PosixErrorOr<bool> IsVsyscallEnabled() {
|
||||
ASSIGN_OR_RETURN_ERRNO(auto contents, GetContents("/proc/self/maps"));
|
||||
ASSIGN_OR_RETURN_ERRNO(auto maps, ParseProcMaps(contents));
|
||||
|
||||
@@ -84,6 +84,10 @@ PosixErrorOr<ProcMapsEntry> ParseProcMapsLine(absl::string_view line);
|
||||
PosixErrorOr<std::vector<ProcMapsEntry>> ParseProcMaps(
|
||||
absl::string_view contents);
|
||||
|
||||
// Returns the unique entry in entries containing the given address.
|
||||
PosixErrorOr<ProcMapsEntry> FindUniqueMapsEntry(
|
||||
std::vector<ProcMapsEntry> const&, uintptr_t);
|
||||
|
||||
// Returns true if vsyscall (emmulation or not) is enabled.
|
||||
PosixErrorOr<bool> IsVsyscallEnabled();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user