blob: 8baff84d107decfe0614acd131b64265bb5c2c24 [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* 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 "berberis/base/mmap.h"
#include <sys/mman.h>
#include <atomic>
#include <cstdint>
#include <cstdlib>
#include <random> // for old versions of GLIBC only (see below)
#include "berberis/base/checks.h"
namespace berberis {
#if defined(__LP64__) && !defined(__x86_64__)
namespace {
// arc4random was introduced in GLIBC 2.36
#if defined(__GLIBC__) && ((__GLIBC__ < 2) || ((__GLIBC__ == 2) && (__GLIBC_MINOR__ < 36)))
uint32_t arc4random_uniform(uint32_t upper_bound) {
// Fall back to implementation-defined stl random
static std::random_device random_device("/dev/urandom");
static std::mt19937 generator(random_device());
std::uniform_int_distribution<uint32_t> distrib(0, upper_bound);
return distrib(generator);
}
#endif
void* TryMmap32Bit(MmapImplArgs args) {
// Outside of x86_64 mapping in the lower 32bit address space
// is achieved by trying to map at the random 32bit address with
// hint and then verifying that the resulted map indeed falls in
// lower 32bit address space. Note that if another mapping already
// exists "the kernel picks a new address that may or may not
// depend on the hint." which makes it more difficult.
constexpr uintptr_t kMinAddress = 0x10000;
// This is always positive hence no sign-extend.
constexpr uintptr_t kMaxAddress = std::numeric_limits<int32_t>::max();
// This number is somewhat arbitrary. We want it to be big enough so that it
// doesn't fail prematurely when 2G space has lower availability, but not too
// big so it doesn't take forever.
constexpr size_t kMaxMapAttempts = 512;
static std::atomic<uintptr_t> saved_hint = 0;
uintptr_t hint = saved_hint.load();
uintptr_t arc4_random_upper_bound = kMaxAddress - kMinAddress;
if (args.size == 0) {
return MAP_FAILED;
}
if (__builtin_usubl_overflow(arc4_random_upper_bound, args.size, &arc4_random_upper_bound)) {
return MAP_FAILED;
}
CHECK_LE(arc4_random_upper_bound, kMaxAddress - kMinAddress);
if (hint == 0 || hint > (arc4_random_upper_bound + kMinAddress)) {
hint = arc4random_uniform(static_cast<uint32_t>(arc4_random_upper_bound)) + kMinAddress;
}
for (size_t i = 0; i < kMaxMapAttempts; i++) {
// PROT_NONE, MAP_NORESERVE to make it faster since this may take several attempts.
// We'll do another mmap() with proper flags on top of this one below.
void* addr = mmap(reinterpret_cast<void*>(hint),
args.size,
PROT_NONE,
MAP_ANONYMOUS | MAP_PRIVATE | MAP_NORESERVE,
0,
0);
if (addr == MAP_FAILED) {
return MAP_FAILED;
}
uintptr_t start = reinterpret_cast<uintptr_t>(addr);
uintptr_t end = start + args.size;
if (end <= kMaxAddress) {
saved_hint.store(AlignUpPageSize(end)); // next hint
return mmap(addr, args.size, args.prot, MAP_FIXED | args.flags, args.fd, args.offset);
}
hint = arc4random_uniform(static_cast<uint32_t>(arc4_random_upper_bound)) + kMinAddress;
}
saved_hint.store(0);
return MAP_FAILED;
}
} // namespace
#endif // defined(__LP64__) && !defined(__x86_64__)
void* MmapImpl(MmapImplArgs args) {
if ((args.berberis_flags & kMmapBerberis32Bit) != 0) {
// This doesn't make sense for MAP_FIXED
CHECK_EQ(args.flags & MAP_FIXED, 0);
#if defined(__x86_64__)
args.flags |= MAP_32BIT;
#elif defined(__LP64__)
return TryMmap32Bit(args);
#endif
}
return mmap(args.addr, args.size, args.prot, args.flags, args.fd, args.offset);
}
void* MmapImplOrDie(MmapImplArgs args) {
void* ptr = MmapImpl(args);
CHECK_NE(ptr, MAP_FAILED);
return ptr;
}
void MunmapOrDie(void* ptr, size_t size) {
int res = munmap(ptr, size);
CHECK_EQ(res, 0);
}
void MprotectOrDie(void* ptr, size_t size, int prot) {
int res = mprotect(ptr, size, prot);
CHECK_EQ(res, 0);
}
} // namespace berberis