/* * 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 "sm_service_manager.hpp" #include "../sm_wait_list.hpp" namespace ams::sm::impl { namespace { /* Constexpr definitions. */ static constexpr size_t ProcessCountMax = 0x50; static constexpr size_t ServiceCountMax = 0x100 + 0x10; /* Extra 0x10 services over Nintendo for homebrew. */ static constexpr size_t FutureMitmCountMax = 0x20; static constexpr size_t AccessControlSizeMax = 0x200; constexpr sm::ServiceName InitiallyDeferredServices[] = { ServiceName::Encode("fsp-srv") }; /* Types. */ struct ProcessInfo { os::ProcessId process_id; ncm::ProgramId program_id; cfg::OverrideStatus override_status; size_t access_control_size; u8 access_control[AccessControlSizeMax]; }; constexpr ProcessInfo InvalidProcessInfo = { .process_id = os::InvalidProcessId, .program_id = ncm::InvalidProgramId, .override_status = {}, .access_control_size = 0, .access_control = {}, }; struct ServiceInfo { ServiceName name; os::ProcessId owner_process_id; os::ProcessId mitm_process_id; os::ProcessId mitm_waiting_ack_process_id; svc::Handle mitm_port_h; svc::Handle mitm_query_h; svc::Handle port_h; svc::Handle mitm_fwd_sess_h; s32 max_sessions; bool is_light; bool mitm_waiting_ack; }; constexpr ServiceInfo InvalidServiceInfo = { .name = sm::InvalidServiceName, .owner_process_id = os::InvalidProcessId, .mitm_process_id = os::InvalidProcessId, .mitm_waiting_ack_process_id = os::InvalidProcessId, .mitm_port_h = svc::InvalidHandle, .mitm_query_h = svc::InvalidHandle, .port_h = svc::InvalidHandle, .mitm_fwd_sess_h = svc::InvalidHandle, .max_sessions = 0, .is_light = false, .mitm_waiting_ack = false, }; class AccessControlEntry { private: const u8 *m_entry; size_t m_capacity; public: AccessControlEntry(const void *e, size_t c) : m_entry(static_cast(e)), m_capacity(c) { /* ... */ } AccessControlEntry GetNextEntry() const { return AccessControlEntry(m_entry + this->GetSize(), m_capacity - this->GetSize()); } size_t GetSize() const { return this->GetServiceNameSize() + 1; } size_t GetServiceNameSize() const { return (m_entry[0] & 7) + 1; } ServiceName GetServiceName() const { return ServiceName::Encode(reinterpret_cast(m_entry + 1), this->GetServiceNameSize()); } bool IsHost() const { return (m_entry[0] & 0x80) != 0; } bool IsWildcard() const { return m_entry[this->GetServiceNameSize()] == '*'; } bool IsValid() const { /* Validate that we can access data. */ if (m_entry == nullptr || m_capacity == 0) { return false; } /* Validate that the size is correct. */ return this->GetSize() <= m_capacity; } }; class InitialProcessIdLimits { private: os::ProcessId min; os::ProcessId max; public: InitialProcessIdLimits() { /* Retrieve process limits. */ cfg::GetInitialProcessRange(&this->min, &this->max); /* Ensure range is sane. */ AMS_ABORT_UNLESS(this->min <= this->max); } bool IsInitialProcess(os::ProcessId process_id) const { AMS_ABORT_UNLESS(process_id != os::InvalidProcessId); return this->min <= process_id && process_id <= this->max; } }; /* Static members. */ /* NOTE: In 12.0.0, Nintendo added multithreaded processing to sm; however, official sm does not do */ /* any kind of mutual exclusivity when accessing (and modifying) global state. Previously, this was */ /* not a problem, because sm was strictly single-threaded, and so two threads could not race eachother. */ /* We will add a mutex (and perform locking) in order to prevent simultaneous access to global state. */ constinit os::Mutex g_mutex{true}; constinit std::array g_process_list = [] { std::array list = {}; /* Initialize each info. */ for (auto &process_info : list) { process_info = InvalidProcessInfo; } return list; }(); constinit std::array g_service_list = [] { std::array list = {}; /* Initialize each info. */ for (auto &service_info : list) { service_info = InvalidServiceInfo; } return list; }(); constinit std::array g_future_mitm_list = [] { std::array list = {}; /* Initialize each info. */ for (auto &name : list) { name = InvalidServiceName; } return list; }(); constinit bool g_ended_initial_defers = false; InitialProcessIdLimits g_initial_process_id_limits; /* Helper functionality. */ bool IsInitialProcess(os::ProcessId process_id) { return g_initial_process_id_limits.IsInitialProcess(process_id); } constexpr inline bool IsValidProcessId(os::ProcessId process_id) { return process_id != os::InvalidProcessId; } Result ValidateAccessControl(AccessControlEntry access_control, ServiceName service, bool is_host, bool is_wildcard) { /* Iterate over all entries in the access control, checking to see if we have a match. */ while (access_control.IsValid()) { if (access_control.IsHost() == is_host) { bool is_valid = true; if (access_control.IsWildcard() == is_wildcard) { /* Check for exact match. */ is_valid &= access_control.GetServiceName() == service; } else if (access_control.IsWildcard()) { /* Also allow fuzzy match for wildcard. */ ServiceName ac_service = access_control.GetServiceName(); is_valid &= std::memcmp(std::addressof(ac_service), std::addressof(service), access_control.GetServiceNameSize() - 1) == 0; } R_SUCCEED_IF(is_valid); } access_control = access_control.GetNextEntry(); } return sm::ResultNotAllowed(); } Result ValidateAccessControl(AccessControlEntry restriction, AccessControlEntry access) { /* Ensure that every entry in the access control is allowed by the restriction control. */ while (access.IsValid()) { R_TRY(ValidateAccessControl(restriction, access.GetServiceName(), access.IsHost(), access.IsWildcard())); access = access.GetNextEntry(); } return ResultSuccess(); } Result ValidateServiceName(ServiceName service) { /* Service names must be non-empty. */ R_UNLESS(service.name[0] != 0, sm::ResultInvalidServiceName()); /* Get name length. */ size_t name_len; for (name_len = 1; name_len < sizeof(service); name_len++) { if (service.name[name_len] == 0) { break; } } /* Names must be all-zero after they end. */ while (name_len < sizeof(service)) { R_UNLESS(service.name[name_len++] == 0, sm::ResultInvalidServiceName()); } return ResultSuccess(); } bool ShouldDeferForInit(ServiceName service) { /* Once end has been called, we're done. */ if (g_ended_initial_defers) { return false; } /* This is a mechanism by which certain services will always be deferred until sm:m receives a special command. */ /* This can be extended with more services as needed at a later date. */ for (const auto &service_name : InitiallyDeferredServices) { if (service == service_name) { return true; } } return false; } ProcessInfo *GetProcessInfo(os::ProcessId process_id) { /* Find a process info with a matching id. */ for (auto &process_info : g_process_list) { if (process_info.process_id == process_id) { return std::addressof(process_info); } } return nullptr; } ProcessInfo *GetFreeProcessInfo() { return GetProcessInfo(os::InvalidProcessId); } bool HasProcessInfo(os::ProcessId process_id) { return GetProcessInfo(process_id) != nullptr; } ServiceInfo *GetServiceInfo(ServiceName service_name) { /* Find a service with a matching name. */ for (auto &service_info : g_service_list) { if (service_info.name == service_name) { return std::addressof(service_info); } } return nullptr; } ServiceInfo *GetFreeServiceInfo() { return GetServiceInfo(InvalidServiceName); } bool HasServiceInfo(ServiceName service) { return GetServiceInfo(service) != nullptr; } bool HasMitm(ServiceName service) { const ServiceInfo *service_info = GetServiceInfo(service); return service_info != nullptr && IsValidProcessId(service_info->mitm_process_id); } Result AddFutureMitmDeclaration(ServiceName service) { for (auto &future_mitm : g_future_mitm_list) { if (future_mitm == InvalidServiceName) { future_mitm = service; return ResultSuccess(); } } return sm::ResultOutOfServices(); } bool HasFutureMitmDeclaration(ServiceName service) { for (const auto &future_mitm : g_future_mitm_list){ if (future_mitm == service) { return true; } } return false; } void ClearFutureMitmDeclaration(ServiceName service) { for (auto &future_mitm : g_future_mitm_list) { if (future_mitm == service) { future_mitm = InvalidServiceName; } } /* This might undefer some requests. */ TriggerResume(service); } void GetMitmProcessInfo(MitmProcessInfo *out_info, os::ProcessId process_id) { /* Anything that can request a mitm session must have a process info. */ const auto process_info = GetProcessInfo(process_id); AMS_ABORT_UNLESS(process_info != nullptr); /* Write to output. */ out_info->process_id = process_id; out_info->program_id = process_info->program_id; out_info->override_status = process_info->override_status; } bool IsMitmDisallowed(ncm::ProgramId program_id) { /* Mitm used on certain programs can prevent the boot process from completing. */ /* TODO: Is there a way to do this that's less hardcoded? Needs design thought. */ return program_id == ncm::SystemProgramId::Loader || program_id == ncm::SystemProgramId::Pm || program_id == ncm::SystemProgramId::Spl || program_id == ncm::SystemProgramId::Boot || program_id == ncm::SystemProgramId::Ncm || program_id == ncm::AtmosphereProgramId::Mitm || program_id == ncm::SystemProgramId::Creport; } Result GetMitmServiceHandleImpl(svc::Handle *out, ServiceInfo *service_info, const MitmProcessInfo &client_info) { /* Send command to query if we should mitm. */ bool should_mitm; { /* TODO: Convert mitm internal messaging to use tipc? */ ::Service srv { .session = service_info->mitm_query_h }; R_ABORT_UNLESS(::serviceDispatchInOut(std::addressof(srv), 65000, client_info, should_mitm)); } /* If we shouldn't mitm, give normal session. */ R_UNLESS(should_mitm, svc::ConnectToPort(out, service_info->port_h)); /* Create both handles. */ { /* Get the forward handle. */ svc::Handle fwd_hnd; R_TRY(svc::ConnectToPort(std::addressof(fwd_hnd), service_info->port_h)); /* Ensure that the forward handle is closed, if we fail to get the mitm handle. */ auto fwd_guard = SCOPE_GUARD { R_ABORT_UNLESS(svc::CloseHandle(fwd_hnd)); }; /* Get the mitm handle. */ /* This should be guaranteed to succeed, since we got a forward handle. */ svc::Handle hnd; R_ABORT_UNLESS(svc::ConnectToPort(std::addressof(hnd), service_info->mitm_port_h)); /* We got both handles, so we no longer need to clean up the forward handle. */ fwd_guard.Cancel(); /* Save the handles to their respective storages. */ service_info->mitm_fwd_sess_h = fwd_hnd; *out = hnd; } service_info->mitm_waiting_ack_process_id = client_info.process_id; service_info->mitm_waiting_ack = true; return ResultSuccess(); } Result GetServiceHandleImpl(svc::Handle *out, ServiceInfo *service_info, os::ProcessId process_id) { /* Clear handle output. */ *out = svc::InvalidHandle; /* Check if we should return a mitm handle. */ if (IsValidProcessId(service_info->mitm_process_id) && service_info->mitm_process_id != process_id) { /* Get mitm process info, ensure that we're allowed to mitm the given program. */ MitmProcessInfo client_info; GetMitmProcessInfo(std::addressof(client_info), process_id); if (!IsMitmDisallowed(client_info.program_id)) { /* Get a mitm service handle. */ return GetMitmServiceHandleImpl(out, service_info, client_info); } } /* We're not returning a mitm handle, so just return a normal port handle. */ return svc::ConnectToPort(out, service_info->port_h); } Result RegisterServiceImpl(svc::Handle *out, os::ProcessId process_id, ServiceName service, size_t max_sessions, bool is_light) { /* Validate service name. */ R_TRY(ValidateServiceName(service)); /* Don't try to register something already registered. */ R_UNLESS(!HasServiceInfo(service), sm::ResultAlreadyRegistered()); /* Get free service. */ ServiceInfo *free_service = GetFreeServiceInfo(); R_UNLESS(free_service != nullptr, sm::ResultOutOfServices()); /* Create the new service. */ *out = svc::InvalidHandle; svc::Handle server_hnd = svc::InvalidHandle; R_TRY(svc::CreatePort(out, std::addressof(server_hnd), max_sessions, is_light, reinterpret_cast(free_service->name.name))); /* Save info. */ free_service->name = service; free_service->owner_process_id = process_id; free_service->max_sessions = max_sessions; free_service->is_light = is_light; free_service->port_h = server_hnd; /* This might undefer some requests. */ TriggerResume(service); return ResultSuccess(); } void UnregisterServiceImpl(ServiceInfo *service_info) { /* Close all valid handles. */ if (service_info->port_h != svc::InvalidHandle) { R_ABORT_UNLESS(svc::CloseHandle(service_info->port_h)); } if (service_info->mitm_port_h != svc::InvalidHandle) { R_ABORT_UNLESS(svc::CloseHandle(service_info->mitm_port_h)); } if (service_info->mitm_query_h != svc::InvalidHandle) { R_ABORT_UNLESS(svc::CloseHandle(service_info->mitm_query_h)); } if (service_info->mitm_fwd_sess_h != svc::InvalidHandle) { R_ABORT_UNLESS(svc::CloseHandle(service_info->mitm_fwd_sess_h)); } /* Reset the info's state. */ *service_info = InvalidServiceInfo; } } /* Client disconnection callback. */ void OnClientDisconnected(os::ProcessId process_id) { /* Acquire exclusive access to global state. */ std::scoped_lock lk(g_mutex); /* Ensure that the process id is valid. */ if (process_id == os::InvalidProcessId) { return; } /* Unregister all services a client hosts, on attached-client-close. */ for (auto &service_info : g_service_list) { if (service_info.name != InvalidServiceName && service_info.owner_process_id == process_id) { UnregisterServiceImpl(std::addressof(service_info)); } } } /* Process management. */ Result RegisterProcess(os::ProcessId process_id, ncm::ProgramId program_id, cfg::OverrideStatus override_status, const void *acid_sac, size_t acid_sac_size, const void *aci_sac, size_t aci_sac_size) { /* Acquire exclusive access to global state. */ std::scoped_lock lk(g_mutex); /* Check that access control will fit in the ServiceInfo. */ R_UNLESS(aci_sac_size <= AccessControlSizeMax, sm::ResultTooLargeAccessControl()); /* Get free process. */ ProcessInfo *proc = GetFreeProcessInfo(); R_UNLESS(proc != nullptr, sm::ResultOutOfProcesses()); /* Validate restrictions. */ R_UNLESS(aci_sac_size != 0, sm::ResultNotAllowed()); R_TRY(ValidateAccessControl(AccessControlEntry(acid_sac, acid_sac_size), AccessControlEntry(aci_sac, aci_sac_size))); /* Save info. */ proc->process_id = process_id; proc->program_id = program_id; proc->override_status = override_status; proc->access_control_size = aci_sac_size; std::memcpy(proc->access_control, aci_sac, proc->access_control_size); return ResultSuccess(); } Result UnregisterProcess(os::ProcessId process_id) { /* Acquire exclusive access to global state. */ std::scoped_lock lk(g_mutex); /* Find the process. */ ProcessInfo *proc = GetProcessInfo(process_id); R_UNLESS(proc != nullptr, sm::ResultInvalidClient()); /* Free the process. */ *proc = InvalidProcessInfo; return ResultSuccess(); } /* Service management. */ Result HasService(bool *out, ServiceName service) { /* Acquire exclusive access to global state. */ std::scoped_lock lk(g_mutex); /* Validate service name. */ R_TRY(ValidateServiceName(service)); *out = HasServiceInfo(service); return ResultSuccess(); } Result WaitService(ServiceName service) { /* Acquire exclusive access to global state. */ std::scoped_lock lk(g_mutex); /* Check that we have the service. */ bool has_service = false; R_TRY(impl::HasService(&has_service, service)); /* If we do, we can succeed immediately. */ R_SUCCEED_IF(has_service); /* Otherwise, we want to wait until the service is registered. */ return StartRegisterRetry(service); } Result GetServiceHandle(svc::Handle *out, os::ProcessId process_id, ServiceName service) { /* Acquire exclusive access to global state. */ std::scoped_lock lk(g_mutex); /* Validate service name. */ R_TRY(ValidateServiceName(service)); /* In 8.0.0, Nintendo removed the service apm:p -- however, all homebrew attempts to get */ /* a handle to this when calling appletInitialize(). Because hbl has access to all services, */ /* This would return true, and homebrew would *wait forever* trying to get a handle to a service */ /* that will never register. Thus, in the interest of not breaking every single piece of homebrew */ /* we will provide a little first class help. */ constexpr ServiceName ApmP = ServiceName::Encode("apm:p"); R_UNLESS((hos::GetVersion() < hos::Version_8_0_0) || (service != ApmP), sm::ResultNotAllowed()); /* Check that the process is registered and allowed to get the service. */ if (!IsInitialProcess(process_id)) { ProcessInfo *proc = GetProcessInfo(process_id); R_UNLESS(proc != nullptr, sm::ResultInvalidClient()); R_TRY(ValidateAccessControl(AccessControlEntry(proc->access_control, proc->access_control_size), service, false, false)); } /* Get service info. Check to see if we need to defer this until later. */ ServiceInfo *service_info = GetServiceInfo(service); if (service_info == nullptr || ShouldDeferForInit(service) || HasFutureMitmDeclaration(service) || service_info->mitm_waiting_ack) { return StartRegisterRetry(service); } /* Get a handle from the service info. */ R_TRY_CATCH(GetServiceHandleImpl(out, service_info, process_id)) { R_CONVERT(svc::ResultOutOfSessions, sm::ResultOutOfSessions()) } R_END_TRY_CATCH; return ResultSuccess(); } Result RegisterService(svc::Handle *out, os::ProcessId process_id, ServiceName service, size_t max_sessions, bool is_light) { /* Acquire exclusive access to global state. */ std::scoped_lock lk(g_mutex); /* Validate service name. */ R_TRY(ValidateServiceName(service)); /* Check that the process is registered and allowed to register the service. */ if (!IsInitialProcess(process_id)) { ProcessInfo *proc = GetProcessInfo(process_id); R_UNLESS(proc != nullptr, sm::ResultInvalidClient()); R_TRY(ValidateAccessControl(AccessControlEntry(proc->access_control, proc->access_control_size), service, true, false)); } /* Check that the service isn't already registered. */ R_UNLESS(!HasServiceInfo(service), sm::ResultAlreadyRegistered()); return RegisterServiceImpl(out, process_id, service, max_sessions, is_light); } Result RegisterServiceForSelf(svc::Handle *out, ServiceName service, size_t max_sessions) { /* Acquire exclusive access to global state. */ std::scoped_lock lk(g_mutex); return RegisterServiceImpl(out, os::GetCurrentProcessId(), service, max_sessions, false); } Result UnregisterService(os::ProcessId process_id, ServiceName service) { /* Acquire exclusive access to global state. */ std::scoped_lock lk(g_mutex); /* Validate service name. */ R_TRY(ValidateServiceName(service)); /* Check that the process is registered. */ if (!IsInitialProcess(process_id)) { R_UNLESS(HasProcessInfo(process_id), sm::ResultInvalidClient()); } /* Ensure that the service is actually registered. */ ServiceInfo *service_info = GetServiceInfo(service); R_UNLESS(service_info != nullptr, sm::ResultNotRegistered()); /* Check if we have permission to do this. */ R_UNLESS(service_info->owner_process_id == process_id, sm::ResultNotAllowed()); /* Unregister the service. */ UnregisterServiceImpl(service_info); return ResultSuccess(); } /* Mitm extensions. */ Result HasMitm(bool *out, ServiceName service) { /* Acquire exclusive access to global state. */ std::scoped_lock lk(g_mutex); /* Validate service name. */ R_TRY(ValidateServiceName(service)); /* Get whether we have a mitm. */ *out = HasMitm(service); return ResultSuccess(); } Result WaitMitm(ServiceName service) { /* Acquire exclusive access to global state. */ std::scoped_lock lk(g_mutex); /* Check that we have the mitm. */ bool has_mitm = false; R_TRY(impl::HasMitm(std::addressof(has_mitm), service)); /* If we do, we can succeed immediately. */ R_SUCCEED_IF(has_mitm); /* Otherwise, we want to wait until the service is registered. */ return StartRegisterRetry(service); } Result InstallMitm(svc::Handle *out, svc::Handle *out_query, os::ProcessId process_id, ServiceName service) { /* Acquire exclusive access to global state. */ std::scoped_lock lk(g_mutex); /* Validate service name. */ R_TRY(ValidateServiceName(service)); /* Check that the process is registered and allowed to register the service. */ if (!IsInitialProcess(process_id)) { ProcessInfo *proc = GetProcessInfo(process_id); R_UNLESS(proc != nullptr, sm::ResultInvalidClient()); R_TRY(ValidateAccessControl(AccessControlEntry(proc->access_control, proc->access_control_size), service, true, false)); } /* Validate that the service exists. */ ServiceInfo *service_info = GetServiceInfo(service); /* If it doesn't exist, defer until it does. */ R_UNLESS(service_info != nullptr, StartRegisterRetry(service)); /* Validate that the service isn't already being mitm'd. */ R_UNLESS(!IsValidProcessId(service_info->mitm_process_id), sm::ResultAlreadyRegistered()); /* Always clear output. */ *out = svc::InvalidHandle; *out_query = svc::InvalidHandle; /* If we don't have a future mitm declaration, add one. */ /* Client will clear this when ready to process. */ const bool has_existing_future_declaration = HasFutureMitmDeclaration(service); if (!has_existing_future_declaration) { R_TRY(AddFutureMitmDeclaration(service)); } auto future_guard = SCOPE_GUARD { if (!has_existing_future_declaration) { ClearFutureMitmDeclaration(service); } }; /* Create mitm handles. */ { /* Get the port handles. */ svc::Handle hnd, port_hnd; R_TRY(svc::CreatePort(std::addressof(hnd), std::addressof(port_hnd), service_info->max_sessions, service_info->is_light, reinterpret_cast(service_info->name.name))); /* Ensure that we clean up the port handles, if something goes wrong creating the query sessions. */ auto port_guard = SCOPE_GUARD { R_ABORT_UNLESS(svc::CloseHandle(hnd)); R_ABORT_UNLESS(svc::CloseHandle(port_hnd)); }; /* Create the session for our query service. */ svc::Handle qry_hnd, mitm_qry_hnd; R_TRY(svc::CreateSession(std::addressof(qry_hnd), std::addressof(mitm_qry_hnd), false, 0)); /* We created the query service session, so we no longer need to clean up the port handles. */ port_guard.Cancel(); /* Copy to output. */ service_info->mitm_process_id = process_id; service_info->mitm_port_h = port_hnd; service_info->mitm_query_h = mitm_qry_hnd; *out = hnd; *out_query = qry_hnd; /* This might undefer some requests. */ TriggerResume(service); } future_guard.Cancel(); return ResultSuccess(); } Result UninstallMitm(os::ProcessId process_id, ServiceName service) { /* Acquire exclusive access to global state. */ std::scoped_lock lk(g_mutex); /* Validate service name. */ R_TRY(ValidateServiceName(service)); /* Check that the process is registered. */ if (!IsInitialProcess(process_id)) { ProcessInfo *proc = GetProcessInfo(process_id); R_UNLESS(proc != nullptr, sm::ResultInvalidClient()); } /* Validate that the service exists. */ ServiceInfo *service_info = GetServiceInfo(service); R_UNLESS(service_info != nullptr, sm::ResultNotRegistered()); /* Validate that the client process_id is the mitm process. */ R_UNLESS(service_info->mitm_process_id == process_id, sm::ResultNotAllowed()); /* Uninstall the mitm. */ { /* Close mitm handles. */ R_ABORT_UNLESS(svc::CloseHandle(service_info->mitm_port_h)); R_ABORT_UNLESS(svc::CloseHandle(service_info->mitm_query_h)); /* Reset mitm members. */ service_info->mitm_port_h = svc::InvalidHandle; service_info->mitm_query_h = svc::InvalidHandle; service_info->mitm_process_id = os::InvalidProcessId; } return ResultSuccess(); } Result DeclareFutureMitm(os::ProcessId process_id, ServiceName service) { /* Acquire exclusive access to global state. */ std::scoped_lock lk(g_mutex); /* Validate service name. */ R_TRY(ValidateServiceName(service)); /* Check that the process is registered and allowed to register the service. */ if (!IsInitialProcess(process_id)) { ProcessInfo *proc = GetProcessInfo(process_id); R_UNLESS(proc != nullptr, sm::ResultInvalidClient()); R_TRY(ValidateAccessControl(AccessControlEntry(proc->access_control, proc->access_control_size), service, true, false)); } /* Check that mitm hasn't already been registered or declared. */ R_UNLESS(!HasMitm(service), sm::ResultAlreadyRegistered()); R_UNLESS(!HasFutureMitmDeclaration(service), sm::ResultAlreadyRegistered()); /* Try to forward declare it. */ R_TRY(AddFutureMitmDeclaration(service)); return ResultSuccess(); } Result ClearFutureMitm(os::ProcessId process_id, ServiceName service) { /* Acquire exclusive access to global state. */ std::scoped_lock lk(g_mutex); /* Validate service name. */ R_TRY(ValidateServiceName(service)); /* Check that the process is registered and allowed to register the service. */ if (!IsInitialProcess(process_id)) { ProcessInfo *proc = GetProcessInfo(process_id); R_UNLESS(proc != nullptr, sm::ResultInvalidClient()); R_TRY(ValidateAccessControl(AccessControlEntry(proc->access_control, proc->access_control_size), service, true, false)); } /* Check that a future mitm declaration is present or we have a mitm. */ if (HasMitm(service)) { /* Validate that the service exists. */ ServiceInfo *service_info = GetServiceInfo(service); R_UNLESS(service_info != nullptr, sm::ResultNotRegistered()); /* Validate that the client process_id is the mitm process. */ R_UNLESS(service_info->mitm_process_id == process_id, sm::ResultNotAllowed()); } else { R_UNLESS(HasFutureMitmDeclaration(service), sm::ResultNotRegistered()); } /* Clear the forward declaration. */ ClearFutureMitmDeclaration(service); return ResultSuccess(); } Result AcknowledgeMitmSession(MitmProcessInfo *out_info, svc::Handle *out_hnd, os::ProcessId process_id, ServiceName service) { /* Acquire exclusive access to global state. */ std::scoped_lock lk(g_mutex); /* Validate service name. */ R_TRY(ValidateServiceName(service)); /* Check that the process is registered. */ if (!IsInitialProcess(process_id)) { ProcessInfo *proc = GetProcessInfo(process_id); R_UNLESS(proc != nullptr, sm::ResultInvalidClient()); } /* Validate that the service exists. */ ServiceInfo *service_info = GetServiceInfo(service); R_UNLESS(service_info != nullptr, sm::ResultNotRegistered()); /* Validate that the client process_id is the mitm process, and that an acknowledgement is waiting. */ R_UNLESS(service_info->mitm_process_id == process_id, sm::ResultNotAllowed()); R_UNLESS(service_info->mitm_waiting_ack, sm::ResultNotAllowed()); /* Acknowledge. */ { /* Copy the mitm info to output. */ GetMitmProcessInfo(out_info, service_info->mitm_waiting_ack_process_id); /* Set the output handle. */ *out_hnd = service_info->mitm_fwd_sess_h; service_info->mitm_fwd_sess_h = svc::InvalidHandle; /* Clear acknowledgement-related fields. */ service_info->mitm_waiting_ack = false; service_info->mitm_waiting_ack_process_id = os::InvalidProcessId; } /* Undefer requests to the session. */ TriggerResume(service); return ResultSuccess(); } /* Deferral extension (works around FS bug). */ Result EndInitialDefers() { /* Acquire exclusive access to global state. */ std::scoped_lock lk(g_mutex); /* Note that we have ended the initial deferral period. */ g_ended_initial_defers = true; /* This might undefer some requests. */ for (const auto &service_name : InitiallyDeferredServices) { TriggerResume(service_name); } return ResultSuccess(); } }