Move source files to src/, add Makefile, fix all build and linkage errors, etc.

This commit is contained in:
TuxSH
2018-02-25 20:00:50 +01:00
parent 4c7aa566f0
commit b0ea9c1a0b
58 changed files with 385 additions and 203 deletions

View File

@@ -0,0 +1,40 @@
#include "bootconfig.h"
void bootconfig_load_and_verify(const bootconfig_t *bootconfig) {
/* TODO */
}
void bootconfig_clear(void){
/* TODO */
}
/* Actual configuration getters. */
bool bootconfig_is_package2_plaintext(void) {
return false;
/* TODO */
}
bool bootconfig_is_package2_unsigned(void) {
return false;
/* TODO */
}
bool bootconfig_disable_program_verification(void) {
return false;
/* TODO */
}
bool bootconfig_is_debug_mode(void) {
return false;
/* TODO */
}
uint64_t bootconfig_get_memory_arrangement(void) {
return 0ULL;
/* TODO */
}
uint64_t bootconfig_get_kernel_memory_configuration(void) {
return 0ULL;
/* TODO */
}

View File

@@ -0,0 +1,28 @@
#ifndef EXOSPHERE_BOOTCONFIG_H
#define EXOSPHERE_BOOTCONFIG_H
#include <stdbool.h>
#include <stdint.h>
/* This provides management for Switch BootConfig. */
typedef struct {
uint8_t unsigned_config[0x200];
uint8_t signature[0x100];
uint8_t signed_config[0x100];
uint8_t unknown_config[0x240];
} bootconfig_t;
void bootconfig_load_and_verify(const bootconfig_t *bootconfig);
void bootconfig_clear(void);
/* Actual configuration getters. */
bool bootconfig_is_package2_plaintext(void);
bool bootconfig_is_package2_unsigned(void);
bool bootconfig_disable_program_verification(void);
bool bootconfig_is_debug_mode(void);
uint64_t bootconfig_get_memory_arrangement(void);
uint64_t bootconfig_get_kernel_memory_configuration(void);
#endif

20
exosphere/src/cache.h Normal file
View File

@@ -0,0 +1,20 @@
#ifndef EXOSPHERE_CACHE_H
#define EXOSPHERE_CACHE_H
#include <stdint.h>
void tlb_invalidate_all(void);
void tlb_invalidate_all_inner_shareable(void);
void tlb_invalidate_page(const volatile void *page);
void tlb_invalidate_page_inner_shareable(const void *page);
void flush_dcache_all(void);
void invalidate_dcache_all(void);
void flush_dcache_range(const void *start, const void *end);
void invalidate_dcache_range(const void *start, const void *end);
void invalidate_icache_inner_shareable(void);
#endif

229
exosphere/src/cache.s Normal file
View File

@@ -0,0 +1,229 @@
.section .text.tlb_invalidate_all, "ax", %progbits
.type tlb_invalidate_all, %function
.global tlb_invalidate_all
tlb_invalidate_all:
dsb sy
tlbi alle3
dsb sy
isb
ret
.section .text.tlb_invalidate_all_inner_shareable, "ax", %progbits
.type tlb_invalidate_all_inner_shareable, %function
.global tlb_invalidate_all_inner_shareable
tlb_invalidate_all_inner_shareable:
dsb ish
tlbi alle3is
dsb ish
isb
ret
.section .text.tlb_invalidate_page, "ax", %progbits
.type tlb_invalidate_page, %function
.global tlb_invalidate_page
tlb_invalidate_page:
lsr x8, x0, #12
dsb sy
tlbi vale3, x8
dsb sy
isb
ret
.section .text.tlb_invalidate_page_inner_shareable, "ax", %progbits
.type tlb_invalidate_page_inner_shareable, %function
.global tlb_invalidate_page_inner_shareable
tlb_invalidate_page_inner_shareable:
lsr x8, x0, #12
dsb ish
tlbi vale3is, x8
dsb ish
isb
ret
/* The following functions are taken/adapted from https://github.com/u-boot/u-boot/blob/master/arch/arm/cpu/armv8/cache.S */
/*
* (C) Copyright 2013
* David Feng <fenghua@phytium.com.cn>
*
* This file is based on sample code from ARMv8 ARM.
*
* SPDX-License-Identifier: GPL-2.0+
*/
/*
* void __asm_dcache_level(level)
*
* flush or invalidate one level cache.
*
* x0: cache level
* x1: 0 clean & invalidate, 1 invalidate only
* x2~x9: clobbered
*/
.section .text.__asm_dcache_level, "ax", %progbits
.type __asm_dcache_level, %function
__asm_dcache_level:
lsl x12, x0, #1
msr csselr_el1, x12 /* select cache level */
isb /* sync change of cssidr_el1 */
mrs x6, ccsidr_el1 /* read the new cssidr_el1 */
and x2, x6, #7 /* x2 <- log2(cache line size)-4 */
add x2, x2, #4 /* x2 <- log2(cache line size) */
mov x3, #0x3ff
and x3, x3, x6, lsr #3 /* x3 <- max number of #ways */
clz w5, w3 /* bit position of #ways */
mov x4, #0x7fff
and x4, x4, x6, lsr #13 /* x4 <- max number of #sets */
/* x12 <- cache level << 1 */
/* x2 <- line length offset */
/* x3 <- number of cache ways - 1 */
/* x4 <- number of cache sets - 1 */
/* x5 <- bit position of #ways */
loop_set:
mov x6, x3 /* x6 <- working copy of #ways */
loop_way:
lsl x7, x6, x5
orr x9, x12, x7 /* map way and level to cisw value */
lsl x7, x4, x2
orr x9, x9, x7 /* map set number to cisw value */
tbz w1, #0, 1f
dc isw, x9
b 2f
1: dc cisw, x9 /* clean & invalidate by set/way */
2: subs x6, x6, #1 /* decrement the way */
b.ge loop_way
subs x4, x4, #1 /* decrement the set */
b.ge loop_set
ret
/*
* void __asm_flush_dcache_all(int invalidate_only)
*
* x0: 0 clean & invalidate, 1 invalidate only
*
* flush or invalidate all data cache by SET/WAY.
*/
.section .text.__asm_dcache_all, "ax", %progbits
.type __asm_dcache_all, %function
__asm_dcache_all:
mov x1, x0
dsb sy
mrs x10, clidr_el1 /* read clidr_el1 */
lsr x11, x10, #24
and x11, x11, #0x7 /* x11 <- loc */
cbz x11, finished /* if loc is 0, exit */
mov x15, lr
mov x0, #0 /* start flush at cache level 0 */
/* x0 <- cache level */
/* x10 <- clidr_el1 */
/* x11 <- loc */
/* x15 <- return address */
loop_level:
lsl x12, x0, #1
add x12, x12, x0 /* x0 <- tripled cache level */
lsr x12, x10, x12
and x12, x12, #7 /* x12 <- cache type */
cmp x12, #2
b.lt skip /* skip if no cache or icache */
bl __asm_dcache_level /* x1 = 0 flush, 1 invalidate */
skip:
add x0, x0, #1 /* increment cache level */
cmp x11, x0
b.gt loop_level
mov x0, #0
msr csselr_el1, x0 /* restore csselr_el1 */
dsb sy
isb
mov lr, x15
finished:
ret
.section .text.flush_dcache_all, "ax", %progbits
.type flush_dcache_all, %function
.global flush_dcache_all
flush_dcache_all:
mov x0, #0
b __asm_dcache_all
.section .text.invalidate_dcache_all, "ax", %progbits
.type invalidate_dcache_all, %function
.global invalidate_dcache_all
invalidate_dcache_all:
mov x0, #1
b __asm_dcache_all
/*
* void __asm_flush_dcache_range(start, end) (renamed -> flush_dcache_range)
*
* clean & invalidate data cache in the range
*
* x0: start address
* x1: end address
*/
.section .text.flush_dcache_range, "ax", %progbits
.type flush_dcache_range, %function
.global flush_dcache_range
flush_dcache_range:
mrs x3, ctr_el0
lsr x3, x3, #16
and x3, x3, #0xf
mov x2, #4
lsl x2, x2, x3 /* cache line size */
/* x2 <- minimal cache line size in cache system */
sub x3, x2, #1
bic x0, x0, x3
1: dc civac, x0 /* clean & invalidate data or unified cache */
add x0, x0, x2
cmp x0, x1
b.lo 1b
dsb sy
ret
/*
* void __asm_invalidate_dcache_range(start, end) (-> invalidate_dcache_range)
*
* invalidate data cache in the range
*
* x0: start address
* x1: end address
*/
.section .text.invalidate_dcache_range, "ax", %progbits
.type invalidate_dcache_range, %function
.global invalidate_dcache_range
invalidate_dcache_range:
mrs x3, ctr_el0
ubfm x3, x3, #16, #19
mov x2, #4
lsl x2, x2, x3 /* cache line size */
/* x2 <- minimal cache line size in cache system */
sub x3, x2, #1
bic x0, x0, x3
1: dc ivac, x0 /* invalidate data or unified cache */
add x0, x0, x2
cmp x0, x1
b.lo 1b
dsb sy
ret
/*
* void __asm_invalidate_icache_all(void) (-> invalidate_icache_inner_shareable)
*
* invalidate all icache entries.
*/
.section .text.invalidate_icache_inner_shareable, "ax", %progbits
.type invalidate_icache_inner_shareable, %function
.global invalidate_icache_inner_shareable
invalidate_icache_inner_shareable:
dsb ish
isb
ic ialluis
dsb ish
isb
ret

View File

@@ -0,0 +1,89 @@
#include "utils.h"
#include "mmu.h"
#include "memory_map.h"
/*
extern void (*__preinit_array_start[])(void);
extern void (*__preinit_array_end[])(void);
extern void (*__init_array_start[])(void);
extern void (*__init_array_end[])(void);
extern void _init(void);
extern uint8_t __warmboot_crt0_start__[], __warmboot_crt0_end__[], __warmboot_crt0_lma__[];
extern uint8_t __main_start__[], __main_end__[], __main_lma__[];
extern uint8_t __pk2ldr_start__[], __pk2ldr_end__[], __pk2ldr_lma__[];
extern uint8_t __vectors_start__[], __vectors_end__[], __vectors_lma__[];*/
extern void flush_dcache_all_tzram_pa(void);
extern void invalidate_icache_all_tzram_pa(void);
uintptr_t get_coldboot_crt0_stack_address(void);
static void configure_ttbls(void) {
uintptr_t *mmu_l1_tbl = (uintptr_t *)(tzram_get_segment_pa(TZRAM_SEGEMENT_ID_SECMON_EVT) + 0x800 - 64);
uintptr_t *mmu_l2_tbl = (uintptr_t *)tzram_get_segment_pa(TZRAM_SEGMENT_ID_L2_TRANSLATION_TABLE);
uintptr_t *mmu_l3_tbl = (uintptr_t *)tzram_get_segment_pa(TZRAM_SEGMENT_ID_L3_TRANSLATION_TABLE);
mmu_init_table(mmu_l1_tbl, 64); /* 33-bit address space */
mmu_init_table(mmu_l2_tbl, 4096);
/*
Nintendo uses the same L3 table for everything, but they make sure
nothing clashes.
*/
mmu_init_table(mmu_l3_tbl, 4096);
mmu_map_table(1, mmu_l1_tbl, 0x40000000, mmu_l2_tbl, 0);
mmu_map_table(1, mmu_l1_tbl, 0x1C0000000, mmu_l2_tbl, 0);
mmu_map_table(2, mmu_l2_tbl, 0x40000000, mmu_l3_tbl, 0);
mmu_map_table(2, mmu_l2_tbl, 0x7C000000, mmu_l3_tbl, 0);
mmu_map_table(2, mmu_l2_tbl, 0x1F0000000ull, mmu_l3_tbl, 0);
identity_map_all_mappings(mmu_l1_tbl, mmu_l3_tbl);
mmio_map_all_devices(mmu_l3_tbl);
lp0_map_all_plaintext_ram_segments(mmu_l3_tbl);
lp0_map_all_ciphertext_ram_segments(mmu_l3_tbl);
tzram_map_all_segments(mmu_l3_tbl);
}
#if 0
static void copy_lma_to_vma(unsigned int segment_id, void *lma, size_t size, bool vma_is_pa) {
uintptr_t vma = vma_is_pa ? tzram_get_segment_pa(segment_id) : tzram_get_segment_address(segment_id);
uintptr_t vma_offset = (uintptr_t)lma & 0xFFF;
uint64_t *p_vma = (uint64_t *)vma;
uint64_t *p_lma = (uint64_t *)lma;
for (size_t i = 0; i < size / 8; i++) {
p_vma[vma_offset / 8 + i] = p_lma[i];
}
}
static void __libc_init_array(void) {
for (size_t i = 0; i < __preinit_array_end - __preinit_array_start; i++)
__preinit_array_start[i]();
_init(); /* FIXME: do we have this gcc-provided symbol if we build with -nostartfiles? */
for (size_t i = 0; i < __init_array_end - __init_array_start; i++)
__init_array_start[i]();
}
#endif
uintptr_t get_coldboot_crt0_stack_address(void) {
return tzram_get_segment_pa(TZRAM_SEGMENT_ID_CORE3_STACK) + 0x800;
}
void coldboot_init(void) {
/* TODO: Set NX BOOTLOADER clock time field */
/*copy_lma_to_vma(TZRAM_SEGMENT_ID_WARMBOOT_CRT0_AND_MAIN, __warmboot_crt0_lma__, __warmboot_crt0_end__ - __warmboot_crt0_start__, true);*/
/* TODO: set some mmio regs, etc. */
/* TODO: initialize DMA controllers */
configure_ttbls();
/*copy_lma_to_vma(TZRAM_SEGMENT_ID_WARMBOOT_CRT0_AND_MAIN, __main_lma__, __main_end__ - __main_start__, false);
copy_lma_to_vma(TZRAM_SEGMENT_ID_PK2LDR, __pk2ldr_lma__, __pk2ldr_end__ - __pk2ldr_start__, false);
copy_lma_to_vma(TZRAM_SEGEMENT_ID_SECMON_EVT, __vectors_lma__, __vectors_end__ - __vectors_start__, false);*/
/* TODO: set the MMU regs & tlbi & enable MMU */
flush_dcache_all_tzram_pa();
invalidate_icache_all_tzram_pa();
/* TODO: zero-initialize the cpu context */
/* Nintendo clears the (emtpy) pk2ldr's BSS section, but we embed it 0-filled in the binary */
/*__libc_init_array(); construct global objects */
}

View File

@@ -0,0 +1,39 @@
#include <string.h>
#include "utils.h"
#include "mmu.h"
#include "memory_map.h"
#include "cache.h"
/*
extern void (*__fini_array_start[])(void);
extern void (*__fini_array_end[])(void);
extern void _fini(void);*/
extern uint8_t __pk2ldr_start__[], __pk2ldr_end__[];
extern void __jump_to_lower_el(uint64_t arg, uintptr_t ep, unsigned int el);
void coldboot_main(void);
#if 0
/* Needs to be called for EL3->EL3 chainloading (and only in that case). TODO: use it */
__attribute__((used)) static void __libc_fini_array(void) {
for (size_t i = __fini_array_end - __fini_array_start; i > 0; i--)
__fini_array_start[i - 1]();
_fini(); /* FIXME: do we have this gcc-provided symbol if we build with -nostartfiles? */
}
#endif
void coldboot_main(void) {
#if 0
uintptr_t *mmu_l3_table = (uintptr_t *)tzram_get_segment_address(TZRAM_SEGMENT_ID_L3_TRANSLATION_TABLE);
uintptr_t pk2ldr = tzram_get_segment_address(TZRAM_SEGMENT_ID_PK2LDR);
/* Clear and unmap pk2ldr (which is reused as exception entry stacks) */
memset((void *)pk2ldr, 0, __pk2ldr_end__ - __pk2ldr_start__);
mmu_unmap_range(3, mmu_l3_table, pk2ldr, __pk2ldr_end__ - __pk2ldr_start__);
tlb_invalidate_all_inner_shareable();
#endif
/* TODO: stuff & jump to lower EL */
}

View File

@@ -0,0 +1,90 @@
#include <stdint.h>
#include "bootconfig.h"
#include "configitem.h"
#include "interrupt.h"
#include "package2.h"
#include "se.h"
#include "fuse.h"
#include "utils.h"
int g_battery_profile = 0;
uint32_t configitem_set(enum ConfigItem item, uint64_t value) {
if (item != CONFIGITEM_BATTERYPROFILE) {
return 2;
}
g_battery_profile = ((int)(value != 0)) & 1;
return 0; /* FIXME: what should we return there */
}
bool configitem_is_recovery_boot(void) {
uint64_t is_recovery_boot;
if (configitem_get(CONFIGITEM_ISRECOVERYBOOT, &is_recovery_boot) != 0) {
generic_panic();
}
return is_recovery_boot != 0;
}
bool configitem_is_retail(void) {
uint64_t is_retail;
if (configitem_get(CONFIGITEM_ISRETAIL, &is_retail) != 0) {
generic_panic();
}
return is_retail != 0;
}
uint32_t configitem_get(enum ConfigItem item, uint64_t *p_outvalue) {
uint32_t result = 0;
switch (item) {
case CONFIGITEM_DISABLEPROGRAMVERIFICATION:
*p_outvalue = (int)(bootconfig_disable_program_verification());
break;
case CONFIGITEM_DRAMID:
*p_outvalue = fuse_get_dram_id();
break;
case CONFIGITEM_SECURITYENGINEIRQ:
/* SE is interrupt #0x2C. */
*p_outvalue = INTERRUPT_ID_USER_SECURITY_ENGINE;
break;
case CONFIGITEM_VERSION:
/* Always returns maxver - 1 on hardware. */
*p_outvalue = PACKAGE2_MAXVER_400_CURRENT - 1;
break;
case CONFIGITEM_HARDWARETYPE:
*p_outvalue = fuse_get_hardware_type();
break;
case CONFIGITEM_ISRETAIL:
*p_outvalue = fuse_get_retail_type();
break;
case CONFIGITEM_ISRECOVERYBOOT:
/* TODO: This requires reading values passed to crt0 via NX_Bootloader. TBD pending crt0 implementation. */
*p_outvalue = 0;
break;
case CONFIGITEM_DEVICEID:
*p_outvalue = fuse_get_device_id();
break;
case CONFIGITEM_BOOTREASON:
/* TODO: This requires reading values passed to crt0 via NX_Bootloader. TBD pending crt0 implementation. */
break;
case CONFIGITEM_MEMORYARRANGE:
*p_outvalue = bootconfig_get_memory_arrangement();
break;
case CONFIGITEM_ISDEBUGMODE:
*p_outvalue = (int)(bootconfig_is_debug_mode());
break;
case CONFIGITEM_KERNELMEMORYCONFIGURATION:
*p_outvalue = bootconfig_get_kernel_memory_configuration();
break;
case CONFIGITEM_BATTERYPROFILE:
*p_outvalue = g_battery_profile;
break;
default:
result = 2;
break;
}
return result;
}

View File

@@ -0,0 +1,29 @@
#ifndef EXOSPHERE_CFG_ITEM_H
#define EXOSPHERE_CFG_ITEM_H
#include <stdbool.h>
#include <stdint.h>
enum ConfigItem {
CONFIGITEM_DISABLEPROGRAMVERIFICATION = 1,
CONFIGITEM_DRAMID = 2,
CONFIGITEM_SECURITYENGINEIRQ = 3,
CONFIGITEM_VERSION = 4,
CONFIGITEM_HARDWARETYPE = 5,
CONFIGITEM_ISRETAIL = 6,
CONFIGITEM_ISRECOVERYBOOT = 7,
CONFIGITEM_DEVICEID = 8,
CONFIGITEM_BOOTREASON = 9,
CONFIGITEM_MEMORYARRANGE = 10,
CONFIGITEM_ISDEBUGMODE = 11,
CONFIGITEM_KERNELMEMORYCONFIGURATION = 12,
CONFIGITEM_BATTERYPROFILE = 13
};
uint32_t configitem_set(enum ConfigItem item, uint64_t value);
uint32_t configitem_get(enum ConfigItem item, uint64_t *p_outvalue);
bool configitem_is_recovery_boot(void);
bool configitem_is_retail(void);
#endif

View File

@@ -0,0 +1,70 @@
#include <stdint.h>
#include "cpu_context.h"
#include "pmc.h"
#include "timers.h"
#include "utils.h"
saved_cpu_context_t g_cpu_contexts[NUM_CPU_CORES] = {0};
void set_core_entrypoint_and_context_id(uint32_t core, uint64_t entrypoint_addr, uint64_t context_id) {
g_cpu_contexts[core].ELR_EL3 = entrypoint_addr;
g_cpu_contexts[core].context_id = context_id;
}
uint32_t cpu_on(uint32_t core, uint64_t entrypoint_addr, uint64_t context_id) {
/* Is core valid? */
if (core >= NUM_CPU_CORES) {
return 0xFFFFFFFE;
}
/* Is core already on? */
if (g_cpu_contexts[core].is_active) {
return 0xFFFFFFFC;
}
set_core_entrypoint_and_context_id(core, entrypoint_addr, context_id);
const uint32_t status_masks[NUM_CPU_CORES] = {0x4000, 0x200, 0x400, 0x800};
const uint32_t toggle_vals[NUM_CPU_CORES] = {0xE, 0x9, 0xA, 0xB};
/* Check if we're already in the correct state. */
if ((APBDEV_PMC_PWRGATE_STATUS_0 & status_masks[core]) != status_masks[core]) {
uint32_t counter = 5001;
/* Poll the start bit until 0 */
while (APBDEV_PMC_PWRGATE_TOGGLE_0 & 0x100) {
wait(1);
counter--;
if (counter < 1) {
return 0;
}
}
/* Program PWRGATE_TOGGLE with the START bit set to 1, selecting CE[N] */
APBDEV_PMC_PWRGATE_TOGGLE_0 = toggle_vals[core] | 0x100;
/* Poll until we're in the correct state. */
counter = 5001;
while (counter > 0) {
if ((APBDEV_PMC_PWRGATE_STATUS_0 & status_masks[core]) == status_masks[core]) {
break;
}
wait(1);
counter--;
}
}
return 0;
}
uint32_t cpu_off(void) {
return 0;
/* TODO */
}
uint32_t cpu_suspend(uint64_t power_state, uint64_t entrypoint_addr, uint64_t context_id) {
(void)power_state;
(void)entrypoint_addr;
(void)context_id;
return 0;
/* TODO */
}

View File

@@ -0,0 +1,54 @@
#ifndef EXOSPHERE_CPU_CTX_H
#define EXOSPHERE_CPU_CTX_H
#include <stdint.h>
/* Exosphere CPU Management functionality. */
typedef struct {
uint64_t context_id;
uint64_t ELR_EL3;
int is_active;
int is_saved;
uint32_t OSDTRRX_EL1;
uint32_t OSDTRTX_EL1;
uint32_t MDSCR_EL1;
uint32_t OSECCR_EL1;
uint32_t MDCCINT_EL1;
uint32_t DBGCLAIMCLR_EL1;
uint32_t DBGVCR32_EL2;
uint32_t SDER32_EL3;
uint32_t MDCR_EL2;
uint32_t MDCR_EL3;
uint64_t DBGBVR0_EL1;
uint64_t DBGBCR0_EL1;
uint64_t DBGBVR1_EL1;
uint64_t DBGBCR1_EL1;
uint64_t DBGBVR2_EL1;
uint64_t DBGBCR2_EL1;
uint64_t DBGBVR3_EL1;
uint64_t DBGBCR3_EL1;
uint64_t DBGBVR4_EL1;
uint64_t DBGBCR4_EL1;
uint64_t DBGBVR5_EL1;
uint64_t DBGBCR5_EL1;
uint64_t DBGWVR0_EL1;
uint64_t DBGWCR0_EL1;
uint64_t DBGWVR1_EL1;
uint64_t DBGWCR1_EL1;
uint64_t DBGWVR2_EL1;
uint64_t DBGWCR2_EL1;
uint64_t DBGWVR3_EL1;
uint64_t DBGWCR3_EL1;
} saved_cpu_context_t;
#define NUM_CPU_CORES 4
void set_core_entrypoint_and_context_id(uint32_t core, uint64_t entrypoint_addr, uint64_t context_id);
uint32_t cpu_on(uint32_t core, uint64_t entrypoint_addr, uint64_t context_id);
uint32_t cpu_off(void); /* TODO */
uint32_t cpu_suspend(uint64_t power_state, uint64_t entrypoint_addr, uint64_t context_id); /* TODO */
#endif

253
exosphere/src/exceptions.s Normal file
View File

@@ -0,0 +1,253 @@
/* Some macros taken from https://github.com/ARM-software/arm-trusted-firmware/blob/master/include/common/aarch64/asm_macros.S */
/*
* Copyright (c) 2013-2017, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
/*
* Declare the exception vector table, enforcing it is aligned on a
* 2KB boundary, as required by the ARMv8 architecture.
* Use zero bytes as the fill value to be stored in the padding bytes
* so that it inserts illegal AArch64 instructions. This increases
* security, robustness and potentially facilitates debugging.
*/
.macro vector_base label, section_name=.vectors
.section \section_name, "ax"
.align 11, 0
\label:
.endm
/*
* Create an entry in the exception vector table, enforcing it is
* aligned on a 128-byte boundary, as required by the ARMv8 architecture.
* Use zero bytes as the fill value to be stored in the padding bytes
* so that it inserts illegal AArch64 instructions. This increases
* security, robustness and potentially facilitates debugging.
*/
.macro vector_entry label, section_name=.vectors
.cfi_sections .debug_frame
.section \section_name, "ax"
.align 7, 0
.type \label, %function
.func \label
.cfi_startproc
\label:
.endm
/*
* This macro verifies that the given vector doesnt exceed the
* architectural limit of 32 instructions. This is meant to be placed
* immediately after the last instruction in the vector. It takes the
* vector entry as the parameter
*/
.macro check_vector_size since
.endfunc
.cfi_endproc
.if (. - \since) > (32 * 4)
.error "Vector exceeds 32 instructions"
.endif
.endm
/* Actual Vectors for Exosphere. */
.global exosphere_vectors
vector_base exosphere_vectors
/* Current EL, SP0 */
.global unknown_exception
unknown_exception:
vector_entry synch_sp0
/* Panic with color FF7700, code 10. */
mov x0, #0x10
movk x0, #0x07F0,lsl#16
b panic
check_vector_size synch_sp0
vector_entry irq_sp0
b unknown_exception
check_vector_size irq_sp0
vector_entry fiq_sp0
b unknown_exception
check_vector_size fiq_sp0
vector_entry serror_sp0
b unknown_exception
check_vector_size serror_sp0
/* Current EL, SPx */
vector_entry synch_spx
b unknown_exception
check_vector_size synch_spx
vector_entry irq_spx
b unknown_exception
check_vector_size irq_spx
vector_entry fiq_spx
b unknown_exception
check_vector_size fiq_spx
vector_entry serror_spx
b unknown_exception
check_vector_size serror_spx
/* Lower EL, A64 */
vector_entry synch_a64
stp x29, x30, [sp, #-0x10]!
/* Verify SMC. */
mrs x30, esr_el3
lsr w29, w30, #0x1A
cmp w29, #0x17
ldp x29, x30, [sp],#0x10
b.ne unknown_exception
/* Call appropriate handler. */
stp x29, x30, [sp, #-0x10]!
mrs x29, mpidr_el1
and x29, x29, #0x3
cmp x29, #0x3
b.ne handle_core012_smc_exception
bl handle_core3_smc_exception
ldp x29, x30, [sp],#0x10
eret
check_vector_size synch_a64
vector_entry irq_a64
b unknown_exception
check_vector_size irq_a64
vector_entry fiq_a64
stp x29, x30, [sp, #-0x10]!
mrs x29, mpidr_el1
and x29, x29, #0x3
cmp x29, #0x3
b.ne unknown_exception
stp x28, x29, [sp, #-0x10]!
stp x26, x27, [sp, #-0x10]!
bl handle_fiq_exception
ldp x26, x27, [sp],#0x10
ldp x28, x29, [sp],#0x10
ldp x29, x30, [sp],#0x10
eret
check_vector_size fiq_a64
vector_entry serror_a64
b unknown_exception
.endfunc
.cfi_endproc
/* To save space, insert in an unused vector segment. */
.global handle_core012_smc_exception
.type handle_core012_smc_exception, %function
handle_core012_smc_exception:
stp x6, x7, [sp, #-0x10]!
stp x4, x5, [sp, #-0x10]!
stp x2, x3, [sp, #-0x10]!
stp x0, x1, [sp, #-0x10]!
bl set_priv_smc_in_progress
bl get_smc_core012_stack_address
mov x29, x0
ldp x0, x1, [sp],#0x10
ldp x2, x3, [sp],#0x10
ldp x4, x5, [sp],#0x10
ldp x6, x7, [sp],#0x10
mov x30, sp
mov sp, x29
stp x29, x30, [sp, #-0x10]!
bl handle_core3_smc_exception
ldp x29, x30, [sp],#0x10
mov sp, x30
stp x6, x7, [sp, #-0x10]!
stp x4, x5, [sp, #-0x10]!
stp x2, x3, [sp, #-0x10]!
stp x0, x1, [sp, #-0x10]!
bl clear_priv_smc_in_progress
ldp x0, x1, [sp],#0x10
ldp x2, x3, [sp],#0x10
ldp x4, x5, [sp],#0x10
ldp x6, x7, [sp],#0x10
ldp x29, x30, [sp],#0x10
eret
/* Lower EL, A32 */
vector_entry synch_a32
b unknown_exception
check_vector_size synch_a32
vector_entry irq_a32
b unknown_exception
check_vector_size irq_a32
vector_entry fiq_a32
b fiq_a64
.endfunc
.cfi_endproc
/* To save space, insert in an unused vector segment. */
.global handle_fiq_exception
.type handle_fiq_exception, %function
handle_fiq_exception:
stp x29, x30, [sp, #-0x10]!
stp x24, x25, [sp, #-0x10]!
stp x22, x23, [sp, #-0x10]!
stp x20, x21, [sp, #-0x10]!
stp x18, x19, [sp, #-0x10]!
stp x16, x17, [sp, #-0x10]!
stp x14, x15, [sp, #-0x10]!
stp x12, x13, [sp, #-0x10]!
stp x10, x11, [sp, #-0x10]!
stp x8, x9, [sp, #-0x10]!
stp x6, x7, [sp, #-0x10]!
stp x4, x5, [sp, #-0x10]!
stp x2, x3, [sp, #-0x10]!
stp x0, x1, [sp, #-0x10]!
bl handle_registered_interrupt
ldp x0, x1, [sp],#0x10
ldp x2, x3, [sp],#0x10
ldp x4, x5, [sp],#0x10
ldp x6, x7, [sp],#0x10
ldp x8, x9, [sp],#0x10
ldp x10, x11, [sp],#0x10
ldp x12, x13, [sp],#0x10
ldp x14, x15, [sp],#0x10
ldp x16, x17, [sp],#0x10
ldp x18, x19, [sp],#0x10
ldp x20, x21, [sp],#0x10
ldp x22, x23, [sp],#0x10
ldp x24, x25, [sp],#0x10
ldp x29, x30, [sp],#0x10
ret
vector_entry serror_a32
b unknown_exception
.endfunc
.cfi_endproc
/* To save space, insert in an unused vector segment. */
.global handle_core3_smc_exception
.type handle_core3_smc_exception, %function
handle_core3_smc_exception:
stp x29, x30, [sp, #-0x10]!
stp x18, x19, [sp, #-0x10]!
stp x16, x17, [sp, #-0x10]!
stp x14, x15, [sp, #-0x10]!
stp x12, x13, [sp, #-0x10]!
stp x10, x11, [sp, #-0x10]!
stp x8, x9, [sp, #-0x10]!
stp x6, x7, [sp, #-0x10]!
stp x4, x5, [sp, #-0x10]!
stp x2, x3, [sp, #-0x10]!
stp x0, x1, [sp, #-0x10]!
mrs x0, esr_el3
and x0, x0, #0xFFFF
mov x1, sp
bl call_smc_handler
ldp x0, x1, [sp],#0x10
ldp x2, x3, [sp],#0x10
ldp x4, x5, [sp],#0x10
ldp x6, x7, [sp],#0x10
ldp x8, x9, [sp],#0x10
ldp x10, x11, [sp],#0x10
ldp x12, x13, [sp],#0x10
ldp x14, x15, [sp],#0x10
ldp x16, x17, [sp],#0x10
ldp x18, x19, [sp],#0x10
ret

229
exosphere/src/fuse.c Normal file
View File

@@ -0,0 +1,229 @@
#include <string.h>
#include "fuse.h"
#include "utils.h"
#include "timers.h"
/* Prototypes for internal commands. */
void fuse_make_regs_visible(void);
void fuse_enable_power(void);
void fuse_disable_power(void);
void fuse_wait_idle(void);
/* Initialize the FUSE driver */
void fuse_init(void)
{
fuse_make_regs_visible();
/* TODO: Overrides (iROM patches) and various reads happen here */
}
/* Make all fuse registers visible */
void fuse_make_regs_visible(void)
{
/* TODO: Replace this with a proper CLKRST driver */
volatile uint32_t* misc_clk_reg = (volatile uint32_t *)mmio_get_device_address(MMIO_DEVID_CLKRST) + 0x48;
uint32_t misc_clk_val = *misc_clk_reg;
*misc_clk_reg = (misc_clk_val | (1 << 28));
}
/* Enable power to the fuse hardware array */
void fuse_enable_power(void)
{
FUSE_REGS->FUSE_PWR_GOOD_SW = 1;
wait(1);
}
/* Disable power to the fuse hardware array */
void fuse_disable_power(void)
{
FUSE_REGS->FUSE_PWR_GOOD_SW = 0;
wait(1);
}
/* Wait for the fuse driver to go idle */
void fuse_wait_idle(void)
{
uint32_t ctrl_val = 0;
/* Wait for STATE_IDLE */
while ((ctrl_val & (0xF0000)) != 0x40000)
{
wait(1);
ctrl_val = FUSE_REGS->FUSE_CTRL;
}
}
/* Read a fuse from the hardware array */
uint32_t fuse_hw_read(uint32_t addr)
{
fuse_wait_idle();
/* Program the target address */
FUSE_REGS->FUSE_REG_ADDR = addr;
/* Enable read operation in control register */
uint32_t ctrl_val = FUSE_REGS->FUSE_CTRL;
ctrl_val &= ~0x3;
ctrl_val |= 0x1; /* Set FUSE_READ command */
FUSE_REGS->FUSE_CTRL = ctrl_val;
fuse_wait_idle();
return FUSE_REGS->FUSE_REG_READ;
}
/* Write a fuse in the hardware array */
void fuse_hw_write(uint32_t value, uint32_t addr)
{
fuse_wait_idle();
/* Program the target address and value */
FUSE_REGS->FUSE_REG_ADDR = addr;
FUSE_REGS->FUSE_REG_WRITE = value;
/* Enable write operation in control register */
uint32_t ctrl_val = FUSE_REGS->FUSE_CTRL;
ctrl_val &= ~0x3;
ctrl_val |= 0x2; /* Set FUSE_WRITE command */
FUSE_REGS->FUSE_CTRL = ctrl_val;
fuse_wait_idle();
}
/* Sense the fuse hardware array into the shadow cache */
void fuse_hw_sense(void)
{
fuse_wait_idle();
/* Enable sense operation in control register */
uint32_t ctrl_val = FUSE_REGS->FUSE_CTRL;
ctrl_val &= ~0x3;
ctrl_val |= 0x3; /* Set FUSE_SENSE command */
FUSE_REGS->FUSE_CTRL = ctrl_val;
fuse_wait_idle();
}
/* Disables all fuse programming. */
void fuse_disable_programming(void) {
FUSE_REGS->FUSE_DIS_PGM = 1;
}
/* Unknown exactly what this does, but it alters the contents read from the fuse cache. */
void fuse_secondary_private_key_disable(void) {
FUSE_REGS->FUSE_PRIVATEKEYDISABLE = 0x10;
}
/* Read the SKU info register from the shadow cache */
uint32_t fuse_get_sku_info(void)
{
return FUSE_CHIP_REGS->FUSE_SKU_INFO;
}
/* Read the bootrom patch version from a register in the shadow cache */
uint32_t fuse_get_bootrom_patch_version(void)
{
return FUSE_CHIP_REGS->FUSE_SOC_SPEEDO_1;
}
/* Read a spare bit register from the shadow cache */
uint32_t fuse_get_spare_bit(uint32_t idx)
{
uint32_t spare_bit_val = 0;
if ((idx >= 0) && (idx < 32))
spare_bit_val = FUSE_CHIP_REGS->FUSE_SPARE_BIT[idx];
return spare_bit_val;
}
/* Read a reserved ODM register from the shadow cache */
uint32_t fuse_get_reserved_odm(uint32_t idx)
{
uint32_t reserved_odm_val = 0;
if ((idx >= 0) && (idx < 8))
reserved_odm_val = FUSE_CHIP_REGS->FUSE_RESERVED_ODM[idx];
return reserved_odm_val;
}
/* Derive the Device ID using values in the shadow cache */
uint64_t fuse_get_device_id(void) {
uint64_t device_id = 0;
uint64_t y_coord = FUSE_CHIP_REGS->FUSE_Y_COORDINATE & 0x1FF;
uint64_t x_coord = FUSE_CHIP_REGS->FUSE_X_COORDINATE & 0x1FF;
uint64_t wafer_id = FUSE_CHIP_REGS->FUSE_WAFER_ID & 0x3F;
uint32_t lot_code = FUSE_CHIP_REGS->FUSE_LOT_CODE_0;
uint64_t fab_code = FUSE_CHIP_REGS->FUSE_FAB_CODE & 0x3F;
uint64_t derived_lot_code = 0;
for (unsigned int i = 0; i < 5; i++) {
derived_lot_code = (derived_lot_code * 0x24) + ((lot_code >> (24 - 6*i)) & 0x3F);
}
derived_lot_code &= 0x03FFFFFF;
device_id |= y_coord << 0;
device_id |= x_coord << 9;
device_id |= wafer_id << 18;
device_id |= derived_lot_code << 24;
device_id |= fab_code << 50;
return device_id;
}
/* Get the DRAM ID using values in the shadow cache */
uint32_t fuse_get_dram_id(void) {
return (FUSE_CHIP_REGS->FUSE_RESERVED_ODM[4] >> 3) & 0x7;
}
/* Derive the Hardware Type using values in the shadow cache */
uint32_t fuse_get_hardware_type(void) {
uint32_t hardware_type = ((FUSE_CHIP_REGS->FUSE_RESERVED_ODM[4] >> 7) & 2) | ((FUSE_CHIP_REGS->FUSE_RESERVED_ODM[4] >> 2) & 1);
if (hardware_type) {
if (hardware_type == 1) {
return 0;
}
if (hardware_type == 2) {
return 1;
}
} else if ((FUSE_CHIP_REGS->FUSE_SPARE_BIT[9] & 1) == 0) {
return 0;
}
return 3;
}
/* Derive the Retail Type using values in the shadow cache */
uint32_t fuse_get_retail_type(void) {
/* Retail type = IS_RETAIL | UNIT_TYPE */
uint32_t retail_type = ((FUSE_CHIP_REGS->FUSE_RESERVED_ODM[4] >> 7) & 4) | (FUSE_CHIP_REGS->FUSE_RESERVED_ODM[4] & 3);
if (retail_type == 4) { /* Standard retail unit, IS_RETAIL | 0. */
return 1;
} else if (retail_type == 3) { /* Standard dev unit, 0 | DEV_UNIT. */
return 0;
}
return 2; /* IS_RETAIL | DEV_UNIT */
}
/* Derive the 16-byte Hardware Info using values in the shadow cache, and copy to output buffer. */
void fuse_get_hardware_info(void *dst) {
uint32_t hw_info[0x4];
uint32_t unk_hw_fuse = FUSE_CHIP_REGS->_0x120 & 0x3F;
uint32_t y_coord = FUSE_CHIP_REGS->FUSE_Y_COORDINATE & 0x1FF;
uint32_t x_coord = FUSE_CHIP_REGS->FUSE_X_COORDINATE & 0x1FF;
uint32_t wafer_id = FUSE_CHIP_REGS->FUSE_WAFER_ID & 0x3F;
uint32_t lot_code_0 = FUSE_CHIP_REGS->FUSE_LOT_CODE_0;
uint32_t lot_code_1 = FUSE_CHIP_REGS->FUSE_LOT_CODE_1 & 0x0FFFFFFF;
uint32_t fab_code = FUSE_CHIP_REGS->FUSE_FAB_CODE & 0x3F;
uint32_t vendor_code = FUSE_CHIP_REGS->FUSE_VENDOR_CODE & 0xF;
/* Hardware Info = unk_hw_fuse || Y_COORD || X_COORD || WAFER_ID || LOT_CODE || FAB_CODE || VENDOR_ID */
hw_info[0] = (uint32_t)((lot_code_1 << 30) | (wafer_id << 24) | (x_coord << 15) | (y_coord << 6) | (unk_hw_fuse));
hw_info[1] = (uint32_t)((lot_code_0 << 26) | (lot_code_1 >> 2));
hw_info[2] = (uint32_t)((fab_code << 26) | (lot_code_0 >> 6));
hw_info[3] = (uint32_t)(vendor_code);
memcpy(dst, hw_info, 0x10);
}

193
exosphere/src/fuse.h Normal file
View File

@@ -0,0 +1,193 @@
#ifndef EXOSPHERE_FUSE_H
#define EXOSPHERE_FUSE_H
#include <stdbool.h>
#include <stdint.h>
#include "memory_map.h"
/* Exosphere driver for the Tegra X1 FUSE registers. */
typedef struct {
uint32_t FUSE_CTRL;
uint32_t FUSE_REG_ADDR;
uint32_t FUSE_REG_READ;
uint32_t FUSE_REG_WRITE;
uint32_t FUSE_TIME_RD1;
uint32_t FUSE_TIME_RD2;
uint32_t FUSE_TIME_PGM1;
uint32_t FUSE_TIME_PGM2;
uint32_t FUSE_PRIV2INTFC;
uint32_t FUSE_FUSEBYPASS;
uint32_t FUSE_PRIVATEKEYDISABLE;
uint32_t FUSE_DIS_PGM;
uint32_t FUSE_WRITE_ACCESS;
uint32_t FUSE_PWR_GOOD_SW;
uint32_t _0x38[0x32];
} fuse_registers_t;
typedef struct {
uint32_t FUSE_PRODUCTION_MODE;
uint32_t _0x4;
uint32_t _0x8;
uint32_t _0xC;
uint32_t FUSE_SKU_INFO;
uint32_t FUSE_CPU_SPEEDO_0;
uint32_t FUSE_CPU_IDDQ;
uint32_t _0x1C;
uint32_t _0x20;
uint32_t _0x24;
uint32_t FUSE_FT_REV;
uint32_t FUSE_CPU_SPEEDO_1;
uint32_t FUSE_CPU_SPEEDO_2;
uint32_t FUSE_SOC_SPEEDO_0;
uint32_t FUSE_SOC_SPEEDO_1;
uint32_t FUSE_SOC_SPEEDO_2;
uint32_t FUSE_SOC_IDDQ;
uint32_t _0x44;
uint32_t FUSE_FA;
uint32_t _0x4C;
uint32_t _0x50;
uint32_t _0x54;
uint32_t _0x58;
uint32_t _0x5C;
uint32_t _0x60;
uint32_t FUSE_PUBLIC_KEY[0x8];
uint32_t FUSE_TSENSOR_1;
uint32_t FUSE_TSENSOR_2;
uint32_t _0x8C;
uint32_t FUSE_CP_REV;
uint32_t _0x94;
uint32_t FUSE_TSENSOR_0;
uint32_t FUSE_FIRST_BOOTROM_PATCH_SIZE_REG;
uint32_t FUSE_SECURITY_MODE;
uint32_t FUSE_PRIVATE_KEY[0x4];
uint32_t FUSE_DEVICE_KEY;
uint32_t _0xB8;
uint32_t _0xBC;
uint32_t FUSE_RESERVED_SW;
uint32_t FUSE_VP8_ENABLE;
uint32_t FUSE_RESERVED_ODM[0x8];
uint32_t _0xE8;
uint32_t _0xEC;
uint32_t FUSE_SKU_USB_CALIB;
uint32_t FUSE_SKU_DIRECT_CONFIG;
uint32_t _0xF8;
uint32_t _0xFC;
uint32_t FUSE_VENDOR_CODE;
uint32_t FUSE_FAB_CODE;
uint32_t FUSE_LOT_CODE_0;
uint32_t FUSE_LOT_CODE_1;
uint32_t FUSE_WAFER_ID;
uint32_t FUSE_X_COORDINATE;
uint32_t FUSE_Y_COORDINATE;
uint32_t _0x11C;
uint32_t _0x120;
uint32_t FUSE_SATA_CALIB;
uint32_t FUSE_GPU_IDDQ;
uint32_t FUSE_TSENSOR_3;
uint32_t _0x130;
uint32_t _0x134;
uint32_t _0x138;
uint32_t _0x13C;
uint32_t _0x140;
uint32_t _0x144;
uint32_t FUSE_OPT_SUBREVISION;
uint32_t _0x14C;
uint32_t _0x150;
uint32_t FUSE_TSENSOR_4;
uint32_t FUSE_TSENSOR_5;
uint32_t FUSE_TSENSOR_6;
uint32_t FUSE_TSENSOR_7;
uint32_t FUSE_OPT_PRIV_SEC_DIS;
uint32_t FUSE_PKC_DISABLE;
uint32_t _0x16C;
uint32_t _0x170;
uint32_t _0x174;
uint32_t _0x178;
uint32_t _0x17C;
uint32_t FUSE_TSENSOR_COMMON;
uint32_t _0x184;
uint32_t _0x188;
uint32_t _0x18C;
uint32_t _0x190;
uint32_t _0x194;
uint32_t _0x198;
uint32_t FUSE_DEBUG_AUTH_OVERRIDE;
uint32_t _0x1A0;
uint32_t _0x1A4;
uint32_t _0x1A8;
uint32_t _0x1AC;
uint32_t _0x1B0;
uint32_t _0x1B4;
uint32_t _0x1B8;
uint32_t _0x1BC;
uint32_t _0x1D0;
uint32_t FUSE_TSENSOR_8;
uint32_t _0x1D8;
uint32_t _0x1DC;
uint32_t _0x1E0;
uint32_t _0x1E4;
uint32_t _0x1E8;
uint32_t _0x1EC;
uint32_t _0x1F0;
uint32_t _0x1F4;
uint32_t _0x1F8;
uint32_t _0x1FC;
uint32_t _0x200;
uint32_t FUSE_RESERVED_CALIB;
uint32_t _0x208;
uint32_t _0x20C;
uint32_t _0x210;
uint32_t _0x214;
uint32_t _0x218;
uint32_t FUSE_TSENSOR_9;
uint32_t _0x220;
uint32_t _0x224;
uint32_t _0x228;
uint32_t _0x22C;
uint32_t _0x230;
uint32_t _0x234;
uint32_t _0x238;
uint32_t _0x23C;
uint32_t _0x240;
uint32_t _0x244;
uint32_t _0x248;
uint32_t _0x24C;
uint32_t FUSE_USB_CALIB_EXT;
uint32_t _0x254;
uint32_t _0x258;
uint32_t _0x25C;
uint32_t _0x260;
uint32_t _0x264;
uint32_t _0x268;
uint32_t _0x26C;
uint32_t _0x270;
uint32_t _0x274;
uint32_t _0x278;
uint32_t _0x27C;
uint32_t FUSE_SPARE_BIT[0x20];
} fuse_chip_registers_t;
#define FUSE_REGS ((volatile fuse_registers_t *)(mmio_get_device_address(MMIO_DEVID_FUSE) + 0x800))
#define FUSE_CHIP_REGS ((volatile fuse_chip_registers_t *)(mmio_get_device_address(MMIO_DEVID_FUSE) + 0x900))
void fuse_init(void);
uint32_t fuse_hw_read(uint32_t addr);
void fuse_hw_write(uint32_t value, uint32_t addr);
void fuse_hw_sense(void);
void fuse_disable_programming(void);
void fuse_secondary_private_key_disable(void);
uint32_t fuse_get_sku_info(void);
uint32_t fuse_get_spare_bit(uint32_t idx);
uint32_t fuse_get_reserved_odm(uint32_t idx);
uint32_t fuse_get_bootrom_patch_version(void);
uint64_t fuse_get_device_id(void);
uint32_t fuse_get_dram_id(void);
uint32_t fuse_get_hardware_type(void);
uint32_t fuse_get_retail_type(void);
void fuse_get_hardware_info(void *dst);
#endif

167
exosphere/src/gcm.c Normal file
View File

@@ -0,0 +1,167 @@
#include <stdint.h>
#include <string.h>
#include "utils.h"
#include "fuse.h"
#include "gcm.h"
#include "sealedkeys.h"
#include "se.h"
/* Shifts right a little endian 128-bit value. */
static void shr_128(uint64_t *val) {
val[0] >>= 1;
val[0] |= (val[1] & 1) << 63;
val[1] >>= 1;
}
/* Shifts left a little endian 128-bit value. */
static void shl_128(uint64_t *val) {
val[1] <<= 1;
val[1] |= (val[0] & (1ULL << 63)) >> 63;
val[0] <<= 1;
}
/* Multiplies two 128-bit numbers X,Y in the GF(128) Galois Field. */
static void gf128_mul(uint8_t *dst, const uint8_t *x, const uint8_t *y) {
uint8_t x_work[0x10];
uint8_t y_work[0x10];
uint8_t dst_work[0x10];
uint64_t *p_x = (uint64_t *)(&x_work[0]);
uint64_t *p_y = (uint64_t *)(&y_work[0]);
uint64_t *p_dst = (uint64_t *)(&dst_work[0]);
/* Initialize buffers. */
for (unsigned int i = 0; i < 0x10; i++) {
x_work[i] = x[0xF-i];
y_work[i] = y[0xF-i];
dst_work[i] = 0;
}
/* Perform operation for each bit in y. */
for (unsigned int round = 0; round < 0x80; round++) {
p_dst[0] ^= p_x[0] * ((y_work[0xF] & 0x80) >> 7);
p_dst[1] ^= p_x[1] * ((y_work[0xF] & 0x80) >> 7);
shl_128(p_y);
uint8_t xval = 0xE1 * (x_work[0] & 1);
shr_128(p_x);
x_work[0xF] ^= xval;
}
for (unsigned int i = 0; i < 0x10; i++) {
dst[i] = dst_work[0xF-i];
}
}
/* Performs an AES-GCM GHASH operation over the data into dst. */
static void ghash(void *dst, const void *data, size_t data_size, const void *j_block, bool encrypt) {
uint8_t x[0x10];
uint8_t h[0x10];
uint64_t *p_x = (uint64_t *)(&x[0]);
uint64_t *p_data = (uint64_t *)data;
memset(x, 0, 0x10);
/* H = aes_ecb_encrypt(zeroes) */
se_aes_128_ecb_encrypt_block(KEYSLOT_SWITCH_TEMPKEY, h, 0x10, x, 0x10);
size_t total_size = data_size;
while (data_size >= 0x10) {
/* X = (X ^ current_block) * H */
p_x[0] ^= p_data[0];
p_x[1] ^= p_data[1];
gf128_mul(x, x, h);
/* Increment p_data by 0x10 bytes. */
p_data += 2;
data_size -= 0x10;
}
/* Nintendo's code *discards all data in the last block* if unaligned. */
/* And treats that block as though it were all-zero. */
/* This is a bug, they just forget to XOR with the copy of the last block they save. */
if (data_size & 0xF) {
gf128_mul(x, x, h);
}
/* Due to a Nintendo bug, the wrong QWORD gets XOR'd in the "final output block" case. */
if (encrypt) {
p_x[1] ^= (uint64_t)(total_size << 3);
} else {
p_x[0] ^= (uint64_t)(total_size << 3);
}
gf128_mul(x, x, h);
/* If final output block, XOR with encrypted J block. */
if (encrypt) {
se_aes_128_ecb_encrypt_block(KEYSLOT_SWITCH_TEMPKEY, h, 0x10, j_block, 0x10);
for (unsigned int i = 0; i < 0x10; i++) {
x[i] ^= h[i];
}
}
/* Copy output. */
memcpy(dst, x, 0x10);
}
/* This function is a doozy. It decrypts and validates a (non-standard) AES-GCM wrapped keypair. */
size_t gcm_decrypt_key(void *dst, size_t dst_size, const void *src, size_t src_size, const void *sealed_kek, size_t kek_size, const void *wrapped_key, size_t key_size, unsigned int usecase, bool is_personalized) {
if (is_personalized == 0) {
/* Devkit keys use a different keyformat without a MAC/Device ID. */
if (src_size <= 0x10 || src_size - 0x10 > dst_size) {
generic_panic();
}
} else {
if (src_size <= 0x30 || src_size - 0x20 > dst_size) {
generic_panic();
}
}
/* Unwrap the key */
unseal_key(KEYSLOT_SWITCH_TEMPKEY, sealed_kek, kek_size, usecase);
decrypt_data_into_keyslot(KEYSLOT_SWITCH_TEMPKEY, KEYSLOT_SWITCH_TEMPKEY, wrapped_key, key_size);
/* Decrypt the GCM keypair, AES-CTR with CTR = blob[:0x10]. */
se_aes_ctr_crypt(KEYSLOT_SWITCH_TEMPKEY, dst, dst_size, src + 0x10, src_size - 0x10, src, 0x10);
if (!is_personalized) {
/* Devkit non-personalized keys have no further authentication. */
return src_size - 0x10;
}
/* J = GHASH(CTR); */
uint8_t j_block[0x10];
ghash(j_block, src, 0x10, NULL, false);
/* MAC = GHASH(PLAINTEXT) ^ ENCRYPT(J) */
/* Note: That MAC is calculated over plaintext is non-standard. */
/* It is supposed to be over the ciphertext. */
uint8_t calc_mac[0x10];
ghash(calc_mac, dst, src_size - 0x20, j_block, true);
/* Const-time memcmp. */
const uint8_t *src_bytes = src;
int different = 0;
for (unsigned int i = 0; i < 0x10; i++) {
different |= src_bytes[src_size - 0x10 + i] ^ calc_mac[i];
}
if (different) {
return 0;
}
if (read64le(src_bytes, src_size - 0x28) != fuse_get_device_id()) {
return 0;
}
return src_size - 0x30;
}

13
exosphere/src/gcm.h Normal file
View File

@@ -0,0 +1,13 @@
#ifndef EXOSPHERE_GCM_H
#define EXOSPHERE_GCM_H
#include <stdbool.h>
#include <stdint.h>
size_t gcm_decrypt_key(void *dst, size_t dst_size,
const void *src, size_t src_size,
const void *sealed_kek, size_t kek_size,
const void *wrapped_key, size_t key_size,
unsigned int usecase, bool is_personalized);
#endif

201
exosphere/src/i2c.c Normal file
View File

@@ -0,0 +1,201 @@
#include <string.h>
#include "i2c.h"
#include "utils.h"
#include "timers.h"
/* Prototypes for internal commands. */
volatile i2c_registers_t *i2c_get_registers_from_id(unsigned int id);
void i2c_load_config(volatile i2c_registers_t *regs);
bool i2c_query(unsigned int id, uint8_t device, uint8_t r, void *dst, size_t dst_size);
bool i2c_send(unsigned int id, uint8_t device, uint8_t r, void *src, size_t src_size);
bool i2c_write(volatile i2c_registers_t *regs, uint8_t device, void *src, size_t src_size);
bool i2c_read(volatile i2c_registers_t *regs, uint8_t device, void *dst, size_t dst_size);
/* Initialize I2C based on registers. */
void i2c_init(unsigned int id) {
volatile i2c_registers_t *regs = i2c_get_registers_from_id(id);
/* Setup divisor, and clear the bus. */
regs->I2C_I2C_CLK_DIVISOR_REGISTER_0 = 0x50001;
regs->I2C_I2C_BUS_CLEAR_CONFIG_0 = 0x90003;
/* Load hardware configuration. */
i2c_load_config(regs);
/* Wait a while until BUS_CLEAR_DONE is set. */
for (unsigned int i = 0; i < 10; i++) {
wait(20000);
if (regs->I2C_INTERRUPT_STATUS_REGISTER_0 & 0x800) {
break;
}
}
/* Read the BUS_CLEAR_STATUS. Result doesn't matter. */
regs->I2C_I2C_BUS_CLEAR_STATUS_0;
/* Read and set the Interrupt Status. */
uint32_t int_status = regs->I2C_INTERRUPT_STATUS_REGISTER_0;
regs->I2C_INTERRUPT_STATUS_REGISTER_0 = int_status;
}
/* Sets a bit in a PMIC register over I2C during CPU shutdown. */
void i2c_send_pmic_cpu_shutdown_cmd(void) {
uint32_t val = 0;
/* PMIC == Device 4:3C. */
i2c_query(4, 0x3C, 0x41, &val, 1);
val |= 4;
i2c_send(4, 0x3C, 0x41, &val, 1);
}
/* Queries the value of TI charger bit over I2C. */
bool i2c_query_ti_charger_bit_7(void) {
uint32_t val = 0;
/* TI Charger = Device 0:6B. */
i2c_query(0, 0x6B, 0, &val, 1);
return (val & 0x80) != 0;
}
/* Clears TI charger bit over I2C. */
void i2c_clear_ti_charger_bit_7(void) {
uint32_t val = 0;
/* TI Charger = Device 0:6B. */
i2c_query(0, 0x6B, 0, &val, 1);
val &= 0x7F;
i2c_send(0, 0x6B, 0, &val, 1);
}
/* Sets TI charger bit over I2C. */
void i2c_set_ti_charger_bit_7(void) {
uint32_t val = 0;
/* TI Charger = Device 0:6B. */
i2c_query(0, 0x6B, 0, &val, 1);
val |= 0x80;
i2c_send(0, 0x6B, 0, &val, 1);
}
/* Get registers pointer based on I2C ID. */
volatile i2c_registers_t *i2c_get_registers_from_id(unsigned int id) {
switch (id) {
case 0:
return I2C1_REGS;
case 1:
return I2C2_REGS;
case 2:
return I2C3_REGS;
case 3:
return I2C4_REGS;
case 4:
return I2C5_REGS;
case 5:
return I2C6_REGS;
default:
generic_panic();
}
return NULL;
}
/* Load hardware config for I2C4. */
void i2c_load_config(volatile i2c_registers_t *regs) {
/* Set MSTR_CONFIG_LOAD, TIMEOUT_CONFIG_LOAD, undocumented bit. */
regs->I2C_I2C_CONFIG_LOAD_0 = 0x25;
/* Wait a bit for master config to be loaded. */
for (unsigned int i = 0; i < 20; i++) {
wait(1);
if (!(regs->I2C_I2C_CONFIG_LOAD_0 & 1)) {
break;
}
}
}
/* Reads a register from a device over I2C, writes result to output. */
bool i2c_query(unsigned int id, uint8_t device, uint8_t r, void *dst, size_t dst_size) {
volatile i2c_registers_t *regs = i2c_get_registers_from_id(id);
uint32_t val = r;
/* Write single byte register ID to device. */
if (!i2c_write(regs, device, &val, 1)) {
return false;
}
/* Limit output size to 32-bits. */
if (dst_size > 4) {
return false;
}
return i2c_read(regs, device, dst, dst_size);
}
/* Writes a value to a register over I2C. */
bool i2c_send(unsigned int id, uint8_t device, uint8_t r, void *src, size_t src_size) {
uint32_t val = r;
if (src_size <= 3) {
memcpy(((uint8_t *)&val) + 1, src, src_size);
return i2c_write(i2c_get_registers_from_id(id), device, &val, src_size + 1);
} else {
return false;
}
}
/* Writes bytes to device over I2C. */
bool i2c_write(volatile i2c_registers_t *regs, uint8_t device, void *src, size_t src_size) {
if (src_size > 4) {
return false;
}
/* Set device for 7-bit write mode. */
regs->I2C_I2C_CMD_ADDR0_0 = device << 1;
/* Load in data to write. */
regs->I2C_I2C_CMD_DATA1_0 = read32le(src, 0);
/* Set config with LENGTH = src_size, NEW_MASTER_FSM, DEBOUNCE_CNT = 4T. */
regs->I2C_I2C_CNFG_0 = ((src_size << 1) - 2) | 0x2800;
i2c_load_config(regs);
/* Config |= SEND; */
regs->I2C_I2C_CNFG_0 |= 0x200;
while (regs->I2C_I2C_STATUS_0 & 0x100) {
/* Wait until not busy. */
}
/* Return CMD1_STAT == SL1_XFER_SUCCESSFUL. */
return (regs->I2C_I2C_STATUS_0 & 0xF) == 0;
}
/* Reads bytes from device over I2C. */
bool i2c_read(volatile i2c_registers_t *regs, uint8_t device, void *dst, size_t dst_size) {
if (dst_size > 4) {
return false;
}
/* Set device for 7-bit read mode. */
regs->I2C_I2C_CMD_ADDR0_0 = (device << 1) | 1;
/* Set config with LENGTH = dst_size, NEW_MASTER_FSM, DEBOUNCE_CNT = 4T. */
regs->I2C_I2C_CNFG_0 = ((dst_size << 1) - 2) | 0x2840;
i2c_load_config(regs);
/* Config |= SEND; */
regs->I2C_I2C_CNFG_0 |= 0x200;
while (regs->I2C_I2C_STATUS_0 & 0x100) {
/* Wait until not busy. */
}
/* Ensure success. */
if ((regs->I2C_I2C_STATUS_0 & 0xF) != 0) {
return false;
}
uint32_t val = regs->I2C_I2C_CMD_DATA1_0;
memcpy(dst, &val, dst_size);
return true;
}

70
exosphere/src/i2c.h Normal file
View File

@@ -0,0 +1,70 @@
#ifndef EXOSPHERE_I2C_H
#define EXOSPHERE_I2C_H
#include <stdbool.h>
#include <stdint.h>
#include "memory_map.h"
/* Exosphere driver for the Tegra X1 I2C registers. */
typedef struct {
uint32_t I2C_I2C_CNFG_0;
uint32_t I2C_I2C_CMD_ADDR0_0;
uint32_t I2C_I2C_CMD_ADDR1_0;
uint32_t I2C_I2C_CMD_DATA1_0;
uint32_t I2C_I2C_CMD_DATA2_0;
uint32_t _0x14;
uint32_t _0x18;
uint32_t I2C_I2C_STATUS_0;
uint32_t I2C_I2C_SL_CNFG_0;
uint32_t I2C_I2C_SL_RCVD_0;
uint32_t I2C_I2C_SL_STATUS_0;
uint32_t I2C_I2C_SL_ADDR1_0;
uint32_t I2C_I2C_SL_ADDR2_0;
uint32_t I2C_I2C_TLOW_SEXT_0;
uint32_t _0x38;
uint32_t I2C_I2C_SL_DELAY_COUNT_0;
uint32_t I2C_I2C_SL_INT_MASK_0;
uint32_t I2C_I2C_SL_INT_SOURCE_0;
uint32_t I2C_I2C_SL_INT_SET_0;
uint32_t _0x4C;
uint32_t I2C_I2C_TX_PACKET_FIFO_0;
uint32_t I2C_I2C_RX_FIFO_0;
uint32_t I2C_PACKET_TRANSFER_STATUS_0;
uint32_t I2C_FIFO_CONTROL_0;
uint32_t I2C_FIFO_STATUS_0;
uint32_t I2C_INTERRUPT_MASK_REGISTER_0;
uint32_t I2C_INTERRUPT_STATUS_REGISTER_0;
uint32_t I2C_I2C_CLK_DIVISOR_REGISTER_0;
uint32_t I2C_I2C_INTERRUPT_SOURCE_REGISTER_0;
uint32_t I2C_I2C_INTERRUPT_SET_REGISTER_0;
uint32_t I2C_I2C_SLV_TX_PACKET_FIFO_0;
uint32_t I2C_I2C_SLV_RX_FIFO_0;
uint32_t I2C_I2C_SLV_PACKET_STATUS_0;
uint32_t I2C_I2C_BUS_CLEAR_CONFIG_0;
uint32_t I2C_I2C_BUS_CLEAR_STATUS_0;
uint32_t I2C_I2C_CONFIG_LOAD_0;
uint32_t _0x90;
uint32_t I2C_I2C_INTERFACE_TIMING_0_0;
uint32_t I2C_I2C_INTERFACE_TIMING_1_0;
uint32_t I2C_I2C_HS_INTERFACE_TIMING_0_0;
uint32_t I2C_I2C_HS_INTERFACE_TIMING_1_0;
} i2c_registers_t;
#define I2C1_REGS ((volatile i2c_registers_t *)(mmio_get_device_address(MMIO_DEVID_DTV_I2C234) + 0x000))
#define I2C2_REGS ((volatile i2c_registers_t *)(mmio_get_device_address(MMIO_DEVID_DTV_I2C234) + 0x400))
#define I2C3_REGS ((volatile i2c_registers_t *)(mmio_get_device_address(MMIO_DEVID_DTV_I2C234) + 0x500))
#define I2C4_REGS ((volatile i2c_registers_t *)(mmio_get_device_address(MMIO_DEVID_DTV_I2C234) + 0x700))
#define I2C5_REGS ((volatile i2c_registers_t *)(mmio_get_device_address(MMIO_DEVID_I2C56_SPI2B) + 0x000))
#define I2C6_REGS ((volatile i2c_registers_t *)(mmio_get_device_address(MMIO_DEVID_I2C56_SPI2B) + 0x100))
void i2c_init(unsigned int id);
void i2c_send_pmic_cpu_shutdown_cmd(void);
bool i2c_query_ti_charger_bit_7(void);
void i2c_clear_ti_charger_bit_7(void);
void i2c_set_ti_charger_bit_7(void);
#endif

99
exosphere/src/interrupt.c Normal file
View File

@@ -0,0 +1,99 @@
#include <stdint.h>
#include <stdbool.h>
#include "utils.h"
#include "interrupt.h"
/* Global of registered handlers. */
struct {
unsigned int id;
void (*handler)(void);
} g_registered_interrupts[MAX_REGISTERED_INTERRUPTS] = { {0, NULL}, {0, NULL}, {0, NULL}, {0, NULL} };
static unsigned int get_interrupt_id(void) {
return 0;
/* TODO */
}
/* Initializes the GIC. TODO: This must be called during wakeup. */
void intr_initialize_gic(void) {
/* Setup interrupts 0-0x1F as nonsecure with highest non-secure priority. */
GICD_IGROUPR[0] = 0xFFFFFFFF;
for (unsigned int i = 0; i < 0x20; i++) {
GICD_IPRIORITYR[i] = GIC_PRI_HIGHEST_NONSECURE;
}
/* Setup the GICC. */
GICC_CTLR = 0x1D9;
GICC_PMR = GIC_PRI_HIGHEST_NONSECURE;
GICC_BPR = 7;
}
/* Sets an interrupt's group in the GICD. */
void intr_set_group(unsigned int id, int group) {
GICD_IGROUPR[id >> 5] = (GICD_IGROUPR[id >> 5] & (~(1 << (id & 0x1F)))) | ((group & 1) << (id & 0x1F));
}
/* Sets an interrupt id as pending in the GICD. */
void intr_set_pending(unsigned int id) {
GICD_ISPENDR[id >> 5] = 1 << (id & 0x1F);
}
/* Sets an interrupt's priority in the GICD. */
void intr_set_priority(unsigned int id, uint8_t priority) {
GICD_IPRIORITYR[id] = priority;
}
/* Sets an interrupt's target CPU mask in the GICD. */
void intr_set_cpu_mask(unsigned int id, uint8_t mask) {
GICD_ITARGETSR[id] = mask;
}
/* Sets an interrupt's edge/level bits in the GICD. */
void intr_set_edge_level(unsigned int id, int edge_level) {
GICD_ICFGR[id >> 4] = GICD_ICFGR[id >> 4] & ((~(3 << ((id & 0xF) << 1))) | (((edge_level & 1) << 1) << ((id & 0xF) << 1)));
}
/* Sets an interrupt's enabled status in the GICD. */
void intr_set_enabled(unsigned int id, int enabled) {
GICD_ISENABLER[id >> 5] = (enabled & 1) << (id & 0x1F);
}
/* To be called by FIQ handler. */
void handle_registered_interrupt(void) {
unsigned int interrupt_id = get_interrupt_id();
if (interrupt_id <= 0xDF) {
bool found_handler = false;
for (unsigned int i = 0; i < MAX_REGISTERED_INTERRUPTS; i++) {
if (g_registered_interrupts[i].id == interrupt_id) {
found_handler = true;
g_registered_interrupts[i].handler();
/* Mark that interrupt is done. */
GICC_EOIR = interrupt_id;
break;
}
}
/* We must have found a handler, or something went wrong. */
if (!found_handler) {
generic_panic();
}
}
}
/* Registers an interrupt into the global. */
void intr_register_handler(unsigned int id, void (*handler)(void)) {
bool registered_handler = false;
for (unsigned int i = 0; i < MAX_REGISTERED_INTERRUPTS; i++) {
if (g_registered_interrupts[i].id == 0) {
g_registered_interrupts[i].handler = handler;
g_registered_interrupts[i].id = id;
registered_handler = true;
break;
}
}
/* Failure to register is an error condition. */
if (!registered_handler) {
generic_panic();
}
}

50
exosphere/src/interrupt.h Normal file
View File

@@ -0,0 +1,50 @@
#ifndef EXOSPHERE_INTERRUPT_H
#define EXOSPHERE_INTERRUPT_H
#include <stdint.h>
#include "memory_map.h"
/* Exosphere driver for the Tegra X1 GIC-400 registers. */
#define MAX_REGISTERED_INTERRUPTS 4
#define INTERRUPT_ID_SECURITY_ENGINE 0x5A
#define INTERRUPT_ID_USER_SECURITY_ENGINE 0x2C
#define GICD_BASE (mmio_get_device_address(MMIO_DEVID_GICD))
#define GICC_BASE (mmio_get_device_address(MMIO_DEVID_GICC))
#define GICD_IGROUPR ((volatile uint32_t *)(GICD_BASE + 0x080ULL))
#define GICD_ISENABLER ((volatile uint32_t *)(GICD_BASE + 0x100ULL))
#define GICD_ISPENDR ((volatile uint32_t *)(GICD_BASE + 0x200ULL))
#define GICD_IPRIORITYR ((volatile uint8_t *)(GICD_BASE + 0x400ULL))
#define GICD_ITARGETSR ((volatile uint8_t *)(GICD_BASE + 0x800ULL))
#define GICD_ICFGR ((volatile uint32_t *)(GICD_BASE + 0xC00ULL))
#define GICC_CTLR (*((volatile uint32_t *)(GICC_BASE + 0x0000ULL)))
#define GICC_PMR (*((volatile uint32_t *)(GICC_BASE + 0x0004ULL)))
#define GICC_BPR (*((volatile uint32_t *)(GICC_BASE + 0x0008ULL)))
#define GICC_IAR (*((volatile uint32_t *)(GICC_BASE + 0x000CULL)))
#define GICC_EOIR (*((volatile uint32_t *)(GICC_BASE + 0x0010ULL)))
#define GIC_PRI_HIGHEST_SECURE 0x00
#define GIC_PRI_HIGHEST_NONSECURE 0x80
#define GIC_GROUP_SECURE 0
#define GIC_GROUP_NONSECURE 1
/* To be called by FIQ handler. */
void handle_registered_interrupt(void);
/* Initializes the GIC. TODO: This must be called during wakeup. */
void intr_initialize_gic(void);
void intr_register_handler(unsigned int id, void (*handler)(void));
void intr_set_group(unsigned int id, int group);
void intr_set_pending(unsigned int id);
void intr_set_priority(unsigned int id, uint8_t priority);
void intr_set_cpu_mask(unsigned int id, uint8_t mask);
void intr_set_edge_level(unsigned int id, int edge_level);
void intr_set_enabled(unsigned int id, int enabled);
#endif

25
exosphere/src/lock.h Normal file
View File

@@ -0,0 +1,25 @@
#ifndef EXOSPHERE_LOCK_H
#define EXOSPHERE_LOCK_H
#include <stdbool.h>
/* Simple atomics driver for Exosphere. */
/* Acquire a lock. */
static inline void lock_acquire(bool *l) {
while (__atomic_test_and_set(l, __ATOMIC_ACQUIRE)) {
/* Wait to acquire lock. */
}
}
/* Release a lock. */
static inline void lock_release(bool *l) {
__atomic_clear(l, __ATOMIC_RELEASE);
}
/* Try to acquire a lock. */
static inline bool lock_try_acquire(bool *l) {
return __atomic_test_and_set(l, __ATOMIC_ACQUIRE);
}
#endif

91
exosphere/src/masterkey.c Normal file
View File

@@ -0,0 +1,91 @@
#include <stdbool.h>
#include <stdint.h>
#include "utils.h"
#include "masterkey.h"
#include "se.h"
unsigned int g_mkey_revision = 0;
bool g_determined_mkey_revision = false;
uint8_t g_old_masterkeys[MASTERKEY_REVISION_MAX][0x10];
/* TODO: Dev keys. */
/* TODO: Extend with new vectors, as needed. */
const uint8_t mkey_vectors[MASTERKEY_REVISION_MAX][0x10] =
{
{0x0C, 0xF0, 0x59, 0xAC, 0x85, 0xF6, 0x26, 0x65, 0xE1, 0xE9, 0x19, 0x55, 0xE6, 0xF2, 0x67, 0x3D}, /* Zeroes encrypted with Master Key 00. */
{0x29, 0x4C, 0x04, 0xC8, 0xEB, 0x10, 0xED, 0x9D, 0x51, 0x64, 0x97, 0xFB, 0xF3, 0x4D, 0x50, 0xDD}, /* Master key 00 encrypted with Master key 01. */
{0xDE, 0xCF, 0xEB, 0xEB, 0x10, 0xAE, 0x74, 0xD8, 0xAD, 0x7C, 0xF4, 0x9E, 0x62, 0xE0, 0xE8, 0x72}, /* Master key 01 encrypted with Master key 02. */
{0x0A, 0x0D, 0xDF, 0x34, 0x22, 0x06, 0x6C, 0xA4, 0xE6, 0xB1, 0xEC, 0x71, 0x85, 0xCA, 0x4E, 0x07}, /* Master key 02 encrypted with Master key 03. */
};
bool check_mkey_revision(unsigned int revision) {
uint8_t final_vector[0x10];
unsigned int check_keyslot = KEYSLOT_SWITCH_MASTERKEY;
if (revision > 0) {
/* Generate old master key array. */
for (unsigned int i = revision; i > 0; i--) {
se_aes_ecb_decrypt_block(check_keyslot, g_old_masterkeys[i-1], 0x10, mkey_vectors[i], 0x10);
set_aes_keyslot(KEYSLOT_SWITCH_TEMPKEY, g_old_masterkeys[i-1], 0x10);
check_keyslot = KEYSLOT_SWITCH_TEMPKEY;
}
}
se_aes_ecb_decrypt_block(check_keyslot, final_vector, 0x10, mkey_vectors[0], 0x10);
for (unsigned int i = 0; i < 0x10; i++) {
if (final_vector[i] != 0) {
return false;
}
}
return true;
}
void mkey_detect_revision(void) {
if (g_determined_mkey_revision) {
generic_panic();
}
for (unsigned int rev = 0; rev < MASTERKEY_REVISION_MAX; rev++) {
if (check_mkey_revision(rev)) {
g_determined_mkey_revision = true;
g_mkey_revision = rev;
break;
}
}
/* We must have determined the master key, or we're not running on a Switch. */
/* TODO: When panic is implemented, make this a really distinctive color. */
/* Maybe bright red? */
if (!g_determined_mkey_revision) {
generic_panic();
}
}
unsigned int mkey_get_revision(void) {
if (!g_determined_mkey_revision) {
generic_panic();
}
return g_mkey_revision;
}
unsigned int mkey_get_keyslot(unsigned int revision) {
if (!g_determined_mkey_revision || revision >= MASTERKEY_REVISION_MAX) {
generic_panic();
}
if (revision > g_mkey_revision) {
generic_panic();
}
if (revision == g_mkey_revision) {
return KEYSLOT_SWITCH_MASTERKEY;
} else {
/* Load into a temp keyslot. */
set_aes_keyslot(KEYSLOT_SWITCH_TEMPKEY, g_old_masterkeys[revision], 0x10);
return KEYSLOT_SWITCH_TEMPKEY;
}
}

21
exosphere/src/masterkey.h Normal file
View File

@@ -0,0 +1,21 @@
#ifndef EXOSPHERE_MASTERKEY_H
#define EXOSPHERE_MASTERKEY_H
/* This is glue code to enable master key support across versions. */
/* TODO: Update to 0x5 on release of new master key. */
#define MASTERKEY_REVISION_MAX 0x4
#define MASTERKEY_REVISION_100_230 0x00
#define MASTERKEY_REVISION_300 0x01
#define MASTERKEY_REVISION_301_302 0x02
#define MASTERKEY_REVISION_400_CURRENT 0x03
/* This should be called early on in initialization. */
void mkey_detect_revision(void);
unsigned int mkey_get_revision(void);
unsigned int mkey_get_keyslot(unsigned int revision);
#endif

104
exosphere/src/mc.c Normal file
View File

@@ -0,0 +1,104 @@
#include <stdint.h>
#include "memory_map.h"
#include "mc.h"
volatile security_carveout_t *get_carveout_by_id(unsigned int carveout) {
if (CARVEOUT_ID_MIN <= carveout && carveout <= CARVEOUT_ID_MAX) {
return (volatile security_carveout_t *)(MC_BASE + 0xC08ULL + 0x50 * (carveout - CARVEOUT_ID_MIN));
}
generic_panic();
return NULL;
switch (carveout) {
case 4: /* Kernel carveout */
return (volatile security_carveout_t *)(MC_BASE + 0xCF8ULL);
case 5: /* Unused Kernel carveout */
return (volatile security_carveout_t *)(MC_BASE + 0xD48ULL);
default:
generic_panic();
return NULL;
}
}
void configure_default_carveouts(void) {
/* Configure Carveout 1 (UNUSED) */
volatile security_carveout_t *carveout = get_carveout_by_id(1);
carveout->paddr_low = 0;
carveout->paddr_high = 0;
carveout->size_big_pages = 0;
carveout->flags_0 = 0;
carveout->flags_1 = 0;
carveout->flags_2 = 0;
carveout->flags_3 = 0;
carveout->flags_4 = 0;
carveout->flags_5 = 0;
carveout->flags_6 = 0;
carveout->flags_7 = 0;
carveout->flags_8 = 0;
carveout->flags_9 = 0;
carveout->allowed_clients = 0x04000006;
/* Configure Carveout 2 (GPU UCODE) */
carveout = get_carveout_by_id(2);
carveout->paddr_low = 0x80020000;
carveout->paddr_high = 0;
carveout->size_big_pages = 2; /* 0x40000 */
carveout->flags_0 = 0;
carveout->flags_1 = 0;
carveout->flags_2 = 0x3000000;
carveout->flags_3 = 0;
carveout->flags_4 = 0x300;
carveout->flags_5 = 0;
carveout->flags_6 = 0;
carveout->flags_7 = 0;
carveout->flags_8 = 0;
carveout->flags_9 = 0;
carveout->allowed_clients = 0x440167E;
/* Configure Carveout 3 (UNUSED GPU) */
carveout = get_carveout_by_id(3);
carveout->paddr_low = 0;
carveout->paddr_high = 0;
carveout->size_big_pages = 0;
carveout->flags_0 = 0;
carveout->flags_1 = 0;
carveout->flags_2 = 0x3000000;
carveout->flags_3 = 0;
carveout->flags_4 = 0x300;
carveout->flags_5 = 0;
carveout->flags_6 = 0;
carveout->flags_7 = 0;
carveout->flags_8 = 0;
carveout->flags_9 = 0;
carveout->allowed_clients = 0x4401E7E;
/* Configure default Kernel carveouts based on 2.0.0+. */
/* Configure Carveout 4 (KERNEL_BUILTINS) */
configure_kernel_carveout(5, 0x80060000, KERNEL_CARVEOUT_SIZE_MAX);
/* Configure Carveout 5 (KERNEL_UNUSED) */
configure_kernel_carveout(5, 0, 0);
}
void configure_kernel_carveout(unsigned int carveout_id, uint64_t address, uint64_t size) {
if (carveout_id != 4 && carveout_id != 5) {
generic_panic();
}
volatile security_carveout_t *carveout = get_carveout_by_id(carveout_id);
carveout->paddr_low = (uint32_t)(address & 0xFFFFFFFF);
carveout->paddr_high = (uint32_t)(address >> 32);
carveout->size_big_pages = (uint32_t)(size >> 17);
carveout->flags_0 = 0x70E3407F;
carveout->flags_1 = 0x1A620880;
carveout->flags_2 = 0x303C00;
carveout->flags_3 = 0xCF0830BB;
carveout->flags_4 = 0x3;
carveout->flags_5 = 0;
carveout->flags_6 = 0;
carveout->flags_7 = 0;
carveout->flags_8 = 0;
carveout->flags_9 = 0;
carveout->allowed_clients = 0x8B;
}

40
exosphere/src/mc.h Normal file
View File

@@ -0,0 +1,40 @@
#ifndef EXOSPHERE_MC_H
#define EXOSPHERE_MC_H
#include <stdint.h>
#include "memory_map.h"
/* Exosphere driver for the Tegra X1 Memory Controller. */
#define MC_BASE (mmio_get_device_address(MMIO_DEVID_MC))
#define CARVEOUT_ID_MIN 1
#define CARVEOUT_ID_MAX 5
#define KERNEL_CARVEOUT_SIZE_MAX 0x1FFE0000
typedef struct {
uint32_t allowed_clients;
uint32_t paddr_low;
uint32_t paddr_high;
uint32_t size_big_pages;
uint32_t flags_0;
uint32_t flags_1;
uint32_t flags_2;
uint32_t flags_3;
uint32_t flags_4;
uint32_t flags_5;
uint32_t flags_6;
uint32_t flags_7;
uint32_t flags_8;
uint32_t flags_9;
uint8_t padding[0x28];
} security_carveout_t;
volatile security_carveout_t *get_carveout_by_id(unsigned int carveout);
void configure_default_carveouts(void);
void configure_kernel_carveout(unsigned int carveout_id, uint64_t address, uint64_t size);
#endif

310
exosphere/src/memory_map.h Normal file
View File

@@ -0,0 +1,310 @@
#ifndef EXOSPHERE_MEMORY_MAP_H
#define EXOSPHERE_MEMORY_MAP_H
#include "mmu.h"
#define ATTRIB_MEMTYPE_NORMAL MMU_PTE_BLOCK_MEMTYPE(0)
#define ATTRIB_MEMTYPE_DEVICE MMU_PTE_BLOCK_MEMTYPE(1)
static const struct {
uintptr_t address;
uint64_t size;
uint64_t attributes;
bool is_block_range;
} g_identity_mappings[] = {
{ 0x40020000, 0x20000, 0, false }, /* iRAM-C+D (contains the secmon's coldboot crt0) */
{ 0x7C010000, 0x10000, 0, false }, /* TZRAM (contains the secmon's warmboot crt0) */
{ 0x80000000, 4u << 30, MMU_PTE_BLOCK_XN | MMU_PTE_BLOCK_NS, true }, /* DRAM (4GB) */
};
static const struct {
uintptr_t pa;
size_t size;
bool is_secure;
} g_devices[] = {
{ 0x50041000, 0x1000, true }, /* ARM Interrupt Distributor */
{ 0x50042000, 0x2000, true }, /* Interrupt Controller Physical CPU interface */
{ 0x70006000, 0x1000, false }, /* UART-A */
{ 0x60006000, 0x1000, false }, /* Clock and Reset */
{ 0x7000E000, 0x1000, true }, /* RTC, PMC */
{ 0x60005000, 0x1000, true }, /* TMRs, WDTs */
{ 0x6000C000, 0x1000, true }, /* System Registers */
{ 0x70012000, 0x2000, true }, /* SE */
{ 0x700F0000, 0x1000, true }, /* SYSCTR0 */
{ 0x70019000, 0x1000, true }, /* MC */
{ 0x7000F000, 0x1000, true }, /* FUSE (0x7000F800) */
{ 0x70000000, 0x4000, true }, /* MISC */
{ 0x60007000, 0x1000, true }, /* Flow Controller */
{ 0x40002000, 0x1000, true }, /* NX bootloader mailbox page */
{ 0x7000D000, 0x1000, true }, /* I2C-5,6 - SPI 2B-1 to 4 */
{ 0x6000D000, 0x1000, true }, /* GPIO-1 - GPIO-8 */
{ 0x7000C000, 0x1000, true }, /* I2C-I2C4 */
{ 0x6000F000, 0x1000, true }, /* Exception vectors */
};
static const struct {
uintptr_t pa;
size_t size;
uint64_t attributes;
} g_lp0_entry_ram_segments[] = {
{ 0x40020000, 0x10000, MMU_PTE_BLOCK_NS | ATTRIB_MEMTYPE_DEVICE }, /* Encrypted TZRAM */
{ 0x40003000, 0x01000, MMU_PTE_BLOCK_NS | ATTRIB_MEMTYPE_DEVICE }, /* LP0 entry code */
{ 0x7C010000, 0x10000, MMU_AP_PRIV_RO | ATTRIB_MEMTYPE_NORMAL }, /* TZRAM to encrypt */
};
static const struct {
uintptr_t pa;
size_t size;
uint64_t attributes;
} g_warmboot_ram_segments[] = {
{ 0x8000F000, 0x01000, MMU_PTE_BLOCK_NS | ATTRIB_MEMTYPE_DEVICE }, /* Encrypted SE state for bootROM */
{ 0x80010000, 0x10000, MMU_PTE_BLOCK_NS | ATTRIB_MEMTYPE_DEVICE }, /* Encrypted TZRAM for warmboot.bin */
};
static const struct {
size_t tzram_offset;
size_t map_size;
size_t increment; /* for alignment, guard pages, etc. */
bool is_code_segment; /* note: code is RWX */
} g_tzram_segments[] = {
{ 0x3000, 0x10000 - 0x2000 - 0x3000, 0x10000, true }, /* Warmboot crt0 sections and main code segment */
{ 0x10000 - 0x2000, 0x2000, 0x04000, true }, /* pk2ldr segment */
{ 0, 0, 0x02000, false }, /* SPL .bss buffer, NOT mapped at startup */
{ 0x10000 - 0x2000, 0x1000, 0x02000, false }, /* Core 0,1,2 stack */
{ 0x10000 - 0x1000, 0x1000, 0x02000, false }, /* Core 3 stack */
{ 0, 0x1000, 0x02000, true }, /* Secure Monitor exception vectors, some init stacks */
{ 0x1000, 0x1000, 0x02000, false }, /* L2 translation table */
{ 0x2000, 0x1000, 0x02000, false }, /* L3 translation table */
};
#define MMIO_BASE 0x1F0080000ull
#define LP0_ENTRY_RAM_SEGMENT_BASE (MMIO_BASE + 0x000100000)
#define WARMBOOT_RAM_SEGMENT_BASE (LP0_ENTRY_RAM_SEGMENT_BASE + 0x000047000) /* increment seems to be arbitrary ? */
#define TZRAM_SEGMENT_BASE (MMIO_BASE + 0x0001E0000)
#define MMIO_DEVID_GICD 0
#define MMIO_DEVID_GICC 1
#define MMIO_DEVID_UART_A 2
#define MMIO_DEVID_CLKRST 3
#define MMIO_DEVID_RTC_PMC 4
#define MMIO_DEVID_TMRs_WDTs 5
#define MMIO_DEVID_SYSREGS 6
#define MMIO_DEVID_SE 7
#define MMIO_DEVID_SYSCTR0 8
#define MMIO_DEVID_MC 9
#define MMIO_DEVID_FUSE 10
#define MMIO_DEVID_MISC 11
#define MMIO_DEVID_FLOWCTRL 12
#define MMIO_DEVID_NXBOOTLOADER_MAILBOX 13
#define MMIO_DEVID_I2C56_SPI2B 14
#define MMIO_DEVID_GPIO 15
#define MMIO_DEVID_DTV_I2C234 16
#define MMIO_DEVID_EXCEPTION_VECTORS 17
#define LP0_ENTRY_RAM_SEGMENT_ID_DECRYPTED_TZRAM 0
#define LP0_ENTRY_RAM_SEGMENT_ID_LP0_ENTRY_CODE 1
#define LP0_ENTRY_RAM_SEGMENT_ID_CURRENT_TZRAM 2
#define WARMBOOT_RAM_SEGMENT_ID_SE_STATE 0
#define WARMBOOT_RAM_SEGMENT_ID_TZRAM 1
#define TZRAM_SEGMENT_ID_WARMBOOT_CRT0_AND_MAIN 0
#define TZRAM_SEGMENT_ID_PK2LDR 1
#define TZRAM_SEGMENT_ID_USERPAGE 2
#define TZRAM_SEGMENT_ID_CORE012_STACK 3
#define TZRAM_SEGMENT_ID_CORE3_STACK 4
#define TZRAM_SEGEMENT_ID_SECMON_EVT 5
#define TZRAM_SEGMENT_ID_L2_TRANSLATION_TABLE 6
#define TZRAM_SEGMENT_ID_L3_TRANSLATION_TABLE 7
/**********************************************************************************************/
static inline void identity_map_all_mappings(uintptr_t *mmu_l1_tbl, uintptr_t *mmu_l3_tbl) {
static uint64_t base_attributes = MMU_PTE_BLOCK_INNER_SHAREBLE | ATTRIB_MEMTYPE_NORMAL;
for(size_t i = 0; i < sizeof(g_identity_mappings) / sizeof(g_identity_mappings[0]); i++) {
uint64_t attributes = base_attributes | g_identity_mappings[i].attributes;
if(g_identity_mappings[i].is_block_range) {
mmu_map_block_range(1, mmu_l1_tbl, g_identity_mappings[i].address, g_identity_mappings[i].address,
g_identity_mappings[i].size, attributes);
}
else {
mmu_map_page_range(mmu_l3_tbl, g_identity_mappings[i].address, g_identity_mappings[i].address,
g_identity_mappings[i].size, attributes);
}
}
}
static inline void identity_unmap_all_mappings(uintptr_t *mmu_l1_tbl, uintptr_t *mmu_l3_tbl) {
for(size_t i = 0; i < sizeof(g_identity_mappings) / sizeof(g_identity_mappings[0]); i++) {
if(g_identity_mappings[i].is_block_range) {
mmu_unmap_range(1, mmu_l1_tbl, g_identity_mappings[i].address, g_identity_mappings[i].size);
}
else {
mmu_unmap_range(3, mmu_l3_tbl, g_identity_mappings[i].address, g_identity_mappings[i].size);
}
}
}
/**********************************************************************************************/
static inline uintptr_t mmio_get_device_pa(unsigned int device_id) {
return g_devices[device_id].pa;
}
#ifndef MEMORY_MAP_USE_IDENTIY_MAPPING
static inline uintptr_t mmio_get_device_address(unsigned int device_id) {
size_t offset = 0;
for(unsigned int i = 0; i < device_id; i++) {
offset += g_devices[i].size;
offset += 0x1000; /* guard page */
}
return MMIO_BASE + offset;
}
#else
static inline uintptr_t mmio_get_device_address(unsigned int device_id) {
return mmio_get_device_pa(device_id);
}
#endif
static inline void mmio_map_all_devices(uintptr_t *mmu_l3_tbl) {
static const uint64_t secure_device_attributes = MMU_PTE_BLOCK_XN | MMU_PTE_BLOCK_INNER_SHAREBLE | ATTRIB_MEMTYPE_DEVICE;
static const uint64_t device_attributes = MMU_PTE_BLOCK_NS | secure_device_attributes;
for(size_t i = 0, offset = 0; i < sizeof(g_devices) / sizeof(g_devices[0]); i++) {
uint64_t attributes = g_devices[i].is_secure ? secure_device_attributes : device_attributes;
mmu_map_page_range(mmu_l3_tbl, MMIO_BASE + offset, g_devices[i].pa, g_devices[i].size, attributes);
offset += g_devices[i].size;
offset += 0x1000; /* insert guard page */
}
}
static inline void mmio_unmap_all_devices(uintptr_t *mmu_l3_tbl) {
for(size_t i = 0, offset = 0; i < sizeof(g_devices) / sizeof(g_devices[0]); i++) {
mmu_unmap_range(3, mmu_l3_tbl, MMIO_BASE + offset, g_devices[i].size);
offset += g_devices[i].size;
offset += 0x1000; /* insert guard page */
}
}
/**********************************************************************************************/
static inline uintptr_t lp0_get_plaintext_ram_segment_pa(unsigned int segment_id) {
return g_lp0_entry_ram_segments[segment_id].pa;
}
#ifndef MEMORY_MAP_USE_IDENTIY_MAPPING
static inline uintptr_t lp0_get_plaintext_ram_segment_address(unsigned int segment_id) {
return LP0_ENTRY_RAM_SEGMENT_BASE + 0x10000 * segment_id;
}
#else
static inline uintptr_t lp0_get_plaintext_ram_segment_address(unsigned int segment_id) {
return lp0_get_plaintext_ram_segment_pa(segment_id);
}
#endif
static inline void lp0_map_all_plaintext_ram_segments(uintptr_t *mmu_l3_tbl) {
for(size_t i = 0, offset = 0; i < sizeof(g_lp0_entry_ram_segments) / sizeof(g_lp0_entry_ram_segments[0]); i++) {
uint64_t attributes = MMU_PTE_BLOCK_XN | MMU_PTE_BLOCK_INNER_SHAREBLE | g_lp0_entry_ram_segments[i].attributes;
mmu_map_page_range(mmu_l3_tbl, LP0_ENTRY_RAM_SEGMENT_BASE + offset, g_lp0_entry_ram_segments[i].pa,
g_lp0_entry_ram_segments[i].size, attributes);
offset += 0x10000;
}
}
static inline void lp0_unmap_all_plaintext_ram_segments(uintptr_t *mmu_l3_tbl) {
for(size_t i = 0, offset = 0; i < sizeof(g_lp0_entry_ram_segments) / sizeof(g_lp0_entry_ram_segments[0]); i++) {
mmu_unmap_range(3, mmu_l3_tbl, LP0_ENTRY_RAM_SEGMENT_BASE + offset, g_lp0_entry_ram_segments[i].size);
offset += 0x10000;
}
}
/**********************************************************************************************/
static inline uintptr_t lp0_get_ciphertext_ram_segment_pa(unsigned int segment_id) {
return g_warmboot_ram_segments[segment_id].pa;
}
#ifndef MEMORY_MAP_USE_IDENTIY_MAPPING
static inline uintptr_t lp0_get_ciphertext_ram_segment_address(unsigned int segment_id) {
size_t offset = 0;
for(unsigned int i = 0; i < segment_id; i++) {
offset += g_warmboot_ram_segments[i].size;
}
return WARMBOOT_RAM_SEGMENT_BASE + offset;
}
#else
static inline uintptr_t lp0_get_ciphertext_ram_segment_address(unsigned int segment_id) {
return lp0_get_ciphertext_ram_segment_pa(segment_id);
}
#endif
static inline void lp0_map_all_ciphertext_ram_segments(uintptr_t *mmu_l3_tbl) {
for(size_t i = 0, offset = 0; i < sizeof(g_warmboot_ram_segments) / sizeof(g_warmboot_ram_segments[0]); i++) {
uint64_t attributes = MMU_PTE_BLOCK_XN | MMU_PTE_BLOCK_INNER_SHAREBLE | g_warmboot_ram_segments[i].attributes;
mmu_map_page_range(mmu_l3_tbl, WARMBOOT_RAM_SEGMENT_BASE + offset, g_warmboot_ram_segments[i].pa,
g_warmboot_ram_segments[i].size, attributes);
offset += g_warmboot_ram_segments[i].size;
}
}
static inline void lp0_unmap_all_ciphertext_ram_segments(uintptr_t *mmu_l3_tbl) {
for(size_t i = 0, offset = 0; i < sizeof(g_warmboot_ram_segments) / sizeof(g_warmboot_ram_segments[0]); i++) {
mmu_unmap_range(3, mmu_l3_tbl, WARMBOOT_RAM_SEGMENT_BASE + offset, g_warmboot_ram_segments[i].size);
offset += g_warmboot_ram_segments[i].size;
}
}
/**********************************************************************************************/
static inline uintptr_t tzram_get_segment_pa(unsigned int segment_id) {
return 0x7C010000 + g_tzram_segments[segment_id].tzram_offset;
}
#ifndef MEMORY_MAP_USE_IDENTIY_MAPPING
static inline uintptr_t tzram_get_segment_address(unsigned int segment_id) {
size_t offset = 0;
for(unsigned int i = 0; i < segment_id; i++) {
offset += g_tzram_segments[i].increment;
}
return TZRAM_SEGMENT_BASE + offset;
}
#else
static inline uintptr_t tzram_get_segment_address(unsigned int segment_id) {
return tzram_get_segment_pa(segment_id);
}
#endif
static inline void tzram_map_all_segments(uintptr_t *mmu_l3_tbl) {
/* Except the SPL userpage */
for(size_t i = 0, offset = 0; i < sizeof(g_tzram_segments) / sizeof(g_tzram_segments[0]); i++) {
uint64_t attributes = (g_tzram_segments[i].is_code_segment ? 0 : MMU_PTE_BLOCK_XN) | MMU_PTE_BLOCK_INNER_SHAREBLE | ATTRIB_MEMTYPE_NORMAL;
if(g_tzram_segments[i].map_size == 0) {
continue;
}
mmu_map_page_range(mmu_l3_tbl, TZRAM_SEGMENT_BASE + offset, 0x7C010000 + g_tzram_segments[i].tzram_offset,
g_tzram_segments[i].map_size, attributes);
offset += g_tzram_segments[i].increment;
}
}
static inline void tzram_unmap_all_segments(uintptr_t *mmu_l3_tbl) {
/* Except the SPL userpage */
for(size_t i = 0, offset = 0; i < sizeof(g_warmboot_ram_segments) / sizeof(g_warmboot_ram_segments[0]); i++) {
if(g_tzram_segments[i].map_size == 0) {
continue;
}
mmu_unmap_range(3, mmu_l3_tbl, TZRAM_SEGMENT_BASE + offset, g_tzram_segments[i].map_size);
offset += g_tzram_segments[i].increment;
}
}
#endif

173
exosphere/src/mmu.h Normal file
View File

@@ -0,0 +1,173 @@
#ifndef EXOSPHERE_MMU_H
#define EXOSPHERE_MMU_H
#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>
#include "utils.h"
#ifndef MMU_GRANULE_TYPE
#define MMU_GRANULE_TYPE 0 /* 0: 4KB, 1: 64KB, 2: 16KB. The Switch always uses a 4KB granule size. */
#endif
#if MMU_GRANULE_TYPE == 0
#define MMU_Lx_SHIFT(x) (12 + 9 * (3 - (x)))
#define MMU_Lx_MASK(x) (BIT(9) - 1)
#elif MMU_GRANULE_TYPE == 1
/* 64 KB, no L0 here */
#define MMU_Lx_SHIFT(x) (16 + 13 * (3 - (x)))
#define MMU_Lx_MASK(x) ((x) == 1 ? (BIT(5) - 1) : (BIT(13) - 1))
#elif MMU_GRANULE_TYPE == 2
#define MMU_Lx_SHIFT(x) (14 + 11 * (3 - (x)))
#define MMU_Lx_MASK(x) ((x) == 0 ? 1 : (BIT(11) - 1))
#endif
/*
* The following defines are adapted from uboot:
*
* (C) Copyright 2013
* David Feng <fenghua@phytium.com.cn>
*
* SPDX-License-Identifier: GPL-2.0+
*/
#define MMU_MT_DEVICE_NGNRNE 0
#define MMU_MT_DEVICE_NGNRE 1
#define MMU_MT_DEVICE_GRE 2
#define MMU_MT_NORMAL_NC 3
#define MMU_MT_NORMAL 4
/*
* Hardware page table definitions.
*
*/
#define MMU_PTE_TYPE_MASK 3
#define MMU_PTE_TYPE_FAULT 0
#define MMU_PTE_TYPE_TABLE 3
#define MMU_PTE_TYPE_BLOCK 1
/* L3 only */
#define MMU_PTE_TYPE_PAGE 3
#define MMU_PTE_TABLE_PXN BITL(59)
#define MMU_PTE_TABLE_XN BITL(60)
#define MMU_PTE_TABLE_AP BITL(61)
#define MMU_PTE_TABLE_NS BITL(63)
/*
* Block
*/
#define MMU_PTE_BLOCK_MEMTYPE(x) ((x) << 2)
#define MMU_PTE_BLOCK_NS BIT(5)
#define MMU_PTE_BLOCK_NON_SHAREABLE (0 << 8)
#define MMU_PTE_BLOCK_OUTER_SHAREABLE (2 << 8)
#define MMU_PTE_BLOCK_INNER_SHAREBLE (3 << 8)
#define MMU_PTE_BLOCK_AF BIT(10)
#define MMU_PTE_BLOCK_NG BIT(11)
#define MMU_PTE_BLOCK_PXN BITL(53)
#define MMU_PTE_BLOCK_UXN BITL(54)
#define MMU_PTE_BLOCK_XN MMU_PTE_BLOCK_UXN
/*
* AP[2:1]
*/
#define MMU_AP_PRIV_RW (0 << 6)
#define MMU_AP_RW (1 << 6)
#define MMU_AP_PRIV_RO (2 << 6)
#define MMU_AP_RO (3 << 6)
/*
* S2AP[2:1] (for stage2 translations; secmon doesn't use it)
*/
#define MMU_S2AP_NONE (0 << 6)
#define MMU_S2AP_RO (1 << 6)
#define MMU_S2AP_WO (2 << 6)
#define MMU_S2AP_RW (3 << 6)
/*
* AttrIndx[2:0]
*/
#define MMU_PMD_ATTRINDX(t) ((t) << 2)
#define MMU_PMD_ATTRINDX_MASK (7 << 2)
/*
* TCR flags.
*/
#define TCR_T0SZ(x) ((64 - (x)) << 0)
#define TCR_IRGN_NC (0 << 8)
#define TCR_IRGN_WBWA (1 << 8)
#define TCR_IRGN_WT (2 << 8)
#define TCR_IRGN_WBNWA (3 << 8)
#define TCR_IRGN_MASK (3 << 8)
#define TCR_ORGN_NC (0 << 10)
#define TCR_ORGN_WBWA (1 << 10)
#define TCR_ORGN_WT (2 << 10)
#define TCR_ORGN_WBNWA (3 << 10)
#define TCR_ORGN_MASK (3 << 10)
#define TCR_NOT_SHARED (0 << 12)
#define TCR_SHARED_OUTER (2 << 12)
#define TCR_SHARED_INNER (3 << 12)
#define TCR_TG0_4K (0 << 14)
#define TCR_TG0_64K (1 << 14)
#define TCR_TG0_16K (2 << 14)
#define TCR_EPD1_DISABLE BIT(23)
#define TCR_EL1_RSVD BIT(31)
#define TCR_EL2_RSVD (BIT(31) | BIT(23))
#define TCR_EL3_RSVD (BIT(31) | BIT(23))
static inline void mmu_init_table(uintptr_t *tbl, size_t num_entries) {
for(size_t i = 0; i < num_entries; i++) {
tbl[i] = MMU_PTE_TYPE_FAULT;
}
}
/*
All the functions below assume base_addr is valid.
They do not invalidate the TLB, which must be done separately.
*/
static inline unsigned int mmu_compute_index(unsigned int level, uintptr_t base_addr) {
return (base_addr >> MMU_Lx_SHIFT(level)) & MMU_Lx_MASK(level);
}
static inline void mmu_map_table(unsigned int level, uintptr_t *tbl, uintptr_t base_addr, uintptr_t *next_lvl_tbl_pa, uint64_t attrs) {
tbl[mmu_compute_index(level, base_addr)] = (uintptr_t)next_lvl_tbl_pa | attrs | MMU_PTE_TYPE_TABLE;
}
static inline void mmu_map_block(unsigned int level, uintptr_t *tbl, uintptr_t base_addr, uintptr_t phys_addr, uint64_t attrs) {
tbl[mmu_compute_index(level, base_addr)] = phys_addr | attrs | MMU_PTE_BLOCK_AF | MMU_PTE_TYPE_BLOCK;
}
static inline void mmu_map_page(uintptr_t *tbl, uintptr_t base_addr, uintptr_t phys_addr, uint64_t attrs) {
tbl[mmu_compute_index(3, base_addr)] = phys_addr | attrs | MMU_PTE_BLOCK_AF | MMU_PTE_TYPE_PAGE;
}
static inline void mmu_unmap(unsigned int level, uintptr_t *tbl, uintptr_t base_addr) {
tbl[mmu_compute_index(level, base_addr)] = MMU_PTE_TYPE_FAULT;
}
static inline void mmu_map_block_range(unsigned int level, uintptr_t *tbl, uintptr_t base_addr, uintptr_t phys_addr, size_t size, uint64_t attrs) {
size = ((size + (BITL(MMU_Lx_SHIFT(level)) - 1)) >> MMU_Lx_SHIFT(level)) << MMU_Lx_SHIFT(level);
for(size_t offset = 0; offset < size; offset += MMU_Lx_SHIFT(level)) {
mmu_map_block(level, tbl, base_addr + offset, phys_addr + offset, attrs);
}
}
static inline void mmu_map_page_range(uintptr_t *tbl, uintptr_t base_addr, uintptr_t phys_addr, size_t size, uint64_t attrs) {
size = ((size + (BITL(MMU_Lx_SHIFT(3)) - 1)) >> MMU_Lx_SHIFT(3)) << MMU_Lx_SHIFT(3);
for(size_t offset = 0; offset < size; offset += MMU_Lx_SHIFT(3)) {
mmu_map_page(tbl, base_addr + offset, phys_addr + offset, attrs);
}
}
static inline void mmu_unmap_range(unsigned int level, uintptr_t *tbl, uintptr_t base_addr, size_t size) {
size = ((size + (BITL(MMU_Lx_SHIFT(level)) - 1)) >> MMU_Lx_SHIFT(level)) << MMU_Lx_SHIFT(level);
for(size_t offset = 0; offset < size; offset += MMU_Lx_SHIFT(level)) {
mmu_unmap(level, tbl, base_addr + offset);
}
}
#endif

451
exosphere/src/package2.c Normal file
View File

@@ -0,0 +1,451 @@
#include <string.h>
#include "utils.h"
#include "memory_map.h"
#include "cpu_context.h"
#include "package2.h"
#include "configitem.h"
#include "se.h"
#include "masterkey.h"
#include "cache.h"
#include "randomcache.h"
#include "timers.h"
/* Hardware init, sets up the RNG and SESSION keyslots, derives new DEVICE key. */
static void setup_se(void) {
uint8_t work_buffer[0x10];
/* Sanity check the Security Engine. */
se_verify_flags_cleared();
se_clear_interrupts();
/* Perform some sanity initialization. */
volatile security_engine_t *p_security_engine = get_security_engine_address();
p_security_engine->_0x4 = 0;
p_security_engine->AES_KEY_READ_DISABLE_REG = 0;
p_security_engine->RSA_KEY_READ_DISABLE_REG = 0;
p_security_engine->_0x0 &= 0xFFFFFFFB;
/* Currently unknown what each flag does. */
for (unsigned int i = 0; i < KEYSLOT_AES_MAX; i++) {
set_aes_keyslot_flags(i, 0x15);
}
for (unsigned int i = 4; i < KEYSLOT_AES_MAX; i++) {
set_aes_keyslot_flags(i, 0x40);
}
for (unsigned int i = 0; i < KEYSLOT_RSA_MAX; i++) {
set_rsa_keyslot_flags(i, 0x41);
}
/* Detect Master Key revision. */
mkey_detect_revision();
/* Setup new device key, if necessary. */
if (mkey_get_revision() >= MASTERKEY_REVISION_400_CURRENT) {
const uint8_t new_devicekey_source_4x[0x10] = {0x8B, 0x4E, 0x1C, 0x22, 0x42, 0x07, 0xC8, 0x73, 0x56, 0x94, 0x08, 0x8B, 0xCC, 0x47, 0x0F, 0x5D};
se_aes_ecb_decrypt_block(KEYSLOT_SWITCH_4XNEWDEVICEKEYGENKEY, work_buffer, 0x10, new_devicekey_source_4x, 0x10);
decrypt_data_into_keyslot(KEYSLOT_SWITCH_DEVICEKEY, KEYSLOT_SWITCH_4XNEWCONSOLEKEYGENKEY, work_buffer, 0x10);
clear_aes_keyslot(KEYSLOT_SWITCH_4XNEWCONSOLEKEYGENKEY);
set_aes_keyslot_flags(KEYSLOT_SWITCH_DEVICEKEY, 0xFF);
}
se_initialize_rng(KEYSLOT_SWITCH_DEVICEKEY);
/* Generate random data, transform with device key to get RNG key. */
se_generate_random(KEYSLOT_SWITCH_DEVICEKEY, work_buffer, 0x10);
decrypt_data_into_keyslot(KEYSLOT_SWITCH_RNGKEY, KEYSLOT_SWITCH_DEVICEKEY, work_buffer, 0x10);
set_aes_keyslot_flags(KEYSLOT_SWITCH_RNGKEY, 0xFF);
/* Repeat for Session key. */
se_generate_random(KEYSLOT_SWITCH_DEVICEKEY, work_buffer, 0x10);
decrypt_data_into_keyslot(KEYSLOT_SWITCH_SESSIONKEY, KEYSLOT_SWITCH_DEVICEKEY, work_buffer, 0x10);
set_aes_keyslot_flags(KEYSLOT_SWITCH_SESSIONKEY, 0xFF);
/* TODO: Create Test Vector, to validate keyslot data is unchanged post warmboot. */
}
static void setup_boot_config(void) {
/* Load boot config only if dev unit. */
if (configitem_is_retail()) {
bootconfig_clear();
} else {
flush_dcache_range((uint8_t *)NX_BOOTLOADER_BOOTCONFIG_POINTER, (uint8_t *)NX_BOOTLOADER_BOOTCONFIG_POINTER + sizeof(bootconfig_t));
bootconfig_load_and_verify((bootconfig_t *)NX_BOOTLOADER_BOOTCONFIG_POINTER);
}
}
static bool rsa2048_pss_verify(const void *signature, size_t signature_size, const void *modulus, size_t modulus_size, const void *data, size_t data_size) {
uint8_t message[RSA_2048_BYTES];
uint8_t h_buf[0x24];
/* Hardcode RSA with keyslot 0. */
const uint8_t public_exponent[4] = {0x00, 0x01, 0x00, 0x01};
set_rsa_keyslot(0, modulus, modulus_size, public_exponent, sizeof(public_exponent));
se_synchronous_exp_mod(0, message, sizeof(message), signature, signature_size);
/* Validate sanity byte. */
if (message[RSA_2048_BYTES - 1] != 0xBC) {
return false;
}
/* Copy Salt into MGF1 Hash Buffer. */
memset(h_buf, 0, sizeof(h_buf));
memcpy(h_buf, message + RSA_2048_BYTES - 0x20 - 0x1, 0x20);
/* Decrypt maskedDB (via inline MGF1). */
uint8_t seed = 0;
uint8_t mgf1_buf[0x20];
for (unsigned int ofs = 0; ofs < RSA_2048_BYTES - 0x20 - 1; ofs += 0x20) {
h_buf[sizeof(h_buf) - 1] = seed++;
flush_dcache_range(h_buf, h_buf + sizeof(h_buf));
se_calculate_sha256(mgf1_buf, h_buf, sizeof(h_buf));
for (unsigned int i = ofs; i < ofs + 0x20 && i < RSA_2048_BYTES - 0x20 - 1; i++) {
message[i] ^= mgf1_buf[i - ofs];
}
}
/* Constant lmask for rsa-2048-pss. */
message[0] &= 0x7F;
/* Validate DB is of the form 0000...0001. */
for (unsigned int i = 0; i < RSA_2048_BYTES - 0x20 - 0x20 - 1 - 1; i++) {
if (message[i] != 0) {
return false;
}
}
if (message[RSA_2048_BYTES - 0x20 - 0x20 - 1 - 1] != 1) {
return false;
}
/* Check hash correctness. */
uint8_t validate_buf[8 + 0x20 + 0x20];
uint8_t validate_hash[0x20];
memset(validate_buf, 0, sizeof(validate_buf));
flush_dcache_range((uint8_t *)data, (uint8_t *)data + data_size);
se_calculate_sha256(&validate_buf[8], data, data_size);
memcpy(&validate_buf[0x28], &message[RSA_2048_BYTES - 0x20 - 0x20 - 1], 0x20);
flush_dcache_range(validate_buf, validate_buf + sizeof(validate_buf));
se_calculate_sha256(validate_hash, validate_buf, sizeof(validate_buf));
return memcmp(h_buf, validate_hash, 0x20) == 0;
}
static void package2_crypt_ctr(unsigned int master_key_rev, void *dst, size_t dst_size, const void *src, size_t src_size, const void *ctr, size_t ctr_size) {
/* Derive package2 key. */
const uint8_t package2_key_source[0x10] = {0xFB, 0x8B, 0x6A, 0x9C, 0x79, 0x00, 0xC8, 0x49, 0xEF, 0xD2, 0x4D, 0x85, 0x4D, 0x30, 0xA0, 0xC7};
flush_dcache_range((uint8_t *)dst, (uint8_t *)dst + dst_size);
flush_dcache_range((uint8_t *)src, (uint8_t *)src + src_size);
unsigned int keyslot = mkey_get_keyslot(master_key_rev);
decrypt_data_into_keyslot(KEYSLOT_SWITCH_PACKAGE2KEY, keyslot, package2_key_source, 0x10);
/* Perform Encryption. */
se_aes_ctr_crypt(KEYSLOT_SWITCH_PACKAGE2KEY, dst, dst_size, src, src_size, ctr, ctr_size);
}
static void verify_header_signature(package2_header_t *header) {
const uint8_t *modulus;
if (configitem_is_retail()) {
const uint8_t package2_modulus_retail[0x100] = {
0x8D, 0x13, 0xA7, 0x77, 0x6A, 0xE5, 0xDC, 0xC0, 0x3B, 0x25, 0xD0, 0x58, 0xE4, 0x20, 0x69, 0x59,
0x55, 0x4B, 0xAB, 0x70, 0x40, 0x08, 0x28, 0x07, 0xA8, 0xA7, 0xFD, 0x0F, 0x31, 0x2E, 0x11, 0xFE,
0x47, 0xA0, 0xF9, 0x9D, 0xDF, 0x80, 0xDB, 0x86, 0x5A, 0x27, 0x89, 0xCD, 0x97, 0x6C, 0x85, 0xC5,
0x6C, 0x39, 0x7F, 0x41, 0xF2, 0xFF, 0x24, 0x20, 0xC3, 0x95, 0xA6, 0xF7, 0x9D, 0x4A, 0x45, 0x74,
0x8B, 0x5D, 0x28, 0x8A, 0xC6, 0x99, 0x35, 0x68, 0x85, 0xA5, 0x64, 0x32, 0x80, 0x9F, 0xD3, 0x48,
0x39, 0xA2, 0x1D, 0x24, 0x67, 0x69, 0xDF, 0x75, 0xAC, 0x12, 0xB5, 0xBD, 0xC3, 0x29, 0x90, 0xBE,
0x37, 0xE4, 0xA0, 0x80, 0x9A, 0xBE, 0x36, 0xBF, 0x1F, 0x2C, 0xAB, 0x2B, 0xAD, 0xF5, 0x97, 0x32,
0x9A, 0x42, 0x9D, 0x09, 0x8B, 0x08, 0xF0, 0x63, 0x47, 0xA3, 0xE9, 0x1B, 0x36, 0xD8, 0x2D, 0x8A,
0xD7, 0xE1, 0x54, 0x11, 0x95, 0xE4, 0x45, 0x88, 0x69, 0x8A, 0x2B, 0x35, 0xCE, 0xD0, 0xA5, 0x0B,
0xD5, 0x5D, 0xAC, 0xDB, 0xAF, 0x11, 0x4D, 0xCA, 0xB8, 0x1E, 0xE7, 0x01, 0x9E, 0xF4, 0x46, 0xA3,
0x8A, 0x94, 0x6D, 0x76, 0xBD, 0x8A, 0xC8, 0x3B, 0xD2, 0x31, 0x58, 0x0C, 0x79, 0xA8, 0x26, 0xE9,
0xD1, 0x79, 0x9C, 0xCB, 0xD4, 0x2B, 0x6A, 0x4F, 0xC6, 0xCC, 0xCF, 0x90, 0xA7, 0xB9, 0x98, 0x47,
0xFD, 0xFA, 0x4C, 0x6C, 0x6F, 0x81, 0x87, 0x3B, 0xCA, 0xB8, 0x50, 0xF6, 0x3E, 0x39, 0x5D, 0x4D,
0x97, 0x3F, 0x0F, 0x35, 0x39, 0x53, 0xFB, 0xFA, 0xCD, 0xAB, 0xA8, 0x7A, 0x62, 0x9A, 0x3F, 0xF2,
0x09, 0x27, 0x96, 0x3F, 0x07, 0x9A, 0x91, 0xF7, 0x16, 0xBF, 0xC6, 0x3A, 0x82, 0x5A, 0x4B, 0xCF,
0x49, 0x50, 0x95, 0x8C, 0x55, 0x80, 0x7E, 0x39, 0xB1, 0x48, 0x05, 0x1E, 0x21, 0xC7, 0x24, 0x4F
};
modulus = package2_modulus_retail;
} else {
const uint8_t package2_modulus_dev[0x100] = {
0xB3, 0x65, 0x54, 0xFB, 0x0A, 0xB0, 0x1E, 0x85, 0xA7, 0xF6, 0xCF, 0x91, 0x8E, 0xBA, 0x96, 0x99,
0x0D, 0x8B, 0x91, 0x69, 0x2A, 0xEE, 0x01, 0x20, 0x4F, 0x34, 0x5C, 0x2C, 0x4F, 0x4E, 0x37, 0xC7,
0xF1, 0x0B, 0xD4, 0xCD, 0xA1, 0x7F, 0x93, 0xF1, 0x33, 0x59, 0xCE, 0xB1, 0xE9, 0xDD, 0x26, 0xE6,
0xF3, 0xBB, 0x77, 0x87, 0x46, 0x7A, 0xD6, 0x4E, 0x47, 0x4A, 0xD1, 0x41, 0xB7, 0x79, 0x4A, 0x38,
0x06, 0x6E, 0xCF, 0x61, 0x8F, 0xCD, 0xC1, 0x40, 0x0B, 0xFA, 0x26, 0xDC, 0xC0, 0x34, 0x51, 0x83,
0xD9, 0x3B, 0x11, 0x54, 0x3B, 0x96, 0x27, 0x32, 0x9A, 0x95, 0xBE, 0x1E, 0x68, 0x11, 0x50, 0xA0,
0x6B, 0x10, 0xA8, 0x83, 0x8B, 0xF5, 0xFC, 0xBC, 0x90, 0x84, 0x7A, 0x5A, 0x5C, 0x43, 0x52, 0xE6,
0xC8, 0x26, 0xE9, 0xFE, 0x06, 0xA0, 0x8B, 0x53, 0x0F, 0xAF, 0x1E, 0xC4, 0x1C, 0x0B, 0xCF, 0x50,
0x1A, 0xA4, 0xF3, 0x5C, 0xFB, 0xF0, 0x97, 0xE4, 0xDE, 0x32, 0x0A, 0x9F, 0xE3, 0x5A, 0xAA, 0xB7,
0x44, 0x7F, 0x5C, 0x33, 0x60, 0xB9, 0x0F, 0x22, 0x2D, 0x33, 0x2A, 0xE9, 0x69, 0x79, 0x31, 0x42,
0x8F, 0xE4, 0x3A, 0x13, 0x8B, 0xE7, 0x26, 0xBD, 0x08, 0x87, 0x6C, 0xA6, 0xF2, 0x73, 0xF6, 0x8E,
0xA7, 0xF2, 0xFE, 0xFB, 0x6C, 0x28, 0x66, 0x0D, 0xBD, 0xD7, 0xEB, 0x42, 0xA8, 0x78, 0xE6, 0xB8,
0x6B, 0xAE, 0xC7, 0xA9, 0xE2, 0x40, 0x6E, 0x89, 0x20, 0x82, 0x25, 0x8E, 0x3C, 0x6A, 0x60, 0xD7,
0xF3, 0x56, 0x8E, 0xEC, 0x8D, 0x51, 0x8A, 0x63, 0x3C, 0x04, 0x78, 0x23, 0x0E, 0x90, 0x0C, 0xB4,
0xE7, 0x86, 0x3B, 0x4F, 0x8E, 0x13, 0x09, 0x47, 0x32, 0x0E, 0x04, 0xB8, 0x4D, 0x5B, 0xB0, 0x46,
0x71, 0xB0, 0x5C, 0xF4, 0xAD, 0x63, 0x4F, 0xC5, 0xE2, 0xAC, 0x1E, 0xC4, 0x33, 0x96, 0x09, 0x7B
};
modulus = package2_modulus_dev;
}
/* This is normally only allowed on dev units, but we'll allow it anywhere. */
if (bootconfig_is_package2_unsigned() == 0 && rsa2048_pss_verify(header->signature, 0x100, modulus, 0x100, header->encrypted_header, 0x100) == 0) {
generic_panic();
}
}
static bool validate_package2_metadata(package2_meta_t *metadata) {
if (metadata->magic != MAGIC_PK21) {
return false;
}
/* Package2 size, version number is stored XORed in header CTR. */
/* Nintendo, what the fuck? */
uint32_t package_size = metadata->ctr_dwords[0] ^ metadata->ctr_dwords[2] ^ metadata->ctr_dwords[3];
uint8_t header_version = (uint8_t)((metadata->ctr_dwords[1] ^ (metadata->ctr_dwords[1] >> 16) ^ (metadata->ctr_dwords[1] >> 24)) & 0xFF);
/* Ensure package isn't too big or too small. */
if (package_size <= sizeof(package2_header_t) || package_size > PACKAGE2_SIZE_MAX - sizeof(package2_header_t)) {
return false;
}
/* Validate that we're working with a header we know how to handle. */
if (header_version > MASTERKEY_REVISION_MAX) {
return false;
}
/* Require aligned entrypoint. */
if (metadata->entrypoint & 3) {
return false;
}
/* Validate section size sanity. */
if (metadata->section_sizes[0] + metadata->section_sizes[1] + metadata->section_sizes[2] + sizeof(package2_header_t) != package_size) {
return false;
}
bool entrypoint_found = false;
/* Header has space for 4 sections, but only 3 are validated/potentially loaded on hardware. */
for (unsigned int section = 0; section < PACKAGE2_SECTION_MAX; section++) {
/* Validate section size alignment. */
if (metadata->section_sizes[section] & 3) {
return false;
}
/* Validate section does not overflow. */
if (check_32bit_additive_overflow(metadata->section_offsets[section], metadata->section_sizes[section])) {
return false;
}
/* Check for entrypoint presence. */
uint32_t section_end = metadata->section_offsets[section] + metadata->section_sizes[section];
if (metadata->section_offsets[section] <= metadata->entrypoint && metadata->entrypoint < section_end) {
entrypoint_found = true;
}
/* Ensure no overlap with later sections. */
for (unsigned int later_section = section + 1; later_section < PACKAGE2_SECTION_MAX; later_section++) {
uint32_t later_section_end = metadata->section_offsets[later_section] + metadata->section_sizes[later_section];
if (overlaps(metadata->section_offsets[section], section_end, metadata->section_offsets[later_section], later_section_end)) {
return false;
}
}
/* Validate section hashes. */
void *section_data = (void *)((uint8_t *)NX_BOOTLOADER_PACKAGE2_LOAD_ADDRESS + sizeof(package2_header_t) + metadata->section_offsets[section]);
uint8_t calculated_hash[0x20];
se_calculate_sha256(calculated_hash, section_data, metadata->section_sizes[section]);
if (memcmp(calculated_hash, metadata->section_hashes[section], sizeof(metadata->section_hashes[section])) != 0) {
return false;
}
}
/* Ensure that entrypoint is present in one of our sections. */
if (!entrypoint_found) {
return false;
}
/* Perform version checks. */
/* We will be compatible with all package2s released before current, but not newer ones. */
if (metadata->version_max >= PACKAGE2_MINVER_THEORETICAL && metadata->version_min < PACKAGE2_MAXVER_400_CURRENT) {
return false;
}
return true;
}
/* Decrypts package2 header, and returns the master key revision required. */
static uint32_t decrypt_and_validate_header(package2_header_t *header) {
package2_meta_t metadata;
if (bootconfig_is_package2_plaintext() == 0) {
uint32_t mkey_rev;
/* Try to decrypt for all possible master keys. */
for (mkey_rev = 0; mkey_rev < MASTERKEY_REVISION_MAX; mkey_rev++) {
package2_crypt_ctr(mkey_rev, &metadata, sizeof(package2_meta_t), &header->metadata, sizeof(package2_meta_t), header->metadata.ctr, sizeof(header->metadata.ctr));
/* Copy the ctr (which stores information) into the decrypted metadata. */
memcpy(metadata.ctr, header->metadata.ctr, sizeof(header->metadata.ctr));
/* See if this is the correct key. */
if (validate_package2_metadata(&metadata)) {
header->metadata = metadata;
break;
}
}
/* Ensure we successfully decrypted the header. */
generic_panic();
}
return 0;
}
static void load_package2_sections(package2_meta_t *metadata, uint32_t master_key_rev) {
/* By default, copy data directly from where NX_BOOTLOADER puts it. */
void *load_buf = NX_BOOTLOADER_PACKAGE2_LOAD_ADDRESS;
/* Check whether any of our sections overlap this region. If they do, we must relocate and copy from elsewhere. */
bool needs_relocation = false;
for (unsigned int section = 0; section < PACKAGE2_SECTION_MAX; section++) {
uint64_t section_start = DRAM_BASE_PHYSICAL + (uint64_t)metadata->section_offsets[section];
uint64_t section_end = section_start + (uint64_t)metadata->section_sizes[section];
if (overlaps(section_start, section_end, (uint64_t)(NX_BOOTLOADER_PACKAGE2_LOAD_ADDRESS), (uint64_t)(NX_BOOTLOADER_PACKAGE2_LOAD_ADDRESS) + PACKAGE2_SIZE_MAX)) {
needs_relocation = true;
}
}
if (needs_relocation) {
/* This code should *always* succeed in finding a carveout within seven loops, */
/* due to the section size limit, and section number limit. */
/* However, Nintendo tries panics after 8 loops if a safe section is not found. */
/* This should never be the case, mathematically. */
/* We will replicate this behavior. */
bool found_safe_carveout = false;
uint64_t potential_base_start = DRAM_BASE_PHYSICAL;
uint64_t potential_base_end = potential_base_start + PACKAGE2_SIZE_MAX;
for (unsigned int i = 0; i < 8; i++) {
int is_safe = 1;
for (unsigned int section = 0; section < PACKAGE2_SECTION_MAX; section++) {
uint64_t section_start = DRAM_BASE_PHYSICAL + (uint64_t)metadata->section_offsets[section];
uint64_t section_end = section_start + (uint64_t)metadata->section_sizes[section];
if (overlaps(section_start, section_end, potential_base_start, potential_base_end)) {
is_safe = 0;
}
}
found_safe_carveout |= is_safe;
if (found_safe_carveout) {
break;
}
potential_base_start += PACKAGE2_SIZE_MAX;
potential_base_end += PACKAGE2_SIZE_MAX;
}
if (!found_safe_carveout) {
generic_panic();
}
/* Relocate to new carveout. */
memcpy((void *)potential_base_start, load_buf, PACKAGE2_SIZE_MAX);
memset(load_buf, 0, PACKAGE2_SIZE_MAX);
load_buf = (void *)potential_base_start;
}
/* Copy each section to its appropriate location, decrypting if necessary. */
for (unsigned int section = 0; section < PACKAGE2_SECTION_MAX; section++) {
if (metadata->section_sizes[section] == 0) {
continue;
}
void *dst_start = (void *)(DRAM_BASE_PHYSICAL + (uint64_t)metadata->section_offsets[section]);
void *src_start = load_buf + sizeof(package2_header_t) + metadata->section_offsets[section];
size_t size = (size_t)metadata->section_sizes[section];
if (bootconfig_is_package2_plaintext()) {
memcpy(dst_start, src_start, size);
} else {
package2_crypt_ctr(master_key_rev, dst_start, size, src_start, size, metadata->section_ctrs[section], 0x10);
}
}
/* Clear the encrypted package2 from memory. */
memset(load_buf, 0, PACKAGE2_SIZE_MAX);
}
uintptr_t get_pk2ldr_stack_address(void) {
return tzram_get_segment_address(TZRAM_SEGMENT_ID_PK2LDR) + 0x2000;
}
/* This function is called during coldboot init, and validates a package2. */
/* This package2 is read into memory by a concurrent BPMP bootloader. */
void load_package2(void) {
/* Setup the Security Engine. */
setup_se();
/* TODO: bootup_misc_mmio(). */
/* This func will also be called on warmboot. */
/* And will verify stored SE Test Vector, clear keyslots, */
/* Generate an SRK, set the warmboot firmware location, */
/* Configure the GPU uCode carveout, configure the Kernel default carveouts, */
/* Initialize the PMC secure scratch registers, initialize MISC registers, */
/* And assign "se_operation_completed" to Interrupt 0x5A. */
/* TODO: Read and save BOOTREASON stored by NX_BOOTLOADER at 0x1F009FE00 */
/* Initialize cache'd random bytes for kernel. */
randomcache_init();
/* TODO: memclear the initial copy of Exosphere running in IRAM (relocated to TZRAM by earlier code). */
/* Let NX Bootloader know that we're running. */
MAILBOX_NX_BOOTLOADER_IS_SECMON_AWAKE = 1;
/* Synchronize with NX BOOTLOADER. */
if (MAILBOX_NX_BOOTLOADER_SETUP_STATE == NX_BOOTLOADER_STATE_INIT) {
while (MAILBOX_NX_BOOTLOADER_SETUP_STATE < NX_BOOTLOADER_STATE_MOVED_BOOTCONFIG) {
wait(1);
}
}
/* Load Boot Config into global. */
setup_boot_config();
/* Synchronize with NX BOOTLOADER. */
if (MAILBOX_NX_BOOTLOADER_SETUP_STATE == NX_BOOTLOADER_STATE_MOVED_BOOTCONFIG) {
while (MAILBOX_NX_BOOTLOADER_SETUP_STATE < NX_BOOTLOADER_STATE_LOADED_PACKAGE2) {
wait(1);
}
}
/* Load header from NX_BOOTLOADER-initialized DRAM. */
package2_header_t header;
flush_dcache_range((uint8_t *)NX_BOOTLOADER_PACKAGE2_LOAD_ADDRESS, (uint8_t *)NX_BOOTLOADER_PACKAGE2_LOAD_ADDRESS + sizeof(header));
memcpy(&header, NX_BOOTLOADER_PACKAGE2_LOAD_ADDRESS, sizeof(header));
flush_dcache_range((uint8_t *)&header, (uint8_t *)&header + sizeof(header));
/* Perform signature checks. */
verify_header_signature(&header);
/* Decrypt header, get key revision required. */
uint32_t package2_mkey_rev = decrypt_and_validate_header(&header);
/* Load Package2 Sections. */
load_package2_sections(&header.metadata, package2_mkey_rev);
/* Clean up cache. */
flush_dcache_all();
invalidate_icache_inner_shareable();
/* Set CORE0 entrypoint for Package2. */
set_core_entrypoint_and_context_id(0, DRAM_BASE_PHYSICAL + header.metadata.entrypoint, 0);
/* Synchronize with NX BOOTLOADER. */
if (MAILBOX_NX_BOOTLOADER_SETUP_STATE == NX_BOOTLOADER_STATE_LOADED_PACKAGE2) {
while (MAILBOX_NX_BOOTLOADER_SETUP_STATE < NX_BOOTLOADER_STATE_FINISHED) {
wait(1);
}
}
/* TODO: MISC register 0x1F0098C00 |= 0x2000; */
/* TODO: Update SCR_EL3 depending on value in Bootconfig. */
}

78
exosphere/src/package2.h Normal file
View File

@@ -0,0 +1,78 @@
#ifndef EXOSPHERE_PACKAGE2_H
#define EXOSPHERE_PACKAGE2_H
/* This is code responsible for validating a package2. Actual file reading is done by bootloader. */
#include <stdint.h>
#include "bootconfig.h"
#include "memory_map.h"
/* Physaddr 0x40002EF8 */
#define MAILBOX_NX_BOOTLOADER_BASE (mmio_get_device_address(MMIO_DEVID_NXBOOTLOADER_MAILBOX))
#define MAILBOX_NX_BOOTLOADER_SETUP_STATE (*((volatile uint32_t *)(MAILBOX_NX_BOOTLOADER_BASE + 0xEF8ULL)))
#define NX_BOOTLOADER_STATE_INIT 0
#define NX_BOOTLOADER_STATE_MOVED_BOOTCONFIG 1
#define NX_BOOTLOADER_STATE_LOADED_PACKAGE2 2
#define NX_BOOTLOADER_STATE_FINISHED 3
/* Physaddr 0x40002EFC */
#define MAILBOX_NX_BOOTLOADER_IS_SECMON_AWAKE (*((volatile uint32_t *)(MAILBOX_NX_BOOTLOADER_BASE + 0xEFCULL)))
#define NX_BOOTLOADER_BOOTCONFIG_POINTER ((void *)(0x4003D000ULL))
#define NX_BOOTLOADER_PACKAGE2_LOAD_ADDRESS ((void *)(0xA9800000ULL))
#define DRAM_BASE_PHYSICAL (0x80000000ULL)
#define MAGIC_PK21 (0x31324B50)
#define PACKAGE2_SIZE_MAX 0x7FC000
#define PACKAGE2_SECTION_MAX 0x3
#define PACKAGE2_MINVER_THEORETICAL 0x0
#define PACKAGE2_MAXVER_100 0x2
#define PACKAGE2_MAXVER_200 0x3
#define PACKAGE2_MAXVER_300 0x4
#define PACKAGE2_MAXVER_302 0x5
#define PACKAGE2_MAXVER_400_CURRENT 0x6
#define PACKAGE2_MINVER_100 0x3
#define PACKAGE2_MINVER_200 0x4
#define PACKAGE2_MINVER_300 0x5
#define PACKAGE2_MINVER_302 0x6
#define PACKAGE2_MINVER_400_CURRENT 0x7
#pragma pack(push, 1)
typedef struct {
union {
uint8_t ctr[0x10];
uint32_t ctr_dwords[0x4];
};
uint8_t section_ctrs[4][0x10];
uint32_t magic;
uint32_t entrypoint;
uint32_t _0x58;
uint8_t version_max; /* Must be > TZ value. */
uint8_t version_min; /* Must be < TZ value. */
uint16_t _0x5E;
uint32_t section_sizes[4];
uint32_t section_offsets[4];
uint8_t section_hashes[4][0x20];
} package2_meta_t;
#pragma pack(pop)
#pragma pack(push, 1)
typedef struct {
uint8_t signature[0x100];
union {
package2_meta_t metadata;
uint8_t encrypted_header[0x100];
};
} package2_header_t;
#pragma pack(pop)
void load_package2(void);
#endif

14
exosphere/src/pmc.h Normal file
View File

@@ -0,0 +1,14 @@
#ifndef EXOSPHERE_PMC_H
#define EXOSPHERE_PMC_H
#include <stdint.h>
#include "memory_map.h"
/* Exosphere register definitions for the Tegra X1 PMC. */
#define PMC_BASE (mmio_get_device_address(MMIO_DEVID_RTC_PMC) + 0x400ULL)
#define APBDEV_PMC_PWRGATE_TOGGLE_0 (*((volatile uint32_t *)(PMC_BASE + 0x30)))
#define APBDEV_PMC_PWRGATE_STATUS_0 (*((volatile uint32_t *)(PMC_BASE + 0x38)))
#endif

View File

@@ -0,0 +1,73 @@
#include <stdint.h>
#include <string.h>
#include "utils.h"
#include "randomcache.h"
#include "se.h"
#include "cache.h"
/* TrustZone maintains a cache of random for the kernel. */
/* So that requests can still be serviced even when a */
/* usermode SMC is in progress. */
static uint8_t g_random_cache[0x400];
static unsigned int g_random_cache_low = 0;
static unsigned int g_random_cache_high = 0x3FF;
void randomcache_refill_segment(unsigned int offset, unsigned int size) {
if (offset + size >= 0x400) {
size = 0x400 - offset;
}
flush_dcache_range(&g_random_cache[offset], &g_random_cache[offset + size]);
se_generate_random(KEYSLOT_SWITCH_RNGKEY, &g_random_cache[offset], size);
flush_dcache_range(&g_random_cache[offset], &g_random_cache[offset + size]);
}
void randomcache_init(void) {
randomcache_refill_segment(0, 0x400);
g_random_cache_low = 0;
g_random_cache_high = 0x3FF;
}
void randomcache_refill(void) {
unsigned int high_plus_one = (g_random_cache_high + 1) & 0x3FF;
if (g_random_cache_low != high_plus_one) {
/* Only refill if there's data to refill. */
if (g_random_cache_low < high_plus_one) {
/* NOTE: There is a bug in official code that causes this to not work properly. */
/* In particular, official code checks whether high_plus_one == 0x400. */
/* However, because high_plus_one is &= 0x3FF'd, this can never be the case. */
/* We will implement according to Nintendo's intention, and not include their bug. */
/* This should have no impact on actual observable results, anyway, since this data is random anyway... */
if (g_random_cache_high != 0x3FF) { /* This is if (true) in Nintendo's code due to the above bug. */
randomcache_refill_segment(high_plus_one, 0x400 - high_plus_one);
g_random_cache_high = (g_random_cache_high + 0x400 - high_plus_one) & 0x3FF;
}
if (g_random_cache_low > 0) {
randomcache_refill_segment(0, g_random_cache_low);
g_random_cache_high = (g_random_cache_high + g_random_cache_low) & 0x3FF;
}
} else { /* g_random_cache_low > high_plus_one */
randomcache_refill_segment(high_plus_one, g_random_cache_low - high_plus_one);
g_random_cache_high = g_random_cache_low - 1;
}
}
}
void randomcache_getbytes(void *dst, size_t num_bytes) {
unsigned int low = g_random_cache_low;
memcpy(dst, &g_random_cache[low], num_bytes);
unsigned int new_low = low + num_bytes;
if (new_low + 0x38 > 0x3FF) {
new_low = 0;
}
g_random_cache_low = new_low;
}

View File

@@ -0,0 +1,13 @@
#ifndef EXOSPHERE_RANDOM_CACHE_H
#define EXOSPHERE_RANDOM_CACHE_H
#include <stdint.h>
/* This method must be called on startup. */
void randomcache_init(void);
void randomcache_refill(void);
void randomcache_getbytes(void *dst, size_t num_bytes);
#endif

596
exosphere/src/se.c Normal file
View File

@@ -0,0 +1,596 @@
#include <string.h>
#include "utils.h"
#include "interrupt.h"
#include "se.h"
#include "memory_map.h"
#include "cache.h"
#include "se.h"
/* Macro for the SE registers. */
#define SECURITY_ENGINE ((volatile security_engine_t *)(mmio_get_device_address(MMIO_DEVID_SE)))
void trigger_se_rsa_op(void *buf, size_t size);
void trigger_se_blocking_op(unsigned int op, void *dst, size_t dst_size, const void *src, size_t src_size);
/* Globals for driver. */
unsigned int (*g_se_callback)(void);
unsigned int g_se_modulus_sizes[KEYSLOT_RSA_MAX];
unsigned int g_se_exp_sizes[KEYSLOT_RSA_MAX];
/* Initialize a SE linked list. */
void ll_init(se_ll_t *ll, void *buffer, size_t size) {
ll->num_entries = 0; /* 1 Entry. */
if (buffer != NULL) {
ll->addr_info.address = (uint32_t) get_physical_address(buffer);
ll->addr_info.size = (uint32_t) size;
} else {
ll->addr_info.address = 0;
ll->addr_info.size = 0;
}
flush_dcache_range((uint8_t *)ll, (uint8_t *)ll + sizeof(*ll));
}
void set_security_engine_callback(unsigned int (*callback)(void)) {
if (callback == NULL || g_se_callback != NULL) {
generic_panic();
}
g_se_callback = callback;
}
/* Fires on Security Engine operation completion. */
void se_operation_completed(void) {
SECURITY_ENGINE->INT_ENABLE_REG = 0;
if (g_se_callback != NULL) {
g_se_callback();
g_se_callback = NULL;
}
}
void se_check_for_error(void) {
if (SECURITY_ENGINE->INT_STATUS_REG & 0x10000 || SECURITY_ENGINE->FLAGS_REG & 3 || SECURITY_ENGINE->ERR_STATUS_REG) {
generic_panic();
}
}
void se_trigger_interrupt(void) {
intr_set_pending(INTERRUPT_ID_USER_SECURITY_ENGINE);
}
void se_verify_flags_cleared(void) {
if (SECURITY_ENGINE->FLAGS_REG & 3) {
generic_panic();
}
}
void se_clear_interrupts(void) {
/* TODO */
}
/* Set the flags for an AES keyslot. */
void set_aes_keyslot_flags(unsigned int keyslot, unsigned int flags) {
if (keyslot >= KEYSLOT_AES_MAX) {
generic_panic();
}
/* Misc flags. */
if (flags & ~0x80) {
SECURITY_ENGINE->AES_KEYSLOT_FLAGS[keyslot] = ~flags;
}
/* Disable keyslot reads. */
if (flags & 0x80) {
SECURITY_ENGINE->AES_KEY_READ_DISABLE_REG &= ~(1 << keyslot);
}
}
/* Set the flags for an RSA keyslot. */
void set_rsa_keyslot_flags(unsigned int keyslot, unsigned int flags) {
if (keyslot >= KEYSLOT_RSA_MAX) {
generic_panic();
}
/* Misc flags. */
if (flags & ~0x80) {
/* TODO: Why are flags assigned this way? */
SECURITY_ENGINE->RSA_KEYSLOT_FLAGS[keyslot] = (((flags >> 4) & 4) | (flags & 3)) ^ 7;
}
/* Disable keyslot reads. */
if (flags & 0x80) {
SECURITY_ENGINE->RSA_KEY_READ_DISABLE_REG &= ~(1 << keyslot);
}
}
void clear_aes_keyslot(unsigned int keyslot) {
if (keyslot >= KEYSLOT_AES_MAX) {
generic_panic();
}
/* Zero out the whole keyslot and IV. */
for (unsigned int i = 0; i < 0x10; i++) {
SECURITY_ENGINE->AES_KEYTABLE_ADDR = (keyslot << 4) | i;
SECURITY_ENGINE->AES_KEYTABLE_DATA = 0;
}
}
void clear_rsa_keyslot(unsigned int keyslot) {
if (keyslot >= KEYSLOT_RSA_MAX) {
generic_panic();
}
/* Zero out the whole keyslot. */
for (unsigned int i = 0; i < 0x40; i++) {
/* Select Keyslot Modulus[i] */
SECURITY_ENGINE->RSA_KEYTABLE_ADDR = (keyslot << 7) | i | 0x40;
SECURITY_ENGINE->RSA_KEYTABLE_DATA = 0;
}
for (unsigned int i = 0; i < 0x40; i++) {
/* Select Keyslot Expontent[i] */
SECURITY_ENGINE->RSA_KEYTABLE_ADDR = (keyslot << 7) | i;
SECURITY_ENGINE->RSA_KEYTABLE_DATA = 0;
}
}
void set_aes_keyslot(unsigned int keyslot, const void *key, size_t key_size) {
if (keyslot >= KEYSLOT_AES_MAX || key_size > KEYSIZE_AES_MAX) {
generic_panic();
}
for (size_t i = 0; i < (key_size >> 2); i++) {
SECURITY_ENGINE->AES_KEYTABLE_ADDR = (keyslot << 4) | i;
SECURITY_ENGINE->AES_KEYTABLE_DATA = read32le(key, 4 * i);
}
}
void set_rsa_keyslot(unsigned int keyslot, const void *modulus, size_t modulus_size, const void *exponent, size_t exp_size) {
if (keyslot >= KEYSLOT_RSA_MAX || modulus_size > KEYSIZE_RSA_MAX || exp_size > KEYSIZE_RSA_MAX) {
generic_panic();
}
for (size_t i = 0; i < (modulus_size >> 2); i++) {
SECURITY_ENGINE->RSA_KEYTABLE_ADDR = (keyslot << 7) | 0x40 | i;
SECURITY_ENGINE->RSA_KEYTABLE_DATA = read32be(modulus, 4 * i);
}
for (size_t i = 0; i < (exp_size >> 2); i++) {
SECURITY_ENGINE->RSA_KEYTABLE_ADDR = (keyslot << 7) | i;
SECURITY_ENGINE->RSA_KEYTABLE_DATA = read32be(exponent, 4 * i);
}
g_se_modulus_sizes[keyslot] = modulus_size;
g_se_exp_sizes[keyslot] = exp_size;
}
void set_aes_keyslot_iv(unsigned int keyslot, const void *iv, size_t iv_size) {
if (keyslot >= KEYSLOT_AES_MAX || iv_size > 0x10) {
generic_panic();
}
for (size_t i = 0; i < (iv_size >> 2); i++) {
SECURITY_ENGINE->AES_KEYTABLE_ADDR = (keyslot << 4) | 8 | i;
SECURITY_ENGINE->AES_KEYTABLE_DATA = read32le(iv, 4 * i);
}
}
void clear_aes_keyslot_iv(unsigned int keyslot) {
if (keyslot >= KEYSLOT_AES_MAX) {
generic_panic();
}
for (size_t i = 0; i < (0x10 >> 2); i++) {
SECURITY_ENGINE->AES_KEYTABLE_ADDR = (keyslot << 4) | 8;
SECURITY_ENGINE->AES_KEYTABLE_DATA = 0;
}
}
void set_se_ctr(const void *ctr) {
for (unsigned int i = 0; i < 4; i++) {
SECURITY_ENGINE->CRYPTO_CTR_REG[i] = read32le(ctr, i * 4);
}
}
void decrypt_data_into_keyslot(unsigned int keyslot_dst, unsigned int keyslot_src, const void *wrapped_key, size_t wrapped_key_size) {
if (keyslot_dst >= KEYSLOT_AES_MAX || keyslot_src >= KEYSIZE_AES_MAX || wrapped_key_size > KEYSIZE_AES_MAX) {
generic_panic();
}
SECURITY_ENGINE->CONFIG_REG = (ALG_AES_DEC | DST_KEYTAB);
SECURITY_ENGINE->CRYPTO_REG = keyslot_src << 24;
SECURITY_ENGINE->BLOCK_COUNT_REG = 0;
SECURITY_ENGINE->CRYPTO_KEYTABLE_DST_REG = keyslot_dst << 8;
flush_dcache_range(wrapped_key, (const uint8_t *)wrapped_key + wrapped_key_size);
/* TODO: trigger_se_aes_op(OP_START, NULL, 0, wrapped_key, wrapped_key_size); */
}
void se_aes_crypt_insecure_internal(unsigned int keyslot, uint32_t out_ll_paddr, uint32_t in_ll_paddr, size_t size, unsigned int crypt_config, bool encrypt, unsigned int (*callback)(void)) {
if (keyslot >= KEYSLOT_AES_MAX) {
generic_panic();
}
if (size == 0) {
return;
}
/* Setup Config register. */
if (encrypt) {
SECURITY_ENGINE->CONFIG_REG = (ALG_AES_ENC | DST_MEMORY);
} else {
SECURITY_ENGINE->CONFIG_REG = (ALG_AES_DEC | DST_MEMORY);
}
/* Setup Crypto register. */
SECURITY_ENGINE->CRYPTO_REG = crypt_config | (keyslot << 24) | (encrypt << 8);
/* Mark this encryption as insecure -- this makes the SE not a secure busmaster. */
SECURITY_ENGINE->CRYPTO_REG |= 0x80000000;
/* Appropriate number of blocks. */
SECURITY_ENGINE->BLOCK_COUNT_REG = (size >> 4) - 1;
/* Set the callback, for after the async operation. */
set_security_engine_callback(callback);
/* Enable SE Interrupt firing for async op. */
SECURITY_ENGINE->INT_ENABLE_REG = 0x10;
/* Setup Input/Output lists */
SECURITY_ENGINE->IN_LL_ADDR_REG = in_ll_paddr;
SECURITY_ENGINE->OUT_LL_ADDR_REG = out_ll_paddr;
/* Set registers for operation. */
SECURITY_ENGINE->ERR_STATUS_REG = SECURITY_ENGINE->ERR_STATUS_REG;
SECURITY_ENGINE->INT_STATUS_REG = SECURITY_ENGINE->INT_STATUS_REG;
SECURITY_ENGINE->OPERATION_REG = 1;
/* Ensure writes go through. */
__asm__ __volatile__ ("dsb ish" : : : "memory");
}
void se_aes_ctr_crypt_insecure(unsigned int keyslot, uint32_t out_ll_paddr, uint32_t in_ll_paddr, size_t size, const void *ctr, unsigned int (*callback)(void)) {
/* Unknown what this write does, but official code writes it for CTR mode. */
SECURITY_ENGINE->_0x80C = 1;
set_se_ctr(ctr);
se_aes_crypt_insecure_internal(keyslot, out_ll_paddr, in_ll_paddr, size, 0x81E, true, callback);
}
void se_aes_cbc_encrypt_insecure(unsigned int keyslot, uint32_t out_ll_paddr, uint32_t in_ll_paddr, size_t size, const void *iv, unsigned int (*callback)(void)) {
set_aes_keyslot_iv(keyslot, iv, 0x10);
se_aes_crypt_insecure_internal(keyslot, out_ll_paddr, in_ll_paddr, size, 0x44, true, callback);
}
void se_aes_cbc_decrypt_insecure(unsigned int keyslot, uint32_t out_ll_paddr, uint32_t in_ll_paddr, size_t size, const void *iv, unsigned int (*callback)(void)) {
set_aes_keyslot_iv(keyslot, iv, 0x10);
se_aes_crypt_insecure_internal(keyslot, out_ll_paddr, in_ll_paddr, size, 0x66, false, callback);
}
void se_exp_mod(unsigned int keyslot, void *buf, size_t size, unsigned int (*callback)(void)) {
uint8_t stack_buf[KEYSIZE_RSA_MAX];
if (keyslot >= KEYSLOT_RSA_MAX || size > KEYSIZE_RSA_MAX) {
generic_panic();
}
/* Endian swap the input. */
for (size_t i = size; i > 0; i--) {
stack_buf[i] = *((uint8_t *)buf + size - i);
}
SECURITY_ENGINE->CONFIG_REG = (ALG_RSA | DST_RSAREG);
SECURITY_ENGINE->RSA_CONFIG = keyslot << 24;
SECURITY_ENGINE->RSA_KEY_SIZE_REG = (g_se_modulus_sizes[keyslot] >> 6) - 1;
SECURITY_ENGINE->RSA_EXP_SIZE_REG = g_se_exp_sizes[keyslot] >> 2;
set_security_engine_callback(callback);
/* Enable SE Interrupt firing for async op. */
SECURITY_ENGINE->INT_ENABLE_REG = 0x10;
flush_dcache_range(stack_buf, stack_buf + KEYSIZE_RSA_MAX);
trigger_se_rsa_op(stack_buf, size);
while (!(SECURITY_ENGINE->INT_STATUS_REG & 2)) { /* Wait a while */ }
}
void se_synchronous_exp_mod(unsigned int keyslot, void *dst, size_t dst_size, const void *src, size_t src_size) {
uint8_t stack_buf[KEYSIZE_RSA_MAX];
if (keyslot >= KEYSLOT_RSA_MAX || src_size > KEYSIZE_RSA_MAX || dst_size > KEYSIZE_RSA_MAX) {
generic_panic();
}
/* Endian swap the input. */
for (size_t i = src_size; i > 0; i--) {
stack_buf[i] = *((uint8_t *)src + src_size - i);
}
SECURITY_ENGINE->CONFIG_REG = (ALG_RSA | DST_RSAREG);
SECURITY_ENGINE->RSA_CONFIG = keyslot << 24;
SECURITY_ENGINE->RSA_KEY_SIZE_REG = (g_se_modulus_sizes[keyslot] >> 6) - 1;
SECURITY_ENGINE->RSA_EXP_SIZE_REG = g_se_exp_sizes[keyslot] >> 2;
flush_dcache_range(stack_buf, stack_buf + KEYSIZE_RSA_MAX);
trigger_se_blocking_op(1, NULL, 0, stack_buf, src_size);
se_get_exp_mod_output(dst, dst_size);
}
void se_get_exp_mod_output(void *buf, size_t size) {
size_t num_dwords = (size >> 2);
if (num_dwords < 1) {
return;
}
uint32_t *p_out = ((uint32_t *)buf) + num_dwords - 1;
uint32_t offset = 0;
/* Copy endian swapped output. */
while (num_dwords) {
*p_out = read32be(SECURITY_ENGINE->RSA_OUTPUT, offset);
offset += 4;
p_out--;
num_dwords--;
}
}
void trigger_se_rsa_op(void *buf, size_t size) {
se_ll_t in_ll;
ll_init(&in_ll, (void *)buf, size);
/* Set the input LL. */
SECURITY_ENGINE->IN_LL_ADDR_REG = get_physical_address(&in_ll);
/* Set registers for operation. */
SECURITY_ENGINE->ERR_STATUS_REG = SECURITY_ENGINE->ERR_STATUS_REG;
SECURITY_ENGINE->INT_STATUS_REG = SECURITY_ENGINE->INT_STATUS_REG;
SECURITY_ENGINE->OPERATION_REG = 1;
/* Ensure writes go through. */
__asm__ __volatile__ ("dsb ish" : : : "memory");
}
void trigger_se_blocking_op(unsigned int op, void *dst, size_t dst_size, const void *src, size_t src_size) {
se_ll_t in_ll;
se_ll_t out_ll;
ll_init(&in_ll, (void *)src, src_size);
ll_init(&out_ll, dst, dst_size);
/* Set the LLs. */
SECURITY_ENGINE->IN_LL_ADDR_REG = get_physical_address(&in_ll);
SECURITY_ENGINE->OUT_LL_ADDR_REG = get_physical_address(&out_ll);
/* Set registers for operation. */
SECURITY_ENGINE->ERR_STATUS_REG = SECURITY_ENGINE->ERR_STATUS_REG;
SECURITY_ENGINE->INT_STATUS_REG = SECURITY_ENGINE->INT_STATUS_REG;
SECURITY_ENGINE->OPERATION_REG = op;
while (!(SECURITY_ENGINE->INT_STATUS_REG & 0x10)) { /* Wait a while */ }
se_check_for_error();
}
/* Secure AES Functionality. */
void se_perform_aes_block_operation(void *dst, size_t dst_size, const void *src, size_t src_size) {
uint8_t block[0x10];
if (src_size > sizeof(block) || dst_size > sizeof(block)) {
generic_panic();
}
/* Load src data into block. */
memset(block, 0, sizeof(block));
memcpy(block, src, src_size);
flush_dcache_range(block, block + sizeof(block));
/* Trigger AES operation. */
SECURITY_ENGINE->BLOCK_COUNT_REG = 0;
trigger_se_blocking_op(1, block, sizeof(block), block, sizeof(block));
/* Copy output data into dst. */
flush_dcache_range(block, block + sizeof(block));
memcpy(dst, block, dst_size);
}
void se_aes_ctr_crypt(unsigned int keyslot, void *dst, size_t dst_size, const void *src, size_t src_size, const void *ctr, size_t ctr_size) {
if (keyslot >= KEYSLOT_AES_MAX || ctr_size != 0x10) {
generic_panic();
}
unsigned int num_blocks = src_size >> 4;
/* Unknown what this write does, but official code writes it for CTR mode. */
SECURITY_ENGINE->_0x80C = 1;
SECURITY_ENGINE->CONFIG_REG = (ALG_AES_ENC | DST_MEMORY);
SECURITY_ENGINE->CRYPTO_REG = (keyslot << 24) | 0x91E;
set_se_ctr(ctr);
/* Handle any aligned blocks. */
size_t aligned_size = (size_t)num_blocks << 4;
if (aligned_size) {
SECURITY_ENGINE->BLOCK_COUNT_REG = num_blocks - 1;
trigger_se_blocking_op(1, dst, dst_size, src, aligned_size);
}
/* Handle final, unaligned block. */
if (aligned_size < dst_size && aligned_size < src_size) {
size_t last_block_size = dst_size - aligned_size;
if (src_size < dst_size) {
last_block_size = src_size - aligned_size;
}
se_perform_aes_block_operation(dst + aligned_size, last_block_size, (uint8_t *)src + aligned_size, src_size - aligned_size);
}
}
void se_aes_ecb_encrypt_block(unsigned int keyslot, void *dst, size_t dst_size, const void *src, size_t src_size, unsigned int config_high) {
if (keyslot >= KEYSLOT_AES_MAX || dst_size != 0x10 || src_size != 0x10) {
generic_panic();
}
/* Set configuration high (256-bit vs 128-bit) based on parameter. */
SECURITY_ENGINE->CONFIG_REG = (ALG_AES_ENC | DST_MEMORY) | (config_high << 16);
SECURITY_ENGINE->CRYPTO_REG = keyslot << 24;
se_perform_aes_block_operation(dst, 0x10, src, 0x10);
}
void se_aes_128_ecb_encrypt_block(unsigned int keyslot, void *dst, size_t dst_size, const void *src, size_t src_size) {
se_aes_ecb_encrypt_block(keyslot, dst, dst_size, src, src_size, 0);
}
void se_aes_256_ecb_encrypt_block(unsigned int keyslot, void *dst, size_t dst_size, const void *src, size_t src_size) {
se_aes_ecb_encrypt_block(keyslot, dst, dst_size, src, src_size, 0x202);
}
void se_aes_ecb_decrypt_block(unsigned int keyslot, void *dst, size_t dst_size, const void *src, size_t src_size) {
if (keyslot >= KEYSLOT_AES_MAX || dst_size != 0x10 || src_size != 0x10) {
generic_panic();
}
SECURITY_ENGINE->CONFIG_REG = (ALG_AES_DEC | DST_MEMORY);
SECURITY_ENGINE->CRYPTO_REG = keyslot << 24;
se_perform_aes_block_operation(dst, 0x10, src, 0x10);
}
void shift_left_xor_rb(uint8_t *key) {
uint8_t prev_high_bit = 0;
for (unsigned int i = 0; i < 0x10; i++) {
uint8_t cur_byte = key[0xF - i];
key[0xF - i] = (cur_byte << 1) | (prev_high_bit);
prev_high_bit = cur_byte >> 7;
}
if (prev_high_bit) {
key[0xF] ^= 0x87;
}
}
void se_compute_aes_cmac(unsigned int keyslot, void *cmac, size_t cmac_size, const void *data, size_t data_size, unsigned int config_high) {
if (keyslot >= KEYSLOT_AES_MAX) {
generic_panic();
}
/* Generate the derived key, to be XOR'd with final output block. */
uint8_t derived_key[0x10];
memset(derived_key, 0, sizeof(derived_key));
se_aes_128_ecb_encrypt_block(keyslot, derived_key, sizeof(derived_key), derived_key, sizeof(derived_key));
shift_left_xor_rb(derived_key);
if (data_size & 0xF) {
shift_left_xor_rb(derived_key);
}
SECURITY_ENGINE->CONFIG_REG = (ALG_AES_ENC | DST_HASHREG) | (config_high << 16);
SECURITY_ENGINE->CRYPTO_REG = (keyslot << 24) | (0x145);
clear_aes_keyslot_iv(keyslot);
unsigned int num_blocks = (data_size + 0xF) >> 4;
/* Handle aligned blocks. */
if (num_blocks > 1) {
SECURITY_ENGINE->BLOCK_COUNT_REG = num_blocks - 2;
trigger_se_blocking_op(1, NULL, 0, data, data_size);
SECURITY_ENGINE->CRYPTO_REG |= 0x80;
}
/* Create final block. */
uint8_t last_block[0x10];
memset(last_block, 0, sizeof(last_block));
if (data_size & 0xF) {
memcpy(last_block, data + (data_size & ~0xF), data_size & 0xF);
last_block[data_size & 0xF] = 0x80; /* Last block = data || 100...0 */
} else if (data_size >= 0x10) {
memcpy(last_block, data + data_size - 0x10, 0x10);
}
for (unsigned int i = 0; i < 0x10; i++) {
last_block[i] ^= derived_key[i];
}
/* Perform last operation. */
flush_dcache_range(last_block, last_block + sizeof(last_block));
trigger_se_blocking_op(1, NULL, 0, last_block, sizeof(last_block));
/* Copy output CMAC. */
for (unsigned int i = 0; i < (cmac_size >> 2); i++) {
((uint32_t *)cmac)[i] = read32le(SECURITY_ENGINE->HASH_RESULT_REG, i << 2);
}
}
void se_compute_aes_128_cmac(unsigned int keyslot, void *cmac, size_t cmac_size, const void *data, size_t data_size) {
se_compute_aes_cmac(keyslot, cmac, cmac_size, data, data_size, 0);
}
void se_compute_aes_256_cmac(unsigned int keyslot, void *cmac, size_t cmac_size, const void *data, size_t data_size) {
se_compute_aes_cmac(keyslot, cmac, cmac_size, data, data_size, 0x202);
}
/* SHA256 Implementation. */
void se_calculate_sha256(void *dst, const void *src, size_t src_size) {
/* Setup config for SHA256, size = BITS(src_size) */
SECURITY_ENGINE->CONFIG_REG = (ENCMODE_SHA256 | ALG_SHA | DST_HASHREG);
SECURITY_ENGINE->SHA_CONFIG_REG = 1;
SECURITY_ENGINE->SHA_MSG_LENGTH_REG = (unsigned int)(src_size << 3);
SECURITY_ENGINE->_0x20C = 0;
SECURITY_ENGINE->_0x210 = 0;
SECURITY_ENGINE->SHA_MSG_LEFT_REG = 0;
SECURITY_ENGINE->_0x218 = (unsigned int)(src_size << 3);
SECURITY_ENGINE->_0x21C = 0;
SECURITY_ENGINE->_0x220 = 0;
SECURITY_ENGINE->_0x224 = 0;
/* Trigger the operation. */
trigger_se_blocking_op(1, NULL, 0, src, src_size);
/* Copy output hash. */
for (unsigned int i = 0; i < (0x20 >> 2); i++) {
((uint32_t *)dst)[i] = read32be(SECURITY_ENGINE->HASH_RESULT_REG, i << 2);
}
}
/* RNG API */
void se_initialize_rng(unsigned int keyslot) {
if (keyslot >= KEYSLOT_AES_MAX) {
generic_panic();
}
/* To initialize the RNG, we'll perform an RNG operation into an output buffer. */
/* This will be discarded, when done. */
uint8_t output_buf[0x10];
SECURITY_ENGINE->RNG_SRC_CONFIG_REG = 3; /* Entropy enable + Entropy lock enable */
SECURITY_ENGINE->RNG_RESEED_INTERVAL_REG = 70001;
SECURITY_ENGINE->CONFIG_REG = (ALG_RNG | DST_MEMORY);
SECURITY_ENGINE->CRYPTO_REG = (keyslot << 24) | 0x108;
SECURITY_ENGINE->RNG_CONFIG_REG = 5;
SECURITY_ENGINE->BLOCK_COUNT_REG = 0;
trigger_se_blocking_op(1, output_buf, 0x10, NULL, 0);
}
void se_generate_random(unsigned int keyslot, void *dst, size_t size) {
if (keyslot >= KEYSLOT_AES_MAX) {
generic_panic();
}
uint32_t num_blocks = size >> 4;
size_t aligned_size = num_blocks << 4;
SECURITY_ENGINE->CONFIG_REG = (ALG_RNG | DST_MEMORY);
SECURITY_ENGINE->CRYPTO_REG = (keyslot << 24) | 0x108;
SECURITY_ENGINE->RNG_CONFIG_REG = 4;
if (num_blocks >= 1) {
SECURITY_ENGINE->BLOCK_COUNT_REG = num_blocks - 1;
trigger_se_blocking_op(1, dst, aligned_size, NULL, 0);
}
if (size > aligned_size) {
se_perform_aes_block_operation(dst + aligned_size, size - aligned_size, NULL, 0);
}
}

193
exosphere/src/se.h Normal file
View File

@@ -0,0 +1,193 @@
#ifndef EXOSPHERE_SE_H
#define EXOSPHERE_SE_H
#include <stdint.h>
#include <stddef.h>
#include "memory_map.h"
/* Exosphere driver for the Tegra X1 security engine. */
#define KEYSLOT_SWITCH_PACKAGE2KEY 0x8
#define KEYSLOT_SWITCH_TEMPKEY 0x9
#define KEYSLOT_SWITCH_SESSIONKEY 0xA
#define KEYSLOT_SWITCH_RNGKEY 0xB
#define KEYSLOT_SWITCH_MASTERKEY 0xC
#define KEYSLOT_SWITCH_DEVICEKEY 0xD
/* This keyslot was added in 4.0.0. */
#define KEYSLOT_SWITCH_4XNEWDEVICEKEYGENKEY 0xD
#define KEYSLOT_SWITCH_4XNEWCONSOLEKEYGENKEY 0xE
#define KEYSLOT_SWITCH_4XOLDDEVICEKEY 0xF
#define KEYSLOT_AES_MAX 0x10
#define KEYSLOT_RSA_MAX 0x2
#define KEYSIZE_AES_MAX 0x20
#define KEYSIZE_RSA_MAX 0x100
#define ALG_SHIFT (12)
#define ALG_DEC_SHIFT (8)
#define ALG_NOP (0 << ALG_SHIFT)
#define ALG_AES_ENC (1 << ALG_SHIFT)
#define ALG_AES_DEC ((1 << ALG_DEC_SHIFT) | ALG_NOP)
#define ALG_RNG (2 << ALG_SHIFT)
#define ALG_SHA (3 << ALG_SHIFT)
#define ALG_RSA (4 << ALG_SHIFT)
#define DST_SHIFT (2)
#define DST_MEMORY (0 << DST_SHIFT)
#define DST_HASHREG (1 << DST_SHIFT)
#define DST_KEYTAB (2 << DST_SHIFT)
#define DST_SRK (3 << DST_SHIFT)
#define DST_RSAREG (4 << DST_SHIFT)
#define ENCMODE_SHIFT (24)
#define DECMODE_SHIFT (16)
#define ENCMODE_SHA256 (5 << ENCMODE_SHIFT)
#define HASH_DISABLE (0x0)
#define HASH_ENABLE (0x1)
#define OP_ABORT 0
#define OP_START 1
#define OP_RESTART 2
#define OP_CTX_SAVE 3
#define OP_RESTART_IN 4
#define RSA_2048_BYTES 0x100
typedef struct security_engine {
unsigned int _0x0;
unsigned int _0x4;
unsigned int OPERATION_REG;
unsigned int INT_ENABLE_REG;
unsigned int INT_STATUS_REG;
unsigned int CONFIG_REG;
unsigned int IN_LL_ADDR_REG;
unsigned int _0x1C;
unsigned int _0x20;
unsigned int OUT_LL_ADDR_REG;
unsigned int _0x28;
unsigned int _0x2C;
unsigned char HASH_RESULT_REG[0x20];
unsigned char _0x50[0x1B0];
unsigned int SHA_CONFIG_REG;
unsigned int SHA_MSG_LENGTH_REG;
unsigned int _0x20C;
unsigned int _0x210;
unsigned int SHA_MSG_LEFT_REG;
unsigned int _0x218;
unsigned int _0x21C;
unsigned int _0x220;
unsigned int _0x224;
unsigned char _0x228[0x5C];
unsigned int AES_KEY_READ_DISABLE_REG;
unsigned int AES_KEYSLOT_FLAGS[0x10];
unsigned char _0x2C4[0x3C];
unsigned int _0x300;
unsigned int CRYPTO_REG;
unsigned int CRYPTO_CTR_REG[4];
unsigned int BLOCK_COUNT_REG;
unsigned int AES_KEYTABLE_ADDR;
unsigned int AES_KEYTABLE_DATA;
unsigned int _0x324;
unsigned int _0x328;
unsigned int _0x32C;
unsigned int CRYPTO_KEYTABLE_DST_REG;
unsigned char _0x334[0xC];
unsigned int RNG_CONFIG_REG;
unsigned int RNG_SRC_CONFIG_REG;
unsigned int RNG_RESEED_INTERVAL_REG;
unsigned char _0x34C[0xB4];
unsigned int RSA_CONFIG;
unsigned int RSA_KEY_SIZE_REG;
unsigned int RSA_EXP_SIZE_REG;
unsigned int RSA_KEY_READ_DISABLE_REG;
unsigned int RSA_KEYSLOT_FLAGS[2];
unsigned int _0x418;
unsigned int _0x41C;
unsigned int RSA_KEYTABLE_ADDR;
unsigned int RSA_KEYTABLE_DATA;
unsigned char RSA_OUTPUT[0x100];
unsigned char _0x528[0x2D8];
unsigned int FLAGS_REG;
unsigned int ERR_STATUS_REG;
unsigned int _0x808;
unsigned int _0x80C;
unsigned int _0x810;
unsigned int _0x814;
unsigned int _0x818;
unsigned int _0x81C;
unsigned char _0x820[0x17E0];
} security_engine_t;
typedef struct {
uint32_t address;
uint32_t size;
} se_addr_info_t;
typedef struct {
uint32_t num_entries; /* Set to total entries - 1 */
se_addr_info_t addr_info; /* This should really be an array...but for our use case it works. */
} se_ll_t;
/* TODO: Define constants for the C driver. */
/* WIP, API subject to change. */
static inline volatile security_engine_t *get_security_engine_address(void) {
return (volatile security_engine_t *)(mmio_get_device_address(MMIO_DEVID_SE));
}
/* This function MUST be registered to fire on the appropriate interrupt. */
void se_operation_completed(void);
void se_check_for_error(void);
void se_trigger_interrupt(void);
void se_clear_interrupts(void); /* TODO */
void se_verify_flags_cleared(void);
void set_aes_keyslot_flags(unsigned int keyslot, unsigned int flags);
void set_rsa_keyslot_flags(unsigned int keyslot, unsigned int flags);
void clear_aes_keyslot(unsigned int keyslot);
void clear_rsa_keyslot(unsigned int keyslot);
void set_aes_keyslot(unsigned int keyslot, const void *key, size_t key_size);
void decrypt_data_into_keyslot(unsigned int keyslot_dst, unsigned int keyslot_src, const void *wrapped_key, size_t wrapped_key_size);
void set_rsa_keyslot(unsigned int keyslot, const void *modulus, size_t modulus_size, const void *exponent, size_t exp_size);
void set_aes_keyslot_iv(unsigned int keyslot, const void *iv, size_t iv_size);
void set_se_ctr(const void *ctr);
/* Insecure AES API */
void se_aes_ctr_crypt_insecure(unsigned int keyslot, uint32_t out_ll_paddr, uint32_t in_ll_paddr, size_t size, const void *ctr, unsigned int (*callback)(void));
void se_aes_cbc_encrypt_insecure(unsigned int keyslot, uint32_t out_ll_paddr, uint32_t in_ll_paddr, size_t size, const void *iv, unsigned int (*callback)(void));
void se_aes_cbc_decrypt_insecure(unsigned int keyslot, uint32_t out_ll_paddr, uint32_t in_ll_paddr, size_t size, const void *iv, unsigned int (*callback)(void));
/* Secure AES API */
void se_compute_aes_128_cmac(unsigned int keyslot, void *cmac, size_t cmac_size, const void *data, size_t data_size);
void se_compute_aes_256_cmac(unsigned int keyslot, void *cmac, size_t cmac_size, const void *data, size_t data_size);
void se_aes_128_ecb_encrypt_block(unsigned int keyslot, void *dst, size_t dst_size, const void *src, size_t src_size);
void se_aes_256_ecb_encrypt_block(unsigned int keyslot, void *dst, size_t dst_size, const void *src, size_t src_size);
void se_aes_ctr_crypt(unsigned int keyslot, void *dst, size_t dst_size, const void *src, size_t src_size, const void *ctr, size_t ctr_size);
void se_aes_ecb_decrypt_block(unsigned int keyslot, void *dst, size_t dst_size, const void *src, size_t src_size);
/* Hash API */
void se_calculate_sha256(void *dst, const void *src, size_t src_size);
/* RSA API */
void se_exp_mod(unsigned int keyslot, void *buf, size_t size, unsigned int (*callback)(void));
void se_get_exp_mod_output(void *buf, size_t size);
void se_synchronous_exp_mod(unsigned int keyslot, void *dst, size_t dst_size, const void *src, size_t src_size);
/* RNG API */
void se_initialize_rng(unsigned int keyslot);
void se_generate_random(unsigned int keyslot, void *dst, size_t size);
/* TODO: SE context save API. */
#endif /* EXOSPHERE_SE_H */

View File

@@ -0,0 +1,63 @@
#include <stdint.h>
#include "utils.h"
#include "sealedkeys.h"
#include "se.h"
const uint8_t g_titlekey_seal_key_source[0x10] = {
0xCB, 0xB7, 0x6E, 0x38, 0xA1, 0xCB, 0x77, 0x0F, 0xB2, 0xA5, 0xB2, 0x9D, 0xD8, 0x56, 0x9F, 0x76
};
const uint8_t g_seal_key_sources[CRYPTOUSECASE_MAX][0x10] = {
{0xF4, 0x0C, 0x16, 0x26, 0x0D, 0x46, 0x3B, 0xE0, 0x8C, 0x6A, 0x56, 0xE5, 0x82, 0xD4, 0x1B, 0xF6},
{0x7F, 0x54, 0x2C, 0x98, 0x1E, 0x54, 0x18, 0x3B, 0xBA, 0x63, 0xBD, 0x4C, 0x13, 0x5B, 0xF1, 0x06},
{0xC7, 0x3F, 0x73, 0x60, 0xB7, 0xB9, 0x9D, 0x74, 0x0A, 0xF8, 0x35, 0x60, 0x1A, 0x18, 0x74, 0x63},
{0x0E, 0xE0, 0xC4, 0x33, 0x82, 0x66, 0xE8, 0x08, 0x39, 0x13, 0x41, 0x7D, 0x04, 0x64, 0x2B, 0x6D}
};
void seal_key_internal(void *dst, const void *src, const uint8_t *seal_key_source) {
decrypt_data_into_keyslot(KEYSLOT_SWITCH_TEMPKEY, KEYSLOT_SWITCH_SESSIONKEY, seal_key_source, 0x10);
se_aes_128_ecb_encrypt_block(KEYSLOT_SWITCH_TEMPKEY, dst, 0x10, src, 0x10);
}
void unseal_key_internal(unsigned int keyslot, const void *src, const uint8_t *seal_key_source) {
decrypt_data_into_keyslot(KEYSLOT_SWITCH_TEMPKEY, KEYSLOT_SWITCH_SESSIONKEY, seal_key_source, 0x10);
decrypt_data_into_keyslot(keyslot, KEYSLOT_SWITCH_TEMPKEY, src, 0x10);
}
void seal_titlekey(void *dst, size_t dst_size, const void *src, size_t src_size) {
if (dst_size != 0x10 || src_size != 0x10) {
generic_panic();
}
seal_key_internal(dst, src, g_titlekey_seal_key_source);
}
void unseal_titlekey(unsigned int keyslot, const void *src, size_t src_size) {
if (src_size != 0x10) {
generic_panic();
}
unseal_key_internal(keyslot, src, g_titlekey_seal_key_source);
}
void seal_key(void *dst, size_t dst_size, const void *src, size_t src_size, unsigned int usecase) {
if (usecase >= CRYPTOUSECASE_MAX || dst_size != 0x10 || src_size != 0x10) {
generic_panic();
}
seal_key_internal(dst, src, g_seal_key_sources[usecase]);
}
void unseal_key(unsigned int keyslot, const void *src, size_t src_size, unsigned int usecase) {
if (usecase >= CRYPTOUSECASE_MAX || src_size != 0x10) {
generic_panic();
}
unseal_key_internal(keyslot, src, g_seal_key_sources[usecase]);
}

View File

@@ -0,0 +1,21 @@
#ifndef EXOSPHERE_SEALED_KEYS_H
#define EXOSPHERE_SEALED_KEYS_H
#include <stdint.h>
/* Key sealing/unsealing functionality. */
#define CRYPTOUSECASE_AES 0
#define CRYPTOUSECASE_RSAPRIVATE 1
#define CRYPTOUSECASE_SECUREEXPMOD 2
#define CRYPTOUSECASE_RSAOAEP 3
#define CRYPTOUSECASE_MAX 4
void seal_titlekey(void *dst, size_t dst_size, const void *src, size_t src_size);
void unseal_titlekey(unsigned int keyslot, const void *src, size_t src_size);
void seal_key(void *dst, size_t dst_size, const void *src, size_t src_size, unsigned int usecase);
void unseal_key(unsigned int keyslot, const void *src, size_t src_size, unsigned int usecase);
#endif

562
exosphere/src/smc_api.c Normal file
View File

@@ -0,0 +1,562 @@
#include <stdint.h>
#include "utils.h"
#include "memory_map.h"
#include "configitem.h"
#include "cpu_context.h"
#include "lock.h"
#include "masterkey.h"
#include "mc.h"
#include "memory_map.h"
#include "pmc.h"
#include "randomcache.h"
#include "sealedkeys.h"
#include "smc_api.h"
#include "smc_user.h"
#include "se.h"
#include "userpage.h"
#include "titlekey.h"
#define SMC_USER_HANDLERS 0x13
#define SMC_PRIV_HANDLERS 0x9
/* User SMC prototypes */
uint32_t smc_set_config(smc_args_t *args);
uint32_t smc_get_config(smc_args_t *args);
uint32_t smc_check_status(smc_args_t *args);
uint32_t smc_get_result(smc_args_t *args);
uint32_t smc_exp_mod(smc_args_t *args);
uint32_t smc_get_random_bytes_for_user(smc_args_t *args);
uint32_t smc_generate_aes_kek(smc_args_t *args);
uint32_t smc_load_aes_key(smc_args_t *args);
uint32_t smc_crypt_aes(smc_args_t *args);
uint32_t smc_generate_specific_aes_key(smc_args_t *args);
uint32_t smc_compute_cmac(smc_args_t *args);
uint32_t smc_load_rsa_oaep_key(smc_args_t *args);
uint32_t smc_decrypt_rsa_private_key(smc_args_t *args);
uint32_t smc_load_secure_exp_mod_key(smc_args_t *args);
uint32_t smc_secure_exp_mod(smc_args_t *args);
uint32_t smc_unwrap_rsa_oaep_wrapped_titlekey(smc_args_t *args);
uint32_t smc_load_titlekey(smc_args_t *args);
uint32_t smc_unwrap_aes_wrapped_titlekey(smc_args_t *args);
/* Privileged SMC prototypes */
uint32_t smc_cpu_suspend(smc_args_t *args);
uint32_t smc_cpu_off(smc_args_t *args);
uint32_t smc_cpu_on(smc_args_t *args);
/* uint32_t smc_get_config(smc_args_t *args); */
uint32_t smc_get_random_bytes_for_priv(smc_args_t *args);
uint32_t smc_panic(smc_args_t *args);
uint32_t smc_configure_carveout(smc_args_t *args);
uint32_t smc_read_write_register(smc_args_t *args);
typedef struct {
uint32_t id;
uint32_t (*handler)(smc_args_t *args);
} smc_table_entry_t;
typedef struct {
smc_table_entry_t *handlers;
uint32_t num_handlers;
} smc_table_t;
smc_table_entry_t g_smc_user_table[SMC_USER_HANDLERS] = {
{0, NULL},
{0xC3000401, smc_set_config},
{0xC3000002, smc_get_config},
{0xC3000003, smc_check_status},
{0xC3000404, smc_get_result},
{0xC3000E05, smc_exp_mod},
{0xC3000006, smc_get_random_bytes_for_user},
{0xC3000007, smc_generate_aes_kek},
{0xC3000008, smc_load_aes_key},
{0xC3000009, smc_crypt_aes},
{0xC300000A, smc_generate_specific_aes_key},
{0xC300040B, smc_compute_cmac},
{0xC300100C, smc_load_rsa_oaep_key},
{0xC300100D, smc_decrypt_rsa_private_key},
{0xC300100E, smc_load_secure_exp_mod_key},
{0xC300060F, smc_secure_exp_mod},
{0xC3000610, smc_unwrap_rsa_oaep_wrapped_titlekey},
{0xC3000011, smc_load_titlekey},
{0xC3000012, smc_unwrap_aes_wrapped_titlekey}
};
smc_table_entry_t g_smc_priv_table[SMC_PRIV_HANDLERS] = {
{0, NULL},
{0xC4000001, smc_cpu_suspend},
{0x84000002, smc_cpu_off},
{0xC4000003, smc_cpu_on},
{0xC3000004, smc_get_config}, /* NOTE: Same function as for USER */
{0xC3000005, smc_get_random_bytes_for_priv},
{0xC3000006, smc_panic},
{0xC3000007, smc_configure_carveout},
{0xC3000008, smc_read_write_register}
};
smc_table_t g_smc_tables[2] = {
{ /* SMC_HANDLER_USER */
g_smc_user_table,
SMC_USER_HANDLERS
},
{ /* SMC_HANDLER_PRIV */
g_smc_priv_table,
SMC_PRIV_HANDLERS
}
};
bool g_is_user_smc_in_progress = false;
bool g_is_priv_smc_in_progress = false;
uintptr_t get_smc_core012_stack_address(void) {
return tzram_get_segment_address(TZRAM_SEGMENT_ID_CORE012_STACK) + 0x1000;
}
uintptr_t get_exception_entry_stack_address(unsigned int core_id) {
/* For core3, this is also the smc stack */
if (core_id == 3) {
return tzram_get_segment_address(TZRAM_SEGMENT_ID_CORE3_STACK) + 0x1000;
}
else {
return tzram_get_segment_address(TZRAM_SEGEMENT_ID_SECMON_EVT) + 0x80 * (core_id + 1);
}
}
/* Privileged SMC lock must be available to exceptions.s. */
void set_priv_smc_in_progress(void) {
lock_acquire(&g_is_priv_smc_in_progress);
}
void clear_priv_smc_in_progress(void) {
lock_release(&g_is_priv_smc_in_progress);
}
uint32_t (*g_smc_callback)(void *, uint64_t) = NULL;
uint64_t g_smc_callback_key = 0;
uint64_t try_set_smc_callback(uint32_t (*callback)(void *, uint64_t)) {
uint64_t key;
/* TODO: Atomics... */
if (g_smc_callback_key) {
return 0;
}
se_generate_random(KEYSLOT_SWITCH_RNGKEY, &key, sizeof(uint64_t));
g_smc_callback_key = key;
g_smc_callback = callback;
return key;
}
void clear_smc_callback(uint64_t key) {
/* TODO: Atomics... */
if (g_smc_callback_key == key) {
g_smc_callback_key = 0;
}
}
void call_smc_handler(uint32_t handler_id, smc_args_t *args) {
unsigned char smc_id;
unsigned int result;
unsigned int (*smc_handler)(smc_args_t *args);
/* Validate top-level handler. */
if (handler_id != SMC_HANDLER_USER && handler_id != SMC_HANDLER_PRIV) {
generic_panic();
}
/* Validate core is appropriate for handler. */
if (handler_id == SMC_HANDLER_USER && get_core_id() != 3) {
/* USER SMCs must be called via svcCallSecureMonitor on core 3 (where spl runs) */
generic_panic();
}
/* Validate sub-handler index */
if ((smc_id = (unsigned char)args->X[0]) >= g_smc_tables[handler_id].num_handlers) {
generic_panic();
}
/* Validate sub-handler */
if (g_smc_tables[handler_id].handlers[smc_id].id != args->X[0]) {
generic_panic();
}
/* Validate handler. */
if ((smc_handler = g_smc_tables[handler_id].handlers[smc_id].handler) == NULL) {
generic_panic();
}
/* Call function. */
args->X[0] = smc_handler(args);
(void)result; /* FIXME: result unused */
}
uint32_t smc_wrapper_sync(smc_args_t *args, uint32_t (*handler)(smc_args_t *)) {
uint32_t result;
if (!lock_try_acquire(&g_is_user_smc_in_progress)) {
return 3;
}
result = handler(args);
lock_release(&g_is_user_smc_in_progress);
return result;
}
uint32_t smc_wrapper_async(smc_args_t *args, uint32_t (*handler)(smc_args_t *), uint32_t (*callback)(void *, uint64_t)) {
uint32_t result;
uint64_t key;
if (!lock_try_acquire(&g_is_user_smc_in_progress)) {
return 3;
}
if ((key = try_set_smc_callback(callback)) != 0) {
result = handler(args);
if (result == 0) {
/* Pass the status check key back to userland. */
args->X[1] = key;
/* Early return, leaving g_is_user_smc_in_progress locked */
return result;
} else {
/* No status to check. */
clear_smc_callback(key);
}
} else {
/* smcCheckStatus needs to be called. */
result = 3;
}
lock_release(&g_is_user_smc_in_progress);
return result;
}
uint32_t smc_set_config(smc_args_t *args) {
/* Actual value presumed in X3 on hardware. */
return configitem_set((enum ConfigItem)args->X[1], args->X[3]);
}
uint32_t smc_get_config(smc_args_t *args) {
uint64_t out_item = 0;
uint32_t result;
result = configitem_get((enum ConfigItem)args->X[1], &out_item);
args->X[1] = out_item;
return result;
}
uint32_t smc_check_status(smc_args_t *args) {
if (g_smc_callback_key == 0) {
return 4;
}
if (args->X[1] != g_smc_callback_key) {
return 5;
}
args->X[1] = g_smc_callback(NULL, 0);
g_smc_callback_key = 0;
return 0;
}
uint32_t smc_get_result(smc_args_t *args) {
uint32_t status;
unsigned char result_buf[0x400];
upage_ref_t page_ref;
void *user_address = (void *)args->X[2];
if (g_smc_callback_key == 0) {
return 4;
}
if (args->X[1] != g_smc_callback_key) {
return 5;
}
/* Check result size */
if (args->X[3] > 0x400) {
return 2;
}
args->X[1] = g_smc_callback(result_buf, args->X[3]);
g_smc_callback_key = 0;
/* Initialize page reference. */
if (upage_init(&page_ref, user_address) == 0) {
return 2;
}
/* Copy result output back to user. */
if (secure_copy_to_user(&page_ref, user_address, result_buf, (size_t)args->X[3]) == 0) {
return 2;
}
return 0;
(void)status; /* FIXME: status unused */
}
uint32_t smc_exp_mod_get_result(void *buf, uint64_t size) {
if (get_exp_mod_done() != 1) {
return 3;
}
if (size != 0x100) {
return 2;
}
se_get_exp_mod_output(buf, 0x100);
/* smc_exp_mod is done now. */
lock_release(&g_is_user_smc_in_progress);
return 0;
}
uint32_t smc_exp_mod(smc_args_t *args) {
return smc_wrapper_async(args, user_exp_mod, smc_exp_mod_get_result);
}
uint32_t smc_get_random_bytes_for_user(smc_args_t *args) {
return smc_wrapper_sync(args, user_get_random_bytes);
}
uint32_t smc_generate_aes_kek(smc_args_t *args) {
return smc_wrapper_sync(args, user_generate_aes_kek);
}
uint32_t smc_load_aes_key(smc_args_t *args) {
return smc_wrapper_sync(args, user_load_aes_key);
}
uint32_t smc_crypt_aes_status_check(void *buf, uint64_t size) {
/* Buf and size are unused. */
if (get_crypt_aes_done() != 1) {
return 3;
}
/* smc_crypt_aes is done now. */
lock_release(&g_is_user_smc_in_progress);
return 0;
}
uint32_t smc_crypt_aes(smc_args_t *args) {
return smc_wrapper_async(args, user_crypt_aes, smc_crypt_aes_status_check);
}
uint32_t smc_generate_specific_aes_key(smc_args_t *args) {
return smc_wrapper_sync(args, user_generate_specific_aes_key);
}
uint32_t smc_compute_cmac(smc_args_t *args) {
return smc_wrapper_sync(args, user_compute_cmac);
}
uint32_t smc_load_rsa_oaep_key(smc_args_t *args) {
return smc_wrapper_sync(args, user_load_rsa_oaep_key);
}
uint32_t smc_decrypt_rsa_private_key(smc_args_t *args) {
return smc_wrapper_sync(args, user_decrypt_rsa_private_key);
}
uint32_t smc_load_secure_exp_mod_key(smc_args_t *args) {
return smc_wrapper_sync(args, user_load_secure_exp_mod_key);
}
uint32_t smc_secure_exp_mod(smc_args_t *args) {
return smc_wrapper_async(args, user_secure_exp_mod, smc_exp_mod_get_result);
}
uint32_t smc_unwrap_rsa_oaep_wrapped_titlekey_get_result(void *buf, uint64_t size) {
uint64_t *p_sealed_key = (uint64_t *)buf;
uint8_t rsa_wrapped_titlekey[0x100];
uint8_t aes_wrapped_titlekey[0x10];
uint8_t titlekey[0x10];
uint64_t sealed_titlekey[2];
if (get_exp_mod_done() != 1) {
return 3;
}
if (size != 0x10) {
return 2;
}
se_get_exp_mod_output(rsa_wrapped_titlekey, 0x100);
if (tkey_rsa_oaep_unwrap(aes_wrapped_titlekey, 0x10, rsa_wrapped_titlekey, 0x100) != 0x10) {
/* Failed to extract RSA OAEP wrapped key. */
lock_release(&g_is_user_smc_in_progress);
return 2;
}
tkey_aes_unwrap(titlekey, 0x10, aes_wrapped_titlekey, 0x10);
seal_titlekey(sealed_titlekey, 0x10, titlekey, 0x10);
p_sealed_key[0] = sealed_titlekey[0];
p_sealed_key[1] = sealed_titlekey[1];
/* smc_unwrap_rsa_oaep_wrapped_titlekey is done now. */
lock_release(&g_is_user_smc_in_progress);
return 0;
}
uint32_t smc_unwrap_rsa_oaep_wrapped_titlekey(smc_args_t *args) {
return smc_wrapper_async(args, user_unwrap_rsa_oaep_wrapped_titlekey, smc_unwrap_rsa_oaep_wrapped_titlekey_get_result);
}
uint32_t smc_load_titlekey(smc_args_t *args) {
return smc_wrapper_sync(args, user_load_titlekey);
}
uint32_t smc_unwrap_aes_wrapped_titlekey(smc_args_t *args) {
return smc_wrapper_sync(args, user_unwrap_aes_wrapped_titlekey);
}
uint32_t smc_cpu_on(smc_args_t *args) {
return cpu_on((uint32_t)args->X[1], args->X[2], args->X[3]);
}
uint32_t smc_cpu_off(smc_args_t *args) {
return cpu_off();
}
/* Wrapper for cpu_suspend */
uint32_t cpu_suspend_wrapper(smc_args_t *args) {
return cpu_suspend(args->X[1], args->X[2], args->X[3]);
}
uint32_t smc_cpu_suspend(smc_args_t *args) {
return smc_wrapper_sync(args, cpu_suspend_wrapper);
}
uint32_t smc_get_random_bytes_for_priv(smc_args_t *args) {
/* This is an interesting SMC. */
/* The kernel must NEVER be unable to get random bytes, if it needs them */
/* As such: */
uint32_t result;
if (!lock_try_acquire(&g_is_user_smc_in_progress)) {
if (args->X[1] > 0x38) {
return 2;
}
/* Retrieve bytes from the cache. */
size_t num_bytes = (size_t)args->X[1];
randomcache_getbytes(&args->X[1], num_bytes);
result = 0;
} else {
/* If the kernel isn't denied service by a usermode SMC, generate fresh random bytes. */
result = user_get_random_bytes(args);
/* Also, refill our cache while we have the chance in case we get denied later. */
randomcache_refill();
lock_release(&g_is_user_smc_in_progress);
}
return result;
}
uint32_t smc_read_write_register(smc_args_t *args) {
uint64_t address = args->X[1];
uint32_t mask = (uint32_t)(args->X[2]);
uint32_t value = (uint32_t)(args->X[3]);
volatile uint32_t *p_mmio = NULL;
/* Address must be aligned. */
if (address & 3) {
return 2;
}
/* Check for PMC registers. */
if (0x7000E400 <= address && address <= 0x7000EFFF) {
const uint8_t pmc_whitelist[0x28] = {
0xB9, 0xF9, 0x07, 0x00, 0x00, 0x00, 0x80, 0x03,
0x00, 0x00, 0x00, 0x17, 0x00, 0xC4, 0x07, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x20, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x40, 0x00
};
/* Offset = Address - PMC_BASE */
uint32_t offset = (uint32_t)(address - 0x7000E400);
uint32_t wl_ind = (offset >> 5);
/* If address is whitelisted, allow write. */
if (wl_ind < sizeof(pmc_whitelist) && (pmc_whitelist[wl_ind] & (1 << ((offset >> 2) & 0x7)))) {
p_mmio = (volatile uint32_t *)(PMC_BASE + offset);
} else {
return 2;
}
} else if (mkey_get_revision() >= MASTERKEY_REVISION_400_CURRENT && mmio_get_device_pa(MMIO_DEVID_MC) <= address &&
address < mmio_get_device_pa(MMIO_DEVID_MC) + 0x1000) {
/* Memory Controller RW supported only on 4.0.0+ */
const uint8_t mc_whitelist[0x68] = {
0x9F, 0x31, 0x30, 0x00, 0xF0, 0xFF, 0xF7, 0x01,
0xCD, 0xFE, 0xC0, 0xFE, 0x00, 0x00, 0x00, 0x00,
0x03, 0x40, 0x73, 0x3E, 0x2F, 0x00, 0x00, 0x6E,
0x30, 0x05, 0x06, 0xB0, 0x71, 0xC8, 0x43, 0x04,
0x80, 0x1F, 0x08, 0x80, 0x03, 0x00, 0x0E, 0x00,
0x08, 0x00, 0xE0, 0x00, 0x0E, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x30, 0xF0, 0x03, 0x03, 0x30,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x31, 0x00, 0x40, 0x00, 0x00,
0x00, 0x03, 0x00, 0x00, 0xE4, 0xFF, 0xFF, 0x01,
0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0xFE, 0x0F,
0x01, 0x00, 0x80, 0x00, 0x00, 0x08, 0x00, 0x00
};
uint32_t offset = (uint32_t)(address - 0x70019000);
uint32_t wl_ind = (offset >> 5);
/* If address is whitelisted, allow write. */
if (wl_ind < sizeof(mc_whitelist) && (mc_whitelist[wl_ind] & (1 << ((offset >> 2) & 0x7)))) {
p_mmio = (volatile uint32_t *)(mmio_get_device_address(MMIO_DEVID_MC) + offset);
} else {
/* These addresses are not allowed by the whitelist. */
/* They correspond to SMMU DISABLE for the BPMP, and for APB-DMA. */
/* However, smcReadWriteRegister returns 0 for these addresses despite not actually performing the write. */
/* This is "probably" to fuck with hackers who got access to smcReadWriteRegister and are trying to get */
/* control of the BPMP for jamais vu etc., since there's no other reason to return 0 despite failure. */
if (address == 0x7001923C || address == 0x70019298) {
return 0;
}
return 2;
}
}
/* Perform actual write. */
if (p_mmio != NULL) {
uint32_t old_value;
/* Write whole value. */
if (mask == 0xFFFFFFFF) {
old_value = 0;
} else {
old_value = *p_mmio;
}
if (mask) {
*p_mmio = (old_value & ~mask) | (value & mask);
}
/* Return old value. */
args->X[1] = old_value;
return 0;
}
return 2;
}
uint32_t smc_configure_carveout(smc_args_t *args) {
if (args->X[0] > 1) {
return 2;
}
unsigned int carveout_id = (unsigned int)args->X[1];
uint64_t address = args->X[2];
uint64_t size = args->X[3];
/* Ensure carveout isn't too big. */
if (size > KERNEL_CARVEOUT_SIZE_MAX) {
return 2;
}
/* Configuration is one-shot, and cannot be done multiple times. */
static bool configured_carveouts[2] = {false, false};
if (configured_carveouts[carveout_id]) {
return 2;
}
configure_kernel_carveout(carveout_id + 4, address, size);
configured_carveouts[carveout_id] = true;
return 0;
}
uint32_t smc_panic(smc_args_t *args) {
(void)args;
return 0;
/* TODO */
}

21
exosphere/src/smc_api.h Normal file
View File

@@ -0,0 +1,21 @@
#ifndef EXOSPHERE_SMC_API_H
#define EXOSPHERE_SMC_API_H
#include <stdint.h>
#define SMC_HANDLER_USER 0
#define SMC_HANDLER_PRIV 1
typedef struct {
uint64_t X[8];
} smc_args_t;
void set_priv_smc_in_progress(void);
void clear_priv_smc_in_progress(void);
uintptr_t get_smc_core012_stack_address(void);
uintptr_t get_exception_entry_stack_address(unsigned int core_id);
void call_smc_handler(unsigned int handler_id, smc_args_t *args);
#endif

617
exosphere/src/smc_user.c Normal file
View File

@@ -0,0 +1,617 @@
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include "utils.h"
#include "cache.h"
#include "configitem.h"
#include "gcm.h"
#include "masterkey.h"
#include "smc_api.h"
#include "smc_user.h"
#include "se.h"
#include "fuse.h"
#include "sealedkeys.h"
#include "userpage.h"
#include "titlekey.h"
/* Globals. */
static bool g_crypt_aes_done = false;
static bool g_exp_mod_done = false;
static uint8_t g_secure_exp_mod_exponent[0x100];
static uint8_t g_rsa_oaep_exponent[0x100];
void set_exp_mod_done(bool done) {
g_exp_mod_done = done;
}
bool get_exp_mod_done(void) {
return g_exp_mod_done;
}
uint32_t exp_mod_done_handler(void) {
set_exp_mod_done(true);
se_trigger_interrupt();
return 0;
}
uint32_t user_exp_mod(smc_args_t *args) {
uint8_t modulus[0x100];
uint8_t exponent[0x100];
uint8_t input[0x100];
upage_ref_t page_ref;
/* Validate size. */
if (args->X[4] == 0 || args->X[4] > 0x100 || (args->X[4] & 3) != 0) {
return 2;
}
size_t exponent_size = (size_t)args->X[4];
void *user_input = (void *)args->X[1];
void *user_exponent = (void *)args->X[2];
void *user_modulus = (void *)args->X[3];
/* Copy user data into secure memory. */
if (upage_init(&page_ref, user_input) == 0) {
return 2;
}
if (user_copy_to_secure(&page_ref, input, user_input, 0x100) == 0) {
return 2;
}
if (user_copy_to_secure(&page_ref, exponent, user_exponent, exponent_size) == 0) {
return 2;
}
if (user_copy_to_secure(&page_ref, modulus, user_modulus, 0x100) == 0) {
return 2;
}
set_exp_mod_done(false);
/* Hardcode RSA keyslot 0. */
set_rsa_keyslot(0, modulus, 0x100, exponent, exponent_size);
se_exp_mod(0, input, 0x100, exp_mod_done_handler);
return 0;
}
uint32_t user_get_random_bytes(smc_args_t *args) {
uint8_t random_bytes[0x40];
if (args->X[1] > 0x38) {
return 2;
}
size_t size = (size_t)args->X[1];
flush_dcache_range(random_bytes, random_bytes + size);
se_generate_random(KEYSLOT_SWITCH_RNGKEY, random_bytes, size);
flush_dcache_range(random_bytes, random_bytes + size);
memcpy(&args->X[1], random_bytes, size);
return 0;
}
uint32_t user_generate_aes_kek(smc_args_t *args) {
uint64_t wrapped_kek[2];
uint8_t kek_source[0x10];
uint64_t kek[2];
uint64_t sealed_kek[2];
wrapped_kek[0] = args->X[1];
wrapped_kek[1] = args->X[2];
unsigned int master_key_rev = (unsigned int)args->X[3];
if (master_key_rev > 0) {
master_key_rev -= 1; /* GenerateAesKek offsets by one. */
}
if (master_key_rev >= MASTERKEY_REVISION_MAX) {
return 2;
}
uint64_t packed_options = args->X[4];
if (packed_options > 0xFF) {
return 2;
}
/* Switched the output based on how the system was booted. */
uint8_t mask_id = (uint8_t)((packed_options >> 1) & 3);
/* Switches the output based on how it will be used. */
uint8_t usecase = (uint8_t)((packed_options >> 5) & 3);
/* Switched the output based on whether it should be console unique. */
bool is_personalized = (int)(packed_options & 1);
bool is_recovery_boot = configitem_is_recovery_boot();
/* Mask 2 is only allowed when booted from recovery. */
if (mask_id == 2 && !is_recovery_boot) {
return 2;
}
/* Mask 1 is only allowed when booted normally. */
if (mask_id == 1 && is_recovery_boot) {
return 2;
}
/* Masks 0, 3 are allowed all the time. */
const uint8_t kek_seeds[4][0x10] = {
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
{0xA2, 0xAB, 0xBF, 0x9C, 0x92, 0x2F, 0xBB, 0xE3, 0x78, 0x79, 0x9B, 0xC0, 0xCC, 0xEA, 0xA5, 0x74},
{0x57, 0xE2, 0xD9, 0x45, 0xE4, 0x92, 0xF4, 0xFD, 0xC3, 0xF9, 0x86, 0x38, 0x89, 0x78, 0x9F, 0x3C},
{0xE5, 0x4D, 0x9A, 0x02, 0xF0, 0x4F, 0x5F, 0xA8, 0xAD, 0x76, 0x0A, 0xF6, 0x32, 0x95, 0x59, 0xBB}
};
const uint8_t kek_masks[4][0x10] = {
{0x4D, 0x87, 0x09, 0x86, 0xC4, 0x5D, 0x20, 0x72, 0x2F, 0xBA, 0x10, 0x53, 0xDA, 0x92, 0xE8, 0xA9},
{0x25, 0x03, 0x31, 0xFB, 0x25, 0x26, 0x0B, 0x79, 0x8C, 0x80, 0xD2, 0x69, 0x98, 0xE2, 0x22, 0x77},
{0x76, 0x14, 0x1D, 0x34, 0x93, 0x2D, 0xE1, 0x84, 0x24, 0x7B, 0x66, 0x65, 0x55, 0x04, 0x65, 0x81},
{0xAF, 0x3D, 0xB7, 0xF3, 0x08, 0xA2, 0xD8, 0xA2, 0x08, 0xCA, 0x18, 0xA8, 0x69, 0x46, 0xC9, 0x0B}
};
/* Create kek source. */
for (unsigned int i = 0; i < 0x10; i++) {
kek_source[i] = kek_seeds[usecase][i] ^ kek_masks[mask_id][i];
}
unsigned int keyslot;
if (is_personalized) {
/* Behavior changed in 4.0.0. */
if (mkey_get_revision() >= MASTERKEY_REVISION_400_CURRENT) {
if (master_key_rev >= 1) {
keyslot = KEYSLOT_SWITCH_DEVICEKEY; /* New device key, 4.x. */
} else {
keyslot = KEYSLOT_SWITCH_4XOLDDEVICEKEY; /* Old device key, 4.x. */
}
} else {
keyslot = KEYSLOT_SWITCH_DEVICEKEY;
}
} else {
keyslot = mkey_get_keyslot(master_key_rev);
}
/* Derive kek. */
decrypt_data_into_keyslot(KEYSLOT_SWITCH_TEMPKEY, keyslot, kek_source, 0x10);
se_aes_ecb_decrypt_block(KEYSLOT_SWITCH_TEMPKEY, kek, 0x10, wrapped_kek, 0x10);
/* Seal kek. */
seal_key(sealed_kek, 0x10, kek, 0x10, usecase);
args->X[1] = sealed_kek[0];
args->X[2] = sealed_kek[1];
return 0;
}
uint32_t user_load_aes_key(smc_args_t *args) {
uint64_t sealed_kek[2];
uint64_t wrapped_key[2];
uint32_t keyslot = (uint32_t)args->X[1];
if (keyslot > 3) {
return 2;
}
/* Copy keydata */
sealed_kek[0] = args->X[2];
sealed_kek[1] = args->X[3];
wrapped_key[0] = args->X[4];
wrapped_key[1] = args->X[5];
/* Unseal the kek. */
unseal_key(KEYSLOT_SWITCH_TEMPKEY, sealed_kek, 0x10, CRYPTOUSECASE_AES);
/* Unwrap the key. */
decrypt_data_into_keyslot(keyslot, KEYSLOT_SWITCH_TEMPKEY, wrapped_key, 0x10);
return 0;
}
void set_crypt_aes_done(bool done) {
g_crypt_aes_done = done;
}
bool get_crypt_aes_done(void) {
return g_crypt_aes_done;
}
uint32_t crypt_aes_done_handler(void) {
se_check_for_error();
set_crypt_aes_done(true);
se_trigger_interrupt();
return 0;
}
uint32_t user_crypt_aes(smc_args_t *args) {
uint32_t keyslot = args->X[1] & 3;
uint32_t mode = (args->X[1] >> 4) & 3;
uint64_t iv_ctr[2];
iv_ctr[0] = args->X[2];
iv_ctr[1] = args->X[3];
uint32_t in_ll_paddr = (uint32_t)(args->X[4]);
uint32_t out_ll_paddr = (uint32_t)(args->X[5]);
size_t size = args->X[6];
if (size & 0xF) {
generic_panic();
}
set_crypt_aes_done(false);
uint64_t result = 0;
switch (mode) {
case 0: /* CBC Encryption */
se_aes_cbc_encrypt_insecure(keyslot, out_ll_paddr, in_ll_paddr, size, iv_ctr, crypt_aes_done_handler);
result = 0;
break;
case 1: /* CBC Decryption */
se_aes_cbc_decrypt_insecure(keyslot, out_ll_paddr, in_ll_paddr, size, iv_ctr, crypt_aes_done_handler);
result = 0;
break;
case 2: /* CTR "Encryption" */
se_aes_ctr_crypt_insecure(keyslot, out_ll_paddr, in_ll_paddr, size, iv_ctr, crypt_aes_done_handler);
result = 0;
break;
case 3:
default:
result = 1;
break;
}
return result;
}
uint32_t user_generate_specific_aes_key(smc_args_t *args) {
uint64_t wrapped_key[2];
uint8_t key[0x10];
unsigned int master_key_rev;
bool should_mask;
wrapped_key[0] = args->X[1];
wrapped_key[1] = args->X[2];
if (args->X[4] > MASTERKEY_REVISION_MAX) {
return 2;
}
master_key_rev = (unsigned int)(args->X[4]);
if (args->X[3] > 1) {
return 2;
}
should_mask = args->X[3] != 0;
unsigned int keyslot;
/* Behavior changed in 4.0.0. */
if (mkey_get_revision() >= MASTERKEY_REVISION_400_CURRENT) {
if (master_key_rev >= 2) {
keyslot = KEYSLOT_SWITCH_DEVICEKEY; /* New device key, 4.x. */
} else {
keyslot = KEYSLOT_SWITCH_4XOLDDEVICEKEY; /* Old device key, 4.x. */
}
} else {
keyslot = KEYSLOT_SWITCH_DEVICEKEY;
}
if (fuse_get_bootrom_patch_version() < 0x7F) {
/* On dev units, use a fixed "all-zeroes" seed. */
/* Yes, this data really is all-zero in actual TrustZone .rodata. */
uint8_t dev_specific_aes_key_source[0x10] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t dev_specific_aes_key_ctr[0x10] = {0x3C, 0xD5, 0x92, 0xEC, 0x68, 0x31, 0x4A, 0x06, 0xD4, 0x1B, 0x0C, 0xD9, 0xF6, 0x2E, 0xD9, 0xE9};
uint8_t dev_specific_aes_key_mask[0x10] = {0xAC, 0xCA, 0x9A, 0xCA, 0xFF, 0x2E, 0xB9, 0x22, 0xCC, 0x1F, 0x4F, 0xAD, 0xDD, 0x77, 0x21, 0x1E};
flush_dcache_range(key, key + 0x10);
se_aes_ctr_crypt(keyslot, key, 0x10, dev_specific_aes_key_source, 0x10, dev_specific_aes_key_ctr, 0x10);
flush_dcache_range(key, key + 0x10);
if (should_mask) {
for (unsigned int i = 0; i < 0x10; i++) {
key[i] ^= dev_specific_aes_key_mask[i];
}
}
} else {
/* On retail, standard kek->key decryption. */
uint8_t retail_specific_aes_key_source[0x10] = {0xE2, 0xD6, 0xB8, 0x7A, 0x11, 0x9C, 0xB8, 0x80, 0xE8, 0x22, 0x88, 0x8A, 0x46, 0xFB, 0xA1, 0x95};
decrypt_data_into_keyslot(KEYSLOT_SWITCH_TEMPKEY, keyslot, retail_specific_aes_key_source, 0x10);
se_aes_ecb_decrypt_block(KEYSLOT_SWITCH_TEMPKEY, key, 0x10, wrapped_key, 0x10);
}
args->X[1] = key[0];
args->X[2] = key[1];
return 0;
}
uint32_t user_compute_cmac(smc_args_t *args) {
uint32_t keyslot = (uint32_t)args->X[1];
void *user_address = (void *)args->X[2];
size_t size = (size_t)args->X[3];
uint8_t user_data[0x400];
uint64_t result_cmac[2];
upage_ref_t page_ref;
/* Validate keyslot and size. */
if (keyslot > 3 || args->X[3] > 0x400) {
return 2;
}
if (upage_init(&page_ref, user_address) == 0 || user_copy_to_secure(&page_ref, user_data, user_address, size) == 0) {
return 2;
}
flush_dcache_range(user_data, user_data + size);
se_compute_aes_128_cmac(keyslot, result_cmac, 0x10, user_data, size);
/* Copy CMAC out. */
args->X[1] = result_cmac[0];
args->X[2] = result_cmac[1];
return 0;
}
uint32_t user_load_rsa_oaep_key(smc_args_t *args) {
uint64_t sealed_kek[2];
uint64_t wrapped_key[2];
bool is_personalized;
uint8_t user_data[0x400];
void *user_address;
size_t size;
upage_ref_t page_ref;
/* Copy keydata */
sealed_kek[0] = args->X[1];
sealed_kek[1] = args->X[2];
if (args->X[3] > 1) {
return 2;
}
is_personalized = args->X[3] != 0;
user_address = (void *)args->X[4];
size = (size_t)args->X[5];
wrapped_key[0] = args->X[6];
wrapped_key[1] = args->X[7];
if (is_personalized && size != 0x240) {
return 2;
}
if (!is_personalized && (size != 0x220 || fuse_get_bootrom_patch_version() >= 0x7F)) {
return 2;
}
if (upage_init(&page_ref, user_address) == 0 || user_copy_to_secure(&page_ref, user_data, user_address, size) == 0) {
return 2;
}
/* Ensure that our private key is 0x100 bytes. */
if (gcm_decrypt_key(user_data, size, user_data, size, sealed_kek, 0x10, wrapped_key, 0x10, CRYPTOUSECASE_RSAOAEP, is_personalized) < 0x100) {
return 2;
}
memcpy(g_rsa_oaep_exponent, user_data, 0x100);
return 0;
}
uint32_t user_decrypt_rsa_private_key(smc_args_t *args) {
uint64_t sealed_kek[2];
uint64_t wrapped_key[2];
bool is_personalized;
uint8_t user_data[0x400];
void *user_address;
size_t size;
upage_ref_t page_ref;
/* Copy keydata */
sealed_kek[0] = args->X[1];
sealed_kek[1] = args->X[2];
if (args->X[3] > 1) {
return 2;
}
is_personalized = args->X[3] != 0;
user_address = (void *)args->X[4];
size = (size_t)args->X[5];
wrapped_key[0] = args->X[6];
wrapped_key[1] = args->X[7];
if (size > 0x240) {
return 2;
}
if (is_personalized && size < 0x31) {
return 2;
}
if (!is_personalized && (size < 0x11 || fuse_get_bootrom_patch_version() >= 0x7F)) {
return 2;
}
if (upage_init(&page_ref, user_address) == 0 || user_copy_to_secure(&page_ref, user_data, user_address, size) == 0) {
return 2;
}
size_t out_size;
if ((out_size = gcm_decrypt_key(user_data, size, user_data, size, sealed_kek, 0x10, wrapped_key, 0x10, CRYPTOUSECASE_RSAPRIVATE, is_personalized)) == 0) {
return 2;
}
if (secure_copy_to_user(&page_ref, user_address, user_data, size) == 0) {
return 2;
}
args->X[1] = out_size;
return 0;
}
uint32_t user_load_secure_exp_mod_key(smc_args_t *args) {
uint64_t sealed_kek[2];
uint64_t wrapped_key[2];
bool is_personalized;
uint8_t user_data[0x400];
void *user_address;
size_t size;
upage_ref_t page_ref;
/* Copy keydata */
sealed_kek[0] = args->X[1];
sealed_kek[1] = args->X[2];
if (args->X[3] > 1) {
return 2;
}
is_personalized = args->X[3] != 0;
user_address = (void *)args->X[4];
size = (size_t)args->X[5];
wrapped_key[0] = args->X[6];
wrapped_key[1] = args->X[7];
if (is_personalized && size != 0x130) {
return 2;
}
if (!is_personalized && (size != 0x110 || fuse_get_bootrom_patch_version() >= 0x7F)) {
return 2;
}
if (upage_init(&page_ref, user_address) == 0 || user_copy_to_secure(&page_ref, user_data, user_address, size) == 0) {
return 2;
}
size_t out_size;
/* Ensure that our key is non-zero bytes. */
if ((out_size = gcm_decrypt_key(user_data, size, user_data, size, sealed_kek, 0x10, wrapped_key, 0x10, CRYPTOUSECASE_SECUREEXPMOD, is_personalized)) == 0) {
return 2;
}
/* Copy key to global. */
if (out_size <= 0x100) {
memcpy(g_secure_exp_mod_exponent, user_data, out_size);
} else {
memcpy(g_secure_exp_mod_exponent, user_data, 0x100);
}
return 0;
}
uint32_t user_secure_exp_mod(smc_args_t *args) {
uint8_t modulus[0x100];
uint8_t input[0x100];
upage_ref_t page_ref;
void *user_input = (void *)args->X[1];
void *user_modulus = (void *)args->X[2];
/* Copy user data into secure memory. */
if (upage_init(&page_ref, user_input) == 0) {
return 2;
}
if (user_copy_to_secure(&page_ref, input, user_input, 0x100) == 0) {
return 2;
}
if (user_copy_to_secure(&page_ref, modulus, user_modulus, 0x100) == 0) {
return 2;
}
set_exp_mod_done(false);
/* Hardcode RSA keyslot 0. */
set_rsa_keyslot(0, modulus, 0x100, g_secure_exp_mod_exponent, 0x100);
se_exp_mod(0, input, 0x100, exp_mod_done_handler);
return 0;
}
uint32_t user_unwrap_rsa_oaep_wrapped_titlekey(smc_args_t *args) {
uint8_t modulus[0x100];
uint8_t wrapped_key[0x100];
upage_ref_t page_ref;
void *user_wrapped_key = (void *)args->X[1];
void *user_modulus = (void *)args->X[2];
unsigned int master_key_rev = (unsigned int)args->X[7];
if (master_key_rev >= MASTERKEY_REVISION_MAX) {
return 2;
}
/* Copy user data into secure memory. */
if (upage_init(&page_ref, user_wrapped_key) == 0) {
return 2;
}
if (user_copy_to_secure(&page_ref, wrapped_key, user_wrapped_key, 0x100) == 0) {
return 2;
}
if (user_copy_to_secure(&page_ref, modulus, user_modulus, 0x100) == 0) {
return 2;
}
set_exp_mod_done(false);
/* Expected label_hash occupies args->X[3] to args->X[6]. */
tkey_set_expected_label_hash(&args->X[3]);
tkey_set_master_key_rev(master_key_rev);
/* Hardcode RSA keyslot 0. */
set_rsa_keyslot(0, modulus, 0x100, g_rsa_oaep_exponent, 0x100);
se_exp_mod(0, wrapped_key, 0x100, exp_mod_done_handler);
return 0;
}
uint32_t user_load_titlekey(smc_args_t *args) {
uint64_t sealed_titlekey[2];
uint32_t keyslot = (uint32_t)args->X[1];
if (keyslot > 3) {
return 2;
}
/* Copy keydata */
sealed_titlekey[0] = args->X[2];
sealed_titlekey[1] = args->X[3];
/* Unseal the key. */
unseal_titlekey(keyslot, sealed_titlekey, 0x10);
return 0;
}
uint32_t user_unwrap_aes_wrapped_titlekey(smc_args_t *args) {
uint64_t aes_wrapped_titlekey[2];
uint8_t titlekey[0x10];
uint64_t sealed_titlekey[2];
aes_wrapped_titlekey[0] = args->X[1];
aes_wrapped_titlekey[1] = args->X[2];
unsigned int master_key_rev = (unsigned int)args->X[3];
if (master_key_rev >= MASTERKEY_REVISION_MAX) {
return 2;
}
tkey_set_master_key_rev(master_key_rev);
tkey_aes_unwrap(titlekey, 0x10, aes_wrapped_titlekey, 0x10);
seal_titlekey(sealed_titlekey, 0x10, titlekey, 0x10);
args->X[1] = sealed_titlekey[0];
args->X[2] = sealed_titlekey[1];
return 0; /* FIXME: what should we return there */
}

28
exosphere/src/smc_user.h Normal file
View File

@@ -0,0 +1,28 @@
#ifndef EXOSPHERE_SMC_USER_H
#define EXOSPHERE_SMC_USER_H
#include "smc_api.h"
uint32_t user_exp_mod(smc_args_t *args);
uint32_t user_get_random_bytes(smc_args_t *args);
uint32_t user_generate_aes_kek(smc_args_t *args);
uint32_t user_load_aes_key(smc_args_t *args);
uint32_t user_crypt_aes(smc_args_t *args);
uint32_t user_generate_specific_aes_key(smc_args_t *args);
uint32_t user_compute_cmac(smc_args_t *args);
uint32_t user_load_rsa_oaep_key(smc_args_t *args);
uint32_t user_decrypt_rsa_private_key(smc_args_t *args);
uint32_t user_load_secure_exp_mod_key(smc_args_t *args);
uint32_t user_secure_exp_mod(smc_args_t *args);
uint32_t user_unwrap_rsa_oaep_wrapped_titlekey(smc_args_t *args);
uint32_t user_load_titlekey(smc_args_t *args);
uint32_t user_unwrap_aes_wrapped_titlekey(smc_args_t *args);
void set_crypt_aes_done(bool done);
bool get_crypt_aes_done(void);
void set_exp_mod_done(bool done);
bool get_exp_mod_done(void);
#endif

150
exosphere/src/start.s Normal file
View File

@@ -0,0 +1,150 @@
/* For some reason GAS doesn't know about it, even with .cpu cortex-a57 */
#define cpuactlr_el1 s3_1_c15_c2_0
.macro ERRATUM_INVALIDATE_BTB_AT_BOOT
/* Nintendo copy-pasted https://github.com/ARM-software/arm-trusted-firmware/blob/master/plat/nvidia/tegra/common/aarch64/tegra_helpers.S#L312 */
/*
* Copyright (c) 2015-2017, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
/* The following comments are mine. */
/* mask all interrupts */
msr daifset, 0b1111
/*
Enable invalidates of branch target buffer, then flush
the entire instruction cache at the local level, and
with the reg change, the branch target buffer, then disable
invalidates of the branch target buffer again.
*/
mrs x0, cpuactlr_el1
orr x0, x0, #1
msr cpuactlr_el1, x0
dsb sy
isb
ic iallu
dsb sy
isb
mrs x0, cpuactlr_el1
bic x0, x0, #1
msr cpuactlr_el1, x0
.rept 7
nop /* wait long enough for the write to cpuactlr_el1 to have completed */
.endr
/* if the OS lock is set, disable it and request a warm reset */
mrs x0, oslsr_el1
ands x0, x0, #2
b.eq 2f
mov x0, xzr
msr oslar_el1, x0
mov x0, #(1 << 63)
msr cpuactlr_el1, x0 /* disable regional clock gating */
isb
mov x0, #3
msr rmr_el3, x0
isb
dsb sy
/* Nintendo forgot to copy-paste the branch instruction below. */
1:
wfi
b 1b
.rept 65
nop /* guard against speculative excecution */
.endr
2:
/* set the OS lock */
mov x0, #1
msr oslar_el1, x0
.endm
.align 6
.section .text.cold.start, "ax", %progbits
.global __start_cold
__start_cold:
ERRATUM_INVALIDATE_BTB_AT_BOOT
msr spsel, #0
bl get_coldboot_crt0_stack_address /* should be optimized so it doesn't make function calls */
mov sp, x0
bl coldboot_init
ldr x16, =__jump_to_main_cold
br x16
.align 6
.section .text.warm.start, "ax", %progbits
.global __start_warm
__start_warm:
ERRATUM_INVALIDATE_BTB_AT_BOOT
/* For some reasons, Nintendo uses spsel, #1 here, causing issues if an exception occurs */
msr spsel, #0
bl get_warmboot_crt0_stack_address /* should be optimized so it doesn't make function calls */
mov sp, x0
bl warmboot_init
ldr x16, =__jump_to_main_warm
br x16
.section .text.__jump_to_main_cold, "ax", %progbits
__jump_to_main_cold:
bl __set_exception_entry_stack_pointer
bl get_pk2ldr_stack_address
mov sp, x0
bl load_package2
mov w0, #3 /* use core3 stack temporarily */
bl get_exception_entry_stack_address
mov sp, x0
b coldboot_main
.section .text.__jump_to_main_warm, "ax", %progbits
__jump_to_main_warm:
/* Nintendo doesn't do that here, causing issues if an exception occurs */
bl __set_exception_entry_stack_pointer
bl get_pk2ldr_stack_address
mov sp, x0
bl load_package2
mov w0, #3 /* use core0,1,2 stack bottom + 0x800 (VA of warmboot crt0 sp) temporarily */
bl get_exception_entry_stack_address
add sp, x0, #0x800
b warmboot_main
.section .text.__set_exception_entry_stack, "ax", %progbits
.type __set_exception_entry_stack, %function
.global __set_exception_entry_stack
__set_exception_entry_stack_pointer:
/* If SPSel == 1 on entry, make sure your function doesn't use stack variables! */
mov x16, lr
mrs x17, spsel
mrs x0, mpidr_el1
and w0, w0, #3
bl get_exception_entry_stack_address /* should be optimized so it doesn't make function calls */
msr spsel, #1
mov sp, x0
msr spsel, x17
mov lr, x16
ret
.section .text.__jump_to_lower_el, "ax", %progbits
.global __jump_to_lower_el
.type __jump_to_lower_el, %function
__jump_to_lower_el:
/* x0: arg (context ID), x1: entrypoint, w2: exception level */
msr elr_el3, x1
mov w1, #((0b1111 << 6) | 1) /* DAIF set and SP = SP_ELx*/
orr w1, w2, w2, lsl#2
msr spsr_el3, x1
bl __set_exception_entry_stack_pointer
isb
eret

8
exosphere/src/timers.c Normal file
View File

@@ -0,0 +1,8 @@
#include "timers.h"
void wait(uint32_t microseconds) {
uint32_t old_time = TIMERUS_CNTR_1US_0;
while (TIMERUS_CNTR_1US_0 - old_time <= microseconds) {
/* Spin-lock. */
}
}

15
exosphere/src/timers.h Normal file
View File

@@ -0,0 +1,15 @@
#ifndef EXOSPHERE_TIMERS_H
#define EXOSPHERE_TIMERS_H
#include <stdint.h>
#include "memory_map.h"
/* Exosphere driver for the Tegra X1 Timers. */
#define TIMERS_BASE (mmio_get_device_address(MMIO_DEVID_TMRs_WDTs))
#define TIMERUS_CNTR_1US_0 (*((volatile uint32_t *)(TIMERS_BASE + 0x10)))
void wait(uint32_t microseconds);
#endif

156
exosphere/src/titlekey.c Normal file
View File

@@ -0,0 +1,156 @@
#include <stdint.h>
#include <string.h>
#include "utils.h"
#include "cache.h"
#include "titlekey.h"
#include "masterkey.h"
#include "se.h"
uint64_t g_tkey_expected_label_hash[4];
unsigned int g_tkey_master_key_rev = MASTERKEY_REVISION_MAX;
/* Set the expected db prefix. */
void tkey_set_expected_label_hash(uint64_t *label_hash) {
for (unsigned int i = 0; i < 4; i++) {
g_tkey_expected_label_hash[i] = label_hash[i];
}
}
void tkey_set_master_key_rev(unsigned int master_key_rev) {
if (master_key_rev >= MASTERKEY_REVISION_MAX) {
generic_panic();
}
}
/* Reference for MGF1 can be found here: https://en.wikipedia.org/wiki/Mask_generation_function#MGF1 */
void calculate_mgf1_and_xor(void *masked, size_t masked_size, const void *seed, size_t seed_size) {
uint8_t cur_hash[0x20];
uint8_t hash_buf[0xE4];
if (seed_size >= 0xE0) {
generic_panic();
}
size_t hash_buf_size = seed_size + 4;
memcpy(hash_buf, seed, seed_size);
uint32_t round = 0;
uint8_t *p_out = (uint8_t *)masked;
while (masked_size) {
size_t cur_size = masked_size;
if (cur_size > 0x20) {
cur_size = 0x20;
}
hash_buf[seed_size + 0] = (uint8_t)((round >> 24) & 0xFF);
hash_buf[seed_size + 1] = (uint8_t)((round >> 16) & 0xFF);
hash_buf[seed_size + 2] = (uint8_t)((round >> 8) & 0xFF);
hash_buf[seed_size + 3] = (uint8_t)((round >> 0) & 0xFF);
round++;
flush_dcache_range(hash_buf, hash_buf + hash_buf_size);
se_calculate_sha256(cur_hash, hash_buf, hash_buf_size);
for (unsigned int i = 0; i < cur_size; i++) {
*p_out ^= cur_hash[i];
p_out++;
}
masked_size -= cur_size;
}
}
size_t tkey_rsa_oaep_unwrap(void *dst, size_t dst_size, void *src, size_t src_size) {
if (src_size != 0x100) {
generic_panic();
}
/* RSA Wrapped titlekeys use RSA-OAEP. */
/* Message is of the form prefix || maskedSalt || maskedDB. */
/* maskedSalt = salt ^ MGF1(maskedDB) */
/* maskedDB = DB ^ MGF1(salt) */
/* Salt is random and not validated in any way. */
/* DB is of the form label_hash || 00....01 || wrapped_titlekey. */
/* label_hash is, in practice, a constant in es .rodata. */
/* I have no idea why Nintendo did this, it should be either nonconstant (in tik) or in tz .rodata. */
uint8_t *message = (uint8_t *)src;
/* Prefix should always be zero. */
if (*message != 0) {
return 0;
}
uint8_t *salt = message + 1;
uint8_t *db = message + 0x21;
/* This will be passed to smc_unwrap_rsa_oaep_wrapped_titlekey. */
uint8_t *expected_label_hash = (uint8_t *)(&g_tkey_expected_label_hash[0]);
/* Unmask the salt. */
calculate_mgf1_and_xor(salt, 0x20, db, 0xDF);
/* Unmask the DB. */
calculate_mgf1_and_xor(db, 0xDF, salt, 0x20);
/* Validate expected salt. */
for (unsigned int i = 0; i < 0x20; i++) {
if (expected_label_hash[i] != db[i]) {
return 0;
}
}
/* Don't validate salt from message[1:0x21] at all. */
/* Advance pointer to DB, since we've validated the salt prefix. */
db += 0x20;
/* DB must be of the form 0000...01 || wrapped_titlekey */
if (*db != 0) {
return 0;
}
/* Locate wrapped_titlekey inside DB. */
size_t wrapped_key_offset_in_db = 0;
while (wrapped_key_offset_in_db < 0xBF) {
if (db[wrapped_key_offset_in_db] == 0) {
wrapped_key_offset_in_db++;
} else if (db[wrapped_key_offset_in_db] == 1) {
wrapped_key_offset_in_db++;
break;
} else {
/* Invalid wrapped titlekey prefix. */
return 0;
}
}
/* Validate size... */
size_t wrapped_titlekey_size = 0xBF - wrapped_key_offset_in_db;
if (wrapped_titlekey_size > dst_size || wrapped_titlekey_size == 0) {
return 0;
}
/* Extract the wrapped key. */
memcpy(dst, &db[wrapped_key_offset_in_db], wrapped_titlekey_size);
return wrapped_key_offset_in_db;
}
void tkey_aes_unwrap(void *dst, size_t dst_size, const void *src, size_t src_size) {
if (g_tkey_master_key_rev >= MASTERKEY_REVISION_MAX || dst_size != 0x10 || src_size != 0x10) {
generic_panic();
}
const uint8_t titlekek_source[0x10] = {
0x1E, 0xDC, 0x7B, 0x3B, 0x60, 0xE6, 0xB4, 0xD8, 0x78, 0xB8, 0x17, 0x15, 0x98, 0x5E, 0x62, 0x9B
};
/* Generate the appropriate titlekek into keyslot 9. */
unsigned int master_keyslot = mkey_get_keyslot(g_tkey_master_key_rev);
decrypt_data_into_keyslot(KEYSLOT_SWITCH_TEMPKEY, master_keyslot, titlekek_source, 0x10);
/* Unwrap the titlekey using the titlekek. */
se_aes_ecb_decrypt_block(KEYSLOT_SWITCH_TEMPKEY, dst, 0x10, src, 0x10);
}

13
exosphere/src/titlekey.h Normal file
View File

@@ -0,0 +1,13 @@
#ifndef EXOSPHERE_TITLEKEY_H
#define EXOSPHERE_TITLEKEY_H
#include <stdint.h>
void tkey_set_expected_label_hash(uint64_t *label_hash);
void tkey_set_master_key_rev(unsigned int master_key_rev);
size_t tkey_rsa_oaep_unwrap(void *dst, size_t dst_size, void *src, size_t src_size);
void tkey_aes_unwrap(void *dst, size_t dst_size, const void *src, size_t src_size);
#endif

36
exosphere/src/uart.c Normal file
View File

@@ -0,0 +1,36 @@
#include "uart.h"
void uart_initialize(uint16_t divider) {
/* Setup UART in 16450 mode. We assume the relevant UART clock has been enabled. */
/* Disable FIFO */
UART_IIR_FCR_0 = 0x00;
/* Set DLAB */
UART_LCR_0 = 0x80;
UART_THR_DLAB_0_0 = (uint8_t)divider;
UART_IER_DLAB_0_0 = (uint8_t)(divider >> 8);
/* 8N1 mode */
UART_LCR_0 = 0x03;
}
void uart_transmit_char(char ch) {
/* Wait for THR to be empty */
while (!(UART_LSR_0 & 0x20)) {}
UART_THR_DLAB_0_0 = ch;
}
void uart_transmit_str(const char *str) {
while (*str) {
uart_transmit_char(*str++);
}
}
void uart_transmit_hex(uint32_t value) {
for (unsigned int i = 0; i < 8; i++) {
uint32_t nibble = (value >> (28 - i * 4)) & 0xF;
uart_transmit_char("0123456789ABCDEF"[nibble]);
}
}

24
exosphere/src/uart.h Normal file
View File

@@ -0,0 +1,24 @@
#ifndef EXOSPHERE_UART_H
#define EXOSPHERE_UART_H
#include <stdint.h>
#include "memory_map.h"
/* Exosphere driver for the Tegra X1 UARTs. */
/* TODO: Should we bother with support UARTB-D? */
#define UARTA_BASE (mmio_get_device_address(MMIO_DEVID_UART_A))
#define UART_THR_DLAB_0_0 (*((volatile uint32_t *)(UARTA_BASE + 0x0)))
#define UART_IER_DLAB_0_0 (*((volatile uint32_t *)(UARTA_BASE + 0x4)))
#define UART_IIR_FCR_0 (*((volatile uint32_t *)(UARTA_BASE+ 0x8)))
#define UART_LCR_0 (*((volatile uint32_t *)(UARTA_BASE + 0xC)))
#define UART_LSR_0 (*((volatile uint32_t *)(UARTA_BASE + 0x14)))
void uart_initialize(uint16_t divider);
void uart_transmit_char(char ch);
void uart_transmit_str(const char *str);
void uart_transmit_hex(uint32_t value);
#endif

71
exosphere/src/userpage.c Normal file
View File

@@ -0,0 +1,71 @@
#include <string.h>
#include "utils.h"
#include "userpage.h"
#include "memory_map.h"
#include "cache.h"
static uintptr_t g_user_page_user_address = 0ULL;
static inline uintptr_t get_page_for_address(void *address) {
return ((uintptr_t)(address)) & ~0xFFFULL;
}
/* Create a user page reference for the desired address. */
/* Returns 1 on success, 0 on failure. */
bool upage_init(upage_ref_t *upage, void *user_address) {
upage->user_address = get_page_for_address(user_address);
upage->secure_monitor_address = 0ULL;
if (g_user_page_user_address != 0ULL) {
/* Different physical address indicate SPL was rebooted, or another process got access to svcCallSecureMonitor. Panic. */
if (g_user_page_user_address != upage->user_address) {
generic_panic();
}
upage->secure_monitor_address = USER_PAGE_SECURE_MONITOR_ADDR;
} else {
/* Weakly validate SPL's physically random address is in DRAM. */
if (upage->user_address >> 31) {
static const uint64_t userpage_attributes = MMU_PTE_BLOCK_XN | MMU_PTE_BLOCK_INNER_SHAREBLE | MMU_PTE_BLOCK_NS | ATTRIB_MEMTYPE_NORMAL;
uintptr_t *mmu_l3_tbl = (uintptr_t *)tzram_get_segment_address(TZRAM_SEGMENT_ID_L3_TRANSLATION_TABLE);
g_user_page_user_address = upage->user_address;
mmu_map_page(mmu_l3_tbl, USER_PAGE_SECURE_MONITOR_ADDR, upage->user_address, userpage_attributes);
tlb_invalidate_page_inner_shareable((void *)USER_PAGE_SECURE_MONITOR_ADDR);
upage->secure_monitor_address = USER_PAGE_SECURE_MONITOR_ADDR;
}
}
return upage->secure_monitor_address != 0ULL;
}
bool user_copy_to_secure(upage_ref_t *upage, void *secure_dst, void *user_src, size_t size) {
/* Fail if the page doesn't match. */
if (get_page_for_address(user_src) != upage->user_address) {
return false;
}
/* Fail if we go past the page boundary. */
if (size != 0 && get_page_for_address(user_src + size - 1) != upage->user_address) {
return false;
}
void *secure_src = (void *)(upage->secure_monitor_address + ((uintptr_t)user_src - upage->user_address));
memcpy(secure_dst, secure_src, size);
return true;
}
bool secure_copy_to_user(upage_ref_t *upage, void *user_dst, void *secure_src, size_t size) {
/* Fail if the page doesn't match. */
if (get_page_for_address(user_dst) != upage->user_address) {
return false;
}
/* Fail if we go past the page boundary. */
if (size != 0 && get_page_for_address(user_dst + size - 1) != upage->user_address) {
return false;
}
void *secure_dst = (void *)(upage->secure_monitor_address + ((uintptr_t)user_dst - upage->user_address));
memcpy(secure_dst, secure_src, size);
return true;
}

19
exosphere/src/userpage.h Normal file
View File

@@ -0,0 +1,19 @@
#ifndef EXOSPHERE_USERPAGE_H
#define EXOSPHERE_USERPAGE_H
#include "utils.h"
#include "memory_map.h"
#define USER_PAGE_SECURE_MONITOR_ADDR (tzram_get_segment_address(TZRAM_SEGMENT_ID_USERPAGE))
typedef struct {
uintptr_t user_address;
uintptr_t secure_monitor_address;
} upage_ref_t;
bool upage_init(upage_ref_t *user_page, void *user_address);
bool user_copy_to_secure(upage_ref_t *user_page, void *secure_dst, void *user_src, size_t size);
bool secure_copy_to_user(upage_ref_t *user_page, void *user_dst, void *secure_src, size_t size);
#endif

18
exosphere/src/utils.c Normal file
View File

@@ -0,0 +1,18 @@
#include "utils.h"
void panic(uint32_t code) {
(void)code; /* TODO */
}
void generic_panic(void) {
/* TODO */
}
__attribute__((noinline)) bool overlaps(uint64_t as, uint64_t ae, uint64_t bs, uint64_t be)
{
if(as <= bs && bs <= ae)
return true;
if(bs <= as && as <= be)
return true;
return false;
}

44
exosphere/src/utils.h Normal file
View File

@@ -0,0 +1,44 @@
#ifndef EXOSPHERE_UTILS_H
#define EXOSPHERE_UTILS_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#define BIT(x) (1u << (x))
#define BITL(x) (1ull << (x))
void panic(uint32_t code);
void generic_panic(void);
bool overlaps(uint64_t as, uint64_t ae, uint64_t bs, uint64_t be);
static inline uintptr_t get_physical_address(const void *vaddr) {
uintptr_t PAR;
__asm__ __volatile__ ("at s1e3r, %0" :: "r"(vaddr));
__asm__ __volatile__ ("mrs %0, par_el1" : "=r"(PAR));
return (PAR & 1) ? 0ULL : (PAR & 0x00000FFFFFFFF000ULL) | ((uintptr_t)vaddr & 0xFFF);
}
static inline uint32_t read32le(const volatile void *dword, size_t offset) {
return *(uint32_t *)((uintptr_t)dword + offset);
}
static inline uint32_t read32be(const volatile void *dword, size_t offset) {
return __builtin_bswap32(read32le(dword, offset));
}
static inline uint64_t read64le(const volatile void *qword, size_t offset) {
return *(uint64_t *)((uintptr_t)qword + offset);
}
static inline unsigned int get_core_id(void) {
uint64_t core_id;
__asm__ __volatile__ ("mrs %0, mpidr_el1" : "=r"(core_id));
return (unsigned int)core_id & 3;
}
static inline bool check_32bit_additive_overflow(uint32_t a, uint32_t b) {
return __builtin_add_overflow_p(a, b, (uint32_t)0);
}
#endif

View File

@@ -0,0 +1,16 @@
#include "utils.h"
#include "memory_map.h"
uintptr_t get_warmboot_crt0_stack_address(void);
void flush_dcache_all_tzram_pa(void) {
/* TODO */
}
void invalidate_icache_all_tzram_pa(void) {
/* TODO */
}
uintptr_t get_warmboot_crt0_stack_address(void) {
return tzram_get_segment_pa(TZRAM_SEGMENT_ID_CORE3_STACK) + 0x800;
}

View File

@@ -0,0 +1,11 @@
#include "utils.h"
#include "mmu.h"
#include "memory_map.h"
extern void __jump_to_lower_el(uint64_t arg, uintptr_t ep, unsigned int el);
void warmboot_main(void);
void warmboot_main(void) {
/* TODO */
}