Switch to using exceptions for guest exiting

Guest-driven exiting could cause objects left on the heap due to a `std::longjmp` from high up in the host call stack, this has been fixed by introducing `ExitException` which implicitly unrolls the stack with the exception handling mechanism.
This commit is contained in:
PixelyIon 2021-10-16 12:34:24 +01:00
parent eff5711c49
commit 3b4bbd2b38
4 changed files with 33 additions and 9 deletions

View File

@ -2,6 +2,7 @@
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) // Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include <os.h> #include <os.h>
#include <nce.h>
#include <kernel/types/KProcess.h> #include <kernel/types/KProcess.h>
#include <common/trace.h> #include <common/trace.h>
#include <vfs/npdm.h> #include <vfs/npdm.h>
@ -218,9 +219,7 @@ namespace skyline::kernel::svc {
void ExitProcess(const DeviceState &state) { void ExitProcess(const DeviceState &state) {
state.logger->Debug("Exiting process"); state.logger->Debug("Exiting process");
if (state.thread->id) throw nce::NCE::ExitException(true);
state.process->Kill(false);
std::longjmp(state.thread->originalCtx, true);
} }
constexpr i32 IdealCoreDontCare{-1}; constexpr i32 IdealCoreDontCare{-1};
@ -279,7 +278,7 @@ namespace skyline::kernel::svc {
void ExitThread(const DeviceState &state) { void ExitThread(const DeviceState &state) {
state.logger->Debug("Exiting current thread"); state.logger->Debug("Exiting current thread");
std::longjmp(state.thread->originalCtx, true); throw nce::NCE::ExitException(false);
} }
void SleepThread(const DeviceState &state) { void SleepThread(const DeviceState &state) {

View File

@ -14,6 +14,12 @@
#include "nce.h" #include "nce.h"
namespace skyline::nce { namespace skyline::nce {
NCE::ExitException::ExitException(bool killAllThreads) : killAllThreads(killAllThreads) {}
const char *NCE::ExitException::what() const noexcept {
return killAllThreads ? "ExitProcess" : "ExitThread";
}
void NCE::SvcHandler(u16 svcId, ThreadContext *ctx) { void NCE::SvcHandler(u16 svcId, ThreadContext *ctx) {
TRACE_EVENT_END("guest"); TRACE_EVENT_END("guest");
@ -42,6 +48,12 @@ namespace skyline::nce {
} }
abi::__cxa_end_catch(); // We call this prior to the longjmp to cause the exception object to be destroyed abi::__cxa_end_catch(); // We call this prior to the longjmp to cause the exception object to be destroyed
std::longjmp(state.thread->originalCtx, true); std::longjmp(state.thread->originalCtx, true);
} catch (const ExitException &e) {
if (e.killAllThreads && state.thread->id) {
signal::BlockSignal({SIGINT});
state.process->Kill(false);
}
std::longjmp(state.thread->originalCtx, true);
} catch (const std::exception &e) { } catch (const std::exception &e) {
if (svc) if (svc)
state.logger->ErrorNoPrefix("{} (SVC: {})\nStack Trace:{}", e.what(), svc.name, state.loader->GetStackTrace()); state.logger->ErrorNoPrefix("{} (SVC: {})\nStack Trace:{}", e.what(), svc.name, state.loader->GetStackTrace());
@ -94,7 +106,7 @@ namespace skyline::nce {
std::ifstream status("/proc/self/status"); std::ifstream status("/proc/self/status");
constexpr std::string_view TracerPidTag = "TracerPid:"; constexpr std::string_view TracerPidTag = "TracerPid:";
for (std::string line; std::getline(status, line); ) { for (std::string line; std::getline(status, line);) {
if (line.starts_with(TracerPidTag)) { if (line.starts_with(TracerPidTag)) {
line = line.substr(TracerPidTag.size()); line = line.substr(TracerPidTag.size());

View File

@ -17,6 +17,21 @@ namespace skyline::nce {
static void SvcHandler(u16 svcId, ThreadContext *ctx); static void SvcHandler(u16 svcId, ThreadContext *ctx);
public: public:
/**
* @brief An exception which causes the throwing thread to exit alongside all threads optionally
* @note Exiting must not be performed directly as it could leak temporary objects on the stack by not calling their destructors
*/
struct ExitException : std::exception {
bool killAllThreads; //!< If to kill all threads or just the throwing thread
ExitException(bool killAllThreads = true);
virtual const char* what() const noexcept;
};
/**
* @brief Handles any signals in the NCE threads
*/
static void SignalHandler(int signal, siginfo *info, ucontext *ctx, void **tls); static void SignalHandler(int signal, siginfo *info, ucontext *ctx, void **tls);
NCE(const DeviceState &state); NCE(const DeviceState &state);

View File

@ -1,6 +1,7 @@
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) // Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include <nce.h>
#include <kernel/types/KProcess.h> #include <kernel/types/KProcess.h>
#include <services/hosbinder/IHOSBinderDriver.h> #include <services/hosbinder/IHOSBinderDriver.h>
#include "ISelfController.h" #include "ISelfController.h"
@ -9,10 +10,7 @@ namespace skyline::service::am {
ISelfController::ISelfController(const DeviceState &state, ServiceManager &manager) : libraryAppletLaunchableEvent(std::make_shared<type::KEvent>(state, false)), accumulatedSuspendedTickChangedEvent(std::make_shared<type::KEvent>(state, false)), hosbinder(manager.CreateOrGetService<hosbinder::IHOSBinderDriver>("dispdrv")), BaseService(state, manager) {} ISelfController::ISelfController(const DeviceState &state, ServiceManager &manager) : libraryAppletLaunchableEvent(std::make_shared<type::KEvent>(state, false)), accumulatedSuspendedTickChangedEvent(std::make_shared<type::KEvent>(state, false)), hosbinder(manager.CreateOrGetService<hosbinder::IHOSBinderDriver>("dispdrv")), BaseService(state, manager) {}
Result ISelfController::Exit(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { Result ISelfController::Exit(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
if (state.thread->id) throw nce::NCE::ExitException(true);
state.process->Kill(false);
std::longjmp(state.thread->originalCtx, true);
return {};
} }
Result ISelfController::LockExit(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { Result ISelfController::LockExit(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {