Loading...
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 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
 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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
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
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
/*
 * Copyright (c) 2019 Apple Inc. All rights reserved.
 *
 * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
 *
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. The rights granted to you under the License
 * may not be used to create, or enable the creation or redistribution of,
 * unlawful or unlicensed copies of an Apple operating system, or to
 * circumvent, violate, or enable the circumvention or violation of, any
 * terms of an Apple operating system software license agreement.
 *
 * Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this file.
 *
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 *
 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
 */
/*!
 * ARM64-specific functions required to support hibernation exit.
 */

#include <mach/mach_types.h>
#include <kern/misc_protos.h>
#include <IOKit/IOHibernatePrivate.h>
#include <machine/pal_hibernate.h>
#include <pexpert/arm/dockchannel.h>
#include <ptrauth.h>
#include <arm/cpu_data_internal.h>
#include <arm/cpu_internal.h>
#include <libkern/section_keywords.h>


pal_hib_tramp_result_t gHibTramp;
pal_hib_globals_t gHibernateGlobals MARK_AS_HIBERNATE_DATA_CONST_LATE;

// as a workaround for <rdar://problem/70121432> References between different compile units in xnu shouldn't go through GOT
// all of the extern symbols that we refer to in this file have to be declared with hidden visibility
extern IOHibernateImageHeader *gIOHibernateCurrentHeader __attribute__((visibility("hidden")));
extern const uint32_t ccsha256_initial_state[8] __attribute__((visibility("hidden")));
extern void AccelerateCrypto_SHA256_compress(ccdigest_state_t state, size_t numBlocks, const void *data) __attribute__((visibility("hidden")));
extern void ccdigest_final_64be(const struct ccdigest_info *di, ccdigest_ctx_t, unsigned char *digest) __attribute__((visibility("hidden")));
extern struct pmap_cpu_data_array_entry pmap_cpu_data_array[MAX_CPUS] __attribute__((visibility("hidden")));
extern bool hib_entry_pmap_lockdown __attribute__((visibility("hidden")));

uintptr_t
hibernate_restore_phys_page(uint64_t src, uint64_t dst, uint32_t len, __unused uint32_t procFlags)
{
	void *d = (void*)pal_hib_map(DEST_COPY_AREA, dst);
	__nosan_memcpy(d, (void*)src, len);
	return (uintptr_t)d;
}

uintptr_t
pal_hib_map(pal_hib_map_type_t virt, uint64_t phys)
{
	switch (virt) {
	case DEST_COPY_AREA:
	case COPY_PAGE_AREA:
	case SCRATCH_AREA:
	case WKDM_AREA:
		return phys + gHibTramp.memSlide;
	case BITMAP_AREA:
	case IMAGE_AREA:
	case IMAGE2_AREA:
		return phys;
	default:
		HIB_ASSERT(0);
	}
}

void
pal_hib_restore_pal_state(__unused uint32_t *arg)
{
}

void
pal_hib_resume_init(pal_hib_ctx_t *ctx, hibernate_page_list_t *map, uint32_t *nextFree)
{
}

void
pal_hib_restored_page(pal_hib_ctx_t *ctx, pal_hib_restore_stage_t stage, ppnum_t ppnum)
{
}

void
pal_hib_patchup(pal_hib_ctx_t *ctx)
{

	/* Reinit the ppl hib lock as it was saved to the hibernation image held. */
	ppl_hib_lock_reinit();

	// DRAM pages are captured from a PPL context, so here we restore all cpu_data structures to a non-PPL context
	for (int i = 0; i < MAX_CPUS; i++) {
		pmap_cpu_data_array[i].cpu_data.ppl_state = PPL_STATE_KERNEL;
		pmap_cpu_data_array[i].cpu_data.ppl_kern_saved_sp = 0;
	}

	// cluster CTRR state needs to be reconfigured
	init_ctrr_cluster_states();

	// Calls into the pmap that could potentially modify pmap data structures
	// during image copying were explicitly blocked on hibernation entry.
	// Resetting this variable to false allows those calls to be made again.
	hib_entry_pmap_lockdown = false;
}

void
pal_hib_decompress_page(void *src, void *dst, void *scratch, unsigned int compressedSize)
{
	const void *wkdmSrc;
	if (((uint64_t)src) & 63) {
		// the wkdm instruction requires that our source buffer be aligned, so copy into an aligned buffer if necessary
		__nosan_memcpy(scratch, src, compressedSize);
		wkdmSrc = scratch;
	} else {
		wkdmSrc = src;
	}
	HIB_ASSERT((((uint64_t)wkdmSrc) & 63) == 0);
	HIB_ASSERT((((uint64_t)dst) & PAGE_MASK) == 0);
	struct {
		uint32_t reserved:12;
		uint32_t status:3;
		uint32_t reserved2:17;
		uint32_t popcnt:18;
		uint32_t reserved3:14;
	} result = { .status = ~0u };
	__asm__ volatile ("wkdmd %0, %1" : "=r"(result): "r"(dst), "0"(wkdmSrc));
	HIB_ASSERT(result.status == 0);
}

// proc_reg's ARM_TTE_TABLE_NS has both NSTABLE and NS set
#define ARM_LPAE_NSTABLE             0x8000000000000000ULL

#define TOP_LEVEL                    1
#define LAST_TABLE_LEVEL             3
#define PAGE_GRANULE_SHIFT           14
#define PAGE_GRANULE_SIZE            ((size_t)1<<PAGE_GRANULE_SHIFT)
#define PAGE_GRANULE_MASK            (PAGE_GRANULE_SIZE-1)
#define LEVEL_SHIFT(level)           (47 - (level * 11))

#define PTE_EMPTY(ent)               ((ent) == 0)

typedef struct {
	hibernate_page_list_t *bitmap;
	uint32_t nextFree;
	uint64_t page_table_base;
} map_ctx;

static void
hib_bzero(volatile void *s, size_t n)
{
	uintptr_t p = (uintptr_t)s;

	// can't use __nosan_bzero while the MMU is off, so do it manually
	while (n > sizeof(uint64_t)) {
		*(volatile uint64_t *)p = 0;
		p += sizeof(uint64_t);
		n -= sizeof(uint64_t);
	}
	while (n > sizeof(uint32_t)) {
		*(volatile uint32_t *)p = 0;
		p += sizeof(uint32_t);
		n -= sizeof(uint32_t);
	}
	while (n) {
		*(volatile char *)p = 0;
		p++;
		n--;
	}
}

static uint64_t
allocate_page(map_ctx *ctx)
{
	// pages that were unnecessary for preservation when we entered hibernation are
	// marked as free in ctx->bitmap, so they are available for scratch usage during
	// resume; here, we "borrow" one of these free pages to use as part of our temporary
	// page tables
	ppnum_t ppnum = hibernate_page_list_grab(ctx->bitmap, &ctx->nextFree);
	hibernate_page_bitset(ctx->bitmap, FALSE, ppnum);
	uint64_t result = ptoa_64(ppnum);
	hib_bzero((void *)result, PAGE_SIZE);
	return result;
}

static void
create_map_entries(map_ctx *ctx, uint64_t vaddr, uint64_t paddr, uint64_t size, uint64_t map_flags)
{
	// if we've set gHibTramp.memSlide, we should already be running with the MMU on;
	// in this case, we don't permit further modification to the page table
	HIB_ASSERT(!gHibTramp.memSlide);

	int level = TOP_LEVEL;
	volatile uint64_t *table_base = (uint64_t *)ctx->page_table_base;
	if (map_flags == 0) {
		paddr = 0; // no physical address for none mappings
	}

	while (size) {
		HIB_ASSERT(level >= 1);
		HIB_ASSERT(level <= LAST_TABLE_LEVEL);

		size_t level_shift = LEVEL_SHIFT(level);
		size_t level_entries = PAGE_GRANULE_SIZE / sizeof(uint64_t);
		size_t level_size = 1ull << level_shift;
		size_t level_mask = level_size - 1;
		size_t index = (vaddr >> level_shift) & (level_entries - 1);
		// Can we make block entries here? Must be permitted at this
		// level, have enough bytes remaining, and both virtual and
		// physical addresses aligned to a block.
		if ((level >= 2) &&
		    size >= level_size &&
		    ((vaddr | paddr) & level_mask) == 0) {
			// Map contiguous blocks.
			size_t num_entries = MIN(size / level_size, level_entries - index);
			if (map_flags) {
				uint64_t entry = map_flags | ((level < LAST_TABLE_LEVEL) ? ARM_TTE_TYPE_BLOCK : ARM_TTE_TYPE_L3BLOCK);
				for (size_t i = 0; i < num_entries; i++) {
					HIB_ASSERT(PTE_EMPTY(table_base[index + i]));
					table_base[index + i] = entry | paddr;
					paddr += level_size;
				}
			} else {
				// make sure all the corresponding entries are empty
				for (size_t i = 0; i < num_entries; i++) {
					HIB_ASSERT(PTE_EMPTY(table_base[index + i]));
				}
			}
			size_t mapped = num_entries * level_size;
			size -= mapped;
			if (size) {
				// map the remaining at the top level
				level = TOP_LEVEL;
				table_base = (uint64_t *)ctx->page_table_base;
				vaddr += mapped;
				// paddr already incremented above if necessary
			}
		} else {
			// Sub-divide into a next level table.
			HIB_ASSERT(level < LAST_TABLE_LEVEL);
			uint64_t entry = table_base[index];
			HIB_ASSERT((entry & (ARM_TTE_VALID | ARM_TTE_TYPE_MASK)) != (ARM_TTE_VALID | ARM_TTE_TYPE_BLOCK)); // Breaking down blocks not implemented
			uint64_t sub_base = entry & ARM_TTE_TABLE_MASK;
			if (!sub_base) {
				sub_base = allocate_page(ctx);
				HIB_ASSERT((sub_base & PAGE_GRANULE_MASK) == 0);
				table_base[index] = sub_base | ARM_LPAE_NSTABLE | ARM_TTE_TYPE_TABLE | ARM_TTE_VALID;
			}
			// map into the sub table
			level++;
			table_base = (uint64_t *)sub_base;
		}
	}
}

static void
map_range_start_end(map_ctx *ctx, uint64_t start, uint64_t end, uint64_t slide, uint64_t flags)
{
	HIB_ASSERT(end >= start);
	create_map_entries(ctx, start + slide, start, end - start, flags);
}

#define MAP_FLAGS_COMMON (ARM_PTE_AF | ARM_PTE_NS | ARM_TTE_VALID | ARM_PTE_SH(SH_OUTER_MEMORY) | ARM_PTE_ATTRINDX(CACHE_ATTRINDX_WRITEBACK))
#define MAP_DEVICE       (ARM_PTE_AF | ARM_TTE_VALID | ARM_PTE_PNX | ARM_PTE_NX | ARM_PTE_SH(SH_NONE) | ARM_PTE_ATTRINDX(CACHE_ATTRINDX_DISABLE))
#define MAP_RO           (MAP_FLAGS_COMMON | ARM_PTE_PNX | ARM_PTE_NX | ARM_PTE_AP(AP_RONA))
#define MAP_RW           (MAP_FLAGS_COMMON | ARM_PTE_PNX | ARM_PTE_NX)
#define MAP_RX           (MAP_FLAGS_COMMON | ARM_PTE_AP(AP_RONA))

static void
map_register_page(map_ctx *ctx, vm_address_t regPage)
{
	uint64_t regBase = trunc_page(regPage);
	if (regBase) {
		map_range_start_end(ctx, regBase, regBase + PAGE_SIZE, 0, MAP_DEVICE);
	}
}

static void
iterate_bitmaps(const map_ctx *ctx, bool (^callback)(const hibernate_bitmap_t *bank_bitmap))
{
	hibernate_bitmap_t *bank_bitmap = &ctx->bitmap->bank_bitmap[0];
	for (uint32_t bank = 0; bank < ctx->bitmap->bank_count; bank++) {
		if (!callback(bank_bitmap)) {
			return;
		}
		bank_bitmap = (hibernate_bitmap_t*)&bank_bitmap->bitmap[bank_bitmap->bitmapwords];
	}
}

// during hibernation resume, we can't use the original kernel page table (because we don't know what it was), so we instead
// create a temporary page table to use during hibernation resume; since the original kernel page table was part of DRAM,
// it will be restored by the time we're done with hibernation resume, at which point we can jump through the reset vector
// to reload the original page table
void
pal_hib_resume_tramp(uint32_t headerPpnum)
{
	uint64_t header_phys = ptoa_64(headerPpnum);
	IOHibernateImageHeader *header = (IOHibernateImageHeader *)header_phys;
	IOHibernateHibSegInfo *seg_info = &header->hibSegInfo;
	uint64_t hib_text_start = ptoa_64(header->restore1CodePhysPage);

	__block map_ctx ctx = {};
	uint64_t map_phys = header_phys
	    + (offsetof(IOHibernateImageHeader, fileExtentMap)
	    + header->fileExtentMapSize
	    + ptoa_32(header->restore1PageCount)
	    + header->previewSize);
	ctx.bitmap = (hibernate_page_list_t *)map_phys;

	// find the bank describing xnu's map
	__block uint64_t phys_start = 0, phys_end = 0;
	iterate_bitmaps(&ctx, ^bool (const hibernate_bitmap_t *bank_bitmap) {
		if ((bank_bitmap->first_page <= header->restore1CodePhysPage) &&
		(bank_bitmap->last_page >= header->restore1CodePhysPage)) {
		        phys_start = ptoa_64(bank_bitmap->first_page);
		        phys_end = ptoa_64(bank_bitmap->last_page) + PAGE_SIZE;
		        return false;
		}
		return true;
	});

	HIB_ASSERT(phys_start != 0);
	HIB_ASSERT(phys_end != 0);

	hib_bzero(&gHibTramp, sizeof(gHibTramp));

	// During hibernation resume, we create temporary mappings that do not collide with where any of the kernel mappings were originally.
	// Technically, non-collision isn't a requirement, but doing this means that if some code accidentally jumps to a VA in the original
	// kernel map, it won't be present in our temporary map and we'll get an exception when jumping to an unmapped address.
	// The base address of our temporary mappings is adjusted by a random amount as a "poor-man's ASLR". We don’t have a good source of random
	// numbers in this context, so we just use some of the bits from one of imageHeaderHMMAC, which should be random enough.
	uint16_t rand = (uint16_t)(((header->imageHeaderHMAC[0]) << 8) | header->imageHeaderHMAC[1]);
	uint64_t mem_slide = gHibernateGlobals.kernelSlide - (phys_end - phys_start) * 4 - rand * 256 * PAGE_SIZE;

	// make sure we don't clobber any of the pages we need for restore
	hibernate_reserve_restore_pages(header_phys, header, ctx.bitmap);

	// init nextFree
	hibernate_page_list_grab(ctx.bitmap, &ctx.nextFree);

	// map ttbr1 pages
	ctx.page_table_base = allocate_page(&ctx);
	gHibTramp.ttbr1 = ctx.page_table_base;

	uint64_t first_seg_start = 0, last_seg_end = 0, hib_text_end = 0;
	for (size_t i = 0; i < NUM_HIBSEGINFO_SEGMENTS; i++) {
		uint64_t size = ptoa_64(seg_info->segments[i].pageCount);
		if (size) {
			uint64_t seg_start = ptoa_64(seg_info->segments[i].physPage);
			uint64_t seg_end = seg_start + size;
			uint32_t protection = seg_info->segments[i].protection;
			if (protection != VM_PROT_NONE) {
				// make sure the segment is in bounds
				HIB_ASSERT(seg_start >= phys_start);
				HIB_ASSERT(seg_end <= phys_end);

				if (!first_seg_start) {
					first_seg_start = seg_start;
				}
				if (last_seg_end) {
					// map the "hole" as RW
					map_range_start_end(&ctx, last_seg_end, seg_start, mem_slide, MAP_RW);
				}
				// map the segments described in machine_header at their original locations
				bool executable = (protection & VM_PROT_EXECUTE);
				bool writeable = (protection & VM_PROT_WRITE);
				uint64_t map_flags = executable ? MAP_RX : writeable ? MAP_RW : MAP_RO;
				map_range_start_end(&ctx, seg_start, seg_end, gHibernateGlobals.kernelSlide, map_flags);
				last_seg_end = seg_end;
			}
			if (seg_info->segments[i].physPage == header->restore1CodePhysPage) {
				// this is the hibtext segment, so remember where it ends
				hib_text_end = seg_end;
			}
		}
	}
	// map the rest of kernel memory (the pages that come before and after our segments) as RW
	map_range_start_end(&ctx, phys_start, first_seg_start, mem_slide, MAP_RW);
	map_range_start_end(&ctx, last_seg_end, phys_end, mem_slide, MAP_RW);

	// map all of the remaining banks that we didn't already deal with
	iterate_bitmaps(&ctx, ^bool (const hibernate_bitmap_t *bank_bitmap) {
		uint64_t bank_start = ptoa_64(bank_bitmap->first_page);
		uint64_t bank_end = ptoa_64(bank_bitmap->last_page) + PAGE_SIZE;
		if (bank_start == phys_start) {
		        // skip this bank since we already covered it above
		} else {
		        // map the bank RW
		        map_range_start_end(&ctx, bank_start, bank_end, mem_slide, MAP_RW);
		}
		return true;
	});

	// map ttbr0 pages
	ctx.page_table_base = allocate_page(&ctx);
	gHibTramp.ttbr0 = ctx.page_table_base;

	// map hib text P=V so that we can still execute at its physical address
	map_range_start_end(&ctx, hib_text_start, hib_text_end, 0, MAP_RX);

	// map the hib image P=V, RW
	uint64_t image_start = trunc_page(header_phys);
	uint64_t image_end = round_page(header_phys + header->image1Size);
	map_range_start_end(&ctx, image_start, image_end, 0, MAP_RW);

	// map the handoff pages P=V, RO
	image_start = ptoa_64(header->handoffPages);
	image_end = image_start + ptoa_64(header->handoffPageCount);
	map_range_start_end(&ctx, image_start, image_end, 0, MAP_RO);

	// map some device register pages
	if (gHibernateGlobals.dockChannelRegPhysBase) {
#define dockchannel_uart_base gHibernateGlobals.dockChannelRegPhysBase
		vm_address_t dockChannelRegPhysBase = trunc_page(&rDOCKCHANNELS_DEV_WSTAT(DOCKCHANNEL_UART_CHANNEL));
		map_register_page(&ctx, dockChannelRegPhysBase);
	}
	map_register_page(&ctx, gHibernateGlobals.hibUartRegPhysBase);
	map_register_page(&ctx, gHibernateGlobals.hmacRegBase);

	gHibTramp.memSlide = mem_slide;
}