mirror of
https://github.com/Atmosphere-NX/Atmosphere.git
synced 2024-11-15 03:27:49 +01:00
Merge pull request #585 from Atmosphere-NX/sm_rewrite
Completely re-write sm.
This commit is contained in:
commit
e62754ed56
@ -1 +1 @@
|
||||
Subproject commit 4ccee4257763373c8e8dd5453246a592482f9a86
|
||||
Subproject commit 0c26276b21e4cf0f7a0665c0280b3888ebfbd1af
|
@ -41,7 +41,7 @@ ARCH := -march=armv8-a -mtune=cortex-a57 -mtp=soft -fPIE
|
||||
CFLAGS := -g -Wall -O2 -ffunction-sections \
|
||||
$(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
|
||||
|
||||
|
@ -16,18 +16,22 @@
|
||||
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "sm_dmnt_service.hpp"
|
||||
#include "sm_registration.hpp"
|
||||
#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<ServiceRecord> records, Out<u64> out_count, u64 offset) {
|
||||
R_ASSERT(sm::ListServiceRecords(records.buffer, out_count.GetPointer(), offset, records.num_elements));
|
||||
}
|
||||
|
||||
void DmntService::AtmosphereGetRecordSize(Out<u64> record_size) {
|
||||
record_size.SetValue(sizeof(ServiceRecord));
|
||||
}
|
||||
|
||||
Result DmntService::AtmosphereGetRecord(Out<SmServiceRecord> record, SmServiceName service) {
|
||||
return Registration::GetServiceRecord(smEncodeName(service.name), record.GetPointer());
|
||||
}
|
||||
|
||||
void DmntService::AtmosphereListRecords(OutBuffer<SmServiceRecord> records, Out<u64> out_count, u64 offset) {
|
||||
Registration::ListServiceRecords(offset, records.num_elements, records.buffer, out_count.GetPointer());
|
||||
}
|
||||
|
||||
void DmntService::AtmosphereGetRecordSize(Out<u64> record_size) {
|
||||
record_size.SetValue(sizeof(SmServiceRecord));
|
||||
}
|
||||
|
||||
|
@ -19,22 +19,28 @@
|
||||
#include <stratosphere.hpp>
|
||||
#include "sm_types.hpp"
|
||||
|
||||
enum DmntServiceCmd {
|
||||
Dmnt_Cmd_AtmosphereGetRecord = 65000,
|
||||
Dmnt_Cmd_AtmosphereListRecords = 65001,
|
||||
Dmnt_Cmd_AtmosphereGetRecordSize = 65002,
|
||||
};
|
||||
namespace sts::sm {
|
||||
|
||||
class DmntService final : public IServiceObject {
|
||||
private:
|
||||
/* Actual commands. */
|
||||
virtual Result AtmosphereGetRecord(Out<SmServiceRecord> record, SmServiceName service);
|
||||
virtual void AtmosphereListRecords(OutBuffer<SmServiceRecord> records, Out<u64> out_count, u64 offset);
|
||||
virtual void AtmosphereGetRecordSize(Out<u64> record_size);
|
||||
public:
|
||||
DEFINE_SERVICE_DISPATCH_TABLE {
|
||||
MakeServiceCommandMeta<Dmnt_Cmd_AtmosphereGetRecord, &DmntService::AtmosphereGetRecord>(),
|
||||
MakeServiceCommandMeta<Dmnt_Cmd_AtmosphereListRecords, &DmntService::AtmosphereListRecords>(),
|
||||
MakeServiceCommandMeta<Dmnt_Cmd_AtmosphereGetRecordSize, &DmntService::AtmosphereGetRecordSize>(),
|
||||
};
|
||||
};
|
||||
/* Service definition. */
|
||||
class DmntService final : public IServiceObject {
|
||||
protected:
|
||||
/* Command IDs. */
|
||||
enum class CommandId {
|
||||
AtmosphereGetRecord = 65000,
|
||||
AtmosphereListRecords = 65001,
|
||||
AtmosphereGetRecordSize = 65002,
|
||||
};
|
||||
private:
|
||||
/* Actual commands. */
|
||||
virtual Result AtmosphereGetRecord(Out<ServiceRecord> record, ServiceName service);
|
||||
virtual void AtmosphereListRecords(OutBuffer<ServiceRecord> records, Out<u64> out_count, u64 offset);
|
||||
virtual void AtmosphereGetRecordSize(Out<u64> record_size);
|
||||
public:
|
||||
DEFINE_SERVICE_DISPATCH_TABLE {
|
||||
MakeServiceCommandMeta<CommandId::AtmosphereGetRecord, &DmntService::AtmosphereGetRecord>(),
|
||||
MakeServiceCommandMeta<CommandId::AtmosphereListRecords, &DmntService::AtmosphereListRecords>(),
|
||||
MakeServiceCommandMeta<CommandId::AtmosphereGetRecordSize, &DmntService::AtmosphereGetRecordSize>(),
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -22,10 +22,10 @@
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "sm_manager_service.hpp"
|
||||
#include "sm_service_manager.hpp"
|
||||
#include "sm_user_service.hpp"
|
||||
#include "sm_manager_service.hpp"
|
||||
#include "sm_dmnt_service.hpp"
|
||||
#include "sm_registration.hpp"
|
||||
|
||||
extern "C" {
|
||||
extern u32 __start__;
|
||||
@ -54,15 +54,15 @@ void __libnx_exception_handler(ThreadExceptionDump *ctx) {
|
||||
|
||||
|
||||
void __libnx_initheap(void) {
|
||||
void* addr = nx_inner_heap;
|
||||
size_t size = nx_inner_heap_size;
|
||||
void* addr = nx_inner_heap;
|
||||
size_t size = nx_inner_heap_size;
|
||||
|
||||
/* Newlib */
|
||||
extern char* fake_heap_start;
|
||||
extern char* fake_heap_end;
|
||||
/* Newlib */
|
||||
extern char* fake_heap_start;
|
||||
extern char* fake_heap_end;
|
||||
|
||||
fake_heap_start = (char*)addr;
|
||||
fake_heap_end = (char*)addr + size;
|
||||
fake_heap_start = (char*)addr;
|
||||
fake_heap_end = (char*)addr + size;
|
||||
}
|
||||
|
||||
void __appInit(void) {
|
||||
@ -75,37 +75,30 @@ void __appExit(void) {
|
||||
/* Nothing to clean up, because we're sm. */
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
consoleDebugInit(debugDevice_SVC);
|
||||
|
||||
/* TODO: What's a good timeout value to use here? */
|
||||
/* Create service waitable manager. */
|
||||
static auto s_server_manager = WaitableManager(1);
|
||||
|
||||
/* 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. */
|
||||
Handle smm_h;
|
||||
R_ASSERT(Registration::RegisterServiceForSelf(smEncodeName("sm:m"), 1, false, &smm_h));
|
||||
|
||||
s_server_manager.AddWaitable(new ExistingPortServer<ManagerService>(smm_h, 1));
|
||||
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));
|
||||
|
||||
/*===== ATMOSPHERE EXTENSION =====*/
|
||||
/* Create sm:dmnt manually. */
|
||||
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. */
|
||||
s_server_manager.Process();
|
||||
|
||||
/* Cleanup. */
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -16,22 +16,26 @@
|
||||
|
||||
#include <switch.h>
|
||||
#include <stratosphere.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) {
|
||||
return sm::RegisterProcess(pid, acid_sac.buffer, acid_sac.num_elements, aci0_sac.buffer, aci0_sac.num_elements);
|
||||
}
|
||||
|
||||
Result ManagerService::UnregisterProcess(u64 pid) {
|
||||
return sm::UnregisterProcess(pid);
|
||||
}
|
||||
|
||||
void ManagerService::AtmosphereEndInitDefers() {
|
||||
R_ASSERT(sm::EndInitialDefers());
|
||||
}
|
||||
|
||||
void ManagerService::AtmosphereHasMitm(Out<bool> out, ServiceName service) {
|
||||
R_ASSERT(sm::HasMitm(out.GetPointer(), service));
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
Result ManagerService::UnregisterProcess(u64 pid) {
|
||||
return Registration::UnregisterProcess(pid);
|
||||
}
|
||||
|
||||
void ManagerService::AtmosphereEndInitDefers() {
|
||||
Registration::EndInitDefers();
|
||||
}
|
||||
|
||||
void ManagerService::AtmosphereHasMitm(Out<bool> out, SmServiceName service) {
|
||||
out.SetValue(Registration::HasMitm(smEncodeName(service.name)));
|
||||
}
|
||||
|
||||
|
@ -19,27 +19,33 @@
|
||||
#include <stratosphere.hpp>
|
||||
#include "sm_types.hpp"
|
||||
|
||||
enum ManagerServiceCmd {
|
||||
Manager_Cmd_RegisterProcess = 0,
|
||||
Manager_Cmd_UnregisterProcess = 1,
|
||||
namespace sts::sm {
|
||||
|
||||
Manager_Cmd_AtmosphereEndInitDefers = 65000,
|
||||
Manager_Cmd_AtmosphereHasMitm = 65001,
|
||||
};
|
||||
/* Service definition. */
|
||||
class ManagerService final : public IServiceObject {
|
||||
protected:
|
||||
/* Command IDs. */
|
||||
enum class CommandId {
|
||||
RegisterProcess = 0,
|
||||
UnregisterProcess = 1,
|
||||
|
||||
class ManagerService final : public IServiceObject {
|
||||
private:
|
||||
/* Actual commands. */
|
||||
virtual Result RegisterProcess(u64 pid, InBuffer<u8> acid_sac, InBuffer<u8> aci0_sac);
|
||||
virtual Result UnregisterProcess(u64 pid);
|
||||
virtual void AtmosphereEndInitDefers();
|
||||
virtual void AtmosphereHasMitm(Out<bool> out, SmServiceName service);
|
||||
public:
|
||||
DEFINE_SERVICE_DISPATCH_TABLE {
|
||||
MakeServiceCommandMeta<Manager_Cmd_RegisterProcess, &ManagerService::RegisterProcess>(),
|
||||
MakeServiceCommandMeta<Manager_Cmd_UnregisterProcess, &ManagerService::UnregisterProcess>(),
|
||||
AtmosphereEndInitDefers = 65000,
|
||||
AtmosphereHasMitm = 65001,
|
||||
};
|
||||
private:
|
||||
/* Actual commands. */
|
||||
virtual Result RegisterProcess(u64 pid, InBuffer<u8> acid_sac, InBuffer<u8> aci0_sac);
|
||||
virtual Result UnregisterProcess(u64 pid);
|
||||
virtual void AtmosphereEndInitDefers();
|
||||
virtual void AtmosphereHasMitm(Out<bool> out, ServiceName service);
|
||||
public:
|
||||
DEFINE_SERVICE_DISPATCH_TABLE {
|
||||
MakeServiceCommandMeta<CommandId::RegisterProcess, &ManagerService::RegisterProcess>(),
|
||||
MakeServiceCommandMeta<CommandId::UnregisterProcess, &ManagerService::UnregisterProcess>(),
|
||||
|
||||
MakeServiceCommandMeta<Manager_Cmd_AtmosphereEndInitDefers, &ManagerService::AtmosphereEndInitDefers>(),
|
||||
MakeServiceCommandMeta<Manager_Cmd_AtmosphereHasMitm, &ManagerService::AtmosphereHasMitm>(),
|
||||
};
|
||||
};
|
||||
MakeServiceCommandMeta<CommandId::AtmosphereEndInitDefers, &ManagerService::AtmosphereEndInitDefers>(),
|
||||
MakeServiceCommandMeta<CommandId::AtmosphereHasMitm, &ManagerService::AtmosphereHasMitm>(),
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
@ -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);
|
||||
};
|
730
stratosphere/sm/source/sm_service_manager.cpp
Normal file
730
stratosphere/sm/source/sm_service_manager.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
48
stratosphere/sm/source/sm_service_manager.hpp
Normal file
48
stratosphere/sm/source/sm_service_manager.hpp
Normal 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();
|
||||
|
||||
}
|
@ -15,21 +15,57 @@
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
struct SmServiceName {
|
||||
char name[sizeof(u64)];
|
||||
};
|
||||
#include <cstring>
|
||||
|
||||
static_assert(__alignof__(SmServiceName) == 1, "SmServiceName definition!");
|
||||
namespace sts::sm {
|
||||
|
||||
/* For Debug Monitor extensions. */
|
||||
struct SmServiceRecord {
|
||||
u64 service_name;
|
||||
u64 owner_pid;
|
||||
u64 max_sessions;
|
||||
u64 mitm_pid;
|
||||
u64 mitm_waiting_ack_pid;
|
||||
bool is_light;
|
||||
bool mitm_waiting_ack;
|
||||
};
|
||||
struct ServiceName {
|
||||
static constexpr size_t MaxLength = 8;
|
||||
|
||||
static_assert(sizeof(SmServiceRecord) == 0x30, "SmServiceRecord definition!");
|
||||
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. */
|
||||
struct ServiceRecord {
|
||||
ServiceName service;
|
||||
u64 owner_pid;
|
||||
u64 max_sessions;
|
||||
u64 mitm_pid;
|
||||
u64 mitm_waiting_ack_pid;
|
||||
bool is_light;
|
||||
bool mitm_waiting_ack;
|
||||
};
|
||||
static_assert(sizeof(ServiceRecord) == 0x30, "ServiceRecord definition!");
|
||||
|
||||
/* For process validation. */
|
||||
static constexpr u64 InvalidProcessId = static_cast<u64>(-1ull);
|
||||
|
||||
}
|
||||
|
@ -16,93 +16,58 @@
|
||||
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "sm_user_service.hpp"
|
||||
#include "sm_registration.hpp"
|
||||
#include "sm_service_manager.hpp"
|
||||
|
||||
Result UserService::Initialize(PidDescriptor pid) {
|
||||
this->pid = pid.pid;
|
||||
this->has_initialized = true;
|
||||
return ResultSuccess;
|
||||
}
|
||||
namespace sts::sm {
|
||||
|
||||
Result UserService::GetService(Out<MovedHandle> out_h, SmServiceName service) {
|
||||
Handle session_h = 0;
|
||||
|
||||
if (!this->has_initialized) {
|
||||
return ResultSmInvalidClient;
|
||||
Result UserService::Initialize(PidDescriptor pid) {
|
||||
this->pid = pid.pid;
|
||||
this->has_initialized = true;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
R_TRY(Registration::GetServiceForPid(this->pid, smEncodeName(service.name), &session_h));
|
||||
|
||||
out_h.SetValue(session_h);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result UserService::RegisterService(Out<MovedHandle> out_h, SmServiceName service, u32 max_sessions, bool is_light) {
|
||||
Handle service_h = 0;
|
||||
|
||||
if (!this->has_initialized) {
|
||||
return ResultSmInvalidClient;
|
||||
Result UserService::EnsureInitialized() {
|
||||
if (!this->has_initialized) {
|
||||
return ResultSmInvalidClient;
|
||||
}
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
R_TRY(Registration::RegisterServiceForPid(this->pid, smEncodeName(service.name), max_sessions, (is_light & 1) != 0, &service_h));
|
||||
|
||||
out_h.SetValue(service_h);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result UserService::UnregisterService(SmServiceName service) {
|
||||
if (!this->has_initialized) {
|
||||
return ResultSmInvalidClient;
|
||||
Result UserService::GetService(Out<MovedHandle> out_h, ServiceName service) {
|
||||
R_TRY(this->EnsureInitialized());
|
||||
return sm::GetServiceHandle(out_h.GetHandlePointer(), this->pid, service);
|
||||
}
|
||||
|
||||
return Registration::UnregisterServiceForPid(this->pid, smEncodeName(service.name));
|
||||
}
|
||||
|
||||
Result UserService::AtmosphereInstallMitm(Out<MovedHandle> srv_h, Out<MovedHandle> qry_h, SmServiceName service) {
|
||||
Handle service_h = 0;
|
||||
Handle query_h = 0;
|
||||
|
||||
if (!this->has_initialized) {
|
||||
return ResultSmInvalidClient;
|
||||
Result UserService::RegisterService(Out<MovedHandle> out_h, ServiceName service, u32 max_sessions, bool is_light) {
|
||||
R_TRY(this->EnsureInitialized());
|
||||
return sm::RegisterService(out_h.GetHandlePointer(), this->pid, service, max_sessions, is_light);
|
||||
}
|
||||
|
||||
R_TRY(Registration::InstallMitmForPid(this->pid, smEncodeName(service.name), &service_h, &query_h));
|
||||
|
||||
srv_h.SetValue(service_h);
|
||||
qry_h.SetValue(query_h);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result UserService::AtmosphereUninstallMitm(SmServiceName service) {
|
||||
if (!this->has_initialized) {
|
||||
return ResultSmInvalidClient;
|
||||
Result UserService::UnregisterService(ServiceName service) {
|
||||
R_TRY(this->EnsureInitialized());
|
||||
return sm::UnregisterService(this->pid, service);
|
||||
}
|
||||
|
||||
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;
|
||||
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);
|
||||
}
|
||||
|
||||
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) {
|
||||
if (!this->has_initialized) {
|
||||
return ResultSmInvalidClient;
|
||||
Result UserService::AtmosphereUninstallMitm(ServiceName service) {
|
||||
R_TRY(this->EnsureInitialized());
|
||||
return sm::UninstallMitm(this->pid, service);
|
||||
}
|
||||
|
||||
if (Registration::IsInitialProcess(pid)) {
|
||||
return ResultSmNotAllowed;
|
||||
Result UserService::AtmosphereAcknowledgeMitmSession(Out<u64> client_pid, Out<MovedHandle> fwd_h, ServiceName service) {
|
||||
R_TRY(this->EnsureInitialized());
|
||||
return sm::AcknowledgeMitmSession(client_pid.GetPointer(), fwd_h.GetHandlePointer(), this->pid, service);
|
||||
}
|
||||
|
||||
Result UserService::AtmosphereAssociatePidTidForMitm(u64 pid, u64 tid) {
|
||||
R_TRY(this->EnsureInitialized());
|
||||
return sm::AssociatePidTidForMitm(pid, tid);
|
||||
}
|
||||
|
||||
return Registration::AssociatePidTidForMitm(pid, tid);
|
||||
}
|
||||
|
@ -19,46 +19,52 @@
|
||||
#include <stratosphere.hpp>
|
||||
#include "sm_types.hpp"
|
||||
|
||||
enum UserServiceCmd {
|
||||
User_Cmd_Initialize = 0,
|
||||
User_Cmd_GetService = 1,
|
||||
User_Cmd_RegisterService = 2,
|
||||
User_Cmd_UnregisterService = 3,
|
||||
namespace sts::sm {
|
||||
|
||||
User_Cmd_AtmosphereInstallMitm = 65000,
|
||||
User_Cmd_AtmosphereUninstallMitm = 65001,
|
||||
User_Cmd_AtmosphereAssociatePidTidForMitm = 65002,
|
||||
User_Cmd_AtmosphereAcknowledgeMitmSession = 65003,
|
||||
};
|
||||
/* Service definition. */
|
||||
class UserService final : public IServiceObject {
|
||||
protected:
|
||||
/* Command IDs. */
|
||||
enum class CommandId {
|
||||
Initialize = 0,
|
||||
GetService = 1,
|
||||
RegisterService = 2,
|
||||
UnregisterService = 3,
|
||||
|
||||
class UserService final : public IServiceObject {
|
||||
private:
|
||||
u64 pid = U64_MAX;
|
||||
bool has_initialized = false;
|
||||
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 GetService(Out<MovedHandle> out_h, ServiceName service);
|
||||
virtual Result RegisterService(Out<MovedHandle> out_h, ServiceName service, u32 max_sessions, bool is_light);
|
||||
virtual Result UnregisterService(ServiceName service);
|
||||
|
||||
/* Actual commands. */
|
||||
virtual Result Initialize(PidDescriptor pid);
|
||||
virtual Result GetService(Out<MovedHandle> out_h, SmServiceName service);
|
||||
virtual Result RegisterService(Out<MovedHandle> out_h, SmServiceName service, u32 max_sessions, bool is_light);
|
||||
virtual Result UnregisterService(SmServiceName service);
|
||||
/* Atmosphere commands. */
|
||||
virtual Result AtmosphereInstallMitm(Out<MovedHandle> srv_h, Out<MovedHandle> qry_h, ServiceName service);
|
||||
virtual Result AtmosphereUninstallMitm(ServiceName service);
|
||||
virtual Result AtmosphereAssociatePidTidForMitm(u64 pid, u64 tid);
|
||||
virtual Result AtmosphereAcknowledgeMitmSession(Out<u64> client_pid, Out<MovedHandle> fwd_h, ServiceName service);
|
||||
public:
|
||||
DEFINE_SERVICE_DISPATCH_TABLE {
|
||||
MakeServiceCommandMeta<CommandId::Initialize, &UserService::Initialize>(),
|
||||
MakeServiceCommandMeta<CommandId::GetService, &UserService::GetService>(),
|
||||
MakeServiceCommandMeta<CommandId::RegisterService, &UserService::RegisterService>(),
|
||||
MakeServiceCommandMeta<CommandId::UnregisterService, &UserService::UnregisterService>(),
|
||||
|
||||
/* Atmosphere commands. */
|
||||
virtual Result AtmosphereInstallMitm(Out<MovedHandle> srv_h, Out<MovedHandle> qry_h, SmServiceName service);
|
||||
virtual Result AtmosphereUninstallMitm(SmServiceName service);
|
||||
virtual Result AtmosphereAssociatePidTidForMitm(u64 pid, u64 tid);
|
||||
virtual Result AtmosphereAcknowledgeMitmSession(Out<u64> client_pid, Out<MovedHandle> fwd_h, SmServiceName service);
|
||||
public:
|
||||
DEFINE_SERVICE_DISPATCH_TABLE {
|
||||
MakeServiceCommandMeta<User_Cmd_Initialize, &UserService::Initialize>(),
|
||||
MakeServiceCommandMeta<User_Cmd_GetService, &UserService::GetService>(),
|
||||
MakeServiceCommandMeta<User_Cmd_RegisterService, &UserService::RegisterService>(),
|
||||
MakeServiceCommandMeta<User_Cmd_UnregisterService, &UserService::UnregisterService>(),
|
||||
MakeServiceCommandMeta<CommandId::AtmosphereInstallMitm, &UserService::AtmosphereInstallMitm>(),
|
||||
MakeServiceCommandMeta<CommandId::AtmosphereUninstallMitm, &UserService::AtmosphereUninstallMitm>(),
|
||||
MakeServiceCommandMeta<CommandId::AtmosphereAssociatePidTidForMitm, &UserService::AtmosphereAssociatePidTidForMitm>(),
|
||||
MakeServiceCommandMeta<CommandId::AtmosphereAcknowledgeMitmSession, &UserService::AtmosphereAcknowledgeMitmSession>(),
|
||||
};
|
||||
};
|
||||
|
||||
#ifdef SM_ENABLE_MITM
|
||||
MakeServiceCommandMeta<User_Cmd_AtmosphereInstallMitm, &UserService::AtmosphereInstallMitm>(),
|
||||
MakeServiceCommandMeta<User_Cmd_AtmosphereUninstallMitm, &UserService::AtmosphereUninstallMitm>(),
|
||||
MakeServiceCommandMeta<User_Cmd_AtmosphereAssociatePidTidForMitm, &UserService::AtmosphereAssociatePidTidForMitm>(),
|
||||
MakeServiceCommandMeta<User_Cmd_AtmosphereAcknowledgeMitmSession, &UserService::AtmosphereAcknowledgeMitmSession>(),
|
||||
#endif
|
||||
};
|
||||
};
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user