Merge pull request #14008 from luiscape:luis/map-fixed

PiperOrigin-RevId: 964917211
This commit is contained in:
gVisor bot
2026-08-14 15:23:54 -07:00
7 changed files with 144 additions and 24 deletions
+16 -15
View File
@@ -33,21 +33,22 @@ const (
// Flags for mmap(2).
const (
MAP_SHARED = 1 << 0
MAP_PRIVATE = 1 << 1
MAP_DROPPABLE = 1 << 3
MAP_FIXED = 1 << 4
MAP_ANONYMOUS = 1 << 5
MAP_32BIT = 1 << 6 // arch/x86/include/uapi/asm/mman.h
MAP_GROWSDOWN = 1 << 8
MAP_DENYWRITE = 1 << 11
MAP_EXECUTABLE = 1 << 12
MAP_LOCKED = 1 << 13
MAP_NORESERVE = 1 << 14
MAP_POPULATE = 1 << 15
MAP_NONBLOCK = 1 << 16
MAP_STACK = 1 << 17
MAP_HUGETLB = 1 << 18
MAP_SHARED = 1 << 0
MAP_PRIVATE = 1 << 1
MAP_DROPPABLE = 1 << 3
MAP_FIXED = 1 << 4
MAP_ANONYMOUS = 1 << 5
MAP_32BIT = 1 << 6 // arch/x86/include/uapi/asm/mman.h
MAP_GROWSDOWN = 1 << 8
MAP_DENYWRITE = 1 << 11
MAP_EXECUTABLE = 1 << 12
MAP_LOCKED = 1 << 13
MAP_NORESERVE = 1 << 14
MAP_POPULATE = 1 << 15
MAP_NONBLOCK = 1 << 16
MAP_STACK = 1 << 17
MAP_HUGETLB = 1 << 18
MAP_FIXED_NOREPLACE = 1 << 20
)
// Flags for mremap(2).
+6
View File
@@ -333,6 +333,12 @@ type MMapOpts struct {
// be replaced. If Unmap is true, Fixed must be true.
Unmap bool
// NoReplace is true if MAP_FIXED_NOREPLACE semantics apply: the mapping
// must be located at Addr, and EEXIST (rather than ENOMEM) is returned if
// any mapping already exists in the range. If NoReplace is true, Fixed
// must be true and Unmap must be false.
NoReplace bool
// If Map32Bit is true, all addresses in the created mapping must fit in a
// 32-bit integer. (Note that the "end address" of the mapping, i.e. the
// address of the first byte *after* the mapping, need not fit in a 32-bit
+3
View File
@@ -110,6 +110,9 @@ func (mm *MemoryManager) MMap(ctx context.Context, opts memmap.MMapOpts) (hostar
if opts.Unmap && !opts.Fixed {
return 0, linuxerr.EINVAL
}
if opts.NoReplace && (!opts.Fixed || opts.Unmap) {
return 0, linuxerr.EINVAL
}
if opts.GrowsDown && opts.Mappable != nil {
return 0, linuxerr.EINVAL
}
+18 -2
View File
@@ -50,6 +50,7 @@ func (mm *MemoryManager) createVMALocked(ctx context.Context, opts memmap.MMapOp
Stack: opts.Stack,
Private: opts.Private,
Unmap: opts.Unmap,
NoReplace: opts.NoReplace,
Map32Bit: opts.Map32Bit,
})
if err != nil {
@@ -148,6 +149,9 @@ type findAvailableOpts struct {
// - Addr must be page-aligned.
//
// - Unmap allows existing guard pages in the returned range.
//
// - NoReplace allows existing guard pages (but not existing vmas) in the
// returned range.
Addr hostarch.Addr
Fixed bool
@@ -155,6 +159,7 @@ type findAvailableOpts struct {
Stack bool
Private bool
Unmap bool
NoReplace bool
Map32Bit bool
}
@@ -183,8 +188,19 @@ func (mm *MemoryManager) findAvailableLocked(length uint64, opts findAvailableOp
if opts.Unmap {
return ar.Start, nil
}
vgap := mm.vmas.FindGap(ar.Start)
if opts.NoReplace {
// MAP_FIXED_NOREPLACE fails with EEXIST iff the requested
// range overlaps an existing vma; like Linux, it may map over
// guard pages of adjacent MAP_GROWSDOWN mappings. Compare
// Linux's mm/mmap.c:do_mmap() => find_vma_intersection().
if vgap.Ok() && vgap.Range().IsSupersetOf(ar) {
return ar.Start, nil
}
return 0, linuxerr.EEXIST
}
// Check for the presence of an existing vma or guard page.
if vgap := mm.vmas.FindGap(ar.Start); vgap.Ok() && vgap.availableRange().IsSupersetOf(ar) {
if vgap.Ok() && vgap.availableRange().IsSupersetOf(ar) {
return ar.Start, nil
}
}
@@ -603,7 +619,7 @@ func (vseg vmaIterator) seekNextLowerBound(addr hostarch.Addr) vmaIterator {
}
// availableRange returns the subset of vgap.Range() in which new vmas may be
// created without MMapOpts.Unmap == true.
// created without MMapOpts.Unmap or MMapOpts.NoReplace set.
func (vgap vmaGapIterator) availableRange() hostarch.AddrRange {
ar := vgap.Range()
next := vgap.NextSegment()
+4
View File
@@ -89,4 +89,8 @@ var MmapFlagSet = abi.FlagSet{
Flag: linux.MAP_HUGETLB,
Name: "MAP_HUGETLB",
},
{
Flag: linux.MAP_FIXED_NOREPLACE,
Name: "MAP_FIXED_NOREPLACE",
},
}
+9 -7
View File
@@ -47,6 +47,7 @@ func Mmap(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *
flags := args[3].Int()
fd := args[4].Int()
fixed := flags&linux.MAP_FIXED != 0
noReplace := flags&linux.MAP_FIXED_NOREPLACE != 0
private := flags&linux.MAP_PRIVATE != 0
shared := flags&linux.MAP_SHARED != 0
anon := flags&linux.MAP_ANONYMOUS != 0
@@ -58,13 +59,14 @@ func Mmap(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *
}
opts := memmap.MMapOpts{
Length: args[1].Uint64(),
Offset: args[5].Uint64(),
Addr: args[0].Pointer(),
Fixed: fixed,
Unmap: fixed,
Map32Bit: map32bit,
Private: private,
Length: args[1].Uint64(),
Offset: args[5].Uint64(),
Addr: args[0].Pointer(),
Fixed: fixed || noReplace,
Unmap: fixed && !noReplace,
NoReplace: noReplace,
Map32Bit: map32bit,
Private: private,
Perms: hostarch.AccessType{
Read: linux.PROT_READ&prot != 0,
Write: linux.PROT_WRITE&prot != 0,
+88
View File
@@ -30,6 +30,7 @@
#include <sys/wait.h>
#include <unistd.h>
#include <cstdint>
#include <limits>
#include <vector>
@@ -51,6 +52,10 @@ using ::testing::AnyOf;
using ::testing::Eq;
using ::testing::Gt;
#ifndef MAP_FIXED_NOREPLACE
#define MAP_FIXED_NOREPLACE 0x100000
#endif
namespace gvisor {
namespace testing {
@@ -385,6 +390,89 @@ TEST_F(MMapTest, MapFixed64) {
}
#endif
// Kernels predating Linux 4.17 silently ignore MAP_FIXED_NOREPLACE, treating
// addr as a hint; partially overlapping requests return EEXIST only from 4.19
// (Linux commit 7aa867dd8952 "mm/mmap.c: don't clobber partially overlapping
// mappings with MAP_FIXED_NOREPLACE").
#define SKIP_IF_NO_MAP_FIXED_NOREPLACE() \
do { \
if (!IsRunningOnGvisor()) { \
auto version = ASSERT_NO_ERRNO_AND_VALUE(GetKernelVersion()); \
SKIP_IF(version.major < 4 || \
(version.major == 4 && version.minor < 19)); \
} \
} while (0)
// MAP_FIXED_NOREPLACE gives us exactly the requested address when the range
// is free.
TEST_F(MMapTest, MapFixedNoReplaceEmpty) {
SKIP_IF_NO_MAP_FIXED_NOREPLACE();
EXPECT_THAT(Map(0x30000000, kPageSize, PROT_NONE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE, -1, 0),
SyscallSucceedsWithValue(0x30000000));
}
// MAP_FIXED_NOREPLACE fails with EEXIST if any mapping already exists in the
// requested range; it must not fall back to a different address or clobber
// the existing mapping.
TEST_F(MMapTest, MapFixedNoReplaceExisting) {
SKIP_IF_NO_MAP_FIXED_NOREPLACE();
// Establish a writable mapping and store a sentinel value in it.
ASSERT_THAT(Map(0x30000000, kPageSize, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0),
SyscallSucceedsWithValue(0x30000000));
constexpr uint32_t kSentinel = 0xdeadbeef;
auto* val = reinterpret_cast<volatile uint32_t*>(addr_);
*val = kSentinel;
// MAP_FIXED_NOREPLACE over the occupied range must fail with EEXIST, without
// relocating to a different address.
EXPECT_THAT(reinterpret_cast<uintptr_t>(mmap(
reinterpret_cast<void*>(0x30000000), kPageSize, PROT_NONE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE, -1, 0)),
SyscallFailsWithErrno(EEXIST));
// The existing mapping must be untouched: the sentinel is still there.
EXPECT_EQ(*val, kSentinel);
}
// MAP_FIXED_NOREPLACE fails with EEXIST even if the requested range only
// partially overlaps an existing mapping. Compare Linux commit 7aa867dd8952
// ("mm/mmap.c: don't clobber partially overlapping mappings with
// MAP_FIXED_NOREPLACE").
TEST_F(MMapTest, MapFixedNoReplacePartialOverlap) {
SKIP_IF_NO_MAP_FIXED_NOREPLACE();
ASSERT_THAT(Map(0x30001000, kPageSize, PROT_NONE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0),
SyscallSucceedsWithValue(0x30001000));
EXPECT_THAT(reinterpret_cast<uintptr_t>(mmap(
reinterpret_cast<void*>(0x30000000), 2 * kPageSize, PROT_NONE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE, -1, 0)),
SyscallFailsWithErrno(EEXIST));
}
// MAP_FIXED_NOREPLACE takes precedence over MAP_FIXED: existing mappings are
// not clobbered.
TEST_F(MMapTest, MapFixedNoReplaceWithMapFixed) {
SKIP_IF_NO_MAP_FIXED_NOREPLACE();
ASSERT_THAT(Map(0x30000000, kPageSize, PROT_NONE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0),
SyscallSucceedsWithValue(0x30000000));
EXPECT_THAT(reinterpret_cast<uintptr_t>(mmap(
reinterpret_cast<void*>(0x30000000), kPageSize, PROT_NONE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED | MAP_FIXED_NOREPLACE,
-1, 0)),
SyscallFailsWithErrno(EEXIST));
}
// Like MAP_FIXED, MAP_FIXED_NOREPLACE requires a page-aligned address.
TEST_F(MMapTest, MapFixedNoReplaceAlignment) {
SKIP_IF_NO_MAP_FIXED_NOREPLACE();
EXPECT_THAT(Map(0x30000001, kPageSize, PROT_NONE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE, -1, 0),
SyscallFailsWithErrno(EINVAL));
}
// MAP_STACK allowed.
// There isn't a good way to verify it did anything.
TEST_F(MMapTest, MapStack) {