exo2: implement SmcComputeAes, SmcGetResult, SmcGetResultData

This commit is contained in:
Michael Scire
2020-05-15 14:58:45 -07:00
committed by SciresM
parent b6b114ec40
commit e0dbfc69a8
16 changed files with 486 additions and 24 deletions

View File

@@ -15,6 +15,7 @@
*/
#include <exosphere.hpp>
#include "secmon_cache.hpp"
#include "secmon_setup.hpp"
#include "secmon_map.hpp"
namespace ams::secmon {
@@ -24,8 +25,12 @@ namespace ams::secmon {
constexpr inline const uintptr_t BootCodeAddress = MemoryRegionVirtualTzramBootCode.GetAddress();
constexpr inline const size_t BootCodeSize = MemoryRegionVirtualTzramBootCode.GetSize();
constinit uintptr_t g_smc_user_page_physical_address = 0;
using namespace ams::mmu;
constexpr inline PageTableMappingAttribute MappingAttributesEl3NonSecureRwData = AddMappingAttributeIndex(PageTableMappingAttributes_El3NonSecureRwData, MemoryAttributeIndexNormal);
constexpr void UnmapBootCodeImpl(u64 *l1, u64 *l2, u64 *l3, uintptr_t boot_code, size_t boot_code_size) {
/* Unmap the L3 entries corresponding to the boot code. */
InvalidateL3Entries(l3, boot_code, boot_code_size);
@@ -42,6 +47,16 @@ namespace ams::secmon {
InvalidateL1Entries(l1, MemoryRegionPhysical.GetAddress(), MemoryRegionPhysical.GetSize());
}
constexpr void MapSmcUserPageImpl(u64 *l3, uintptr_t address) {
/* Set the L3 entry. */
SetL3BlockEntry(l3, MemoryRegionVirtualSmcUserPage.GetAddress(), address, MemoryRegionVirtualSmcUserPage.GetSize(), MappingAttributesEl3NonSecureRwData);
}
constexpr void UnmapSmcUserPageImpl(u64 *l3) {
/* Unmap the L3 entry. */
InvalidateL3Entries(l3, MemoryRegionVirtualSmcUserPage.GetAddress(), MemoryRegionVirtualSmcUserPage.GetSize());
}
void ClearLow(uintptr_t address, size_t size) {
/* Clear the low part. */
util::ClearMemory(reinterpret_cast<void *>(address), size / 2);
@@ -85,4 +100,43 @@ namespace ams::secmon {
secmon::EnsureMappingConsistency();
}
uintptr_t MapSmcUserPage(uintptr_t address) {
if (g_smc_user_page_physical_address != 0) {
if (!(MemoryRegionDram.GetAddress() <= address && address <= MemoryRegionDramHigh.GetEndAddress() - MemoryRegionVirtualSmcUserPage.GetSize())) {
return 0;
}
if (!util::IsAligned(address, 4_KB)) {
return 0;
}
g_smc_user_page_physical_address = address;
u64 * const l2_l3 = MemoryRegionVirtualTzramL2L3PageTable.GetPointer<u64>();
MapSmcUserPageImpl(l2_l3, address);
/* Ensure the mappings are consistent. */
secmon::EnsureMappingConsistency(MemoryRegionVirtualSmcUserPage.GetAddress());
} else {
AMS_ABORT_UNLESS(address == g_smc_user_page_physical_address);
}
return MemoryRegionVirtualSmcUserPage.GetAddress();
}
void UnmapSmcUserPage() {
if (g_smc_user_page_physical_address == 0) {
return;
}
u64 * const l2_l3 = MemoryRegionVirtualTzramL2L3PageTable.GetPointer<u64>();
UnmapSmcUserPageImpl(l2_l3);
/* Ensure the mappings are consistent. */
secmon::EnsureMappingConsistency(MemoryRegionVirtualSmcUserPage.GetAddress());
g_smc_user_page_physical_address = 0;
}
}

View File

@@ -20,4 +20,7 @@ namespace ams::secmon {
void UnmapTzram();
uintptr_t MapSmcUserPage(uintptr_t address);
void UnmapSmcUserPage();
}

View File

@@ -20,6 +20,7 @@
#include "secmon_cpu_context.hpp"
#include "secmon_interrupt_handler.hpp"
#include "secmon_misc.hpp"
#include "smc/secmon_random_cache.hpp"
#include "smc/secmon_smc_power_management.hpp"
#include "smc/secmon_smc_se_lock.hpp"
@@ -938,6 +939,9 @@ namespace ams::secmon {
ExitChargerHiZMode();
}
/* Refill the random cache, which is volatile and thus wiped on warmboot. */
smc::FillRandomCache();
/* Unlock the security engine. */
secmon::smc::UnlockSecurityEngine();
}

View File

@@ -47,6 +47,13 @@ namespace ams::secmon::smc {
KeyType_Count,
};
enum CipherMode {
CipherMode_CbcEncryption = 0,
CipherMode_CbcDecryption = 1,
CipherMode_Ctr = 2,
CipherMode_Cmac = 3,
};
struct GenerateAesKekOption {
using IsDeviceUnique = util::BitPack32::Field<0, 1, bool>;
using KeyTypeIndex = util::BitPack32::Field<1, 4, KeyType>;
@@ -54,6 +61,11 @@ namespace ams::secmon::smc {
using Reserved = util::BitPack32::Field<8, 24, u32>;
};
struct ComputeAesOption {
using KeySlot = util::BitPack32::Field<0, 3, int>;
using CipherModeIndex = util::BitPack32::Field<4, 2, CipherMode>;
};
constexpr const u8 SealKeySources[SealKey_Count][AesKeySize] = {
[SealKey_LoadAesKey] = { 0xF4, 0x0C, 0x16, 0x26, 0x0D, 0x46, 0x3B, 0xE0, 0x8C, 0x6A, 0x56, 0xE5, 0x82, 0xD4, 0x1B, 0xF6 },
[SealKey_DecryptDeviceUniqueData] = { 0x7F, 0x54, 0x2C, 0x98, 0x1E, 0x54, 0x18, 0x3B, 0xBA, 0x63, 0xBD, 0x4C, 0x13, 0x5B, 0xF1, 0x06 },
@@ -81,6 +93,40 @@ namespace ams::secmon::smc {
[SealKey_LoadEsClientCertKey] = { 0x89, 0x96, 0x43, 0x9A, 0x7C, 0xD5, 0x59, 0x55, 0x24, 0xD5, 0x24, 0x18, 0xAB, 0x6C, 0x04, 0x61 },
};
constexpr uintptr_t LinkedListAddressMinimum = secmon::MemoryRegionDram.GetAddress();
constexpr size_t LinkedListAddressRangeSize = 4_MB - 2_KB;
constexpr uintptr_t LinkedListAddressMaximum = LinkedListAddressMinimum + LinkedListAddressRangeSize;
constexpr size_t LinkedListSize = 12;
constexpr bool IsValidLinkedListAddress(uintptr_t address) {
return LinkedListAddressMinimum <= address && address <= (LinkedListAddressMaximum - LinkedListSize);
}
constinit bool g_is_compute_aes_completed = false;
void SecurityEngineDoneHandler() {
/* Check that the compute succeeded. */
se::ValidateAesOperationResult();
/* End the asynchronous operation. */
g_is_compute_aes_completed = true;
EndAsyncOperation();
}
SmcResult GetComputeAesResult(void *dst, size_t size) {
/* Arguments are unused. */
AMS_UNUSED(dst);
AMS_UNUSED(size);
/* Check that the operation is completed. */
SMC_R_UNLESS(g_is_compute_aes_completed, Busy);
/* Unlock the security engine and succeed. */
UnlockSecurityEngine();
return SmcResult::Success;
}
int PrepareMasterKey(int generation) {
if (generation == GetKeyGeneration()) {
return pkg1::AesKeySlot_Master;
@@ -199,6 +245,42 @@ namespace ams::secmon::smc {
return SmcResult::Success;
}
SmcResult ComputeAesImpl(SmcArguments &args) {
/* Decode arguments. */
u8 iv[se::AesBlockSize];
const util::BitPack32 option = { static_cast<u32>(args.r[1]) };
std::memcpy(iv, std::addressof(args.r[2]), sizeof(iv));
const u32 input_address = args.r[4];
const u32 output_address = args.r[5];
const u32 size = args.r[6];
const int slot = option.Get<ComputeAesOption::KeySlot>();
const auto cipher_mode = option.Get<ComputeAesOption::CipherModeIndex>();
/* Validate arguments. */
SMC_R_UNLESS(pkg1::IsUserAesKeySlot(slot), InvalidArgument);
SMC_R_UNLESS(util::IsAligned(size, se::AesBlockSize), InvalidArgument);
SMC_R_UNLESS(IsValidLinkedListAddress(input_address), InvalidArgument);
SMC_R_UNLESS(IsValidLinkedListAddress(output_address), InvalidArgument);
/* We're starting an aes operation, so reset the completion status. */
g_is_compute_aes_completed = false;
/* Dispatch the correct aes operation asynchronously. */
switch (cipher_mode) {
case CipherMode_CbcEncryption: se::EncryptAes128CbcAsync(output_address, slot, input_address, size, iv, sizeof(iv), SecurityEngineDoneHandler); break;
case CipherMode_CbcDecryption: se::DecryptAes128CbcAsync(output_address, slot, input_address, size, iv, sizeof(iv), SecurityEngineDoneHandler); break;
case CipherMode_Ctr: se::ComputeAes128CtrAsync(output_address, slot, input_address, size, iv, sizeof(iv), SecurityEngineDoneHandler); break;
case CipherMode_Cmac:
return SmcResult::NotImplemented;
default:
return SmcResult::InvalidArgument;
}
return SmcResult::Success;
}
}
SmcResult SmcGenerateAesKek(SmcArguments &args) {
@@ -210,8 +292,7 @@ namespace ams::secmon::smc {
}
SmcResult SmcComputeAes(SmcArguments &args) {
/* TODO */
return SmcResult::NotImplemented;
return LockSecurityEngineAndInvokeAsync(args, ComputeAesImpl, GetComputeAesResult);
}
SmcResult SmcGenerateSpecificAesKey(SmcArguments &args) {

View File

@@ -16,17 +16,91 @@
#include <exosphere.hpp>
#include "../secmon_error.hpp"
#include "secmon_smc_result.hpp"
#include "secmon_user_page_mapper.hpp"
namespace ams::secmon::smc {
namespace {
constinit u64 g_async_key = InvalidAsyncKey;
constinit GetResultHandler g_async_handler = nullptr;
u64 GenerateRandomU64() {
/* NOTE: This is one of the only places where Nintendo does not do data flushing. */
/* to ensure coherency when doing random byte generation. */
/* It is not clear why it is necessary elsewhere but not here. */
/* TODO: Figure out why. */
u64 v;
se::GenerateRandomBytes(std::addressof(v), sizeof(v));
return v;
}
}
u64 BeginAsyncOperation(GetResultHandler handler) {
/* Only allow one async operation at a time. */
if (g_async_key != InvalidAsyncKey) {
return InvalidAsyncKey;
}
/* Generate a random async key. */
g_async_key = GenerateRandomU64();
g_async_handler = handler;
return g_async_key;
}
void CancelAsyncOperation(u64 async_key) {
if (async_key == g_async_key) {
g_async_key = InvalidAsyncKey;
}
}
void EndAsyncOperation() {
gic::SetPending(SecurityEngineUserInterruptId);
}
SmcResult SmcGetResult(SmcArguments &args) {
/* TODO */
return SmcResult::NotImplemented;
/* Decode arguments. */
const u64 async_key = args.r[1];
/* Validate arguments. */
SMC_R_UNLESS(g_async_key != InvalidAsyncKey, NoAsyncOperation);
SMC_R_UNLESS(g_async_key == async_key, InvalidAsyncOperation);
/* Call the handler. */
args.r[1] = static_cast<u64>(g_async_handler(nullptr, 0));
g_async_key = InvalidAsyncKey;
return SmcResult::Success;
}
SmcResult SmcGetResultData(SmcArguments &args) {
/* TODO */
return SmcResult::NotImplemented;
/* Decode arguments. */
const u64 async_key = args.r[1];
const uintptr_t user_phys_addr = args.r[2];
const size_t user_size = args.r[3];
/* Allocate a work buffer on the stack. */
alignas(8) u8 work_buffer[1_KB];
/* Validate arguments. */
SMC_R_UNLESS(g_async_key != InvalidAsyncKey, NoAsyncOperation);
SMC_R_UNLESS(g_async_key == async_key, InvalidAsyncOperation);
SMC_R_UNLESS(user_size <= sizeof(work_buffer), InvalidArgument);
/* Call the handler. */
args.r[1] = static_cast<u64>(g_async_handler(work_buffer, user_size));
g_async_key = InvalidAsyncKey;
/* Map the user buffer. */
{
UserPageMapper mapper(user_phys_addr);
SMC_R_UNLESS(mapper.Map(), InvalidArgument);
SMC_R_UNLESS(mapper.CopyToUser(user_phys_addr, work_buffer, user_size), InvalidArgument);
}
return SmcResult::Success;
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright (c) 2018-2020 Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <exosphere.hpp>
#include "../secmon_map.hpp"
#include "secmon_user_page_mapper.hpp"
namespace ams::secmon::smc {
bool UserPageMapper::Map() {
this->virtual_address = MapSmcUserPage(this->physical_address);
return this->virtual_address != 0;
}
void *UserPageMapper::GetPointerTo(uintptr_t phys, size_t size) const {
/* Ensure we stay within the page. */
if (util::AlignDown(phys, 4_KB) != this->physical_address) {
return nullptr;
}
if (size != 0) {
if (util::AlignDown(phys + size - 1, 4_KB) != this->physical_address) {
return nullptr;
}
}
return reinterpret_cast<void *>(phys + (this->virtual_address - this->physical_address));
}
bool UserPageMapper::CopyToUser(uintptr_t dst_phys, const void *src, size_t size) const {
void * const dst = this->GetPointerTo(dst_phys, size);
if (dst == nullptr) {
return false;
}
std::memcpy(dst, src, size);
return true;
}
bool UserPageMapper::CopyFromUser(void *dst, uintptr_t src_phys, size_t size) const {
const void * const src = this->GetPointerTo(src_phys, size);
if (src == nullptr) {
return false;
}
std::memcpy(dst, src, size);
return true;
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright (c) 2018-2020 Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <exosphere.hpp>
#include "secmon_smc_common.hpp"
namespace ams::secmon::smc {
class UserPageMapper {
private:
uintptr_t physical_address;
uintptr_t virtual_address;
public:
constexpr UserPageMapper(uintptr_t phys) : physical_address(util::AlignDown(phys, 4_KB)), virtual_address() { /* ... */ }
bool Map();
void *GetPointerTo(uintptr_t phys, size_t size) const;
bool CopyToUser(uintptr_t dst_phys, const void *src, size_t size) const;
bool CopyFromUser(void *dst, uintptr_t src_phys, size_t size) const;
};
}