blob: d3ae5c58d6e9d8977bb2ba22cfb1a344988ebb46 [file]
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef PARTITION_ALLOC_PAGE_ALLOCATOR_INTERNALS_POSIX_H_
#define PARTITION_ALLOC_PAGE_ALLOCATOR_INTERNALS_POSIX_H_
#include <sys/mman.h>
#include <sys/syscall.h>
#include <algorithm>
#include <atomic>
#include <cerrno>
#include <cstdint>
#include <cstring>
#include "partition_alloc/build_config.h"
#include "partition_alloc/buildflags.h"
#include "partition_alloc/oom.h"
#include "partition_alloc/page_allocator.h"
#include "partition_alloc/page_allocator_constants.h"
#include "partition_alloc/partition_alloc_base/compiler_specific.h"
#include "partition_alloc/partition_alloc_base/posix/eintr_wrapper.h"
#include "partition_alloc/partition_alloc_check.h"
#include "partition_alloc/thread_isolation/thread_isolation.h"
#if PA_BUILDFLAG(IS_ANDROID) || PA_BUILDFLAG(IS_LINUX)
#include <sys/prctl.h>
#endif
#if PA_BUILDFLAG(IS_LINUX) || PA_BUILDFLAG(IS_CHROMEOS)
#include <sys/resource.h>
#endif
#ifndef MAP_ANONYMOUS
#define MAP_ANONYMOUS MAP_ANON
#endif
namespace partition_alloc::internal {
#if defined(LINUX_NAME_REGION)
void NameRegion(void* start, size_t length, PageTag page_tag);
#endif // defined(LINUX_NAME_REGION)
#if PA_BUILDFLAG(IS_APPLE)
// Tests whether the version of macOS supports the MAP_JIT flag and if the
// current process is signed with the hardened runtime and the allow-jit
// entitlement, returning whether MAP_JIT should be used to allocate regions
// that will contain JIT-compiled executable code.
bool UseMapJit();
#endif // PA_BUILDFLAG(IS_IOS)
// |mmap| uses a nearby address if the hint address is blocked.
constexpr bool kHintIsAdvisory = true;
std::atomic<int32_t> s_allocPageErrorCode{0};
int GetAccessFlags(PageAccessibilityConfiguration accessibility);
uintptr_t SystemAllocPagesInternal(uintptr_t hint,
size_t length,
PageAccessibilityConfiguration accessibility,
PageTag page_tag,
int file_descriptor_for_shared_alloc) {
#if PA_BUILDFLAG(IS_APPLE)
// Use a custom tag to make it easier to distinguish PartitionAlloc regions
// in vmmap(1). Tags between 240-255 are supported.
int fd = file_descriptor_for_shared_alloc == -1
? VM_MAKE_TAG(static_cast<int>(page_tag))
: file_descriptor_for_shared_alloc;
#else
int fd = file_descriptor_for_shared_alloc;
#endif
int access_flag = GetAccessFlags(accessibility);
int map_flags = MAP_ANONYMOUS | MAP_PRIVATE;
#if PA_BUILDFLAG(IS_APPLE)
// On macOS, executables that are code signed with the "runtime" option cannot
// execute writable memory by default. They can opt into this capability by
// specifying the "com.apple.security.cs.allow-jit" code signing entitlement
// and allocating the region with the MAP_JIT flag.
static const bool kUseMapJit = UseMapJit();
if (accessibility.permissions ==
PageAccessibilityConfiguration::kInaccessibleWillJitLater &&
kUseMapJit) {
map_flags |= MAP_JIT;
// iOS devices do not support toggling the page permissions after a MAP_JIT
// call, they must be set initially. iOS has per-thread W^X state that
// takes precedence over the mapping's permissions for MAP_JIT regions.
// See https://developer.apple.com/forums/thread/672804
#if PA_BUILDFLAG(IS_IOS)
access_flag = PROT_READ | PROT_WRITE | PROT_EXEC;
#endif
}
#endif
void* ret = mmap(reinterpret_cast<void*>(hint), length, access_flag,
map_flags, fd, 0);
if (ret == MAP_FAILED) {
s_allocPageErrorCode = errno;
ret = nullptr;
}
#if defined(LINUX_NAME_REGION)
if (ret) {
NameRegion(ret, length, page_tag);
}
#endif
return reinterpret_cast<uintptr_t>(ret);
}
bool TrySetSystemPagesAccessInternal(
uintptr_t address,
size_t length,
PageAccessibilityConfiguration accessibility) {
#if PA_BUILDFLAG(ENABLE_THREAD_ISOLATION)
if (accessibility.thread_isolation.enabled) {
return 0 == MprotectWithThreadIsolation(reinterpret_cast<void*>(address),
length,
GetAccessFlags(accessibility),
accessibility.thread_isolation);
}
#endif // PA_BUILDFLAG(ENABLE_THREAD_ISOLATION)
return 0 == WrapEINTR(mprotect)(reinterpret_cast<void*>(address), length,
GetAccessFlags(accessibility));
}
void SetSystemPagesAccessInternal(
uintptr_t address,
size_t length,
PageAccessibilityConfiguration accessibility) {
int access_flags = GetAccessFlags(accessibility);
int ret;
#if PA_BUILDFLAG(ENABLE_THREAD_ISOLATION)
if (accessibility.thread_isolation.enabled) {
ret = MprotectWithThreadIsolation(reinterpret_cast<void*>(address), length,
GetAccessFlags(accessibility),
accessibility.thread_isolation);
} else
#endif // PA_BUILDFLAG(ENABLE_THREAD_ISOLATION)
{
ret = WrapEINTR(mprotect)(reinterpret_cast<void*>(address), length,
GetAccessFlags(accessibility));
}
// On Linux, man mprotect(2) states that ENOMEM is returned when (1) internal
// kernel data structures cannot be allocated, (2) the address range is
// invalid, or (3) this would split an existing mapping in a way that would
// exceed the maximum number of allowed mappings.
//
// Neither are very likely, but we still get a lot of crashes here. This is
// because setrlimit(RLIMIT_DATA)'s limit is checked and enforced here, if the
// access flags match a "data" mapping, which in our case would be MAP_PRIVATE
// | MAP_ANONYMOUS, and PROT_WRITE. see the call to may_expand_vm() in
// mm/mprotect.c in the kernel for details.
//
// In this case, we are almost certainly bumping into the sandbox limit, mark
// the crash as OOM. See SandboxLinux::LimitAddressSpace() for details.
if (ret == -1 && errno == ENOMEM) {
// Likely setrlimit(2) related.
if (access_flags & PROT_WRITE) {
OOM_CRASH(length);
} else {
// Linux-based systems have a low-ish default limit of ~65k VMAs per
// process, and we've seen crashes triggered by that. Single out this
// error so that we can track it in crashes.
OnErrnoNoMem();
}
}
PA_PCHECK(0 == ret);
}
void FreePagesInternal(uintptr_t address, size_t length) {
PA_PCHECK(0 == munmap(reinterpret_cast<void*>(address), length));
}
uintptr_t TrimMappingInternal(uintptr_t base_address,
size_t base_length,
size_t trim_length,
PageAccessibilityConfiguration accessibility,
size_t pre_slack,
size_t post_slack) {
uintptr_t ret = base_address;
// We can resize the allocation run. Release unneeded memory before and after
// the aligned range.
if (pre_slack) {
FreePages(base_address, pre_slack);
ret = base_address + pre_slack;
}
if (post_slack) {
FreePages(ret + trim_length, post_slack);
}
return ret;
}
void DecommitSystemPagesInternal(
uintptr_t address,
size_t length,
PageAccessibilityDisposition accessibility_disposition) {
// In POSIX, there is no decommit concept. Discarding is an effective way of
// implementing the Windows semantics where the OS is allowed to not swap the
// pages in the region.
DiscardSystemPages(address, length);
bool change_permissions =
accessibility_disposition == PageAccessibilityDisposition::kRequireUpdate;
#if PA_BUILDFLAG(DCHECKS_ARE_ON)
// This is not guaranteed, show that we're serious.
//
// More specifically, several callers have had issues with assuming that
// memory is zeroed, this would hopefully make these bugs more visible. We
// don't memset() everything, because ranges can be very large, and doing it
// over the entire range could make Chrome unusable with
// PA_BUILDFLAG(DCHECKS_ARE_ON).
//
// Only do it when we are about to change the permissions, since we don't know
// the previous permissions, and cannot restore them.
if (!DecommittedMemoryIsAlwaysZeroed() && change_permissions) {
// Memory may not be writable.
size_t size = std::min(length, 2 * SystemPageSize());
void* ptr = reinterpret_cast<void*>(address);
PA_CHECK(mprotect(ptr, size, PROT_WRITE) == 0);
PA_UNSAFE_TODO(memset(ptr, 0xcc, size));
}
#endif
// Make pages inaccessible, unless the caller requested to keep permissions.
//
// Note, there is a small window between these calls when the pages can be
// incorrectly touched and brought back to memory. Not ideal, but doing those
// operations in the opposite order resulted in PMF regression on Mac (see
// crbug.com/1153021).
if (change_permissions) {
SetSystemPagesAccess(address, length,
PageAccessibilityConfiguration(
PageAccessibilityConfiguration::kInaccessible));
}
}
bool DecommitAndZeroSystemPagesInternal(uintptr_t address,
size_t length,
PageTag page_tag) {
int fd = -1;
#if PA_BUILDFLAG(IS_APPLE)
fd = VM_MAKE_TAG(static_cast<int>(page_tag));
#endif
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/mmap.html: "If
// a MAP_FIXED request is successful, then any previous mappings [...] for
// those whole pages containing any part of the address range [pa,pa+len)
// shall be removed, as if by an appropriate call to munmap(), before the
// new mapping is established." As a consequence, the memory will be
// zero-initialized on next access.
void* ptr = reinterpret_cast<void*>(address);
void* ret = mmap(ptr, length, PROT_NONE,
MAP_FIXED | MAP_ANONYMOUS | MAP_PRIVATE, fd, 0);
if (ret == MAP_FAILED) {
// Decomitting may create additional VMAs (e.g. if we're decommitting pages
// in the middle of a larger mapping) and so it can fail with ENOMEM if the
// limit of VMAs is exceeded.
PA_CHECK(errno == ENOMEM);
return false;
}
PA_CHECK(ret == ptr);
// Since we just remapped the region, need to set is name again.
#if defined(LINUX_NAME_REGION)
NameRegion(ret, length, page_tag);
#endif
return true;
}
void RecommitSystemPagesInternal(
uintptr_t address,
size_t length,
PageAccessibilityConfiguration accessibility,
PageAccessibilityDisposition accessibility_disposition) {
// On POSIX systems, the caller needs to simply read the memory to recommit
// it. However, if decommit changed the permissions, recommit has to change
// them back.
if (accessibility_disposition ==
PageAccessibilityDisposition::kRequireUpdate) {
SetSystemPagesAccess(address, length, accessibility);
}
#if PA_BUILDFLAG(IS_APPLE)
// On macOS, to update accounting, we need to make another syscall. For more
// details, see https://crbug.com/823915.
madvise(reinterpret_cast<void*>(address), length, MADV_FREE_REUSE);
#endif
}
bool TryRecommitSystemPagesInternal(
uintptr_t address,
size_t length,
PageAccessibilityConfiguration accessibility,
PageAccessibilityDisposition accessibility_disposition) {
// On POSIX systems, the caller needs to simply read the memory to recommit
// it. However, if decommit changed the permissions, recommit has to change
// them back.
if (accessibility_disposition ==
PageAccessibilityDisposition::kRequireUpdate) {
bool ok = TrySetSystemPagesAccess(address, length, accessibility);
if (!ok) {
return false;
}
}
#if PA_BUILDFLAG(IS_APPLE)
// On macOS, to update accounting, we need to make another syscall. For more
// details, see https://crbug.com/823915.
madvise(reinterpret_cast<void*>(address), length, MADV_FREE_REUSE);
#endif
return true;
}
void DiscardSystemPagesInternal(uintptr_t address, size_t length) {
void* ptr = reinterpret_cast<void*>(address);
#if PA_BUILDFLAG(IS_APPLE)
int ret = madvise(ptr, length, MADV_FREE_REUSABLE);
if (ret) {
// MADV_FREE_REUSABLE sometimes fails, so fall back to MADV_DONTNEED.
ret = madvise(ptr, length, MADV_DONTNEED);
}
PA_PCHECK(ret == 0);
#else // PA_BUILDFLAG(IS_APPLE)
// We have experimented with other flags, but with suboptimal results.
//
// MADV_FREE (Linux): Makes our memory measurements less predictable;
// performance benefits unclear.
//
// Therefore, we just do the simple thing: MADV_DONTNEED.
PA_PCHECK(0 == madvise(ptr, length, MADV_DONTNEED));
#endif // PA_BUILDFLAG(IS_APPLE)
}
bool SealSystemPagesInternal(uintptr_t address, size_t length) {
// TODO(sroettger): we either need to ensure that __NR_mseal is defined in the
// headers used by builders or define it ourselves.
#if PA_BUILDFLAG(IS_LINUX) && defined(__NR_mseal)
long ret = syscall(__NR_mseal, address, length, 0);
return ret == 0;
#else
return false;
#endif
}
} // namespace partition_alloc::internal
#endif // PARTITION_ALLOC_PAGE_ALLOCATOR_INTERNALS_POSIX_H_