From 7581306109775150bde798e93e39029ec85210e8 Mon Sep 17 00:00:00 2001 From: Michael Scire Date: Thu, 9 May 2019 01:17:56 -0700 Subject: [PATCH] boot: implement CheckAndRepairBootImages --- stratosphere/boot/Makefile | 2 +- stratosphere/boot/source/boot_main.cpp | 5 +- .../boot/source/boot_repair_boot_images.cpp | 31 + .../boot/source/updater/updater_api.cpp | 720 ++++++++++++++++++ .../boot/source/updater/updater_api.hpp | 62 ++ .../source/updater/updater_bis_management.cpp | 185 +++++ .../source/updater/updater_bis_management.hpp | 242 ++++++ .../boot/source/updater/updater_bis_save.cpp | 69 ++ .../boot/source/updater/updater_bis_save.hpp | 42 + .../boot/source/updater/updater_files.cpp | 72 ++ .../boot/source/updater/updater_paths.cpp | 110 +++ .../boot/source/updater/updater_types.hpp | 42 + stratosphere/libstratosphere | 2 +- 13 files changed, 1581 insertions(+), 3 deletions(-) create mode 100644 stratosphere/boot/source/boot_repair_boot_images.cpp create mode 100644 stratosphere/boot/source/updater/updater_api.cpp create mode 100644 stratosphere/boot/source/updater/updater_api.hpp create mode 100644 stratosphere/boot/source/updater/updater_bis_management.cpp create mode 100644 stratosphere/boot/source/updater/updater_bis_management.hpp create mode 100644 stratosphere/boot/source/updater/updater_bis_save.cpp create mode 100644 stratosphere/boot/source/updater/updater_bis_save.hpp create mode 100644 stratosphere/boot/source/updater/updater_files.cpp create mode 100644 stratosphere/boot/source/updater/updater_paths.cpp create mode 100644 stratosphere/boot/source/updater/updater_types.hpp diff --git a/stratosphere/boot/Makefile b/stratosphere/boot/Makefile index 7ab062393..1a8be006b 100644 --- a/stratosphere/boot/Makefile +++ b/stratosphere/boot/Makefile @@ -33,7 +33,7 @@ endef #--------------------------------------------------------------------------------- TARGET := $(notdir $(CURDIR)) BUILD := build -SOURCES := source source/i2c_driver +SOURCES := source source/i2c_driver source/updater DATA := data INCLUDES := include ../../common/include EXEFS_SRC := exefs_src diff --git a/stratosphere/boot/source/boot_main.cpp b/stratosphere/boot/source/boot_main.cpp index 60f4ee169..5f8bd8cdb 100644 --- a/stratosphere/boot/source/boot_main.cpp +++ b/stratosphere/boot/source/boot_main.cpp @@ -134,13 +134,16 @@ int main(int argc, char **argv) /* Configure the PMC wake pin settings. */ Boot::SetInitialWakePinConfiguration(); + /* Configure output clock. */ if (hw_type != HardwareType_Copper) { Boot::SetInitialClockConfiguration(); } + /* Set Fan enable config (Copper only). */ Boot::SetFanEnabled(); - /* TODO: CheckAndRepairBootImages(); */ + /* Repair boot partitions in NAND if needed. */ + Boot::CheckAndRepairBootImages(); /* Tell PM to start boot2. */ if (R_FAILED(pmshellNotifyBootFinished())) { diff --git a/stratosphere/boot/source/boot_repair_boot_images.cpp b/stratosphere/boot/source/boot_repair_boot_images.cpp new file mode 100644 index 000000000..1ca43ad63 --- /dev/null +++ b/stratosphere/boot/source/boot_repair_boot_images.cpp @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2018-2019 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 "boot_functions.hpp" +#include "updater/updater_api.hpp" + +static u8 __attribute__((__aligned__(0x1000))) g_boot_image_work_buffer[0x10000]; + +void Boot::CheckAndRepairBootImages() { + const BootImageUpdateType boot_image_update_type = Updater::GetBootImageUpdateType(Boot::GetHardwareType()); + + bool repaired_normal, repaired_safe; + Result rc = Updater::VerifyBootImagesAndRepairIfNeeded(&repaired_normal, &repaired_safe, g_boot_image_work_buffer, sizeof(g_boot_image_work_buffer), boot_image_update_type); + if (R_SUCCEEDED(rc) && repaired_normal) { + /* Nintendo only performs a reboot on successful normal repair. */ + Boot::RebootSystem(); + } +} diff --git a/stratosphere/boot/source/updater/updater_api.cpp b/stratosphere/boot/source/updater/updater_api.cpp new file mode 100644 index 000000000..8c464947d --- /dev/null +++ b/stratosphere/boot/source/updater/updater_api.cpp @@ -0,0 +1,720 @@ +/* + * Copyright (c) 2018-2019 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 + +#include "updater_api.hpp" +#include "updater_bis_save.hpp" + +Result Updater::ValidateWorkBuffer(const void *work_buffer, size_t work_buffer_size) { + if (work_buffer_size < BctSize + EksSize) { + return ResultUpdaterTooSmallWorkBuffer; + } + if (reinterpret_cast(work_buffer) & 0xFFF) { + return ResultUpdaterMisalignedWorkBuffer; + } + if (reinterpret_cast(work_buffer_size) & 0x1FF) { + return ResultUpdaterMisalignedWorkBuffer; + } + return ResultSuccess; +} + +BootImageUpdateType Updater::GetBootImageUpdateType(HardwareType hw_type) { + switch (hw_type) { + case HardwareType_Icosa: + case HardwareType_Copper: + return BootImageUpdateType_Erista; + case HardwareType_Hoag: + case HardwareType_Iowa: + return BootImageUpdateType_Mariko; + default: + std::abort(); + } +} + +bool Updater::HasEks(BootImageUpdateType boot_image_update_type) { + switch (boot_image_update_type) { + case BootImageUpdateType_Erista: + return true; + case BootImageUpdateType_Mariko: + return false; + default: + std::abort(); + } +} + +bool Updater::HasAutoRcmPreserve(BootImageUpdateType boot_image_update_type) { + switch (boot_image_update_type) { + case BootImageUpdateType_Erista: + return true; + case BootImageUpdateType_Mariko: + return false; + default: + std::abort(); + } +} + +u32 Updater::GetNcmTitleType(BootModeType mode) { + switch (mode) { + case BootModeType_Normal: + return NcmContentMetaType_BootImagePackage; + case BootModeType_Safe: + return NcmContentMetaType_BootImagePackageSafe; + default: + std::abort(); + } +} + +Result Updater::GetVerificationState(VerificationState *out, void *work_buffer, size_t work_buffer_size) { + Result rc; + + /* Always set output to true before doing anything else. */ + out->needs_verify_normal = true; + out->needs_verify_safe = true; + + /* Ensure work buffer is big enough for us to do what we want to do. */ + if (R_FAILED((rc = ValidateWorkBuffer(work_buffer, work_buffer_size)))) { + return rc; + } + + /* Initialize boot0 save accessor. */ + BisSave save; + if (R_FAILED((rc = save.Initialize(work_buffer, work_buffer_size)))) { + return rc; + } + ON_SCOPE_EXIT { save.Finalize(); }; + + /* Load save from NAND. */ + if (R_FAILED((rc = save.Load()))) { + return rc; + } + + /* Read data from save. */ + out->needs_verify_normal = save.GetNeedsVerification(BootModeType_Normal); + out->needs_verify_safe = save.GetNeedsVerification(BootModeType_Safe); + return ResultSuccess; +} + +Result Updater::VerifyBootImagesAndRepairIfNeeded(bool *out_repaired_normal, bool *out_repaired_safe, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type) { + Result rc; + + /* Always set output to false before doing anything else. */ + *out_repaired_normal = false; + *out_repaired_safe = false; + + /* Ensure work buffer is big enough for us to do what we want to do. */ + if (R_FAILED((rc = ValidateWorkBuffer(work_buffer, work_buffer_size)))) { + return rc; + } + + /* Get verification state from NAND. */ + VerificationState verification_state; + if (R_FAILED((rc = GetVerificationState(&verification_state, work_buffer, work_buffer_size)))) { + return rc; + } + + /* If we don't need to verify anything, we're done. */ + if (!verification_state.needs_verify_normal && !verification_state.needs_verify_safe) { + return ResultSuccess; + } + + /* Get a session to ncm. */ + DoWithSmSession([&]() { + if (R_FAILED(ncmInitialize())) { + std::abort(); + } + }); + ON_SCOPE_EXIT { ncmExit(); }; + + /* Verify normal, verify safe as needed. */ + if (verification_state.needs_verify_normal) { + rc = VerifyBootImagesAndRepairIfNeeded(out_repaired_normal, BootModeType_Normal, work_buffer, work_buffer_size, boot_image_update_type); + if (rc == ResultUpdaterBootImagePackageNotFound) { + /* Nintendo considers failure to locate bip a success. TODO: don't do that? */ + rc = ResultSuccess; + } + if (R_FAILED(rc)) { + return rc; + } + } + if (verification_state.needs_verify_safe) { + rc = VerifyBootImagesAndRepairIfNeeded(out_repaired_safe, BootModeType_Safe, work_buffer, work_buffer_size, boot_image_update_type); + if (rc == ResultUpdaterBootImagePackageNotFound) { + /* Nintendo considers failure to locate bip a success. TODO: don't do that? */ + rc = ResultSuccess; + } + if (R_FAILED(rc)) { + return rc; + } + } + + return ResultSuccess; +} + +Result Updater::VerifyBootImagesAndRepairIfNeeded(bool *out_repaired, BootModeType mode, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type) { + Result rc; + + /* Get system data id for boot images (819/81A/81B/81C). */ + u64 bip_data_id; + if (R_FAILED((rc = GetBootImagePackageDataId(&bip_data_id, mode, work_buffer, work_buffer_size)))) { + return rc; + } + + /* Verify the boot images in NAND. */ + if (R_FAILED((rc = VerifyBootImages(bip_data_id, mode, work_buffer, work_buffer_size, boot_image_update_type)))) { + /* If we failed for a reason other than repair needed, bail out. */ + if (rc != ResultUpdaterNeedsRepairBootImages) { + return rc; + } + /* Perform repair. */ + *out_repaired = true; + if (R_FAILED((rc = UpdateBootImages(bip_data_id, mode, work_buffer, work_buffer_size, boot_image_update_type)))) { + return rc; + } + } + + /* We've either just verified or just repaired. Either way, we don't need to verify any more. */ + return SetVerificationNeeded(mode, false, work_buffer, work_buffer_size); +} + +Result Updater::GetBootImagePackageDataId(u64 *out_data_id, BootModeType mode, void *work_buffer, size_t work_buffer_size) { + Result rc; + + /* Ensure we can read content metas. */ + constexpr size_t MaxContentMetas = 0x40; + if (work_buffer_size < sizeof(NcmMetaRecord) * MaxContentMetas) { + std::abort(); + } + + /* Open NAND System meta database, list contents. */ + NcmContentMetaDatabase meta_db; + if (R_FAILED((rc = ncmOpenContentMetaDatabase(FsStorageId_NandSystem, &meta_db)))) { + return rc; + } + ON_SCOPE_EXIT { serviceClose(&meta_db.s); }; + + NcmMetaRecord *records = reinterpret_cast(work_buffer); + + const u32 title_type = GetNcmTitleType(mode); + u32 written_entries; + u32 total_entries; + if (R_FAILED((rc = ncmContentMetaDatabaseList(&meta_db, title_type, 0, 0, UINT64_MAX, records, MaxContentMetas * sizeof(*records), &written_entries, &total_entries)))) { + return rc; + } + if (total_entries == 0) { + return ResultUpdaterBootImagePackageNotFound; + } + + if (total_entries != written_entries) { + std::abort(); + } + + /* Output is sorted, return the lowest valid exfat entry. */ + if (total_entries > 1) { + for (size_t i = 0; i < total_entries; i++) { + u8 attr; + if (R_FAILED((rc = ncmContentMetaDatabaseGetAttributes(&meta_db, &records[i], &attr)))) { + return rc; + } + + if (attr & NcmContentMetaAttribute_Exfat) { + *out_data_id = records[i].titleId; + return ResultSuccess; + } + } + } + + /* If there's only one entry or no exfat entries, return that entry. */ + *out_data_id = records[0].titleId; + return ResultSuccess; +} + +Result Updater::VerifyBootImages(u64 data_id, BootModeType mode, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type) { + switch (mode) { + case BootModeType_Normal: + return VerifyBootImagesNormal(data_id, work_buffer, work_buffer_size, boot_image_update_type); + case BootModeType_Safe: + return VerifyBootImagesSafe(data_id, work_buffer, work_buffer_size, boot_image_update_type); + default: + std::abort(); + } +} + +Result Updater::ValidateBctFileHash(Boot0Accessor &accessor, Boot0Partition which, const void *stored_hash, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type) { + Result rc; + + /* Ensure work buffer is big enough for us to do what we want to do. */ + if (R_FAILED((rc = ValidateWorkBuffer(work_buffer, work_buffer_size)))) { + return rc; + } + + void *bct = reinterpret_cast(reinterpret_cast(work_buffer) + 0); + void *work = reinterpret_cast(reinterpret_cast(work_buffer) + BctSize); + + size_t size; + if (R_FAILED((rc = ReadFile(&size, bct, BctSize, GetBctPath(boot_image_update_type))))) { + return rc; + } + if (HasEks(boot_image_update_type)) { + if (R_FAILED((rc = accessor.UpdateEks(bct, work)))) { + return rc; + } + } + if (HasAutoRcmPreserve(boot_image_update_type)) { + if (R_FAILED((rc = accessor.PreserveAutoRcm(bct, work, which)))) { + return rc; + } + } + + u8 file_hash[SHA256_HASH_SIZE]; + sha256CalculateHash(file_hash, bct, BctSize); + + if (std::memcmp(file_hash, stored_hash, SHA256_HASH_SIZE) == 0) { + return ResultSuccess; + } + + return ResultUpdaterNeedsRepairBootImages; +} + +Result Updater::VerifyBootImagesNormal(u64 data_id, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type) { + Result rc; + + /* Ensure work buffer is big enough for us to do what we want to do. */ + if (R_FAILED((rc = ValidateWorkBuffer(work_buffer, work_buffer_size)))) { + return rc; + } + + if (R_FAILED((rc = romfsMountFromDataArchive(data_id, FsStorageId_NandSystem, GetBootImagePackageMountPath())))) { + if (rc == ResultFsTargetNotFound) { + return ResultUpdaterBootImagePackageNotFound; + } + return rc; + } + ON_SCOPE_EXIT { if (R_FAILED(romfsUnmount(GetBootImagePackageMountPath()))) { std::abort(); } }; + + /* Read and validate hashes of boot images. */ + { + size_t size; + u8 nand_hash[SHA256_HASH_SIZE]; + u8 file_hash[SHA256_HASH_SIZE]; + + Boot0Accessor boot0_accessor; + if (R_FAILED((rc = boot0_accessor.Initialize()))) { + return rc; + } + ON_SCOPE_EXIT { boot0_accessor.Finalize(); }; + + /* Compare BCT hashes. */ + if (R_FAILED((rc = boot0_accessor.GetHash(nand_hash, BctSize, work_buffer, work_buffer_size, Boot0Partition::BctNormalMain)))) { + return rc; + } + if (R_FAILED((rc = ValidateBctFileHash(boot0_accessor, Boot0Partition::BctNormalMain, nand_hash, work_buffer, work_buffer_size, boot_image_update_type)))) { + return rc; + } + + /* Compare BCT Sub hashes. */ + if (R_FAILED((rc = boot0_accessor.GetHash(nand_hash, BctSize, work_buffer, work_buffer_size, Boot0Partition::BctNormalSub)))) { + return rc; + } + if (R_FAILED((rc = ValidateBctFileHash(boot0_accessor, Boot0Partition::BctNormalSub, nand_hash, work_buffer, work_buffer_size, boot_image_update_type)))) { + return rc; + } + + /* Compare Package1 Normal/Sub hashes. */ + if (R_FAILED((rc = GetFileHash(&size, file_hash, GetPackage1Path(boot_image_update_type), work_buffer, work_buffer_size)))) { + return rc; + } + if (R_FAILED((rc = boot0_accessor.GetHash(nand_hash, size, work_buffer, work_buffer_size, Boot0Partition::Package1NormalMain)))) { + return rc; + } + if (std::memcmp(file_hash, nand_hash, SHA256_HASH_SIZE) != 0) { + return ResultUpdaterNeedsRepairBootImages; + } + if (R_FAILED((rc = boot0_accessor.GetHash(nand_hash, size, work_buffer, work_buffer_size, Boot0Partition::Package1NormalSub)))) { + return rc; + } + if (std::memcmp(file_hash, nand_hash, SHA256_HASH_SIZE) != 0) { + return ResultUpdaterNeedsRepairBootImages; + } + + /* Compare Package2 Normal/Sub hashes. */ + if (R_FAILED((rc = GetFileHash(&size, file_hash, GetPackage2Path(boot_image_update_type), work_buffer, work_buffer_size)))) { + return rc; + } + if (R_FAILED((rc = GetPackage2Hash(nand_hash, size, work_buffer, work_buffer_size, Package2Type::NormalMain)))) { + return rc; + } + if (std::memcmp(file_hash, nand_hash, SHA256_HASH_SIZE) != 0) { + return ResultUpdaterNeedsRepairBootImages; + } + if (R_FAILED((rc = GetPackage2Hash(nand_hash, size, work_buffer, work_buffer_size, Package2Type::NormalSub)))) { + return rc; + } + if (std::memcmp(file_hash, nand_hash, SHA256_HASH_SIZE) != 0) { + return ResultUpdaterNeedsRepairBootImages; + } + } + + return ResultSuccess; +} + +Result Updater::VerifyBootImagesSafe(u64 data_id, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type) { + Result rc; + + /* Ensure work buffer is big enough for us to do what we want to do. */ + if (R_FAILED((rc = ValidateWorkBuffer(work_buffer, work_buffer_size)))) { + return rc; + } + + if (R_FAILED((rc = romfsMountFromDataArchive(data_id, FsStorageId_NandSystem, GetBootImagePackageMountPath())))) { + if (rc == ResultFsTargetNotFound) { + return ResultUpdaterBootImagePackageNotFound; + } + return rc; + } + ON_SCOPE_EXIT { if (R_FAILED(romfsUnmount(GetBootImagePackageMountPath()))) { std::abort(); } }; + + /* Read and validate hashes of boot images. */ + { + size_t size; + u8 nand_hash[SHA256_HASH_SIZE]; + u8 file_hash[SHA256_HASH_SIZE]; + + Boot0Accessor boot0_accessor; + if (R_FAILED((rc = boot0_accessor.Initialize()))) { + return rc; + } + ON_SCOPE_EXIT { boot0_accessor.Finalize(); }; + + Boot1Accessor boot1_accessor; + if (R_FAILED((rc = boot1_accessor.Initialize()))) { + return rc; + } + ON_SCOPE_EXIT { boot1_accessor.Finalize(); }; + + + /* Compare BCT hashes. */ + if (R_FAILED((rc = boot0_accessor.GetHash(nand_hash, BctSize, work_buffer, work_buffer_size, Boot0Partition::BctSafeMain)))) { + return rc; + } + if (R_FAILED((rc = ValidateBctFileHash(boot0_accessor, Boot0Partition::BctSafeMain, nand_hash, work_buffer, work_buffer_size, boot_image_update_type)))) { + return rc; + } + + /* Compare BCT Sub hashes. */ + if (R_FAILED((rc = boot0_accessor.GetHash(nand_hash, BctSize, work_buffer, work_buffer_size, Boot0Partition::BctSafeSub)))) { + return rc; + } + if (R_FAILED((rc = ValidateBctFileHash(boot0_accessor, Boot0Partition::BctSafeSub, nand_hash, work_buffer, work_buffer_size, boot_image_update_type)))) { + return rc; + } + + /* Compare Package1 Normal/Sub hashes. */ + if (R_FAILED((rc = GetFileHash(&size, file_hash, GetPackage1Path(boot_image_update_type), work_buffer, work_buffer_size)))) { + return rc; + } + if (R_FAILED((rc = boot1_accessor.GetHash(nand_hash, size, work_buffer, work_buffer_size, Boot1Partition::Package1SafeMain)))) { + return rc; + } + if (std::memcmp(file_hash, nand_hash, SHA256_HASH_SIZE) != 0) { + return ResultUpdaterNeedsRepairBootImages; + } + if (R_FAILED((rc = boot1_accessor.GetHash(nand_hash, size, work_buffer, work_buffer_size, Boot1Partition::Package1SafeSub)))) { + return rc; + } + if (std::memcmp(file_hash, nand_hash, SHA256_HASH_SIZE) != 0) { + return ResultUpdaterNeedsRepairBootImages; + } + + /* Compare Package2 Normal/Sub hashes. */ + if (R_FAILED((rc = GetFileHash(&size, file_hash, GetPackage2Path(boot_image_update_type), work_buffer, work_buffer_size)))) { + return rc; + } + if (R_FAILED((rc = GetPackage2Hash(nand_hash, size, work_buffer, work_buffer_size, Package2Type::SafeMain)))) { + return rc; + } + if (std::memcmp(file_hash, nand_hash, SHA256_HASH_SIZE) != 0) { + return ResultUpdaterNeedsRepairBootImages; + } + if (R_FAILED((rc = GetPackage2Hash(nand_hash, size, work_buffer, work_buffer_size, Package2Type::SafeSub)))) { + return rc; + } + if (std::memcmp(file_hash, nand_hash, SHA256_HASH_SIZE) != 0) { + return ResultUpdaterNeedsRepairBootImages; + } + } + + return ResultSuccess; +} + +Result Updater::UpdateBootImages(u64 data_id, BootModeType mode, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type) { + switch (mode) { + case BootModeType_Normal: + return UpdateBootImagesNormal(data_id, work_buffer, work_buffer_size, boot_image_update_type); + case BootModeType_Safe: + return UpdateBootImagesSafe(data_id, work_buffer, work_buffer_size, boot_image_update_type); + default: + std::abort(); + } +} + +Result Updater::UpdateBootImagesNormal(u64 data_id, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type) { + Result rc; + + /* Ensure work buffer is big enough for us to do what we want to do. */ + if (R_FAILED((rc = ValidateWorkBuffer(work_buffer, work_buffer_size)))) { + return rc; + } + + if (R_FAILED((rc = romfsMountFromDataArchive(data_id, FsStorageId_NandSystem, GetBootImagePackageMountPath())))) { + if (rc == ResultFsTargetNotFound) { + return ResultUpdaterBootImagePackageNotFound; + } + return rc; + } + ON_SCOPE_EXIT { if (R_FAILED(romfsUnmount(GetBootImagePackageMountPath()))) { std::abort(); } }; + + { + Boot0Accessor boot0_accessor; + if (R_FAILED((rc = boot0_accessor.Initialize()))) { + return rc; + } + ON_SCOPE_EXIT { boot0_accessor.Finalize(); }; + + /* Write Package1 sub. */ + if (R_FAILED((rc = boot0_accessor.Clear(work_buffer, work_buffer_size, Boot0Partition::Package1NormalSub)))) { + return rc; + } + if (R_FAILED((rc = boot0_accessor.Write(GetPackage1Path(boot_image_update_type), work_buffer, work_buffer_size, Boot0Partition::Package1NormalSub)))) { + return rc; + } + + /* Write Package2 sub. */ + if (R_FAILED((rc = WritePackage2(work_buffer, work_buffer_size, Package2Type::NormalSub, boot_image_update_type)))) { + return rc; + } + + /* Write BCT sub + BCT main, in that order. */ + { + void *bct = reinterpret_cast(reinterpret_cast(work_buffer) + 0); + void *work = reinterpret_cast(reinterpret_cast(work_buffer) + BctSize); + + size_t size; + if (R_FAILED((rc = ReadFile(&size, bct, BctSize, GetBctPath(boot_image_update_type))))) { + return rc; + } + if (HasEks(boot_image_update_type)) { + if (R_FAILED((rc = boot0_accessor.UpdateEks(bct, work)))) { + return rc; + } + } + + /* Only preserve autorcm if on a unit with unpatched rcm bug. */ + if (HasAutoRcmPreserve(boot_image_update_type) && !IsRcmBugPatched()) { + if (R_FAILED((rc = boot0_accessor.PreserveAutoRcm(bct, work, Boot0Partition::BctNormalSub)))) { + return rc; + } + if (R_FAILED((rc = boot0_accessor.Write(bct, BctSize, Boot0Partition::BctNormalSub)))) { + return rc; + } + if (R_FAILED((rc = boot0_accessor.PreserveAutoRcm(bct, work, Boot0Partition::BctNormalMain)))) { + return rc; + } + if (R_FAILED((rc = boot0_accessor.Write(bct, BctSize, Boot0Partition::BctNormalMain)))) { + return rc; + } + } else { + if (R_FAILED((rc = boot0_accessor.Write(bct, BctSize, Boot0Partition::BctNormalSub)))) { + return rc; + } + if (R_FAILED((rc = boot0_accessor.Write(bct, BctSize, Boot0Partition::BctNormalMain)))) { + return rc; + } + } + } + + /* Write Package2 main. */ + if (R_FAILED((rc = WritePackage2(work_buffer, work_buffer_size, Package2Type::NormalMain, boot_image_update_type)))) { + return rc; + } + + /* Write Package1 main. */ + if (R_FAILED((rc = boot0_accessor.Clear(work_buffer, work_buffer_size, Boot0Partition::Package1NormalMain)))) { + return rc; + } + if (R_FAILED((rc = boot0_accessor.Write(GetPackage1Path(boot_image_update_type), work_buffer, work_buffer_size, Boot0Partition::Package1NormalMain)))) { + return rc; + } + } + + return ResultSuccess; +} + +Result Updater::UpdateBootImagesSafe(u64 data_id, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type) { + Result rc; + + /* Ensure work buffer is big enough for us to do what we want to do. */ + if (R_FAILED((rc = ValidateWorkBuffer(work_buffer, work_buffer_size)))) { + return rc; + } + + if (R_FAILED((rc = romfsMountFromDataArchive(data_id, FsStorageId_NandSystem, GetBootImagePackageMountPath())))) { + if (rc == ResultFsTargetNotFound) { + return ResultUpdaterBootImagePackageNotFound; + } + return rc; + } + ON_SCOPE_EXIT { if (R_FAILED(romfsUnmount(GetBootImagePackageMountPath()))) { std::abort(); } }; + + { + Boot0Accessor boot0_accessor; + if (R_FAILED((rc = boot0_accessor.Initialize()))) { + return rc; + } + ON_SCOPE_EXIT { boot0_accessor.Finalize(); }; + + Boot1Accessor boot1_accessor; + if (R_FAILED((rc = boot1_accessor.Initialize()))) { + return rc; + } + ON_SCOPE_EXIT { boot1_accessor.Finalize(); }; + + + /* Write Package1 sub. */ + if (R_FAILED((rc = boot1_accessor.Clear(work_buffer, work_buffer_size, Boot1Partition::Package1SafeSub)))) { + return rc; + } + if (R_FAILED((rc = boot1_accessor.Write(GetPackage1Path(boot_image_update_type), work_buffer, work_buffer_size, Boot1Partition::Package1SafeSub)))) { + return rc; + } + + /* Write Package2 sub. */ + if (R_FAILED((rc = WritePackage2(work_buffer, work_buffer_size, Package2Type::SafeSub, boot_image_update_type)))) { + return rc; + } + + /* Write BCT sub + BCT main, in that order. */ + { + void *bct = reinterpret_cast(reinterpret_cast(work_buffer) + 0); + void *work = reinterpret_cast(reinterpret_cast(work_buffer) + BctSize); + + size_t size; + if (R_FAILED((rc = ReadFile(&size, bct, BctSize, GetBctPath(boot_image_update_type))))) { + return rc; + } + if (HasEks(boot_image_update_type)) { + if (R_FAILED((rc = boot0_accessor.UpdateEks(bct, work)))) { + return rc; + } + } + /* Only preserve autorcm if on a unit with unpatched rcm bug. */ + if (HasAutoRcmPreserve(boot_image_update_type) && !IsRcmBugPatched()) { + if (R_FAILED((rc = boot0_accessor.PreserveAutoRcm(bct, work, Boot0Partition::BctSafeSub)))) { + return rc; + } + if (R_FAILED((rc = boot0_accessor.Write(bct, BctSize, Boot0Partition::BctSafeSub)))) { + return rc; + } + if (R_FAILED((rc = boot0_accessor.PreserveAutoRcm(bct, work, Boot0Partition::BctSafeMain)))) { + return rc; + } + if (R_FAILED((rc = boot0_accessor.Write(bct, BctSize, Boot0Partition::BctSafeMain)))) { + return rc; + } + } else { + if (R_FAILED((rc = boot0_accessor.Write(bct, BctSize, Boot0Partition::BctSafeSub)))) { + return rc; + } + if (R_FAILED((rc = boot0_accessor.Write(bct, BctSize, Boot0Partition::BctSafeMain)))) { + return rc; + } + } + } + + /* Write Package2 main. */ + if (R_FAILED((rc = WritePackage2(work_buffer, work_buffer_size, Package2Type::SafeMain, boot_image_update_type)))) { + return rc; + } + + /* Write Package1 main. */ + if (R_FAILED((rc = boot1_accessor.Clear(work_buffer, work_buffer_size, Boot1Partition::Package1SafeMain)))) { + return rc; + } + if (R_FAILED((rc = boot1_accessor.Write(GetPackage1Path(boot_image_update_type), work_buffer, work_buffer_size, Boot1Partition::Package1SafeMain)))) { + return rc; + } + } + + return ResultSuccess; +} + +Result Updater::SetVerificationNeeded(BootModeType mode, bool needed, void *work_buffer, size_t work_buffer_size) { + Result rc; + + /* Ensure work buffer is big enough for us to do what we want to do. */ + if (R_FAILED((rc = ValidateWorkBuffer(work_buffer, work_buffer_size)))) { + return rc; + } + + /* Initialize boot0 save accessor. */ + BisSave save; + if (R_FAILED((rc = save.Initialize(work_buffer, work_buffer_size)))) { + return rc; + } + ON_SCOPE_EXIT { save.Finalize(); }; + + /* Load save from NAND. */ + if (R_FAILED((rc = save.Load()))) { + return rc; + } + + /* Set whether we need to verify, then save to nand. */ + save.SetNeedsVerification(mode, needed); + if (R_FAILED((rc = save.Save()))) { + return rc; + } + + return ResultSuccess; +} + +Result Updater::GetPackage2Hash(void *dst_hash, size_t package2_size, void *work_buffer, size_t work_buffer_size, Package2Type which) { + Result rc; + + Package2Accessor accessor(which); + if (R_FAILED((rc = accessor.Initialize()))) { + return rc; + } + ON_SCOPE_EXIT { accessor.Finalize(); }; + + return accessor.GetHash(dst_hash, package2_size, work_buffer, work_buffer_size, Package2Partition::Package2); +} + +Result Updater::WritePackage2(void *work_buffer, size_t work_buffer_size, Package2Type which, BootImageUpdateType boot_image_update_type) { + Result rc; + + Package2Accessor accessor(which); + if (R_FAILED((rc = accessor.Initialize()))) { + return rc; + } + ON_SCOPE_EXIT { accessor.Finalize(); }; + + return accessor.Write(GetPackage2Path(boot_image_update_type), work_buffer, work_buffer_size, Package2Partition::Package2); +} diff --git a/stratosphere/boot/source/updater/updater_api.hpp b/stratosphere/boot/source/updater/updater_api.hpp new file mode 100644 index 000000000..1ca08e652 --- /dev/null +++ b/stratosphere/boot/source/updater/updater_api.hpp @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2018-2019 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 + +#include "updater_types.hpp" + +class Boot0Accessor; +enum class Boot0Partition; +enum class Package2Type; + +class Updater { + private: + static bool HasEks(BootImageUpdateType boot_image_update_type); + static bool HasAutoRcmPreserve(BootImageUpdateType boot_image_update_type); + static u32 GetNcmTitleType(BootModeType mode); + static Result ValidateWorkBuffer(const void *work_buffer, size_t work_buffer_size); + static Result GetVerificationState(VerificationState *out, void *work_buffer, size_t work_buffer_size); + static Result VerifyBootImagesAndRepairIfNeeded(bool *out_repaired, BootModeType mode, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type); + static Result GetBootImagePackageDataId(u64 *out_data_id, BootModeType mode, void *work_buffer, size_t work_buffer_size); + static Result VerifyBootImages(u64 data_id, BootModeType mode, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type); + static Result VerifyBootImagesNormal(u64 data_id, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type); + static Result VerifyBootImagesSafe(u64 data_id, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type); + static Result UpdateBootImages(u64 data_id, BootModeType mode, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type); + static Result UpdateBootImagesNormal(u64 data_id, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type); + static Result UpdateBootImagesSafe(u64 data_id, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type); + static Result SetVerificationNeeded(BootModeType mode, bool needed, void *work_buffer, size_t work_buffer_size); + static Result RepairBootImages(u64 data_id, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type); + + /* Path functionality. */ + static const char *GetBootImagePackageMountPath(); + static const char *GetBctPath(BootImageUpdateType boot_image_update_type); + static const char *GetPackage1Path(BootImageUpdateType boot_image_update_type); + static const char *GetPackage2Path(BootImageUpdateType boot_image_update_type); + + /* File helpers. */ + static Result ReadFile(size_t *out_size, void *dst, size_t dst_size, const char *path); + static Result GetFileHash(size_t *out_size, void *dst_hash, const char *path, void *work_buffer, size_t work_buffer_size); + + /* Package helpers. */ + static Result ValidateBctFileHash(Boot0Accessor &accessor, Boot0Partition which, const void *stored_hash, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type); + static Result GetPackage2Hash(void *dst_hash, size_t package2_size, void *work_buffer, size_t work_buffer_size, Package2Type which); + static Result WritePackage2(void *work_buffer, size_t work_buffer_size, Package2Type which, BootImageUpdateType boot_image_update_type); + public: + static BootImageUpdateType GetBootImageUpdateType(HardwareType hw_type); + static Result VerifyBootImagesAndRepairIfNeeded(bool *out_repaired_normal, bool *out_repaired_safe, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type); +}; \ No newline at end of file diff --git a/stratosphere/boot/source/updater/updater_bis_management.cpp b/stratosphere/boot/source/updater/updater_bis_management.cpp new file mode 100644 index 000000000..b65528119 --- /dev/null +++ b/stratosphere/boot/source/updater/updater_bis_management.cpp @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2018-2019 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 + +#include "updater_bis_management.hpp" + +Result BisAccessor::Initialize() { + Result rc = fsOpenBisStorage(&this->storage, this->partition_id); + if (R_SUCCEEDED(rc)) { + this->active = true; + } + return rc; +} + +void BisAccessor::Finalize() { + if (this->active) { + fsStorageClose(&this->storage); + this->active = false; + } +} + +Result BisAccessor::Read(void *dst, size_t size, u64 offset) { + if (offset % SectorAlignment) { + std::abort(); + } + return fsStorageRead(&this->storage, offset, dst, size); +} + +Result BisAccessor::Write(u64 offset, const void *src, size_t size) { + if (offset % SectorAlignment) { + std::abort(); + } + return fsStorageWrite(&this->storage, offset, src, size); +} + +Result BisAccessor::Write(u64 offset, size_t size, const char *bip_path, void *work_buffer, size_t work_buffer_size) { + Result rc; + if (offset % SectorAlignment != 0 || work_buffer_size % SectorAlignment != 0) { + std::abort(); + } + + FILE *bip_fp = fopen(bip_path, "rb"); + if (bip_fp == NULL) { + return ResultUpdaterInvalidBootImagePackage; + } + ON_SCOPE_EXIT { fclose(bip_fp); }; + + size_t written = 0; + while (true) { + std::memset(work_buffer, 0, work_buffer_size); + size_t read_size = fread(work_buffer, 1, work_buffer_size, bip_fp); + if (read_size != work_buffer_size) { + if (ferror(bip_fp)) { + return fsdevGetLastResult(); + } + } + if (written + read_size > size) { + std::abort(); + } + + size_t aligned_size = ((read_size + SectorAlignment - 1) / SectorAlignment) * SectorAlignment; + if (R_FAILED((rc = this->Write(offset + written, work_buffer, aligned_size)))) { + return rc; + } + written += read_size; + + if (read_size != work_buffer_size) { + break; + } + } + return ResultSuccess; +} + +Result BisAccessor::Clear(u64 offset, u64 size, void *work_buffer, size_t work_buffer_size) { + Result rc; + if (offset % SectorAlignment != 0 || work_buffer_size % SectorAlignment != 0) { + std::abort(); + } + + std::memset(work_buffer, 0, work_buffer_size); + + size_t written = 0; + while (written < size) { + size_t cur_write_size = std::min(work_buffer_size, size - written); + if (R_FAILED((rc = this->Write(offset + written, work_buffer, cur_write_size)))) { + return rc; + } + written += cur_write_size; + } + return ResultSuccess; +} + +Result BisAccessor::GetHash(void *dst, u64 offset, u64 size, u64 hash_size, void *work_buffer, size_t work_buffer_size) { + Result rc; + if (offset % SectorAlignment != 0 || work_buffer_size % SectorAlignment != 0) { + std::abort(); + } + + Sha256Context sha_ctx; + sha256ContextCreate(&sha_ctx); + + size_t total_read = 0; + while (total_read < hash_size) { + size_t cur_read_size = std::min(work_buffer_size, size - total_read); + size_t cur_update_size = std::min(cur_read_size, hash_size - total_read); + if (R_FAILED((rc = this->Read(work_buffer, cur_read_size, offset + total_read)))) { + return rc; + } + sha256ContextUpdate(&sha_ctx, work_buffer, cur_update_size); + total_read += cur_read_size; + } + sha256ContextGetHash(&sha_ctx, dst); + + return ResultSuccess; +} + +size_t Boot0Accessor::GetBootloaderVersion(void *bct) { + u32 version = *reinterpret_cast(reinterpret_cast(bct) + BctVersionOffset); + if (version > BctVersionMax) { + std::abort(); + } + + return static_cast(version); +} + +size_t Boot0Accessor::GetEksIndex(size_t bootloader_version) { + if (bootloader_version > BctVersionMax) { + std::abort(); + } + + return (bootloader_version > 0) ? bootloader_version - 1 : 0; +} + +void Boot0Accessor::CopyEks(void *dst_bct, const void *src_eks, size_t eks_index) { + std::memcpy(reinterpret_cast(dst_bct) + BctEksOffset, reinterpret_cast(src_eks) + eks_index * EksEntrySize, EksBlobSize); +} + +Result Boot0Accessor::UpdateEks(void *dst_bct, void *eks_work_buffer) { + size_t read_size; + Result rc = this->Read(&read_size, eks_work_buffer, EksSize, Boot0Partition::Eks); + if (R_FAILED(rc)) { + return rc; + } + + return this->UpdateEksManually(dst_bct, eks_work_buffer); +} + +Result Boot0Accessor::UpdateEksManually(void *dst_bct, const void *src_eks) { + this->CopyEks(dst_bct, src_eks, GetEksIndex(GetBootloaderVersion(dst_bct))); + return ResultSuccess; +} + +Result Boot0Accessor::PreserveAutoRcm(void *dst_bct, void *work_buffer, Boot0Partition which) { + std::memset(work_buffer, 0, BctSize); + + size_t read_size; + Result rc = this->Read(&read_size, work_buffer, BctSize, which); + if (R_FAILED(rc)) { + return rc; + } + + void *dst_pubk = reinterpret_cast(reinterpret_cast(dst_bct) + BctPubkOffset); + void *src_pubk = reinterpret_cast(reinterpret_cast(work_buffer) + BctPubkOffset); + std::memcpy(dst_pubk, src_pubk, BctPubkSize); + return ResultSuccess; +} \ No newline at end of file diff --git a/stratosphere/boot/source/updater/updater_bis_management.hpp b/stratosphere/boot/source/updater/updater_bis_management.hpp new file mode 100644 index 000000000..b4260c8c0 --- /dev/null +++ b/stratosphere/boot/source/updater/updater_bis_management.hpp @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2018-2019 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 + +#include "updater_types.hpp" + +class BisAccessor { + public: + static constexpr size_t SectorAlignment = 0x200; + private: + FsStorage storage = {}; + u32 partition_id; + bool active; + public: + BisAccessor(u32 id) : partition_id(id), active(false) { } + ~BisAccessor() { + if (this->active) { + fsStorageClose(&storage); + } + } + + public: + Result Initialize(); + void Finalize(); + protected: + Result Read(void *dst, size_t size, u64 offset); + Result Write(u64 offset, const void *src, size_t size); + Result Write(u64 offset, size_t size, const char *bip_path, void *work_buffer, size_t work_buffer_size); + Result Clear(u64 offset, u64 size, void *work_buffer, size_t work_buffer_size); + Result GetHash(void *dst, u64 offset, u64 size, u64 hash_size, void *work_buffer, size_t work_buffer_size); +}; + +template +struct OffsetSizeEntry { + EnumType which; + u64 offset; + size_t size; +}; + +enum class Boot0Partition { + BctNormalMain, + BctSafeMain, + BctNormalSub, + BctSafeSub, + BctSave, + Package1NormalMain, + Package1NormalSub, + Eks, + Count, +}; + +enum class Boot1Partition { + Package1SafeMain, + Package1SafeSub, + Package1RepairMain, + Package1RepairSub, + Count, +}; + +enum class Package2Partition { + BootConfig, + Package2, + Count, +}; + +struct Boot0Meta { + using EnumType = Boot0Partition; + using OffsetSizeType = OffsetSizeEntry; + + static constexpr size_t NumEntries = static_cast(EnumType::Count); + static constexpr OffsetSizeType Entries[NumEntries] = { + {Boot0Partition::BctNormalMain, 0 * BctSize, BctSize}, + {Boot0Partition::BctSafeMain, 1 * BctSize, BctSize}, + {Boot0Partition::BctNormalSub, 2 * BctSize, BctSize}, + {Boot0Partition::BctSafeSub, 3 * BctSize, BctSize}, + {Boot0Partition::BctSave, 63 * BctSize, BctSize}, + {Boot0Partition::Package1NormalMain, 0x100000, 0x40000}, + {Boot0Partition::Package1NormalSub, 0x140000, 0x40000}, + {Boot0Partition::Eks, 0x180000, EksSize}, + }; +}; + +struct Boot1Meta { + using EnumType = Boot1Partition; + using OffsetSizeType = OffsetSizeEntry; + + static constexpr size_t NumEntries = static_cast(EnumType::Count); + static constexpr OffsetSizeType Entries[NumEntries] = { + {Boot1Partition::Package1SafeMain, 0x00000, 0x40000}, + {Boot1Partition::Package1SafeSub, 0x40000, 0x40000}, + {Boot1Partition::Package1RepairMain, 0x80000, 0x40000}, + {Boot1Partition::Package1RepairSub, 0xC0000, 0x40000}, + }; +}; + +struct Package2Meta { + using EnumType = Package2Partition; + using OffsetSizeType = OffsetSizeEntry; + + static constexpr size_t NumEntries = static_cast(EnumType::Count); + static constexpr OffsetSizeType Entries[NumEntries] = { + {Package2Partition::BootConfig, 0x0000, 0x004000}, + {Package2Partition::Package2, 0x4000, 0x7FC000}, + }; +}; + +template +class PartitionAccessor : public BisAccessor { + public: + using EnumType = typename Meta::EnumType; + using OffsetSizeType = typename Meta::OffsetSizeType; + public: + PartitionAccessor(u32 id) : BisAccessor(id) { } + private: + constexpr const OffsetSizeType *FindEntry(EnumType which) { + for (size_t i = 0; i < Meta::NumEntries; i++) { + if (Meta::Entries[i].which == which) { + return &Meta::Entries[i]; + } + } + std::abort(); + } + public: + Result Read(size_t *out_size, void *dst, size_t size, EnumType which) { + const auto entry = FindEntry(which); + if (size < entry->size) { + std::abort(); + } + + Result rc = BisAccessor::Read(dst, entry->size, entry->offset); + if (R_FAILED(rc)) { + return rc; + } + + *out_size = entry->size; + return ResultSuccess; + } + + Result Write(const void *src, size_t size, EnumType which) { + const auto entry = FindEntry(which); + if (size > entry->size || size % BisAccessor::SectorAlignment != 0) { + std::abort(); + } + + return BisAccessor::Write(entry->offset, src, size); + } + + Result Write(const char *bip_path, void *work_buffer, size_t work_buffer_size, EnumType which) { + const auto entry = FindEntry(which); + return BisAccessor::Write(entry->offset, entry->size, bip_path, work_buffer, work_buffer_size); + } + + Result Clear(void *work_buffer, size_t work_buffer_size, EnumType which) { + const auto entry = FindEntry(which); + return BisAccessor::Clear(entry->offset, entry->size, work_buffer, work_buffer_size); + } + + Result GetHash(void *dst, u64 hash_size, void *work_buffer, size_t work_buffer_size, EnumType which) { + const auto entry = FindEntry(which); + return BisAccessor::GetHash(dst, entry->offset, entry->size, hash_size, work_buffer, work_buffer_size); + } +}; + +static constexpr u32 BisStorageId_Boot0 = 0; +static constexpr u32 BisStorageId_Boot1 = 10; + +enum class Package2Type { + NormalMain, + NormalSub, + SafeMain, + SafeSub, + RepairMain, + RepairSub, +}; + +static constexpr u32 GetPackage2StorageId(Package2Type which) { + switch (which) { + case Package2Type::NormalMain: + return 21; + case Package2Type::NormalSub: + return 22; + case Package2Type::SafeMain: + return 23; + case Package2Type::SafeSub: + return 24; + case Package2Type::RepairMain: + return 25; + case Package2Type::RepairSub: + return 26; + default: + std::abort(); + } +} + +class Boot0Accessor : public PartitionAccessor { + public: + static constexpr u32 PartitionId = 0; + static constexpr size_t BctPubkOffset = 0x210; + static constexpr size_t BctPubkSize = 0x100; + static constexpr size_t BctEksOffset = 0x450; + static constexpr size_t BctVersionOffset = 0x2330; + static constexpr size_t BctVersionMax = 0x20; + public: + Boot0Accessor() : PartitionAccessor(PartitionId) { } + private: + static size_t GetBootloaderVersion(void *bct); + static size_t GetEksIndex(size_t bootloader_version); + static void CopyEks(void *dst_bct, const void *src_eks, size_t eks_index); + public: + Result UpdateEks(void *dst_bct, void *eks_work_buffer); + Result UpdateEksManually(void *dst_bct, const void *src_eks); + Result PreserveAutoRcm(void *dst_bct, void *work_buffer, Boot0Partition which); +}; + +class Boot1Accessor : public PartitionAccessor { + public: + static constexpr u32 PartitionId = 10; + public: + Boot1Accessor() : PartitionAccessor(PartitionId) { } +}; + +class Package2Accessor : public PartitionAccessor { + public: + Package2Accessor(Package2Type which) : PartitionAccessor(GetPackage2StorageId(which)) { } +}; + diff --git a/stratosphere/boot/source/updater/updater_bis_save.cpp b/stratosphere/boot/source/updater/updater_bis_save.cpp new file mode 100644 index 000000000..6910e6fe3 --- /dev/null +++ b/stratosphere/boot/source/updater/updater_bis_save.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2018-2019 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 + +#include "updater_bis_save.hpp" + +size_t BisSave::GetVerificationFlagOffset(BootModeType mode) { + switch (mode) { + case BootModeType_Normal: + return 0; + case BootModeType_Safe: + return 1; + default: + return 2; + } +} + +Result BisSave::Initialize(void *work_buffer, size_t work_buffer_size) { + if (work_buffer_size < SaveSize || reinterpret_cast(work_buffer) & 0xFFF || work_buffer_size & 0x1FF) { + std::abort(); + } + + Result rc = this->accessor.Initialize(); + if (R_FAILED(rc)) { + return rc; + } + this->save_buffer = work_buffer; + return ResultSuccess; +} + +void BisSave::Finalize() { + this->accessor.Finalize(); +} + +Result BisSave::Load() { + size_t read_size; + return this->accessor.Read(&read_size, this->save_buffer, SaveSize, Boot0Partition::BctSave); +} + +Result BisSave::Save() { + return this->accessor.Write(this->save_buffer, SaveSize, Boot0Partition::BctSave); +} + +bool BisSave::GetNeedsVerification(BootModeType mode) { + return reinterpret_cast(this->save_buffer)[GetVerificationFlagOffset(mode)] != 0; +} + +void BisSave::SetNeedsVerification(BootModeType mode, bool needs_verification) { + reinterpret_cast(this->save_buffer)[GetVerificationFlagOffset(mode)] = needs_verification ? 1 : 0; +} \ No newline at end of file diff --git a/stratosphere/boot/source/updater/updater_bis_save.hpp b/stratosphere/boot/source/updater/updater_bis_save.hpp new file mode 100644 index 000000000..574398304 --- /dev/null +++ b/stratosphere/boot/source/updater/updater_bis_save.hpp @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2018-2019 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 + +#include "updater_types.hpp" +#include "updater_bis_management.hpp" + +class BisSave { + public: + static constexpr size_t SaveSize = BctSize; + private: + Boot0Accessor accessor; + void *save_buffer; + public: + BisSave() : save_buffer(nullptr) { } + private: + static size_t GetVerificationFlagOffset(BootModeType mode); + public: + Result Initialize(void *work_buffer, size_t work_buffer_size); + void Finalize(); + + Result Load(); + Result Save(); + bool GetNeedsVerification(BootModeType mode); + void SetNeedsVerification(BootModeType mode, bool needs_verification); +}; diff --git a/stratosphere/boot/source/updater/updater_files.cpp b/stratosphere/boot/source/updater/updater_files.cpp new file mode 100644 index 000000000..234f4e419 --- /dev/null +++ b/stratosphere/boot/source/updater/updater_files.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2018-2019 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 + +#include "updater_api.hpp" + +Result Updater::ReadFile(size_t *out_size, void *dst, size_t dst_size, const char *path) { + FILE *fp = fopen(path, "rb"); + if (fp == NULL) { + return ResultUpdaterInvalidBootImagePackage; + } + ON_SCOPE_EXIT { fclose(fp); }; + + std::memset(dst, 0, dst_size); + size_t read_size = fread(dst, 1, dst_size, fp); + if (ferror(fp)) { + return fsdevGetLastResult(); + } + *out_size = read_size; + return ResultSuccess; +} + +Result Updater::GetFileHash(size_t *out_size, void *dst_hash, const char *path, void *work_buffer, size_t work_buffer_size) { + FILE *fp = fopen(path, "rb"); + if (fp == NULL) { + return ResultUpdaterInvalidBootImagePackage; + } + ON_SCOPE_EXIT { fclose(fp); }; + + Sha256Context sha_ctx; + sha256ContextCreate(&sha_ctx); + + size_t total_size = 0; + while (true) { + size_t read_size = fread(work_buffer, 1, work_buffer_size, fp); + if (ferror(fp)) { + return fsdevGetLastResult(); + } + if (read_size == 0) { + break; + } + + sha256ContextUpdate(&sha_ctx, work_buffer, read_size); + total_size += read_size; + if (read_size != work_buffer_size) { + break; + } + } + + sha256ContextGetHash(&sha_ctx, dst_hash); + *out_size = total_size; + return ResultSuccess; +} diff --git a/stratosphere/boot/source/updater/updater_paths.cpp b/stratosphere/boot/source/updater/updater_paths.cpp new file mode 100644 index 000000000..215b81025 --- /dev/null +++ b/stratosphere/boot/source/updater/updater_paths.cpp @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2018-2019 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 +#include + +#include "updater_api.hpp" + +static const char *BootImagePackageMountPath = "bip"; +static const char *BctPathNx = "bip:/nx/bct"; +static const char *Package1PathNx = "bip:/nx/package1"; +static const char *Package2PathNx = "bip:/nx/package2"; +static const char *BctPathA = "bip:/a/bct"; +static const char *Package1PathA = "bip:/a/package1"; +static const char *Package2PathA = "bip:/a/package2"; + +const char *Updater::GetBootImagePackageMountPath() { + return BootImagePackageMountPath; +} + +static const char *ChooseCandidatePath(const char **candidates, size_t num_candidates) { + if (num_candidates == 0) { + std::abort(); + } + + for (size_t i = 0; i < num_candidates; i++) { + struct stat buf; + if (stat(candidates[i], &buf) != 0) { + continue; + } + + if (!S_ISREG(buf.st_mode)) { + continue; + } + + return candidates[i]; + } + + /* Nintendo just uses the last candidate if they all fail...should we abort? */ + return candidates[num_candidates - 1]; +} + +const char *Updater::GetBctPath(BootImageUpdateType boot_image_update_type) { + switch (boot_image_update_type) { + case BootImageUpdateType_Erista: + { + const char *candidates[] = {BctPathNx}; + return ChooseCandidatePath(candidates, sizeof(candidates) / sizeof(candidates[0])); + } + case BootImageUpdateType_Mariko: + { + const char *candidates[] = {BctPathA, BctPathNx}; + return ChooseCandidatePath(candidates, sizeof(candidates) / sizeof(candidates[0])); + } + default: + std::abort(); + } +} + +const char *Updater::GetPackage1Path(BootImageUpdateType boot_image_update_type) { + switch (boot_image_update_type) { + case BootImageUpdateType_Erista: + { + const char *candidates[] = {Package1PathNx}; + return ChooseCandidatePath(candidates, sizeof(candidates) / sizeof(candidates[0])); + } + case BootImageUpdateType_Mariko: + { + const char *candidates[] = {Package1PathA, Package1PathNx}; + return ChooseCandidatePath(candidates, sizeof(candidates) / sizeof(candidates[0])); + } + default: + std::abort(); + } +} + +const char *Updater::GetPackage2Path(BootImageUpdateType boot_image_update_type) { + switch (boot_image_update_type) { + case BootImageUpdateType_Erista: + { + const char *candidates[] = {Package2PathNx}; + return ChooseCandidatePath(candidates, sizeof(candidates) / sizeof(candidates[0])); + } + case BootImageUpdateType_Mariko: + { + const char *candidates[] = {Package2PathA, Package2PathNx}; + return ChooseCandidatePath(candidates, sizeof(candidates) / sizeof(candidates[0])); + } + default: + std::abort(); + } +} \ No newline at end of file diff --git a/stratosphere/boot/source/updater/updater_types.hpp b/stratosphere/boot/source/updater/updater_types.hpp new file mode 100644 index 000000000..c50b7f611 --- /dev/null +++ b/stratosphere/boot/source/updater/updater_types.hpp @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2018-2019 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 + +/* TODO: Better way to do this? */ +#include "../boot_types.hpp" + +enum BootImageUpdateType { + BootImageUpdateType_Erista, + BootImageUpdateType_Mariko, +}; + +enum BootModeType { + BootModeType_Normal, + BootModeType_Safe, +}; + +static constexpr size_t BctSize = 0x4000; +static constexpr size_t EksSize = 0x4000; +static constexpr size_t EksEntrySize = 0x200; +static constexpr size_t EksBlobSize = 0xB0; + +struct VerificationState { + bool needs_verify_normal; + bool needs_verify_safe; +}; \ No newline at end of file diff --git a/stratosphere/libstratosphere b/stratosphere/libstratosphere index c3d344cd1..e8707a658 160000 --- a/stratosphere/libstratosphere +++ b/stratosphere/libstratosphere @@ -1 +1 @@ -Subproject commit c3d344cd1bbad9e759732f866db59a84e4f86357 +Subproject commit e8707a6581ecf33a8e35601c8f2e11018bbdc7ec