Linux: Implements a fault safe memcpy routine

We are required in our syscall emulation to handle cases where pointers
are invalid. This means we need to pessimistically assume a memcpy will
fault when reading application memory.

This implements a signal handler based approach to catching the SIGSEGV
on memcpy and returning an EFAULT if it faults.
This commit is contained in:
Ryan Houdek 2024-01-17 04:09:54 -08:00
parent f956f008ea
commit c5ffc0664d
4 changed files with 92 additions and 0 deletions

View File

@ -4,6 +4,7 @@ set (SRCS
VDSO_Emulation.cpp
LinuxSyscalls/GdbServer.cpp
LinuxSyscalls/EmulatedFiles/EmulatedFiles.cpp
LinuxSyscalls/FaultSafeMemcpy.cpp
LinuxSyscalls/FileManagement.cpp
LinuxSyscalls/LinuxAllocator.cpp
LinuxSyscalls/NetStream.cpp

View File

@ -0,0 +1,72 @@
// SPDX-License-Identifier: MIT
#include "LinuxSyscalls/Syscalls.h"
namespace FEX::HLE::FaultSafeMemcpy {
#ifdef _M_ARM_64
__attribute__((naked))
size_t CopyFromUser(void *Dest, const void* Src, size_t Size) {
__asm volatile(R"(
// Early exit if a memcpy of size zero.
cbz x2, 2f;
1:
.globl CopyFromUser_FaultInst
CopyFromUser_FaultInst:
ldrb w3, [x1], 1; // <- This line can fault.
strb w3, [x0], 1;
sub x2, x2, 1;
cbnz x2, 1b;
2:
mov x0, 0;
ret;
)"
::: "memory");
}
__attribute__((naked))
size_t CopyToUser(void *Dest, const void* Src, size_t Size) {
__asm volatile(R"(
// Early exit if a memcpy of size zero.
cbz x2, 2f;
1:
ldrb w3, [x1], 1;
.globl CopyToUser_FaultInst
CopyToUser_FaultInst:
strb w3, [x0], 1; // <- This line can fault.
sub x2, x2, 1;
cbnz x2, 1b;
2:
mov x0, 0;
ret;
)"
::: "memory");
}
extern "C" uint64_t CopyFromUser_FaultInst;
void * const CopyFromUser_FaultLocation = &CopyFromUser_FaultInst;
extern "C" uint64_t CopyToUser_FaultInst;
void * const CopyToUser_FaultLocation = &CopyToUser_FaultInst;
bool IsFaultLocation(uint64_t PC) {
return reinterpret_cast<void*>(PC) == CopyFromUser_FaultLocation ||
reinterpret_cast<void*>(PC) == CopyToUser_FaultLocation;
}
#else
size_t CopyFromUser(void *Dest, const void* Src, size_t Size) {
memcpy(Dest, Src, Size);
return Size;
}
size_t CopyToUser(void *Dest, const void* Src, size_t Size) {
memcpy(Dest, Src, Size);
return Size;
}
bool IsFaultLocation(uint64_t PC) {
return false;
}
#endif
}

View File

@ -7,6 +7,7 @@ $end_info$
*/
#include "LinuxSyscalls/SignalDelegator.h"
#include "LinuxSyscalls/Syscalls.h"
#include <FEXCore/Core/Context.h>
#include <FEXCore/Core/CoreState.h>
@ -1467,6 +1468,14 @@ namespace FEX::HLE {
#endif
}
}
else if (Signal == SIGSEGV &&
SigInfo.si_code == SEGV_ACCERR &&
FaultSafeMemcpy::IsFaultLocation(ArchHelpers::Context::GetPc(UContext))) {
// Return from the subroutine, returning EFAULT.
ArchHelpers::Context::SetArmReg(UContext, 0, EFAULT);
ArchHelpers::Context::SetPc(UContext, ArchHelpers::Context::GetArmReg(UContext, 30));
return;
}
else {
if (IsAsyncSignal(&SigInfo, Signal) && MustDeferSignal) {
// If the signal is asynchronous (as determined by si_code) and FEX is in a state of needing

View File

@ -531,6 +531,16 @@ static bool HasSyscallError(const void* Result) {
template<bool IncrementOffset, typename T>
uint64_t GetDentsEmulation(int fd, T *dirp, uint32_t count);
namespace FaultSafeMemcpy {
// These are little helper functions for cases when FEX needs to copy data to or from the application in a robust fashion.
// CopyFromUser and CopyToUser are memcpy routines that expect to safely SIGSEGV when reading or writing application memory respectively.
// Returns zero if the memcpy completed, or crashes with SIGABRT and a log message if it faults.
[[nodiscard]] size_t CopyFromUser(void *Dest, const void* Src, size_t Size);
[[nodiscard]] size_t CopyToUser(void *Dest, const void* Src, size_t Size);
bool IsFaultLocation(uint64_t PC);
}
}
// Registers syscall for both 32bit and 64bit