From 473cbfa9516315d5efc47538e67448c41d5960da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Wed, 16 May 2018 12:31:12 +0200 Subject: [PATCH 1/2] ES: Add more logging in title import functions ...to make troubleshooting easier. --- Source/Core/Core/IOS/ES/ES.cpp | 12 +++++++++++ Source/Core/Core/IOS/ES/TitleManagement.cpp | 24 +++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/Source/Core/Core/IOS/ES/ES.cpp b/Source/Core/Core/IOS/ES/ES.cpp index 5adc1d6253..0743dc7efa 100644 --- a/Source/Core/Core/IOS/ES/ES.cpp +++ b/Source/Core/Core/IOS/ES/ES.cpp @@ -931,7 +931,10 @@ ReturnCode ES::VerifyContainer(VerifyContainerType type, VerifyMode mode, Common::ScopeGuard ca_guard{[&] { iosc.DeleteObject(handle, PID_ES); }}; ret = iosc.ImportCertificate(ca_cert.GetBytes().data(), IOSC::HANDLE_ROOT_KEY, handle, PID_ES); if (ret != IPC_SUCCESS) + { + ERROR_LOG(IOS_ES, "VerifyContainer: IOSC_ImportCertificate(ca) failed with error %d", ret); return ret; + } IOSC::Handle issuer_handle; const IOSC::ObjectSubType subtype = @@ -942,7 +945,10 @@ ReturnCode ES::VerifyContainer(VerifyContainerType type, VerifyMode mode, Common::ScopeGuard issuer_guard{[&] { iosc.DeleteObject(issuer_handle, PID_ES); }}; ret = iosc.ImportCertificate(issuer_cert.GetBytes().data(), handle, issuer_handle, PID_ES); if (ret != IPC_SUCCESS) + { + ERROR_LOG(IOS_ES, "VerifyContainer: IOSC_ImportCertificate(issuer) failed with error %d", ret); return ret; + } // Calculate the SHA1 of the signed blob. const size_t skip = type == VerifyContainerType::Device ? offsetof(SignatureECC, issuer) : @@ -955,7 +961,10 @@ ReturnCode ES::VerifyContainer(VerifyContainerType type, VerifyMode mode, const std::vector signature = signed_blob.GetSignatureData(); ret = iosc.VerifyPublicKeySign(sha1, issuer_handle, signature.data(), PID_ES); if (ret != IPC_SUCCESS) + { + ERROR_LOG(IOS_ES, "VerifyContainer: IOSC_VerifyPublicKeySign failed with error %d", ret); return ret; + } if (mode == VerifyMode::UpdateCertStore) { @@ -970,7 +979,10 @@ ReturnCode ES::VerifyContainer(VerifyContainerType type, VerifyMode mode, // Import the signed blob to iosc_handle (if a handle was passed to us). if (ret == IPC_SUCCESS && iosc_handle) + { ret = iosc.ImportCertificate(signed_blob.GetBytes().data(), issuer_handle, iosc_handle, PID_ES); + ERROR_LOG(IOS_ES, "VerifyContainer: IOSC_ImportCertificate(final) failed with error %d", ret); + } return ret; } diff --git a/Source/Core/Core/IOS/ES/TitleManagement.cpp b/Source/Core/Core/IOS/ES/TitleManagement.cpp index 1f1c988591..e4dc6db841 100644 --- a/Source/Core/Core/IOS/ES/TitleManagement.cpp +++ b/Source/Core/Core/IOS/ES/TitleManagement.cpp @@ -140,6 +140,8 @@ static void ResetTitleImportContext(ES::Context* context, IOSC& iosc) ReturnCode ES::ImportTmd(Context& context, const std::vector& tmd_bytes) { + INFO_LOG(IOS_ES, "ImportTmd"); + // Ioctlv 0x2b writes the TMD to /tmp/title.tmd (for imports) and doesn't seem to write it // to either /import or /title. So here we simply have to set the import TMD. ResetTitleImportContext(&context, m_ios.GetIOSC()); @@ -155,16 +157,26 @@ ReturnCode ES::ImportTmd(Context& context, const std::vector& tmd_bytes) ret = VerifyContainer(VerifyContainerType::TMD, VerifyMode::UpdateCertStore, context.title_import_export.tmd, cert_store); if (ret != IPC_SUCCESS) + { + ERROR_LOG(IOS_ES, "ImportTmd: VerifyContainer failed with error %d", ret); return ret; + } if (!InitImport(context.title_import_export.tmd)) + { + ERROR_LOG(IOS_ES, "ImportTmd: Failed to initialise title import"); return ES_EIO; + } ret = InitBackupKey(m_title_context.tmd, m_ios.GetIOSC(), &context.title_import_export.key_handle); if (ret != IPC_SUCCESS) + { + ERROR_LOG(IOS_ES, "ImportTmd: InitBackupKey failed with error %d", ret); return ret; + } + INFO_LOG(IOS_ES, "ImportTmd: All checks passed, marking context as valid"); context.title_import_export.valid = true; return IPC_SUCCESS; } @@ -421,7 +433,10 @@ IPCCommandResult ES::ImportContentEnd(Context& context, const IOCtlVRequest& req ReturnCode ES::ImportTitleDone(Context& context) { if (!context.title_import_export.valid || context.title_import_export.content.valid) + { + ERROR_LOG(IOS_ES, "ImportTitleDone: No title import, or a content import is still in progress"); return ES_EINVAL; + } // Make sure all listed, non-optional contents have been imported. const u64 title_id = context.title_import_export.tmd.GetTitleId(); @@ -442,13 +457,22 @@ ReturnCode ES::ImportTitleDone(Context& context) return m_ios.GetFS()->GetMetadata(PID_KERNEL, PID_KERNEL, path).Succeeded(); }); if (!has_all_required_contents) + { + ERROR_LOG(IOS_ES, "ImportTitleDone: Some required contents are missing"); return ES_EINVAL; + } if (!WriteImportTMD(context.title_import_export.tmd)) + { + ERROR_LOG(IOS_ES, "ImportTitleDone: Failed to write import TMD"); return ES_EIO; + } if (!FinishImport(context.title_import_export.tmd)) + { + ERROR_LOG(IOS_ES, "ImportTitleDone: Failed to finalise title import"); return ES_EIO; + } INFO_LOG(IOS_ES, "ImportTitleDone: title %016" PRIx64, title_id); ResetTitleImportContext(&context, m_ios.GetIOSC()); From f82e2f0b92c4fb1b333538bb91985112d48324db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Wed, 16 May 2018 13:26:38 +0200 Subject: [PATCH 2/2] ES: Fix content check in ImportTitleDone ImportTitleDone only checks if all required contents have been imported for system titles. This fixes the system menu not being able to recreate title directories to copy a save back to the NAND by using title import functionality. --- Source/Core/Core/IOS/ES/TitleManagement.cpp | 41 ++++++++++++--------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/Source/Core/Core/IOS/ES/TitleManagement.cpp b/Source/Core/Core/IOS/ES/TitleManagement.cpp index e4dc6db841..04831a6108 100644 --- a/Source/Core/Core/IOS/ES/TitleManagement.cpp +++ b/Source/Core/Core/IOS/ES/TitleManagement.cpp @@ -430,6 +430,26 @@ IPCCommandResult ES::ImportContentEnd(Context& context, const IOCtlVRequest& req return GetDefaultReply(ImportContentEnd(context, content_fd)); } +static bool HasAllRequiredContents(IOS::HLE::Kernel& ios, const IOS::ES::TMDReader& tmd) +{ + const u64 title_id = tmd.GetTitleId(); + const std::vector contents = tmd.GetContents(); + const IOS::ES::SharedContentMap shared_content_map{ios.GetFS()}; + return std::all_of(contents.cbegin(), contents.cend(), [&](const IOS::ES::Content& content) { + if (content.IsOptional()) + return true; + + if (content.IsShared()) + return shared_content_map.GetFilenameFromSHA1(content.sha1).has_value(); + + // Note: the import hasn't been finalised yet, so the whole title directory + // is still in /import, not /title. + const std::string path = + Common::GetImportTitlePath(title_id) + StringFromFormat("/content/%08x.app", content.id); + return ios.GetFS()->GetMetadata(PID_KERNEL, PID_KERNEL, path).Succeeded(); + }); +} + ReturnCode ES::ImportTitleDone(Context& context) { if (!context.title_import_export.valid || context.title_import_export.content.valid) @@ -438,25 +458,10 @@ ReturnCode ES::ImportTitleDone(Context& context) return ES_EINVAL; } - // Make sure all listed, non-optional contents have been imported. + // For system titles, make sure all listed, non-optional contents have been imported. const u64 title_id = context.title_import_export.tmd.GetTitleId(); - const std::vector contents = context.title_import_export.tmd.GetContents(); - const IOS::ES::SharedContentMap shared_content_map{m_ios.GetFS()}; - const bool has_all_required_contents = - std::all_of(contents.cbegin(), contents.cend(), [&](const IOS::ES::Content& content) { - if (content.IsOptional()) - return true; - - if (content.IsShared()) - return shared_content_map.GetFilenameFromSHA1(content.sha1).has_value(); - - // Note: the import hasn't been finalised yet, so the whole title directory - // is still in /import, not /title. - const std::string path = Common::GetImportTitlePath(title_id) + - StringFromFormat("/content/%08x.app", content.id); - return m_ios.GetFS()->GetMetadata(PID_KERNEL, PID_KERNEL, path).Succeeded(); - }); - if (!has_all_required_contents) + if (title_id - 0x100000001LL <= 0x100 && + !HasAllRequiredContents(m_ios, context.title_import_export.tmd)) { ERROR_LOG(IOS_ES, "ImportTitleDone: Some required contents are missing"); return ES_EINVAL;