1
0
Fork 0
mirror of https://github.com/Atmosphere-NX/Atmosphere.git synced 2024-12-28 21:26:14 +00:00

Merge pull request #585 from Atmosphere-NX/sm_rewrite

Completely re-write sm.
This commit is contained in:
SciresM 2019-06-21 17:37:06 -07:00 committed by GitHub
commit e62754ed56
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 1013 additions and 939 deletions

@ -1 +1 @@
Subproject commit 4ccee4257763373c8e8dd5453246a592482f9a86 Subproject commit 0c26276b21e4cf0f7a0665c0280b3888ebfbd1af

View file

@ -41,7 +41,7 @@ ARCH := -march=armv8-a -mtune=cortex-a57 -mtp=soft -fPIE
CFLAGS := -g -Wall -O2 -ffunction-sections \ CFLAGS := -g -Wall -O2 -ffunction-sections \
$(ARCH) $(DEFINES) $(ARCH) $(DEFINES)
CFLAGS += $(INCLUDE) -D__SWITCH__ -DSM_ENABLE_MITM -DSM_ENABLE_INIT_DEFERS -DSM_MINIMUM_SESSION_LIMIT=8 CFLAGS += $(INCLUDE) -D__SWITCH__ -DSM_MINIMUM_SESSION_LIMIT=8
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++17 CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++17

View file

@ -16,18 +16,22 @@
#include <switch.h> #include <switch.h>
#include <stratosphere.hpp> #include <stratosphere.hpp>
#include "sm_dmnt_service.hpp"
#include "sm_registration.hpp"
Result DmntService::AtmosphereGetRecord(Out<SmServiceRecord> record, SmServiceName service) { #include "sm_dmnt_service.hpp"
return Registration::GetServiceRecord(smEncodeName(service.name), record.GetPointer()); #include "sm_service_manager.hpp"
namespace sts::sm {
Result DmntService::AtmosphereGetRecord(Out<ServiceRecord> record, ServiceName service) {
return sm::GetServiceRecord(record.GetPointer(), service);
} }
void DmntService::AtmosphereListRecords(OutBuffer<SmServiceRecord> records, Out<u64> out_count, u64 offset) { void DmntService::AtmosphereListRecords(OutBuffer<ServiceRecord> records, Out<u64> out_count, u64 offset) {
Registration::ListServiceRecords(offset, records.num_elements, records.buffer, out_count.GetPointer()); R_ASSERT(sm::ListServiceRecords(records.buffer, out_count.GetPointer(), offset, records.num_elements));
} }
void DmntService::AtmosphereGetRecordSize(Out<u64> record_size) { void DmntService::AtmosphereGetRecordSize(Out<u64> record_size) {
record_size.SetValue(sizeof(SmServiceRecord)); record_size.SetValue(sizeof(ServiceRecord));
} }
}

View file

@ -19,22 +19,28 @@
#include <stratosphere.hpp> #include <stratosphere.hpp>
#include "sm_types.hpp" #include "sm_types.hpp"
enum DmntServiceCmd { namespace sts::sm {
Dmnt_Cmd_AtmosphereGetRecord = 65000,
Dmnt_Cmd_AtmosphereListRecords = 65001,
Dmnt_Cmd_AtmosphereGetRecordSize = 65002,
};
/* Service definition. */
class DmntService final : public IServiceObject { class DmntService final : public IServiceObject {
protected:
/* Command IDs. */
enum class CommandId {
AtmosphereGetRecord = 65000,
AtmosphereListRecords = 65001,
AtmosphereGetRecordSize = 65002,
};
private: private:
/* Actual commands. */ /* Actual commands. */
virtual Result AtmosphereGetRecord(Out<SmServiceRecord> record, SmServiceName service); virtual Result AtmosphereGetRecord(Out<ServiceRecord> record, ServiceName service);
virtual void AtmosphereListRecords(OutBuffer<SmServiceRecord> records, Out<u64> out_count, u64 offset); virtual void AtmosphereListRecords(OutBuffer<ServiceRecord> records, Out<u64> out_count, u64 offset);
virtual void AtmosphereGetRecordSize(Out<u64> record_size); virtual void AtmosphereGetRecordSize(Out<u64> record_size);
public: public:
DEFINE_SERVICE_DISPATCH_TABLE { DEFINE_SERVICE_DISPATCH_TABLE {
MakeServiceCommandMeta<Dmnt_Cmd_AtmosphereGetRecord, &DmntService::AtmosphereGetRecord>(), MakeServiceCommandMeta<CommandId::AtmosphereGetRecord, &DmntService::AtmosphereGetRecord>(),
MakeServiceCommandMeta<Dmnt_Cmd_AtmosphereListRecords, &DmntService::AtmosphereListRecords>(), MakeServiceCommandMeta<CommandId::AtmosphereListRecords, &DmntService::AtmosphereListRecords>(),
MakeServiceCommandMeta<Dmnt_Cmd_AtmosphereGetRecordSize, &DmntService::AtmosphereGetRecordSize>(), MakeServiceCommandMeta<CommandId::AtmosphereGetRecordSize, &DmntService::AtmosphereGetRecordSize>(),
}; };
}; };
}

View file

@ -22,10 +22,10 @@
#include <switch.h> #include <switch.h>
#include <stratosphere.hpp> #include <stratosphere.hpp>
#include "sm_manager_service.hpp" #include "sm_service_manager.hpp"
#include "sm_user_service.hpp" #include "sm_user_service.hpp"
#include "sm_manager_service.hpp"
#include "sm_dmnt_service.hpp" #include "sm_dmnt_service.hpp"
#include "sm_registration.hpp"
extern "C" { extern "C" {
extern u32 __start__; extern u32 __start__;
@ -75,31 +75,25 @@ void __appExit(void) {
/* Nothing to clean up, because we're sm. */ /* Nothing to clean up, because we're sm. */
} }
int main(int argc, char **argv) int main(int argc, char **argv)
{ {
consoleDebugInit(debugDevice_SVC); /* Create service waitable manager. */
/* TODO: What's a good timeout value to use here? */
static auto s_server_manager = WaitableManager(1); static auto s_server_manager = WaitableManager(1);
/* Create sm:, (and thus allow things to register to it). */ /* Create sm:, (and thus allow things to register to it). */
s_server_manager.AddWaitable(new ManagedPortServer<UserService>("sm:", 0x40)); s_server_manager.AddWaitable(new ManagedPortServer<sts::sm::UserService>("sm:", 0x40));
/* Create sm:m manually. */ /* Create sm:m manually. */
Handle smm_h; Handle smm_h;
R_ASSERT(Registration::RegisterServiceForSelf(smEncodeName("sm:m"), 1, false, &smm_h)); R_ASSERT(sts::sm::RegisterServiceForSelf(&smm_h, sts::sm::ServiceName::Encode("sm:m"), 1));
s_server_manager.AddWaitable(new ExistingPortServer<sts::sm::ManagerService>(smm_h, 1));
s_server_manager.AddWaitable(new ExistingPortServer<ManagerService>(smm_h, 1));
/*===== ATMOSPHERE EXTENSION =====*/ /*===== ATMOSPHERE EXTENSION =====*/
/* Create sm:dmnt manually. */ /* Create sm:dmnt manually. */
Handle smdmnt_h; Handle smdmnt_h;
R_ASSERT(Registration::RegisterServiceForSelf(smEncodeName("sm:dmnt"), 1, false, &smdmnt_h)); R_ASSERT(sts::sm::RegisterServiceForSelf(&smdmnt_h, sts::sm::ServiceName::Encode("sm:dmnt"), 1));
s_server_manager.AddWaitable(new ExistingPortServer<sts::sm::DmntService>(smm_h, 1));;
s_server_manager.AddWaitable(new ExistingPortServer<DmntService>(smm_h, 1));;
/*================================*/ /*================================*/
/* Loop forever, servicing our services. */ /* Loop forever, servicing our services. */
@ -108,4 +102,3 @@ int main(int argc, char **argv)
/* Cleanup. */ /* Cleanup. */
return 0; return 0;
} }

View file

@ -16,22 +16,26 @@
#include <switch.h> #include <switch.h>
#include <stratosphere.hpp> #include <stratosphere.hpp>
#include "sm_manager_service.hpp" #include "sm_manager_service.hpp"
#include "sm_registration.hpp" #include "sm_service_manager.hpp"
namespace sts::sm {
Result ManagerService::RegisterProcess(u64 pid, InBuffer<u8> acid_sac, InBuffer<u8> aci0_sac) { Result ManagerService::RegisterProcess(u64 pid, InBuffer<u8> acid_sac, InBuffer<u8> aci0_sac) {
return Registration::RegisterProcess(pid, acid_sac.buffer, acid_sac.num_elements, aci0_sac.buffer, aci0_sac.num_elements); return sm::RegisterProcess(pid, acid_sac.buffer, acid_sac.num_elements, aci0_sac.buffer, aci0_sac.num_elements);
} }
Result ManagerService::UnregisterProcess(u64 pid) { Result ManagerService::UnregisterProcess(u64 pid) {
return Registration::UnregisterProcess(pid); return sm::UnregisterProcess(pid);
} }
void ManagerService::AtmosphereEndInitDefers() { void ManagerService::AtmosphereEndInitDefers() {
Registration::EndInitDefers(); R_ASSERT(sm::EndInitialDefers());
} }
void ManagerService::AtmosphereHasMitm(Out<bool> out, SmServiceName service) { void ManagerService::AtmosphereHasMitm(Out<bool> out, ServiceName service) {
out.SetValue(Registration::HasMitm(smEncodeName(service.name))); R_ASSERT(sm::HasMitm(out.GetPointer(), service));
} }
}

View file

@ -19,27 +19,33 @@
#include <stratosphere.hpp> #include <stratosphere.hpp>
#include "sm_types.hpp" #include "sm_types.hpp"
enum ManagerServiceCmd { namespace sts::sm {
Manager_Cmd_RegisterProcess = 0,
Manager_Cmd_UnregisterProcess = 1,
Manager_Cmd_AtmosphereEndInitDefers = 65000,
Manager_Cmd_AtmosphereHasMitm = 65001,
};
/* Service definition. */
class ManagerService final : public IServiceObject { class ManagerService final : public IServiceObject {
protected:
/* Command IDs. */
enum class CommandId {
RegisterProcess = 0,
UnregisterProcess = 1,
AtmosphereEndInitDefers = 65000,
AtmosphereHasMitm = 65001,
};
private: private:
/* Actual commands. */ /* Actual commands. */
virtual Result RegisterProcess(u64 pid, InBuffer<u8> acid_sac, InBuffer<u8> aci0_sac); virtual Result RegisterProcess(u64 pid, InBuffer<u8> acid_sac, InBuffer<u8> aci0_sac);
virtual Result UnregisterProcess(u64 pid); virtual Result UnregisterProcess(u64 pid);
virtual void AtmosphereEndInitDefers(); virtual void AtmosphereEndInitDefers();
virtual void AtmosphereHasMitm(Out<bool> out, SmServiceName service); virtual void AtmosphereHasMitm(Out<bool> out, ServiceName service);
public: public:
DEFINE_SERVICE_DISPATCH_TABLE { DEFINE_SERVICE_DISPATCH_TABLE {
MakeServiceCommandMeta<Manager_Cmd_RegisterProcess, &ManagerService::RegisterProcess>(), MakeServiceCommandMeta<CommandId::RegisterProcess, &ManagerService::RegisterProcess>(),
MakeServiceCommandMeta<Manager_Cmd_UnregisterProcess, &ManagerService::UnregisterProcess>(), MakeServiceCommandMeta<CommandId::UnregisterProcess, &ManagerService::UnregisterProcess>(),
MakeServiceCommandMeta<Manager_Cmd_AtmosphereEndInitDefers, &ManagerService::AtmosphereEndInitDefers>(), MakeServiceCommandMeta<CommandId::AtmosphereEndInitDefers, &ManagerService::AtmosphereEndInitDefers>(),
MakeServiceCommandMeta<Manager_Cmd_AtmosphereHasMitm, &ManagerService::AtmosphereHasMitm>(), MakeServiceCommandMeta<CommandId::AtmosphereHasMitm, &ManagerService::AtmosphereHasMitm>(),
}; };
}; };
}

View file

@ -1,632 +0,0 @@
/*
* Copyright (c) 2018-2019 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 <http://www.gnu.org/licenses/>.
*/
#include <switch.h>
#include <algorithm>
#include <stratosphere.hpp>
#include "sm_registration.hpp"
#include "meta_tools.hpp"
static std::array<Registration::Process, REGISTRATION_LIST_MAX_PROCESS> g_process_list = {0};
static std::array<Registration::Service, REGISTRATION_LIST_MAX_SERVICE> g_service_list = {0};
static u64 g_initial_process_id_low = 0;
static u64 g_initial_process_id_high = 0;
static bool g_determined_initial_process_ids = false;
static bool g_end_init_defers = false;
u64 GetServiceNameLength(u64 service) {
u64 service_name_len = 0;
while (service & 0xFF) {
service_name_len++;
service >>= 8;
}
return service_name_len;
}
/* Atmosphere extension utilities. */
void Registration::EndInitDefers() {
g_end_init_defers = true;
}
constexpr u64 EncodeNameConstant(const char *name) {
u64 service = 0;
for (unsigned int i = 0; i < sizeof(service); i++) {
if (name[i] == '\x00') {
break;
}
service |= ((u64)name[i]) << (8 * i);
}
return service;
}
bool Registration::ShouldInitDefer(u64 service) {
/* Only enable if compile-time generated. */
#ifndef SM_ENABLE_INIT_DEFERS
return false;
#endif
if (g_end_init_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. */
constexpr u64 FSP_SRV = EncodeNameConstant("fsp-srv");
return service == FSP_SRV;
}
/* Utilities. */
Registration::Process *Registration::GetProcessForPid(u64 pid) {
auto process_it = std::find_if(g_process_list.begin(), g_process_list.end(), member_equals_fn(&Process::pid, pid));
if (process_it == g_process_list.end()) {
return nullptr;
}
return &*process_it;
}
Registration::Process *Registration::GetFreeProcess() {
return GetProcessForPid(0);
}
Registration::Service *Registration::GetService(u64 service_name) {
auto service_it = std::find_if(g_service_list.begin(), g_service_list.end(), member_equals_fn(&Service::service_name, service_name));
if (service_it == g_service_list.end()) {
return nullptr;
}
return &*service_it;
}
Registration::Service *Registration::GetFreeService() {
return GetService(0);
}
bool Registration::IsValidForSac(u8 *sac, size_t sac_size, u64 service, bool is_host) {
u8 cur_ctrl;
u64 cur_service;
u64 service_for_compare;
bool cur_is_host;
size_t remaining = sac_size;
while (remaining) {
cur_ctrl = *sac++;
remaining--;
size_t cur_size = (cur_ctrl & 7) + 1;
if (cur_size > remaining) {
break;
}
cur_is_host = (cur_ctrl & 0x80) != 0;
cur_service = 0;
for (unsigned int i = 0; i < cur_size; i++) {
cur_service |= ((u64)sac[i]) << (8 * i);
}
/* Check if the last byte is a wildcard ('*') */
service_for_compare = service;
if (sac[cur_size - 1] == '*') {
u64 mask = U64_MAX;
for (unsigned int i = 0; i < 8 - (cur_size - 1); i++) {
mask >>= 8;
}
/* Mask cur_service, service with 0xFF.. up until the wildcard. */
cur_service &= mask;
service_for_compare &= mask;
}
if (cur_service == service_for_compare && (is_host == cur_is_host)) {
return true;
}
sac += cur_size;
remaining -= cur_size;
}
return false;
}
bool Registration::ValidateSacAgainstRestriction(u8 *r_sac, size_t r_sac_size, u8 *sac, size_t sac_size) {
u8 cur_ctrl;
u64 cur_service;
bool cur_is_host;
size_t remaining = sac_size;
while (remaining) {
cur_ctrl = *sac++;
remaining--;
size_t cur_size = (cur_ctrl & 7) + 1;
if (cur_size > remaining) {
break;
}
cur_is_host = (cur_ctrl & 0x80) != 0;
cur_service = 0;
for (unsigned int i = 0; i < cur_size; i++) {
cur_service |= ((u64)sac[i]) << (8 * i);
}
if (!IsValidForSac(r_sac, r_sac_size, cur_service, cur_is_host)) {
return false;
}
sac += cur_size;
remaining -= cur_size;
}
return true;
}
void Registration::CacheInitialProcessIdLimits() {
if (g_determined_initial_process_ids) {
return;
}
if ((GetRuntimeFirmwareVersion() >= FirmwareVersion_500)) {
svcGetSystemInfo(&g_initial_process_id_low, 2, 0, 0);
svcGetSystemInfo(&g_initial_process_id_high, 2, 0, 1);
} else {
g_initial_process_id_low = 0;
g_initial_process_id_high = REGISTRATION_INITIAL_PID_MAX;
}
g_determined_initial_process_ids = true;
}
bool Registration::IsInitialProcess(u64 pid) {
CacheInitialProcessIdLimits();
return g_initial_process_id_low <= pid && pid <= g_initial_process_id_high;
}
u64 Registration::GetInitialProcessId() {
CacheInitialProcessIdLimits();
if (IsInitialProcess(1)) {
return 1;
}
return g_initial_process_id_low;
}
/* Process management. */
Result Registration::RegisterProcess(u64 pid, u8 *acid_sac, size_t acid_sac_size, u8 *aci0_sac, size_t aci0_sac_size) {
if (aci0_sac_size > REGISTRATION_MAX_SAC_SIZE) {
return ResultSmTooLargeAccessControl;
}
Registration::Process *proc = GetFreeProcess();
if (proc == NULL) {
return ResultSmInsufficientProcesses;
}
if (aci0_sac_size && !ValidateSacAgainstRestriction(acid_sac, acid_sac_size, aci0_sac, aci0_sac_size)) {
return ResultSmNotAllowed;
}
proc->pid = pid;
proc->sac_size = aci0_sac_size;
std::copy(aci0_sac, aci0_sac + aci0_sac_size, proc->sac);
return ResultSuccess;
}
Result Registration::UnregisterProcess(u64 pid) {
Registration::Process *proc = GetProcessForPid(pid);
if (proc == NULL) {
return ResultSmInvalidClient;
}
proc->pid = 0;
return ResultSuccess;
}
/* Service management. */
bool Registration::HasService(u64 service) {
return std::any_of(g_service_list.begin(), g_service_list.end(), member_equals_fn(&Service::service_name, service));
}
bool Registration::HasMitm(u64 service) {
Registration::Service *target_service = GetService(service);
return target_service != NULL && target_service->mitm_pid != 0;
}
Result Registration::GetMitmServiceHandleImpl(Registration::Service *target_service, u64 pid, Handle *out) {
/* Send command to query if we should mitm. */
IpcCommand c;
ipcInitialize(&c);
struct {
u64 magic;
u64 cmd_id;
u64 pid;
} *info = ((decltype(info))ipcPrepareHeader(&c, sizeof(*info)));
info->magic = SFCI_MAGIC;
info->cmd_id = 65000;
info->pid = pid;
R_TRY(ipcDispatch(target_service->mitm_query_h));
/* Parse response to see if we should mitm. */
bool should_mitm;
{
IpcParsedCommand r;
ipcParse(&r);
struct {
u64 magic;
u64 result;
bool should_mitm;
} *resp = ((decltype(resp))r.Raw);
R_TRY(resp->result);
should_mitm = resp->should_mitm;
}
/* If we shouldn't mitm, give normal session. */
if (!should_mitm) {
return svcConnectToPort(out, target_service->port_h);
}
/* Create both handles. If the second fails, close the first to prevent leak. */
R_TRY(svcConnectToPort(&target_service->mitm_fwd_sess_h, target_service->port_h));
R_TRY_CLEANUP(svcConnectToPort(out, target_service->mitm_port_h), {
svcCloseHandle(target_service->mitm_fwd_sess_h);
target_service->mitm_fwd_sess_h = 0;
});
target_service->mitm_waiting_ack_pid = pid;
target_service->mitm_waiting_ack = true;
return ResultSuccess;
}
Result Registration::GetServiceHandleImpl(Registration::Service *target_service, u64 pid, Handle *out) {
/* Clear handle output. */
*out = 0;
/* If not mitm'd or mitm service is requesting, get normal session. */
if (target_service->mitm_pid == 0 || target_service->mitm_pid == pid) {
return svcConnectToPort(out, target_service->port_h);
}
/* We're mitm'd. */
if (R_FAILED(GetMitmServiceHandleImpl(target_service, pid, out))) {
/* If the Mitm service is dead, just give a normal session. */
return svcConnectToPort(out, target_service->port_h);
}
return ResultSuccess;
}
Result Registration::GetServiceHandle(u64 pid, u64 service, Handle *out) {
Registration::Service *target_service = GetService(service);
if (target_service == NULL || ShouldInitDefer(service) || target_service->mitm_waiting_ack) {
/* Note: This defers the result until later. */
return ResultServiceFrameworkRequestDeferredByUser;
}
R_TRY_CATCH(GetServiceHandleImpl(target_service, pid, out)) {
/* Convert Kernel result to SM result. */
R_CATCH(ResultKernelOutOfSessions) {
return ResultSmInsufficientSessions;
}
} R_END_TRY_CATCH;
return ResultSuccess;
}
Result Registration::GetServiceForPid(u64 pid, u64 service, Handle *out) {
if (!service) {
return ResultSmInvalidServiceName;
}
u64 service_name_len = GetServiceNameLength(service);
/* If the service has bytes after a null terminator, that's no good. */
if (service_name_len != 8 && (service >> (8 * service_name_len))) {
return ResultSmInvalidServiceName;
}
/* 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. */
if (GetRuntimeFirmwareVersion() >= FirmwareVersion_800 && service == EncodeNameConstant("apm:p")) {
return ResultSmNotAllowed;
}
if (!IsInitialProcess(pid)) {
Registration::Process *proc = GetProcessForPid(pid);
if (proc == NULL) {
return ResultSmInvalidClient;
}
if (!IsValidForSac(proc->sac, proc->sac_size, service, false)) {
return ResultSmNotAllowed;
}
}
return GetServiceHandle(pid, service, out);
}
Result Registration::RegisterServiceForPid(u64 pid, u64 service, u64 max_sessions, bool is_light, Handle *out) {
if (!service) {
return ResultSmInvalidServiceName;
}
u64 service_name_len = GetServiceNameLength(service);
/* If the service has bytes after a null terminator, that's no good. */
if (service_name_len != 8 && (service >> (8 * service_name_len))) {
return ResultSmInvalidServiceName;
}
if (!IsInitialProcess(pid)) {
Registration::Process *proc = GetProcessForPid(pid);
if (proc == NULL) {
return ResultSmInvalidClient;
}
if (!IsValidForSac(proc->sac, proc->sac_size, service, true)) {
return ResultSmNotAllowed;
}
}
if (HasService(service)) {
return ResultSmAlreadyRegistered;
}
#ifdef SM_MINIMUM_SESSION_LIMIT
if (max_sessions < SM_MINIMUM_SESSION_LIMIT) {
max_sessions = SM_MINIMUM_SESSION_LIMIT;
}
#endif
Registration::Service *free_service = GetFreeService();
if (free_service == NULL) {
return ResultSmInsufficientServices;
}
*out = 0;
*free_service = (const Registration::Service){0};
R_TRY(svcCreatePort(out, &free_service->port_h, max_sessions, is_light, (char *)&free_service->service_name));
free_service->service_name = service;
free_service->owner_pid = pid;
free_service->max_sessions = max_sessions;
free_service->is_light = is_light;
return ResultSuccess;
}
Result Registration::RegisterServiceForSelf(u64 service, u64 max_sessions, bool is_light, Handle *out) {
u64 pid;
R_TRY(svcGetProcessId(&pid, CUR_PROCESS_HANDLE));
u64 service_name_len = GetServiceNameLength(service);
/* If the service has bytes after a null terminator, that's no good. */
if (service_name_len != 8 && (service >> (8 * service_name_len))) {
return ResultSmInvalidServiceName;
}
if (HasService(service)) {
return ResultSmAlreadyRegistered;
}
#ifdef SM_MINIMUM_SESSION_LIMIT
if (max_sessions < SM_MINIMUM_SESSION_LIMIT) {
max_sessions = SM_MINIMUM_SESSION_LIMIT;
}
#endif
Registration::Service *free_service = GetFreeService();
if (free_service == NULL) {
return ResultSmInsufficientServices;
}
*out = 0;
*free_service = (const Registration::Service){0};
R_TRY(svcCreatePort(out, &free_service->port_h, max_sessions, is_light, (char *)&free_service->service_name));
free_service->service_name = service;
free_service->owner_pid = pid;
free_service->max_sessions = max_sessions;
free_service->is_light = is_light;
return ResultSuccess;
}
Result Registration::UnregisterServiceForPid(u64 pid, u64 service) {
if (!service) {
return ResultSmInvalidServiceName;
}
u64 service_name_len = GetServiceNameLength(service);
/* If the service has bytes after a null terminator, that's no good. */
if (service_name_len != 8 && (service >> (8 * service_name_len))) {
return ResultSmInvalidServiceName;
}
Registration::Service *target_service = GetService(service);
if (target_service == NULL) {
return ResultSmNotRegistered;
}
if (!IsInitialProcess(pid) && target_service->owner_pid != pid) {
return ResultSmNotAllowed;
}
svcCloseHandle(target_service->port_h);
svcCloseHandle(target_service->mitm_port_h);
svcCloseHandle(target_service->mitm_query_h);
*target_service = (const Registration::Service){0};
return ResultSuccess;
}
Result Registration::InstallMitmForPid(u64 pid, u64 service, Handle *out, Handle *query_out) {
if (!service) {
return ResultSmInvalidServiceName;
}
u64 service_name_len = GetServiceNameLength(service);
/* If the service has bytes after a null terminator, that's no good. */
if (service_name_len != 8 && (service >> (8 * service_name_len))) {
return ResultSmInvalidServiceName;
}
/* Verify we're allowed to mitm the service. */
if (!IsInitialProcess(pid)) {
Registration::Process *proc = GetProcessForPid(pid);
if (proc == NULL) {
return ResultSmInvalidClient;
}
if (!IsValidForSac(proc->sac, proc->sac_size, service, true)) {
return ResultSmNotAllowed;
}
}
/* Verify the service exists. */
Registration::Service *target_service = GetService(service);
if (target_service == NULL) {
return ResultServiceFrameworkRequestDeferredByUser;
}
/* Verify the service isn't already being mitm'd. */
if (target_service->mitm_pid != 0) {
return ResultSmAlreadyRegistered;
}
*out = 0;
u64 x = 0;
R_TRY(svcCreatePort(out, &target_service->mitm_port_h, target_service->max_sessions, target_service->is_light, (char *)&x));
R_TRY(svcCreateSession(query_out, &target_service->mitm_query_h, 0, 0));
target_service->mitm_pid = pid;
return ResultSuccess;
}
Result Registration::UninstallMitmForPid(u64 pid, u64 service) {
if (!service) {
return ResultSmInvalidServiceName;
}
u64 service_name_len = GetServiceNameLength(service);
/* If the service has bytes after a null terminator, that's no good. */
if (service_name_len != 8 && (service >> (8 * service_name_len))) {
return ResultSmInvalidServiceName;
}
Registration::Service *target_service = GetService(service);
if (target_service == NULL) {
return ResultSmNotRegistered;
}
if (!IsInitialProcess(pid) && target_service->mitm_pid != pid) {
return ResultSmNotAllowed;
}
svcCloseHandle(target_service->mitm_port_h);
svcCloseHandle(target_service->mitm_query_h);
target_service->mitm_pid = 0;
return ResultSuccess;
}
Result Registration::AcknowledgeMitmSessionForPid(u64 pid, u64 service, Handle *out, u64 *out_pid) {
if (!service) {
return ResultSmInvalidServiceName;
}
u64 service_name_len = GetServiceNameLength(service);
/* If the service has bytes after a null terminator, that's no good. */
if (service_name_len != 8 && (service >> (8 * service_name_len))) {
return ResultSmInvalidServiceName;
}
Registration::Service *target_service = GetService(service);
if (target_service == NULL) {
return ResultSmNotRegistered;
}
if ((!IsInitialProcess(pid) && target_service->mitm_pid != pid) || !target_service->mitm_waiting_ack) {
return ResultSmNotAllowed;
}
*out = target_service->mitm_fwd_sess_h;
*out_pid = target_service->mitm_waiting_ack_pid;
target_service->mitm_fwd_sess_h = 0;
target_service->mitm_waiting_ack_pid = 0;
target_service->mitm_waiting_ack = false;
return ResultSuccess;
}
Result Registration::AssociatePidTidForMitm(u64 pid, u64 tid) {
for (auto &service : g_service_list) {
if (service.mitm_pid) {
IpcCommand c;
ipcInitialize(&c);
struct {
u64 magic;
u64 cmd_id;
u64 pid;
u64 tid;
} *info = ((decltype(info))ipcPrepareHeader(&c, sizeof(*info)));
info->magic = SFCI_MAGIC;
info->cmd_id = 65001;
info->pid = pid;
info->tid = tid;
ipcDispatch(service.mitm_query_h);
}
}
return ResultSuccess;
}
void Registration::ConvertServiceToRecord(Registration::Service *service, SmServiceRecord *record) {
record->service_name = service->service_name;
record->owner_pid = service->owner_pid;
record->max_sessions = service->max_sessions;
record->mitm_pid = service->mitm_pid;
record->mitm_waiting_ack_pid = service->mitm_waiting_ack_pid;
record->is_light = service->is_light;
record->mitm_waiting_ack = service->mitm_waiting_ack;
}
Result Registration::GetServiceRecord(u64 service, SmServiceRecord *out) {
if (!service) {
return ResultSmInvalidServiceName;
}
u64 service_name_len = GetServiceNameLength(service);
/* If the service has bytes after a null terminator, that's no good. */
if (service_name_len != 8 && (service >> (8 * service_name_len))) {
return ResultSmInvalidServiceName;
}
Registration::Service *target_service = GetService(service);
if (target_service == NULL) {
return ResultSmNotRegistered;
}
ConvertServiceToRecord(target_service, out);
return ResultSuccess;
}
void Registration::ListServiceRecords(u64 offset, u64 max_out, SmServiceRecord *out, u64 *out_count) {
u64 count = 0;
for (auto it = g_service_list.begin(); it != g_service_list.end() && count < max_out; it++) {
if (it->service_name != 0) {
if (offset > 0) {
offset--;
} else {
ConvertServiceToRecord(it, out++);
count++;
}
}
}
*out_count = count;
}

View file

@ -1,92 +0,0 @@
/*
* Copyright (c) 2018-2019 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <switch.h>
#include "sm_types.hpp"
#define REGISTRATION_LIST_MAX_PROCESS (0x40)
#define REGISTRATION_LIST_MAX_SERVICE (0x100)
#define REGISTRATION_MAX_SAC_SIZE (0x200)
#define REGISTRATION_INITIAL_PID_MAX 0x50
class Registration {
public:
struct Process {
u64 pid;
u64 sac_size;
u8 sac[REGISTRATION_MAX_SAC_SIZE];
};
struct Service {
u64 service_name;
u64 owner_pid;
Handle port_h;
/* Debug. */
u64 max_sessions;
bool is_light;
/* Extension. */
u64 mitm_pid;
Handle mitm_port_h;
Handle mitm_query_h;
bool mitm_waiting_ack;
u64 mitm_waiting_ack_pid;
Handle mitm_fwd_sess_h;
};
/* Utilities. */
static void EndInitDefers();
static bool ShouldInitDefer(u64 service);
static Registration::Process *GetProcessForPid(u64 pid);
static Registration::Process *GetFreeProcess();
static Registration::Service *GetService(u64 service);
static Registration::Service *GetFreeService();
static bool IsValidForSac(u8 *sac, size_t sac_size, u64 service, bool is_host);
static bool ValidateSacAgainstRestriction(u8 *r_sac, size_t r_sac_size, u8 *sac, size_t sac_size);
static void CacheInitialProcessIdLimits();
static bool IsInitialProcess(u64 pid);
static u64 GetInitialProcessId();
/* Process management. */
static Result RegisterProcess(u64 pid, u8 *acid_sac, size_t acid_sac_size, u8 *aci0_sac, size_t aci0_sac_size);
static Result UnregisterProcess(u64 pid);
/* Service management. */
static bool HasService(u64 service);
static bool HasMitm(u64 service);
static Result GetMitmServiceHandleImpl(Registration::Service *service, u64 pid, Handle *out);
static Result GetServiceHandleImpl(Registration::Service *service, u64 pid, Handle *out);
static Result GetServiceHandle(u64 pid, u64 service, Handle *out);
static Result GetServiceForPid(u64 pid, u64 service, Handle *out);
static Result RegisterServiceForPid(u64 pid, u64 service, u64 max_sessions, bool is_light, Handle *out);
static Result RegisterServiceForSelf(u64 service, u64 max_sessions, bool is_light, Handle *out);
static Result UnregisterServiceForPid(u64 pid, u64 service);
/* Extension. */
static Result InstallMitmForPid(u64 pid, u64 service, Handle *out, Handle *query_out);
static Result UninstallMitmForPid(u64 pid, u64 service);
static Result AcknowledgeMitmSessionForPid(u64 pid, u64 service, Handle *out, u64 *out_pid);
static Result AssociatePidTidForMitm(u64 pid, u64 tid);
static void ConvertServiceToRecord(Registration::Service *service, SmServiceRecord *record);
static Result GetServiceRecord(u64 service, SmServiceRecord *out);
static void ListServiceRecords(u64 offset, u64 max_out, SmServiceRecord *out, u64 *out_count);
};

View file

@ -0,0 +1,730 @@
/*
* Copyright (c) 2018-2019 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 <http://www.gnu.org/licenses/>.
*/
#include <switch.h>
#include <stratosphere.hpp>
#include "sm_service_manager.hpp"
namespace sts::sm {
/* Anonymous namespace for implementation details. */
namespace {
/* Constexpr definitions. */
static constexpr size_t ProcessCountMax = 0x40;
static constexpr size_t ServiceCountMax = 0x100;
static constexpr size_t AccessControlSizeMax = 0x200;
/* Types. */
struct ProcessInfo {
u64 pid;
size_t access_control_size;
u8 access_control[AccessControlSizeMax];
ProcessInfo() {
this->Free();
}
void Free() {
this->pid = InvalidProcessId;
this->access_control_size = 0;
std::memset(this->access_control, 0, sizeof(this->access_control));
}
};
struct ServiceInfo {
ServiceName name;
u64 owner_pid;
AutoHandle port_h;
/* Debug. */
u64 max_sessions;
bool is_light;
/* Mitm Extension. */
u64 mitm_pid;
AutoHandle mitm_port_h;
AutoHandle mitm_query_h;
/* Acknowledgement members. */
bool mitm_waiting_ack;
u64 mitm_waiting_ack_pid;
AutoHandle mitm_fwd_sess_h;
ServiceInfo() {
this->Free();
}
void Free() {
/* Close any open handles. */
this->port_h.Clear();
this->mitm_port_h.Clear();
this->mitm_query_h.Clear();
this->mitm_fwd_sess_h.Clear();
/* Reset all other members. */
this->name = InvalidServiceName;
this->owner_pid = InvalidProcessId;
this->max_sessions = 0;
this->is_light = false;
this->mitm_pid = InvalidProcessId;
this->mitm_waiting_ack = false;
this->mitm_waiting_ack_pid = InvalidProcessId;
}
void FreeMitm() {
/* Close mitm handles. */
this->mitm_port_h.Clear();
this->mitm_query_h.Clear();
/* Reset mitm members. */
this->mitm_pid = InvalidProcessId;
}
void AcknowledgeMitmSession(u64 *out_pid, Handle *out_hnd) {
/* Copy to output. */
*out_pid = this->mitm_waiting_ack_pid;
*out_hnd = this->mitm_fwd_sess_h.Move();
this->mitm_waiting_ack = false;
this->mitm_waiting_ack_pid = InvalidProcessId;
}
};
class AccessControlEntry {
private:
const u8 *entry;
size_t capacity;
public:
AccessControlEntry(const void *e, size_t c) : entry(reinterpret_cast<const u8 *>(e)), capacity(c) {
/* ... */
}
AccessControlEntry GetNextEntry() const {
return AccessControlEntry(this->entry + this->GetSize(), this->capacity - this->GetSize());
}
size_t GetSize() const {
return this->GetServiceNameSize() + 1;
}
size_t GetServiceNameSize() const {
return (this->entry[0] & 7) + 1;
}
ServiceName GetServiceName() const {
return ServiceName::Encode(reinterpret_cast<const char *>(this->entry + 1), this->GetServiceNameSize());
}
bool IsHost() const {
return (this->entry[0] & 0x80) != 0;
}
bool IsWildcard() const {
return this->entry[this->GetServiceNameSize()] == '*';
}
bool IsValid() const {
/* Validate that we can access data. */
if (this->entry == nullptr || this->capacity == 0) {
return false;
}
/* Validate that the size is correct. */
return this->GetSize() <= this->capacity;
}
};
class InitialProcessIdLimits {
public:
static constexpr u64 InitialProcessIdMin = 0x00;
static constexpr u64 InitialProcessIdMax = 0x50;
private:
u64 min;
u64 max;
public:
InitialProcessIdLimits() {
if (GetRuntimeFirmwareVersion() >= FirmwareVersion_500) {
/* On 5.0.0+, we can get precise limits from svcGetSystemInfo. */
R_ASSERT(svcGetSystemInfo(&this->min, SystemInfoType_InitialProcessIdRange, INVALID_HANDLE, InitialProcessIdRangeInfo_Minimum));
R_ASSERT(svcGetSystemInfo(&this->max, SystemInfoType_InitialProcessIdRange, INVALID_HANDLE, InitialProcessIdRangeInfo_Maximum));
} else if (GetRuntimeFirmwareVersion() >= FirmwareVersion_400) {
/* On 4.0.0-4.1.0, we can get the precise limits from normal svcGetInfo. */
R_ASSERT(svcGetInfo(&this->min, InfoType_InitialProcessIdRange, INVALID_HANDLE, InitialProcessIdRangeInfo_Minimum));
R_ASSERT(svcGetInfo(&this->max, InfoType_InitialProcessIdRange, INVALID_HANDLE, InitialProcessIdRangeInfo_Maximum));
} else {
/* On < 4.0.0, we just use hardcoded extents. */
this->min = InitialProcessIdMin;
this->max = InitialProcessIdMax;
}
/* Ensure range is sane. */
if (this->min > this->max) {
std::abort();
}
}
bool IsInitialProcess(u64 pid) const {
if (pid == InvalidProcessId) {
std::abort();
}
return this->min <= pid && pid <= this->max;
}
};
/* Static members. */
ProcessInfo g_process_list[ProcessCountMax];
ServiceInfo g_service_list[ServiceCountMax];
InitialProcessIdLimits g_initial_process_id_limits;
bool g_ended_initial_defers;
/* Helper functions for interacting with processes/services. */
ProcessInfo *GetProcessInfo(u64 pid) {
for (size_t i = 0; i < ProcessCountMax; i++) {
if (g_process_list[i].pid == pid) {
return &g_process_list[i];
}
}
return nullptr;
}
ProcessInfo *GetFreeProcessInfo() {
return GetProcessInfo(InvalidProcessId);
}
bool HasProcessInfo(u64 pid) {
return GetProcessInfo(pid) != nullptr;
}
ServiceInfo *GetServiceInfo(ServiceName service_name) {
for (size_t i = 0; i < ServiceCountMax; i++) {
if (g_service_list[i].name == service_name) {
return &g_service_list[i];
}
}
return nullptr;
}
ServiceInfo *GetFreeServiceInfo() {
return GetServiceInfo(InvalidServiceName);
}
bool HasServiceInfo(ServiceName service) {
return GetServiceInfo(service) != nullptr;
}
void GetServiceInfoRecord(ServiceRecord *out_record, const ServiceInfo *service_info) {
out_record->service = service_info->name;
out_record->owner_pid = service_info->owner_pid;
out_record->max_sessions = service_info->max_sessions;
out_record->mitm_pid = service_info->mitm_pid;
out_record->mitm_waiting_ack_pid = service_info->mitm_waiting_ack_pid;
out_record->is_light = service_info->is_light;
out_record->mitm_waiting_ack = service_info->mitm_waiting_ack;
}
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) {
if (access_control.IsWildcard() == is_wildcard) {
/* Check for exact match. */
if (access_control.GetServiceName() == service) {
return ResultSuccess;
}
} else if (access_control.IsWildcard()) {
/* Also allow fuzzy match for wildcard. */
ServiceName ac_service = access_control.GetServiceName();
if (std::memcmp(&ac_service, &service, access_control.GetServiceNameSize() - 1) == 0) {
return ResultSuccess;
}
}
}
access_control = access_control.GetNextEntry();
}
return ResultSmNotAllowed;
}
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. */
if (service.name[0] == 0) {
return ResultSmInvalidServiceName;
}
/* 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)) {
if (service.name[name_len++] != 0) {
return ResultSmInvalidServiceName;
}
}
return ResultSuccess;
}
bool IsInitialProcess(u64 pid) {
return g_initial_process_id_limits.IsInitialProcess(pid);
}
bool IsValidProcessId(u64 pid) {
return pid != InvalidProcessId;
}
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. */
return service == ServiceName::Encode("fsp-srv");
}
Result GetMitmServiceHandleImpl(Handle *out, ServiceInfo *service_info, u64 pid) {
/* Send command to query if we should mitm. */
{
IpcCommand c;
ipcInitialize(&c);
struct {
u64 magic;
u64 cmd_id;
u64 pid;
} *info = ((decltype(info))ipcPrepareHeader(&c, sizeof(*info)));
info->magic = SFCI_MAGIC;
info->cmd_id = 65000;
info->pid = pid;
R_TRY(ipcDispatch(service_info->mitm_query_h.Get()));
}
/* Parse response to see if we should mitm. */
bool should_mitm;
{
IpcParsedCommand r;
ipcParse(&r);
struct {
u64 magic;
u64 result;
bool should_mitm;
} *resp = ((decltype(resp))r.Raw);
R_TRY(resp->result);
should_mitm = resp->should_mitm;
}
/* If we shouldn't mitm, give normal session. */
if (!should_mitm) {
return svcConnectToPort(out, service_info->port_h.Get());
}
/* Create both handles. */
{
AutoHandle fwd_hnd, hnd;
R_TRY(svcConnectToPort(fwd_hnd.GetPointer(), service_info->port_h.Get()));
R_TRY(svcConnectToPort(hnd.GetPointer(), service_info->mitm_port_h.Get()));
service_info->mitm_fwd_sess_h = std::move(fwd_hnd);
*out = hnd.Move();
}
service_info->mitm_waiting_ack_pid = pid;
service_info->mitm_waiting_ack = true;
return ResultSuccess;
}
Result GetServiceHandleImpl(Handle *out, ServiceInfo *service_info, u64 pid) {
/* Clear handle output. */
*out = INVALID_HANDLE;
/* If not mitm'd or mitm service is requesting, get normal session. */
if (!IsValidProcessId(service_info->mitm_pid) || service_info->mitm_pid == pid) {
return svcConnectToPort(out, service_info->port_h.Get());
}
/* We're mitm'd. Assert, because mitm service host dead is an error state. */
R_ASSERT(GetMitmServiceHandleImpl(out, service_info, pid));
return ResultSuccess;
}
Result RegisterServiceImpl(Handle *out, u64 pid, ServiceName service, size_t max_sessions, bool is_light) {
/* Validate service name. */
R_TRY(ValidateServiceName(service));
/* Don't try to register something already registered. */
if (HasServiceInfo(service)) {
return ResultSmAlreadyRegistered;
}
/* Adjust session limit, if compile flags tell us to. */
#ifdef SM_MINIMUM_SESSION_LIMIT
if (max_sessions < SM_MINIMUM_SESSION_LIMIT) {
max_sessions = SM_MINIMUM_SESSION_LIMIT;
}
#endif
/* Get free service. */
ServiceInfo *free_service = GetFreeServiceInfo();
if (free_service == nullptr) {
return ResultSmInsufficientServices;
}
/* Create the new service. */
*out = INVALID_HANDLE;
R_TRY(svcCreatePort(out, free_service->port_h.GetPointerAndClear(), max_sessions, is_light, free_service->name.name));
/* Save info. */
free_service->name = service;
free_service->owner_pid = pid;
free_service->max_sessions = max_sessions;
free_service->is_light = is_light;
return ResultSuccess;
}
}
/* Process management. */
Result RegisterProcess(u64 pid, const void *acid_sac, size_t acid_sac_size, const void *aci0_sac, size_t aci0_sac_size) {
/* Check that access control will fit in the ServiceInfo. */
if (aci0_sac_size > AccessControlSizeMax) {
return ResultSmTooLargeAccessControl;
}
/* Get free process. */
ProcessInfo *proc = GetFreeProcessInfo();
if (proc == nullptr) {
return ResultSmInsufficientProcesses;
}
/* Validate restrictions. */
if (!aci0_sac_size) {
return ResultSmNotAllowed;
}
R_TRY(ValidateAccessControl(AccessControlEntry(acid_sac, acid_sac_size), AccessControlEntry(aci0_sac, aci0_sac_size)));
/* Save info. */
proc->pid = pid;
proc->access_control_size = aci0_sac_size;
std::memcpy(proc->access_control, aci0_sac, proc->access_control_size);
return ResultSuccess;
}
Result UnregisterProcess(u64 pid) {
/* Find the process. */
ProcessInfo *proc = GetProcessInfo(pid);
if (proc == nullptr) {
return ResultSmInvalidClient;
}
proc->Free();
return ResultSuccess;
}
/* Service management. */
Result HasService(bool *out, ServiceName service) {
*out = HasServiceInfo(service);
return ResultSuccess;
}
Result GetServiceHandle(Handle *out, u64 pid, ServiceName service) {
/* 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. */
if (GetRuntimeFirmwareVersion() >= FirmwareVersion_800 && service == ServiceName::Encode("apm:p")) {
return ResultSmNotAllowed;
}
/* Check that the process is registered and allowed to get the service. */
if (!IsInitialProcess(pid)) {
ProcessInfo *proc = GetProcessInfo(pid);
if (proc == nullptr) {
return ResultSmInvalidClient;
}
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) || service_info->mitm_waiting_ack) {
return ResultServiceFrameworkRequestDeferredByUser;
}
/* Get a handle from the service info. */
R_TRY_CATCH(GetServiceHandleImpl(out, service_info, pid)) {
/* Convert Kernel result to SM result. */
R_CATCH(ResultKernelOutOfSessions) {
return ResultSmInsufficientSessions;
}
} R_END_TRY_CATCH;
return ResultSuccess;
}
Result RegisterService(Handle *out, u64 pid, ServiceName service, size_t max_sessions, bool is_light) {
/* Validate service name. */
R_TRY(ValidateServiceName(service));
/* Check that the process is registered and allowed to register the service. */
if (!IsInitialProcess(pid)) {
ProcessInfo *proc = GetProcessInfo(pid);
if (proc == nullptr) {
return ResultSmInvalidClient;
}
R_TRY(ValidateAccessControl(AccessControlEntry(proc->access_control, proc->access_control_size), service, true, false));
}
if (HasServiceInfo(service)) {
return ResultSmAlreadyRegistered;
}
return RegisterServiceImpl(out, pid, service, max_sessions, is_light);
}
Result RegisterServiceForSelf(Handle *out, ServiceName service, size_t max_sessions) {
u64 self_pid;
R_TRY(svcGetProcessId(&self_pid, CUR_PROCESS_HANDLE));
return RegisterServiceImpl(out, self_pid, service, max_sessions, false);
}
Result UnregisterService(u64 pid, ServiceName service) {
/* Validate service name. */
R_TRY(ValidateServiceName(service));
/* Check that the process is registered. */
if (!IsInitialProcess(pid)) {
if (!HasProcessInfo(pid)) {
return ResultSmInvalidClient;
}
}
/* Ensure that the service is actually registered. */
ServiceInfo *service_info = GetServiceInfo(service);
if (service_info == nullptr) {
return ResultSmNotRegistered;
}
/* Check if we have permission to do this. */
if (service_info->owner_pid != pid) {
return ResultSmNotAllowed;
}
/* Unregister the service. */
service_info->Free();
return ResultSuccess;
}
/* Mitm extensions. */
Result HasMitm(bool *out, ServiceName service) {
/* Validate service name. */
R_TRY(ValidateServiceName(service));
const ServiceInfo *service_info = GetServiceInfo(service);
*out = service_info != nullptr && IsValidProcessId(service_info->mitm_pid);
return ResultSuccess;
}
Result InstallMitm(Handle *out, Handle *out_query, u64 pid, ServiceName service) {
/* Validate service name. */
R_TRY(ValidateServiceName(service));
/* Check that the process is registered and allowed to register the service. */
if (!IsInitialProcess(pid)) {
ProcessInfo *proc = GetProcessInfo(pid);
if (proc == nullptr) {
return ResultSmInvalidClient;
}
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 (service_info == nullptr) {
/* If it doesn't exist, defer until it does. */
return ResultServiceFrameworkRequestDeferredByUser;
}
/* Validate that the service isn't already being mitm'd. */
if (IsValidProcessId(service_info->mitm_pid)) {
return ResultSmAlreadyRegistered;
}
/* Always clear output. */
*out = INVALID_HANDLE;
*out_query = INVALID_HANDLE;
/* Create mitm handles. */
{
AutoHandle hnd, port_hnd, qry_hnd, mitm_qry_hnd;
u64 x = 0;
R_TRY(svcCreatePort(hnd.GetPointer(), port_hnd.GetPointer(), service_info->max_sessions, service_info->is_light, reinterpret_cast<char *>(&x)));
R_TRY(svcCreateSession(qry_hnd.GetPointer(), mitm_qry_hnd.GetPointer(), 0, 0));
/* Copy to output. */
service_info->mitm_pid = pid;
service_info->mitm_port_h = std::move(port_hnd);
service_info->mitm_query_h = std::move(mitm_qry_hnd);
*out = hnd.Move();
*out_query = qry_hnd.Move();
}
return ResultSuccess;
}
Result UninstallMitm(u64 pid, ServiceName service) {
/* Validate service name. */
R_TRY(ValidateServiceName(service));
/* Check that the process is registered. */
if (!IsInitialProcess(pid)) {
ProcessInfo *proc = GetProcessInfo(pid);
if (proc == nullptr) {
return ResultSmInvalidClient;
}
}
/* Validate that the service exists. */
ServiceInfo *service_info = GetServiceInfo(service);
if (service_info == nullptr) {
return ResultSmNotRegistered;
}
/* Validate that the client pid is the mitm process. */
if (service_info->mitm_pid != pid) {
return ResultSmNotAllowed;
}
/* Free Mitm session info. */
service_info->FreeMitm();
return ResultSuccess;
}
Result AcknowledgeMitmSession(u64 *out_pid, Handle *out_hnd, u64 pid, ServiceName service) {
/* Validate service name. */
R_TRY(ValidateServiceName(service));
/* Check that the process is registered. */
if (!IsInitialProcess(pid)) {
ProcessInfo *proc = GetProcessInfo(pid);
if (proc == nullptr) {
return ResultSmInvalidClient;
}
}
/* Validate that the service exists. */
ServiceInfo *service_info = GetServiceInfo(service);
if (service_info == nullptr) {
return ResultSmNotRegistered;
}
/* Validate that the client pid is the mitm process, and that an acknowledgement is waiting. */
if (service_info->mitm_pid != pid || !service_info->mitm_waiting_ack) {
return ResultSmNotAllowed;
}
/* Acknowledge. */
service_info->AcknowledgeMitmSession(out_pid, out_hnd);
return ResultSuccess;
}
Result AssociatePidTidForMitm(u64 pid, u64 tid) {
for (size_t i = 0; i < ServiceCountMax; i++) {
const ServiceInfo *service_info = &g_service_list[i];
if (IsValidProcessId(service_info->mitm_pid)) {
/* Send association command to all mitm processes. */
IpcCommand c;
ipcInitialize(&c);
struct {
u64 magic;
u64 cmd_id;
u64 pid;
u64 tid;
} *info = ((decltype(info))ipcPrepareHeader(&c, sizeof(*info)));
info->magic = SFCI_MAGIC;
info->cmd_id = 65001;
info->pid = pid;
info->tid = tid;
ipcDispatch(service_info->mitm_query_h.Get());
}
}
return ResultSuccess;
}
/* Dmnt record extensions. */
Result GetServiceRecord(ServiceRecord *out, ServiceName service) {
/* Validate service name. */
R_TRY(ValidateServiceName(service));
/* Validate that the service exists. */
const ServiceInfo *service_info = GetServiceInfo(service);
if (service_info == nullptr) {
return ResultSmNotRegistered;
}
GetServiceInfoRecord(out, service_info);
return ResultSuccess;
}
Result ListServiceRecords(ServiceRecord *out, u64 *out_count, u64 offset, u64 max_count) {
u64 count = 0;
for (size_t i = 0; i < ServiceCountMax && count < max_count; i++) {
const ServiceInfo *service_info = &g_service_list[i];
if (service_info->name != InvalidServiceName) {
if (offset == 0) {
GetServiceInfoRecord(out++, service_info);
count++;
} else {
offset--;
}
}
}
*out_count = 0;
return ResultSuccess;
}
/* Deferral extension (works around FS bug). */
Result EndInitialDefers() {
g_ended_initial_defers = true;
return ResultSuccess;
}
}

View file

@ -0,0 +1,48 @@
/*
* Copyright (c) 2018-2019 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <switch.h>
#include "sm_types.hpp"
namespace sts::sm {
/* Process management. */
Result RegisterProcess(u64 pid, const void *acid_sac, size_t acid_sac_size, const void *aci0_sac, size_t aci0_sac_size);
Result UnregisterProcess(u64 pid);
/* Service management. */
Result HasService(bool *out, ServiceName service);
Result GetServiceHandle(Handle *out, u64 pid, ServiceName service);
Result RegisterService(Handle *out, u64 pid, ServiceName service, size_t max_sessions, bool is_light);
Result RegisterServiceForSelf(Handle *out, ServiceName service, size_t max_sessions);
Result UnregisterService(u64 pid, ServiceName service);
/* Mitm extensions. */
Result HasMitm(bool *out, ServiceName service);
Result InstallMitm(Handle *out, Handle *out_query, u64 pid, ServiceName service);
Result UninstallMitm(u64 pid, ServiceName service);
Result AcknowledgeMitmSession(u64 *out_pid, Handle *out_hnd, u64 pid, ServiceName service);
Result AssociatePidTidForMitm(u64 pid, u64 tid);
/* Dmnt record extensions. */
Result GetServiceRecord(ServiceRecord *out, ServiceName service);
Result ListServiceRecords(ServiceRecord *out, u64 *out_count, u64 offset, u64 max_count);
/* Deferral extension (works around FS bug). */
Result EndInitialDefers();
}

View file

@ -15,15 +15,47 @@
*/ */
#pragma once #pragma once
struct SmServiceName { #include <cstring>
char name[sizeof(u64)];
};
static_assert(__alignof__(SmServiceName) == 1, "SmServiceName definition!"); namespace sts::sm {
struct ServiceName {
static constexpr size_t MaxLength = 8;
char name[MaxLength];
static constexpr ServiceName Encode(const char *name, size_t name_size) {
ServiceName out{};
for (size_t i = 0; i < MaxLength; i++) {
if (i < name_size) {
out.name[i] = name[i];
} else {
out.name[i] = 0;
}
}
return out;
}
static constexpr ServiceName Encode(const char *name) {
return Encode(name, std::strlen(name));
}
};
static constexpr ServiceName InvalidServiceName = ServiceName::Encode("");
static_assert(alignof(ServiceName) == 1, "ServiceName definition!");
inline bool operator==(const ServiceName &lhs, const ServiceName &rhs) {
return std::memcmp(&lhs, &rhs, sizeof(ServiceName)) == 0;
}
inline bool operator!=(const ServiceName &lhs, const ServiceName &rhs) {
return !(lhs == rhs);
}
/* For Debug Monitor extensions. */ /* For Debug Monitor extensions. */
struct SmServiceRecord { struct ServiceRecord {
u64 service_name; ServiceName service;
u64 owner_pid; u64 owner_pid;
u64 max_sessions; u64 max_sessions;
u64 mitm_pid; u64 mitm_pid;
@ -31,5 +63,9 @@ struct SmServiceRecord {
bool is_light; bool is_light;
bool mitm_waiting_ack; bool mitm_waiting_ack;
}; };
static_assert(sizeof(ServiceRecord) == 0x30, "ServiceRecord definition!");
static_assert(sizeof(SmServiceRecord) == 0x30, "SmServiceRecord definition!"); /* For process validation. */
static constexpr u64 InvalidProcessId = static_cast<u64>(-1ull);
}

View file

@ -16,8 +16,11 @@
#include <switch.h> #include <switch.h>
#include <stratosphere.hpp> #include <stratosphere.hpp>
#include "sm_user_service.hpp" #include "sm_user_service.hpp"
#include "sm_registration.hpp" #include "sm_service_manager.hpp"
namespace sts::sm {
Result UserService::Initialize(PidDescriptor pid) { Result UserService::Initialize(PidDescriptor pid) {
this->pid = pid.pid; this->pid = pid.pid;
@ -25,84 +28,46 @@ Result UserService::Initialize(PidDescriptor pid) {
return ResultSuccess; return ResultSuccess;
} }
Result UserService::GetService(Out<MovedHandle> out_h, SmServiceName service) { Result UserService::EnsureInitialized() {
Handle session_h = 0;
if (!this->has_initialized) { if (!this->has_initialized) {
return ResultSmInvalidClient; return ResultSmInvalidClient;
} }
R_TRY(Registration::GetServiceForPid(this->pid, smEncodeName(service.name), &session_h));
out_h.SetValue(session_h);
return ResultSuccess; return ResultSuccess;
} }
Result UserService::RegisterService(Out<MovedHandle> out_h, SmServiceName service, u32 max_sessions, bool is_light) { Result UserService::GetService(Out<MovedHandle> out_h, ServiceName service) {
Handle service_h = 0; R_TRY(this->EnsureInitialized());
return sm::GetServiceHandle(out_h.GetHandlePointer(), this->pid, service);
if (!this->has_initialized) {
return ResultSmInvalidClient;
} }
R_TRY(Registration::RegisterServiceForPid(this->pid, smEncodeName(service.name), max_sessions, (is_light & 1) != 0, &service_h)); Result UserService::RegisterService(Out<MovedHandle> out_h, ServiceName service, u32 max_sessions, bool is_light) {
R_TRY(this->EnsureInitialized());
out_h.SetValue(service_h); return sm::RegisterService(out_h.GetHandlePointer(), this->pid, service, max_sessions, is_light);
return ResultSuccess;
} }
Result UserService::UnregisterService(SmServiceName service) { Result UserService::UnregisterService(ServiceName service) {
if (!this->has_initialized) { R_TRY(this->EnsureInitialized());
return ResultSmInvalidClient; return sm::UnregisterService(this->pid, service);
} }
return Registration::UnregisterServiceForPid(this->pid, smEncodeName(service.name)); Result UserService::AtmosphereInstallMitm(Out<MovedHandle> srv_h, Out<MovedHandle> qry_h, ServiceName service) {
R_TRY(this->EnsureInitialized());
return sm::InstallMitm(srv_h.GetHandlePointer(), qry_h.GetHandlePointer(), this->pid, service);
} }
Result UserService::AtmosphereInstallMitm(Out<MovedHandle> srv_h, Out<MovedHandle> qry_h, SmServiceName service) { Result UserService::AtmosphereUninstallMitm(ServiceName service) {
Handle service_h = 0; R_TRY(this->EnsureInitialized());
Handle query_h = 0; return sm::UninstallMitm(this->pid, service);
if (!this->has_initialized) {
return ResultSmInvalidClient;
} }
R_TRY(Registration::InstallMitmForPid(this->pid, smEncodeName(service.name), &service_h, &query_h)); Result UserService::AtmosphereAcknowledgeMitmSession(Out<u64> client_pid, Out<MovedHandle> fwd_h, ServiceName service) {
R_TRY(this->EnsureInitialized());
srv_h.SetValue(service_h); return sm::AcknowledgeMitmSession(client_pid.GetPointer(), fwd_h.GetHandlePointer(), this->pid, service);
qry_h.SetValue(query_h);
return ResultSuccess;
}
Result UserService::AtmosphereUninstallMitm(SmServiceName service) {
if (!this->has_initialized) {
return ResultSmInvalidClient;
}
return Registration::UninstallMitmForPid(this->pid, smEncodeName(service.name));
}
Result UserService::AtmosphereAcknowledgeMitmSession(Out<u64> client_pid, Out<MovedHandle> fwd_h, SmServiceName service) {
Handle out_fwd_h = 0;
if (!this->has_initialized) {
return ResultSmInvalidClient;
}
R_TRY(Registration::AcknowledgeMitmSessionForPid(this->pid, smEncodeName(service.name), &out_fwd_h, client_pid.GetPointer()));
fwd_h.SetValue(out_fwd_h);
return ResultSuccess;
} }
Result UserService::AtmosphereAssociatePidTidForMitm(u64 pid, u64 tid) { Result UserService::AtmosphereAssociatePidTidForMitm(u64 pid, u64 tid) {
if (!this->has_initialized) { R_TRY(this->EnsureInitialized());
return ResultSmInvalidClient; return sm::AssociatePidTidForMitm(pid, tid);
} }
if (Registration::IsInitialProcess(pid)) {
return ResultSmNotAllowed;
}
return Registration::AssociatePidTidForMitm(pid, tid);
} }

View file

@ -19,46 +19,52 @@
#include <stratosphere.hpp> #include <stratosphere.hpp>
#include "sm_types.hpp" #include "sm_types.hpp"
enum UserServiceCmd { namespace sts::sm {
User_Cmd_Initialize = 0,
User_Cmd_GetService = 1,
User_Cmd_RegisterService = 2,
User_Cmd_UnregisterService = 3,
User_Cmd_AtmosphereInstallMitm = 65000,
User_Cmd_AtmosphereUninstallMitm = 65001,
User_Cmd_AtmosphereAssociatePidTidForMitm = 65002,
User_Cmd_AtmosphereAcknowledgeMitmSession = 65003,
};
/* Service definition. */
class UserService final : public IServiceObject { class UserService final : public IServiceObject {
private: protected:
u64 pid = U64_MAX; /* Command IDs. */
bool has_initialized = false; enum class CommandId {
Initialize = 0,
GetService = 1,
RegisterService = 2,
UnregisterService = 3,
/* Actual commands. */ AtmosphereInstallMitm = 65000,
AtmosphereUninstallMitm = 65001,
AtmosphereAssociatePidTidForMitm = 65002,
AtmosphereAcknowledgeMitmSession = 65003,
};
private:
u64 pid = InvalidProcessId;
bool has_initialized = false;
private:
Result EnsureInitialized();
public:
/* Official commands. */
virtual Result Initialize(PidDescriptor pid); virtual Result Initialize(PidDescriptor pid);
virtual Result GetService(Out<MovedHandle> out_h, SmServiceName service); virtual Result GetService(Out<MovedHandle> out_h, ServiceName service);
virtual Result RegisterService(Out<MovedHandle> out_h, SmServiceName service, u32 max_sessions, bool is_light); virtual Result RegisterService(Out<MovedHandle> out_h, ServiceName service, u32 max_sessions, bool is_light);
virtual Result UnregisterService(SmServiceName service); virtual Result UnregisterService(ServiceName service);
/* Atmosphere commands. */ /* Atmosphere commands. */
virtual Result AtmosphereInstallMitm(Out<MovedHandle> srv_h, Out<MovedHandle> qry_h, SmServiceName service); virtual Result AtmosphereInstallMitm(Out<MovedHandle> srv_h, Out<MovedHandle> qry_h, ServiceName service);
virtual Result AtmosphereUninstallMitm(SmServiceName service); virtual Result AtmosphereUninstallMitm(ServiceName service);
virtual Result AtmosphereAssociatePidTidForMitm(u64 pid, u64 tid); virtual Result AtmosphereAssociatePidTidForMitm(u64 pid, u64 tid);
virtual Result AtmosphereAcknowledgeMitmSession(Out<u64> client_pid, Out<MovedHandle> fwd_h, SmServiceName service); virtual Result AtmosphereAcknowledgeMitmSession(Out<u64> client_pid, Out<MovedHandle> fwd_h, ServiceName service);
public: public:
DEFINE_SERVICE_DISPATCH_TABLE { DEFINE_SERVICE_DISPATCH_TABLE {
MakeServiceCommandMeta<User_Cmd_Initialize, &UserService::Initialize>(), MakeServiceCommandMeta<CommandId::Initialize, &UserService::Initialize>(),
MakeServiceCommandMeta<User_Cmd_GetService, &UserService::GetService>(), MakeServiceCommandMeta<CommandId::GetService, &UserService::GetService>(),
MakeServiceCommandMeta<User_Cmd_RegisterService, &UserService::RegisterService>(), MakeServiceCommandMeta<CommandId::RegisterService, &UserService::RegisterService>(),
MakeServiceCommandMeta<User_Cmd_UnregisterService, &UserService::UnregisterService>(), MakeServiceCommandMeta<CommandId::UnregisterService, &UserService::UnregisterService>(),
#ifdef SM_ENABLE_MITM MakeServiceCommandMeta<CommandId::AtmosphereInstallMitm, &UserService::AtmosphereInstallMitm>(),
MakeServiceCommandMeta<User_Cmd_AtmosphereInstallMitm, &UserService::AtmosphereInstallMitm>(), MakeServiceCommandMeta<CommandId::AtmosphereUninstallMitm, &UserService::AtmosphereUninstallMitm>(),
MakeServiceCommandMeta<User_Cmd_AtmosphereUninstallMitm, &UserService::AtmosphereUninstallMitm>(), MakeServiceCommandMeta<CommandId::AtmosphereAssociatePidTidForMitm, &UserService::AtmosphereAssociatePidTidForMitm>(),
MakeServiceCommandMeta<User_Cmd_AtmosphereAssociatePidTidForMitm, &UserService::AtmosphereAssociatePidTidForMitm>(), MakeServiceCommandMeta<CommandId::AtmosphereAcknowledgeMitmSession, &UserService::AtmosphereAcknowledgeMitmSession>(),
MakeServiceCommandMeta<User_Cmd_AtmosphereAcknowledgeMitmSession, &UserService::AtmosphereAcknowledgeMitmSession>(),
#endif
}; };
}; };
}