/* * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, * 2018 * Jonathan Schleifer <js@heap.zone> * * All rights reserved. * * This file is part of ObjFW. It may be distributed under the terms of the * Q Public License 1.0, which can be found in the file LICENSE.QPL included in * the packaging of this file. * * Alternatively, it may be distributed under the terms of the GNU General * Public License, either version 2 or 3, which can be found in the file * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this * file. */ #include "config.h" #include <errno.h> #include <stdlib.h> #ifdef HAVE_SYS_MMAN_H # include <sys/mman.h> #endif #import "OFSecureData.h" #import "OFString.h" #import "OFSystemInfo.h" #import "OFInitializationFailedException.h" #import "OFInvalidArgumentException.h" #import "OFOutOfMemoryException.h" #import "OFOutOfRangeException.h" #ifdef OF_HAVE_THREADS # import "threading.h" #endif #define CHUNK_SIZE 16 struct page { struct page *next, *previous; void *map; unsigned char *page; }; #if defined(OF_HAVE_COMPILER_TLS) static thread_local struct page *firstPage = NULL; static thread_local struct page *lastPage = NULL; #elif defined(OF_HAVE_THREADS) static of_tlskey_t firstPageKey, lastPageKey; #else static struct page *firstPage = NULL; static struct page *lastPage = NULL; #endif static void * mapPages(size_t numPages) { size_t pageSize = [OFSystemInfo pageSize]; void *pointer; if (numPages > SIZE_MAX / pageSize) @throw [OFOutOfRangeException exception]; #if defined(HAVE_MMAP) && defined(HAVE_MLOCK) && defined(MAP_ANON) if ((pointer = mmap(NULL, numPages * pageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0)) == MAP_FAILED) @throw [OFOutOfMemoryException exceptionWithRequestedSize: pageSize]; if (mlock(pointer, numPages * pageSize) != 0 && errno != EPERM) @throw [OFOutOfMemoryException exceptionWithRequestedSize: pageSize]; #else if ((pointer = malloc(numPages * pageSize)) == NULL) @throw [OFOutOfMemoryException exceptionWithRequestedSize: pageSize]; #endif return pointer; } static void unmapPages(void *pointer, size_t numPages) { size_t pageSize = [OFSystemInfo pageSize]; if (numPages > SIZE_MAX / pageSize) @throw [OFOutOfRangeException exception]; #if defined(HAVE_MMAP) && defined(HAVE_MLOCK) && defined(MAP_ANON) munlock(pointer, numPages * pageSize); munmap(pointer, numPages * pageSize); #else free(pointer); #endif } static struct page * addPage(void) { size_t pageSize = [OFSystemInfo pageSize]; size_t mapSize = OF_ROUND_UP_POW2(8, pageSize / CHUNK_SIZE) / 8; struct page *page; #if !defined(OF_HAVE_COMPILER_TLS) && defined(OF_HAVE_THREADS) struct page *lastPage; #endif if ((page = malloc(sizeof(*page))) == NULL) @throw [OFOutOfMemoryException exceptionWithRequestedSize: sizeof(*page)]; if ((page->map = malloc(mapSize)) == NULL) @throw [OFOutOfMemoryException exceptionWithRequestedSize: mapSize]; of_explicit_memset(page->map, 0, mapSize); page->page = mapPages(1); of_explicit_memset(page->page, 0, pageSize); #if !defined(OF_HAVE_COMPILER_TLS) && defined(OF_HAVE_THREADS) lastPage = of_tlskey_get(lastPageKey); #endif page->previous = lastPage; page->next = NULL; if (lastPage != NULL) lastPage->next = page; #if defined(OF_HAVE_COMPILER_TLS) || !defined(OF_HAVE_THREADS) lastPage = page; if (firstPage == NULL) firstPage = page; #else OF_ENSURE(of_tlskey_set(lastPageKey, page)); if (of_tlskey_get(firstPageKey) == NULL) OF_ENSURE(of_tlskey_set(firstPageKey, page)); #endif return page; } static void removePageIfEmpty(struct page *page) { unsigned char *map = page->map; size_t pageSize = [OFSystemInfo pageSize]; size_t mapSize = OF_ROUND_UP_POW2(8, pageSize / CHUNK_SIZE) / 8; for (size_t i = 0; i < mapSize; i++) if (map[i] != 0) return; unmapPages(page->page, 1); free(page->map); if (page->previous != NULL) page->previous->next = page->next; if (page->next != NULL) page->next->previous = page->previous; #if defined(OF_HAVE_COMPILER_TLS) || !defined(OF_HAVE_THREADS) if (firstPage == page) firstPage = page->next; if (lastPage == page) lastPage = page->previous; #else if (of_tlskey_get(firstPageKey) == page) OF_ENSURE(of_tlskey_set(firstPageKey, page->next)); if (of_tlskey_get(lastPageKey) == page) OF_ENSURE(of_tlskey_set(lastPageKey, page->previous)); #endif free(page); } static void * allocateMemory(struct page *page, size_t bytes) { size_t chunks, chunksLeft, pageSize, i, firstChunk; bytes = OF_ROUND_UP_POW2(CHUNK_SIZE, bytes); chunks = chunksLeft = bytes / CHUNK_SIZE; firstChunk = 0; pageSize = [OFSystemInfo pageSize]; for (i = 0; i < pageSize / CHUNK_SIZE; i++) { if (of_bitset_isset(page->map, i)) { chunksLeft = chunks; firstChunk = i + 1; continue; } if (--chunksLeft == 0) break; } if (chunksLeft == 0) { for (size_t j = firstChunk; j < firstChunk + chunks; j++) of_bitset_set(page->map, j); return page->page + (CHUNK_SIZE * firstChunk); } return NULL; } static void freeMemory(struct page *page, void *pointer, size_t bytes) { size_t chunks, chunkIndex; bytes = OF_ROUND_UP_POW2(CHUNK_SIZE, bytes); chunks = bytes / CHUNK_SIZE; chunkIndex = ((uintptr_t)pointer - (uintptr_t)page->page) / CHUNK_SIZE; of_explicit_memset(pointer, 0, bytes); for (size_t i = 0; i < chunks; i++) of_bitset_clear(page->map, chunkIndex + i); } @implementation OFSecureData #if !defined(OF_HAVE_COMPILER_TLS) && defined(OF_HAVE_THREADS) + (void)initialize { if (self != [OFSecureData class]) return; if (!of_tlskey_new(&firstPageKey) || !of_tlskey_new(&lastPageKey)) @throw [OFInitializationFailedException exceptionWithClass: self]; } #endif + (bool)isSecure { #if defined(HAVE_MMAP) && defined(HAVE_MLOCK) && defined(MAP_ANON) bool isSecure = true; size_t pageSize = [OFSystemInfo pageSize]; void *pointer; if ((pointer = mmap(NULL, pageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0)) == MAP_FAILED) @throw [OFOutOfMemoryException exceptionWithRequestedSize: pageSize]; if (mlock(pointer, pageSize) != 0) { if (errno != EPERM) { munmap(pointer, pageSize); @throw [OFOutOfMemoryException exceptionWithRequestedSize: pageSize]; } isSecure = false; } munlock(pointer, pageSize); munmap(pointer, pageSize); return isSecure; #else return false; #endif } + (instancetype)dataWithCount: (size_t)count { return [[[self alloc] initWithCount: count] autorelease]; } + (instancetype)dataWithItemSize: (size_t)itemSize count: (size_t)count { return [[[self alloc] initWithItemSize: itemSize count: count] autorelease]; } #ifdef OF_HAVE_FILES + (instancetype)dataWithContentsOfFile: (OFString *)path { OF_UNRECOGNIZED_SELECTOR } #endif + (instancetype)dataWithContentsOfURL: (OFURL *)URL { OF_UNRECOGNIZED_SELECTOR } + (instancetype)dataWithStringRepresentation: (OFString *)string { OF_UNRECOGNIZED_SELECTOR } + (instancetype)dataWithBase64EncodedString: (OFString *)string { OF_UNRECOGNIZED_SELECTOR } + (instancetype)dataWithSerialization: (OFXMLElement *)element { OF_UNRECOGNIZED_SELECTOR } - (instancetype)initWithCount: (size_t)count { return [self initWithItemSize: 1 count: count]; } - (instancetype)initWithItemSize: (size_t)itemSize count: (size_t)count { self = [super init]; @try { size_t pageSize = [OFSystemInfo pageSize]; if (count > SIZE_MAX / itemSize) @throw [OFOutOfRangeException exception]; if (count * itemSize >= pageSize) _items = mapPages(OF_ROUND_UP_POW2(pageSize, count * itemSize) / pageSize); else { #if !defined(OF_HAVE_COMPILER_TLS) && defined(OF_HAVE_THREADS) struct page *lastPage = of_tlskey_get(lastPageKey); #endif for (struct page *page = lastPage; page != NULL; page = page->previous) { _items = allocateMemory(page, count * itemSize); if (_items != NULL) { _page = page; break; } } if (_items == NULL) { _page = addPage(); _items = allocateMemory(_page, count * itemSize); if (_items == NULL) @throw [OFOutOfMemoryException exceptionWithRequestedSize: count * itemSize]; } } _itemSize = itemSize; _count = count; } @catch (id e) { [self release]; @throw e; } return self; } - (instancetype)initWithItems: (const void *)items itemSize: (size_t)itemSize count: (size_t)count { self = [self initWithItemSize: itemSize count: count]; memcpy(_items, items, count * itemSize); return self; } - (instancetype)initWithItemsNoCopy: (void *)items itemSize: (size_t)itemSize count: (size_t)count freeWhenDone: (bool)freeWhenDone { self = [self initWithItems: items itemSize: itemSize count: count]; if (freeWhenDone) { of_explicit_memset(items, 0, count * itemSize); free(items); } return self; } #ifdef OF_HAVE_FILES - (instancetype)initWithContentsOfFile: (OFString *)path { OF_INVALID_INIT_METHOD } #endif - (instancetype)initWithContentsOfURL: (OFURL *)URL { OF_INVALID_INIT_METHOD } - (instancetype)initWithStringRepresentation: (OFString *)string { OF_INVALID_INIT_METHOD } - (instancetype)initWithBase64EncodedString: (OFString *)string { OF_INVALID_INIT_METHOD } - (instancetype)initWithSerialization: (OFXMLElement *)element { OF_INVALID_INIT_METHOD } - (void)dealloc { size_t pageSize = [OFSystemInfo pageSize]; if (_count * _itemSize > pageSize) unmapPages(_items, OF_ROUND_UP_POW2(pageSize, _count * _itemSize) / pageSize); else if (_page != NULL) { if (_items != NULL) freeMemory(_page, _items, _count * _itemSize); removePageIfEmpty(_page); } [super dealloc]; } - (void)zero { of_explicit_memset(_items, 0, _count * _itemSize); } - (id)copy { return [[OFSecureData alloc] initWithItems: _items itemSize: _itemSize count: _count]; } - (id)mutableCopy { return [[OFSecureData alloc] initWithItems: _items itemSize: _itemSize count: _count]; } - (OFString *)description { return @"<OFSecureData>"; } - (OFString *)stringRepresentation { OF_UNRECOGNIZED_SELECTOR } - (OFString *)stringByBase64Encoding { OF_UNRECOGNIZED_SELECTOR } #ifdef OF_HAVE_FILES - (void)writeToFile: (OFString *)path { OF_UNRECOGNIZED_SELECTOR } #endif - (void)writeToURL: (OFURL *)URL { OF_UNRECOGNIZED_SELECTOR } - (OFXMLElement *)XMLElementBySerializing { OF_UNRECOGNIZED_SELECTOR } - (OFData *)messagePackRepresentation { OF_UNRECOGNIZED_SELECTOR } @end