vn-verdnaturachat/ios/Pods/FirebaseCrashlytics/Crashlytics/Crashlytics/Helpers/FIRCLSAllocate.c

239 lines
7.4 KiB
C

// Copyright 2019 Google
//
// 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 <stdatomic.h>
#include "FIRCLSAllocate.h"
#include "FIRCLSHost.h"
#include "FIRCLSUtility.h"
#include <errno.h>
#include <libkern/OSAtomic.h>
#include <mach/vm_map.h>
#include <mach/vm_param.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
void* FIRCLSAllocatorSafeAllocateFromRegion(FIRCLSAllocationRegion* region, size_t size);
FIRCLSAllocatorRef FIRCLSAllocatorCreate(size_t writableSpace, size_t readableSpace) {
FIRCLSAllocatorRef allocator;
FIRCLSAllocationRegion writableRegion;
FIRCLSAllocationRegion readableRegion;
size_t allocationSize;
vm_size_t pageSize;
void* buffer;
// | GUARD | WRITABLE_REGION | GUARD | READABLE_REGION | GUARD |
pageSize = FIRCLSHostGetPageSize();
readableSpace += sizeof(FIRCLSAllocator); // add the space for our allocator itself
// we can only protect at the page level, so we need all of our regions to be
// exact multples of pages. But, we don't need anything in the special-case of zero.
writableRegion.size = 0;
if (writableSpace > 0) {
writableRegion.size = ((writableSpace / pageSize) + 1) * pageSize;
}
readableRegion.size = 0;
if (readableSpace > 0) {
readableRegion.size = ((readableSpace / pageSize) + 1) * pageSize;
}
// Make one big, continous allocation, adding additional pages for our guards. Note
// that we cannot use malloc (or valloc) in this case, because we need to assert full
// ownership over these allocations. mmap is a much better choice. We also mark these
// pages as MAP_NOCACHE.
allocationSize = writableRegion.size + readableRegion.size + pageSize * 3;
buffer =
mmap(0, allocationSize, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE | MAP_NOCACHE, -1, 0);
if (buffer == MAP_FAILED) {
FIRCLSSDKLogError("Mapping failed %s\n", strerror(errno));
return NULL;
}
// move our cursors into position
writableRegion.cursor = (void*)((uintptr_t)buffer + pageSize);
readableRegion.cursor = (void*)((uintptr_t)buffer + pageSize + writableRegion.size + pageSize);
writableRegion.start = writableRegion.cursor;
readableRegion.start = readableRegion.cursor;
FIRCLSSDKLogInfo("Mapping: %p %p %p, total: %zu K\n", buffer, writableRegion.start,
readableRegion.start, allocationSize / 1024);
// protect first guard page
if (mprotect(buffer, pageSize, PROT_NONE) != 0) {
FIRCLSSDKLogError("First guard protection failed %s\n", strerror(errno));
return NULL;
}
// middle guard
if (mprotect((void*)((uintptr_t)buffer + pageSize + writableRegion.size), pageSize, PROT_NONE) !=
0) {
FIRCLSSDKLogError("Middle guard protection failed %s\n", strerror(errno));
return NULL;
}
// end guard
if (mprotect((void*)((uintptr_t)buffer + pageSize + writableRegion.size + pageSize +
readableRegion.size),
pageSize, PROT_NONE) != 0) {
FIRCLSSDKLogError("Last guard protection failed %s\n", strerror(errno));
return NULL;
}
// now, perform our first "allocation", which is to place our allocator into the read-only region
allocator = FIRCLSAllocatorSafeAllocateFromRegion(&readableRegion, sizeof(FIRCLSAllocator));
// set up its data structure
allocator->buffer = buffer;
allocator->protectionEnabled = false;
allocator->readableRegion = readableRegion;
allocator->writeableRegion = writableRegion;
FIRCLSSDKLogDebug("Allocator successfully created %p", allocator);
return allocator;
}
void FIRCLSAllocatorDestroy(FIRCLSAllocatorRef allocator) {
if (allocator) {
}
}
bool FIRCLSAllocatorProtect(FIRCLSAllocatorRef allocator) {
void* address;
if (!FIRCLSIsValidPointer(allocator)) {
FIRCLSSDKLogError("Invalid allocator");
return false;
}
if (allocator->protectionEnabled) {
FIRCLSSDKLogWarn("Write protection already enabled");
return true;
}
// This has to be done first
allocator->protectionEnabled = true;
vm_size_t pageSize = FIRCLSHostGetPageSize();
// readable region
address =
(void*)((uintptr_t)allocator->buffer + pageSize + allocator->writeableRegion.size + pageSize);
return mprotect(address, allocator->readableRegion.size, PROT_READ) == 0;
}
bool FIRCLSAllocatorUnprotect(FIRCLSAllocatorRef allocator) {
size_t bufferSize;
if (!allocator) {
return false;
}
vm_size_t pageSize = FIRCLSHostGetPageSize();
bufferSize = (uintptr_t)allocator->buffer + pageSize + allocator->writeableRegion.size +
pageSize + allocator->readableRegion.size + pageSize;
allocator->protectionEnabled =
!(mprotect(allocator->buffer, bufferSize, PROT_READ | PROT_WRITE) == 0);
return allocator->protectionEnabled;
}
void* FIRCLSAllocatorSafeAllocateFromRegion(FIRCLSAllocationRegion* region, size_t size) {
void* newCursor;
void* originalCursor;
// Here's the idea
// - read the current cursor
// - compute what our new cursor should be
// - attempt a swap
// if the swap fails, some other thread has modified stuff, and we have to start again
// if the swap works, everything has been updated correctly and we are done
do {
originalCursor = region->cursor;
// this shouldn't happen unless we make a mistake with our size pre-computations
if ((uintptr_t)originalCursor - (uintptr_t)region->start + size > region->size) {
FIRCLSSDKLog("Unable to allocate sufficient memory, falling back to malloc\n");
void* ptr = malloc(size);
if (!ptr) {
FIRCLSSDKLog("Unable to malloc in FIRCLSAllocatorSafeAllocateFromRegion\n");
return NULL;
}
return ptr;
}
newCursor = (void*)((uintptr_t)originalCursor + size);
} while (!atomic_compare_exchange_strong(&region->cursor, &originalCursor, newCursor));
return originalCursor;
}
void* FIRCLSAllocatorSafeAllocate(FIRCLSAllocatorRef allocator,
size_t size,
FIRCLSAllocationType type) {
FIRCLSAllocationRegion* region;
if (!allocator) {
// fall back to malloc in this case
FIRCLSSDKLog("Allocator invalid, falling back to malloc\n");
void* ptr = malloc(size);
if (!ptr) {
FIRCLSSDKLog("Unable to malloc in FIRCLSAllocatorSafeAllocate\n");
return NULL;
}
return ptr;
}
if (allocator->protectionEnabled) {
FIRCLSSDKLog("Allocator already protected, falling back to malloc\n");
void* ptr = malloc(size);
if (!ptr) {
FIRCLSSDKLog("Unable to malloc in FIRCLSAllocatorSafeAllocate\n");
return NULL;
}
return ptr;
}
switch (type) {
case CLS_READONLY:
region = &allocator->readableRegion;
break;
case CLS_READWRITE:
region = &allocator->writeableRegion;
break;
default:
return NULL;
}
return FIRCLSAllocatorSafeAllocateFromRegion(region, size);
}
void FIRCLSAllocatorFree(FIRCLSAllocatorRef allocator, void* ptr) {
if (!allocator) {
free(ptr);
}
// how do we do deallocations?
}