AutorÃa | Ultima modificación | Ver Log |
// 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 "Crashlytics/Crashlytics/Helpers/FIRCLSAllocate.h"#include "Crashlytics/Crashlytics/Components/FIRCLSHost.h"#include "Crashlytics/Crashlytics/Helpers/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 positionwritableRegion.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 pageif (mprotect(buffer, pageSize, PROT_NONE) != 0) {FIRCLSSDKLogError("First guard protection failed %s\n", strerror(errno));return NULL;}// middle guardif (mprotect((void*)((uintptr_t)buffer + pageSize + writableRegion.size), pageSize, PROT_NONE) !=0) {FIRCLSSDKLogError("Middle guard protection failed %s\n", strerror(errno));return NULL;}// end guardif (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 regionallocator = FIRCLSAllocatorSafeAllocateFromRegion(&readableRegion, sizeof(FIRCLSAllocator));// set up its data structureallocator->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 firstallocator->protectionEnabled = true;vm_size_t pageSize = FIRCLSHostGetPageSize();// readable regionaddress =(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 donedo {originalCursor = region->cursor;// this shouldn't happen unless we make a mistake with our size pre-computationsif ((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(®ion->cursor, &originalCursor, newCursor));return originalCursor;}void* FIRCLSAllocatorSafeAllocate(FIRCLSAllocatorRef allocator,size_t size,FIRCLSAllocationType type) {FIRCLSAllocationRegion* region;if (!allocator) {// fall back to malloc in this caseFIRCLSSDKLog("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?}