IPC: perform memory checks, virtually no perf hit

This commit is contained in:
Gauvain 'GovanifY' Roussel-Tarbouriech 2020-08-20 07:33:27 +02:00 committed by tellowkrinkle
parent 89ce774d7e
commit 439ed9617c
2 changed files with 50 additions and 33 deletions

View File

@ -169,24 +169,24 @@ char* SocketIPC::MakeFailIPC(char* ret_buffer)
SocketIPC::IPCBuffer SocketIPC::ParseCommand(char* buf, char* ret_buffer) SocketIPC::IPCBuffer SocketIPC::ParseCommand(char* buf, char* ret_buffer)
{ {
// currently all our instructions require a running VM so we check once // currently all our instructions require a running VM so we check once
// here, will help perf when/if we implement multi-ipc processing in one // here, slightly helps performance
// socket roundtrip.
if (!m_vm->HasActiveMachine()) if (!m_vm->HasActiveMachine())
return IPCBuffer{1, MakeFailIPC(ret_buffer)}; return IPCBuffer{1, MakeFailIPC(ret_buffer)};
u16 batch = 1; u16 batch = 1;
u32 ret_cnt = 1; u32 ret_cnt = 1;
u32 buf_cnt = 0;
if ((IPCCommand)buf[0] == MsgMultiCommand) if ((IPCCommand)buf[0] == MsgMultiCommand)
{ {
batch = FromArray<u16>(buf, 1); batch = FromArray<u16>(&buf[buf_cnt], 1);
buf += 3; buf_cnt += 3;
} }
for (u16 i = 0; i < batch; i++) for (u16 i = 0; i < batch; i++)
{ {
// YY YY YY YY from schema below // YY YY YY YY from schema below
u32 a = FromArray<u32>(buf, 1); u32 a = FromArray<u32>(&buf[buf_cnt], 1);
// IPC Message event (1 byte) // IPC Message event (1 byte)
// | Memory address (4 byte) // | Memory address (4 byte)
@ -197,82 +197,87 @@ SocketIPC::IPCBuffer SocketIPC::ParseCommand(char* buf, char* ret_buffer)
// | return value (VLE) // | return value (VLE)
// | | // | |
// reply: XX ZZ ZZ ZZ ZZ // reply: XX ZZ ZZ ZZ ZZ
// switch ((IPCCommand)buf[buf_cnt])
// NB: memory safety checking would be very expansive in our case,
// implemented per command and simply a mess. As our threat model is
// nonexistant, knowing we can disable the IPC at any time and having
// checks client-side it simply makes more sense to not do check
// server-side, as bad as this sounds.
// Re security threat model: we control the entire emulated memory of
// the emulated game, we can DoS our emulator easily by abusing the IPC
// features already, and regardless of where you read this there's
// probably no sandbox implemented, so simply being able to write to the
// game memory code region would make us control the JIT and have probably
// full access over the host machine.
switch ((IPCCommand)buf[0])
{ {
case MsgRead8: case MsgRead8:
{ {
if (!SafetyChecks(buf_cnt, 5, ret_cnt, 1))
goto error;
u8 res; u8 res;
res = memRead8(a); res = memRead8(a);
ToArray(ret_buffer, res, ret_cnt); ToArray(ret_buffer, res, ret_cnt);
ret_cnt += 1; ret_cnt += 1;
buf += 5; buf_cnt += 5;
break; break;
} }
case MsgRead16: case MsgRead16:
{ {
if (!SafetyChecks(buf_cnt, 5, ret_cnt, 2))
goto error;
u16 res; u16 res;
res = memRead16(a); res = memRead16(a);
ToArray(ret_buffer, res, ret_cnt); ToArray(ret_buffer, res, ret_cnt);
ret_cnt += 2; ret_cnt += 2;
buf += 5; buf_cnt += 5;
break; break;
} }
case MsgRead32: case MsgRead32:
{ {
if (!SafetyChecks(buf_cnt, 5, ret_cnt, 4))
goto error;
u32 res; u32 res;
res = memRead32(a); res = memRead32(a);
ToArray(ret_buffer, res, ret_cnt); ToArray(ret_buffer, res, ret_cnt);
ret_cnt += 4; ret_cnt += 4;
buf += 5; buf_cnt += 5;
break; break;
} }
case MsgRead64: case MsgRead64:
{ {
if (!SafetyChecks(buf_cnt, 5, ret_cnt, 8))
goto error;
u64 res; u64 res;
memRead64(a, &res); memRead64(a, &res);
ToArray(ret_buffer, res, ret_cnt); ToArray(ret_buffer, res, ret_cnt);
ret_cnt += 8; ret_cnt += 8;
buf += 5; buf_cnt += 5;
break; break;
} }
case MsgWrite8: case MsgWrite8:
{ {
memWrite8(a, FromArray<u8>(buf, 5)); if (!SafetyChecks(buf_cnt, 6, ret_cnt))
buf += 6; goto error;
memWrite8(a, FromArray<u8>(&buf[buf_cnt], 5));
buf_cnt += 6;
break; break;
} }
case MsgWrite16: case MsgWrite16:
{ {
memWrite16(a, FromArray<u16>(buf, 5)); if (!SafetyChecks(buf_cnt, 7, ret_cnt))
buf += 7; goto error;
memWrite16(a, FromArray<u16>(&buf[buf_cnt], 5));
buf_cnt += 7;
break; break;
} }
case MsgWrite32: case MsgWrite32:
{ {
memWrite32(a, FromArray<u32>(buf, 5)); if (!SafetyChecks(buf_cnt, 9, ret_cnt))
buf += 9; goto error;
memWrite32(a, FromArray<u32>(&buf[buf_cnt], 5));
buf_cnt += 9;
break; break;
} }
case MsgWrite64: case MsgWrite64:
{ {
memWrite64(a, FromArray<u64>(buf, 5)); if (!SafetyChecks(buf_cnt, 13, ret_cnt))
buf += 13; goto error;
memWrite64(a, FromArray<u64>(&buf[buf_cnt], 5));
buf_cnt += 13;
break; break;
} }
default: default:
{ {
error:
return IPCBuffer{1, MakeFailIPC(ret_buffer)}; return IPCBuffer{1, MakeFailIPC(ret_buffer)};
} }
} }

View File

@ -42,18 +42,17 @@ protected:
int m_sock = 0; int m_sock = 0;
#endif #endif
/** /**
* Maximum memory used by an IPC message request. * Maximum memory used by an IPC message request.
* Equivalent to 50,000 Write64 requests. * Equivalent to 50,000 Write64 requests.
*/ */
const unsigned int MAX_IPC_SIZE = 650000; #define MAX_IPC_SIZE 650000
/** /**
* Maximum memory used by an IPC message reply. * Maximum memory used by an IPC message reply.
* Equivalent to 50,000 Read64 replies. * Equivalent to 50,000 Read64 replies.
*/ */
const unsigned int MAX_IPC_RETURN_SIZE = 450000; #define MAX_IPC_RETURN_SIZE 450000
/** /**
* IPC return buffer. * IPC return buffer.
@ -154,6 +153,19 @@ protected:
return *(T*)(arr + i); return *(T*)(arr + i);
} }
/**
* Ensures an IPC message isn't too big.
* return value: false if checks failed, true otherwise.
*/
static inline bool SafetyChecks(u32 command_len, int command_size, u32 reply_len, int reply_size = 0)
{
bool res = ((command_len + command_size) >= MAX_IPC_SIZE ||
(reply_len + reply_size) >= MAX_IPC_RETURN_SIZE);
if (unlikely(res))
return false;
return true;
}
public: public:
/* Initializers */ /* Initializers */
SocketIPC(SysCoreThread* vm); SocketIPC(SysCoreThread* vm);