-
Notifications
You must be signed in to change notification settings - Fork 135
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3375 from Sonicadvance1/implement_efault_support
Linux: Implements a fault safe memcpy routine
- Loading branch information
Showing
8 changed files
with
285 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
72 changes: 72 additions & 0 deletions
72
Source/Tools/LinuxEmulation/LinuxSyscalls/FaultSafeMemcpy.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
169 changes: 169 additions & 0 deletions
169
unittests/FEXLinuxTests/tests/syscalls/syscalls_efault.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
#include <catch2/catch.hpp> | ||
|
||
#include <cstdint> | ||
#include <unistd.h> | ||
#include <sys/mman.h> | ||
#include <sys/syscall.h> | ||
#include <poll.h> | ||
#include <signal.h> | ||
|
||
TEST_CASE("poll") { | ||
// poll can return EFAULT if first argument is pointed to invalid pointer. | ||
// Using mmap specifically for allocating with PROT_NONE. | ||
struct pollfd *invalid_fds = reinterpret_cast<struct pollfd *>(mmap(nullptr, sysconf(_SC_PAGESIZE), PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); | ||
auto ret = ::syscall(SYS_poll, invalid_fds, 1, 0); | ||
REQUIRE(ret == -1); | ||
CHECK(errno == EFAULT); | ||
} | ||
|
||
TEST_CASE("ppoll") { | ||
// ppoll can return EFAULT for arguments 1, 3, 4. | ||
// Using mmap specifically for allocating with PROT_NONE. | ||
struct pollfd *invalid_fds = reinterpret_cast<struct pollfd *>(mmap(nullptr, sysconf(_SC_PAGESIZE), PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); | ||
struct timespec *invalid_timespec = reinterpret_cast<struct timespec*>(mmap(nullptr, sysconf(_SC_PAGESIZE), PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); | ||
sigset_t *invalid_sigset = reinterpret_cast<sigset_t*>(mmap(nullptr, sysconf(_SC_PAGESIZE), PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); | ||
|
||
SECTION("invalid fds") { | ||
auto ret = ::syscall(SYS_ppoll, invalid_fds, 1, 0, nullptr, nullptr); | ||
REQUIRE(ret == -1); | ||
CHECK(errno == EFAULT); | ||
} | ||
|
||
SECTION("invalid timespec") { | ||
struct pollfd valid_fds { | ||
.fd = STDOUT_FILENO, | ||
.events = 0, | ||
.revents = 0, | ||
}; | ||
auto ret = ::syscall(SYS_ppoll, &valid_fds, 1, invalid_timespec, nullptr, sizeof(uint64_t)); | ||
REQUIRE(ret == -1); | ||
CHECK(errno == EFAULT); | ||
} | ||
|
||
SECTION("invalid sigset") { | ||
struct pollfd valid_fds { | ||
.fd = STDOUT_FILENO, | ||
.events = 0, | ||
.revents = 0, | ||
}; | ||
|
||
struct timespec valid_ts{}; | ||
auto ret = ::syscall(SYS_ppoll, &valid_fds, 1, &valid_ts, invalid_sigset, sizeof(uint64_t)); | ||
REQUIRE(ret == -1); | ||
CHECK(errno == EFAULT); | ||
} | ||
|
||
SECTION("valid configuration") { | ||
struct pollfd valid_fds { | ||
.fd = STDOUT_FILENO, | ||
.events = 0, | ||
.revents = 0, | ||
}; | ||
|
||
struct timespec valid_ts{}; | ||
sigset_t valid_sigset{}; | ||
sigemptyset(&valid_sigset); | ||
auto ret = ::syscall(SYS_ppoll, &valid_fds, 1, &valid_ts, &valid_sigset, sizeof(uint64_t)); | ||
REQUIRE(ret == 0); | ||
} | ||
|
||
SECTION("invalid timespec write-back") { | ||
struct pollfd valid_fds { | ||
.fd = STDOUT_FILENO, | ||
.events = 0, | ||
.revents = 0, | ||
}; | ||
|
||
// Kernel will read timespec, but it then can't write the result back. | ||
mprotect(invalid_timespec, sysconf(_SC_PAGESIZE), PROT_READ | PROT_WRITE); | ||
invalid_timespec->tv_sec = 1; | ||
mprotect(invalid_timespec, sysconf(_SC_PAGESIZE), PROT_READ); | ||
|
||
sigset_t valid_sigset{}; | ||
sigemptyset(&valid_sigset); | ||
auto ret = ::syscall(SYS_ppoll, &valid_fds, 1, invalid_timespec, &valid_sigset, sizeof(uint64_t)); | ||
REQUIRE(ret == 0); | ||
CHECK(invalid_timespec->tv_sec == 1); | ||
} | ||
} | ||
|
||
struct timespec64 { | ||
uint64_t tv_sec, tv_nsec; | ||
}; | ||
|
||
static const timespec64 readonly_ts { | ||
.tv_sec = 1, | ||
.tv_nsec = 0, | ||
}; | ||
|
||
|
||
TEST_CASE("ppoll_64") { | ||
#ifndef SYS_ppoll_time64 | ||
#define SYS_ppoll_time64 SYS_ppoll | ||
#endif | ||
// ppoll can return EFAULT for arguments 1, 3, 4 | ||
// Using mmap specifically for allocating with PROT_NONE. | ||
struct pollfd *invalid_fds = reinterpret_cast<struct pollfd *>(mmap(nullptr, sysconf(_SC_PAGESIZE), PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); | ||
timespec64 *invalid_timespec = reinterpret_cast<timespec64*>(mmap(nullptr, sysconf(_SC_PAGESIZE), PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); | ||
sigset_t *invalid_sigset = reinterpret_cast<sigset_t*>(mmap(nullptr, sysconf(_SC_PAGESIZE), PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); | ||
|
||
SECTION("invalid fds") { | ||
auto ret = ::syscall(SYS_ppoll_time64, invalid_fds, 1, 0, nullptr, nullptr); | ||
REQUIRE(ret == -1); | ||
CHECK(errno == EFAULT); | ||
} | ||
|
||
SECTION("invalid timespec") { | ||
struct pollfd valid_fds { | ||
.fd = STDOUT_FILENO, | ||
.events = 0, | ||
.revents = 0, | ||
}; | ||
auto ret = ::syscall(SYS_ppoll_time64, &valid_fds, 1, invalid_timespec, nullptr, sizeof(uint64_t)); | ||
REQUIRE(ret == -1); | ||
CHECK(errno == EFAULT); | ||
} | ||
|
||
SECTION("invalid sigset") { | ||
struct pollfd valid_fds { | ||
.fd = STDOUT_FILENO, | ||
.events = 0, | ||
.revents = 0, | ||
}; | ||
|
||
timespec64 valid_ts{}; | ||
auto ret = ::syscall(SYS_ppoll_time64, &valid_fds, 1, &valid_ts, invalid_sigset, sizeof(uint64_t)); | ||
REQUIRE(ret == -1); | ||
CHECK(errno == EFAULT); | ||
} | ||
|
||
SECTION("valid configuration") { | ||
struct pollfd valid_fds { | ||
.fd = STDOUT_FILENO, | ||
.events = 0, | ||
.revents = 0, | ||
}; | ||
|
||
timespec64 valid_ts{}; | ||
sigset_t valid_sigset{}; | ||
sigemptyset(&valid_sigset); | ||
auto ret = ::syscall(SYS_ppoll_time64, &valid_fds, 1, &valid_ts, &valid_sigset, sizeof(uint64_t)); | ||
REQUIRE(ret == 0); | ||
} | ||
|
||
SECTION("invalid timespec write-back") { | ||
struct pollfd valid_fds { | ||
.fd = STDOUT_FILENO, | ||
.events = 0, | ||
.revents = 0, | ||
}; | ||
|
||
// Kernel will read timespec, but it then can't write the result back. | ||
sigset_t valid_sigset{}; | ||
sigemptyset(&valid_sigset); | ||
auto ret = ::syscall(SYS_ppoll_time64, &valid_fds, 1, &readonly_ts, &valid_sigset, sizeof(uint64_t)); | ||
REQUIRE(ret == 0); | ||
CHECK(readonly_ts.tv_sec == 1); | ||
} | ||
} | ||
|