From f223c27bb086d174bfd009bcd99e7193bd8188b9 Mon Sep 17 00:00:00 2001 From: Michael Scire Date: Fri, 26 Jun 2020 11:36:38 -0700 Subject: [PATCH] sysupdater: Add ValidateUpdate, begin implementing Async logic --- .../libstratosphere/include/stratosphere.hpp | 1 + .../impl/ams_system_thread_definitions.hpp | 2 + .../include/stratosphere/err.hpp | 19 ++ .../stratosphere/err/err_error_context.hpp | 45 +++++ .../stratosphere/ncm/ncm_content_meta.hpp | 2 +- .../stratosphere/os/os_thread_common.hpp | 2 + .../stratosphere/sf/sf_buffer_tags.hpp | 1 - .../libvapours/include/vapours/results.hpp | 4 +- .../include/vapours/results/nim_results.hpp | 26 +++ .../include/vapours/results/ns_results.hpp | 27 +++ .../sysupdater/sysupdater_async_impl.cpp | 80 ++++++++ .../sysupdater/sysupdater_async_impl.hpp | 116 ++++++++++++ .../sysupdater_async_thread_allocator.cpp | 38 ++++ .../sysupdater_async_thread_allocator.hpp | 24 +++ .../source/sysupdater/sysupdater_module.cpp | 1 + .../source/sysupdater/sysupdater_service.cpp | 178 ++++++++++++++++++ .../source/sysupdater/sysupdater_service.hpp | 8 + .../sysupdater_thread_allocator.cpp | 55 ++++++ .../sysupdater_thread_allocator.hpp | 51 +++++ 19 files changed, 677 insertions(+), 3 deletions(-) create mode 100644 libraries/libstratosphere/include/stratosphere/err.hpp create mode 100644 libraries/libstratosphere/include/stratosphere/err/err_error_context.hpp create mode 100644 libraries/libvapours/include/vapours/results/nim_results.hpp create mode 100644 libraries/libvapours/include/vapours/results/ns_results.hpp create mode 100644 stratosphere/ams_mitm/source/sysupdater/sysupdater_async_impl.cpp create mode 100644 stratosphere/ams_mitm/source/sysupdater/sysupdater_async_impl.hpp create mode 100644 stratosphere/ams_mitm/source/sysupdater/sysupdater_async_thread_allocator.cpp create mode 100644 stratosphere/ams_mitm/source/sysupdater/sysupdater_async_thread_allocator.hpp create mode 100644 stratosphere/ams_mitm/source/sysupdater/sysupdater_thread_allocator.cpp create mode 100644 stratosphere/ams_mitm/source/sysupdater/sysupdater_thread_allocator.hpp diff --git a/libraries/libstratosphere/include/stratosphere.hpp b/libraries/libstratosphere/include/stratosphere.hpp index 81f1e2371..7a51e59eb 100644 --- a/libraries/libstratosphere/include/stratosphere.hpp +++ b/libraries/libstratosphere/include/stratosphere.hpp @@ -45,6 +45,7 @@ #include #include #include +#include #include #include #include diff --git a/libraries/libstratosphere/include/stratosphere/ams/impl/ams_system_thread_definitions.hpp b/libraries/libstratosphere/include/stratosphere/ams/impl/ams_system_thread_definitions.hpp index 3db6c394c..7d60ab72d 100644 --- a/libraries/libstratosphere/include/stratosphere/ams/impl/ams_system_thread_definitions.hpp +++ b/libraries/libstratosphere/include/stratosphere/ams/impl/ams_system_thread_definitions.hpp @@ -62,6 +62,7 @@ namespace ams::impl { AMS_DEFINE_SYSTEM_THREAD(16, mitm_fs, RomFileSystemInitializeThread); AMS_DEFINE_SYSTEM_THREAD(21, mitm, DebugThrowThread); AMS_DEFINE_SYSTEM_THREAD(21, mitm_sysupdater, IpcServer); + AMS_DEFINE_SYSTEM_THREAD(21, mitm_sysupdater, AsyncPrepareSdCardUpdateTask); /* boot2. */ AMS_DEFINE_SYSTEM_THREAD(20, boot2, Main); @@ -94,6 +95,7 @@ namespace ams::impl { /* ns.*/ AMS_DEFINE_SYSTEM_THREAD(21, ns, ApplicationManagerIpcSession); + AMS_DEFINE_SYSTEM_THREAD(21, nssrv, AsyncPrepareCardUpdateTask); /* settings. */ AMS_DEFINE_SYSTEM_THREAD(21, settings, Main); diff --git a/libraries/libstratosphere/include/stratosphere/err.hpp b/libraries/libstratosphere/include/stratosphere/err.hpp new file mode 100644 index 000000000..c8cb979a0 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/err.hpp @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include diff --git a/libraries/libstratosphere/include/stratosphere/err/err_error_context.hpp b/libraries/libstratosphere/include/stratosphere/err/err_error_context.hpp new file mode 100644 index 000000000..df40e82b6 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/err/err_error_context.hpp @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include + +namespace ams::err { + + enum class ErrorContextType : u8 { + None = 0, + Http = 1, + FileSystem = 2, + WebMediaPlayer = 3, + LocalContentShare = 4, + }; + + struct PaddingErrorContext { + u8 padding[0x200 - 8]; + }; + + struct ErrorContext : public sf::LargeData, public sf::PrefersMapAliasTransferMode { + ErrorContextType type; + u8 reserved[7]; + + union { + PaddingErrorContext padding; + }; + }; + static_assert(sizeof(ErrorContext) == 0x200); + static_assert(util::is_pod::value); + +} diff --git a/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_meta.hpp b/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_meta.hpp index 3f015e80c..9810b7658 100644 --- a/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_meta.hpp +++ b/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_meta.hpp @@ -45,7 +45,7 @@ namespace ams::ncm { }; } - constexpr ContentMetaKey ToKey() { + constexpr ContentMetaKey ToKey() const { return ContentMetaKey::Make(this->id, this->version, this->type); } }; diff --git a/libraries/libstratosphere/include/stratosphere/os/os_thread_common.hpp b/libraries/libstratosphere/include/stratosphere/os/os_thread_common.hpp index 8e634dc9e..8c9373fc9 100644 --- a/libraries/libstratosphere/include/stratosphere/os/os_thread_common.hpp +++ b/libraries/libstratosphere/include/stratosphere/os/os_thread_common.hpp @@ -27,6 +27,8 @@ namespace ams::os { constexpr inline s32 DefaultThreadPriority = ThreadPriorityRangeSize / 2; constexpr inline s32 LowestThreadPriority = ThreadPriorityRangeSize - 1; + constexpr inline s32 InvalidThreadPriority = 127; + constexpr inline s32 LowestSystemThreadPriority = 35; constexpr inline s32 HighestSystemThreadPriority = -12; diff --git a/libraries/libstratosphere/include/stratosphere/sf/sf_buffer_tags.hpp b/libraries/libstratosphere/include/stratosphere/sf/sf_buffer_tags.hpp index 985a42775..4467e1776 100644 --- a/libraries/libstratosphere/include/stratosphere/sf/sf_buffer_tags.hpp +++ b/libraries/libstratosphere/include/stratosphere/sf/sf_buffer_tags.hpp @@ -13,7 +13,6 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ - #pragma once namespace ams::sf { diff --git a/libraries/libvapours/include/vapours/results.hpp b/libraries/libvapours/include/vapours/results.hpp index fbebb06b3..4d4c41a56 100644 --- a/libraries/libvapours/include/vapours/results.hpp +++ b/libraries/libvapours/include/vapours/results.hpp @@ -37,8 +37,10 @@ #include #include #include -#include #include +#include +#include +#include #include #include #include diff --git a/libraries/libvapours/include/vapours/results/nim_results.hpp b/libraries/libvapours/include/vapours/results/nim_results.hpp new file mode 100644 index 000000000..c0607726c --- /dev/null +++ b/libraries/libvapours/include/vapours/results/nim_results.hpp @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include + +namespace ams::nim { + + R_DEFINE_NAMESPACE_RESULT_MODULE(137); + + R_DEFINE_ERROR_RESULT(HttpConnectionCanceled, 70); + +} diff --git a/libraries/libvapours/include/vapours/results/ns_results.hpp b/libraries/libvapours/include/vapours/results/ns_results.hpp new file mode 100644 index 000000000..557b03c82 --- /dev/null +++ b/libraries/libvapours/include/vapours/results/ns_results.hpp @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include + +namespace ams::ns { + + R_DEFINE_NAMESPACE_RESULT_MODULE(16); + + R_DEFINE_ERROR_RESULT(Canceled, 90); + R_DEFINE_ERROR_RESULT(OutOfMaxRunningTask, 110); + +} diff --git a/stratosphere/ams_mitm/source/sysupdater/sysupdater_async_impl.cpp b/stratosphere/ams_mitm/source/sysupdater/sysupdater_async_impl.cpp new file mode 100644 index 000000000..caebfc224 --- /dev/null +++ b/stratosphere/ams_mitm/source/sysupdater/sysupdater_async_impl.cpp @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include "sysupdater_async_impl.hpp" +#include "sysupdater_async_thread_allocator.hpp" + +namespace ams::mitm::sysupdater { + + Result AsyncBase::ToAsyncResult(Result result) { + R_TRY_CATCH(result) { + R_CONVERT(nim::ResultHttpConnectionCanceled, ns::ResultCanceled()); + R_CONVERT(ncm::ResultInstallTaskCancelled, ns::ResultCanceled()); + } R_END_TRY_CATCH; + + return ResultSuccess(); + } + + AsyncPrepareSdCardUpdateImpl::~AsyncPrepareSdCardUpdateImpl() { + if (this->thread_info) { + os::WaitThread(this->thread_info->thread); + os::DestroyThread(this->thread_info->thread); + GetAsyncThreadAllocator()->Free(*this->thread_info); + } + } + + Result AsyncPrepareSdCardUpdateImpl::Run() { + /* Get a thread info. */ + ThreadInfo info; + R_TRY(GetAsyncThreadAllocator()->Allocate(std::addressof(info))); + + /* Set the thread info's priority. */ + info.priority = AMS_GET_SYSTEM_THREAD_PRIORITY(mitm_sysupdater, AsyncPrepareSdCardUpdateTask); + + /* Ensure that we clean up appropriately. */ + ON_SCOPE_EXIT { + if (!this->thread_info) { + GetAsyncThreadAllocator()->Free(info); + } + }; + + /* Create a thread for the task. */ + R_TRY(os::CreateThread(info.thread, [](void *arg) { + auto *_this = reinterpret_cast(arg); + _this->result = _this->Execute(); + _this->event.Signal(); + }, this, info.stack, info.stack_size, info.priority)); + + /* Set the thread name. */ + os::SetThreadNamePointer(info.thread, AMS_GET_SYSTEM_THREAD_NAME(mitm_sysupdater, AsyncPrepareSdCardUpdateTask)); + + /* Start the thread. */ + os::StartThread(info.thread); + + /* Set our thread info. */ + this->thread_info = info; + return ResultSuccess(); + } + + Result AsyncPrepareSdCardUpdateImpl::Execute() { + return this->task->PrepareAndExecute(); + } + + void AsyncPrepareSdCardUpdateImpl::CancelImpl() { + this->task->Cancel(); + } + +} diff --git a/stratosphere/ams_mitm/source/sysupdater/sysupdater_async_impl.hpp b/stratosphere/ams_mitm/source/sysupdater/sysupdater_async_impl.hpp new file mode 100644 index 000000000..0458e5f8b --- /dev/null +++ b/stratosphere/ams_mitm/source/sysupdater/sysupdater_async_impl.hpp @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include "sysupdater_thread_allocator.hpp" + +namespace ams::mitm::sysupdater { + + class ErrorContextHolder { + private: + err::ErrorContext error_context; + public: + constexpr ErrorContextHolder() : error_context{} { /* ... */ } + + virtual ~ErrorContextHolder() { /* ... */ } + + template + Result SaveErrorContextIfFailed(T &async, Result result) { + if (R_FAILED(result)) { + async.GetErrorContext(std::addressof(this->error_context)); + return result; + } + + return ResultSuccess(); + } + + template + Result GetAndSaveErrorContext(T &async) { + R_TRY(this->SaveErrorContextIfFailed(async, async.Get())); + return ResultSuccess(); + } + + template + Result SaveInternalTaskErrorContextIfFailed(T &async, Result result) { + if (R_FAILED(result)) { + async.CreateErrorContext(std::addressof(this->error_context)); + return result; + } + + return ResultSuccess(); + } + + const err::ErrorContext &GetErrorContextImpl() { + return this->error_context; + } + }; + + class AsyncBase { + public: + virtual ~AsyncBase() { /* ... */ } + + Result Cancel() { + this->CancelImpl(); + return ResultSuccess(); + } + + static Result ToAsyncResult(Result result); + + virtual Result GetErrorContext(sf::Out out) { + *out = {}; + return ResultSuccess(); + } + private: + virtual void CancelImpl() = 0; + }; + + class AsyncResultBase : public AsyncBase { + public: + Result Get() { + return ToAsyncResult(this->GetImpl()); + } + private: + virtual Result GetImpl() = 0; + }; + + /* NOTE: Based off of ns AsyncPrepareCardUpdateImpl. */ + /* We don't implement the RequestServer::ManagedStop details, as we don't implement stoppable request list. */ + class AsyncPrepareSdCardUpdateImpl : public AsyncResultBase, private ErrorContextHolder { + private: + Result result; + os::SystemEvent event; + std::optional thread_info; + ncm::InstallTaskBase *task; + public: + AsyncPrepareSdCardUpdateImpl(ncm::InstallTaskBase *task) : result(ResultSuccess()), event(os::EventClearMode_ManualClear, true), thread_info(), task(task) { /* ... */ } + virtual ~AsyncPrepareSdCardUpdateImpl(); + + os::SystemEvent &GetEvent() { return this->event; } + + virtual Result GetErrorContext(sf::Out out) override { + *out = ErrorContextHolder::GetErrorContextImpl(); + return ResultSuccess(); + } + + Result Run(); + private: + Result Execute(); + + virtual void CancelImpl() override; + virtual Result GetImpl() override { return this->result; } + }; + +} diff --git a/stratosphere/ams_mitm/source/sysupdater/sysupdater_async_thread_allocator.cpp b/stratosphere/ams_mitm/source/sysupdater/sysupdater_async_thread_allocator.cpp new file mode 100644 index 000000000..8b02fd81a --- /dev/null +++ b/stratosphere/ams_mitm/source/sysupdater/sysupdater_async_thread_allocator.cpp @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include "sysupdater_async_thread_allocator.hpp" + +namespace ams::mitm::sysupdater { + + namespace { + + constexpr inline int AsyncThreadCount = 1; + constexpr inline size_t AsyncThreadStackSize = 16_KB; + + os::ThreadType g_async_threads[AsyncThreadCount]; + alignas(os::ThreadStackAlignment) u8 g_async_thread_stack_heap[AsyncThreadCount * AsyncThreadStackSize]; + + constinit ThreadAllocator g_async_thread_allocator(g_async_threads, AsyncThreadCount, os::InvalidThreadPriority, g_async_thread_stack_heap, sizeof(g_async_thread_stack_heap), AsyncThreadStackSize); + + } + + ThreadAllocator *GetAsyncThreadAllocator() { + return std::addressof(g_async_thread_allocator); + } + + +} diff --git a/stratosphere/ams_mitm/source/sysupdater/sysupdater_async_thread_allocator.hpp b/stratosphere/ams_mitm/source/sysupdater/sysupdater_async_thread_allocator.hpp new file mode 100644 index 000000000..b051689c6 --- /dev/null +++ b/stratosphere/ams_mitm/source/sysupdater/sysupdater_async_thread_allocator.hpp @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include "sysupdater_thread_allocator.hpp" + +namespace ams::mitm::sysupdater { + + ThreadAllocator *GetAsyncThreadAllocator(); + +} diff --git a/stratosphere/ams_mitm/source/sysupdater/sysupdater_module.cpp b/stratosphere/ams_mitm/source/sysupdater/sysupdater_module.cpp index e19a7c544..1ee370a8e 100644 --- a/stratosphere/ams_mitm/source/sysupdater/sysupdater_module.cpp +++ b/stratosphere/ams_mitm/source/sysupdater/sysupdater_module.cpp @@ -17,6 +17,7 @@ #include "../amsmitm_initialization.hpp" #include "sysupdater_module.hpp" #include "sysupdater_service.hpp" +#include "sysupdater_async_impl.hpp" namespace ams::mitm::sysupdater { diff --git a/stratosphere/ams_mitm/source/sysupdater/sysupdater_service.cpp b/stratosphere/ams_mitm/source/sysupdater/sysupdater_service.cpp index 2d0a2b008..341044e43 100644 --- a/stratosphere/ams_mitm/source/sysupdater/sysupdater_service.cpp +++ b/stratosphere/ams_mitm/source/sysupdater/sysupdater_service.cpp @@ -122,6 +122,159 @@ namespace ams::mitm::sysupdater { return ResultSuccess(); } + Result ValidateSystemUpdate(Result *out_result, UpdateValidationInfo *out_info, const ncm::PackagedContentMetaReader &update_reader, const char *package_root) { + /* Clear output. */ + *out_result = ResultSuccess(); + + /* We want to track all content the update requires. */ + const size_t num_content_metas = update_reader.GetContentMetaCount(); + bool content_meta_valid[num_content_metas] = {}; + + /* Allocate a buffer to use for validation. */ + size_t data_buffer_size = 1_MB; + void *data_buffer; + do { + data_buffer = std::malloc(data_buffer_size); + if (data_buffer != nullptr) { + break; + } + + data_buffer_size /= 2; + } while (data_buffer_size >= 16_KB); + R_UNLESS(data_buffer != nullptr, fs::ResultAllocationFailureInNew()); + + ON_SCOPE_EXIT { std::free(data_buffer); }; + + /* Declare helper for result validation. */ + auto ValidateResult = [&] ALWAYS_INLINE_LAMBDA (Result result) -> Result { + *out_result = result; + return result; + }; + + /* Iterate over all files to find all content metas. */ + R_TRY(ForEachFileInDirectory(package_root, [&](bool *done, const fs::DirectoryEntry &entry) -> Result { + /* Clear output. */ + *out_info = {}; + + /* Don't early terminate by default. */ + *done = false; + + /* We have nothing to list if we're not looking at a meta. */ + R_SUCCEED_IF(!PathView(entry.name).HasSuffix(".cnmt.nca")); + + /* Read the content meta path, and build. */ + ncm::AutoBuffer package_meta; + R_TRY(LoadContentMeta(std::addressof(package_meta), package_root, entry)); + + /* Create a reader. */ + const auto reader = ncm::PackagedContentMetaReader(package_meta.Get(), package_meta.GetSize()); + + /* Get the key for the reader. */ + const auto key = reader.GetKey(); + + /* Check if we need to validate this content. */ + bool need_validate = false; + size_t validation_index = 0; + for (size_t i = 0; i < num_content_metas; ++i) { + if (update_reader.GetContentMetaInfo(i)->ToKey() == key) { + need_validate = true; + validation_index = i; + break; + } + } + + /* If we don't need to validate, continue. */ + R_SUCCEED_IF(!need_validate); + + /* We're validating. */ + out_info->invalid_key = key; + + /* Validate all contents. */ + for (size_t i = 0; i < reader.GetContentCount(); ++i) { + const auto *content_info = reader.GetContentInfo(i); + const auto &content_id = content_info->GetId(); + const s64 content_size = content_info->info.GetSize(); + out_info->invalid_content_id = content_id; + + /* Get the content id string. */ + auto content_id_str = ncm::GetContentIdString(content_id); + + /* Open the file. */ + fs::FileHandle file; + { + char path[fs::EntryNameLengthMax]; + std::snprintf(path, sizeof(path), "%s%s%s", package_root, content_id_str.data, content_info->GetType() == ncm::ContentType::Meta ? ".cnmt.nca" : ".nca"); + if (R_FAILED(ValidateResult(fs::OpenFile(std::addressof(file), path, ams::fs::OpenMode_Read)))) { + *done = true; + return ResultSuccess(); + } + } + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + /* Validate the file size is correct. */ + s64 file_size; + if (R_FAILED(ValidateResult(fs::GetFileSize(std::addressof(file_size), file)))) { + *done = true; + return ResultSuccess(); + } + if (file_size != content_size) { + *out_result = ncm::ResultInvalidContentHash(); + *done = true; + return ResultSuccess(); + } + + /* Read and hash the file in chunks. */ + crypto::Sha256Generator sha; + sha.Initialize(); + + s64 ofs = 0; + while (ofs < content_size) { + const size_t cur_size = std::min(static_cast(content_size - ofs), data_buffer_size); + if (R_FAILED(ValidateResult(fs::ReadFile(file, ofs, data_buffer, cur_size)))) { + *done = true; + return ResultSuccess(); + } + + sha.Update(data_buffer, cur_size); + + ofs += cur_size; + } + + /* Get the hash. */ + ncm::Digest calc_digest; + sha.GetHash(std::addressof(calc_digest), sizeof(calc_digest)); + + /* Validate the hash. */ + if (std::memcmp(std::addressof(calc_digest), std::addressof(content_info->digest), sizeof(ncm::Digest)) != 0) { + *out_result = ncm::ResultInvalidContentHash(); + *done = true; + return ResultSuccess(); + } + } + + /* Mark the relevant content as validated. */ + content_meta_valid[validation_index] = true; + *out_info = {}; + + return ResultSuccess(); + })); + + /* If we're otherwise going to succeed, ensure that every content was found. */ + if (R_SUCCEEDED(*out_result)) { + for (size_t i = 0; i < num_content_metas; ++i) { + if (!content_meta_valid[i]) { + *out_result = fs::ResultPathNotFound(); + *out_info = { + .invalid_key = update_reader.GetContentMetaInfo(i)->ToKey(), + }; + break; + } + } + } + + return ResultSuccess(); + } + Result ActivateSystemUpdateContentMetaDatabase() { /* TODO: Don't use gamecard db. */ return ncm::ActivateContentMetaDatabase(ncm::StorageId::GameCard); @@ -244,4 +397,29 @@ namespace ams::mitm::sysupdater { return ResultSuccess(); } + Result SystemUpdateService::ValidateUpdate(sf::Out out_validate_result, sf::Out out_validate_info, const ncm::Path &path) { + /* Adjust the path. */ + ncm::Path package_root; + R_TRY(FormatUserPackagePath(std::addressof(package_root), path)); + + /* Parse the update. */ + { + /* Get the content info for the system update. */ + ncm::ContentInfo content_info; + R_TRY(GetSystemUpdateUpdateContentInfoFromPackage(std::addressof(content_info), package_root.str)); + + /* Read the content meta. */ + ncm::AutoBuffer content_meta_buffer; + R_TRY(ReadContentMetaPath(std::addressof(content_meta_buffer), package_root.str, content_info)); + + /* Create a reader. */ + const auto reader = ncm::PackagedContentMetaReader(content_meta_buffer.Get(), content_meta_buffer.GetSize()); + + /* Validate the update. */ + R_TRY(ValidateSystemUpdate(out_validate_result.GetPointer(), out_validate_info.GetPointer(), reader, package_root.str)); + } + + return ResultSuccess(); + }; + } diff --git a/stratosphere/ams_mitm/source/sysupdater/sysupdater_service.hpp b/stratosphere/ams_mitm/source/sysupdater/sysupdater_service.hpp index 8d2cb39be..37b6f2ce8 100644 --- a/stratosphere/ams_mitm/source/sysupdater/sysupdater_service.hpp +++ b/stratosphere/ams_mitm/source/sysupdater/sysupdater_service.hpp @@ -27,16 +27,24 @@ namespace ams::mitm::sysupdater { ncm::FirmwareVariationId firmware_variation_ids[FirmwareVariationCountMax]; }; + struct UpdateValidationInfo { + ncm::ContentMetaKey invalid_key; + ncm::ContentId invalid_content_id; + }; + class SystemUpdateService final : public sf::IServiceObject { private: enum class CommandId { GetUpdateInformation = 0, + ValidateUpdate = 1, }; private: Result GetUpdateInformation(sf::Out out, const ncm::Path &path); + Result ValidateUpdate(sf::Out out_validate_result, sf::Out out_validate_info, const ncm::Path &path); public: DEFINE_SERVICE_DISPATCH_TABLE { MAKE_SERVICE_COMMAND_META(GetUpdateInformation), + MAKE_SERVICE_COMMAND_META(ValidateUpdate), }; }; diff --git a/stratosphere/ams_mitm/source/sysupdater/sysupdater_thread_allocator.cpp b/stratosphere/ams_mitm/source/sysupdater/sysupdater_thread_allocator.cpp new file mode 100644 index 000000000..f01997683 --- /dev/null +++ b/stratosphere/ams_mitm/source/sysupdater/sysupdater_thread_allocator.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include "sysupdater_thread_allocator.hpp" + +namespace ams::mitm::sysupdater { + + Result ThreadAllocator::Allocate(ThreadInfo *out) { + std::scoped_lock lk(this->mutex); + + for (int i = 0; i < this->thread_count; ++i) { + const u64 mask = (static_cast(1) << i); + if ((this->bitmap & mask) == 0) { + *out = { + .thread = this->thread_list + i, + .priority = this->thread_priority, + .stack = this->stack_heap + (this->stack_size * i), + .stack_size = this->stack_size, + }; + this->bitmap |= mask; + return ResultSuccess(); + } + } + + return ns::ResultOutOfMaxRunningTask(); + } + + void ThreadAllocator::Free(const ThreadInfo &info) { + std::scoped_lock lk(this->mutex); + + for (int i = 0; i < this->thread_count; ++i) { + if (info.thread == std::addressof(this->thread_list[i])) { + const u64 mask = (static_cast(1) << i); + this->bitmap &= ~mask; + return; + } + } + + AMS_ABORT("Invalid thread passed to ThreadAllocator::Free"); + } + +} diff --git a/stratosphere/ams_mitm/source/sysupdater/sysupdater_thread_allocator.hpp b/stratosphere/ams_mitm/source/sysupdater/sysupdater_thread_allocator.hpp new file mode 100644 index 000000000..9c57570c6 --- /dev/null +++ b/stratosphere/ams_mitm/source/sysupdater/sysupdater_thread_allocator.hpp @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::mitm::sysupdater { + + struct ThreadInfo { + os::ThreadType *thread; + int priority; + void *stack; + size_t stack_size; + }; + + /* NOTE: Nintendo uses a util::BitArray, but this seems excessive. */ + class ThreadAllocator { + private: + os::ThreadType *thread_list; + const int thread_priority; + const int thread_count; + u8 *stack_heap; + const size_t stack_heap_size; + const size_t stack_size; + u64 bitmap; + os::SdkMutex mutex; + public: + constexpr ThreadAllocator(os::ThreadType *thread_list, int count, int priority, u8 *stack_heap, size_t stack_heap_size, size_t stack_size) + : thread_list(thread_list), thread_priority(priority), thread_count(count), stack_heap(stack_heap), stack_heap_size(stack_heap_size), stack_size(stack_size), bitmap() + { + AMS_ASSERT(count <= static_cast(stack_heap_size / stack_size)); + AMS_ASSERT(count <= static_cast(BITSIZEOF(this->bitmap))); + } + + Result Allocate(ThreadInfo *out); + void Free(const ThreadInfo &info); + }; + +}