Skip to content

Commit

Permalink
Linux: Implements support for EFAULT with ppoll's timeout
Browse files Browse the repository at this point in the history
Only need to handle the timeout structure, the rest is handled in the
kernel itself.
  • Loading branch information
Sonicadvance1 committed Jan 25, 2024
1 parent 18f1a93 commit aca9e7a
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 24 deletions.
15 changes: 13 additions & 2 deletions Source/Tools/LinuxEmulation/LinuxSyscalls/x32/FD.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,12 @@ namespace FEX::HLE::x32 {
struct timespec tp64{};
struct timespec *timed_ptr{};
if (timeout_ts) {
tp64 = *timeout_ts;
struct timespec32 timeout{};
if (FaultSafeMemcpy::CopyFromUser(&timeout, timeout_ts, sizeof(timeout)) == EFAULT) {
return -EFAULT;
}

tp64 = timeout;
timed_ptr = &tp64;
}

Expand All @@ -275,7 +280,13 @@ namespace FEX::HLE::x32 {
sigsetsize);

if (timeout_ts) {
*timeout_ts = tp64;
struct timespec32 timeout{};
timeout = tp64;

if (FaultSafeMemcpy::CopyToUser(timeout_ts, &timeout, sizeof(timeout)) == EFAULT) {
// Write to user memory failed, this can occur if the timeout is defined in read-only memory.
// This is okay to happen, kernel continues happily.
}
}

SYSCALL_ERRNO();
Expand Down
2 changes: 1 addition & 1 deletion Source/Tools/LinuxEmulation/LinuxSyscalls/x32/Types.h
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ timespec32 {
int32_t tv_sec;
int32_t tv_nsec;

timespec32() = delete;
timespec32() = default;

operator timespec() const {
timespec spec{};
Expand Down
44 changes: 23 additions & 21 deletions unittests/FEXLinuxTests/tests/syscalls/syscalls_efault.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,18 @@ TEST_CASE("poll") {
}

TEST_CASE("ppoll") {
// ppoll can return EFAULT for arguements 1, 3, 4
// ppoll can return EFAULT for arguments 1, 3, 4
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,
Expand All @@ -38,7 +38,7 @@ TEST_CASE("ppoll") {
CHECK(errno == EFAULT);
}

{
SECTION("invalid sigset") {
struct pollfd valid_fds {
.fd = STDOUT_FILENO,
.events = 0,
Expand All @@ -51,7 +51,7 @@ TEST_CASE("ppoll") {
CHECK(errno == EFAULT);
}

{
SECTION("valid configuration") {
struct pollfd valid_fds {
.fd = STDOUT_FILENO,
.events = 0,
Expand All @@ -65,7 +65,7 @@ TEST_CASE("ppoll") {
REQUIRE(ret == 0);
}

{
SECTION("invalid timespec write-back") {
struct pollfd valid_fds {
.fd = STDOUT_FILENO,
.events = 0,
Expand All @@ -85,26 +85,32 @@ TEST_CASE("ppoll") {
}
}

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
struct timespec64 {
uint64_t tv_sec, tv_nsec;
};

// ppoll can return EFAULT for arguements 1, 3, 4
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,
Expand All @@ -115,7 +121,7 @@ TEST_CASE("ppoll_64") {
CHECK(errno == EFAULT);
}

{
SECTION("invalid sigset") {
struct pollfd valid_fds {
.fd = STDOUT_FILENO,
.events = 0,
Expand All @@ -128,7 +134,7 @@ TEST_CASE("ppoll_64") {
CHECK(errno == EFAULT);
}

{
SECTION("valid configuration") {
struct pollfd valid_fds {
.fd = STDOUT_FILENO,
.events = 0,
Expand All @@ -142,22 +148,18 @@ TEST_CASE("ppoll_64") {
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_time64, &valid_fds, 1, invalid_timespec, &valid_sigset, sizeof(uint64_t));
auto ret = ::syscall(SYS_ppoll_time64, &valid_fds, 1, &readonly_ts, &valid_sigset, sizeof(uint64_t));
REQUIRE(ret == 0);
CHECK(invalid_timespec->tv_sec == 1);
CHECK(readonly_ts.tv_sec == 1);
}
}

0 comments on commit aca9e7a

Please sign in to comment.