From cf5841ba663961fad8a55b3692da36e7cd1a4f2c Mon Sep 17 00:00:00 2001 From: Jamie Liu Date: Thu, 14 Nov 2024 19:02:32 -0800 Subject: [PATCH] mm: implement prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME) PiperOrigin-RevId: 696727156 --- pkg/abi/linux/prctl.go | 6 + pkg/sentry/fsimpl/tmpfs/regular_file.go | 13 ++ pkg/sentry/memmap/memmap.go | 25 ++- pkg/sentry/mm/mm.go | 11 +- pkg/sentry/mm/procfs.go | 4 +- pkg/sentry/mm/syscalls.go | 163 ++++++++++++++---- pkg/sentry/mm/vma.go | 8 +- .../systrap/usertrap/usertrap_amd64.go | 6 +- pkg/sentry/syscalls/linux/sys_mmap.go | 17 +- pkg/sentry/syscalls/linux/sys_prctl.go | 19 ++ test/syscalls/linux/proc.cc | 118 ++++++++++++- test/util/BUILD | 1 + test/util/proc_util.cc | 23 +++ test/util/proc_util.h | 4 + 14 files changed, 350 insertions(+), 68 deletions(-) diff --git a/pkg/abi/linux/prctl.go b/pkg/abi/linux/prctl.go index 6b0f583a7..c1928ae64 100644 --- a/pkg/abi/linux/prctl.go +++ b/pkg/abi/linux/prctl.go @@ -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 diff --git a/pkg/sentry/fsimpl/tmpfs/regular_file.go b/pkg/sentry/fsimpl/tmpfs/regular_file.go index 24c46fbaf..c4f35cd5e 100644 --- a/pkg/sentry/fsimpl/tmpfs/regular_file.go +++ b/pkg/sentry/fsimpl/tmpfs/regular_file.go @@ -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) } diff --git a/pkg/sentry/memmap/memmap.go b/pkg/sentry/memmap/memmap.go index 254265da2..f4f2226b1 100644 --- a/pkg/sentry/memmap/memmap.go +++ b/pkg/sentry/memmap/memmap.go @@ -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 diff --git a/pkg/sentry/mm/mm.go b/pkg/sentry/mm/mm.go index e87d65bd2..d10a30ce8 100644 --- a/pkg/sentry/mm/mm.go +++ b/pkg/sentry/mm/mm.go @@ -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), } } diff --git a/pkg/sentry/mm/procfs.go b/pkg/sentry/mm/procfs.go index 5b23623b6..ada41095c 100644 --- a/pkg/sentry/mm/procfs.go +++ b/pkg/sentry/mm/procfs.go @@ -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) } diff --git a/pkg/sentry/mm/syscalls.go b/pkg/sentry/mm/syscalls.go index 86b1d33ae..3a23c2d0b 100644 --- a/pkg/sentry/mm/syscalls.go +++ b/pkg/sentry/mm/syscalls.go @@ -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) } diff --git a/pkg/sentry/mm/vma.go b/pkg/sentry/mm/vma.go index 4811085b5..fe984ab90 100644 --- a/pkg/sentry/mm/vma.go +++ b/pkg/sentry/mm/vma.go @@ -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 } diff --git a/pkg/sentry/platform/systrap/usertrap/usertrap_amd64.go b/pkg/sentry/platform/systrap/usertrap/usertrap_amd64.go index 4e5bdfaf1..730f52653 100644 --- a/pkg/sentry/platform/systrap/usertrap/usertrap_amd64.go +++ b/pkg/sentry/platform/systrap/usertrap/usertrap_amd64.go @@ -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, diff --git a/pkg/sentry/syscalls/linux/sys_mmap.go b/pkg/sentry/syscalls/linux/sys_mmap.go index de0879d9f..70e3a5f3f 100644 --- a/pkg/sentry/syscalls/linux/sys_mmap.go +++ b/pkg/sentry/syscalls/linux/sys_mmap.go @@ -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) diff --git a/pkg/sentry/syscalls/linux/sys_prctl.go b/pkg/sentry/syscalls/linux/sys_prctl.go index 949f6ad13..72bd78030 100644 --- a/pkg/sentry/syscalls/linux/sys_prctl.go +++ b/pkg/sentry/syscalls/linux/sys_prctl.go @@ -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, diff --git a/test/syscalls/linux/proc.cc b/test/syscalls/linux/proc.cc index 4503d3d09..db70712e0 100644 --- a/test/syscalls/linux/proc.cc +++ b/test/syscalls/linux/proc.cc @@ -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 {}; + +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()); diff --git a/test/util/BUILD b/test/util/BUILD index c428e259b..10284b9f9 100644 --- a/test/util/BUILD +++ b/test/util/BUILD @@ -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", ], ) diff --git a/test/util/proc_util.cc b/test/util/proc_util.cc index cda299542..a72ec0f97 100644 --- a/test/util/proc_util.cc +++ b/test/util/proc_util.cc @@ -14,6 +14,7 @@ #include "test/util/proc_util.h" +#include #include #include @@ -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> ParseProcMaps( return entries; } +PosixErrorOr FindUniqueMapsEntry( + std::vector 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 IsVsyscallEnabled() { ASSIGN_OR_RETURN_ERRNO(auto contents, GetContents("/proc/self/maps")); ASSIGN_OR_RETURN_ERRNO(auto maps, ParseProcMaps(contents)); diff --git a/test/util/proc_util.h b/test/util/proc_util.h index 14b2cc6ca..6a25ce2d0 100644 --- a/test/util/proc_util.h +++ b/test/util/proc_util.h @@ -84,6 +84,10 @@ PosixErrorOr ParseProcMapsLine(absl::string_view line); PosixErrorOr> ParseProcMaps( absl::string_view contents); +// Returns the unique entry in entries containing the given address. +PosixErrorOr FindUniqueMapsEntry( + std::vector const&, uintptr_t); + // Returns true if vsyscall (emmulation or not) is enabled. PosixErrorOr IsVsyscallEnabled();