diff --git a/src/core/file_sys/cia_container.cpp b/src/core/file_sys/cia_container.cpp index 98ec7f3df..957c541b3 100644 --- a/src/core/file_sys/cia_container.cpp +++ b/src/core/file_sys/cia_container.cpp @@ -13,11 +13,11 @@ namespace FileSys { Loader::ResultStatus CIAContainer::Load(const FileBackend& backend) { - std::vector header_data(sizeof(Header)); + std::vector header_data(sizeof(CIAHeader)); // Load the CIA Header - ResultVal read_result = backend.Read(0, sizeof(Header), header_data.data()); - if (read_result.Failed() || *read_result != sizeof(Header)) + ResultVal read_result = backend.Read(0, sizeof(CIAHeader), header_data.data()); + if (read_result.Failed() || *read_result != sizeof(CIAHeader)) return Loader::ResultStatus::Error; Loader::ResultStatus result = LoadHeader(header_data); @@ -65,8 +65,8 @@ Loader::ResultStatus CIAContainer::Load(const std::string& filepath) { return Loader::ResultStatus::Error; // Load CIA Header - std::vector header_data(sizeof(Header)); - if (file.ReadBytes(header_data.data(), sizeof(Header)) != sizeof(Header)) + std::vector header_data(sizeof(CIAHeader)); + if (file.ReadBytes(header_data.data(), sizeof(CIAHeader)) != sizeof(CIAHeader)) return Loader::ResultStatus::Error; Loader::ResultStatus result = LoadHeader(header_data); @@ -134,11 +134,12 @@ Loader::ResultStatus CIAContainer::Load(std::span file_data) { } Loader::ResultStatus CIAContainer::LoadHeader(std::span header_data, std::size_t offset) { - if (header_data.size() - offset < sizeof(Header)) { + if (header_data.size() - offset < sizeof(CIAHeader)) { return Loader::ResultStatus::Error; } - std::memcpy(&cia_header, header_data.data(), sizeof(Header)); + std::memcpy(&cia_header, header_data.data(), sizeof(CIAHeader)); + has_header = true; return Loader::ResultStatus::Success; } @@ -265,4 +266,7 @@ void CIAContainer::Print() const { LOG_DEBUG(Service_FS, "Content {:x} Offset: 0x{:08x} bytes", i, GetContentOffset(i)); } } +const CIAHeader* CIAContainer::GetHeader() { + return has_header ? &cia_header : nullptr; +} } // namespace FileSys diff --git a/src/core/file_sys/cia_container.h b/src/core/file_sys/cia_container.h index e36a12dba..e562dc9be 100644 --- a/src/core/file_sys/cia_container.h +++ b/src/core/file_sys/cia_container.h @@ -28,6 +28,30 @@ constexpr std::size_t CIA_DEPENDENCY_SIZE = 0x300; constexpr std::size_t CIA_METADATA_SIZE = 0x400; constexpr u32 CIA_SECTION_ALIGNMENT = 0x40; +struct CIAHeader { + u32_le header_size; + u16_le type; + u16_le version; + u32_le cert_size; + u32_le tik_size; + u32_le tmd_size; + u32_le meta_size; + u64_le content_size; + std::array content_present; + + bool IsContentPresent(std::size_t index) const { + // The content_present is a bit array which defines which content in the TMD + // is included in the CIA, so check the bit for this index and add if set. + // The bits in the content index are arranged w/ index 0 as the MSB, 7 as the LSB, etc. + return (content_present[index >> 3] & (0x80 >> (index & 7))) != 0; + } + void SetContentPresent(u16 index) { + content_present[index >> 3] |= (0x80 >> (index & 7)); + } +}; + +static_assert(sizeof(CIAHeader) == CIA_HEADER_SIZE, "CIA Header structure size is wrong"); + /** * Helper which implements an interface to read and write CTR Installable Archive (CIA) files. * Data can either be loaded from a FileBackend, a string path, or from a data array. Data can @@ -49,6 +73,7 @@ public: Loader::ResultStatus LoadTitleMetadata(const TitleMetadata& tmd); Loader::ResultStatus LoadMetadata(std::span meta_data, std::size_t offset = 0); + const CIAHeader* GetHeader(); Ticket& GetTicket(); const TitleMetadata& GetTitleMetadata() const; std::array& GetDependencies(); @@ -69,30 +94,6 @@ public: void Print() const; - struct Header { - u32_le header_size; - u16_le type; - u16_le version; - u32_le cert_size; - u32_le tik_size; - u32_le tmd_size; - u32_le meta_size; - u64_le content_size; - std::array content_present; - - bool IsContentPresent(std::size_t index) const { - // The content_present is a bit array which defines which content in the TMD - // is included in the CIA, so check the bit for this index and add if set. - // The bits in the content index are arranged w/ index 0 as the MSB, 7 as the LSB, etc. - return (content_present[index >> 3] & (0x80 >> (index & 7))) != 0; - } - void SetContentPresent(u16 index) { - content_present[index >> 3] |= (0x80 >> (index & 7)); - } - }; - - static_assert(sizeof(Header) == CIA_HEADER_SIZE, "CIA Header structure size is wrong"); - private: struct Metadata { std::array dependencies; @@ -103,7 +104,8 @@ private: static_assert(sizeof(Metadata) == CIA_METADATA_SIZE, "CIA Metadata structure size is wrong"); - Header cia_header; + bool has_header = false; + CIAHeader cia_header; Metadata cia_metadata; Ticket cia_ticket; TitleMetadata cia_tmd; diff --git a/src/core/file_sys/title_metadata.cpp b/src/core/file_sys/title_metadata.cpp index 09857078f..d0cc2838e 100644 --- a/src/core/file_sys/title_metadata.cpp +++ b/src/core/file_sys/title_metadata.cpp @@ -6,6 +6,7 @@ #include "common/alignment.h" #include "common/file_util.h" #include "common/logging/log.h" +#include "core/file_sys/cia_container.h" #include "core/file_sys/signature.h" #include "core/file_sys/title_metadata.h" #include "core/loader/loader.h" @@ -185,9 +186,14 @@ std::array TitleMetadata::GetContentCTRByIndex(std::size_t index) const return ctr; } -bool TitleMetadata::HasEncryptedContent() const { - return std::any_of(tmd_chunks.begin(), tmd_chunks.end(), [](auto& chunk) { - return (static_cast(chunk.type) & FileSys::TMDContentTypeFlag::Encrypted) != 0; +bool TitleMetadata::HasEncryptedContent(const CIAHeader* header) const { + return std::any_of(tmd_chunks.begin(), tmd_chunks.end(), [header](auto& chunk) { + bool is_crypted = + (static_cast(chunk.type) & FileSys::TMDContentTypeFlag::Encrypted) != 0; + if (header) { + is_crypted = is_crypted && header->IsContentPresent(static_cast(chunk.index)); + } + return is_crypted; }); } diff --git a/src/core/file_sys/title_metadata.h b/src/core/file_sys/title_metadata.h index 379a2389b..d1ca730a6 100644 --- a/src/core/file_sys/title_metadata.h +++ b/src/core/file_sys/title_metadata.h @@ -17,6 +17,8 @@ enum class ResultStatus; namespace FileSys { +struct CIAHeader; + enum TMDContentTypeFlag : u16 { Encrypted = 1 << 0, Disc = 1 << 2, @@ -99,7 +101,7 @@ public: u64 GetContentSizeByIndex(std::size_t index) const; bool GetContentOptional(std::size_t index) const; std::array GetContentCTRByIndex(std::size_t index) const; - bool HasEncryptedContent() const; + bool HasEncryptedContent(const CIAHeader* header = nullptr) const; void SetTitleID(u64 title_id); void SetTitleType(u32 type); diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index 79e1c0ece..ffbd11c4a 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -425,8 +425,8 @@ CIAFile::CIAFile(Core::System& system_, Service::FS::MediaType media_type, bool // If data is being installing from CDN, provide a fake header to the container so that // it's not uninitialized. if (from_cdn) { - FileSys::CIAContainer::Header fake_header{ - .header_size = sizeof(FileSys::CIAContainer::Header), + FileSys::CIAHeader fake_header{ + .header_size = sizeof(FileSys::CIAHeader), .type = 0, .version = 0, .cert_size = 0, @@ -548,6 +548,11 @@ ResultVal CIAFile::WriteContentData(u64 offset, std::size_t length, buffer + (range_min - offset) + available_to_write); if ((tmd.GetContentTypeByIndex(i) & FileSys::TMDContentTypeFlag::Encrypted) != 0) { + if (!decryption_authorized) { + LOG_ERROR(Service_AM, "Blocked unauthorized encrypted CIA installation."); + return Result(ErrorDescription::NotAuthorized, ErrorModule::AM, + ErrorSummary::InvalidState, ErrorLevel::Permanent); + } decryption_state->content[i].ProcessData(temp.data(), temp.data(), temp.size()); } @@ -663,7 +668,8 @@ Result CIAFile::PrepareToImportContent(const FileSys::TitleMetadata& tmd) { content_file_paths.emplace_back(path); } - if (container.GetTitleMetadata().HasEncryptedContent()) { + if (container.GetTitleMetadata().HasEncryptedContent(from_cdn ? nullptr + : container.GetHeader())) { if (!decryption_authorized) { LOG_ERROR(Service_AM, "Blocked unauthorized encrypted CIA installation."); return {ErrorDescription::NotAuthorized, ErrorModule::AM, ErrorSummary::InvalidState, @@ -728,9 +734,7 @@ Result CIAFile::ProvideTMDForAdditionalContent(const FileSys::TitleMetadata& tmd is_additional_content = true; - PrepareToImportContent(container.GetTitleMetadata()); - - return ResultSuccess; + return PrepareToImportContent(container.GetTitleMetadata()); } const FileSys::TitleMetadata& CIAFile::GetTMD() { @@ -762,6 +766,11 @@ ResultVal CIAFile::WriteContentDataIndexed(u16 content_index, u64 o std::vector temp(buffer, buffer + std::min(static_cast(length), remaining_to_write)); if ((tmd.GetContentTypeByIndex(content_index) & FileSys::TMDContentTypeFlag::Encrypted) != 0) { + if (!decryption_authorized) { + LOG_ERROR(Service_AM, "Blocked unauthorized encrypted CIA installation."); + return Result(ErrorDescription::NotAuthorized, ErrorModule::AM, + ErrorSummary::InvalidState, ErrorLevel::Permanent); + } decryption_state->content[content_index].ProcessData(temp.data(), temp.data(), temp.size()); } @@ -1000,7 +1009,7 @@ InstallStatus InstallCIA(const std::string& path, Core::System::GetInstance(), Service::AM::GetTitleMediaType(container.GetTitleMetadata().GetTitleID())); - if (container.GetTitleMetadata().HasEncryptedContent()) { + if (container.GetTitleMetadata().HasEncryptedContent(container.GetHeader())) { LOG_ERROR(Service_AM, "File {} is encrypted! Aborting...", path); return InstallStatus::ErrorEncrypted; }