ObjFW  Check-in [60caadeb5d]

Overview
Comment:Make +[OFSecureData isSecure] per instance

The reason for this change is that whether non-swappable memory can be
allocated or not is something that changes over time, so calling
+[isSecure] always had a potential for a race. The only reliable way is
to allocate the memory and then report whether it's swappable or not.

It's also called -[isSwappable] now to be more precise.

Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 60caadeb5de31d6b25880791350627058b25b359d391e1f7917dbd6c80102eda
User & Date: js on 2019-12-15 14:42:19
Other Links: manifest | tags
Context
2019-12-27
00:41
OFSecureData: Add allowsSwappableMemory property check-in: e629dc83a9 user: js tags: trunk
2019-12-15
15:37
Merge branch 'master' into 1.0 check-in: 11e303eb3b user: js tags: 1.0
14:42
Make +[OFSecureData isSecure] per instance check-in: 60caadeb5d user: js tags: trunk
2019-12-14
19:00
Allow numbers for of_dns_{class,record_type}_parse check-in: 4fc1f15ab2 user: js tags: trunk
Changes

Modified src/OFSecureData.h from [4b4455fb4b] to [95d6612ecd].

18
19
20
21
22
23
24
25
26
27
28
29


30
31
32
33
34

35
36
37



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#import "OFData.h"

OF_ASSUME_NONNULL_BEGIN

/*!
 * @class OFSecureData OFSecureData.h ObjFW/OFSecureData.h
 *
 * @brief A class for storing arbitrary data in secure memory, securely wiping
 *	  it when it gets deallocated.
 *
 * @note Secure memory might be unavailable on the platform, in which case this
 *	 falls back to insecure (potentially swappable) memory.


 */
OF_SUBCLASSING_RESTRICTED
@interface OFSecureData: OFData
{
	struct page *_page;

}

#ifdef OF_HAVE_CLASS_PROPERTIES



@property (class, readonly, nonatomic, getter=isSecure) bool secure;
#endif

/*!
 * @brief All items of the OFSecureData as a C array.
 *
 * Modifying the returned array directly is allowed and will change the contents
 * of the data.
 */
@property (readonly, nonatomic) void *mutableItems OF_RETURNS_INNER_POINTER;

/*!
 * @brief Whether OFSecureData is secure, meaning preventing the data from
 *	  being swapped out is supported.
 */
+ (bool)isSecure;

/*!
 * @brief Preallocates the specified number of bytes.
 *
 * This is useful to allocate secure memory before enabling a sandbox that does
 * not allow it anymore.
 *
 * @note This may only be called once per thread!







|
|

|
|
>
>





>


<
>
>
>
|
<









<
<
<
<
<
<







18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

40
41
42
43

44
45
46
47
48
49
50
51
52






53
54
55
56
57
58
59
#import "OFData.h"

OF_ASSUME_NONNULL_BEGIN

/*!
 * @class OFSecureData OFSecureData.h ObjFW/OFSecureData.h
 *
 * @brief A class for storing arbitrary data in secure (non-swappable) memory,
 *	  securely wiping it when it gets deallocated.
 *
 * @warning Non-swappable memory might be unavailable, in which case this falls
 *	    back to swappable memory, but still wipes the data when it gets
 *	    deallocated. Check the @ref swappable property to see whether a
 *	    particular OFSecureData was allocated in swappable memory.
 */
OF_SUBCLASSING_RESTRICTED
@interface OFSecureData: OFData
{
	struct page *_page;
	bool _swappable;
}


/*!
 * @brief Whether the OFSecureData is in swappable memory.
 */
@property (readonly, nonatomic, getter=isSwappable) bool swappable;


/*!
 * @brief All items of the OFSecureData as a C array.
 *
 * Modifying the returned array directly is allowed and will change the contents
 * of the data.
 */
@property (readonly, nonatomic) void *mutableItems OF_RETURNS_INNER_POINTER;







/*!
 * @brief Preallocates the specified number of bytes.
 *
 * This is useful to allocate secure memory before enabling a sandbox that does
 * not allow it anymore.
 *
 * @note This may only be called once per thread!

Modified src/OFSecureData.m from [4b72bb7708] to [0b2ff22574].

38
39
40
41
42
43
44

45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84


85
86
87
88
89
90
91
92
93
94
95
96
97
98

99
100
101
102
103
104
105
106
#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;
static thread_local struct page **preallocatedPages = NULL;
static thread_local size_t numPreallocatedPages = 0;
#elif defined(OF_HAVE_THREADS)
static of_tlskey_t firstPageKey, lastPageKey;
static of_tlskey_t preallocatedPagesKey, numPreallocatedPagesKey;
#else
static struct page *firstPage = NULL;
static struct page *lastPage = NULL;
static struct page **preallocatedPages = NULL;
static size_t numPreallocatedPages = 0;
#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 *







>



















|













|
<
<




>
>






|







>
|







38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79


80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
#endif

#define CHUNK_SIZE 16

struct page {
	struct page *next, *previous;
	void *map;
	bool swappable;
	unsigned char *page;
};

#if defined(OF_HAVE_COMPILER_TLS)
static thread_local struct page *firstPage = NULL;
static thread_local struct page *lastPage = NULL;
static thread_local struct page **preallocatedPages = NULL;
static thread_local size_t numPreallocatedPages = 0;
#elif defined(OF_HAVE_THREADS)
static of_tlskey_t firstPageKey, lastPageKey;
static of_tlskey_t preallocatedPagesKey, numPreallocatedPagesKey;
#else
static struct page *firstPage = NULL;
static struct page *lastPage = NULL;
static struct page **preallocatedPages = NULL;
static size_t numPreallocatedPages = 0;
#endif

static void *
mapPages(size_t numPages, bool *swappable)
{
	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];

	*swappable = (mlock(pointer, numPages * pageSize) != 0);


#else
	if ((pointer = malloc(numPages * pageSize)) == NULL)
		@throw [OFOutOfMemoryException
		    exceptionWithRequestedSize: pageSize];

	*swappable = true;
#endif

	return pointer;
}

static void
unmapPages(void *pointer, size_t numPages, bool swappable)
{
	size_t pageSize = [OFSystemInfo pageSize];

	if (numPages > SIZE_MAX / pageSize)
		@throw [OFOutOfRangeException exception];

#if defined(HAVE_MMAP) && defined(HAVE_MLOCK) && defined(MAP_ANON)
	if (!swappable)
		munlock(pointer, numPages * pageSize);
	munmap(pointer, numPages * pageSize);
#else
	free(pointer);
#endif
}

static struct page *
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
		@throw [OFOutOfMemoryException
		    exceptionWithRequestedSize: sizeof(*page)];

	if ((page->map = calloc(1, mapSize)) == NULL)
		@throw [OFOutOfMemoryException
		    exceptionWithRequestedSize: 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;







|







152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
		@throw [OFOutOfMemoryException
		    exceptionWithRequestedSize: sizeof(*page)];

	if ((page->map = calloc(1, mapSize)) == NULL)
		@throw [OFOutOfMemoryException
		    exceptionWithRequestedSize: mapSize];

	page->page = mapPages(1, &page->swappable);
	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;
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
	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;








|







191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
	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, page->swappable);
	free(page->map);

	if (page->previous != NULL)
		page->previous->next = page->next;
	if (page->next != NULL)
		page->next->previous = page->previous;

259
260
261
262
263
264
265


266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
	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) ||
	    !of_tlskey_new(&preallocatedPagesKey) ||
	    !of_tlskey_new(&numPreallocatedPagesKey))
		@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
}

+ (void)preallocateMemoryWithSize: (size_t)size
{
	size_t pageSize = [OFSystemInfo pageSize];
	size_t numPages = OF_ROUND_UP_POW2(pageSize, size) / pageSize;
#if !defined(OF_HAVE_COMPILER_TLS) && defined(OF_HAVE_THREADS)
	struct page **preallocatedPages = of_tlskey_get(preallocatedPagesKey);
	size_t numPreallocatedPages;







>
>














<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
































284
285
286
287
288
289
290
	of_explicit_memset(pointer, 0, bytes);

	for (size_t i = 0; i < chunks; i++)
		of_bitset_clear(page->map, chunkIndex + i);
}

@implementation OFSecureData
@synthesize swappable = _swappable;

#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) ||
	    !of_tlskey_new(&preallocatedPagesKey) ||
	    !of_tlskey_new(&numPreallocatedPagesKey))
		@throw [OFInitializationFailedException
		    exceptionWithClass: self];
}
#endif

































+ (void)preallocateMemoryWithSize: (size_t)size
{
	size_t pageSize = [OFSystemInfo pageSize];
	size_t numPages = OF_ROUND_UP_POW2(pageSize, size) / pageSize;
#if !defined(OF_HAVE_COMPILER_TLS) && defined(OF_HAVE_THREADS)
	struct page **preallocatedPages = of_tlskey_get(preallocatedPagesKey);
	size_t numPreallocatedPages;
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
		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) {







|







366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
		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, &_swappable);
		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) {
420
421
422
423
424
425
426


427
428
429
430
431
432
433
				    count * itemSize);

				if (_items == NULL)
					@throw [OFOutOfMemoryException
					    exceptionWithRequestedSize:
					    count * itemSize];
			}


		}

		_itemSize = itemSize;
		_count = count;
	} @catch (id e) {
		[self release];
		@throw e;







>
>







392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
				    count * itemSize);

				if (_items == NULL)
					@throw [OFOutOfMemoryException
					    exceptionWithRequestedSize:
					    count * itemSize];
			}

			_swappable = _page->swappable;
		}

		_itemSize = itemSize;
		_count = count;
	} @catch (id e) {
		[self release];
		@throw e;
494
495
496
497
498
499
500
501

502
503
504
505
506
507
508

- (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);
	}








|
>







468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483

- (void)dealloc
{
	size_t pageSize = [OFSystemInfo pageSize];

	if (_count * _itemSize > pageSize)
		unmapPages(_items,
		    OF_ROUND_UP_POW2(pageSize, _count * _itemSize) / pageSize,
		    _swappable);
	else if (_page != NULL) {
		if (_items != NULL)
			freeMemory(_page, _items, _count * _itemSize);

		removePageIfEmpty(_page);
	}