Artifact 43dbd361d6aac031f095eec09c885d36107548b18571818132d7aa43bed50cf9:
- File
src/OFSecureData.m
— part of check-in
[0e45b7bb1c]
at
2018-04-08 13:37:31
on branch trunk
— OFSecureData: Add a memory allocator
This avoids having at last one page per OFSecureData and allows multiple
small OFSecureData sharing the same page, which is important on systems
where the number of pages that can be locked is very limited. (user: js, size: 10038) [annotate] [blame] [check-ins using]
/* * 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 <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 32 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) @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) return true; #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 (_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