From cec055a44b313c3653692f340626d54769ca96c0 Mon Sep 17 00:00:00 2001 From: Michael Scire Date: Tue, 20 Feb 2018 03:19:35 -0800 Subject: [PATCH] smcGetRandomFor{User,Priv} Implementations. --- exosphere/randomcache.c | 71 +++++++++++++++++++++++++++++++++++++++++ exosphere/randomcache.h | 13 ++++++++ exosphere/smc_api.c | 33 ++++++++++++++++++- exosphere/smc_user.c | 16 ++++++++++ 4 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 exosphere/randomcache.c create mode 100644 exosphere/randomcache.h diff --git a/exosphere/randomcache.c b/exosphere/randomcache.c new file mode 100644 index 000000000..675b64581 --- /dev/null +++ b/exosphere/randomcache.c @@ -0,0 +1,71 @@ +#include + +#include "utils.h" +#include "randomcache.h" +#include "se.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. */ + +volatile uint8_t g_random_cache[0x400]; +volatile unsigned int g_random_cache_low = 0; +volatile 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; +} \ No newline at end of file diff --git a/exosphere/randomcache.h b/exosphere/randomcache.h new file mode 100644 index 000000000..7d0ddc8d2 --- /dev/null +++ b/exosphere/randomcache.h @@ -0,0 +1,13 @@ +#ifndef EXOSPHERE_RANDOM_CACHE_H +#define EXOSPHERE_RANDOM_CACHE_H + +#include + +/* 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 \ No newline at end of file diff --git a/exosphere/smc_api.c b/exosphere/smc_api.c index 80f0f17d6..0419bb858 100644 --- a/exosphere/smc_api.c +++ b/exosphere/smc_api.c @@ -279,6 +279,10 @@ 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); } @@ -368,4 +372,31 @@ uint32_t cpu_suspend_wrapper(smc_args_t *args) { uint32_t smc_cpu_suspend(smc_args_t *args) { return smc_wrapper_sync(args, cpu_suspend_wrapper); -} \ No newline at end of file +} + +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; + + /* TODO: Make atomic. */ + if (g_is_smc_in_progress == 0) { + g_is_smc_in_progress = 1; + /* 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(); + g_is_smc_in_progress = 0; + } else { + 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; + } + return result; +} diff --git a/exosphere/smc_user.c b/exosphere/smc_user.c index 5d9f279a4..287b5ff87 100644 --- a/exosphere/smc_user.c +++ b/exosphere/smc_user.c @@ -75,6 +75,22 @@ uint32_t user_exp_mod(smc_args_t *args) { 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];