diff --git a/config_templates/BCT.ini b/config_templates/BCT.ini index d83c258d9..9e23c843b 100644 --- a/config_templates/BCT.ini +++ b/config_templates/BCT.ini @@ -5,16 +5,6 @@ stage2_mtc_path = atmosphere/fusee-mtc.bin stage2_addr = 0xF0000000 stage2_entrypoint = 0xF0000000 -[exosphere] -; Note: Disabling debugmode will cause parts of ams.tma to not work, in the future. -debugmode = 1 -debugmode_user = 0 -; Note: Disabling usermode exception handlers will cause atmosphere to not fail gracefully under error conditions. -; Support will not be provided to users who disable these. If you do not know what you are doing, leave them on. -disable_user_exception_handlers = 0 -; Note: It's currently unknown what effects enabling the usermode PMU register access may have on official code. -enable_user_pmu_access = 0 - [stratosphere] ; To force-enable nogc, add nogc = 1 ; To force-disable nogc, add nogc = 0 diff --git a/config_templates/exosphere.ini b/config_templates/exosphere.ini new file mode 100644 index 000000000..dc7b3fde8 --- /dev/null +++ b/config_templates/exosphere.ini @@ -0,0 +1,45 @@ +# Key: debugmode, default: 1. +# Desc: Controls whether kernel is debug mode. +# Disabling this may break Atmosphere's debugger in a future release. + +# Key: debugmode_user, default: 0. +# Desc: Controls whether userland is debug mode. + +# Key: disable_user_exception_handlers, default: 0. +# Desc: Controls whether user exception handlers are executed on error. +# NOTE: This will cause atmosphere to not fail gracefully. +# Support may not be provided to users tho disable these. +# If you do not know what you are doing, leave them on. + +# Key: enable_user_pmu_access, default: 0. +# Desc: Controls whether userland has access to the PMU registers. +# NOTE: It is unknown what effects this has on official code. + +# Key: blank_prodinfo_sysmmc, default: 0. +# Desc: Controls whether PRODINFO should be blanked in sysmmc. +# This will cause the system to see dummied out keys and +# serial number information. +# NOTE: This is not known to be safe, as data may be +# cached elsewhere in the system. Usage is not encouraged. + +# Key: blank_prodinfo_emummc, default: 0. +# Desc: Controls whether PRODINFO should be blanked in emummc. +# NOTE: This is not known to be safe, as data may be +# cached elsewhere in the system. Usage is not encouraged. + +# Key: allow_writing_to_cal_sysmmc, default: 0. +# Desc: Controls whether PRODINFO can be written by homebrew in sysmmc. +# NOTE: Usage of this setting is strongly discouraged without +# a safe backup elsewhere. Turning this on will also cause Atmosphere +# to ensure a safe backup of calibration data is stored in unused +# mmc space, encrypted to prevent detection. This backup can be used +# to prevent unrecoverable edits in emergencies. + +[exosphere] +debugmode=1 +debugmode_user=0 +disable_user_exception_handlers=0 +enable_user_pmu_access=0 +blank_prodinfo_sysmmc=0 +blank_prodinfo_emummc=0 +allow_writing_to_cal_sysmmc=0 diff --git a/exosphere/src/configitem.c b/exosphere/src/configitem.c index 37b8da3f7..f538ab8b3 100644 --- a/exosphere/src/configitem.c +++ b/exosphere/src/configitem.c @@ -290,7 +290,15 @@ uint32_t configitem_get(bool privileged, ConfigItem item, uint64_t *p_outvalue) break; case CONFIGITEM_HAS_RCM_BUG_PATCH: /* UNOFFICIAL: Gets whether this unit has the RCM bug patched. */ - *p_outvalue = (int)(fuse_has_rcm_bug_patch());; + *p_outvalue = (int)(fuse_has_rcm_bug_patch()); + break; + case CONFIGITEM_SHOULD_BLANK_PRODINFO: + /* UNOFFICIAL: Gets whether this unit should simulate a "blanked" PRODINFO. */ + *p_outvalue = exosphere_should_blank_prodinfo(); + break; + case CONFIGITEM_ALLOW_CAL_WRITES: + /* UNOFFICIAL: Gets whether this unit should allow writing to the calibration partition. */ + *p_outvalue = exosphere_should_allow_writing_to_cal(); break; default: result = 2; diff --git a/exosphere/src/configitem.h b/exosphere/src/configitem.h index d460a195b..103b07c15 100644 --- a/exosphere/src/configitem.h +++ b/exosphere/src/configitem.h @@ -45,6 +45,8 @@ typedef enum { CONFIGITEM_NEEDS_SHUTDOWN = 65002, CONFIGITEM_EXOSPHERE_VERHASH = 65003, CONFIGITEM_HAS_RCM_BUG_PATCH = 65004, + CONFIGITEM_SHOULD_BLANK_PRODINFO = 65005, + CONFIGITEM_ALLOW_CAL_WRITES = 65006, } ConfigItem; #define REBOOT_KIND_NO_REBOOT 0 diff --git a/exosphere/src/exocfg.c b/exosphere/src/exocfg.c index dcdd1c143..f757f9824 100644 --- a/exosphere/src/exocfg.c +++ b/exosphere/src/exocfg.c @@ -27,6 +27,9 @@ static bool g_has_loaded_config = false; #define EXOSPHERE_CHECK_FLAG(flag) ((g_exosphere_cfg.flags & flag) != 0) +static unsigned int exosphere_is_emummc() { + return g_exosphere_cfg.emummc_cfg.base_cfg.magic == MAGIC_EMUMMC_CONFIG && g_exosphere_cfg.emummc_cfg.base_cfg.type != EMUMMC_TYPE_NONE; +} /* Read config out of IRAM, return target firmware version. */ unsigned int exosphere_load_config(void) { @@ -92,6 +95,26 @@ unsigned int exosphere_should_enable_usermode_pmu_access(void) { return EXOSPHERE_CHECK_FLAG(EXOSPHERE_FLAG_ENABLE_USERMODE_PMU_ACCESS); } +unsigned int exosphere_should_blank_prodinfo(void) { + if (!g_has_loaded_config) { + generic_panic(); + } + + return EXOSPHERE_CHECK_FLAG(EXOSPHERE_FLAG_BLANK_PRODINFO); +} + +unsigned int exosphere_should_allow_writing_to_cal(void) { + if (!g_has_loaded_config) { + generic_panic(); + } + + if (exosphere_is_emummc()) { + return 1; + } else { + return EXOSPHERE_CHECK_FLAG(EXOSPHERE_FLAG_ALLOW_WRITING_TO_CAL_SYSMMC); + } +} + const exo_emummc_config_t *exosphere_get_emummc_config(void) { if (!g_has_loaded_config) { generic_panic(); diff --git a/exosphere/src/exocfg.h b/exosphere/src/exocfg.h index 2743dab42..6be2787b7 100644 --- a/exosphere/src/exocfg.h +++ b/exosphere/src/exocfg.h @@ -41,6 +41,8 @@ #define EXOSPHERE_FLAG_IS_DEBUGMODE_USER (1 << 2u) #define EXOSPHERE_FLAG_DISABLE_USERMODE_EXCEPTION_HANDLERS (1 << 3u) #define EXOSPHERE_FLAG_ENABLE_USERMODE_PMU_ACCESS (1 << 4u) +#define EXOSPHERE_FLAG_BLANK_PRODINFO (1 << 5u) +#define EXOSPHERE_FLAG_ALLOW_WRITING_TO_CAL_SYSMMC (1 << 6u) #define EXOSPHERE_FLAGS_DEFAULT (EXOSPHERE_FLAG_IS_DEBUGMODE_PRIV) typedef struct { @@ -60,6 +62,8 @@ unsigned int exosphere_should_override_debugmode_priv(void); unsigned int exosphere_should_override_debugmode_user(void); unsigned int exosphere_should_disable_usermode_exception_handlers(void); unsigned int exosphere_should_enable_usermode_pmu_access(void); +unsigned int exosphere_should_blank_prodinfo(void); +unsigned int exosphere_should_allow_writing_to_cal(void); const exo_emummc_config_t *exosphere_get_emummc_config(void); diff --git a/fusee/fusee-secondary/src/exocfg.h b/fusee/fusee-secondary/src/exocfg.h index 3b7c4644c..933ffad49 100644 --- a/fusee/fusee-secondary/src/exocfg.h +++ b/fusee/fusee-secondary/src/exocfg.h @@ -31,7 +31,8 @@ #define EXOSPHERE_FLAG_IS_DEBUGMODE_USER (1 << 2u) #define EXOSPHERE_FLAG_DISABLE_USERMODE_EXCEPTION_HANDLERS (1 << 3u) #define EXOSPHERE_FLAG_ENABLE_USERMODE_PMU_ACCESS (1 << 4u) -#define EXOSPHERE_FLAGS_DEFAULT (EXOSPHERE_FLAG_IS_DEBUGMODE_PRIV) +#define EXOSPHERE_FLAG_BLANK_PRODINFO (1 << 5u) +#define EXOSPHERE_FLAG_ALLOW_WRITING_TO_CAL_SYSMMC (1 << 6u) typedef struct { uint32_t magic; @@ -50,5 +51,18 @@ _Static_assert(sizeof(exosphere_config_t) == 0x20 + sizeof(exo_emummc_config_t), #define EXOSPHERE_DEBUGMODE_USER_KEY "debugmode_user" #define EXOSPHERE_DISABLE_USERMODE_EXCEPTION_HANDLERS_KEY "disable_user_exception_handlers" #define EXOSPHERE_ENABLE_USERMODE_PMU_ACCESS_KEY "enable_user_pmu_access" +#define EXOSPHERE_BLANK_PRODINFO_SYSMMC_KEY "blank_prodinfo_sysmmc" +#define EXOSPHERE_BLANK_PRODINFO_EMUMMC_KEY "blank_prodinfo_emummc" +#define EXOSPHERE_ALLOW_WRITING_TO_CAL_SYSMMC_KEY "allow_writing_to_cal_sysmmc" -#endif \ No newline at end of file +typedef struct { + int debugmode; + int debugmode_user; + int disable_user_exception_handlers; + int enable_user_pmu_access; + int blank_prodinfo_sysmmc; + int blank_prodinfo_emummc; + int allow_writing_to_cal_sysmmc; +} exosphere_parse_cfg_t; + +#endif diff --git a/fusee/fusee-secondary/src/nxboot.c b/fusee/fusee-secondary/src/nxboot.c index 35f8c77c7..d1e919839 100644 --- a/fusee/fusee-secondary/src/nxboot.c +++ b/fusee/fusee-secondary/src/nxboot.c @@ -134,38 +134,57 @@ static int emummc_ini_handler(void *user, const char *section, const char *name, } static int exosphere_ini_handler(void *user, const char *section, const char *name, const char *value) { - exosphere_config_t *exo_cfg = (exosphere_config_t *)user; + exosphere_parse_cfg_t *parse_cfg = (exosphere_parse_cfg_t *)user; int tmp = 0; if (strcmp(section, "exosphere") == 0) { - if (strcmp(name, EXOSPHERE_TARGETFW_KEY) == 0) { - sscanf(value, "%ld", &exo_cfg->target_firmware); - } else if (strcmp(name, EXOSPHERE_DEBUGMODE_PRIV_KEY) == 0) { + if (strcmp(name, EXOSPHERE_DEBUGMODE_PRIV_KEY) == 0) { sscanf(value, "%d", &tmp); - if (tmp) { - exo_cfg->flags |= EXOSPHERE_FLAG_IS_DEBUGMODE_PRIV; - } else { - exo_cfg->flags &= ~(EXOSPHERE_FLAG_IS_DEBUGMODE_PRIV); + if (tmp == 1) { + parse_cfg->debugmode = 1; + } else if (tmp == 0) { + parse_cfg->debugmode = 0; } } else if (strcmp(name, EXOSPHERE_DEBUGMODE_USER_KEY) == 0) { sscanf(value, "%d", &tmp); - if (tmp) { - exo_cfg->flags |= EXOSPHERE_FLAG_IS_DEBUGMODE_USER; - } else { - exo_cfg->flags &= ~(EXOSPHERE_FLAG_IS_DEBUGMODE_USER); + if (tmp == 1) { + parse_cfg->debugmode_user = 1; + } else if (tmp == 0) { + parse_cfg->debugmode_user = 0; } } else if (strcmp(name, EXOSPHERE_DISABLE_USERMODE_EXCEPTION_HANDLERS_KEY) == 0) { sscanf(value, "%d", &tmp); - if (tmp) { - exo_cfg->flags |= EXOSPHERE_FLAG_DISABLE_USERMODE_EXCEPTION_HANDLERS; - } else { - exo_cfg->flags &= ~(EXOSPHERE_FLAG_DISABLE_USERMODE_EXCEPTION_HANDLERS); + if (tmp == 1) { + parse_cfg->disable_user_exception_handlers = 1; + } else if (tmp == 0) { + parse_cfg->disable_user_exception_handlers = 0; } } else if (strcmp(name, EXOSPHERE_ENABLE_USERMODE_PMU_ACCESS_KEY) == 0) { sscanf(value, "%d", &tmp); - if (tmp) { - exo_cfg->flags |= EXOSPHERE_FLAG_ENABLE_USERMODE_PMU_ACCESS; - } else { - exo_cfg->flags &= ~(EXOSPHERE_FLAG_ENABLE_USERMODE_PMU_ACCESS); + if (tmp == 1) { + parse_cfg->enable_user_pmu_access = 1; + } else if (tmp == 0) { + parse_cfg->enable_user_pmu_access = 0; + } + } else if (strcmp(name, EXOSPHERE_BLANK_PRODINFO_SYSMMC_KEY) == 0) { + sscanf(value, "%d", &tmp); + if (tmp == 1) { + parse_cfg->blank_prodinfo_sysmmc = 1; + } else if (tmp == 0) { + parse_cfg->blank_prodinfo_sysmmc = 0; + } + } else if (strcmp(name, EXOSPHERE_BLANK_PRODINFO_EMUMMC_KEY) == 0) { + sscanf(value, "%d", &tmp); + if (tmp == 1) { + parse_cfg->blank_prodinfo_emummc = 1; + } else if (tmp == 0) { + parse_cfg->blank_prodinfo_emummc = 0; + } + } else if (strcmp(name, EXOSPHERE_ALLOW_WRITING_TO_CAL_SYSMMC_KEY) == 0) { + sscanf(value, "%d", &tmp); + if (tmp == 1) { + parse_cfg->allow_writing_to_cal_sysmmc = 1; + } else if (tmp == 0) { + parse_cfg->allow_writing_to_cal_sysmmc = 0; } } else { return 0; @@ -348,15 +367,42 @@ static void nxboot_configure_exosphere(uint32_t target_firmware, unsigned int ke exo_cfg.target_firmware = target_firmware; memcpy(&exo_cfg.emummc_cfg, exo_emummc_cfg, sizeof(*exo_emummc_cfg)); + const bool is_emummc = exo_emummc_cfg->base_cfg.magic == MAGIC_EMUMMC_CONFIG && exo_emummc_cfg->base_cfg.type != EMUMMC_TYPE_NONE; + if (keygen_type) { - exo_cfg.flags = EXOSPHERE_FLAGS_DEFAULT | EXOSPHERE_FLAG_PERFORM_620_KEYGEN; + exo_cfg.flags = EXOSPHERE_FLAG_PERFORM_620_KEYGEN; } else { - exo_cfg.flags = EXOSPHERE_FLAGS_DEFAULT; + exo_cfg.flags = 0; } - if (ini_parse_string(get_loader_ctx()->bct0, exosphere_ini_handler, &exo_cfg) < 0) { - fatal_error("[NXBOOT] Failed to parse BCT.ini!\n"); + /* Setup exosphere parse configuration with defaults. */ + exosphere_parse_cfg_t parse_cfg = { + .debugmode = 1, + .debugmode_user = 0, + .disable_user_exception_handlers = 0, + .enable_user_pmu_access = 0, + .blank_prodinfo_sysmmc = 0, + .blank_prodinfo_emummc = 0, + .allow_writing_to_cal_sysmmc = 0, + }; + + /* If we have an ini to read, parse it. */ + char *exosphere_ini = calloc(1, 0x10000); + if (read_from_file(exosphere_ini, 0xFFFF, "exosphere.ini")) { + if (ini_parse_string(exosphere_ini, exosphere_ini_handler, &parse_cfg) < 0) { + fatal_error("[NXBOOT] Failed to parse exosphere.ini!\n"); + } } + free(exosphere_ini); + + /* Apply parse config. */ + if (parse_cfg.debugmode) exo_cfg.flags |= EXOSPHERE_FLAG_IS_DEBUGMODE_PRIV; + if (parse_cfg.debugmode_user) exo_cfg.flags |= EXOSPHERE_FLAG_IS_DEBUGMODE_USER; + if (parse_cfg.disable_user_exception_handlers) exo_cfg.flags |= EXOSPHERE_FLAG_DISABLE_USERMODE_EXCEPTION_HANDLERS; + if (parse_cfg.enable_user_pmu_access) exo_cfg.flags |= EXOSPHERE_FLAG_ENABLE_USERMODE_PMU_ACCESS; + if (parse_cfg.blank_prodinfo_sysmmc && !is_emummc) exo_cfg.flags |= EXOSPHERE_FLAG_BLANK_PRODINFO; + if (parse_cfg.blank_prodinfo_emummc && is_emummc) exo_cfg.flags |= EXOSPHERE_FLAG_BLANK_PRODINFO; + if (parse_cfg.allow_writing_to_cal_sysmmc) exo_cfg.flags |= EXOSPHERE_FLAG_ALLOW_WRITING_TO_CAL_SYSMMC; if ((exo_cfg.target_firmware < ATMOSPHERE_TARGET_FIRMWARE_MIN) || (exo_cfg.target_firmware > ATMOSPHERE_TARGET_FIRMWARE_MAX)) { fatal_error("[NXBOOT] Invalid Exosphere target firmware!\n"); diff --git a/libraries/libstratosphere/include/stratosphere/ams/ams_environment.hpp b/libraries/libstratosphere/include/stratosphere/ams/ams_environment.hpp index 4fdd8f167..d610bce34 100644 --- a/libraries/libstratosphere/include/stratosphere/ams/ams_environment.hpp +++ b/libraries/libstratosphere/include/stratosphere/ams/ams_environment.hpp @@ -22,4 +22,8 @@ namespace ams { /* Will be called by libstratosphere on crash. */ void CrashHandler(ThreadExceptionDump *ctx); + /* API for boot sysmodule. */ + void InitializeForBoot(); + void SetInitialRebootPayload(const void *src, size_t src_size); + } diff --git a/libraries/libstratosphere/include/stratosphere/ams/ams_exosphere_api.hpp b/libraries/libstratosphere/include/stratosphere/ams/ams_exosphere_api.hpp index eb80efef4..bc0efd139 100644 --- a/libraries/libstratosphere/include/stratosphere/ams/ams_exosphere_api.hpp +++ b/libraries/libstratosphere/include/stratosphere/ams/ams_exosphere_api.hpp @@ -27,6 +27,11 @@ namespace ams::exosphere { bool IsRcmBugPatched(); + bool ShouldBlankProdInfo(); + bool ShouldAllowWritesToProdInfo(); + + u64 GetDeviceId(); + void CopyToIram(uintptr_t iram_dst, const void *dram_src, size_t size); void CopyFromIram(void *dram_dst, uintptr_t iram_src, size_t size); diff --git a/libraries/libstratosphere/include/stratosphere/settings.hpp b/libraries/libstratosphere/include/stratosphere/settings.hpp index a509fe93a..d78e42bef 100644 --- a/libraries/libstratosphere/include/stratosphere/settings.hpp +++ b/libraries/libstratosphere/include/stratosphere/settings.hpp @@ -16,11 +16,13 @@ #pragma once -#include "settings/settings_types.hpp" -#include "settings/settings_fwdbg_types.hpp" -#include "settings/settings_fwdbg_api.hpp" -#include "settings/system/settings_error_report.hpp" -#include "settings/system/settings_firmware_version.hpp" -#include "settings/system/settings_product_model.hpp" -#include "settings/system/settings_region.hpp" -#include "settings/system/settings_serial_number.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/libraries/libstratosphere/include/stratosphere/settings/factory/settings_device_certificate.hpp b/libraries/libstratosphere/include/stratosphere/settings/factory/settings_device_certificate.hpp new file mode 100644 index 000000000..b9be05258 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/settings/factory/settings_device_certificate.hpp @@ -0,0 +1,39 @@ +/* + * 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 . + */ +#pragma once +#include + +namespace ams::settings::factory { + + struct EccP256DeviceCertificate { + u8 data[0x180]; + }; + static_assert(sizeof(EccP256DeviceCertificate) == 0x180); + static_assert(std::is_pod::value); + + struct EccB233DeviceCertificate { + u8 data[0x180]; + }; + static_assert(sizeof(EccB233DeviceCertificate) == 0x180); + static_assert(std::is_pod::value); + + struct Rsa2048DeviceCertificate { + u8 data[0x240]; + }; + static_assert(sizeof(Rsa2048DeviceCertificate) == 0x240); + static_assert(std::is_pod::value); + +} diff --git a/libraries/libstratosphere/include/stratosphere/settings/factory/settings_serial_number.hpp b/libraries/libstratosphere/include/stratosphere/settings/factory/settings_serial_number.hpp new file mode 100644 index 000000000..068d7d655 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/settings/factory/settings_serial_number.hpp @@ -0,0 +1,27 @@ +/* + * 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 . + */ +#pragma once +#include + +namespace ams::settings::factory { + + struct SerialNumber { + char str[0x18]; + }; + static_assert(sizeof(SerialNumber) == 0x18); + static_assert(std::is_pod::value); + +} diff --git a/libraries/libstratosphere/include/stratosphere/spl/spl_api.hpp b/libraries/libstratosphere/include/stratosphere/spl/spl_api.hpp index b2ce4a56e..3b9639f82 100644 --- a/libraries/libstratosphere/include/stratosphere/spl/spl_api.hpp +++ b/libraries/libstratosphere/include/stratosphere/spl/spl_api.hpp @@ -27,4 +27,7 @@ namespace ams::spl { bool IsMariko(); bool IsRecoveryBoot(); + Result GenerateAesKek(AccessKey *access_key, const void *key_source, size_t key_source_size, u32 generation, u32 option); + Result GenerateAesKey(void *dst, size_t dst_size, const AccessKey &access_key, const void *key_source, size_t key_source_size); + } diff --git a/libraries/libstratosphere/include/stratosphere/spl/spl_types.hpp b/libraries/libstratosphere/include/stratosphere/spl/spl_types.hpp index e73e66713..43dbb54ed 100644 --- a/libraries/libstratosphere/include/stratosphere/spl/spl_types.hpp +++ b/libraries/libstratosphere/include/stratosphere/spl/spl_types.hpp @@ -183,6 +183,36 @@ namespace ams::spl { static_assert(alignof(AccessKey) == alignof(u8), "KeySource definition!"); #pragma pack(pop) + enum class ConfigItem : u32 { + /* Standard config items. */ + DisableProgramVerification = 1, + DramId = 2, + SecurityEngineIrqNumber = 3, + Version = 4, + HardwareType = 5, + IsRetail = 6, + IsRecoveryBoot = 7, + DeviceId = 8, + BootReason = 9, + MemoryMode = 10, + IsDebugMode = 11, + KernelConfiguration = 12, + IsChargerHiZModeEnabled = 13, + IsQuest = 14, + RegulatorType = 15, + DeviceUniqueKeyGeneration = 16, + Package2Hash = 17, + + /* Extension config items for exosphere. */ + ExosphereApiVersion = 65000, + ExosphereNeedsReboot = 65001, + ExosphereNeedsShutdown = 65002, + ExosphereGitCommitHash = 65003, + ExosphereHasRcmBugPatch = 65004, + ExosphereBlankProdInfo = 65005, + ExosphereAllowCalWrites = 65006, + }; + } /* Extensions to libnx spl config item enum. */ @@ -191,3 +221,5 @@ constexpr inline SplConfigItem SplConfigItem_ExosphereNeedsReboot = static_ca constexpr inline SplConfigItem SplConfigItem_ExosphereNeedsShutdown = static_cast(65002); constexpr inline SplConfigItem SplConfigItem_ExosphereGitCommitHash = static_cast(65003); constexpr inline SplConfigItem SplConfigItem_ExosphereHasRcmBugPatch = static_cast(65004); +constexpr inline SplConfigItem SplConfigItem_ExosphereBlankProdInfo = static_cast(65005); +constexpr inline SplConfigItem SplConfigItem_ExosphereAllowCalWrites = static_cast(65006); diff --git a/libraries/libstratosphere/source/ams/ams_bpc.c b/libraries/libstratosphere/source/ams/ams_bpc.c index d04a69b72..8a3ddee4b 100644 --- a/libraries/libstratosphere/source/ams/ams_bpc.c +++ b/libraries/libstratosphere/source/ams/ams_bpc.c @@ -24,6 +24,11 @@ NX_GENERATE_SERVICE_GUARD(amsBpc); Result _amsBpcInitialize(void) { Handle h; Result rc = svcConnectToNamedPort(&h, "bpc:ams"); /* TODO: ams:bpc */ + while (R_VALUE(rc) == KERNELRESULT(NotFound)) { + svcSleepThread(50000000ul); + rc = svcConnectToNamedPort(&h, "bpc:ams"); + } + if (R_SUCCEEDED(rc)) serviceCreate(&g_amsBpcSrv, h); return rc; } @@ -44,3 +49,11 @@ Result amsBpcRebootToFatalError(void *ctx) { .buffers = { { ctx, 0x450 } }, ); } + + +Result amsBpcSetInitialPayload(const void *src, size_t src_size) { + return serviceDispatch(&g_amsBpcSrv, 65001, + .buffer_attrs = { SfBufferAttr_In | SfBufferAttr_HipcMapAlias }, + .buffers = { { src, src_size } }, + ); +} diff --git a/libraries/libstratosphere/source/ams/ams_bpc.h b/libraries/libstratosphere/source/ams/ams_bpc.h index 05983fa6c..d8a3a9ed2 100644 --- a/libraries/libstratosphere/source/ams/ams_bpc.h +++ b/libraries/libstratosphere/source/ams/ams_bpc.h @@ -28,6 +28,7 @@ void amsBpcExit(void); Service *amsBpcGetServiceSession(void); Result amsBpcRebootToFatalError(void *ctx); +Result amsBpcSetInitialPayload(const void *src, size_t src_size); #ifdef __cplusplus } diff --git a/libraries/libstratosphere/source/ams/ams_environment.cpp b/libraries/libstratosphere/source/ams/ams_environment.cpp index 0af5dc5b1..f4bc04199 100644 --- a/libraries/libstratosphere/source/ams/ams_environment.cpp +++ b/libraries/libstratosphere/source/ams/ams_environment.cpp @@ -36,6 +36,14 @@ namespace ams { extern ncm::ProgramId CurrentProgramId; + void InitializeForBoot() { + R_ABORT_UNLESS(amsBpcInitialize()); + } + + void SetInitialRebootPayload(const void *src, size_t src_size) { + R_ABORT_UNLESS(amsBpcSetInitialPayload(src, src_size)); + } + void WEAK_SYMBOL ExceptionHandler(FatalErrorContext *ctx) { R_ABORT_UNLESS(amsBpcInitialize()); R_ABORT_UNLESS(amsBpcRebootToFatalError(ctx)); diff --git a/libraries/libstratosphere/source/ams/ams_exosphere_api.cpp b/libraries/libstratosphere/source/ams/ams_exosphere_api.cpp index 915166445..63243d66a 100644 --- a/libraries/libstratosphere/source/ams/ams_exosphere_api.cpp +++ b/libraries/libstratosphere/source/ams/ams_exosphere_api.cpp @@ -51,19 +51,32 @@ namespace ams::exosphere { namespace { - inline Result GetRcmBugPatched(bool *out) { - u64 tmp = 0; - R_TRY(spl::smc::ConvertResult(spl::smc::GetConfig(&tmp, 1, SplConfigItem_ExosphereHasRcmBugPatch))); - *out = (tmp != 0); - return ResultSuccess(); + inline u64 GetU64ConfigItem(spl::ConfigItem cfg) { + u64 tmp; + R_ABORT_UNLESS(spl::smc::ConvertResult(spl::smc::GetConfig(std::addressof(tmp), 1, static_cast<::SplConfigItem>(cfg)))); + return tmp; + } + + inline bool GetBooleanConfigItem(spl::ConfigItem cfg) { + return GetU64ConfigItem(cfg) != 0; } } bool IsRcmBugPatched() { - bool rcm_bug_patched; - R_ABORT_UNLESS(GetRcmBugPatched(&rcm_bug_patched)); - return rcm_bug_patched; + return GetBooleanConfigItem(spl::ConfigItem::ExosphereHasRcmBugPatch); + } + + bool ShouldBlankProdInfo() { + return GetBooleanConfigItem(spl::ConfigItem::ExosphereBlankProdInfo); + } + + bool ShouldAllowWritesToProdInfo() { + return GetBooleanConfigItem(spl::ConfigItem::ExosphereAllowCalWrites); + } + + u64 GetDeviceId() { + return GetU64ConfigItem(spl::ConfigItem::DeviceId); } } diff --git a/libraries/libstratosphere/source/spl/spl_api.cpp b/libraries/libstratosphere/source/spl/spl_api.cpp index 032ac52ac..7ffa4c333 100644 --- a/libraries/libstratosphere/source/spl/spl_api.cpp +++ b/libraries/libstratosphere/source/spl/spl_api.cpp @@ -78,4 +78,15 @@ namespace ams::spl { } } + Result GenerateAesKek(AccessKey *access_key, const void *key_source, size_t key_source_size, u32 generation, u32 option) { + AMS_ASSERT(key_source_size == sizeof(KeySource)); + return splCryptoGenerateAesKek(key_source, generation, option, static_cast(access_key)); + } + + Result GenerateAesKey(void *dst, size_t dst_size, const AccessKey &access_key, const void *key_source, size_t key_source_size) { + AMS_ASSERT(dst_size == crypto::AesEncryptor128::KeySize); + AMS_ASSERT(key_source_size == sizeof(KeySource)); + return splCryptoGenerateAesKey(std::addressof(access_key), key_source, dst); + } + } diff --git a/stratosphere/ams_mitm/source/amsmitm_initialization.cpp b/stratosphere/ams_mitm/source/amsmitm_initialization.cpp index ee4abd888..f1c2cc8fc 100644 --- a/stratosphere/ams_mitm/source/amsmitm_initialization.cpp +++ b/stratosphere/ams_mitm/source/amsmitm_initialization.cpp @@ -16,6 +16,7 @@ #include #include "amsmitm_initialization.hpp" #include "amsmitm_fs_utils.hpp" +#include "amsmitm_prodinfo_utils.hpp" #include "bpc_mitm/bpc_ams_power_utils.hpp" #include "set_mitm/settings_sd_kvs.hpp" @@ -58,10 +59,6 @@ namespace ams::mitm { alignas(os::ThreadStackAlignment) u8 g_initialize_thread_stack[InitializeThreadStackSize]; /* Console-unique data backup and protection. */ - constexpr size_t CalibrationBinarySize = 0x8000; - u8 g_calibration_binary_storage_backup[CalibrationBinarySize]; - u8 g_calibration_binary_file_backup[CalibrationBinarySize]; - FsFile g_calibration_binary_file; FsFile g_bis_key_file; /* Emummc file protection. */ @@ -90,57 +87,10 @@ namespace ams::mitm { /* Create a backup directory, if one doesn't exist. */ mitm::fs::CreateAtmosphereSdDirectory("/automatic_backups"); - /* Read the calibration binary. */ - { - FsStorage calibration_binary_storage; - R_ABORT_UNLESS(fsOpenBisStorage(&calibration_binary_storage, FsBisPartitionId_CalibrationBinary)); - ON_SCOPE_EXIT { fsStorageClose(&calibration_binary_storage); }; - - R_ABORT_UNLESS(fsStorageRead(&calibration_binary_storage, 0, g_calibration_binary_storage_backup, CalibrationBinarySize)); - } - - /* Copy serial number from partition. */ - /* TODO: Define the magic numbers? Structs? */ - char serial_number[0x40] = {}; - std::memcpy(serial_number, g_calibration_binary_storage_backup + 0x250, 0x18); - - /* Backup the calibration binary. */ - { - char calibration_binary_backup_name[ams::fs::EntryNameLengthMax + 1]; - GetBackupFileName(calibration_binary_backup_name, sizeof(calibration_binary_backup_name), serial_number, "PRODINFO.bin"); - - mitm::fs::CreateAtmosphereSdFile(calibration_binary_backup_name, CalibrationBinarySize, ams::fs::CreateOption_None); - R_ABORT_UNLESS(mitm::fs::OpenAtmosphereSdFile(&g_calibration_binary_file, calibration_binary_backup_name, ams::fs::OpenMode_ReadWrite)); - - s64 file_size = 0; - R_ABORT_UNLESS(fsFileGetSize(&g_calibration_binary_file, &file_size)); - - bool is_file_backup_valid = file_size == CalibrationBinarySize; - if (is_file_backup_valid) { - u64 read_size = 0; - R_ABORT_UNLESS(fsFileRead(&g_calibration_binary_file, 0, g_calibration_binary_file_backup, CalibrationBinarySize, FsReadOption_None, &read_size)); - AMS_ABORT_UNLESS(read_size == CalibrationBinarySize); - is_file_backup_valid &= std::memcmp(g_calibration_binary_file_backup, "CAL0", 4) == 0; - is_file_backup_valid &= std::memcmp(g_calibration_binary_file_backup + 0x250, serial_number, 0x18) == 0; - const u32 cal_bin_size = *reinterpret_cast(g_calibration_binary_file_backup + 0x8); - is_file_backup_valid &= cal_bin_size + 0x40 <= CalibrationBinarySize; - if (is_file_backup_valid) { - u8 calc_hash[SHA256_HASH_SIZE]; - /* TODO: ams::crypto? */ - sha256CalculateHash(calc_hash, g_calibration_binary_file_backup + 0x40, cal_bin_size); - is_file_backup_valid &= std::memcmp(calc_hash, g_calibration_binary_file_backup + 0x20, sizeof(calc_hash)) == 0; - } - } - - if (!is_file_backup_valid) { - R_ABORT_UNLESS(fsFileSetSize(&g_calibration_binary_file, CalibrationBinarySize)); - R_ABORT_UNLESS(fsFileWrite(&g_calibration_binary_file, 0, g_calibration_binary_storage_backup, CalibrationBinarySize, FsWriteOption_Flush)); - } - - /* Note: g_calibration_binary_file is intentionally not closed here. This prevents any other process from opening it. */ - std::memset(g_calibration_binary_file_backup, 0, CalibrationBinarySize); - std::memset(g_calibration_binary_storage_backup, 0, CalibrationBinarySize); - } + /* Initialize PRODINFO and get a reference for the device. */ + char device_reference[0x40] = {}; + ON_SCOPE_EXIT { std::memset(device_reference, 0, sizeof(device_reference)); }; + mitm::SaveProdInfoBackupsAndWipeMemory(device_reference, sizeof(device_reference)); /* Backup BIS keys. */ { @@ -151,6 +101,7 @@ namespace ams::mitm { u8 bis_keys[4][2][0x10]; std::memset(bis_keys, 0xCC, sizeof(bis_keys)); + ON_SCOPE_EXIT { std::memset(bis_keys, 0xCC, sizeof(bis_keys)); }; /* TODO: Clean this up. */ for (size_t partition = 0; partition < 4; partition++) { @@ -170,7 +121,7 @@ namespace ams::mitm { } char bis_keys_backup_name[ams::fs::EntryNameLengthMax + 1]; - GetBackupFileName(bis_keys_backup_name, sizeof(bis_keys_backup_name), serial_number, "BISKEYS.bin"); + GetBackupFileName(bis_keys_backup_name, sizeof(bis_keys_backup_name), device_reference, "BISKEYS.bin"); mitm::fs::CreateAtmosphereSdFile(bis_keys_backup_name, sizeof(bis_keys), ams::fs::CreateOption_None); R_ABORT_UNLESS(mitm::fs::OpenAtmosphereSdFile(&g_bis_key_file, bis_keys_backup_name, ams::fs::OpenMode_ReadWrite)); @@ -222,6 +173,10 @@ namespace ams::mitm { } void StartInitialize() { + /* Initialize prodinfo. */ + mitm::InitializeProdInfoManagement(); + + /* Launch initialize thread. */ R_ABORT_UNLESS(os::CreateThread(std::addressof(g_initialize_thread), InitializeThreadFunc, nullptr, g_initialize_thread_stack, sizeof(g_initialize_thread_stack), AMS_GET_SYSTEM_THREAD_PRIORITY(mitm, InitializeThread))); os::SetThreadNamePointer(std::addressof(g_initialize_thread), AMS_GET_SYSTEM_THREAD_NAME(mitm, InitializeThread)); os::StartThread(std::addressof(g_initialize_thread)); diff --git a/stratosphere/ams_mitm/source/amsmitm_initialization.hpp b/stratosphere/ams_mitm/source/amsmitm_initialization.hpp index 1c6723a3b..1c9d15f2d 100644 --- a/stratosphere/ams_mitm/source/amsmitm_initialization.hpp +++ b/stratosphere/ams_mitm/source/amsmitm_initialization.hpp @@ -19,6 +19,7 @@ namespace ams::mitm { void StartInitialize(); + bool IsInitialized(); void WaitInitialized(); diff --git a/stratosphere/ams_mitm/source/amsmitm_main.cpp b/stratosphere/ams_mitm/source/amsmitm_main.cpp index 6f5994280..154166eb9 100644 --- a/stratosphere/ams_mitm/source/amsmitm_main.cpp +++ b/stratosphere/ams_mitm/source/amsmitm_main.cpp @@ -95,9 +95,6 @@ void __appExit(void) { } int main(int argc, char **argv) { - /* Start initialization (sd card init, automatic backups, etc) */ - mitm::StartInitialize(); - /* Launch all mitm modules in sequence. */ mitm::LaunchAllModules(); diff --git a/stratosphere/ams_mitm/source/amsmitm_module_management.cpp b/stratosphere/ams_mitm/source/amsmitm_module_management.cpp index f27a72e7b..e139c557a 100644 --- a/stratosphere/ams_mitm/source/amsmitm_module_management.cpp +++ b/stratosphere/ams_mitm/source/amsmitm_module_management.cpp @@ -20,6 +20,7 @@ #include "fs_mitm/fsmitm_module.hpp" #include "set_mitm/setmitm_module.hpp" #include "bpc_mitm/bpcmitm_module.hpp" +#include "bpc_mitm/bpc_ams_module.hpp" #include "ns_mitm/nsmitm_module.hpp" #include "hid_mitm/hidmitm_module.hpp" @@ -31,6 +32,7 @@ namespace ams::mitm { ModuleId_FsMitm, ModuleId_SetMitm, ModuleId_BpcMitm, + ModuleId_BpcAms, ModuleId_NsMitm, ModuleId_HidMitm, @@ -62,6 +64,7 @@ namespace ams::mitm { GetModuleDefinition(), GetModuleDefinition(), GetModuleDefinition(), + GetModuleDefinition(), GetModuleDefinition(), GetModuleDefinition(), }; diff --git a/stratosphere/ams_mitm/source/amsmitm_prodinfo_utils.cpp b/stratosphere/ams_mitm/source/amsmitm_prodinfo_utils.cpp new file mode 100644 index 000000000..0931110b9 --- /dev/null +++ b/stratosphere/ams_mitm/source/amsmitm_prodinfo_utils.cpp @@ -0,0 +1,639 @@ +/* + * 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 . + */ +#include +#include "amsmitm_fs_utils.hpp" +#include "amsmitm_prodinfo_utils.hpp" + +namespace ams::mitm { + + namespace { + + constexpr inline u16 Crc16InitialValue = 0x55AA; + + constexpr inline u16 Crc16Table[] = { + 0x0000, 0xCC01, 0xD801, 0x1400, + 0xF001, 0x3C00, 0x2800, 0xE401, + 0xA001, 0x6C00, 0x7800, 0xB401, + 0x5000, 0x9C01, 0x8801, 0x4400, + }; + + u16 GetCrc16(const void *data, size_t size) { + AMS_ASSERT(data != nullptr); + AMS_ASSERT(size > 0); + + const u8 *src = static_cast(data); + + u16 crc = Crc16InitialValue; + + u16 tmp = 0; + while ((size--) > 0) { + tmp = Crc16Table[crc & 0xF]; + crc = ((crc >> 4) & 0x0FFF) ^ tmp ^ Crc16Table[*src & 0xF]; + tmp = Crc16Table[crc & 0xF]; + crc = ((crc >> 4) & 0x0FFF) ^ tmp ^ Crc16Table[(*(src++) >> 4) & 0xF]; + } + return crc; + } + + bool IsBlank(const void *data, size_t size) { + AMS_ASSERT(data != nullptr); + AMS_ASSERT(size > 0); + + const u8 *src = static_cast(data); + while ((size--) > 0) { + if (*(src++) != 0) { + return false; + } + } + return true; + } + + constexpr inline u32 CalibrationMagic = util::FourCC<'C','A','L','0'>::Code; + + struct Sha256Hash { + u8 data[crypto::Sha256Generator::HashSize]; + }; + + struct CalibrationInfoHeader { + u32 magic; + u32 version; + u32 body_size; + u16 model; + u16 update_count; + u8 pad[0xE]; + u16 crc; + Sha256Hash body_hash; + }; + static_assert(sizeof(CalibrationInfoHeader) == 0x40); + + constexpr inline size_t CalibrationInfoBodySizeMax = CalibrationBinarySize - sizeof(CalibrationInfoHeader); + + struct CalibrationInfo { + CalibrationInfoHeader header; + u8 body[CalibrationInfoBodySizeMax]; /* TODO: CalibrationInfoBody body; */ + + template + Block &GetBlock() { + static_assert(Block::Offset >= sizeof(CalibrationInfoHeader)); + static_assert(Block::Offset < sizeof(CalibrationInfo)); + static_assert(Block::Offset + Block::Size <= sizeof(CalibrationInfo)); + return *static_cast(static_cast(std::addressof(this->body[Block::Offset - sizeof(this->header)]))); + } + + template + const Block &GetBlock() const { + static_assert(Block::Offset >= sizeof(CalibrationInfoHeader)); + static_assert(Block::Offset < sizeof(CalibrationInfo)); + static_assert(Block::Offset + Block::Size <= sizeof(CalibrationInfo)); + return *static_cast(static_cast(std::addressof(this->body[Block::Offset - sizeof(this->header)]))); + } + }; + static_assert(sizeof(CalibrationInfo) == CalibrationBinarySize); + + struct SecureCalibrationInfoBackup { + CalibrationInfo info; + Sha256Hash hash; + u8 pad[SecureCalibrationBinaryBackupSize - sizeof(info) - sizeof(hash)]; + }; + static_assert(sizeof(SecureCalibrationInfoBackup) == SecureCalibrationBinaryBackupSize); + + bool IsValidSha256Hash(const Sha256Hash &hash, const void *data, size_t data_size) { + Sha256Hash calc_hash; + ON_SCOPE_EXIT { ::ams::crypto::ClearMemory(std::addressof(calc_hash), sizeof(calc_hash)); }; + + ::ams::crypto::GenerateSha256Hash(std::addressof(calc_hash), sizeof(calc_hash), data, data_size); + return ::ams::crypto::IsSameBytes(std::addressof(calc_hash), std::addressof(hash), sizeof(Sha256Hash)); + } + + bool IsValid(const CalibrationInfoHeader &header) { + return header.magic == CalibrationMagic && GetCrc16(std::addressof(header), OFFSETOF(CalibrationInfoHeader, crc)) == header.crc; + } + + bool IsValid(const CalibrationInfoHeader &header, const void *body) { + return IsValid(header) && IsValidSha256Hash(header.body_hash, body, header.body_size); + } + + #define DEFINE_CALIBRATION_CRC_BLOCK(_TypeName, _Offset, _Size, _Decl, _MemberName) \ + struct _TypeName { \ + static constexpr size_t Offset = _Offset; \ + static constexpr size_t Size = _Size; \ + static constexpr bool IsCrcBlock = true; \ + static constexpr bool IsShaBlock = false; \ + _Decl; \ + static_assert(Size >= sizeof(_MemberName) + sizeof(u16)); \ + u8 pad[Size - sizeof(_MemberName) - sizeof(u16)]; \ + u16 crc; \ + }; \ + static_assert(sizeof(_TypeName) == _TypeName::Size) + + #define DEFINE_CALIBRATION_SHA_BLOCK(_TypeName, _Offset, _Size, _Decl, _MemberName) \ + struct _TypeName { \ + static constexpr size_t Offset = _Offset; \ + static constexpr size_t Size = _Size; \ + static constexpr bool IsCrcBlock = false; \ + static constexpr bool IsShaBlock = true; \ + _Decl; \ + static_assert(Size == sizeof(_MemberName) + sizeof(Sha256Hash)); \ + Sha256Hash sha256_hash; \ + }; \ + static_assert(sizeof(_TypeName) == _TypeName::Size) + + DEFINE_CALIBRATION_CRC_BLOCK(SerialNumberBlock, 0x0250, 0x020, ::ams::settings::factory::SerialNumber serial_number, serial_number); + DEFINE_CALIBRATION_CRC_BLOCK(EccB233DeviceCertificateBlock, 0x0480, 0x190, ::ams::settings::factory::EccB233DeviceCertificate device_certificate, device_certificate); + DEFINE_CALIBRATION_CRC_BLOCK(SslKeyBlock, 0x09B0, 0x120, u8 ssl_key[0x110], ssl_key); + DEFINE_CALIBRATION_CRC_BLOCK(SslCertificateSizeBlock, 0x0AD0, 0x010, u64 ssl_certificate_size, ssl_certificate_size); + DEFINE_CALIBRATION_SHA_BLOCK(SslCertificateBlock, 0x0AE0, 0x820, u8 ssl_certificate[0x800], ssl_certificate); + DEFINE_CALIBRATION_CRC_BLOCK(EcqvEcdsaAmiiboRootCertificateBlock, 0x35A0, 0x080, u8 data[0x70], data); + DEFINE_CALIBRATION_CRC_BLOCK(EcqvBlsAmiiboRootCertificateBlock, 0x36A0, 0x0A0, u8 data[0x90], data); + DEFINE_CALIBRATION_CRC_BLOCK(ExtendedSslKeyBlock, 0x3AE0, 0x140, u8 ssl_key[0x134], ssl_key); + DEFINE_CALIBRATION_CRC_BLOCK(Rsa2048DeviceKeyBlock, 0x3D70, 0x250, u8 device_key[0x240], device_key); + DEFINE_CALIBRATION_CRC_BLOCK(Rsa2048DeviceCertificateBlock, 0x3FC0, 0x250, ::ams::settings::factory::Rsa2048DeviceCertificate device_certificate, device_certificate); + + #undef DEFINE_CALIBRATION_CRC_BLOCK + #undef DEFINE_CALIBRATION_SHA_BLOCK + + constexpr inline const char BlankSerialNumberString[] = "XAW00000000000"; + + template + void Blank(Block &block) { + if constexpr (std::is_same::value) { + static_assert(sizeof(BlankSerialNumberString) <= sizeof(SerialNumberBlock::serial_number)); + std::memset(std::addressof(block), 0, Block::Size - sizeof(block.crc)); + std::memcpy(block.serial_number.str, BlankSerialNumberString, sizeof(BlankSerialNumberString)); + block.crc = GetCrc16(std::addressof(block), Block::Size - sizeof(block.crc)); + } else if constexpr (std::is_same::value) { + std::memset(std::addressof(block), 0, sizeof(block.ssl_certificate)); + } else if constexpr (Block::IsCrcBlock) { + std::memset(std::addressof(block), 0, Block::Size - sizeof(block.crc)); + block.crc = GetCrc16(std::addressof(block), Block::Size - sizeof(block.crc)); + } else { + static_assert(Block::IsShaBlock); + std::memset(std::addressof(block), 0, Block::Size); + ::ams::crypto::GenerateSha256Hash(std::addressof(block.sha256_hash), sizeof(block.sha256_hash), std::addressof(block), Block::Size - sizeof(block.sha256_hash)); + } + } + + template + bool IsBlank(const Block &block) { + if constexpr (std::is_same::value) { + static_assert(sizeof(BlankSerialNumberString) <= sizeof(SerialNumberBlock::serial_number)); + return std::memcmp(block.serial_number.str, BlankSerialNumberString, sizeof(BlankSerialNumberString) - 1) == 0 || IsBlank(std::addressof(block), Block::Size - sizeof(block.crc)); + } else if constexpr (Block::IsCrcBlock) { + return IsBlank(std::addressof(block), Block::Size - sizeof(block.crc)); + } else { + return IsBlank(std::addressof(block), Block::Size - sizeof(block.sha256_hash)); + } + } + + template + bool IsValid(const Block &block, size_t size = 0) { + if constexpr (Block::IsCrcBlock) { + return GetCrc16(std::addressof(block), Block::Size - sizeof(block.crc)) == block.crc; + } else { + static_assert(Block::IsShaBlock); + return IsValidSha256Hash(block.sha256_hash, std::addressof(block), size != 0 ? size : Block::Size - sizeof(block.sha256_hash)); + } + } + + + void Blank(CalibrationInfo &info) { + /* Set header. */ + info.header.magic = CalibrationMagic; + info.header.body_size = sizeof(info.body); + info.header.crc = GetCrc16(std::addressof(info.header), OFFSETOF(CalibrationInfoHeader, crc)); + + /* Set blocks. */ + Blank(info.GetBlock()); + Blank(info.GetBlock()); + Blank(info.GetBlock()); + Blank(info.GetBlock()); + Blank(info.GetBlock()); + Blank(info.GetBlock()); + if (IsValid(info.GetBlock()) && !IsBlank(info.GetBlock())) Blank(info.GetBlock()); + if (IsValid(info.GetBlock()) && !IsBlank(info.GetBlock())) Blank(info.GetBlock()); + + /* Set header hash. */ + crypto::GenerateSha256Hash(std::addressof(info.header.body_hash), sizeof(info.header.body_hash), std::addressof(info.body), sizeof(info.body)); + } + + bool IsValidHeader(const CalibrationInfo &cal) { + return IsValid(cal.header) && cal.header.body_size <= CalibrationInfoBodySizeMax && IsValid(cal.header, cal.body); + } + + bool IsValidSerialNumber(const char *sn) { + for (size_t i = 0; i < std::strlen(sn); i++) { + if (!std::isalnum(static_cast(sn[i]))) { + return false; + } + } + return true; + } + + void GetSerialNumber(char *dst, const CalibrationInfo &info) { + std::memcpy(dst, std::addressof(info.GetBlock()), sizeof(info.GetBlock().serial_number)); + dst[sizeof(info.GetBlock().serial_number) + 1] = '\x00'; + } + + bool IsValidSerialNumber(const CalibrationInfo &cal) { + char sn[0x20] = {}; + ON_SCOPE_EXIT { std::memset(sn, 0, sizeof(sn)); }; + + GetSerialNumber(sn, cal); + return IsValidSerialNumber(sn); + } + + bool IsValid(const CalibrationInfo &cal) { + return IsValidHeader(cal) && + IsValid(cal.GetBlock()) && + IsValid(cal.GetBlock()) && + IsValid(cal.GetBlock()) && + IsValid(cal.GetBlock()) && + cal.GetBlock().ssl_certificate_size <= sizeof(cal.GetBlock().ssl_certificate) && + IsValid(cal.GetBlock(), cal.GetBlock().ssl_certificate_size) && + IsValid(cal.GetBlock()) && + IsValid(cal.GetBlock()) && + IsValid(cal.GetBlock()) && + IsValidSerialNumber(cal); + } + + bool ContainsCorrectDeviceId(const EccB233DeviceCertificateBlock &block, u64 device_id) { + static constexpr size_t DeviceIdOffset = 0xC6; + char found_device_id_str[sizeof("0011223344556677")] = {}; + ON_SCOPE_EXIT { std::memset(found_device_id_str, 0, sizeof(found_device_id_str)); }; + std::memcpy(found_device_id_str, std::addressof(block.device_certificate.data[DeviceIdOffset]), sizeof(found_device_id_str) - 1); + + static constexpr u64 DeviceIdLowMask = 0x00FFFFFFFFFFFFFFul; + + return (std::strtoul(found_device_id_str, nullptr, 16) & DeviceIdLowMask) == (device_id & DeviceIdLowMask); + } + + bool ContainsCorrectDeviceId(const CalibrationInfo &cal) { + return ContainsCorrectDeviceId(cal.GetBlock(), exosphere::GetDeviceId()); + } + + bool IsValidForSecureBackup(const CalibrationInfo &cal) { + return IsValid(cal) && ContainsCorrectDeviceId(cal); + } + + bool IsBlank(const CalibrationInfo &cal) { + return IsBlank(cal.GetBlock()) || + IsBlank(cal.GetBlock()) || + IsBlank(cal.GetBlock()) || + IsBlank(cal.GetBlock()) || + IsBlank(cal.GetBlock()) || + IsBlank(cal.GetBlock()); + } + + void ReadStorageCalibrationBinary(CalibrationInfo *out) { + FsStorage calibration_binary_storage; + R_ABORT_UNLESS(fsOpenBisStorage(&calibration_binary_storage, FsBisPartitionId_CalibrationBinary)); + ON_SCOPE_EXIT { fsStorageClose(&calibration_binary_storage); }; + + R_ABORT_UNLESS(fsStorageRead(&calibration_binary_storage, 0, out, sizeof(*out))); + } + + constexpr inline const u8 SecureCalibrationBinaryBackupIv[crypto::Aes128CtrDecryptor::IvSize] = {}; + + void ReadStorageEncryptedSecureCalibrationBinaryBackupUnsafe(SecureCalibrationInfoBackup *out) { + FsStorage calibration_binary_storage; + R_ABORT_UNLESS(fsOpenBisStorage(&calibration_binary_storage, FsBisPartitionId_CalibrationBinary)); + ON_SCOPE_EXIT { fsStorageClose(&calibration_binary_storage); }; + + R_ABORT_UNLESS(fsStorageRead(&calibration_binary_storage, SecureCalibrationInfoBackupOffset, out, sizeof(*out))); + } + + void WriteStorageEncryptedSecureCalibrationBinaryBackupUnsafe(const SecureCalibrationInfoBackup *src) { + FsStorage calibration_binary_storage; + R_ABORT_UNLESS(fsOpenBisStorage(&calibration_binary_storage, FsBisPartitionId_CalibrationBinary)); + ON_SCOPE_EXIT { fsStorageClose(&calibration_binary_storage); }; + + R_ABORT_UNLESS(fsStorageWrite(&calibration_binary_storage, SecureCalibrationInfoBackupOffset, src, sizeof(*src))); + } + + void GenerateSecureCalibrationBinaryBackupKey(void *dst, size_t dst_size) { + static constexpr const u8 SecureCalibrationBinaryBackupKeySource[crypto::Aes128CtrDecryptor::KeySize] = { '|', '-', 'A', 'M', 'S', '-', 'C', 'A', 'L', '0', '-', 'K', 'E', 'Y', '-', '|' }; + spl::AccessKey access_key; + ON_SCOPE_EXIT { crypto::ClearMemory(std::addressof(access_key), sizeof(access_key)); }; + + /* Generate a personalized kek. */ + R_ABORT_UNLESS(spl::GenerateAesKek(std::addressof(access_key), SecureCalibrationBinaryBackupKeySource, sizeof(SecureCalibrationBinaryBackupKeySource), 0, 1)); + + /* Generate a personalized key. */ + R_ABORT_UNLESS(spl::GenerateAesKey(dst, dst_size, access_key, SecureCalibrationBinaryBackupKeySource, sizeof(SecureCalibrationBinaryBackupKeySource))); + } + + bool ReadStorageSecureCalibrationBinaryBackup(SecureCalibrationInfoBackup *out) { + /* Read the data. */ + ReadStorageEncryptedSecureCalibrationBinaryBackupUnsafe(out); + + /* Don't leak any data unless we validate. */ + auto clear_guard = SCOPE_GUARD { std::memset(out, 0, sizeof(*out)); }; + + { + /* Create a buffer to hold our key. */ + u8 key[crypto::Aes128CtrDecryptor::KeySize]; + ON_SCOPE_EXIT { crypto::ClearMemory(key, sizeof(key)); }; + + /* Generate the key. */ + GenerateSecureCalibrationBinaryBackupKey(key, sizeof(key)); + + /* Decrypt the data in place. */ + crypto::DecryptAes128Ctr(out, sizeof(*out), key, sizeof(key), SecureCalibrationBinaryBackupIv, sizeof(SecureCalibrationBinaryBackupIv), out, sizeof(*out)); + } + + /* Generate a hash for the data. */ + if (!IsValidSha256Hash(out->hash, std::addressof(out->info), sizeof(out->info))) { + return false; + } + + /* Validate the backup. */ + if (!IsValidForSecureBackup(out->info)) { + return false; + } + + /* Our backup is valid. */ + clear_guard.Cancel(); + return true; + } + + void WriteStorageSecureCalibrationBinaryBackup(SecureCalibrationInfoBackup *src) { + /* Clear the input once we've written it. */ + ON_SCOPE_EXIT { std::memset(src, 0, sizeof(*src)); }; + + /* Ensure that the input is valid. */ + AMS_ABORT_UNLESS(IsValidForSecureBackup(src->info)); + + /* Set the Sha256 hash. */ + crypto::GenerateSha256Hash(std::addressof(src->hash), sizeof(src->hash), std::addressof(src->info), sizeof(src->info)); + + /* Validate the hash. */ + AMS_ABORT_UNLESS(IsValidSha256Hash(src->hash, std::addressof(src->info), sizeof(src->info))); + + /* Encrypt the data. */ + { + /* Create a buffer to hold our key. */ + u8 key[crypto::Aes128CtrDecryptor::KeySize]; + ON_SCOPE_EXIT { crypto::ClearMemory(key, sizeof(key)); }; + + /* Generate the key. */ + GenerateSecureCalibrationBinaryBackupKey(key, sizeof(key)); + + /* Encrypt the data in place. */ + crypto::EncryptAes128Ctr(src, sizeof(*src), key, sizeof(key), SecureCalibrationBinaryBackupIv, sizeof(SecureCalibrationBinaryBackupIv), src, sizeof(*src)); + } + + /* Write the encrypted data. */ + WriteStorageEncryptedSecureCalibrationBinaryBackupUnsafe(src); + } + + void GetBackupFileName(char *dst, size_t dst_size, const CalibrationInfo &info) { + char sn[0x20] = {}; + ON_SCOPE_EXIT { std::memset(sn, 0, sizeof(sn)); }; + + + if (IsValidForSecureBackup(info)) { + GetSerialNumber(sn, info); + std::snprintf(dst, dst_size, "automatic_backups/%s_PRODINFO.bin", sn); + } else { + Sha256Hash hash; + crypto::GenerateSha256Hash(std::addressof(hash), sizeof(hash), std::addressof(info), sizeof(info)); + ON_SCOPE_EXIT { crypto::ClearMemory(std::addressof(hash), sizeof(hash)); }; + + if (IsValid(info)) { + if (IsBlank(info)) { + std::snprintf(dst, dst_size, "automatic_backups/BLANK_PRODINFO_%02X%02X%02X%02X.bin", hash.data[0], hash.data[1], hash.data[2], hash.data[3]); + } else { + GetSerialNumber(sn, info); + std::snprintf(dst, dst_size, "automatic_backups/%s_PRODINFO_%02X%02X%02X%02X.bin", sn, hash.data[0], hash.data[1], hash.data[2], hash.data[3]); + } + } else { + std::snprintf(dst, dst_size, "automatic_backups/INVALID_PRODINFO_%02X%02X%02X%02X.bin", hash.data[0], hash.data[1], hash.data[2], hash.data[3]); + } + } + } + + void SafeRead(ams::fs::fsa::IFile *file, s64 offset, void *dst, size_t size) { + size_t read_size = 0; + R_ABORT_UNLESS(file->Read(std::addressof(read_size), offset, dst, size)); + AMS_ABORT_UNLESS(read_size == size); + } + + alignas(os::MemoryPageSize) CalibrationInfo g_temp_calibration_info = {}; + + void SaveProdInfoBackup(std::optional *dst, const CalibrationInfo &info) { + char backup_fn[0x100]; + GetBackupFileName(backup_fn, sizeof(backup_fn), info); + + /* Create the file, in case it does not exist. */ + mitm::fs::CreateAtmosphereSdFile(backup_fn, sizeof(CalibrationInfo), ams::fs::CreateOption_None); + + /* Open the file. */ + FsFile libnx_file; + R_ABORT_UNLESS(mitm::fs::OpenAtmosphereSdFile(std::addressof(libnx_file), backup_fn, ams::fs::OpenMode_ReadWrite)); + + /* Create our accessor. */ + std::unique_ptr file = std::make_unique(libnx_file); + AMS_ABORT_UNLESS(file != nullptr); + + /* Check if we're valid already. */ + bool valid = false; + s64 size; + R_ABORT_UNLESS(file->GetSize(std::addressof(size))); + if (size == sizeof(CalibrationInfo)) { + SafeRead(file.get(), 0, std::addressof(g_temp_calibration_info), sizeof(g_temp_calibration_info)); + ON_SCOPE_EXIT { std::memset(std::addressof(g_temp_calibration_info), 0, sizeof(g_temp_calibration_info)); }; + + if (std::memcmp(std::addressof(info), std::addressof(g_temp_calibration_info), sizeof(CalibrationInfo)) == 0) { + valid = true; + } + } + + /* If we're not valid, we need to save. */ + if (!valid) { + R_ABORT_UNLESS(file->Write(0, std::addressof(info), sizeof(info), ams::fs::WriteOption::Flush)); + } + + /* Save our storage to output. */ + if (dst != nullptr) { + dst->emplace(std::move(file)); + } + } + + void GetRandomEntropy(Sha256Hash *dst) { + AMS_ASSERT(dst != nullptr); + + u64 data_buffer[3] = {}; + ON_SCOPE_EXIT { crypto::ClearMemory(data_buffer, sizeof(data_buffer)); }; + + data_buffer[0] = os::GetSystemTick().GetInt64Value(); + R_ABORT_UNLESS(svc::GetInfo(data_buffer + 1, svc::InfoType_AliasRegionAddress, svc::PseudoHandle::CurrentProcess, 0)); + if (hos::GetVersion() >= hos::Version_2_0_0) { + R_ABORT_UNLESS(svc::GetInfo(data_buffer + 2, svc::InfoType_RandomEntropy, svc::InvalidHandle, (data_buffer[0] ^ (data_buffer[1] >> 24)) & 3)); + } else { + data_buffer[2] = os::GetSystemTick().GetInt64Value(); + } + + return crypto::GenerateSha256Hash(dst, sizeof(*dst), data_buffer, sizeof(data_buffer)); + } + + void FillWithGarbage(void *dst, size_t dst_size) { + /* Get random entropy. */ + Sha256Hash entropy; + ON_SCOPE_EXIT { crypto::ClearMemory(std::addressof(entropy), sizeof(entropy)); }; + GetRandomEntropy(std::addressof(entropy)); + + /* Clear dst. */ + std::memset(dst, 0xCC, dst_size); + + /* Encrypt dst. */ + static_assert(sizeof(entropy) == crypto::Aes128CtrEncryptor::KeySize + crypto::Aes128CtrEncryptor::IvSize); + crypto::EncryptAes128Ctr(dst, dst_size, entropy.data, crypto::Aes128CtrEncryptor::KeySize, entropy.data + crypto::Aes128CtrEncryptor::KeySize, crypto::Aes128CtrEncryptor::IvSize, dst, dst_size); + } + + alignas(os::MemoryPageSize) CalibrationInfo g_calibration_info = {}; + alignas(os::MemoryPageSize) CalibrationInfo g_blank_calibration_info = {}; + alignas(os::MemoryPageSize) SecureCalibrationInfoBackup g_secure_calibration_info_backup = {}; + + std::optional g_prodinfo_backup_file; + std::optional g_blank_prodinfo_storage; + std::optional g_fake_secure_backup_storage; + + bool g_allow_writes = false; + bool g_has_secure_backup = false; + + os::Mutex g_prodinfo_management_lock(false); + + } + + void InitializeProdInfoManagement() { + std::scoped_lock lk(g_prodinfo_management_lock); + + /* First, get our options. */ + const bool should_blank = exosphere::ShouldBlankProdInfo(); + bool allow_writes = exosphere::ShouldAllowWritesToProdInfo(); + + /* Next, read our prodinfo. */ + ReadStorageCalibrationBinary(std::addressof(g_calibration_info)); + + /* Next, check if we have a secure backup. */ + bool has_secure_backup = ReadStorageSecureCalibrationBinaryBackup(std::addressof(g_secure_calibration_info_backup)); + + /* Only allow writes if we have a secure backup. */ + if (allow_writes && !has_secure_backup) { + /* If we can make a secure backup, great. */ + if (IsValidForSecureBackup(g_calibration_info)) { + g_secure_calibration_info_backup.info = g_calibration_info; + WriteStorageSecureCalibrationBinaryBackup(std::addressof(g_secure_calibration_info_backup)); + g_secure_calibration_info_backup.info = g_calibration_info; + has_secure_backup = true; + } else { + /* Don't allow writes if we can't make a secure backup. */ + allow_writes = false; + } + } + + /* Ensure our preconditions are met. */ + AMS_ABORT_UNLESS(!allow_writes || has_secure_backup); + + /* Set globals. */ + g_allow_writes = allow_writes; + g_has_secure_backup = has_secure_backup; + + /* If we should blank, do so. */ + if (should_blank) { + g_blank_calibration_info = g_calibration_info; + Blank(g_blank_calibration_info); + g_blank_prodinfo_storage.emplace(std::addressof(g_blank_calibration_info), sizeof(g_blank_calibration_info)); + } + + /* Ensure that we have a blank file only if we need one. */ + AMS_ABORT_UNLESS(should_blank == static_cast(g_blank_prodinfo_storage)); + } + + void SaveProdInfoBackupsAndWipeMemory(char *out_name, size_t out_name_size) { + std::scoped_lock lk(g_prodinfo_management_lock); + + ON_SCOPE_EXIT { + FillWithGarbage(std::addressof(g_calibration_info), sizeof(g_calibration_info)); + FillWithGarbage(std::addressof(g_secure_calibration_info_backup), sizeof(g_secure_calibration_info_backup)); + }; + + /* Save our backup. We always prefer to save a secure copy of data over a non-secure one. */ + if (g_has_secure_backup) { + GetSerialNumber(out_name, g_secure_calibration_info_backup.info); + SaveProdInfoBackup(std::addressof(g_prodinfo_backup_file), g_secure_calibration_info_backup.info); + } else { + if (IsValid(g_calibration_info) && !IsBlank(g_calibration_info)) { + GetSerialNumber(out_name, g_calibration_info); + } else { + Sha256Hash hash; + ON_SCOPE_EXIT { crypto::ClearMemory(std::addressof(hash), sizeof(hash)); }; + crypto::GenerateSha256Hash(std::addressof(hash), sizeof(hash), std::addressof(g_calibration_info), sizeof(g_calibration_info)); + + std::snprintf(out_name, out_name_size, "%02X%02X%02X%02X", hash.data[0], hash.data[1], hash.data[2], hash.data[3]); + } + SaveProdInfoBackup(std::addressof(g_prodinfo_backup_file), g_calibration_info); + } + + /* Ensure we made our backup. */ + AMS_ABORT_UNLESS(g_prodinfo_backup_file); + + /* Setup our memory storage. */ + g_fake_secure_backup_storage.emplace(std::addressof(g_secure_calibration_info_backup), sizeof(g_secure_calibration_info_backup)); + + /* Ensure that we have a fake storage. */ + AMS_ABORT_UNLESS(static_cast(g_fake_secure_backup_storage)); + } + + bool ShouldReadBlankCalibrationBinary() { + std::scoped_lock lk(g_prodinfo_management_lock); + return static_cast(g_blank_prodinfo_storage); + } + + bool IsWriteToCalibrationBinaryAllowed() { + std::scoped_lock lk(g_prodinfo_management_lock); + return g_allow_writes; + } + + void ReadFromBlankCalibrationBinary(s64 offset, void *dst, size_t size) { + AMS_ABORT_UNLESS(ShouldReadBlankCalibrationBinary()); + + std::scoped_lock lk(g_prodinfo_management_lock); + R_ABORT_UNLESS(g_blank_prodinfo_storage->Read(offset, dst, size)); + } + + void WriteToBlankCalibrationBinary(s64 offset, const void *src, size_t size) { + AMS_ABORT_UNLESS(ShouldReadBlankCalibrationBinary()); + + std::scoped_lock lk(g_prodinfo_management_lock); + R_ABORT_UNLESS(g_blank_prodinfo_storage->Write(offset, src, size)); + } + + void ReadFromFakeSecureBackupStorage(s64 offset, void *dst, size_t size) { + AMS_ABORT_UNLESS(IsWriteToCalibrationBinaryAllowed()); + + std::scoped_lock lk(g_prodinfo_management_lock); + R_ABORT_UNLESS(g_fake_secure_backup_storage->Read(offset, dst, size)); + } + + void WriteToFakeSecureBackupStorage(s64 offset, const void *src, size_t size) { + AMS_ABORT_UNLESS(IsWriteToCalibrationBinaryAllowed()); + + std::scoped_lock lk(g_prodinfo_management_lock); + R_ABORT_UNLESS(g_fake_secure_backup_storage->Write(offset, src, size)); + } + +} diff --git a/stratosphere/ams_mitm/source/amsmitm_prodinfo_utils.hpp b/stratosphere/ams_mitm/source/amsmitm_prodinfo_utils.hpp new file mode 100644 index 000000000..eae748272 --- /dev/null +++ b/stratosphere/ams_mitm/source/amsmitm_prodinfo_utils.hpp @@ -0,0 +1,39 @@ +/* + * 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 . + */ +#pragma once +#include + +namespace ams::mitm { + + constexpr inline size_t CalibrationBinarySize = 0x8000; + + constexpr inline s64 SecureCalibrationInfoBackupOffset = 3_MB; + constexpr inline size_t SecureCalibrationBinaryBackupSize = 0xC000; + + void InitializeProdInfoManagement(); + + void SaveProdInfoBackupsAndWipeMemory(char *out_name, size_t out_name_size); + + bool ShouldReadBlankCalibrationBinary(); + bool IsWriteToCalibrationBinaryAllowed(); + + void ReadFromBlankCalibrationBinary(s64 offset, void *dst, size_t size); + void WriteToBlankCalibrationBinary(s64 offset, const void *src, size_t size); + + void ReadFromFakeSecureBackupStorage(s64 offset, void *dst, size_t size); + void WriteToFakeSecureBackupStorage(s64 offset, const void *src, size_t size); + +} diff --git a/stratosphere/ams_mitm/source/bpc_mitm/bpc_ams_module.cpp b/stratosphere/ams_mitm/source/bpc_mitm/bpc_ams_module.cpp new file mode 100644 index 000000000..9eb29bab5 --- /dev/null +++ b/stratosphere/ams_mitm/source/bpc_mitm/bpc_ams_module.cpp @@ -0,0 +1,46 @@ +/* + * 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 . + */ +#include "../amsmitm_initialization.hpp" +#include "bpc_ams_module.hpp" +#include "bpc_ams_service.hpp" + +namespace ams::mitm::bpc_ams { + + namespace { + + constexpr sm::ServiceName AtmosphereServiceName = sm::ServiceName::Encode("bpc:ams"); + constexpr size_t AtmosphereMaxSessions = 4; + + constexpr size_t MaxServers = 1; + constexpr size_t MaxSessions = AtmosphereMaxSessions; + using ServerOptions = sf::hipc::DefaultServerManagerOptions; + sf::hipc::ServerManager g_server_manager; + + } + + void MitmModule::ThreadFunction(void *arg) { + /* Create bpc:ams. */ + { + Handle bpcams_h; + R_ABORT_UNLESS(svcManageNamedPort(&bpcams_h, AtmosphereServiceName.name, AtmosphereMaxSessions)); + g_server_manager.RegisterServer(bpcams_h); + } + + /* Loop forever, servicing our services. */ + g_server_manager.LoopProcess(); + } + +} diff --git a/stratosphere/ams_mitm/source/bpc_mitm/bpc_ams_module.hpp b/stratosphere/ams_mitm/source/bpc_mitm/bpc_ams_module.hpp new file mode 100644 index 000000000..259d5b046 --- /dev/null +++ b/stratosphere/ams_mitm/source/bpc_mitm/bpc_ams_module.hpp @@ -0,0 +1,24 @@ +/* + * 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 . + */ +#pragma once +#include +#include "../amsmitm_module.hpp" + +namespace ams::mitm::bpc_ams { + + DEFINE_MITM_MODULE_CLASS(0x8000, AMS_GET_SYSTEM_THREAD_PRIORITY(bpc, IpcServer)); + +} diff --git a/stratosphere/ams_mitm/source/bpc_mitm/bpc_ams_power_utils.cpp b/stratosphere/ams_mitm/source/bpc_mitm/bpc_ams_power_utils.cpp index 4802ab5b7..5ca66b265 100644 --- a/stratosphere/ams_mitm/source/bpc_mitm/bpc_ams_power_utils.cpp +++ b/stratosphere/ams_mitm/source/bpc_mitm/bpc_ams_power_utils.cpp @@ -97,6 +97,21 @@ namespace ams::mitm::bpc { DoRebootToPayload(ctx); } + void SetInitialRebootPayload(const void *payload, size_t payload_size) { + /* Clear payload buffer */ + std::memset(g_reboot_payload, 0xCC, sizeof(g_reboot_payload)); + + /* Ensure valid. */ + AMS_ABORT_UNLESS(payload != nullptr && payload_size <= sizeof(g_reboot_payload)); + + /* Copy in payload. */ + std::memcpy(g_reboot_payload, payload, payload_size); + + /* NOTE: Preferred reboot type will be parsed from settings later on. */ + g_reboot_type = RebootType::ToPayload; + + } + Result LoadRebootPayload() { /* Clear payload buffer */ std::memset(g_reboot_payload, 0xCC, sizeof(g_reboot_payload)); diff --git a/stratosphere/ams_mitm/source/bpc_mitm/bpc_ams_power_utils.hpp b/stratosphere/ams_mitm/source/bpc_mitm/bpc_ams_power_utils.hpp index 58847bdf5..0a2ae78bb 100644 --- a/stratosphere/ams_mitm/source/bpc_mitm/bpc_ams_power_utils.hpp +++ b/stratosphere/ams_mitm/source/bpc_mitm/bpc_ams_power_utils.hpp @@ -24,6 +24,7 @@ namespace ams::mitm::bpc { void ShutdownSystem(); /* Atmosphere power utilities. */ + void SetInitialRebootPayload(const void *payload, size_t payload_size); Result LoadRebootPayload(); Result DetectPreferredRebootFunctionality(); void RebootForFatalError(const ams::FatalErrorContext *ctx); diff --git a/stratosphere/ams_mitm/source/bpc_mitm/bpc_ams_service.cpp b/stratosphere/ams_mitm/source/bpc_mitm/bpc_ams_service.cpp index b1a656da8..bd4f63313 100644 --- a/stratosphere/ams_mitm/source/bpc_mitm/bpc_ams_service.cpp +++ b/stratosphere/ams_mitm/source/bpc_mitm/bpc_ams_service.cpp @@ -13,13 +13,32 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ +#include "../amsmitm_initialization.hpp" #include "bpc_ams_service.hpp" #include "bpc_ams_power_utils.hpp" namespace ams::mitm::bpc { + namespace { + + bool g_set_initial_payload = false; + + } + void AtmosphereService::RebootToFatalError(const ams::FatalErrorContext &ctx) { bpc::RebootForFatalError(&ctx); } + void AtmosphereService::SetInitialRebootPayload(const ams::sf::InBuffer &payload) { + if (!g_set_initial_payload) { + g_set_initial_payload = true; + + /* Set the initial reboot payload. */ + bpc::SetInitialRebootPayload(payload.GetPointer(), payload.GetSize()); + + /* Start the initialization process. */ + ::ams::mitm::StartInitialize(); + } + } + } diff --git a/stratosphere/ams_mitm/source/bpc_mitm/bpc_ams_service.hpp b/stratosphere/ams_mitm/source/bpc_mitm/bpc_ams_service.hpp index 775f2b177..794235691 100644 --- a/stratosphere/ams_mitm/source/bpc_mitm/bpc_ams_service.hpp +++ b/stratosphere/ams_mitm/source/bpc_mitm/bpc_ams_service.hpp @@ -22,13 +22,16 @@ namespace ams::mitm::bpc { class AtmosphereService final : public sf::IServiceObject { private: enum class CommandId { - RebootToFatalError = 65000, + RebootToFatalError = 65000, + SetInitialRebootPayload = 65001, }; private: void RebootToFatalError(const ams::FatalErrorContext &ctx); + void SetInitialRebootPayload(const ams::sf::InBuffer &payload); public: DEFINE_SERVICE_DISPATCH_TABLE { MAKE_SERVICE_COMMAND_META(RebootToFatalError), + MAKE_SERVICE_COMMAND_META(SetInitialRebootPayload), }; }; diff --git a/stratosphere/ams_mitm/source/bpc_mitm/bpcmitm_module.cpp b/stratosphere/ams_mitm/source/bpc_mitm/bpcmitm_module.cpp index a2ac113b0..f3d757bd4 100644 --- a/stratosphere/ams_mitm/source/bpc_mitm/bpcmitm_module.cpp +++ b/stratosphere/ams_mitm/source/bpc_mitm/bpcmitm_module.cpp @@ -16,8 +16,6 @@ #include "../amsmitm_initialization.hpp" #include "bpcmitm_module.hpp" #include "bpc_mitm_service.hpp" -#include "bpc_ams_service.hpp" -#include "bpc_ams_power_utils.hpp" namespace ams::mitm::bpc { @@ -27,11 +25,8 @@ namespace ams::mitm::bpc { constexpr sm::ServiceName DeprecatedMitmServiceName = sm::ServiceName::Encode("bpc:c"); constexpr size_t MitmServiceMaxSessions = 13; - constexpr sm::ServiceName AtmosphereServiceName = sm::ServiceName::Encode("bpc:ams"); - constexpr size_t AtmosphereMaxSessions = 3; - - constexpr size_t MaxServers = 2; - constexpr size_t MaxSessions = MitmServiceMaxSessions + AtmosphereMaxSessions; + constexpr size_t MaxServers = 1; + constexpr size_t MaxSessions = MitmServiceMaxSessions; using ServerOptions = sf::hipc::DefaultServerManagerOptions; sf::hipc::ServerManager g_server_manager; @@ -41,13 +36,6 @@ namespace ams::mitm::bpc { /* Wait until initialization is complete. */ mitm::WaitInitialized(); - /* Create bpc:ams. */ - { - Handle bpcams_h; - R_ABORT_UNLESS(svcManageNamedPort(&bpcams_h, AtmosphereServiceName.name, AtmosphereMaxSessions)); - g_server_manager.RegisterServer(bpcams_h); - } - /* Create bpc mitm. */ const sm::ServiceName service_name = (hos::GetVersion() >= hos::Version_2_0_0) ? MitmServiceName : DeprecatedMitmServiceName; R_ABORT_UNLESS(g_server_manager.RegisterMitmServer(service_name)); diff --git a/stratosphere/ams_mitm/source/fs_mitm/fs_mitm_service.cpp b/stratosphere/ams_mitm/source/fs_mitm/fs_mitm_service.cpp index 8c13c3f70..380f12748 100644 --- a/stratosphere/ams_mitm/source/fs_mitm/fs_mitm_service.cpp +++ b/stratosphere/ams_mitm/source/fs_mitm/fs_mitm_service.cpp @@ -14,9 +14,11 @@ * along with this program. If not, see . */ #include "../amsmitm_fs_utils.hpp" +#include "../amsmitm_initialization.hpp" #include "fs_shim.h" #include "fs_mitm_service.hpp" #include "fsmitm_boot0storage.hpp" +#include "fsmitm_calibration_binary_storage.hpp" #include "fsmitm_layered_romfs_storage.hpp" #include "fsmitm_save_utils.hpp" #include "fsmitm_readonly_layered_filesystem.hpp" @@ -252,7 +254,6 @@ namespace ams::mitm::fs { const bool is_sysmodule = ncm::IsSystemProgramId(this->client_info.program_id); const bool is_hbl = this->client_info.override_status.IsHbl(); const bool can_write_bis = is_sysmodule || (is_hbl && GetSettingsItemBooleanValue("atmosphere", "enable_hbl_bis_write")); - const bool can_read_cal = is_sysmodule || (is_hbl && GetSettingsItemBooleanValue("atmosphere", "enable_hbl_cal_read")); /* Allow HBL to write to boot1 (safe firm) + package2. */ /* This is needed to not break compatibility with ChoiDujourNX, which does not check for write access before beginning an update. */ @@ -265,15 +266,7 @@ namespace ams::mitm::fs { if (bis_partition_id == FsBisPartitionId_BootPartition1Root) { out.SetValue(std::make_shared(new Boot0Storage(bis_storage, this->client_info)), target_object_id); } else if (bis_partition_id == FsBisPartitionId_CalibrationBinary) { - /* PRODINFO should *never* be writable. */ - /* If we have permissions, create a read only storage. */ - if (can_read_cal) { - out.SetValue(std::make_shared(new ReadOnlyStorageAdapter(new RemoteStorage(bis_storage))), target_object_id); - } else { - /* If we can't read cal, return permission denied. */ - fsStorageClose(&bis_storage); - return fs::ResultPermissionDenied(); - } + out.SetValue(std::make_shared(new CalibrationBinaryStorage(bis_storage, this->client_info)), target_object_id); } else { if (can_write_bis || can_write_bis_for_choi_support) { /* We can write, so create a writable storage. */ diff --git a/stratosphere/ams_mitm/source/fs_mitm/fs_mitm_service.hpp b/stratosphere/ams_mitm/source/fs_mitm/fs_mitm_service.hpp index 68749facb..6ac10a225 100644 --- a/stratosphere/ams_mitm/source/fs_mitm/fs_mitm_service.hpp +++ b/stratosphere/ams_mitm/source/fs_mitm/fs_mitm_service.hpp @@ -54,6 +54,11 @@ namespace ams::mitm::fs { return true; } + /* We want to mitm settings, to intercept CAL0. */ + if (program_id == ncm::SystemProgramId::Settings) { + return true; + } + /* We want to mitm sdb, to support sd-romfs redirection of common system archives (like system font, etc). */ if (program_id == ncm::SystemProgramId::Sdb) { return true; diff --git a/stratosphere/ams_mitm/source/fs_mitm/fsmitm_calibration_binary_storage.cpp b/stratosphere/ams_mitm/source/fs_mitm/fsmitm_calibration_binary_storage.cpp new file mode 100644 index 000000000..0f1bee112 --- /dev/null +++ b/stratosphere/ams_mitm/source/fs_mitm/fsmitm_calibration_binary_storage.cpp @@ -0,0 +1,135 @@ +/* + * 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 . + */ +#include +#include "fsmitm_calibration_binary_storage.hpp" + +namespace ams::mitm::fs { + + using namespace ams::fs; + + namespace { + + os::Mutex g_cal0_access_mutex(false); + + } + Result CalibrationBinaryStorage::Read(s64 offset, void *_buffer, size_t size) { + /* Acquire exclusive calibration binary access. */ + std::scoped_lock lk(g_cal0_access_mutex); + + /* Get u8 buffer. */ + u8 *buffer = static_cast(_buffer); + + /* Succeed on zero-size. */ + R_SUCCEED_IF(size == 0); + + /* Handle the blank region. */ + if (this->read_blank) { + if (BlankStartOffset <= offset && offset < BlankEndOffset) { + const size_t blank_size = std::min(size, static_cast(BlankEndOffset - offset)); + mitm::ReadFromBlankCalibrationBinary(offset, buffer, blank_size); + size -= blank_size; + buffer += blank_size; + offset += blank_size; + } + } + + /* Succeed if we're done. */ + R_SUCCEED_IF(size == 0); + + /* Handle any in-between data. */ + if (BlankEndOffset <= offset && offset < FakeSecureStartOffset) { + const size_t mid_size = std::min(size, static_cast(FakeSecureStartOffset - offset)); + R_TRY(Base::Read(offset, buffer, mid_size)); + size -= mid_size; + buffer += mid_size; + offset += mid_size; + } + + /* Succeed if we're done. */ + R_SUCCEED_IF(size == 0); + + /* Handle the secure region. */ + if (FakeSecureStartOffset <= offset && offset < FakeSecureEndOffset) { + const size_t fake_size = std::min(size, static_cast(FakeSecureEndOffset - offset)); + mitm::ReadFromFakeSecureBackupStorage(offset, buffer, fake_size); + size -= fake_size; + buffer += fake_size; + offset += fake_size; + } + + /* Succeed if we're done. */ + R_SUCCEED_IF(size == 0); + + /* Handle any remaining data. */ + return Base::Read(offset, buffer, size); + } + + Result CalibrationBinaryStorage::Write(s64 offset, const void *_buffer, size_t size) { + /* Acquire exclusive calibration binary access. */ + std::scoped_lock lk(g_cal0_access_mutex); + + /* Get const u8 buffer. */ + const u8 *buffer = static_cast(_buffer); + + /* Succeed on zero-size. */ + R_SUCCEED_IF(size == 0); + + /* Only allow writes if we should. */ + R_UNLESS(this->allow_writes, fs::ResultUnsupportedOperation()); + + /* Handle the blank region. */ + if (this->read_blank) { + if (BlankStartOffset <= offset && offset < BlankEndOffset) { + const size_t blank_size = std::min(size, static_cast(BlankEndOffset - offset)); + mitm::WriteToBlankCalibrationBinary(offset, buffer, blank_size); + size -= blank_size; + buffer += blank_size; + offset += blank_size; + } + } + + /* Succeed if we're done. */ + R_SUCCEED_IF(size == 0); + + /* Handle any in-between data. */ + if (BlankEndOffset <= offset && offset < FakeSecureStartOffset) { + const size_t mid_size = std::min(size, static_cast(FakeSecureStartOffset - offset)); + R_TRY(Base::Write(offset, buffer, mid_size)); + size -= mid_size; + buffer += mid_size; + offset += mid_size; + } + + /* Succeed if we're done. */ + R_SUCCEED_IF(size == 0); + + /* Handle the secure region. */ + if (FakeSecureStartOffset <= offset && offset < FakeSecureEndOffset) { + const size_t fake_size = std::min(size, static_cast(FakeSecureEndOffset - offset)); + mitm::WriteToFakeSecureBackupStorage(offset, buffer, fake_size); + size -= fake_size; + buffer += fake_size; + offset += fake_size; + } + + /* Succeed if we're done. */ + R_SUCCEED_IF(size == 0); + + /* Handle any remaining data. */ + return Base::Write(offset, buffer, size); + } + +} diff --git a/stratosphere/ams_mitm/source/fs_mitm/fsmitm_calibration_binary_storage.hpp b/stratosphere/ams_mitm/source/fs_mitm/fsmitm_calibration_binary_storage.hpp new file mode 100644 index 000000000..215e159a7 --- /dev/null +++ b/stratosphere/ams_mitm/source/fs_mitm/fsmitm_calibration_binary_storage.hpp @@ -0,0 +1,53 @@ +/* + * 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 . + */ + +#pragma once +#include +#include "fsmitm_boot0storage.hpp" +#include "../amsmitm_prodinfo_utils.hpp" + +namespace ams::mitm::fs { + + /* Represents a protected calibration binary partition. */ + class CalibrationBinaryStorage : public SectoredStorageAdapter { + public: + using Base = SectoredStorageAdapter; + + static constexpr s64 BlankStartOffset = 0x0; + static constexpr s64 BlankSize = static_cast(CalibrationBinarySize); + static constexpr s64 BlankEndOffset = BlankStartOffset + BlankSize; + + static constexpr s64 FakeSecureStartOffset = SecureCalibrationInfoBackupOffset; + static constexpr s64 FakeSecureSize = static_cast(SecureCalibrationBinaryBackupSize); + static constexpr s64 FakeSecureEndOffset = FakeSecureStartOffset + FakeSecureSize; + private: + sm::MitmProcessInfo client_info; + bool read_blank; + bool allow_writes; + public: + CalibrationBinaryStorage(FsStorage &s, const sm::MitmProcessInfo &c) + : Base(s), client_info(c), + read_blank(mitm::ShouldReadBlankCalibrationBinary()), + allow_writes(mitm::IsWriteToCalibrationBinaryAllowed()) + { + /* ... */ + } + public: + virtual Result Read(s64 offset, void *_buffer, size_t size) override; + virtual Result Write(s64 offset, const void *_buffer, size_t size) override; + }; + +} diff --git a/stratosphere/ams_mitm/source/set_mitm/settings_sd_kvs.cpp b/stratosphere/ams_mitm/source/set_mitm/settings_sd_kvs.cpp index 7e211a945..d52e582d5 100644 --- a/stratosphere/ams_mitm/source/set_mitm/settings_sd_kvs.cpp +++ b/stratosphere/ams_mitm/source/set_mitm/settings_sd_kvs.cpp @@ -328,10 +328,6 @@ namespace ams::settings::fwdbg { /* This is probably undesirable for normal usage. */ R_ABORT_UNLESS(ParseSettingsItemValue("atmosphere", "enable_hbl_bis_write", "u8!0x0")); - /* Enable HBL to read the CAL0 partition. */ - /* This is probably undesirable for normal usage. */ - R_ABORT_UNLESS(ParseSettingsItemValue("atmosphere", "enable_hbl_cal_read", "u8!0x0")); - /* Controls whether dmnt cheats should be toggled on or off by */ /* default. 1 = toggled on by default, 0 = toggled off by default. */ R_ABORT_UNLESS(ParseSettingsItemValue("atmosphere", "dmnt_cheats_enabled_by_default", "u8!0x1")); diff --git a/stratosphere/boot/source/boot_main.cpp b/stratosphere/boot/source/boot_main.cpp index d9229c080..5a3713262 100644 --- a/stratosphere/boot/source/boot_main.cpp +++ b/stratosphere/boot/source/boot_main.cpp @@ -111,6 +111,12 @@ int main(int argc, char **argv) os::SetThreadNamePointer(os::GetCurrentThread(), AMS_GET_SYSTEM_THREAD_NAME(boot, Main)); AMS_ASSERT(os::GetThreadPriority(os::GetCurrentThread()) == AMS_GET_SYSTEM_THREAD_PRIORITY(boot, Main)); + /* Perform atmosphere-specific init. */ + ams::InitializeForBoot(); + + /* Set the reboot payload with ams.mitm. */ + boot::SetInitialRebootPayload(); + /* Change voltage from 3.3v to 1.8v for select devices. */ boot::ChangeGpioVoltageTo1_8v(); diff --git a/stratosphere/boot/source/boot_power_utils.cpp b/stratosphere/boot/source/boot_power_utils.cpp index 357579d2b..9c140421b 100644 --- a/stratosphere/boot/source/boot_power_utils.cpp +++ b/stratosphere/boot/source/boot_power_utils.cpp @@ -67,6 +67,10 @@ namespace ams::boot { DoRebootToPayload(nullptr); } + void SetInitialRebootPayload() { + ::ams::SetInitialRebootPayload(fusee_primary_bin, fusee_primary_bin_size); + } + void RebootForFatalError(ams::FatalErrorContext *ctx) { DoRebootToPayload(ctx); } diff --git a/stratosphere/boot/source/boot_power_utils.hpp b/stratosphere/boot/source/boot_power_utils.hpp index dfe2d339c..db8b49473 100644 --- a/stratosphere/boot/source/boot_power_utils.hpp +++ b/stratosphere/boot/source/boot_power_utils.hpp @@ -23,6 +23,7 @@ namespace ams::boot { void ShutdownSystem(); /* Atmosphere power utilities. */ + void SetInitialRebootPayload(); void RebootForFatalError(ams::FatalErrorContext *ctx); } diff --git a/stratosphere/spl/source/spl_main.cpp b/stratosphere/spl/source/spl_main.cpp index 3b26e7893..fe0df90b2 100644 --- a/stratosphere/spl/source/spl_main.cpp +++ b/stratosphere/spl/source/spl_main.cpp @@ -104,7 +104,7 @@ namespace { constexpr size_t GeneralMaxSessions = 7; constexpr sm::ServiceName CryptoServiceName = sm::ServiceName::Encode("spl:mig"); - constexpr size_t CryptoMaxSessions = 6; + constexpr size_t CryptoMaxSessions = 7; constexpr sm::ServiceName SslServiceName = sm::ServiceName::Encode("spl:ssl"); constexpr size_t SslMaxSessions = 2;