From ec44eaa2630da61e20d53a286ae1a74931c8220b Mon Sep 17 00:00:00 2001 From: Michael Scire Date: Fri, 25 Mar 2022 09:48:24 -0700 Subject: [PATCH] fs: update nca drivers (and dependents/callees) for 14.0.0 changes --- .../include/stratosphere/fssystem.hpp | 3 + ...ystem_aes_ctr_counter_extended_storage.hpp | 4 +- .../fssystem_aes_ctr_storage_external.hpp | 50 ++ .../fssystem_aes_xts_storage_external.hpp | 54 ++ .../fssystem_block_cache_buffered_storage.hpp | 4 +- ...rchical_integrity_verification_storage.hpp | 23 +- .../fssystem_integrity_romfs_storage.hpp | 16 +- ...ssystem_integrity_verification_storage.hpp | 11 +- .../fssystem_nca_file_system_driver.hpp | 75 ++- .../fssystem/fssystem_nca_header.hpp | 73 ++- .../fssystem/fssystem_switch_storage.hpp | 218 +++++++ .../fssrv/fssrv_nca_crypto_configuration.cpp | 24 + ...ystem_aes_ctr_counter_extended_storage.cpp | 9 +- .../fssystem_aes_ctr_storage_external.cpp | 130 ++++ .../fssystem/fssystem_aes_xts_storage.cpp | 25 +- .../fssystem_aes_xts_storage_external.cpp | 233 ++++++++ .../fssystem_block_cache_buffered_storage.cpp | 21 +- .../fssystem_crypto_configuration.cpp | 32 +- ...rchical_integrity_verification_storage.cpp | 118 ++-- .../fssystem_integrity_romfs_storage.cpp | 4 +- ...ssystem_integrity_verification_storage.cpp | 109 ++-- .../fssystem_nca_file_system_driver.cpp | 560 +++++++++++------- .../source/fssystem/fssystem_nca_reader.cpp | 132 ++++- .../include/vapours/results/fs_results.hpp | 20 +- 24 files changed, 1491 insertions(+), 457 deletions(-) create mode 100644 libraries/libstratosphere/include/stratosphere/fssystem/fssystem_aes_ctr_storage_external.hpp create mode 100644 libraries/libstratosphere/include/stratosphere/fssystem/fssystem_aes_xts_storage_external.hpp create mode 100644 libraries/libstratosphere/include/stratosphere/fssystem/fssystem_switch_storage.hpp create mode 100644 libraries/libstratosphere/source/fssystem/fssystem_aes_ctr_storage_external.cpp create mode 100644 libraries/libstratosphere/source/fssystem/fssystem_aes_xts_storage_external.cpp diff --git a/libraries/libstratosphere/include/stratosphere/fssystem.hpp b/libraries/libstratosphere/include/stratosphere/fssystem.hpp index c36b85f65..9121b0e7f 100644 --- a/libraries/libstratosphere/include/stratosphere/fssystem.hpp +++ b/libraries/libstratosphere/include/stratosphere/fssystem.hpp @@ -39,6 +39,9 @@ #include #include #include +#include +#include +#include #include #include #include diff --git a/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_aes_ctr_counter_extended_storage.hpp b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_aes_ctr_counter_extended_storage.hpp index 3550e186d..0043cbee8 100644 --- a/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_aes_ctr_counter_extended_storage.hpp +++ b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_aes_ctr_counter_extended_storage.hpp @@ -32,7 +32,7 @@ namespace ams::fssystem { using IAllocator = BucketTree::IAllocator; - using DecryptFunction = void(*)(void *dst, size_t dst_size, s32 index, const void *enc_key, size_t enc_key_size, const void *iv, size_t iv_size, const void *src, size_t src_size); + using DecryptFunction = void(*)(void *dst, size_t dst_size, u8 index, u8 gen, const void *enc_key, size_t enc_key_size, const void *iv, size_t iv_size, const void *src, size_t src_size); class IDecryptor { public: @@ -72,7 +72,7 @@ namespace ams::fssystem { return BucketTree::QueryEntryStorageSize(NodeSize, sizeof(Entry), entry_count); } - static Result CreateExternalDecryptor(std::unique_ptr *out, DecryptFunction func, s32 key_index); + static Result CreateExternalDecryptor(std::unique_ptr *out, DecryptFunction func, s32 key_index, s32 key_generation); static Result CreateSoftwareDecryptor(std::unique_ptr *out); private: BucketTree m_table; diff --git a/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_aes_ctr_storage_external.hpp b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_aes_ctr_storage_external.hpp new file mode 100644 index 000000000..54c8e487f --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_aes_ctr_storage_external.hpp @@ -0,0 +1,50 @@ +/* + * Copyright (c) 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 +#include + +namespace ams::fssystem { + + /* ACCURATE_TO_VERSION: 14.3.0.0 */ + class AesCtrStorageExternal : public ::ams::fs::IStorage, public ::ams::fs::impl::Newable { + NON_COPYABLE(AesCtrStorageExternal); + NON_MOVEABLE(AesCtrStorageExternal); + public: + static constexpr size_t BlockSize = crypto::Aes128CtrEncryptor::BlockSize; + static constexpr size_t KeySize = crypto::Aes128CtrEncryptor::KeySize; + static constexpr size_t IvSize = crypto::Aes128CtrEncryptor::IvSize; + private: + std::shared_ptr m_base_storage; + u8 m_iv[IvSize]; + DecryptAesCtrFunction m_decrypt_function; + s32 m_key_index; + s32 m_key_generation; + u8 m_encrypted_key[KeySize]; + public: + AesCtrStorageExternal(std::shared_ptr bs, const void *enc_key, size_t enc_key_size, const void *iv, size_t iv_size, DecryptAesCtrFunction df, s32 kidx, s32 kgen); + + virtual Result Read(s64 offset, void *buffer, size_t size) override; + virtual Result OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override; + virtual Result GetSize(s64 *out) override; + virtual Result Flush() override; + virtual Result Write(s64 offset, const void *buffer, size_t size) override; + virtual Result SetSize(s64 size) override; + }; + +} diff --git a/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_aes_xts_storage_external.hpp b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_aes_xts_storage_external.hpp new file mode 100644 index 000000000..3760c9657 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_aes_xts_storage_external.hpp @@ -0,0 +1,54 @@ +/* + * Copyright (c) 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 +#include + +namespace ams::fssystem { + + /* ACCURATE_TO_VERSION: 14.3.0.0 */ + template + class AesXtsStorageExternal : public ::ams::fs::IStorage, public ::ams::fs::impl::Newable { + NON_COPYABLE(AesXtsStorageExternal); + NON_MOVEABLE(AesXtsStorageExternal); + public: + static constexpr size_t AesBlockSize = crypto::Aes128XtsEncryptor::BlockSize; + static constexpr size_t KeySize = crypto::Aes128XtsEncryptor::KeySize; + static constexpr size_t IvSize = crypto::Aes128XtsEncryptor::IvSize; + private: + BasePointer m_base_storage; + char m_key[2][KeySize]; + char m_iv[IvSize]; + const size_t m_block_size; + CryptAesXtsFunction m_encrypt_function; + CryptAesXtsFunction m_decrypt_function; + public: + AesXtsStorageExternal(BasePointer bs, const void *key1, const void *key2, size_t key_size, const void *iv, size_t iv_size, size_t block_size, CryptAesXtsFunction ef, CryptAesXtsFunction df); + + virtual Result Read(s64 offset, void *buffer, size_t size) override; + virtual Result OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override; + virtual Result GetSize(s64 *out) override; + virtual Result Flush() override; + virtual Result Write(s64 offset, const void *buffer, size_t size) override; + virtual Result SetSize(s64 size) override; + }; + + using AesXtsStorageExternalByPointer = AesXtsStorageExternal; + using AesXtsStorageExternalBySharedPointer = AesXtsStorageExternal>; + +} diff --git a/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_block_cache_buffered_storage.hpp b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_block_cache_buffered_storage.hpp index faaaae176..f447d0b07 100644 --- a/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_block_cache_buffered_storage.hpp +++ b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_block_cache_buffered_storage.hpp @@ -99,13 +99,13 @@ namespace ams::fssystem { size_t m_verification_block_shift; s32 m_flags; s32 m_buffer_level; - fs::StorageType m_storage_type; BlockCacheManager m_block_cache_manager; + bool m_is_writable; public: BlockCacheBufferedStorage(); virtual ~BlockCacheBufferedStorage() override; - Result Initialize(fs::IBufferManager *bm, os::SdkRecursiveMutex *mtx, IStorage *data, s64 data_size, size_t verif_block_size, s32 max_cache_entries, bool is_real_data, s8 buffer_level, bool is_keep_burst_mode, fs::StorageType storage_type); + Result Initialize(fs::IBufferManager *bm, os::SdkRecursiveMutex *mtx, IStorage *data, s64 data_size, size_t verif_block_size, s32 max_cache_entries, bool is_real_data, s8 buffer_level, bool is_keep_burst_mode, bool is_writable); void Finalize(); virtual Result Read(s64 offset, void *buffer, size_t size) override; diff --git a/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_hierarchical_integrity_verification_storage.hpp b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_hierarchical_integrity_verification_storage.hpp index 20a9484ab..b867b8a0d 100644 --- a/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_hierarchical_integrity_verification_storage.hpp +++ b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_hierarchical_integrity_verification_storage.hpp @@ -24,7 +24,7 @@ namespace ams::fssystem { - /* ACCURATE_TO_VERSION: Unknown */ + /* ACCURATE_TO_VERSION: 14.3.0.0 */ struct HierarchicalIntegrityVerificationLevelInformation { fs::Int64 offset; @@ -151,20 +151,25 @@ namespace ams::fssystem { os::SdkRecursiveMutex *m_mutex; IntegrityVerificationStorage m_verify_storages[MaxLayers - 1]; BlockCacheBufferedStorage m_buffer_storages[MaxLayers - 1]; + os::Semaphore *m_read_semaphore; + os::Semaphore *m_write_semaphore; s64 m_data_size; s32 m_max_layers; - bool m_is_written_for_rollback; public: - HierarchicalIntegrityVerificationStorage() : m_buffers(nullptr), m_mutex(nullptr), m_data_size(-1), m_is_written_for_rollback(false) { /* ... */ } + HierarchicalIntegrityVerificationStorage() : m_buffers(nullptr), m_mutex(nullptr), m_data_size(-1) { /* ... */ } virtual ~HierarchicalIntegrityVerificationStorage() override { this->Finalize(); } - Result Initialize(const HierarchicalIntegrityVerificationInformation &info, HierarchicalStorageInformation storage, FileSystemBufferManagerSet *bufs, IHash256GeneratorFactory *hgf, os::SdkRecursiveMutex *mtx, fs::StorageType storage_type); + Result Initialize(const HierarchicalIntegrityVerificationInformation &info, HierarchicalStorageInformation storage, FileSystemBufferManagerSet *bufs, IHash256GeneratorFactory *hgf, bool hash_salt_enabled, os::SdkRecursiveMutex *mtx, os::Semaphore *read_sema, os::Semaphore *write_sema, int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level, bool is_writable, bool allow_cleared_blocks); + Result Initialize(const HierarchicalIntegrityVerificationInformation &info, HierarchicalStorageInformation storage, FileSystemBufferManagerSet *bufs, IHash256GeneratorFactory *hgf, bool hash_salt_enabled, os::SdkRecursiveMutex *mtx, int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level, bool is_writable, bool allow_cleared_blocks) { + R_RETURN(this->Initialize(info, storage, bufs, hgf, hash_salt_enabled, mtx, nullptr, nullptr, max_data_cache_entries, max_hash_cache_entries, buffer_level, is_writable, allow_cleared_blocks)); + } + void Finalize(); virtual Result Read(s64 offset, void *buffer, size_t size) override; virtual Result Write(s64 offset, const void *buffer, size_t size) override; - virtual Result SetSize(s64 size) override { AMS_UNUSED(size); return fs::ResultUnsupportedSetSizeForHierarchicalIntegrityVerificationStorage(); } + virtual Result SetSize(s64 size) override { AMS_UNUSED(size); R_THROW(fs::ResultUnsupportedSetSizeForHierarchicalIntegrityVerificationStorage()); } virtual Result GetSize(s64 *out) override; virtual Result Flush() override; @@ -179,10 +184,6 @@ namespace ams::fssystem { return m_data_size >= 0; } - bool IsWrittenForRollback() const { - return m_is_written_for_rollback; - } - FileSystemBufferManagerSet *GetBuffers() { return m_buffers; } @@ -201,6 +202,10 @@ namespace ams::fssystem { fs::SubStorage GetL1HashStorage() { return fs::SubStorage(std::addressof(m_buffer_storages[m_max_layers - 3]), 0, util::DivideUp(m_data_size, this->GetL1HashVerificationBlockSize())); } + public: + static constexpr s8 GetDefaultDataCacheBufferLevel(u32 max_layers) { + return 16 + max_layers - 2; + } }; } diff --git a/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_integrity_romfs_storage.hpp b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_integrity_romfs_storage.hpp index ea8ffd2c0..3bc663102 100644 --- a/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_integrity_romfs_storage.hpp +++ b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_integrity_romfs_storage.hpp @@ -39,33 +39,33 @@ namespace ams::fssystem { IntegrityRomFsStorage() : m_mutex() { /* ... */ } virtual ~IntegrityRomFsStorage() override { this->Finalize(); } - Result Initialize(HierarchicalIntegrityVerificationInformation level_hash_info, Hash master_hash, HierarchicalIntegrityVerificationStorage::HierarchicalStorageInformation storage_info, fs::IBufferManager *bm, IHash256GeneratorFactory *hgf); + Result Initialize(HierarchicalIntegrityVerificationInformation level_hash_info, Hash master_hash, HierarchicalIntegrityVerificationStorage::HierarchicalStorageInformation storage_info, fs::IBufferManager *bm, int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level, IHash256GeneratorFactory *hgf); void Finalize(); virtual Result Read(s64 offset, void *buffer, size_t size) override { - return m_integrity_storage.Read(offset, buffer, size); + R_RETURN(m_integrity_storage.Read(offset, buffer, size)); } virtual Result Write(s64 offset, const void *buffer, size_t size) override { - return m_integrity_storage.Write(offset, buffer, size); + R_RETURN(m_integrity_storage.Write(offset, buffer, size)); } - virtual Result SetSize(s64 size) override { AMS_UNUSED(size); return fs::ResultUnsupportedSetSizeForIntegrityRomFsStorage(); } + virtual Result SetSize(s64 size) override { AMS_UNUSED(size); R_THROW(fs::ResultUnsupportedSetSizeForIntegrityRomFsStorage()); } virtual Result GetSize(s64 *out) override { - return m_integrity_storage.GetSize(out); + R_RETURN(m_integrity_storage.GetSize(out)); } virtual Result Flush() override { - return m_integrity_storage.Flush(); + R_RETURN(m_integrity_storage.Flush()); } virtual Result OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override { - return m_integrity_storage.OperateRange(dst, dst_size, op_id, offset, size, src, src_size); + R_RETURN(m_integrity_storage.OperateRange(dst, dst_size, op_id, offset, size, src, src_size)); } Result Commit() { - return m_integrity_storage.Commit(); + R_RETURN(m_integrity_storage.Commit()); } FileSystemBufferManagerSet *GetBuffers() { diff --git a/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_integrity_verification_storage.hpp b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_integrity_verification_storage.hpp index a74b09398..b1c0d305a 100644 --- a/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_integrity_verification_storage.hpp +++ b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_integrity_verification_storage.hpp @@ -23,7 +23,7 @@ namespace ams::fssystem { - /* ACCURATE_TO_VERSION: Unknown */ + /* ACCURATE_TO_VERSION: 14.3.0.0 */ class IntegrityVerificationStorage : public ::ams::fs::IStorage { NON_COPYABLE(IntegrityVerificationStorage); @@ -43,15 +43,16 @@ namespace ams::fssystem { s64 m_upper_layer_verification_block_size; s64 m_upper_layer_verification_block_order; fs::IBufferManager *m_buffer_manager; - fs::HashSalt m_salt; + util::optional m_salt; bool m_is_real_data; - fs::StorageType m_storage_type; fssystem::IHash256GeneratorFactory *m_hash_generator_factory; + bool m_is_writable; + bool m_allow_cleared_blocks; public: - IntegrityVerificationStorage() : m_verification_block_size(0), m_verification_block_order(0), m_upper_layer_verification_block_size(0), m_upper_layer_verification_block_order(0), m_buffer_manager(nullptr) { /* ... */ } + IntegrityVerificationStorage() : m_verification_block_size(0), m_verification_block_order(0), m_upper_layer_verification_block_size(0), m_upper_layer_verification_block_order(0), m_buffer_manager(nullptr), m_salt(util::nullopt) { /* ... */ } virtual ~IntegrityVerificationStorage() override { this->Finalize(); } - Result Initialize(fs::SubStorage hs, fs::SubStorage ds, s64 verif_block_size, s64 upper_layer_verif_block_size, fs::IBufferManager *bm, fssystem::IHash256GeneratorFactory *hgf, const fs::HashSalt &salt, bool is_real_data, fs::StorageType storage_type); + Result Initialize(fs::SubStorage hs, fs::SubStorage ds, s64 verif_block_size, s64 upper_layer_verif_block_size, fs::IBufferManager *bm, fssystem::IHash256GeneratorFactory *hgf, const util::optional &salt, bool is_real_data, bool is_writable, bool allow_cleared_blocks); void Finalize(); virtual Result Read(s64 offset, void *buffer, size_t size) override; diff --git a/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_nca_file_system_driver.hpp b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_nca_file_system_driver.hpp index c2e28c5a6..dd78929c9 100644 --- a/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_nca_file_system_driver.hpp +++ b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_nca_file_system_driver.hpp @@ -25,7 +25,7 @@ namespace ams::fssystem { - /* ACCURATE_TO_VERSION: 13.4.0.0 */ + /* ACCURATE_TO_VERSION: 14.3.0.0 */ class CompressedStorage; class AesCtrCounterExtendedStorage; @@ -34,8 +34,11 @@ namespace ams::fssystem { struct NcaCryptoConfiguration; - using KeyGenerationFunction = void (*)(void *dst_key, size_t dst_key_size, const void *src_key, size_t src_key_size, s32 key_type, const NcaCryptoConfiguration &cfg); - using DecryptAesCtrFunction = void (*)(void *dst, size_t dst_size, s32 key_type, const void *src_key, size_t src_key_size, const void *iv, size_t iv_size, const void *src, size_t src_size); + using KeyGenerationFunction = void (*)(void *dst_key, size_t dst_key_size, const void *src_key, size_t src_key_size, s32 key_type); + using DecryptAesCtrFunction = void (*)(void *dst, size_t dst_size, u8 key_index, u8 key_generation, const void *src_key, size_t src_key_size, const void *iv, size_t iv_size, const void *src, size_t src_size); + + using CryptAesXtsFunction = Result (*)(void *dst, size_t dst_size, const void *key1, const void *key2, size_t key_size, const void *iv, size_t iv_size, const void *src, size_t src_size); + using VerifySign1Function = bool (*)(const void *sig, size_t sig_size, const void *data, size_t data_size, u8 generation, const NcaCryptoConfiguration &cfg); struct NcaCryptoConfiguration { static constexpr size_t Rsa2048KeyModulusSize = crypto::Rsa2048PssSha256Verifier::ModulusSize; @@ -49,6 +52,8 @@ namespace ams::fssystem { static constexpr s32 KeyAreaEncryptionKeyIndexCount = 3; static constexpr s32 HeaderEncryptionKeyCount = 2; + static constexpr u8 KeyAreaEncryptionKeyIndexZeroKey = 0xFF; + static constexpr size_t KeyGenerationMax = 32; const u8 *header_1_sign_key_moduli[Header1SignatureKeyGenerationMax + 1]; @@ -57,9 +62,13 @@ namespace ams::fssystem { u8 header_encryption_key_source[Aes128KeySize]; u8 header_encrypted_encryption_keys[HeaderEncryptionKeyCount][Aes128KeySize]; KeyGenerationFunction generate_key; + CryptAesXtsFunction decrypt_aes_xts_external; + CryptAesXtsFunction encrypt_aes_xts_external; DecryptAesCtrFunction decrypt_aes_ctr; DecryptAesCtrFunction decrypt_aes_ctr_external; + VerifySign1Function verify_sign1; bool is_plaintext_header_available; + bool is_available_sw_key; #if !defined(ATMOSPHERE_BOARD_NINTENDO_NX) bool is_unsigned_header_available_for_host_tool; @@ -72,30 +81,35 @@ namespace ams::fssystem { }; static_assert(util::is_pod::value); + constexpr inline s32 KeyAreaEncryptionKeyCount = NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount * NcaCryptoConfiguration::KeyGenerationMax; + + enum class KeyType : s32 { + ZeroKey = -2, + InvalidKey = -1, + NcaHeaderKey1 = KeyAreaEncryptionKeyCount + 0, + NcaHeaderKey2 = KeyAreaEncryptionKeyCount + 1, + NcaExternalKey = KeyAreaEncryptionKeyCount + 2, + SaveDataDeviceUniqueMac = KeyAreaEncryptionKeyCount + 3, + SaveDataSeedUniqueMac = KeyAreaEncryptionKeyCount + 4, + SaveDataTransferMac = KeyAreaEncryptionKeyCount + 5, + }; + constexpr inline bool IsInvalidKeyTypeValue(s32 key_type) { return key_type < 0; } constexpr inline s32 GetKeyTypeValue(u8 key_index, u8 key_generation) { - constexpr s32 InvalidKeyTypeValue = -1; - static_assert(IsInvalidKeyTypeValue(InvalidKeyTypeValue)); + if (key_index == NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexZeroKey) { + return util::ToUnderlying(KeyType::ZeroKey); + } if (key_index >= NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount) { - return InvalidKeyTypeValue; + return util::ToUnderlying(KeyType::InvalidKey); } return NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount * key_generation + key_index; } - constexpr inline s32 KeyAreaEncryptionKeyCount = NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount * NcaCryptoConfiguration::KeyGenerationMax; - - enum class KeyType : s32 { - NcaHeaderKey = KeyAreaEncryptionKeyCount + 0, - NcaExternalKey = KeyAreaEncryptionKeyCount + 1, - SaveDataDeviceUniqueMac = KeyAreaEncryptionKeyCount + 2, - SaveDataSeedUniqueMac = KeyAreaEncryptionKeyCount + 3, - }; - class NcaReader : public ::ams::fs::impl::Newable { NON_COPYABLE(NcaReader); NON_MOVEABLE(NcaReader); @@ -108,6 +122,7 @@ namespace ams::fssystem { DecryptAesCtrFunction m_decrypt_aes_ctr; DecryptAesCtrFunction m_decrypt_aes_ctr_external; bool m_is_software_aes_prioritized; + bool m_is_available_sw_key; NcaHeader::EncryptionType m_header_encryption_type; bool m_is_header_sign1_signature_valid; GetDecompressorFunction m_get_decompressor; @@ -144,6 +159,7 @@ namespace ams::fssystem { bool HasInternalDecryptionKeyForAesHw() const; bool IsSoftwareAesPrioritized() const; void PrioritizeSoftwareAes(); + bool IsAvailableSwKey() const; bool HasExternalDecryptionKey() const; const void *GetExternalDecryptionKey() const; void SetExternalDecryptionKey(const void *src, size_t size); @@ -191,12 +207,27 @@ namespace ams::fssystem { NcaPatchInfo &GetPatchInfo(); const NcaPatchInfo &GetPatchInfo() const; const NcaAesCtrUpperIv GetAesCtrUpperIv() const; + + bool IsSkipLayerHashEncryption() const; + Result GetHashTargetOffset(s64 *out) const; + bool ExistsSparseLayer() const; NcaSparseInfo &GetSparseInfo(); const NcaSparseInfo &GetSparseInfo() const; + bool ExistsCompressionLayer() const; NcaCompressionInfo &GetCompressionInfo(); const NcaCompressionInfo &GetCompressionInfo() const; + + bool ExistsPatchMetaHashLayer() const; + NcaMetaDataHashDataInfo &GetPatchMetaDataHashDataInfo(); + const NcaMetaDataHashDataInfo &GetPatchMetaDataHashDataInfo() const; + NcaFsHeader::MetaDataHashType GetPatchMetaHashType() const; + + bool ExistsSparseMetaHashLayer() const; + NcaMetaDataHashDataInfo &GetSparseMetaDataHashDataInfo(); + const NcaMetaDataHashDataInfo &GetSparseMetaDataHashDataInfo() const; + NcaFsHeader::MetaDataHashType GetSparseMetaHashType() const; }; class NcaFileSystemDriver : public ::ams::fs::impl::Newable { @@ -224,6 +255,9 @@ namespace ams::fssystem { std::shared_ptr compressed_storage_meta_storage; std::shared_ptr compressed_storage; + std::shared_ptr patch_layer_info_storage; + std::shared_ptr sparse_layer_info_storage; + /* For tools. */ std::shared_ptr external_original_storage; }; @@ -282,15 +316,24 @@ namespace ams::fssystem { Result CreateSparseStorageCore(std::shared_ptr *out, std::shared_ptr base_storage, s64 base_size, std::shared_ptr meta_storage, const NcaSparseInfo &sparse_info, bool external_info); Result CreateSparseStorage(std::shared_ptr *out, s64 *out_fs_data_offset, std::shared_ptr *out_sparse_storage, std::shared_ptr *out_meta_storage, s32 index, const NcaAesCtrUpperIv &upper_iv, const NcaSparseInfo &sparse_info); - Result CreateAesCtrExMetaStorage(std::shared_ptr *out, std::shared_ptr base_storage, s64 offset, const NcaAesCtrUpperIv &upper_iv, const NcaPatchInfo &patch_info); + Result CreateSparseStorageMetaStorageWithVerification(std::shared_ptr *out, std::shared_ptr *out_verification, std::shared_ptr base_storage, s64 offset, const NcaAesCtrUpperIv &upper_iv, const NcaSparseInfo &sparse_info, const NcaMetaDataHashDataInfo &meta_data_hash_data_info, IHash256GeneratorFactory *hgf); + Result CreateSparseStorageWithVerification(std::shared_ptr *out, s64 *out_fs_data_offset, std::shared_ptr *out_sparse_storage, std::shared_ptr *out_meta_storage, std::shared_ptr *out_verification, s32 index, const NcaAesCtrUpperIv &upper_iv, const NcaSparseInfo &sparse_info, const NcaMetaDataHashDataInfo &meta_data_hash_data_info, NcaFsHeader::MetaDataHashType meta_data_hash_type); + + Result CreateAesCtrExStorageMetaStorage(std::shared_ptr *out, std::shared_ptr base_storage, s64 offset, NcaFsHeader::EncryptionType encryption_type, const NcaAesCtrUpperIv &upper_iv, const NcaPatchInfo &patch_info); Result CreateAesCtrExStorage(std::shared_ptr *out, std::shared_ptr *out_ext, std::shared_ptr base_storage, std::shared_ptr meta_storage, s64 counter_offset, const NcaAesCtrUpperIv &upper_iv, const NcaPatchInfo &patch_info); Result CreateIndirectStorageMetaStorage(std::shared_ptr *out, std::shared_ptr base_storage, const NcaPatchInfo &patch_info); Result CreateIndirectStorage(std::shared_ptr *out, std::shared_ptr *out_ind, std::shared_ptr base_storage, std::shared_ptr original_data_storage, std::shared_ptr meta_storage, const NcaPatchInfo &patch_info); + Result CreatePatchMetaStorage(std::shared_ptr *out_aes_ctr_ex_meta, std::shared_ptr *out_indirect_meta, std::shared_ptr *out_verification, std::shared_ptr base_storage, s64 offset, const NcaAesCtrUpperIv &upper_iv, const NcaPatchInfo &patch_info, const NcaMetaDataHashDataInfo &meta_data_hash_data_info, IHash256GeneratorFactory *hgf); + Result CreateSha256Storage(std::shared_ptr *out, std::shared_ptr base_storage, const NcaFsHeader::HashData::HierarchicalSha256Data &sha256_data, IHash256GeneratorFactory *hgf); Result CreateIntegrityVerificationStorage(std::shared_ptr *out, std::shared_ptr base_storage, const NcaFsHeader::HashData::IntegrityMetaInfo &meta_info, IHash256GeneratorFactory *hgf); + Result CreateIntegrityVerificationStorageForMeta(std::shared_ptr *out, std::shared_ptr *out_verification, std::shared_ptr base_storage, s64 offset, const NcaMetaDataHashDataInfo &meta_data_hash_data_info, IHash256GeneratorFactory *hgf); + Result CreateIntegrityVerificationStorageImpl(std::shared_ptr *out, std::shared_ptr base_storage, const NcaFsHeader::HashData::IntegrityMetaInfo &meta_info, s64 layer_info_offset, int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level, IHash256GeneratorFactory *hgf); + + Result CreateRegionSwitchStorage(std::shared_ptr *out, const NcaFsHeaderReader *header_reader, std::shared_ptr inside_storage, std::shared_ptr outside_storage); Result CreateCompressedStorage(std::shared_ptr *out, std::shared_ptr *out_cmp, std::shared_ptr *out_meta, std::shared_ptr base_storage, const NcaCompressionInfo &compression_info); public: diff --git a/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_nca_header.hpp b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_nca_header.hpp index c4675248b..23ece3e14 100644 --- a/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_nca_header.hpp +++ b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_nca_header.hpp @@ -15,13 +15,14 @@ */ #pragma once #include +#include namespace ams::fssystem { - /* ACCURATE_TO_VERSION: 13.4.0.0 */ + /* ACCURATE_TO_VERSION: 14.3.0.0 */ struct Hash { - static constexpr size_t Size = crypto::Sha256Generator::HashSize; + static constexpr size_t Size = IHash256Generator::HashSize; u8 value[Size]; }; static_assert(sizeof(Hash) == Hash::Size); @@ -185,9 +186,17 @@ namespace ams::fssystem { struct NcaCompressionInfo { NcaBucketInfo bucket; + u8 reserved[8]; }; static_assert(util::is_pod::value); + struct NcaMetaDataHashDataInfo { + fs::Int64 offset; + fs::Int64 size; + Hash hash; + }; + static_assert(util::is_pod::value); + struct NcaFsHeader { static constexpr size_t Size = 0x200; static constexpr size_t HashDataOffset = 0x8; @@ -204,18 +213,28 @@ namespace ams::fssystem { }; enum class EncryptionType : u8 { - Auto = 0, - None = 1, - AesXts = 2, - AesCtr = 3, - AesCtrEx = 4, + Auto = 0, + None = 1, + AesXts = 2, + AesCtr = 3, + AesCtrEx = 4, + AesCtrSkipLayerHash = 5, + AesCtrExSkipLayerHash = 6, }; enum class HashType : u8 { - Auto = 0, - None = 1, - HierarchicalSha256Hash = 2, - HierarchicalIntegrityHash = 3, + Auto = 0, + None = 1, + HierarchicalSha256Hash = 2, + HierarchicalIntegrityHash = 3, + AutoSha3 = 4, + HierarchicalSha3256Hash = 5, + HierarchicalIntegritySha3Hash = 6, + }; + + enum class MetaDataHashType : u8 { + None = 0, + HierarchicalIntegrity = 1, }; union HashData { @@ -265,13 +284,34 @@ namespace ams::fssystem { FsType fs_type; HashType hash_type; EncryptionType encryption_type; - u8 reserved[3]; + MetaDataHashType meta_data_hash_type; + u8 reserved[2]; HashData hash_data; NcaPatchInfo patch_info; NcaAesCtrUpperIv aes_ctr_upper_iv; NcaSparseInfo sparse_info; NcaCompressionInfo compression_info; - u8 pad[0x68]; + NcaMetaDataHashDataInfo meta_data_hash_data_info; + u8 pad[0x30]; + + bool IsSkipLayerHashEncryption() const { + return this->encryption_type == EncryptionType::AesCtrSkipLayerHash || this->encryption_type == EncryptionType::AesCtrExSkipLayerHash; + } + + Result GetHashTargetOffset(s64 *out) const { + switch (this->hash_type) { + case HashType::HierarchicalIntegrityHash: + case HashType::HierarchicalIntegritySha3Hash: + *out = this->hash_data.integrity_meta_info.level_hash_info.info[this->hash_data.integrity_meta_info.level_hash_info.max_layers - 2].offset; + R_SUCCEED(); + case HashType::HierarchicalSha256Hash: + case HashType::HierarchicalSha3256Hash: + *out = this->hash_data.hierarchical_sha256_data.hash_layer_region[this->hash_data.hierarchical_sha256_data.hash_layer_count - 1].offset; + R_SUCCEED(); + default: + R_THROW(fs::ResultInvalidNcaFsHeader()); + } + } }; static_assert(sizeof(NcaFsHeader) == NcaFsHeader::Size); static_assert(util::is_pod::value); @@ -280,4 +320,11 @@ namespace ams::fssystem { inline constexpr const size_t NcaFsHeader::HashData::HierarchicalSha256Data::MasterHashOffset = AMS_OFFSETOF(NcaFsHeader, hash_data.hierarchical_sha256_data.fs_data_master_hash); inline constexpr const size_t NcaFsHeader::HashData::IntegrityMetaInfo::MasterHashOffset = AMS_OFFSETOF(NcaFsHeader, hash_data.integrity_meta_info.master_hash); + struct NcaMetaDataHashData { + s64 layer_info_offset; + NcaFsHeader::HashData::IntegrityMetaInfo integrity_meta_info; + }; + static_assert(sizeof(NcaMetaDataHashData) == sizeof(NcaFsHeader::HashData::IntegrityMetaInfo) + sizeof(s64)); + static_assert(util::is_pod::value); + } diff --git a/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_switch_storage.hpp b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_switch_storage.hpp new file mode 100644 index 000000000..88f3e4aea --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_switch_storage.hpp @@ -0,0 +1,218 @@ +/* + * Copyright (c) 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 + +namespace ams::fssystem { + + /* ACCURATE_TO_VERSION: 14.3.0.0 */ + template + class SwitchStorage : public ::ams::fs::IStorage, public ::ams::fs::impl::Newable { + NON_COPYABLE(SwitchStorage); + NON_MOVEABLE(SwitchStorage); + private: + std::shared_ptr m_true_storage; + std::shared_ptr m_false_storage; + F m_truth_function; + private: + ALWAYS_INLINE std::shared_ptr &SelectStorage() { + return m_truth_function() ? m_true_storage : m_false_storage; + } + public: + SwitchStorage(std::shared_ptr t, std::shared_ptr f, F func) : m_true_storage(std::move(t)), m_false_storage(std::move(f)), m_truth_function(func) { /* ... */ } + + virtual Result Read(s64 offset, void *buffer, size_t size) override { + R_RETURN(this->SelectStorage()->Read(offset, buffer, size)); + } + + virtual Result OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override { + switch (op_id) { + case fs::OperationId::Invalidate: + { + R_TRY(m_true_storage->OperateRange(dst, dst_size, op_id, offset, size, src, src_size)); + R_TRY(m_false_storage->OperateRange(dst, dst_size, op_id, offset, size, src, src_size)); + R_SUCCEED(); + } + case fs::OperationId::QueryRange: + { + R_TRY(this->SelectStorage()->OperateRange(dst, dst_size, op_id, offset, size, src, src_size)); + R_SUCCEED(); + } + default: + R_THROW(fs::ResultUnsupportedOperateRangeForSwitchStorage()); + } + } + + virtual Result GetSize(s64 *out) override { + R_RETURN(this->SelectStorage()->GetSize(out)); + } + + virtual Result Flush() override { + R_RETURN(this->SelectStorage()->Flush()); + } + + virtual Result Write(s64 offset, const void *buffer, size_t size) override { + R_RETURN(this->SelectStorage()->Write(offset, buffer, size)); + } + + virtual Result SetSize(s64 size) override { + R_RETURN(this->SelectStorage()->SetSize(size)); + } + }; + + class RegionSwitchStorage : public ::ams::fs::IStorage, public ::ams::fs::impl::Newable { + NON_COPYABLE(RegionSwitchStorage); + NON_MOVEABLE(RegionSwitchStorage); + public: + struct Region { + s64 offset; + s64 size; + }; + private: + std::shared_ptr m_inside_region_storage; + std::shared_ptr m_outside_region_storage; + Region m_region; + public: + RegionSwitchStorage(std::shared_ptr &&i, std::shared_ptr &&o, Region r) : m_inside_region_storage(std::move(i)), m_outside_region_storage(std::move(o)), m_region(r) { + /* ... */ + } + + virtual Result Read(s64 offset, void *buffer, size_t size) override { + /* Process until we're done. */ + size_t processed = 0; + while (processed < size) { + /* Process on the appropriate storage. */ + s64 cur_size = 0; + if (this->CheckRegions(std::addressof(cur_size), offset + processed, size - processed)) { + R_TRY(m_inside_region_storage->Read(offset + processed, static_cast(buffer) + processed, cur_size)); + } else { + R_TRY(m_outside_region_storage->Read(offset + processed, static_cast(buffer) + processed, cur_size)); + } + + /* Advance. */ + processed += cur_size; + } + + R_SUCCEED(); + } + + virtual Result Write(s64 offset, const void *buffer, size_t size) override { + /* Process until we're done. */ + size_t processed = 0; + while (processed < size) { + /* Process on the appropriate storage. */ + s64 cur_size = 0; + if (this->CheckRegions(std::addressof(cur_size), offset + processed, size - processed)) { + R_TRY(m_inside_region_storage->Write(offset + processed, static_cast(buffer) + processed, cur_size)); + } else { + R_TRY(m_outside_region_storage->Write(offset + processed, static_cast(buffer) + processed, cur_size)); + } + + /* Advance. */ + processed += cur_size; + } + + R_SUCCEED(); + } + + virtual Result OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override { + switch (op_id) { + case fs::OperationId::Invalidate: + { + R_TRY(m_inside_region_storage->OperateRange(dst, dst_size, op_id, offset, size, src, src_size)); + R_TRY(m_outside_region_storage->OperateRange(dst, dst_size, op_id, offset, size, src, src_size)); + R_SUCCEED(); + } + case fs::OperationId::QueryRange: + { + /* Create a new info. */ + fs::QueryRangeInfo merged_info; + merged_info.Clear(); + + /* Process until we're done. */ + s64 processed = 0; + while (processed < size) { + /* Process on the appropriate storage. */ + s64 cur_size = 0; + fs::QueryRangeInfo cur_info; + if (this->CheckRegions(std::addressof(cur_size), offset + processed, size - processed)) { + R_TRY(m_inside_region_storage->OperateRange(std::addressof(cur_info), sizeof(cur_info), op_id, offset + processed, cur_size, src, src_size)); + } else { + R_TRY(m_outside_region_storage->OperateRange(std::addressof(cur_info), sizeof(cur_info), op_id, offset + processed, cur_size, src, src_size)); + } + + /* Merge the current info. */ + merged_info.Merge(cur_info); + + /* Advance. */ + processed += cur_size; + } + + /* Write the merged info. */ + *reinterpret_cast(dst) = merged_info; + R_SUCCEED(); + } + default: + R_THROW(fs::ResultUnsupportedOperateRangeForRegionSwitchStorage()); + } + } + + virtual Result GetSize(s64 *out) override { + R_RETURN(m_inside_region_storage->GetSize(out)); + } + + virtual Result Flush() override { + /* Flush both storages. */ + R_TRY(m_inside_region_storage->Flush()); + R_TRY(m_outside_region_storage->Flush()); + R_SUCCEED(); + } + + virtual Result SetSize(s64 size) override { + /* Set size for both storages. */ + R_TRY(m_inside_region_storage->SetSize(size)); + R_TRY(m_outside_region_storage->SetSize(size)); + R_SUCCEED(); + } + private: + bool CheckRegions(s64 *out_current_size, s64 offset, s64 size) const { + /* Check if our region contains the access. */ + if (m_region.offset <= offset) { + if (offset < m_region.offset + m_region.size) { + if (m_region.offset + m_region.size <= offset + size) { + *out_current_size = m_region.offset + m_region.size - offset; + } else { + *out_current_size = size; + } + return true; + } else { + *out_current_size = size; + return false; + } + } else { + if (m_region.offset <= offset + size) { + *out_current_size = m_region.offset - offset; + } else { + *out_current_size = size; + } + return false; + } + } + }; + +} diff --git a/libraries/libstratosphere/source/fssrv/fssrv_nca_crypto_configuration.cpp b/libraries/libstratosphere/source/fssrv/fssrv_nca_crypto_configuration.cpp index 67a07e18c..514b9dd55 100644 --- a/libraries/libstratosphere/source/fssrv/fssrv_nca_crypto_configuration.cpp +++ b/libraries/libstratosphere/source/fssrv/fssrv_nca_crypto_configuration.cpp @@ -128,14 +128,26 @@ namespace ams::fssrv { /* Key Generation Function */ nullptr, + /* Decrypt Aes Xts Eternal Function */ + nullptr, + + /* Encrypt Aes Xts Eternal Function */ + nullptr, + /* Decrypt Aes Ctr Function */ nullptr, /* Decrypt Aes Ctr External Function */ nullptr, + /* Verify Sign1 Function */ + nullptr, + /* Plaintext Header Available */ false, + + /* Software Key Available */ + true, }; constexpr inline const ::ams::fssystem::NcaCryptoConfiguration DefaultNcaCryptoConfigurationProd = { @@ -169,14 +181,26 @@ namespace ams::fssrv { /* Key Generation Function */ nullptr, + /* Decrypt Aes Xts Eternal Function */ + nullptr, + + /* Encrypt Aes Xts Eternal Function */ + nullptr, + /* Decrypt Aes Ctr Function */ nullptr, /* Decrypt Aes Ctr External Function */ nullptr, + /* Verify Sign1 Function */ + nullptr, + /* Plaintext Header Available */ false, + + /* Software Key Available */ + true, }; } diff --git a/libraries/libstratosphere/source/fssystem/fssystem_aes_ctr_counter_extended_storage.cpp b/libraries/libstratosphere/source/fssystem/fssystem_aes_ctr_counter_extended_storage.cpp index 195f7b4e1..bf8a63156 100644 --- a/libraries/libstratosphere/source/fssystem/fssystem_aes_ctr_counter_extended_storage.cpp +++ b/libraries/libstratosphere/source/fssystem/fssystem_aes_ctr_counter_extended_storage.cpp @@ -33,8 +33,9 @@ namespace ams::fssystem { private: AesCtrCounterExtendedStorage::DecryptFunction m_decrypt_function; s32 m_key_index; + s32 m_key_generation; public: - ExternalDecryptor(AesCtrCounterExtendedStorage::DecryptFunction df, s32 key_idx) : m_decrypt_function(df), m_key_index(key_idx) { + ExternalDecryptor(AesCtrCounterExtendedStorage::DecryptFunction df, s32 key_idx, s32 key_gen) : m_decrypt_function(df), m_key_index(key_idx), m_key_generation(key_gen) { AMS_ASSERT(m_decrypt_function != nullptr); } public: @@ -44,8 +45,8 @@ namespace ams::fssystem { } - Result AesCtrCounterExtendedStorage::CreateExternalDecryptor(std::unique_ptr *out, DecryptFunction func, s32 key_index) { - std::unique_ptr decryptor = std::make_unique(func, key_index); + Result AesCtrCounterExtendedStorage::CreateExternalDecryptor(std::unique_ptr *out, DecryptFunction func, s32 key_index, s32 key_generation) { + std::unique_ptr decryptor = std::make_unique(func, key_index, key_generation); R_UNLESS(decryptor != nullptr, fs::ResultAllocationMemoryFailedInAesCtrCounterExtendedStorageA()); *out = std::move(decryptor); return ResultSuccess(); @@ -280,7 +281,7 @@ namespace ams::fssystem { size_t cur_size = std::min(pooled_buffer.GetSize(), remaining_size); u8 *dst = static_cast(buf) + cur_offset; - m_decrypt_function(pooled_buffer.GetBuffer(), cur_size, m_key_index, enc_key, enc_key_size, ctr, IvSize, dst, cur_size); + m_decrypt_function(pooled_buffer.GetBuffer(), cur_size, m_key_index, m_key_generation, enc_key, enc_key_size, ctr, IvSize, dst, cur_size); std::memcpy(dst, pooled_buffer.GetBuffer(), cur_size); diff --git a/libraries/libstratosphere/source/fssystem/fssystem_aes_ctr_storage_external.cpp b/libraries/libstratosphere/source/fssystem/fssystem_aes_ctr_storage_external.cpp new file mode 100644 index 000000000..256dfa313 --- /dev/null +++ b/libraries/libstratosphere/source/fssystem/fssystem_aes_ctr_storage_external.cpp @@ -0,0 +1,130 @@ +/* + * Copyright (c) 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 + +namespace ams::fssystem { + + AesCtrStorageExternal::AesCtrStorageExternal(std::shared_ptr bs, const void *enc_key, size_t enc_key_size, const void *iv, size_t iv_size, DecryptAesCtrFunction df, s32 kidx, s32 kgen) : m_base_storage(std::move(bs)), m_decrypt_function(df), m_key_index(kidx), m_key_generation(kgen) { + AMS_ASSERT(m_base_storage != nullptr); + AMS_ASSERT(enc_key_size == KeySize); + AMS_ASSERT(iv != nullptr); + AMS_ASSERT(iv_size == IvSize); + AMS_UNUSED(iv_size); + + std::memcpy(m_iv, iv, IvSize); + std::memcpy(m_encrypted_key, enc_key, enc_key_size); + } + + Result AesCtrStorageExternal::Read(s64 offset, void *buffer, size_t size) { + /* Allow zero size. */ + R_SUCCEED_IF(size == 0); + + /* Validate arguments. */ + /* NOTE: For some reason, Nintendo uses InvalidArgument instead of InvalidOffset/InvalidSize here. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + R_UNLESS(util::IsAligned(offset, BlockSize), fs::ResultInvalidArgument()); + R_UNLESS(util::IsAligned(size, BlockSize), fs::ResultInvalidArgument()); + + /* Read the data. */ + R_TRY(m_base_storage->Read(offset, buffer, size)); + + /* Temporarily increase our thread priority. */ + ScopedThreadPriorityChanger cp(+1, ScopedThreadPriorityChanger::Mode::Relative); + + /* Allocate a pooled buffer for decryption. */ + PooledBuffer pooled_buffer; + pooled_buffer.AllocateParticularlyLarge(size, BlockSize); + AMS_ASSERT(pooled_buffer.GetSize() >= BlockSize); + + /* Setup the counter. */ + u8 ctr[IvSize]; + std::memcpy(ctr, m_iv, IvSize); + AddCounter(ctr, IvSize, offset / BlockSize); + + /* Setup tracking. */ + size_t remaining_size = size; + s64 cur_offset = 0; + + while (remaining_size > 0) { + /* Get the current size to process. */ + size_t cur_size = std::min(pooled_buffer.GetSize(), remaining_size); + char *dst = static_cast(buffer) + cur_offset; + + /* Decrypt into the temporary buffer */ + m_decrypt_function(pooled_buffer.GetBuffer(), cur_size, m_key_index, m_key_generation, m_encrypted_key, KeySize, ctr, IvSize, dst, cur_size); + + /* Copy to the destination. */ + std::memcpy(dst, pooled_buffer.GetBuffer(), cur_size); + + /* Update tracking. */ + cur_offset += cur_size; + remaining_size -= cur_size; + + if (remaining_size > 0) { + AddCounter(ctr, IvSize, cur_size / BlockSize); + } + } + + R_SUCCEED(); + } + + Result AesCtrStorageExternal::OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) { + switch (op_id) { + case fs::OperationId::QueryRange: + { + /* Validate that we have an output range info. */ + R_UNLESS(dst != nullptr, fs::ResultNullptrArgument()); + R_UNLESS(dst_size == sizeof(fs::QueryRangeInfo), fs::ResultInvalidSize()); + + /* Operate on our base storage. */ + R_TRY(m_base_storage->OperateRange(dst, dst_size, op_id, offset, size, src, src_size)); + + /* Add in new flags. */ + fs::QueryRangeInfo new_info; + new_info.Clear(); + new_info.aes_ctr_key_type = static_cast(m_key_index >= 0 ? fs::AesCtrKeyTypeFlag::InternalKeyForHardwareAes : fs::AesCtrKeyTypeFlag::ExternalKeyForHardwareAes); + + /* Merge the new info in. */ + reinterpret_cast(dst)->Merge(new_info); + R_SUCCEED(); + } + default: + { + /* Operate on our base storage. */ + R_RETURN(m_base_storage->OperateRange(dst, dst_size, op_id, offset, size, src, src_size)); + } + } + } + + Result AesCtrStorageExternal::GetSize(s64 *out) { + R_RETURN(m_base_storage->GetSize(out)); + } + + Result AesCtrStorageExternal::Flush() { + R_SUCCEED(); + } + + Result AesCtrStorageExternal::Write(s64 offset, const void *buffer, size_t size) { + AMS_UNUSED(offset, buffer, size); + R_THROW(fs::ResultUnsupportedWriteForAesCtrStorageExternal()); + } + + Result AesCtrStorageExternal::SetSize(s64 size) { + AMS_UNUSED(size); + R_THROW(fs::ResultUnsupportedSetSizeForAesCtrStorageExternal()); + } + +} diff --git a/libraries/libstratosphere/source/fssystem/fssystem_aes_xts_storage.cpp b/libraries/libstratosphere/source/fssystem/fssystem_aes_xts_storage.cpp index bd2bf733d..ec6bbd4c9 100644 --- a/libraries/libstratosphere/source/fssystem/fssystem_aes_xts_storage.cpp +++ b/libraries/libstratosphere/source/fssystem/fssystem_aes_xts_storage.cpp @@ -108,7 +108,7 @@ namespace ams::fssystem { AddCounter(ctr, IvSize, 1); } - return ResultSuccess(); + R_SUCCEED(); } template @@ -206,36 +206,39 @@ namespace ams::fssystem { remaining -= write_size; } - return ResultSuccess(); + R_SUCCEED(); } template Result AesXtsStorage::Flush() { - return m_base_storage->Flush(); + R_RETURN(m_base_storage->Flush()); } template Result AesXtsStorage::SetSize(s64 size) { R_UNLESS(util::IsAligned(size, AesBlockSize), fs::ResultUnexpectedInAesXtsStorageA()); - return m_base_storage->SetSize(size); + R_RETURN(m_base_storage->SetSize(size)); } template Result AesXtsStorage::GetSize(s64 *out) { - return m_base_storage->GetSize(out); + R_RETURN(m_base_storage->GetSize(out)); } template Result AesXtsStorage::OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) { - /* Handle the zero size case. */ - R_SUCCEED_IF(size == 0); + /* Unless invalidating cache, check the arguments. */ + if (op_id != fs::OperationId::Invalidate) { + /* Handle the zero size case. */ + R_SUCCEED_IF(size == 0); - /* Ensure alignment. */ - R_UNLESS(util::IsAligned(offset, AesBlockSize), fs::ResultInvalidArgument()); - R_UNLESS(util::IsAligned(size, AesBlockSize), fs::ResultInvalidArgument()); + /* Ensure alignment. */ + R_UNLESS(util::IsAligned(offset, AesBlockSize), fs::ResultInvalidArgument()); + R_UNLESS(util::IsAligned(size, AesBlockSize), fs::ResultInvalidArgument()); + } - return m_base_storage->OperateRange(dst, dst_size, op_id, offset, size, src, src_size); + R_RETURN(m_base_storage->OperateRange(dst, dst_size, op_id, offset, size, src, src_size)); } template class AesXtsStorage; diff --git a/libraries/libstratosphere/source/fssystem/fssystem_aes_xts_storage_external.cpp b/libraries/libstratosphere/source/fssystem/fssystem_aes_xts_storage_external.cpp new file mode 100644 index 000000000..922c5c96e --- /dev/null +++ b/libraries/libstratosphere/source/fssystem/fssystem_aes_xts_storage_external.cpp @@ -0,0 +1,233 @@ +/* + * Copyright (c) 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 + +namespace ams::fssystem { + + template + AesXtsStorageExternal::AesXtsStorageExternal(BasePointer bs, const void *key1, const void *key2, size_t key_size, const void *iv, size_t iv_size, size_t block_size, CryptAesXtsFunction ef, CryptAesXtsFunction df) : m_base_storage(std::move(bs)), m_block_size(block_size), m_encrypt_function(ef), m_decrypt_function(df) { + AMS_ASSERT(key_size == KeySize); + AMS_ASSERT(iv_size == IvSize); + AMS_UNUSED(key_size, iv_size); + + if (key1 != nullptr) { + std::memcpy(m_key[0], key1, KeySize); + } + + if (key2 != nullptr) { + std::memcpy(m_key[1], key2, KeySize); + } + + std::memcpy(m_iv, iv, IvSize); + } + + template + Result AesXtsStorageExternal::Read(s64 offset, void *buffer, size_t size) { + /* Allow zero size. */ + R_SUCCEED_IF(size == 0); + + /* Ensure buffer is valid. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + + /* Ensure we can decrypt. */ + R_UNLESS(m_decrypt_function != nullptr, fs::ResultNullptrArgument()); + + /* We can only read at block aligned offsets. */ + R_UNLESS(util::IsAligned(offset, AesBlockSize), fs::ResultInvalidArgument()); + R_UNLESS(util::IsAligned(size, AesBlockSize), fs::ResultInvalidArgument()); + + /* Read the data. */ + R_TRY(m_base_storage->Read(offset, buffer, size)); + + /* Temporarily increase our thread priority. */ + ScopedThreadPriorityChanger cp(+1, ScopedThreadPriorityChanger::Mode::Relative); + + /* Setup the counter. */ + char ctr[IvSize]; + std::memcpy(ctr, m_iv, IvSize); + AddCounter(ctr, IvSize, offset / m_block_size); + + /* Handle any unaligned data before the start. */ + size_t processed_size = 0; + if ((offset % m_block_size) != 0) { + /* Determine the size of the pre-data read. */ + const size_t skip_size = static_cast(offset - util::AlignDown(offset, m_block_size)); + const size_t data_size = std::min(size, m_block_size - skip_size); + + /* Decrypt into a pooled buffer. */ + { + PooledBuffer tmp_buf(m_block_size, m_block_size); + AMS_ASSERT(tmp_buf.GetSize() >= m_block_size); + + std::memset(tmp_buf.GetBuffer(), 0, skip_size); + std::memcpy(tmp_buf.GetBuffer() + skip_size, buffer, data_size); + + /* Decrypt. */ + R_TRY(m_decrypt_function(tmp_buf.GetBuffer(), m_block_size, m_key[0], m_key[1], KeySize, ctr, IvSize, tmp_buf.GetBuffer(), m_block_size)); + + std::memcpy(buffer, tmp_buf.GetBuffer() + skip_size, data_size); + } + + AddCounter(ctr, IvSize, 1); + processed_size += data_size; + AMS_ASSERT(processed_size == std::min(size, m_block_size - skip_size)); + } + + /* Decrypt aligned chunks. */ + char *cur = static_cast(buffer) + processed_size; + size_t remaining = size - processed_size; + while (remaining > 0) { + const size_t cur_size = std::min(m_block_size, remaining); + R_TRY(m_decrypt_function(cur, cur_size, m_key[0], m_key[1], KeySize, ctr, IvSize, cur, cur_size)); + + remaining -= cur_size; + cur += cur_size; + + AddCounter(ctr, IvSize, 1); + } + + R_SUCCEED(); + } + + template + Result AesXtsStorageExternal::Write(s64 offset, const void *buffer, size_t size) { + /* Allow zero-size writes. */ + R_SUCCEED_IF(size == 0); + + /* Ensure buffer is valid. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + + /* Ensure we can encrypt. */ + R_UNLESS(m_decrypt_function != nullptr, fs::ResultNullptrArgument()); + + /* We can only write at block aligned offsets. */ + R_UNLESS(util::IsAligned(offset, AesBlockSize), fs::ResultInvalidArgument()); + R_UNLESS(util::IsAligned(size, AesBlockSize), fs::ResultInvalidArgument()); + + /* Get a pooled buffer. */ + PooledBuffer pooled_buffer; + const bool use_work_buffer = !IsDeviceAddress(buffer); + if (use_work_buffer) { + pooled_buffer.Allocate(size, m_block_size); + } + + /* Setup the counter. */ + char ctr[IvSize]; + std::memcpy(ctr, m_iv, IvSize); + AddCounter(ctr, IvSize, offset / m_block_size); + + /* Handle any unaligned data before the start. */ + size_t processed_size = 0; + if ((offset % m_block_size) != 0) { + /* Determine the size of the pre-data read. */ + const size_t skip_size = static_cast(offset - util::AlignDown(offset, m_block_size)); + const size_t data_size = std::min(size, m_block_size - skip_size); + + /* Encrypt into a pooled buffer. */ + { + /* NOTE: Nintendo allocates a second pooled buffer here despite having one already allocated above. */ + PooledBuffer tmp_buf(m_block_size, m_block_size); + AMS_ASSERT(tmp_buf.GetSize() >= m_block_size); + + std::memset(tmp_buf.GetBuffer(), 0, skip_size); + std::memcpy(tmp_buf.GetBuffer() + skip_size, buffer, data_size); + + R_TRY(m_encrypt_function(tmp_buf.GetBuffer(), m_block_size, m_key[0], m_key[1], KeySize, ctr, IvSize, tmp_buf.GetBuffer(), m_block_size)); + + R_TRY(m_base_storage->Write(offset, tmp_buf.GetBuffer() + skip_size, data_size)); + } + + AddCounter(ctr, IvSize, 1); + processed_size += data_size; + AMS_ASSERT(processed_size == std::min(size, m_block_size - skip_size)); + } + + /* Encrypt aligned chunks. */ + size_t remaining = size - processed_size; + s64 cur_offset = offset + processed_size; + while (remaining > 0) { + /* Determine data we're writing and where. */ + const size_t write_size = use_work_buffer ? std::min(pooled_buffer.GetSize(), remaining) : remaining; + + /* Encrypt the data, with temporarily increased priority. */ + { + ScopedThreadPriorityChanger cp(+1, ScopedThreadPriorityChanger::Mode::Relative); + + size_t remaining_write = write_size; + size_t encrypt_offset = 0; + while (remaining_write > 0) { + const size_t cur_size = std::min(remaining_write, m_block_size); + const void *src = static_cast(buffer) + processed_size + encrypt_offset; + void *dst = use_work_buffer ? pooled_buffer.GetBuffer() + encrypt_offset : const_cast(src); + + R_TRY(m_encrypt_function(dst, cur_size, m_key[0], m_key[1], KeySize, ctr, IvSize, src, cur_size)); + + AddCounter(ctr, IvSize, 1); + + encrypt_offset += cur_size; + remaining_write -= cur_size; + } + } + + /* Write the encrypted data. */ + const void *write_buf = use_work_buffer ? pooled_buffer.GetBuffer() : static_cast(buffer) + processed_size; + R_TRY(m_base_storage->Write(cur_offset, write_buf, write_size)); + + /* Advance. */ + cur_offset += write_size; + processed_size += write_size; + remaining -= write_size; + } + + R_SUCCEED(); + } + + template + Result AesXtsStorageExternal::OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) { + /* Unless invalidating cache, check the arguments. */ + if (op_id != fs::OperationId::Invalidate) { + /* Handle the zero size case. */ + R_SUCCEED_IF(size == 0); + + /* Ensure alignment. */ + R_UNLESS(util::IsAligned(offset, AesBlockSize), fs::ResultInvalidArgument()); + R_UNLESS(util::IsAligned(size, AesBlockSize), fs::ResultInvalidArgument()); + } + + R_RETURN(m_base_storage->OperateRange(dst, dst_size, op_id, offset, size, src, src_size)); + } + + template + Result AesXtsStorageExternal::GetSize(s64 *out) { + R_RETURN(m_base_storage->GetSize(out)); + } + + template + Result AesXtsStorageExternal::Flush() { + R_RETURN(m_base_storage->Flush()); + } + + template + Result AesXtsStorageExternal::SetSize(s64 size) { + R_UNLESS(util::IsAligned(size, AesBlockSize), fs::ResultUnexpectedInAesXtsStorageA()); + + R_RETURN(m_base_storage->SetSize(size)); + } + + template class AesXtsStorageExternal; + template class AesXtsStorageExternal>; + +} diff --git a/libraries/libstratosphere/source/fssystem/fssystem_block_cache_buffered_storage.cpp b/libraries/libstratosphere/source/fssystem/fssystem_block_cache_buffered_storage.cpp index 80228cd50..8a4418d6f 100644 --- a/libraries/libstratosphere/source/fssystem/fssystem_block_cache_buffered_storage.cpp +++ b/libraries/libstratosphere/source/fssystem/fssystem_block_cache_buffered_storage.cpp @@ -17,8 +17,7 @@ namespace ams::fssystem { - BlockCacheBufferedStorage::BlockCacheBufferedStorage() : m_mutex(), m_data_storage(), m_last_result(ResultSuccess()), m_data_size(), m_verification_block_size(), m_verification_block_shift(), m_flags(), m_buffer_level(-1), m_block_cache_manager() - { + BlockCacheBufferedStorage::BlockCacheBufferedStorage() : m_mutex(), m_data_storage(), m_last_result(ResultSuccess()), m_data_size(), m_verification_block_size(), m_verification_block_shift(), m_flags(), m_buffer_level(-1), m_block_cache_manager() { /* ... */ } @@ -26,7 +25,7 @@ namespace ams::fssystem { this->Finalize(); } - Result BlockCacheBufferedStorage::Initialize(fs::IBufferManager *bm, os::SdkRecursiveMutex *mtx, IStorage *data, s64 data_size, size_t verif_block_size, s32 max_cache_entries, bool is_real_data, s8 buffer_level, bool is_keep_burst_mode, fs::StorageType storage_type) { + Result BlockCacheBufferedStorage::Initialize(fs::IBufferManager *bm, os::SdkRecursiveMutex *mtx, IStorage *data, s64 data_size, size_t verif_block_size, s32 max_cache_entries, bool is_real_data, s8 buffer_level, bool is_keep_burst_mode, bool is_writable) { /* Validate preconditions. */ AMS_ASSERT(data != nullptr); AMS_ASSERT(bm != nullptr); @@ -46,7 +45,7 @@ namespace ams::fssystem { m_last_result = ResultSuccess(); m_flags = 0; m_buffer_level = buffer_level; - m_storage_type = storage_type; + m_is_writable = is_writable; /* Calculate block shift. */ m_verification_block_shift = ILog2(static_cast(verif_block_size)); @@ -385,24 +384,20 @@ namespace ams::fssystem { switch (op_id) { case fs::OperationId::FillZero: { - R_TRY(this->FillZeroImpl(offset, size)); - R_SUCCEED(); + R_RETURN(this->FillZeroImpl(offset, size)); } case fs::OperationId::DestroySignature: { - R_TRY(this->DestroySignatureImpl(offset, size)); - R_SUCCEED(); + R_RETURN(this->DestroySignatureImpl(offset, size)); } case fs::OperationId::Invalidate: { - R_UNLESS(m_storage_type != fs::StorageType_SaveData, fs::ResultUnsupportedOperateRangeForNonSaveDataBlockCacheBufferedStorage()); - R_TRY(this->InvalidateImpl()); - R_SUCCEED(); + R_UNLESS(!m_is_writable, fs::ResultUnsupportedOperateRangeForWritableBlockCacheBufferedStorage()); + R_RETURN(this->InvalidateImpl()); } case fs::OperationId::QueryRange: { - R_TRY(this->QueryRangeImpl(dst, dst_size, offset, size)); - R_SUCCEED(); + R_RETURN(this->QueryRangeImpl(dst, dst_size, offset, size)); } default: R_THROW(fs::ResultUnsupportedOperateRangeForBlockCacheBufferedStorage()); diff --git a/libraries/libstratosphere/source/fssystem/fssystem_crypto_configuration.cpp b/libraries/libstratosphere/source/fssystem/fssystem_crypto_configuration.cpp index daefff14a..c08ec75b4 100644 --- a/libraries/libstratosphere/source/fssystem/fssystem_crypto_configuration.cpp +++ b/libraries/libstratosphere/source/fssystem/fssystem_crypto_configuration.cpp @@ -120,24 +120,24 @@ namespace ams::fssystem { AMS_FUNCTION_LOCAL_STATIC_CONSTINIT(spl::AccessKey, s_nca_header_kek_access_key); AMS_FUNCTION_LOCAL_STATIC_CONSTINIT(spl::AccessKey, s_invalid_nca_kek_access_key); - if (key_type > static_cast(KeyType::NcaHeaderKey) || IsInvalidKeyTypeValue(key_type)) { + if (key_type > static_cast(KeyType::NcaHeaderKey2) || IsInvalidKeyTypeValue(key_type)) { return s_invalid_nca_kek_access_key; - } else if (key_type == static_cast(KeyType::NcaHeaderKey)) { + } else if (key_type == static_cast(KeyType::NcaHeaderKey1) || key_type == static_cast(KeyType::NcaHeaderKey2)) { return s_nca_header_kek_access_key; } else { return s_nca_kek_access_key_array[key_type]; } } - void GenerateNcaKey(void *dst, size_t dst_size, const void *src, size_t src_size, s32 key_type, const NcaCryptoConfiguration &cfg) { - AMS_UNUSED(cfg); - + void GenerateNcaKey(void *dst, size_t dst_size, const void *src, size_t src_size, s32 key_type) { R_ABORT_UNLESS(spl::GenerateAesKey(dst, dst_size, GetNcaKekAccessKey(key_type), src, src_size)); } - void DecryptAesCtr(void *dst, size_t dst_size, s32 key_type, const void *enc_key, size_t enc_key_size, const void *iv, size_t iv_size, const void *src, size_t src_size) { + void DecryptAesCtr(void *dst, size_t dst_size, u8 key_index, u8 key_generation, const void *enc_key, size_t enc_key_size, const void *iv, size_t iv_size, const void *src, size_t src_size) { std::unique_ptr accessor; + const s32 key_type = GetKeyTypeValue(key_index, key_generation); + R_TRY_CATCH(g_key_slot_cache.Find(std::addressof(accessor), enc_key, enc_key_size, key_type)) { R_CATCH(fs::ResultTargetNotFound) { R_ABORT_UNLESS(g_key_slot_cache.AllocateHighPriority(std::addressof(accessor), enc_key, enc_key_size, key_type)); @@ -148,10 +148,11 @@ namespace ams::fssystem { R_ABORT_UNLESS(spl::ComputeCtr(dst, dst_size, accessor->GetKeySlotIndex(), src, src_size, iv, iv_size)); } - void DecryptAesCtrForPreparedKey(void *dst, size_t dst_size, s32 key_type, const void *enc_key, size_t enc_key_size, const void *iv, size_t iv_size, const void *src, size_t src_size) { + void DecryptAesCtrForPreparedKey(void *dst, size_t dst_size, u8 key_index, u8 key_generation, const void *enc_key, size_t enc_key_size, const void *iv, size_t iv_size, const void *src, size_t src_size) { std::unique_ptr accessor; - key_type = static_cast(KeyType::NcaExternalKey); + AMS_UNUSED(key_index, key_generation); + const s32 key_type = static_cast(KeyType::NcaExternalKey); R_TRY_CATCH(g_key_slot_cache.Find(std::addressof(accessor), enc_key, enc_key_size, key_type)) { R_CATCH(fs::ResultTargetNotFound) { @@ -167,6 +168,15 @@ namespace ams::fssystem { R_ABORT_UNLESS(spl::ComputeCtr(dst, dst_size, accessor->GetKeySlotIndex(), src, src_size, iv, iv_size)); } + bool VerifySign1(const void *sig, size_t sig_size, const void *data, size_t data_size, u8 generation, const NcaCryptoConfiguration &cfg) { + const u8 *mod = cfg.header_1_sign_key_moduli[generation]; + const size_t mod_size = NcaCryptoConfiguration::Rsa2048KeyModulusSize; + const u8 *exp = cfg.header_1_sign_key_public_exponent; + const size_t exp_size = NcaCryptoConfiguration::Rsa2048KeyPublicExponentSize; + + return crypto::VerifyRsa2048PssSha256(sig, sig_size, mod, mod_size, exp, exp_size, data, data_size); + } + } const ::ams::fssystem::NcaCryptoConfiguration *GetNcaCryptoConfiguration(bool prod) { @@ -176,9 +186,13 @@ namespace ams::fssystem { /* Set the key generation functions. */ cfg->generate_key = GenerateNcaKey; + cfg->decrypt_aes_xts_external = nullptr; + cfg->encrypt_aes_xts_external = nullptr; cfg->decrypt_aes_ctr = DecryptAesCtr; cfg->decrypt_aes_ctr_external = DecryptAesCtrForPreparedKey; + cfg->verify_sign1 = VerifySign1; cfg->is_plaintext_header_available = !prod; + cfg->is_available_sw_key = true; /* TODO: Should this default to false for host tools with api to set explicitly? */ #if !defined(ATMOSPHERE_BOARD_NINTENDO_NX) @@ -204,7 +218,7 @@ namespace ams::fssystem { } /* Setup the header encryption key. */ - R_ABORT_UNLESS(spl::GenerateAesKek(std::addressof(GetNcaKekAccessKey(static_cast(KeyType::NcaHeaderKey))), nca_crypto_cfg->header_encryption_key_source, KeySize, 0, Option)); + R_ABORT_UNLESS(spl::GenerateAesKek(std::addressof(GetNcaKekAccessKey(static_cast(KeyType::NcaHeaderKey1))), nca_crypto_cfg->header_encryption_key_source, KeySize, 0, Option)); } /* TODO FS-REIMPL: Save stuff. */ diff --git a/libraries/libstratosphere/source/fssystem/fssystem_hierarchical_integrity_verification_storage.cpp b/libraries/libstratosphere/source/fssystem/fssystem_hierarchical_integrity_verification_storage.cpp index b77bb581c..a9c5533f4 100644 --- a/libraries/libstratosphere/source/fssystem/fssystem_hierarchical_integrity_verification_storage.cpp +++ b/libraries/libstratosphere/source/fssystem/fssystem_hierarchical_integrity_verification_storage.cpp @@ -23,11 +23,6 @@ namespace ams::fssystem { constexpr inline u32 IntegrityVerificationStorageVersion = 0x00020000; constexpr inline u32 IntegrityVerificationStorageVersionMask = 0xFFFF0000; - constexpr inline auto MaxSaveDataFsDataCacheEntryCount = 32; - constexpr inline auto MaxSaveDataFsHashCacheEntryCount = 4; - constexpr inline auto MaxRomFsDataCacheEntryCount = 24; - constexpr inline auto MaxRomFsHashCacheEntryCount = 8; - constexpr inline auto AccessCountMax = 5; constexpr inline auto AccessTimeout = TimeSpan::FromMilliSeconds(10); @@ -92,7 +87,7 @@ namespace ams::fssystem { out->layered_hash_sizes[level - 1] = level_size[level]; } - return ResultSuccess(); + R_SUCCEED(); } Result HierarchicalIntegrityVerificationStorageControlArea::Expand(fs::SubStorage meta_storage, const HierarchicalIntegrityVerificationMetaInformation &meta) { @@ -122,7 +117,7 @@ namespace ams::fssystem { R_TRY(meta_storage.Write(0, std::addressof(meta), sizeof(meta))); R_TRY(meta_storage.Flush()); - return ResultSuccess(); + R_SUCCEED(); } Result HierarchicalIntegrityVerificationStorageControlArea::Initialize(fs::SubStorage meta_storage) { @@ -143,36 +138,37 @@ namespace ams::fssystem { /* Validate the meta version. */ R_UNLESS((m_meta.version & IntegrityVerificationStorageVersionMask) == (IntegrityVerificationStorageVersion & IntegrityVerificationStorageVersionMask), fs::ResultUnsupportedVersion()); - return ResultSuccess(); + R_SUCCEED(); } void HierarchicalIntegrityVerificationStorageControlArea::Finalize() { m_storage = fs::SubStorage(); } - Result HierarchicalIntegrityVerificationStorage::Initialize(const HierarchicalIntegrityVerificationInformation &info, HierarchicalStorageInformation storage, FileSystemBufferManagerSet *bufs, IHash256GeneratorFactory *hgf, os::SdkRecursiveMutex *mtx, fs::StorageType storage_type) { + Result HierarchicalIntegrityVerificationStorage::Initialize(const HierarchicalIntegrityVerificationInformation &info, HierarchicalStorageInformation storage, FileSystemBufferManagerSet *bufs, IHash256GeneratorFactory *hgf, bool hash_salt_enabled, os::SdkRecursiveMutex *mtx, os::Semaphore *read_sema, os::Semaphore *write_sema, int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level, bool is_writable, bool allow_cleared_blocks) { /* Validate preconditions. */ AMS_ASSERT(bufs != nullptr); AMS_ASSERT(IntegrityMinLayerCount <= info.max_layers && info.max_layers <= IntegrityMaxLayerCount); /* Set member variables. */ - m_max_layers = info.max_layers; - m_buffers = bufs; - m_mutex = mtx; + m_max_layers = info.max_layers; + m_buffers = bufs; + m_mutex = mtx; + m_read_semaphore = read_sema; + m_write_semaphore = write_sema; - /* Determine our cache counts. */ - const auto max_data_cache_entry_count = (storage_type == fs::StorageType_SaveData) ? MaxSaveDataFsDataCacheEntryCount : MaxRomFsDataCacheEntryCount; - const auto max_hash_cache_entry_count = (storage_type == fs::StorageType_SaveData) ? MaxSaveDataFsHashCacheEntryCount : MaxRomFsHashCacheEntryCount; - - /* Initialize the top level verification storage. */ - { - fs::HashSalt mac; - crypto::GenerateHmacSha256(mac.value, sizeof(mac), info.seed.value, sizeof(info.seed), KeyArray[0].key, KeyArray[0].size); - m_verify_storages[0].Initialize(storage[HierarchicalStorageInformation::MasterStorage], storage[HierarchicalStorageInformation::Layer1Storage], static_cast(1) << info.info[0].block_order, HashSize, m_buffers->buffers[m_max_layers - 2], hgf, mac, false, storage_type); + /* If hash salt is enabled, generate it. */ + util::optional hash_salt = util::nullopt; + if (hash_salt_enabled) { + hash_salt.emplace(); + crypto::GenerateHmacSha256(hash_salt->value, sizeof(hash_salt->value), info.seed.value, sizeof(info.seed), KeyArray[0].key, KeyArray[0].size); } + /* Initialize the top level verification storage. */ + m_verify_storages[0].Initialize(storage[HierarchicalStorageInformation::MasterStorage], storage[HierarchicalStorageInformation::Layer1Storage], static_cast(1) << info.info[0].block_order, HashSize, m_buffers->buffers[m_max_layers - 2], hgf, hash_salt, false, is_writable, allow_cleared_blocks); + /* Ensure we don't leak state if further initialization goes wrong. */ - auto top_verif_guard = SCOPE_GUARD { + ON_RESULT_FAILURE { m_verify_storages[0].Finalize(); m_data_size = -1; @@ -181,14 +177,14 @@ namespace ams::fssystem { }; /* Initialize the top level buffer storage. */ - R_TRY(m_buffer_storages[0].Initialize(m_buffers->buffers[0], m_mutex, std::addressof(m_verify_storages[0]), info.info[0].size, static_cast(1) << info.info[0].block_order, max_hash_cache_entry_count, false, 0x10, false, storage_type)); - auto top_buffer_guard = SCOPE_GUARD { m_buffer_storages[0].Finalize(); }; + R_TRY(m_buffer_storages[0].Initialize(m_buffers->buffers[0], m_mutex, std::addressof(m_verify_storages[0]), info.info[0].size, static_cast(1) << info.info[0].block_order, max_hash_cache_entries, false, 0x10, false, is_writable)); + ON_RESULT_FAILURE_2 { m_buffer_storages[0].Finalize(); }; /* Prepare to initialize the level storages. */ s32 level = 0; /* Ensure we don't leak state if further initialization goes wrong. */ - auto level_guard = SCOPE_GUARD { + ON_RESULT_FAILURE_2 { m_verify_storages[level + 1].Finalize(); for (/* ... */; level > 0; --level) { m_buffer_storages[level].Finalize(); @@ -198,40 +194,43 @@ namespace ams::fssystem { /* Initialize the level storages. */ for (/* ... */; level < m_max_layers - 3; ++level) { - /* Initialize the verification storage. */ - { - fs::SubStorage buffer_storage(std::addressof(m_buffer_storages[level]), 0, info.info[level].size); - fs::HashSalt mac; - crypto::GenerateHmacSha256(mac.value, sizeof(mac), info.seed.value, sizeof(info.seed), KeyArray[level + 1].key, KeyArray[level + 1].size); - m_verify_storages[level + 1].Initialize(buffer_storage, storage[level + 2], static_cast(1) << info.info[level + 1].block_order, static_cast(1) << info.info[level].block_order, m_buffers->buffers[m_max_layers - 2], hgf, mac, false, storage_type); + /* If hash salt is enabled, generate it. */ + util::optional hash_salt = util::nullopt; + if (hash_salt_enabled) { + hash_salt.emplace(); + crypto::GenerateHmacSha256(hash_salt->value, sizeof(hash_salt->value), info.seed.value, sizeof(info.seed), KeyArray[level + 1].key, KeyArray[level + 1].size); } + /* Initialize the verification storage. */ + fs::SubStorage buffer_storage(std::addressof(m_buffer_storages[level]), 0, info.info[level].size); + m_verify_storages[level + 1].Initialize(buffer_storage, storage[level + 2], static_cast(1) << info.info[level + 1].block_order, static_cast(1) << info.info[level].block_order, m_buffers->buffers[m_max_layers - 2], hgf, hash_salt, false, is_writable, allow_cleared_blocks); + /* Initialize the buffer storage. */ - R_TRY(m_buffer_storages[level + 1].Initialize(m_buffers->buffers[level + 1], m_mutex, std::addressof(m_verify_storages[level + 1]), info.info[level + 1].size, static_cast(1) << info.info[level + 1].block_order, max_hash_cache_entry_count, false, 0x11 + static_cast(level), false, storage_type)); + R_TRY(m_buffer_storages[level + 1].Initialize(m_buffers->buffers[level + 1], m_mutex, std::addressof(m_verify_storages[level + 1]), info.info[level + 1].size, static_cast(1) << info.info[level + 1].block_order, max_hash_cache_entries, false, 0x11 + static_cast(level), false, is_writable)); } /* Initialize the final level storage. */ { - /* Initialize the verification storage. */ - { - fs::SubStorage buffer_storage(std::addressof(m_buffer_storages[level]), 0, info.info[level].size); - fs::HashSalt mac; - crypto::GenerateHmacSha256(mac.value, sizeof(mac), info.seed.value, sizeof(info.seed), KeyArray[level + 1].key, KeyArray[level + 1].size); - m_verify_storages[level + 1].Initialize(buffer_storage, storage[level + 2], static_cast(1) << info.info[level + 1].block_order, static_cast(1) << info.info[level].block_order, m_buffers->buffers[m_max_layers - 2], hgf, mac, true, storage_type); + /* If hash salt is enabled, generate it. */ + util::optional hash_salt = util::nullopt; + if (hash_salt_enabled) { + hash_salt.emplace(); + crypto::GenerateHmacSha256(hash_salt->value, sizeof(hash_salt->value), info.seed.value, sizeof(info.seed), KeyArray[level + 1].key, KeyArray[level + 1].size); } + /* Initialize the verification storage. */ + fs::SubStorage buffer_storage(std::addressof(m_buffer_storages[level]), 0, info.info[level].size); + m_verify_storages[level + 1].Initialize(buffer_storage, storage[level + 2], static_cast(1) << info.info[level + 1].block_order, static_cast(1) << info.info[level].block_order, m_buffers->buffers[m_max_layers - 2], hgf, hash_salt, true, is_writable, allow_cleared_blocks); + /* Initialize the buffer storage. */ - R_TRY(m_buffer_storages[level + 1].Initialize(m_buffers->buffers[level + 1], m_mutex, std::addressof(m_verify_storages[level + 1]), info.info[level + 1].size, static_cast(1) << info.info[level + 1].block_order, max_data_cache_entry_count, true, 0x11 + static_cast(level), true, storage_type)); + R_TRY(m_buffer_storages[level + 1].Initialize(m_buffers->buffers[level + 1], m_mutex, std::addressof(m_verify_storages[level + 1]), info.info[level + 1].size, static_cast(1) << info.info[level + 1].block_order, max_data_cache_entries, true, buffer_level, true, is_writable)); } /* Set the data size. */ m_data_size = info.info[level + 1].size; /* We succeeded. */ - level_guard.Cancel(); - top_buffer_guard.Cancel(); - top_verif_guard.Cancel(); - return ResultSuccess(); + R_SUCCEED(); } void HierarchicalIntegrityVerificationStorage::Finalize() { @@ -260,7 +259,11 @@ namespace ams::fssystem { /* Validate arguments. */ R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); - /* Acquire access to the read semaphore. */ + /* If we have a read semaphore, acquire it. */ + if (m_read_semaphore != nullptr) { m_read_semaphore->Acquire(); } + ON_SCOPE_EXIT { if (m_read_semaphore != nullptr) { m_read_semaphore->Release(); } }; + + /* Acquire access to the global read semaphore. */ if (!g_read_semaphore.TimedAcquire(AccessTimeout)) { for (auto level = m_max_layers - 2; level >= 0; --level) { R_TRY(m_buffer_storages[level].Flush()); @@ -272,8 +275,7 @@ namespace ams::fssystem { ON_SCOPE_EXIT { g_read_semaphore.Release(); }; /* Read the data. */ - R_TRY(m_buffer_storages[m_max_layers - 2].Read(offset, buffer, size)); - return ResultSuccess(); + R_RETURN(m_buffer_storages[m_max_layers - 2].Read(offset, buffer, size)); } Result HierarchicalIntegrityVerificationStorage::Write(s64 offset, const void *buffer, size_t size) { @@ -286,6 +288,10 @@ namespace ams::fssystem { /* Validate arguments. */ R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + /* If we have a write semaphore, acquire it. */ + if (m_write_semaphore != nullptr) { m_write_semaphore->Acquire(); } + ON_SCOPE_EXIT { if (m_write_semaphore != nullptr) { m_write_semaphore->Release(); } }; + /* Acquire access to the write semaphore. */ if (!g_write_semaphore.TimedAcquire(AccessTimeout)) { for (auto level = m_max_layers - 2; level >= 0; --level) { @@ -298,20 +304,18 @@ namespace ams::fssystem { ON_SCOPE_EXIT { g_write_semaphore.Release(); }; /* Write the data. */ - R_TRY(m_buffer_storages[m_max_layers - 2].Write(offset, buffer, size)); - m_is_written_for_rollback = true; - return ResultSuccess(); + R_RETURN(m_buffer_storages[m_max_layers - 2].Write(offset, buffer, size)); } Result HierarchicalIntegrityVerificationStorage::GetSize(s64 *out) { AMS_ASSERT(out != nullptr); AMS_ASSERT(m_data_size >= 0); *out = m_data_size; - return ResultSuccess(); + R_SUCCEED(); } Result HierarchicalIntegrityVerificationStorage::Flush() { - return ResultSuccess(); + R_SUCCEED(); } Result HierarchicalIntegrityVerificationStorage::OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) { @@ -320,17 +324,16 @@ namespace ams::fssystem { case fs::OperationId::DestroySignature: { R_TRY(m_buffer_storages[m_max_layers - 2].OperateRange(dst, dst_size, op_id, offset, size, src, src_size)); - m_is_written_for_rollback = true; - return ResultSuccess(); + R_SUCCEED(); } case fs::OperationId::Invalidate: case fs::OperationId::QueryRange: { R_TRY(m_buffer_storages[m_max_layers - 2].OperateRange(dst, dst_size, op_id, offset, size, src, src_size)); - return ResultSuccess(); + R_SUCCEED(); } default: - return fs::ResultUnsupportedOperateRangeForHierarchicalIntegrityVerificationStorage(); + R_THROW(fs::ResultUnsupportedOperateRangeForHierarchicalIntegrityVerificationStorage()); } } @@ -338,15 +341,14 @@ namespace ams::fssystem { for (s32 level = m_max_layers - 2; level >= 0; --level) { R_TRY(m_buffer_storages[level].Commit()); } - return ResultSuccess(); + R_SUCCEED(); } Result HierarchicalIntegrityVerificationStorage::OnRollback() { for (s32 level = m_max_layers - 2; level >= 0; --level) { R_TRY(m_buffer_storages[level].OnRollback()); } - m_is_written_for_rollback = false; - return ResultSuccess(); + R_SUCCEED(); } } diff --git a/libraries/libstratosphere/source/fssystem/fssystem_integrity_romfs_storage.cpp b/libraries/libstratosphere/source/fssystem/fssystem_integrity_romfs_storage.cpp index 3010146fe..a3b2a2857 100644 --- a/libraries/libstratosphere/source/fssystem/fssystem_integrity_romfs_storage.cpp +++ b/libraries/libstratosphere/source/fssystem/fssystem_integrity_romfs_storage.cpp @@ -17,7 +17,7 @@ namespace ams::fssystem { - Result IntegrityRomFsStorage::Initialize(HierarchicalIntegrityVerificationInformation level_hash_info, Hash master_hash, HierarchicalIntegrityVerificationStorage::HierarchicalStorageInformation storage_info, fs::IBufferManager *bm, IHash256GeneratorFactory *hgf) { + Result IntegrityRomFsStorage::Initialize(HierarchicalIntegrityVerificationInformation level_hash_info, Hash master_hash, HierarchicalIntegrityVerificationStorage::HierarchicalStorageInformation storage_info, fs::IBufferManager *bm, int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level, IHash256GeneratorFactory *hgf) { /* Validate preconditions. */ AMS_ASSERT(bm != nullptr); @@ -35,7 +35,7 @@ namespace ams::fssystem { } /* Initialize our integrity storage. */ - return m_integrity_storage.Initialize(level_hash_info, storage_info, std::addressof(m_buffers), hgf, std::addressof(m_mutex), fs::StorageType_RomFs); + R_RETURN(m_integrity_storage.Initialize(level_hash_info, storage_info, std::addressof(m_buffers), hgf, false, std::addressof(m_mutex), max_data_cache_entries, max_hash_cache_entries, buffer_level, false, false)); } void IntegrityRomFsStorage::Finalize() { diff --git a/libraries/libstratosphere/source/fssystem/fssystem_integrity_verification_storage.cpp b/libraries/libstratosphere/source/fssystem/fssystem_integrity_verification_storage.cpp index e60a108b7..385aeabfa 100644 --- a/libraries/libstratosphere/source/fssystem/fssystem_integrity_verification_storage.cpp +++ b/libraries/libstratosphere/source/fssystem/fssystem_integrity_verification_storage.cpp @@ -17,7 +17,7 @@ namespace ams::fssystem { - Result IntegrityVerificationStorage::Initialize(fs::SubStorage hs, fs::SubStorage ds, s64 verif_block_size, s64 upper_layer_verif_block_size, fs::IBufferManager *bm, fssystem::IHash256GeneratorFactory *hgf, const fs::HashSalt &salt, bool is_real_data, fs::StorageType storage_type) { + Result IntegrityVerificationStorage::Initialize(fs::SubStorage hs, fs::SubStorage ds, s64 verif_block_size, s64 upper_layer_verif_block_size, fs::IBufferManager *bm, fssystem::IHash256GeneratorFactory *hgf, const util::optional &salt, bool is_real_data, bool is_writable, bool allow_cleared_blocks) { /* Validate preconditions. */ AMS_ASSERT(verif_block_size >= HashSize); AMS_ASSERT(bm != nullptr); @@ -49,18 +49,19 @@ namespace ams::fssystem { s64 hash_size = 0; s64 data_size = 0; AMS_ASSERT(R_SUCCEEDED(m_hash_storage.GetSize(std::addressof(hash_size)))); - AMS_ASSERT(R_SUCCEEDED(m_data_storage.GetSize(std::addressof(hash_size)))); + AMS_ASSERT(R_SUCCEEDED(m_data_storage.GetSize(std::addressof(data_size)))); AMS_ASSERT(((hash_size / HashSize) * m_verification_block_size) >= data_size); AMS_UNUSED(hash_size, data_size); } /* Set salt. */ - std::memcpy(m_salt.value, salt.value, fs::HashSalt::Size); + m_salt = salt; - /* Set data and storage type. */ - m_is_real_data = is_real_data; - m_storage_type = storage_type; - return ResultSuccess(); + /* Set data, writable, and allow cleared. */ + m_is_real_data = is_real_data; + m_is_writable = is_writable; + m_allow_cleared_blocks = allow_cleared_blocks; + R_SUCCEED(); } void IntegrityVerificationStorage::Finalize() { @@ -146,7 +147,7 @@ namespace ams::fssystem { std::memset(cur_buf, 0, m_verification_block_size); /* Set the result if we should. */ - if (!fs::ResultClearedRealDataVerificationFailed::Includes(cur_result) && m_storage_type != fs::StorageType_Authoring) { + if (!fs::ResultClearedRealDataVerificationFailed::Includes(cur_result) && !m_allow_cleared_blocks) { verify_hash_result = cur_result; } @@ -157,14 +158,14 @@ namespace ams::fssystem { /* If we failed, clear and return. */ if (R_FAILED(cur_result)) { std::memset(buffer, 0, size); - return cur_result; + R_THROW(cur_result); } /* Advance. */ verified_count += cur_count; } - return verify_hash_result; + R_RETURN(verify_hash_result); } Result IntegrityVerificationStorage::Write(s64 offset, const void *buffer, size_t size) { @@ -246,30 +247,32 @@ namespace ams::fssystem { /* Write the data. */ R_TRY(m_data_storage.Write(offset, buffer, std::min(write_size, updated_count << m_verification_block_order))); - return update_result; + R_RETURN(update_result); } Result IntegrityVerificationStorage::GetSize(s64 *out) { - return m_data_storage.GetSize(out); + R_RETURN(m_data_storage.GetSize(out)); } Result IntegrityVerificationStorage::Flush() { /* Flush both storages. */ R_TRY(m_hash_storage.Flush()); R_TRY(m_data_storage.Flush()); - return ResultSuccess(); + R_SUCCEED(); } Result IntegrityVerificationStorage::OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) { /* Validate preconditions. */ - AMS_ASSERT(util::IsAligned(offset, static_cast(m_verification_block_size))); - AMS_ASSERT(util::IsAligned(size, static_cast(m_verification_block_size))); + if (op_id != fs::OperationId::Invalidate) { + AMS_ASSERT(util::IsAligned(offset, static_cast(m_verification_block_size))); + AMS_ASSERT(util::IsAligned(size, static_cast(m_verification_block_size))); + } switch (op_id) { case fs::OperationId::FillZero: { - /* Clear should only be called for save data. */ - AMS_ASSERT(m_storage_type == fs::StorageType_SaveData); + /* FillZero should only be called for writable storages. */ + AMS_ASSERT(m_is_writable); /* Validate the range. */ s64 data_size = 0; @@ -297,12 +300,12 @@ namespace ams::fssystem { remaining_size -= cur_size; } - return ResultSuccess(); + R_SUCCEED(); } case fs::OperationId::DestroySignature: { - /* Clear Signature should only be called for save data. */ - AMS_ASSERT(m_storage_type == fs::StorageType_SaveData); + /* DestroySignature should only be called for save data. */ + AMS_ASSERT(m_is_writable); /* Validate the range. */ s64 data_size = 0; @@ -327,19 +330,19 @@ namespace ams::fssystem { } /* Write the cleared signature. */ - return m_hash_storage.Write(sign_offset, buf.get(), sign_size); + R_RETURN(m_hash_storage.Write(sign_offset, buf.get(), sign_size)); } case fs::OperationId::Invalidate: { - /* Only allow cache invalidation for RomFs. */ - R_UNLESS(m_storage_type != fs::StorageType_SaveData, fs::ResultUnsupportedOperateRangeForNonSaveDataIntegrityVerificationStorage()); + /* Only allow cache invalidation read-only storages. */ + R_UNLESS(!m_is_writable, fs::ResultUnsupportedOperateRangeForWritableIntegrityVerificationStorage()); /* Operate on our storages. */ - R_TRY(m_hash_storage.OperateRange(dst, dst_size, op_id, 0, std::numeric_limits::max(), src, src_size)); - R_TRY(m_data_storage.OperateRange(dst, dst_size, op_id, offset, size, src, src_size)); + R_TRY(m_hash_storage.OperateRange(op_id, 0, std::numeric_limits::max())); + R_TRY(m_data_storage.OperateRange(op_id, offset, size)); - return ResultSuccess(); + R_SUCCEED(); } case fs::OperationId::QueryRange: { @@ -352,31 +355,37 @@ namespace ams::fssystem { const auto actual_size = std::min(size, data_size - offset); /* Query the data storage. */ - R_TRY(m_data_storage.OperateRange(dst, dst_size, op_id, offset, actual_size, src, src_size)); - - return ResultSuccess(); + R_RETURN(m_data_storage.OperateRange(dst, dst_size, op_id, offset, actual_size, src, src_size)); } default: - return fs::ResultUnsupportedOperateRangeForIntegrityVerificationStorage(); + R_THROW(fs::ResultUnsupportedOperateRangeForIntegrityVerificationStorage()); } } void IntegrityVerificationStorage::CalcBlockHash(BlockHash *out, const void *buffer, size_t block_size, std::unique_ptr &generator) const { - /* Initialize the generator. */ - generator->Initialize(); + /* Hash procedure depends on whether or not we're writable. */ + if (m_is_writable) { + /* Compute the hash with or without the hash salt, if we have one. */ + if (m_salt.has_value()) { + /* Initialize the generator. */ + generator->Initialize(); - /* If calculating for save data, hash the salt. */ - if (m_storage_type == fs::StorageType_SaveData) { - generator->Update(m_salt.value, sizeof(m_salt)); - } + /* Hash the salt. */ + generator->Update(m_salt->value, sizeof(m_salt->value)); - /* Update with the buffer and get the hash. */ - generator->Update(buffer, block_size); - generator->GetHash(out, sizeof(*out)); + /* Update with the buffer and get the hash. */ + generator->Update(buffer, block_size); + generator->GetHash(out, sizeof(*out)); + } else { + /* If we have no hash salt, just calculate the hash. */ + m_hash_generator_factory->GenerateHash(out, sizeof(*out), buffer, block_size); + } - /* Set the validation bit, if the hash is for save data. */ - if (m_storage_type == fs::StorageType_SaveData) { + /* Set the validation bit. */ SetValidationBit(out); + } else { + /* If we're not writable, just calculate the hash. */ + m_hash_generator_factory->GenerateHash(out, sizeof(*out), buffer, block_size); } } @@ -407,7 +416,7 @@ namespace ams::fssystem { /* We succeeded. */ clear_guard.Cancel(); - return ResultSuccess(); + R_SUCCEED(); } Result IntegrityVerificationStorage::WriteBlockSignature(const void *src, size_t src_size, s64 offset, size_t size) { @@ -425,7 +434,7 @@ namespace ams::fssystem { R_TRY(m_hash_storage.Write(sign_offset, src, sign_size)); /* We succeeded. */ - return ResultSuccess(); + R_SUCCEED(); } Result IntegrityVerificationStorage::VerifyHash(const void *buf, BlockHash *hash, std::unique_ptr &generator) { @@ -436,8 +445,8 @@ namespace ams::fssystem { /* Get the comparison hash. */ auto &cmp_hash = *hash; - /* If save data, check if the data is uninitialized. */ - if (m_storage_type == fs::StorageType_SaveData) { + /* If writable, check if the data is uninitialized. */ + if (m_is_writable) { bool is_cleared = false; R_TRY(this->IsCleared(std::addressof(is_cleared), cmp_hash)); R_UNLESS(!is_cleared, fs::ResultClearedRealDataVerificationFailed()); @@ -454,19 +463,19 @@ namespace ams::fssystem { /* Return the appropriate result. */ if (m_is_real_data) { - return fs::ResultUnclearedRealDataVerificationFailed(); + R_THROW(fs::ResultUnclearedRealDataVerificationFailed()); } else { - return fs::ResultNonRealDataVerificationFailed(); + R_THROW(fs::ResultNonRealDataVerificationFailed()); } } - return ResultSuccess(); + R_SUCCEED(); } Result IntegrityVerificationStorage::IsCleared(bool *is_cleared, const BlockHash &hash) { /* Validate preconditions. */ AMS_ASSERT(is_cleared != nullptr); - AMS_ASSERT(m_storage_type == fs::StorageType_SaveData); + AMS_ASSERT(m_is_writable); /* Default to uncleared. */ *is_cleared = false; @@ -481,7 +490,7 @@ namespace ams::fssystem { /* Set cleared. */ *is_cleared = true; - return ResultSuccess(); + R_SUCCEED(); } } diff --git a/libraries/libstratosphere/source/fssystem/fssystem_nca_file_system_driver.cpp b/libraries/libstratosphere/source/fssystem/fssystem_nca_file_system_driver.cpp index f55d1d753..9864f67d9 100644 --- a/libraries/libstratosphere/source/fssystem/fssystem_nca_file_system_driver.cpp +++ b/libraries/libstratosphere/source/fssystem/fssystem_nca_file_system_driver.cpp @@ -31,6 +31,12 @@ namespace ams::fssystem { constexpr inline s32 SparseTableCacheBlockSize = SparseStorage::NodeSize; constexpr inline s32 SparseTableCacheCount = 4; + constexpr inline s32 IntegrityDataCacheCount = 24; + constexpr inline s32 IntegrityHashCacheCount = 8; + + constexpr inline s32 IntegrityDataCacheCountForMeta = 16; + constexpr inline s32 IntegrityHashCacheCountForMeta = 2; + //TODO: Better names for these? //constexpr inline s32 CompressedDataBlockSize = 64_KB; //constexpr inline s32 CompressedContinuousReadingSizeMax = 640_KB; @@ -56,21 +62,21 @@ namespace ams::fssystem { AMS_ASSERT(m_storage != nullptr); /* Read from the base storage. */ - return m_storage->Read(offset, buffer, size); + R_RETURN(m_storage->Read(offset, buffer, size)); } virtual Result GetSize(s64 *out) override { /* Validate pre-conditions. */ AMS_ASSERT(m_storage != nullptr); - return m_storage->GetSize(out); + R_RETURN(m_storage->GetSize(out)); } virtual Result Flush() override { /* Validate pre-conditions. */ AMS_ASSERT(m_storage != nullptr); - return m_storage->Flush(); + R_RETURN(m_storage->Flush()); } virtual Result Write(s64 offset, const void *buffer, size_t size) override { @@ -78,200 +84,21 @@ namespace ams::fssystem { AMS_ASSERT(m_storage != nullptr); /* Read from the base storage. */ - return m_storage->Write(offset, buffer, size); + R_RETURN(m_storage->Write(offset, buffer, size)); } virtual Result SetSize(s64 size) override { /* Validate pre-conditions. */ AMS_ASSERT(m_storage != nullptr); - return m_storage->SetSize(size); + R_RETURN(m_storage->SetSize(size)); } virtual Result OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override { /* Validate pre-conditions. */ AMS_ASSERT(m_storage != nullptr); - return m_storage->OperateRange(dst, dst_size, op_id, offset, size, src, src_size); - } - }; - - class AesCtrStorageExternal : public ::ams::fs::IStorage, public ::ams::fs::impl::Newable { - NON_COPYABLE(AesCtrStorageExternal); - NON_MOVEABLE(AesCtrStorageExternal); - public: - static constexpr size_t BlockSize = crypto::Aes128CtrEncryptor::BlockSize; - static constexpr size_t KeySize = crypto::Aes128CtrEncryptor::KeySize; - static constexpr size_t IvSize = crypto::Aes128CtrEncryptor::IvSize; - private: - std::shared_ptr m_base_storage; - u8 m_iv[IvSize]; - DecryptAesCtrFunction m_decrypt_function; - s32 m_key_index; - u8 m_encrypted_key[KeySize]; - public: - AesCtrStorageExternal(std::shared_ptr bs, const void *enc_key, size_t enc_key_size, const void *iv, size_t iv_size, DecryptAesCtrFunction df, s32 kidx) : m_base_storage(std::move(bs)), m_decrypt_function(df), m_key_index(kidx) { - AMS_ASSERT(m_base_storage != nullptr); - AMS_ASSERT(enc_key_size == KeySize); - AMS_ASSERT(iv != nullptr); - AMS_ASSERT(iv_size == IvSize); - AMS_UNUSED(iv_size); - - std::memcpy(m_iv, iv, IvSize); - std::memcpy(m_encrypted_key, enc_key, enc_key_size); - } - - virtual Result Read(s64 offset, void *buffer, size_t size) override { - /* Allow zero size. */ - R_SUCCEED_IF(size == 0); - - /* Validate arguments. */ - /* NOTE: For some reason, Nintendo uses InvalidArgument instead of InvalidOffset/InvalidSize here. */ - R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); - R_UNLESS(util::IsAligned(offset, BlockSize), fs::ResultInvalidArgument()); - R_UNLESS(util::IsAligned(size, BlockSize), fs::ResultInvalidArgument()); - - /* Read the data. */ - R_TRY(m_base_storage->Read(offset, buffer, size)); - - /* Temporarily increase our thread priority. */ - ScopedThreadPriorityChanger cp(+1, ScopedThreadPriorityChanger::Mode::Relative); - - /* Allocate a pooled buffer for decryption. */ - PooledBuffer pooled_buffer; - pooled_buffer.AllocateParticularlyLarge(size, BlockSize); - AMS_ASSERT(pooled_buffer.GetSize() >= BlockSize); - - /* Setup the counter. */ - u8 ctr[IvSize]; - std::memcpy(ctr, m_iv, IvSize); - AddCounter(ctr, IvSize, offset / BlockSize); - - /* Setup tracking. */ - size_t remaining_size = size; - s64 cur_offset = 0; - - while (remaining_size > 0) { - /* Get the current size to process. */ - size_t cur_size = std::min(pooled_buffer.GetSize(), remaining_size); - char *dst = static_cast(buffer) + cur_offset; - - /* Decrypt into the temporary buffer */ - m_decrypt_function(pooled_buffer.GetBuffer(), cur_size, m_key_index, m_encrypted_key, KeySize, ctr, IvSize, dst, cur_size); - - /* Copy to the destination. */ - std::memcpy(dst, pooled_buffer.GetBuffer(), cur_size); - - /* Update tracking. */ - cur_offset += cur_size; - remaining_size -= cur_size; - - if (remaining_size > 0) { - AddCounter(ctr, IvSize, cur_size / BlockSize); - } - } - - return ResultSuccess(); - } - virtual Result OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override { - switch (op_id) { - case fs::OperationId::QueryRange: - { - /* Validate that we have an output range info. */ - R_UNLESS(dst != nullptr, fs::ResultNullptrArgument()); - R_UNLESS(dst_size == sizeof(fs::QueryRangeInfo), fs::ResultInvalidSize()); - - /* Operate on our base storage. */ - R_TRY(m_base_storage->OperateRange(dst, dst_size, op_id, offset, size, src, src_size)); - - /* Add in new flags. */ - fs::QueryRangeInfo new_info; - new_info.Clear(); - new_info.aes_ctr_key_type = static_cast(m_key_index >= 0 ? fs::AesCtrKeyTypeFlag::InternalKeyForHardwareAes : fs::AesCtrKeyTypeFlag::ExternalKeyForHardwareAes); - - /* Merge the new info in. */ - reinterpret_cast(dst)->Merge(new_info); - return ResultSuccess(); - } - default: - { - /* Operate on our base storage. */ - R_TRY(m_base_storage->OperateRange(dst, dst_size, op_id, offset, size, src, src_size)); - return ResultSuccess(); - } - } - } - - virtual Result GetSize(s64 *out) override { - return m_base_storage->GetSize(out); - } - - virtual Result Flush() override { - return ResultSuccess(); - } - - virtual Result Write(s64 offset, const void *buffer, size_t size) override { - AMS_UNUSED(offset, buffer, size); - return fs::ResultUnsupportedWriteForAesCtrStorageExternal(); - } - - virtual Result SetSize(s64 size) override { - AMS_UNUSED(size); - return fs::ResultUnsupportedSetSizeForAesCtrStorageExternal(); - } - }; - - template - class SwitchStorage : public ::ams::fs::IStorage, public ::ams::fs::impl::Newable { - NON_COPYABLE(SwitchStorage); - NON_MOVEABLE(SwitchStorage); - private: - std::shared_ptr m_true_storage; - std::shared_ptr m_false_storage; - F m_truth_function; - private: - ALWAYS_INLINE std::shared_ptr &SelectStorage() { - return m_truth_function() ? m_true_storage : m_false_storage; - } - public: - SwitchStorage(std::shared_ptr t, std::shared_ptr f, F func) : m_true_storage(std::move(t)), m_false_storage(std::move(f)), m_truth_function(func) { /* ... */ } - - virtual Result Read(s64 offset, void *buffer, size_t size) override { - return this->SelectStorage()->Read(offset, buffer, size); - } - - virtual Result OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override { - switch (op_id) { - case fs::OperationId::Invalidate: - { - R_TRY(m_true_storage->OperateRange(dst, dst_size, op_id, offset, size, src, src_size)); - R_TRY(m_false_storage->OperateRange(dst, dst_size, op_id, offset, size, src, src_size)); - return ResultSuccess(); - } - case fs::OperationId::QueryRange: - { - R_TRY(this->SelectStorage()->OperateRange(dst, dst_size, op_id, offset, size, src, src_size)); - return ResultSuccess(); - } - default: - return fs::ResultUnsupportedOperateRangeForSwitchStorage(); - } - } - - virtual Result GetSize(s64 *out) override { - return this->SelectStorage()->GetSize(out); - } - - virtual Result Flush() override { - return this->SelectStorage()->Flush(); - } - - virtual Result Write(s64 offset, const void *buffer, size_t size) override { - return this->SelectStorage()->Write(offset, buffer, size); - } - - virtual Result SetSize(s64 size) override { - return this->SelectStorage()->SetSize(size); + R_RETURN(m_storage->OperateRange(dst, dst_size, op_id, offset, size, src, src_size)); } }; @@ -307,7 +134,7 @@ namespace ams::fssystem { R_UNLESS(*out_splitter != nullptr, fs::ResultAllocationMemoryFailedAllocateShared()); } - return ResultSuccess(); + R_SUCCEED(); } Result NcaFileSystemDriver::OpenStorageImpl(std::shared_ptr *out, NcaFsHeaderReader *out_header_reader, s32 fs_index, StorageContext *ctx) { @@ -328,8 +155,17 @@ namespace ams::fssystem { /* Process sparse layer. */ s64 fs_data_offset = 0; if (out_header_reader->ExistsSparseLayer()) { - /* Create the sparse storage. */ - R_TRY(this->CreateSparseStorage(std::addressof(storage), std::addressof(fs_data_offset), ctx != nullptr ? std::addressof(ctx->current_sparse_storage) : nullptr, ctx != nullptr ? std::addressof(ctx->sparse_storage_meta_storage) : nullptr, fs_index, out_header_reader->GetAesCtrUpperIv(), out_header_reader->GetSparseInfo())); + /* Get the sparse info. */ + const auto &sparse_info = out_header_reader->GetSparseInfo(); + + /* Create based on whether we have a meta hash layer. */ + if (out_header_reader->ExistsSparseMetaHashLayer()) { + /* Create the sparse storage with verification. */ + R_TRY(this->CreateSparseStorageWithVerification(std::addressof(storage), std::addressof(fs_data_offset), ctx != nullptr ? std::addressof(ctx->current_sparse_storage) : nullptr, ctx != nullptr ? std::addressof(ctx->sparse_storage_meta_storage) : nullptr, ctx != nullptr ? std::addressof(ctx->sparse_layer_info_storage) : nullptr, fs_index, out_header_reader->GetAesCtrUpperIv(), sparse_info, out_header_reader->GetSparseMetaDataHashDataInfo(), out_header_reader->GetSparseMetaHashType())); + } else { + /* Create the sparse storage. */ + R_TRY(this->CreateSparseStorage(std::addressof(storage), std::addressof(fs_data_offset), ctx != nullptr ? std::addressof(ctx->current_sparse_storage) : nullptr, ctx != nullptr ? std::addressof(ctx->sparse_storage_meta_storage) : nullptr, fs_index, out_header_reader->GetAesCtrUpperIv(), sparse_info)); + } } else { /* Get the data offsets. */ fs_data_offset = GetFsOffset(*m_reader, fs_index); @@ -350,13 +186,28 @@ namespace ams::fssystem { /* Process patch layer. */ const auto &patch_info = out_header_reader->GetPatchInfo(); + std::shared_ptr patch_meta_aes_ctr_ex_meta_storage; + std::shared_ptr patch_meta_indirect_meta_storage; + if (out_header_reader->ExistsPatchMetaHashLayer()) { + /* Check the meta hash type. */ + R_UNLESS(out_header_reader->GetPatchMetaHashType() == NcaFsHeader::MetaDataHashType::HierarchicalIntegrity, fs::ResultRomNcaInvalidPatchMetaDataHashType()); + + /* Create the patch meta storage. */ + R_TRY(this->CreatePatchMetaStorage(std::addressof(patch_meta_aes_ctr_ex_meta_storage), std::addressof(patch_meta_indirect_meta_storage), ctx != nullptr ? std::addressof(ctx->patch_layer_info_storage) : nullptr, storage, fs_data_offset, out_header_reader->GetAesCtrUpperIv(), patch_info, out_header_reader->GetPatchMetaDataHashDataInfo(), m_hash_generator_factory_selector->GetFactory(fssystem::HashAlgorithmType_Sha2))); + } + if (patch_info.HasAesCtrExTable()) { /* Check the encryption type. */ - AMS_ASSERT(out_header_reader->GetEncryptionType() == NcaFsHeader::EncryptionType::AesCtrEx); + AMS_ASSERT(out_header_reader->GetEncryptionType() == NcaFsHeader::EncryptionType::None || out_header_reader->GetEncryptionType() == NcaFsHeader::EncryptionType::AesCtrEx || out_header_reader->GetEncryptionType() == NcaFsHeader::EncryptionType::AesCtrExSkipLayerHash); /* Create the ex meta storage. */ - std::shared_ptr aes_ctr_ex_storage_meta_storage; - R_TRY(this->CreateAesCtrExMetaStorage(std::addressof(aes_ctr_ex_storage_meta_storage), storage, fs_data_offset, out_header_reader->GetAesCtrUpperIv(), patch_info)); + std::shared_ptr aes_ctr_ex_storage_meta_storage = patch_meta_aes_ctr_ex_meta_storage; + if (aes_ctr_ex_storage_meta_storage == nullptr) { + /* If we don't have a meta storage, we must not have a patch meta hash layer. */ + AMS_ASSERT(!out_header_reader->ExistsPatchMetaHashLayer()); + + R_TRY(this->CreateAesCtrExStorageMetaStorage(std::addressof(aes_ctr_ex_storage_meta_storage), storage, fs_data_offset, out_header_reader->GetEncryptionType(), out_header_reader->GetAesCtrUpperIv(), patch_info)); + } /* Create the ex storage. */ std::shared_ptr aes_ctr_ex_storage; @@ -383,8 +234,18 @@ namespace ams::fssystem { case NcaFsHeader::EncryptionType::AesCtr: R_TRY(this->CreateAesCtrStorage(std::addressof(storage), std::move(storage), fs_data_offset, out_header_reader->GetAesCtrUpperIv(), AlignmentStorageRequirement_None)); break; + case NcaFsHeader::EncryptionType::AesCtrSkipLayerHash: + { + /* Create the aes ctr storage. */ + std::shared_ptr aes_ctr_storage; + R_TRY(this->CreateAesCtrStorage(std::addressof(aes_ctr_storage), storage, fs_data_offset, out_header_reader->GetAesCtrUpperIv(), AlignmentStorageRequirement_None)); + + /* Create region switch storage. */ + R_TRY(this->CreateRegionSwitchStorage(std::addressof(storage), out_header_reader, std::move(storage), std::move(aes_ctr_storage))); + } + break; default: - return fs::ResultInvalidNcaFsHeaderEncryptionType(); + R_THROW(fs::ResultInvalidNcaFsHeaderEncryptionType()); } /* Potentially save storages to our context. */ @@ -396,8 +257,13 @@ namespace ams::fssystem { /* Process indirect layer. */ if (patch_info.HasIndirectTable()) { /* Create the indirect meta storage */ - std::shared_ptr indirect_storage_meta_storage; - R_TRY(this->CreateIndirectStorageMetaStorage(std::addressof(indirect_storage_meta_storage), storage, patch_info)); + std::shared_ptr indirect_storage_meta_storage = patch_meta_indirect_meta_storage; + if (indirect_storage_meta_storage == nullptr) { + /* If we don't have a meta storage, we must not have a patch meta hash layer. */ + AMS_ASSERT(!out_header_reader->ExistsPatchMetaHashLayer()); + + R_TRY(this->CreateIndirectStorageMetaStorage(std::addressof(indirect_storage_meta_storage), storage, patch_info)); + } /* Potentially save the indirect meta storage to our context. */ if (ctx != nullptr) { @@ -436,7 +302,7 @@ namespace ams::fssystem { /* Check if we're sparse or requested to skip the integrity layer. */ if (out_header_reader->ExistsSparseLayer() || (ctx != nullptr && ctx->open_raw_storage)) { *out = std::move(storage); - return ResultSuccess(); + R_SUCCEED(); } /* Create the non-raw storage. */ @@ -456,7 +322,7 @@ namespace ams::fssystem { R_TRY(this->CreateIntegrityVerificationStorage(std::addressof(storage), std::move(storage), header_reader->GetHashData().integrity_meta_info, m_hash_generator_factory_selector->GetFactory(fssystem::HashAlgorithmType_Sha2))); break; default: - return fs::ResultInvalidNcaFsHeaderHashType(); + R_THROW(fs::ResultInvalidNcaFsHeaderHashType()); } /* Process compression layer. */ @@ -466,7 +332,7 @@ namespace ams::fssystem { /* Set output storage. */ *out = std::move(storage); - return ResultSuccess(); + R_SUCCEED(); } Result NcaFileSystemDriver::OpenIndirectableStorageAsOriginal(std::shared_ptr *out, const NcaFsHeaderReader *header_reader, StorageContext *ctx) { @@ -479,8 +345,17 @@ namespace ams::fssystem { /* Process sparse layer. */ s64 fs_data_offset = 0; if (header_reader->ExistsSparseLayer()) { - /* Create the sparse storage. */ - R_TRY(this->CreateSparseStorage(std::addressof(storage), std::addressof(fs_data_offset), ctx != nullptr ? std::addressof(ctx->original_sparse_storage) : nullptr, ctx != nullptr ? std::addressof(ctx->sparse_storage_meta_storage) : nullptr, fs_index, header_reader->GetAesCtrUpperIv(), header_reader->GetSparseInfo())); + /* Get the sparse info. */ + const auto &sparse_info = header_reader->GetSparseInfo(); + + /* Create based on whether we have a meta hash layer. */ + if (header_reader->ExistsSparseMetaHashLayer()) { + /* Create the sparse storage with verification. */ + R_TRY(this->CreateSparseStorageWithVerification(std::addressof(storage), std::addressof(fs_data_offset), ctx != nullptr ? std::addressof(ctx->original_sparse_storage) : nullptr, ctx != nullptr ? std::addressof(ctx->sparse_storage_meta_storage) : nullptr, ctx != nullptr ? std::addressof(ctx->sparse_layer_info_storage) : nullptr, fs_index, header_reader->GetAesCtrUpperIv(), sparse_info, header_reader->GetSparseMetaDataHashDataInfo(), header_reader->GetSparseMetaHashType())); + } else { + /* Create the sparse storage. */ + R_TRY(this->CreateSparseStorage(std::addressof(storage), std::addressof(fs_data_offset), ctx != nullptr ? std::addressof(ctx->original_sparse_storage) : nullptr, ctx != nullptr ? std::addressof(ctx->sparse_storage_meta_storage) : nullptr, fs_index, header_reader->GetAesCtrUpperIv(), sparse_info)); + } } else { /* Get the data offsets. */ fs_data_offset = GetFsOffset(*m_reader, fs_index); @@ -506,12 +381,12 @@ namespace ams::fssystem { R_TRY(this->CreateAesCtrStorage(std::addressof(storage), std::move(storage), fs_data_offset, header_reader->GetAesCtrUpperIv(), AlignmentStorageRequirement_CacheBlockSize)); break; default: - return fs::ResultInvalidNcaFsHeaderEncryptionType(); + R_THROW(fs::ResultInvalidNcaFsHeaderEncryptionType()); } /* Set output storage. */ *out = std::move(storage); - return ResultSuccess(); + R_SUCCEED(); } Result NcaFileSystemDriver::CreateBodySubStorage(std::shared_ptr *out, s64 offset, s64 size) { @@ -532,7 +407,7 @@ namespace ams::fssystem { /* Set the output storage. */ *out = std::move(body_substorage); - return ResultSuccess(); + R_SUCCEED(); } Result NcaFileSystemDriver::CreateAesCtrStorage(std::shared_ptr *out, std::shared_ptr base_storage, s64 offset, const NcaAesCtrUpperIv &upper_iv, AlignmentStorageRequirement alignment_storage_requirement) { @@ -575,7 +450,7 @@ namespace ams::fssystem { /* Create the ctr storage. */ std::shared_ptr aes_ctr_storage; if (m_reader->HasExternalDecryptionKey()) { - aes_ctr_storage = fssystem::AllocateShared(std::move(base_storage), m_reader->GetExternalDecryptionKey(), AesCtrStorageExternal::KeySize, iv, AesCtrStorageExternal::IvSize, m_reader->GetExternalDecryptAesCtrFunctionForExternalKey(), -1); + aes_ctr_storage = fssystem::AllocateShared(std::move(base_storage), m_reader->GetExternalDecryptionKey(), AesCtrStorageExternal::KeySize, iv, AesCtrStorageExternal::IvSize, m_reader->GetExternalDecryptAesCtrFunctionForExternalKey(), -1, -1); R_UNLESS(aes_ctr_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared()); } else { /* Create software decryption storage. */ @@ -584,7 +459,7 @@ namespace ams::fssystem { /* If we have a hardware key and should use it, make the hardware decryption storage. */ if (m_reader->HasInternalDecryptionKeyForAesHw() && !m_reader->IsSoftwareAesPrioritized()) { - auto hw_storage = fssystem::AllocateShared(base_storage, m_reader->GetDecryptionKey(NcaHeader::DecryptionKey_AesCtrHw), AesCtrStorageExternal::KeySize, iv, AesCtrStorageExternal::IvSize, m_reader->GetExternalDecryptAesCtrFunction(), GetKeyTypeValue(m_reader->GetKeyIndex(), m_reader->GetKeyGeneration())); + auto hw_storage = fssystem::AllocateShared(base_storage, m_reader->GetDecryptionKey(NcaHeader::DecryptionKey_AesCtrHw), AesCtrStorageExternal::KeySize, iv, AesCtrStorageExternal::IvSize, m_reader->GetExternalDecryptAesCtrFunction(), m_reader->GetKeyIndex(), m_reader->GetKeyGeneration()); R_UNLESS(hw_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared()); /* Create the selection storage. */ @@ -605,7 +480,7 @@ namespace ams::fssystem { /* Set the out storage. */ *out = std::move(aligned_storage); - return ResultSuccess(); + R_SUCCEED(); } Result NcaFileSystemDriver::CreateAesXtsStorage(std::shared_ptr *out, std::shared_ptr base_storage, s64 offset) { @@ -629,7 +504,7 @@ namespace ams::fssystem { /* Set the out storage. */ *out = std::move(aligned_storage); - return ResultSuccess(); + R_SUCCEED(); } Result NcaFileSystemDriver::CreateSparseStorageMetaStorage(std::shared_ptr *out, std::shared_ptr base_storage, s64 offset, const NcaAesCtrUpperIv &upper_iv, const NcaSparseInfo &sparse_info) { @@ -663,7 +538,7 @@ namespace ams::fssystem { /* Set the output. */ *out = std::move(meta_storage); - return ResultSuccess(); + R_SUCCEED(); } Result NcaFileSystemDriver::CreateSparseStorageCore(std::shared_ptr *out, std::shared_ptr base_storage, s64 base_size, std::shared_ptr meta_storage, const NcaSparseInfo &sparse_info, bool external_info) { @@ -700,7 +575,7 @@ namespace ams::fssystem { /* Set the output. */ *out = std::move(sparse_storage); - return ResultSuccess(); + R_SUCCEED(); } Result NcaFileSystemDriver::CreateSparseStorage(std::shared_ptr *out, s64 *out_fs_data_offset, std::shared_ptr *out_sparse_storage, std::shared_ptr *out_meta_storage, s32 index, const NcaAesCtrUpperIv &upper_iv, const NcaSparseInfo &sparse_info) { @@ -757,13 +632,125 @@ namespace ams::fssystem { /* Set the output storage. */ *out = std::move(sparse_storage); - return ResultSuccess(); + R_SUCCEED(); } - Result NcaFileSystemDriver::CreateAesCtrExMetaStorage(std::shared_ptr *out, std::shared_ptr base_storage, s64 offset, const NcaAesCtrUpperIv &upper_iv, const NcaPatchInfo &patch_info) { + Result NcaFileSystemDriver::CreateSparseStorageMetaStorageWithVerification(std::shared_ptr *out, std::shared_ptr *out_layer_info_storage, std::shared_ptr base_storage, s64 offset, const NcaAesCtrUpperIv &upper_iv, const NcaSparseInfo &sparse_info, const NcaMetaDataHashDataInfo &meta_data_hash_data_info, IHash256GeneratorFactory *hgf) { /* Validate preconditions. */ AMS_ASSERT(out != nullptr); AMS_ASSERT(base_storage != nullptr); + AMS_ASSERT(hgf != nullptr); + + /* Get the base storage size. */ + s64 base_size = 0; + R_TRY(base_storage->GetSize(std::addressof(base_size))); + + /* Get the meta extents. */ + const auto meta_offset = sparse_info.bucket.offset; + const auto meta_size = sparse_info.bucket.size; + R_UNLESS(meta_offset + meta_size - offset <= base_size, fs::ResultNcaBaseStorageOutOfRangeB()); + + /* Get the meta data hash data extents. */ + const s64 meta_data_hash_data_offset = meta_data_hash_data_info.offset; + const s64 meta_data_hash_data_size = util::AlignUp(meta_data_hash_data_info.size, NcaHeader::CtrBlockSize); + R_UNLESS(meta_data_hash_data_offset + meta_data_hash_data_size <= base_size, fs::ResultNcaBaseStorageOutOfRangeB()); + + /* Check that the meta is before the hash data. */ + R_UNLESS(meta_offset + meta_size <= meta_data_hash_data_offset, fs::ResultRomNcaInvalidSparseMetaDataHashDataOffset()); + + /* Check that offsets are appropriately aligned. */ + R_UNLESS(util::IsAligned(meta_data_hash_data_offset, NcaHeader::CtrBlockSize), fs::ResultRomNcaInvalidSparseMetaDataHashDataOffset()); + R_UNLESS(util::IsAligned(meta_offset, NcaHeader::CtrBlockSize), fs::ResultInvalidNcaFsHeader()); + + /* Create the meta storage. */ + auto enc_storage = fssystem::AllocateShared(std::move(base_storage), meta_offset, meta_data_hash_data_offset + meta_data_hash_data_size - meta_offset); + R_UNLESS(enc_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared()); + + /* Create the decrypted storage. */ + std::shared_ptr decrypted_storage; + R_TRY(this->CreateAesCtrStorage(std::addressof(decrypted_storage), std::move(enc_storage), offset + meta_offset, sparse_info.MakeAesCtrUpperIv(upper_iv), AlignmentStorageRequirement_None)); + + /* Create the verification storage. */ + std::shared_ptr integrity_storage; + R_TRY_CATCH(this->CreateIntegrityVerificationStorageForMeta(std::addressof(integrity_storage), out_layer_info_storage, std::move(decrypted_storage), meta_offset, meta_data_hash_data_info, hgf)) { + R_CONVERT(fs::ResultInvalidNcaMetaDataHashDataSize, fs::ResultRomNcaInvalidSparseMetaDataHashDataSize()) + R_CONVERT(fs::ResultInvalidNcaMetaDataHashDataHash, fs::ResultRomNcaInvalidSparseMetaDataHashDataHash()) + } R_END_TRY_CATCH; + + /* Create the meta storage. */ + auto meta_storage = fssystem::AllocateShared(std::move(integrity_storage), 0, meta_size); + R_UNLESS(meta_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared()); + + /* Set the output. */ + *out = std::move(meta_storage); + R_SUCCEED(); + } + + Result NcaFileSystemDriver::CreateSparseStorageWithVerification(std::shared_ptr *out, s64 *out_fs_data_offset, std::shared_ptr *out_sparse_storage, std::shared_ptr *out_meta_storage, std::shared_ptr *out_layer_info_storage, s32 index, const NcaAesCtrUpperIv &upper_iv, const NcaSparseInfo &sparse_info, const NcaMetaDataHashDataInfo &meta_data_hash_data_info, NcaFsHeader::MetaDataHashType meta_data_hash_type) { + /* Validate preconditions. */ + AMS_ASSERT(out != nullptr); + AMS_ASSERT(out_fs_data_offset != nullptr); + + /* Check the sparse info generation. */ + R_UNLESS(sparse_info.generation != 0, fs::ResultInvalidNcaHeader()); + + /* Read and verify the bucket tree header. */ + BucketTree::Header header; + std::memcpy(std::addressof(header), sparse_info.bucket.header, sizeof(header)); + R_TRY(header.Verify()); + + /* Determine the storage extents. */ + const auto fs_offset = GetFsOffset(*m_reader, index); + const auto fs_end_offset = GetFsEndOffset(*m_reader, index); + const auto fs_size = fs_end_offset - fs_offset; + + /* Create the sparse storage. */ + std::shared_ptr sparse_storage; + if (header.entry_count != 0) { + /* Create the body substorage. */ + std::shared_ptr body_substorage; + R_TRY(this->CreateBodySubStorage(std::addressof(body_substorage), sparse_info.physical_offset, util::AlignUp(static_cast(meta_data_hash_data_info.offset) + static_cast(meta_data_hash_data_info.size), NcaHeader::CtrBlockSize))); + + /* Check the meta data hash type. */ + R_UNLESS(meta_data_hash_type == NcaFsHeader::MetaDataHashType::HierarchicalIntegrity, fs::ResultRomNcaInvalidSparseMetaDataHashType()); + + /* Create the meta storage. */ + std::shared_ptr meta_storage; + R_TRY(this->CreateSparseStorageMetaStorageWithVerification(std::addressof(meta_storage), out_layer_info_storage, body_substorage, sparse_info.physical_offset, upper_iv, sparse_info, meta_data_hash_data_info, m_hash_generator_factory_selector->GetFactory(fssystem::HashAlgorithmType_Sha2))); + + /* Potentially set the output meta storage. */ + if (out_meta_storage != nullptr) { + *out_meta_storage = meta_storage; + } + + /* Create the sparse storage. */ + R_TRY(this->CreateSparseStorageCore(std::addressof(sparse_storage), body_substorage, sparse_info.GetPhysicalSize(), std::move(meta_storage), sparse_info, false)); + } else { + /* If there are no entries, there's nothing to actually do. */ + sparse_storage = fssystem::AllocateShared(); + R_UNLESS(sparse_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared()); + + sparse_storage->Initialize(fs_size); + } + + /* Potentially set the output sparse storage. */ + if (out_sparse_storage != nullptr) { + *out_sparse_storage = sparse_storage; + } + + /* Set the output fs data offset. */ + *out_fs_data_offset = fs_offset; + + /* Set the output storage. */ + *out = std::move(sparse_storage); + R_SUCCEED(); + } + + Result NcaFileSystemDriver::CreateAesCtrExStorageMetaStorage(std::shared_ptr *out, std::shared_ptr base_storage, s64 offset, NcaFsHeader::EncryptionType encryption_type, const NcaAesCtrUpperIv &upper_iv, const NcaPatchInfo &patch_info) { + /* Validate preconditions. */ + AMS_ASSERT(out != nullptr); + AMS_ASSERT(base_storage != nullptr); + AMS_ASSERT(encryption_type == NcaFsHeader::EncryptionType::None || encryption_type == NcaFsHeader::EncryptionType::AesCtrEx || encryption_type == NcaFsHeader::EncryptionType::AesCtrExSkipLayerHash); AMS_ASSERT(patch_info.HasAesCtrExTable()); /* Validate patch info extents. */ @@ -786,7 +773,12 @@ namespace ams::fssystem { /* Create the decrypted storage. */ std::shared_ptr decrypted_storage; - R_TRY(this->CreateAesCtrStorage(std::addressof(decrypted_storage), std::move(enc_storage), offset + meta_offset, upper_iv, AlignmentStorageRequirement_None)); + if (encryption_type != NcaFsHeader::EncryptionType::None) { + R_TRY(this->CreateAesCtrStorage(std::addressof(decrypted_storage), std::move(enc_storage), offset + meta_offset, upper_iv, AlignmentStorageRequirement_None)); + } else { + /* If encryption type is none, don't do any decryption. */ + decrypted_storage = std::move(enc_storage); + } /* Create meta storage. */ auto meta_storage = fssystem::AllocateShared(); @@ -802,7 +794,7 @@ namespace ams::fssystem { /* Set the output. */ *out = std::move(aligned_storage); - return ResultSuccess(); + R_SUCCEED(); } Result NcaFileSystemDriver::CreateAesCtrExStorage(std::shared_ptr *out, std::shared_ptr *out_ext, std::shared_ptr base_storage, std::shared_ptr meta_storage, s64 counter_offset, const NcaAesCtrUpperIv &upper_iv, const NcaPatchInfo &patch_info) { @@ -839,7 +831,7 @@ namespace ams::fssystem { if (m_reader->HasExternalDecryptionKey()) { /* Create the decryptor. */ std::unique_ptr decryptor; - R_TRY(AesCtrCounterExtendedStorage::CreateExternalDecryptor(std::addressof(decryptor), m_reader->GetExternalDecryptAesCtrFunctionForExternalKey(), -1)); + R_TRY(AesCtrCounterExtendedStorage::CreateExternalDecryptor(std::addressof(decryptor), m_reader->GetExternalDecryptAesCtrFunctionForExternalKey(), -1, -1)); /* Create the aes ctr ex storage. */ auto impl_storage = fssystem::AllocateShared(); @@ -876,7 +868,7 @@ namespace ams::fssystem { if (m_reader->HasInternalDecryptionKeyForAesHw() && !m_reader->IsSoftwareAesPrioritized()) { /* Create the hardware decryptor. */ std::unique_ptr hw_decryptor; - R_TRY(AesCtrCounterExtendedStorage::CreateExternalDecryptor(std::addressof(hw_decryptor), m_reader->GetExternalDecryptAesCtrFunction(), GetKeyTypeValue(m_reader->GetKeyIndex(), m_reader->GetKeyGeneration()))); + R_TRY(AesCtrCounterExtendedStorage::CreateExternalDecryptor(std::addressof(hw_decryptor), m_reader->GetExternalDecryptAesCtrFunction(), m_reader->GetKeyIndex(), m_reader->GetKeyGeneration())); /* Create the hardware storage. */ auto hw_storage = fssystem::AllocateShared(); @@ -904,7 +896,7 @@ namespace ams::fssystem { /* Set the output. */ *out = std::move(aligned_storage); - return ResultSuccess(); + R_SUCCEED(); } Result NcaFileSystemDriver::CreateIndirectStorageMetaStorage(std::shared_ptr *out, std::shared_ptr base_storage, const NcaPatchInfo &patch_info) { @@ -929,7 +921,7 @@ namespace ams::fssystem { /* Set the output. */ *out = std::move(meta_storage); - return ResultSuccess(); + R_SUCCEED(); } Result NcaFileSystemDriver::CreateIndirectStorage(std::shared_ptr *out, std::shared_ptr *out_ind, std::shared_ptr base_storage, std::shared_ptr original_data_storage, std::shared_ptr meta_storage, const NcaPatchInfo &patch_info) { @@ -985,7 +977,64 @@ namespace ams::fssystem { /* Set the output. */ *out = std::move(indirect_storage); - return ResultSuccess(); + R_SUCCEED(); + } + + Result NcaFileSystemDriver::CreatePatchMetaStorage(std::shared_ptr *out_aes_ctr_ex_meta, std::shared_ptr *out_indirect_meta, std::shared_ptr *out_layer_info_storage, std::shared_ptr base_storage, s64 offset, const NcaAesCtrUpperIv &upper_iv, const NcaPatchInfo &patch_info, const NcaMetaDataHashDataInfo &meta_data_hash_data_info, IHash256GeneratorFactory *hgf) { + /* Validate preconditions. */ + AMS_ASSERT(out_aes_ctr_ex_meta != nullptr); + AMS_ASSERT(out_indirect_meta != nullptr); + AMS_ASSERT(base_storage != nullptr); + AMS_ASSERT(patch_info.HasAesCtrExTable()); + AMS_ASSERT(patch_info.HasIndirectTable()); + AMS_ASSERT(util::IsAligned(patch_info.aes_ctr_ex_size, NcaHeader::XtsBlockSize)); + + /* Validate patch info extents. */ + R_UNLESS(patch_info.indirect_size > 0, fs::ResultInvalidNcaPatchInfoIndirectSize()); + R_UNLESS(patch_info.aes_ctr_ex_size >= 0, fs::ResultInvalidNcaPatchInfoAesCtrExSize()); + R_UNLESS(patch_info.indirect_size + patch_info.indirect_offset <= patch_info.aes_ctr_ex_offset, fs::ResultInvalidNcaPatchInfoAesCtrExOffset()); + R_UNLESS(patch_info.aes_ctr_ex_offset + patch_info.aes_ctr_ex_size <= meta_data_hash_data_info.offset, fs::ResultRomNcaInvalidPatchMetaDataHashDataOffset()); + + /* Get the base storage size. */ + s64 base_size; + R_TRY(base_storage->GetSize(std::addressof(base_size))); + + /* Check that extents remain within range. */ + R_UNLESS(patch_info.indirect_offset + patch_info.indirect_size <= base_size, fs::ResultNcaBaseStorageOutOfRangeE()); + R_UNLESS(patch_info.aes_ctr_ex_offset + patch_info.aes_ctr_ex_size <= base_size, fs::ResultNcaBaseStorageOutOfRangeB()); + + /* Check that metadata hash data extents remain within range. */ + const s64 meta_data_hash_data_offset = meta_data_hash_data_info.offset; + const s64 meta_data_hash_data_size = util::AlignUp(meta_data_hash_data_info.size, NcaHeader::CtrBlockSize); + R_UNLESS(meta_data_hash_data_offset + meta_data_hash_data_size <= base_size, fs::ResultNcaBaseStorageOutOfRangeB()); + + /* Create the encrypted storage. */ + auto enc_storage = fssystem::AllocateShared(std::move(base_storage), patch_info.indirect_offset, meta_data_hash_data_offset + meta_data_hash_data_size - patch_info.indirect_offset); + R_UNLESS(enc_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared()); + + /* Create the decrypted storage. */ + std::shared_ptr decrypted_storage; + R_TRY(this->CreateAesCtrStorage(std::addressof(decrypted_storage), std::move(enc_storage), offset + patch_info.indirect_offset, upper_iv, AlignmentStorageRequirement_None)); + + /* Create the verification storage. */ + std::shared_ptr integrity_storage; + R_TRY_CATCH(this->CreateIntegrityVerificationStorageForMeta(std::addressof(integrity_storage), out_layer_info_storage, std::move(decrypted_storage), patch_info.indirect_offset, meta_data_hash_data_info, hgf)) { + R_CONVERT(fs::ResultInvalidNcaMetaDataHashDataSize, fs::ResultRomNcaInvalidPatchMetaDataHashDataSize()) + R_CONVERT(fs::ResultInvalidNcaMetaDataHashDataHash, fs::ResultRomNcaInvalidPatchMetaDataHashDataHash()) + } R_END_TRY_CATCH; + + /* Create the indirect meta storage. */ + auto indirect_meta_storage = fssystem::AllocateShared(integrity_storage, patch_info.indirect_offset - patch_info.indirect_offset, patch_info.indirect_size); + R_UNLESS(indirect_meta_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared()); + + /* Create the aes ctr ex meta storage. */ + auto aes_ctr_ex_meta_storage = fssystem::AllocateShared(integrity_storage, patch_info.aes_ctr_ex_offset - patch_info.indirect_offset, patch_info.aes_ctr_ex_size); + R_UNLESS(aes_ctr_ex_meta_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared()); + + /* Set the output. */ + *out_aes_ctr_ex_meta = std::move(aes_ctr_ex_meta_storage); + *out_indirect_meta = std::move(indirect_meta_storage); + R_SUCCEED(); } Result NcaFileSystemDriver::CreateSha256Storage(std::shared_ptr *out, std::shared_ptr base_storage, const NcaFsHeader::HashData::HierarchicalSha256Data &hash_data, IHash256GeneratorFactory *hgf) { @@ -1052,13 +1101,50 @@ namespace ams::fssystem { /* Set the output. */ *out = std::move(aligned_storage); - return ResultSuccess(); + R_SUCCEED(); } Result NcaFileSystemDriver::CreateIntegrityVerificationStorage(std::shared_ptr *out, std::shared_ptr base_storage, const NcaFsHeader::HashData::IntegrityMetaInfo &meta_info, IHash256GeneratorFactory *hgf) { + R_RETURN(this->CreateIntegrityVerificationStorageImpl(out, base_storage, meta_info, 0, IntegrityDataCacheCount, IntegrityHashCacheCount, fssystem::HierarchicalIntegrityVerificationStorage::GetDefaultDataCacheBufferLevel(meta_info.level_hash_info.max_layers), hgf)); + } + + Result NcaFileSystemDriver::CreateIntegrityVerificationStorageForMeta(std::shared_ptr *out, std::shared_ptr *out_layer_info_storage, std::shared_ptr base_storage, s64 offset, const NcaMetaDataHashDataInfo &meta_data_hash_data_info, IHash256GeneratorFactory *hgf) { + /* Validate preconditions. */ + AMS_ASSERT(out != nullptr); + + /* Check the meta data hash data size. */ + R_UNLESS(meta_data_hash_data_info.size == sizeof(NcaMetaDataHashData), fs::ResultInvalidNcaMetaDataHashDataSize()); + + /* Read the meta data hash data. */ + NcaMetaDataHashData meta_data_hash_data; + R_TRY(base_storage->Read(meta_data_hash_data_info.offset - offset, std::addressof(meta_data_hash_data), sizeof(meta_data_hash_data))); + + /* Check the meta data hash data hash. */ + u8 meta_data_hash_data_hash[IHash256Generator::HashSize]; + m_hash_generator_factory_selector->GetFactory(fssystem::HashAlgorithmType_Sha2)->GenerateHash(meta_data_hash_data_hash, sizeof(meta_data_hash_data_hash), std::addressof(meta_data_hash_data), sizeof(meta_data_hash_data)); + R_UNLESS(crypto::IsSameBytes(meta_data_hash_data_hash, std::addressof(meta_data_hash_data_info.hash), sizeof(meta_data_hash_data_hash)), fs::ResultInvalidNcaMetaDataHashDataHash()); + + /* Set the out layer info storage, if necessary. */ + if (out_layer_info_storage != nullptr) { + auto layer_info_storage = fssystem::AllocateShared(base_storage, meta_data_hash_data.layer_info_offset - offset, meta_data_hash_data_info.offset + meta_data_hash_data_info.size - meta_data_hash_data.layer_info_offset); + R_UNLESS(layer_info_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared()); + + *out_layer_info_storage = std::move(layer_info_storage); + } + + /* Create the meta storage. */ + auto meta_storage = fssystem::AllocateShared(std::move(base_storage), 0, meta_data_hash_data_info.offset - offset); + R_UNLESS(meta_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared()); + + /* Create the integrity verification storage. */ + R_RETURN(this->CreateIntegrityVerificationStorageImpl(out, std::move(meta_storage), meta_data_hash_data.integrity_meta_info, meta_data_hash_data.layer_info_offset - offset, IntegrityDataCacheCountForMeta, IntegrityHashCacheCountForMeta, 0, hgf)); + } + + Result NcaFileSystemDriver::CreateIntegrityVerificationStorageImpl(std::shared_ptr *out, std::shared_ptr base_storage, const NcaFsHeader::HashData::IntegrityMetaInfo &meta_info, s64 layer_info_offset, int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level, IHash256GeneratorFactory *hgf) { /* Validate preconditions. */ AMS_ASSERT(out != nullptr); AMS_ASSERT(base_storage != nullptr); + AMS_ASSERT(layer_info_offset >= 0); /* Define storage types. */ using VerificationStorage = HierarchicalIntegrityVerificationStorage; @@ -1079,30 +1165,52 @@ namespace ams::fssystem { StorageInfo storage_info; for (s32 i = 0; i < static_cast(level_hash_info.max_layers - 2); ++i) { const auto &layer_info = level_hash_info.info[i]; - R_UNLESS(layer_info.offset + layer_info.size <= base_storage_size, fs::ResultNcaBaseStorageOutOfRangeD()); + R_UNLESS(layer_info_offset + layer_info.offset + layer_info.size <= base_storage_size, fs::ResultNcaBaseStorageOutOfRangeD()); - storage_info[i + 1] = fs::SubStorage(base_storage, layer_info.offset, layer_info.size); + storage_info[i + 1] = fs::SubStorage(base_storage, layer_info_offset + layer_info.offset, layer_info.size); } /* Set the last layer info. */ const auto &layer_info = level_hash_info.info[level_hash_info.max_layers - 2]; - R_UNLESS(layer_info.offset + layer_info.size <= base_storage_size, fs::ResultNcaBaseStorageOutOfRangeD()); - storage_info.SetDataStorage(fs::SubStorage(std::move(base_storage), layer_info.offset, layer_info.size)); + const s64 last_layer_info_offset = layer_info_offset > 0 ? 0 : layer_info.offset; + R_UNLESS(last_layer_info_offset + layer_info.size <= base_storage_size, fs::ResultNcaBaseStorageOutOfRangeD()); + if (layer_info_offset > 0) { + R_UNLESS(last_layer_info_offset + layer_info.size <= layer_info_offset, fs::ResultRomNcaInvalidIntegrityLayerInfoOffset()); + } + storage_info.SetDataStorage(fs::SubStorage(std::move(base_storage), last_layer_info_offset, layer_info.size)); /* Make the integrity romfs storage. */ auto integrity_storage = fssystem::AllocateShared(); R_UNLESS(integrity_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared()); /* Initialize the integrity storage. */ - R_TRY(integrity_storage->Initialize(level_hash_info, meta_info.master_hash, storage_info, m_buffer_manager, hgf)); + R_TRY(integrity_storage->Initialize(level_hash_info, meta_info.master_hash, storage_info, m_buffer_manager, max_data_cache_entries, max_hash_cache_entries, buffer_level, hgf)); /* Set the output. */ *out = std::move(integrity_storage); - return ResultSuccess(); + R_SUCCEED(); + } + + + Result NcaFileSystemDriver::CreateRegionSwitchStorage(std::shared_ptr *out, const NcaFsHeaderReader *header_reader, std::shared_ptr inside_storage, std::shared_ptr outside_storage) { + /* Check pre-conditions. */ + AMS_ASSERT(header_reader->GetHashType() == NcaFsHeader::HashType::HierarchicalIntegrityHash); + + /* Create the region. */ + fssystem::RegionSwitchStorage::Region region = {}; + R_TRY(header_reader->GetHashTargetOffset(std::addressof(region.size))); + + /* Create the region switch storage. */ + auto region_switch_storage = fssystem::AllocateShared(std::move(inside_storage), std::move(outside_storage), region); + R_UNLESS(region_switch_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared()); + + /* Set the output. */ + *out = std::move(region_switch_storage); + R_SUCCEED(); } Result NcaFileSystemDriver::CreateCompressedStorage(std::shared_ptr *out, std::shared_ptr *out_cmp, std::shared_ptr *out_meta, std::shared_ptr base_storage, const NcaCompressionInfo &compression_info) { - return this->CreateCompressedStorage(out, out_cmp, out_meta, std::move(base_storage), compression_info, m_reader->GetDecompressor(), m_allocator, m_buffer_manager); + R_RETURN(this->CreateCompressedStorage(out, out_cmp, out_meta, std::move(base_storage), compression_info, m_reader->GetDecompressor(), m_allocator, m_buffer_manager)); } Result NcaFileSystemDriver::CreateCompressedStorage(std::shared_ptr *out, std::shared_ptr *out_cmp, std::shared_ptr *out_meta, std::shared_ptr base_storage, const NcaCompressionInfo &compression_info, GetDecompressorFunction get_decompressor, MemoryResource *allocator, fs::IBufferManager *buffer_manager) { @@ -1145,7 +1253,7 @@ namespace ams::fssystem { /* Set the output. */ *out = std::move(compressed_storage); - return ResultSuccess(); + R_SUCCEED(); } } diff --git a/libraries/libstratosphere/source/fssystem/fssystem_nca_reader.cpp b/libraries/libstratosphere/source/fssystem/fssystem_nca_reader.cpp index 41740dece..6cb9f3f9d 100644 --- a/libraries/libstratosphere/source/fssystem/fssystem_nca_reader.cpp +++ b/libraries/libstratosphere/source/fssystem/fssystem_nca_reader.cpp @@ -35,7 +35,7 @@ namespace ams::fssystem { } - NcaReader::NcaReader() : m_body_storage(), m_header_storage(), m_decrypt_aes_ctr(), m_decrypt_aes_ctr_external(), m_is_software_aes_prioritized(false), m_header_encryption_type(NcaHeader::EncryptionType::Auto), m_get_decompressor(), m_hash_generator_factory_selector() { + NcaReader::NcaReader() : m_body_storage(), m_header_storage(), m_decrypt_aes_ctr(), m_decrypt_aes_ctr_external(), m_is_software_aes_prioritized(false), m_is_available_sw_key(false), m_header_encryption_type(NcaHeader::EncryptionType::Auto), m_get_decompressor(), m_hash_generator_factory_selector() { std::memset(std::addressof(m_header), 0, sizeof(m_header)); std::memset(std::addressof(m_decryption_keys), 0, sizeof(m_decryption_keys)); std::memset(std::addressof(m_external_decryption_key), 0, sizeof(m_external_decryption_key)); @@ -52,19 +52,42 @@ namespace ams::fssystem { AMS_ASSERT(m_body_storage == nullptr); /* Check that the crypto config is valid. */ - R_UNLESS(crypto_cfg.generate_key != nullptr, fs::ResultInvalidArgument()); + R_UNLESS(crypto_cfg.verify_sign1 != nullptr, fs::ResultInvalidArgument()); - /* Generate keys for header. */ - using AesXtsStorageForNcaHeader = AesXtsStorageBySharedPointer; + /* Create the work header storage storage. */ + std::unique_ptr work_header_storage; + if (crypto_cfg.is_available_sw_key) { + /* If software key is available, we need to be able to generate keys. */ + R_UNLESS(crypto_cfg.generate_key != nullptr, fs::ResultInvalidArgument()); - u8 header_decryption_keys[NcaCryptoConfiguration::HeaderEncryptionKeyCount][NcaCryptoConfiguration::Aes128KeySize]; - for (size_t i = 0; i < NcaCryptoConfiguration::HeaderEncryptionKeyCount; i++) { - crypto_cfg.generate_key(header_decryption_keys[i], AesXtsStorageForNcaHeader::KeySize, crypto_cfg.header_encrypted_encryption_keys[i], AesXtsStorageForNcaHeader::KeySize, static_cast(KeyType::NcaHeaderKey), crypto_cfg); + /* Generate keys for header. */ + using AesXtsStorageForNcaHeader = AesXtsStorageBySharedPointer; + + constexpr const s32 HeaderKeyTypeValues[NcaCryptoConfiguration::HeaderEncryptionKeyCount] = { + static_cast(KeyType::NcaHeaderKey1), + static_cast(KeyType::NcaHeaderKey2), + }; + + u8 header_decryption_keys[NcaCryptoConfiguration::HeaderEncryptionKeyCount][NcaCryptoConfiguration::Aes128KeySize]; + for (size_t i = 0; i < NcaCryptoConfiguration::HeaderEncryptionKeyCount; i++) { + crypto_cfg.generate_key(header_decryption_keys[i], AesXtsStorageForNcaHeader::KeySize, crypto_cfg.header_encrypted_encryption_keys[i], AesXtsStorageForNcaHeader::KeySize, HeaderKeyTypeValues[i]); + } + + /* Create the header storage. */ + const u8 header_iv[AesXtsStorageForNcaHeader::IvSize] = {}; + work_header_storage = std::make_unique(base_storage, header_decryption_keys[0], header_decryption_keys[1], AesXtsStorageForNcaHeader::KeySize, header_iv, AesXtsStorageForNcaHeader::IvSize, NcaHeader::XtsBlockSize); + } else { + /* Software key isn't available, so we need to be able to decrypt externally. */ + R_UNLESS(crypto_cfg.decrypt_aes_xts_external, fs::ResultInvalidArgument()); + + /* Create the header storage. */ + using AesXtsStorageExternalForNcaHeader = AesXtsStorageExternalByPointer; + + const u8 header_iv[AesXtsStorageExternalForNcaHeader::IvSize] = {}; + work_header_storage = std::make_unique(base_storage.get(), nullptr, nullptr, AesXtsStorageExternalForNcaHeader::KeySize, header_iv, AesXtsStorageExternalForNcaHeader::IvSize, NcaHeader::XtsBlockSize, crypto_cfg.encrypt_aes_xts_external, crypto_cfg.decrypt_aes_xts_external); } - /* Create the header storage. */ - const u8 header_iv[AesXtsStorageForNcaHeader::IvSize] = {}; - std::unique_ptr work_header_storage = std::make_unique(base_storage, header_decryption_keys[0], header_decryption_keys[1], AesXtsStorageForNcaHeader::KeySize, header_iv, AesXtsStorageForNcaHeader::IvSize, NcaHeader::XtsBlockSize); + /* Check that we successfully created the storage. */ R_UNLESS(work_header_storage != nullptr, fs::ResultAllocationMemoryFailedInNcaReaderA()); /* Read the header. */ @@ -91,19 +114,15 @@ namespace ams::fssystem { /* Validate the fixed key signature. */ R_UNLESS(m_header.header1_signature_key_generation <= NcaCryptoConfiguration::Header1SignatureKeyGenerationMax, fs::ResultInvalidNcaHeader1SignatureKeyGeneration()); - const u8 *header_1_sign_key_modulus = crypto_cfg.header_1_sign_key_moduli[m_header.header1_signature_key_generation]; - AMS_ABORT_UNLESS(header_1_sign_key_modulus != nullptr); + + /* Verify the header sign1. */ { const u8 *sig = m_header.header_sign_1; const size_t sig_size = NcaHeader::HeaderSignSize; - const u8 *mod = header_1_sign_key_modulus; - const size_t mod_size = NcaCryptoConfiguration::Rsa2048KeyModulusSize; - const u8 *exp = crypto_cfg.header_1_sign_key_public_exponent; - const size_t exp_size = NcaCryptoConfiguration::Rsa2048KeyPublicExponentSize; const u8 *msg = static_cast(static_cast(std::addressof(m_header.magic))); const size_t msg_size = NcaHeader::Size - NcaHeader::HeaderSignSize * NcaHeader::HeaderSignCount; - m_is_header_sign1_signature_valid = crypto::VerifyRsa2048PssSha256(sig, sig_size, mod, mod_size, exp, exp_size, msg, msg_size); + m_is_header_sign1_signature_valid = crypto_cfg.verify_sign1(sig, sig_size, msg, msg_size, m_header.header1_signature_key_generation, crypto_cfg); #if defined(ATMOSPHERE_BOARD_NINTENDO_NX) R_UNLESS(m_is_header_sign1_signature_valid, fs::ResultNcaHeaderSignature1VerificationFailed()); @@ -116,7 +135,7 @@ namespace ams::fssystem { R_UNLESS(m_header.sdk_addon_version >= SdkAddonVersionMin, fs::ResultUnsupportedSdkVersion()); /* Validate the key index. */ - R_UNLESS(m_header.key_index < NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount, fs::ResultInvalidNcaKeyIndex()); + R_UNLESS(m_header.key_index < NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount || m_header.key_index == NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexZeroKey, fs::ResultInvalidNcaKeyIndex()); /* Set our hash generator factory selector. */ m_hash_generator_factory_selector = hgf_selector; @@ -124,15 +143,17 @@ namespace ams::fssystem { /* Check if we have a rights id. */ constexpr const u8 ZeroRightsId[NcaHeader::RightsIdSize] = {}; if (crypto::IsSameBytes(ZeroRightsId, m_header.rights_id, NcaHeader::RightsIdSize)) { - /* If we do, then we don't have an external key, so we need to generate decryption keys. */ - crypto_cfg.generate_key(m_decryption_keys[NcaHeader::DecryptionKey_AesCtr], crypto::AesDecryptor128::KeySize, m_header.encrypted_key_area + NcaHeader::DecryptionKey_AesCtr * crypto::AesDecryptor128::KeySize, crypto::AesDecryptor128::KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration()), crypto_cfg); + /* If we do, then we don't have an external key, so we need to generate decryption keys if software keys are available. */ + if (crypto_cfg.is_available_sw_key) { + crypto_cfg.generate_key(m_decryption_keys[NcaHeader::DecryptionKey_AesCtr], crypto::AesDecryptor128::KeySize, m_header.encrypted_key_area + NcaHeader::DecryptionKey_AesCtr * crypto::AesDecryptor128::KeySize, crypto::AesDecryptor128::KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration())); - /* If we're building for non-nx board (i.e., a host tool), generate all keys for debug. */ - #if !defined(ATMOSPHERE_BOARD_NINTENDO_NX) - crypto_cfg.generate_key(m_decryption_keys[NcaHeader::DecryptionKey_AesXts1], crypto::AesDecryptor128::KeySize, m_header.encrypted_key_area + NcaHeader::DecryptionKey_AesXts1 * crypto::AesDecryptor128::KeySize, crypto::AesDecryptor128::KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration()), crypto_cfg); - crypto_cfg.generate_key(m_decryption_keys[NcaHeader::DecryptionKey_AesXts2], crypto::AesDecryptor128::KeySize, m_header.encrypted_key_area + NcaHeader::DecryptionKey_AesXts2 * crypto::AesDecryptor128::KeySize, crypto::AesDecryptor128::KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration()), crypto_cfg); - crypto_cfg.generate_key(m_decryption_keys[NcaHeader::DecryptionKey_AesCtrEx], crypto::AesDecryptor128::KeySize, m_header.encrypted_key_area + NcaHeader::DecryptionKey_AesCtrEx * crypto::AesDecryptor128::KeySize, crypto::AesDecryptor128::KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration()), crypto_cfg); - #endif + /* If we're building for non-nx board (i.e., a host tool), generate all keys for debug. */ + #if !defined(ATMOSPHERE_BOARD_NINTENDO_NX) + crypto_cfg.generate_key(m_decryption_keys[NcaHeader::DecryptionKey_AesXts1], crypto::AesDecryptor128::KeySize, m_header.encrypted_key_area + NcaHeader::DecryptionKey_AesXts1 * crypto::AesDecryptor128::KeySize, crypto::AesDecryptor128::KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration())); + crypto_cfg.generate_key(m_decryption_keys[NcaHeader::DecryptionKey_AesXts2], crypto::AesDecryptor128::KeySize, m_header.encrypted_key_area + NcaHeader::DecryptionKey_AesXts2 * crypto::AesDecryptor128::KeySize, crypto::AesDecryptor128::KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration())); + crypto_cfg.generate_key(m_decryption_keys[NcaHeader::DecryptionKey_AesCtrEx], crypto::AesDecryptor128::KeySize, m_header.encrypted_key_area + NcaHeader::DecryptionKey_AesCtrEx * crypto::AesDecryptor128::KeySize, crypto::AesDecryptor128::KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration())); + #endif + } /* Copy the hardware speed emulation key. */ std::memcpy(m_decryption_keys[NcaHeader::DecryptionKey_AesCtrHw], m_header.encrypted_key_area + NcaHeader::DecryptionKey_AesCtrHw * crypto::AesDecryptor128::KeySize, crypto::AesDecryptor128::KeySize); @@ -141,6 +162,9 @@ namespace ams::fssystem { /* Clear the external decryption key. */ std::memset(m_external_decryption_key, 0, sizeof(m_external_decryption_key)); + /* Set software key availability. */ + m_is_available_sw_key = crypto_cfg.is_available_sw_key; + /* Set our decryptor functions. */ m_decrypt_aes_ctr = crypto_cfg.decrypt_aes_ctr; m_decrypt_aes_ctr_external = crypto_cfg.decrypt_aes_ctr_external; @@ -309,6 +333,10 @@ namespace ams::fssystem { m_is_software_aes_prioritized = true; } + bool NcaReader::IsAvailableSwKey() const { + return m_is_available_sw_key; + } + bool NcaReader::HasExternalDecryptionKey() const { constexpr const u8 ZeroKey[crypto::AesDecryptor128::KeySize] = {}; return !crypto::IsSameBytes(ZeroKey, this->GetExternalDecryptionKey(), crypto::AesDecryptor128::KeySize); @@ -469,6 +497,18 @@ namespace ams::fssystem { return m_data.aes_ctr_upper_iv; } + bool NcaFsHeaderReader::IsSkipLayerHashEncryption() const { + AMS_ASSERT(this->IsInitialized()); + return m_data.IsSkipLayerHashEncryption(); + } + + Result NcaFsHeaderReader::GetHashTargetOffset(s64 *out) const { + AMS_ASSERT(out != nullptr); + AMS_ASSERT(this->IsInitialized()); + + return m_data.GetHashTargetOffset(out); + } + bool NcaFsHeaderReader::ExistsSparseLayer() const { AMS_ASSERT(this->IsInitialized()); return m_data.sparse_info.generation != 0; @@ -499,4 +539,44 @@ namespace ams::fssystem { return m_data.compression_info; } + bool NcaFsHeaderReader::ExistsPatchMetaHashLayer() const { + AMS_ASSERT(this->IsInitialized()); + return m_data.meta_data_hash_data_info.size != 0 && this->GetPatchInfo().HasIndirectTable(); + } + + NcaMetaDataHashDataInfo &NcaFsHeaderReader::GetPatchMetaDataHashDataInfo() { + AMS_ASSERT(this->IsInitialized()); + return m_data.meta_data_hash_data_info; + } + + const NcaMetaDataHashDataInfo &NcaFsHeaderReader::GetPatchMetaDataHashDataInfo() const { + AMS_ASSERT(this->IsInitialized()); + return m_data.meta_data_hash_data_info; + } + + NcaFsHeader::MetaDataHashType NcaFsHeaderReader::GetPatchMetaHashType() const { + AMS_ASSERT(this->IsInitialized()); + return m_data.meta_data_hash_type; + } + + bool NcaFsHeaderReader::ExistsSparseMetaHashLayer() const { + AMS_ASSERT(this->IsInitialized()); + return m_data.meta_data_hash_data_info.size != 0 && this->ExistsSparseLayer(); + } + + NcaMetaDataHashDataInfo &NcaFsHeaderReader::GetSparseMetaDataHashDataInfo() { + AMS_ASSERT(this->IsInitialized()); + return m_data.meta_data_hash_data_info; + } + + const NcaMetaDataHashDataInfo &NcaFsHeaderReader::GetSparseMetaDataHashDataInfo() const { + AMS_ASSERT(this->IsInitialized()); + return m_data.meta_data_hash_data_info; + } + + NcaFsHeader::MetaDataHashType NcaFsHeaderReader::GetSparseMetaHashType() const { + AMS_ASSERT(this->IsInitialized()); + return m_data.meta_data_hash_type; + } + } diff --git a/libraries/libvapours/include/vapours/results/fs_results.hpp b/libraries/libvapours/include/vapours/results/fs_results.hpp index 25ba6769a..f29c4dbf7 100644 --- a/libraries/libvapours/include/vapours/results/fs_results.hpp +++ b/libraries/libvapours/include/vapours/results/fs_results.hpp @@ -279,6 +279,17 @@ namespace ams::fs { R_DEFINE_ERROR_RESULT(RomHierarchicalSha256BaseStorageTooLarge, 4074); R_DEFINE_ERROR_RESULT(RomHierarchicalSha256HashVerificationFailed, 4075); + R_DEFINE_ERROR_RESULT(RomNcaInvalidCompressionInfo, 4083); + R_DEFINE_ERROR_RESULT(RomNcaInvalidPatchMetaDataHashType, 4084); + R_DEFINE_ERROR_RESULT(RomNcaInvalidIntegrityLayerInfoOffset, 4085); + R_DEFINE_ERROR_RESULT(RomNcaInvalidPatchMetaDataHashDataSize, 4086); + R_DEFINE_ERROR_RESULT(RomNcaInvalidPatchMetaDataHashDataOffset, 4087); + R_DEFINE_ERROR_RESULT(RomNcaInvalidPatchMetaDataHashDataHash, 4088); + R_DEFINE_ERROR_RESULT(RomNcaInvalidSparseMetaDataHashType, 4089); + R_DEFINE_ERROR_RESULT(RomNcaInvalidSparseMetaDataHashDataSize, 4090); + R_DEFINE_ERROR_RESULT(RomNcaInvalidSparseMetaDataHashDataOffset, 4091); + R_DEFINE_ERROR_RESULT(RomNcaInvalidSparseMetaDataHashDataHash, 4092); + R_DEFINE_ERROR_RANGE(RomIntegrityVerificationStorageCorrupted, 4141, 4179); R_DEFINE_ERROR_RESULT(IncorrectRomIntegrityVerificationMagic, 4142); R_DEFINE_ERROR_RESULT(InvalidRomZeroHash, 4143); @@ -349,7 +360,9 @@ namespace ams::fs { R_DEFINE_ERROR_RESULT(InvalidNcaHeader1SignatureKeyGeneration, 4543); /* TODO: Range? */ - R_DEFINE_ERROR_RESULT(InvalidCompressedStorageSize, 4547); + R_DEFINE_ERROR_RESULT(InvalidCompressedStorageSize, 4547); + R_DEFINE_ERROR_RESULT(InvalidNcaMetaDataHashDataSize, 4548); + R_DEFINE_ERROR_RESULT(InvalidNcaMetaDataHashDataHash, 4549); R_DEFINE_ERROR_RANGE(IntegrityVerificationStorageCorrupted, 4601, 4639); R_DEFINE_ERROR_RESULT(IncorrectIntegrityVerificationMagic, 4602); @@ -463,10 +476,10 @@ namespace ams::fs { R_DEFINE_ERROR_RESULT(UnsupportedSetSizeForHierarchicalIntegrityVerificationStorage, 6316); R_DEFINE_ERROR_RESULT(UnsupportedOperateRangeForHierarchicalIntegrityVerificationStorage, 6317); R_DEFINE_ERROR_RESULT(UnsupportedSetSizeForIntegrityVerificationStorage, 6318); - R_DEFINE_ERROR_RESULT(UnsupportedOperateRangeForNonSaveDataIntegrityVerificationStorage, 6319); + R_DEFINE_ERROR_RESULT(UnsupportedOperateRangeForWritableIntegrityVerificationStorage, 6319); R_DEFINE_ERROR_RESULT(UnsupportedOperateRangeForIntegrityVerificationStorage, 6320); R_DEFINE_ERROR_RESULT(UnsupportedSetSizeForBlockCacheBufferedStorage, 6321); - R_DEFINE_ERROR_RESULT(UnsupportedOperateRangeForNonSaveDataBlockCacheBufferedStorage, 6322); + R_DEFINE_ERROR_RESULT(UnsupportedOperateRangeForWritableBlockCacheBufferedStorage, 6322); R_DEFINE_ERROR_RESULT(UnsupportedOperateRangeForBlockCacheBufferedStorage, 6323); R_DEFINE_ERROR_RESULT(UnsupportedWriteForIndirectStorage, 6324); R_DEFINE_ERROR_RESULT(UnsupportedSetSizeForIndirectStorage, 6325); @@ -531,6 +544,7 @@ namespace ams::fs { R_DEFINE_ERROR_RESULT(UnsupportedSetSizeForZeroBitmapHashStorageFile, 6386); R_DEFINE_ERROR_RESULT(UnsupportedWriteForCompressedStorage, 6387); R_DEFINE_ERROR_RESULT(UnsupportedOperateRangeForCompressedStorage, 6388); + R_DEFINE_ERROR_RESULT(UnsupportedOperateRangeForRegionSwitchStorage, 6397); R_DEFINE_ERROR_RANGE(PermissionDenied, 6400, 6449);