168 lines
6.8 KiB
C++
168 lines
6.8 KiB
C++
#include "SceTypes.hpp"
|
|
#include "nid/Sha1.hpp"
|
|
#include "prx/libc/include/general/VabiMacros.hpp"
|
|
#include <cerrno>
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <dlfcn.h>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <sys/mman.h>
|
|
#include <sys/resource.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/wait.h>
|
|
#include <unistd.h>
|
|
|
|
extern "C" {
|
|
int APS5_VABI shm_open_nid_postfix(const char*, int, int);
|
|
int APS5_VABI ftruncate_nid_postfix(int, std::int64_t);
|
|
std::int64_t APS5_VABI pwrite_nid_disambig1_nid_postfix(int, const void*, std::size_t, std::int64_t);
|
|
std::int64_t APS5_VABI fstat_nid_disambig1_nid_postfix(int, FileStat*);
|
|
int APS5_VABI fchmod_nid_postfix(int, int);
|
|
int APS5_VABI close_nid_postfix(int);
|
|
int* APS5_VABI __error_nid_postfix();
|
|
}
|
|
|
|
namespace {
|
|
using Unlink = int (*)(const char*);
|
|
constexpr std::int64_t Page = 0x4000;
|
|
constexpr int ReadWriteCreate = 0xa02;
|
|
|
|
struct Object {
|
|
std::string name = "/anyps5-concurrent-" + std::to_string(getpid());
|
|
std::string hostName = "/anyps5-shm-";
|
|
Object() {
|
|
constexpr char hex[] = "0123456789abcdef";
|
|
const auto digest = Nid::Sha1(std::vector<std::uint8_t>(name.begin(), name.end()));
|
|
for (const auto byte : digest) {
|
|
hostName += hex[byte >> 4];
|
|
hostName += hex[byte & 15];
|
|
}
|
|
::shm_unlink(hostName.c_str());
|
|
}
|
|
~Object() { ::shm_unlink(hostName.c_str()); }
|
|
};
|
|
|
|
bool Race(int descriptor) {
|
|
for (int iteration = 0; iteration < 500; ++iteration) {
|
|
const auto size = (iteration & 1) ? 0 : Page;
|
|
if (ftruncate_nid_postfix(descriptor, size) != 0) return false;
|
|
const auto written = pwrite_nid_disambig1_nid_postfix(descriptor, "x", 1, Page / 2);
|
|
if (written < 0) return false;
|
|
FileStat info{};
|
|
if (fstat_nid_disambig1_nid_postfix(descriptor, &info) != 0 ||
|
|
(info.st_size != 0 && info.st_size != Page)) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool NoDescriptorFallback(int descriptor) {
|
|
if (ftruncate_nid_postfix(descriptor, Page) != 0) return false;
|
|
const pid_t child = fork();
|
|
if (child < 0) return false;
|
|
if (child == 0) {
|
|
const struct rlimit limit{0, 0};
|
|
if (setrlimit(RLIMIT_NOFILE, &limit) != 0) _exit(2);
|
|
std::int64_t written = -2;
|
|
try {
|
|
written = pwrite_nid_disambig1_nid_postfix(descriptor, "x", 1, Page);
|
|
} catch (const std::exception& exception) {
|
|
std::fprintf(stderr, "low-RLIMIT child: fd=%d pwrite threw=%s guest_errno=%d native_errno=%d\n",
|
|
descriptor, exception.what(), *__error_nid_postfix(), errno);
|
|
_exit(3);
|
|
} catch (...) {
|
|
std::fprintf(stderr, "low-RLIMIT child: fd=%d pwrite threw unknown exception guest_errno=%d native_errno=%d\n",
|
|
descriptor, *__error_nid_postfix(), errno);
|
|
_exit(3);
|
|
}
|
|
const int guestError = *__error_nid_postfix();
|
|
const int nativeError = errno;
|
|
struct stat info{};
|
|
if (::fstat(descriptor, &info) != 0) {
|
|
std::fprintf(stderr, "low-RLIMIT child: fd=%d pwrite=%lld guest_errno=%d native_errno=%d fstat_errno=%d\n",
|
|
descriptor, static_cast<long long>(written), guestError, nativeError, errno);
|
|
_exit(4);
|
|
}
|
|
if (written != 0 && !(written == -1 && guestError == 24)) {
|
|
std::fprintf(stderr, "low-RLIMIT child: fd=%d pwrite=%lld guest_errno=%d native_errno=%d st_size=%lld\n",
|
|
descriptor, static_cast<long long>(written), guestError, nativeError, static_cast<long long>(info.st_size));
|
|
_exit(5);
|
|
}
|
|
if (info.st_size != Page) {
|
|
std::fprintf(stderr, "low-RLIMIT child: fd=%d pwrite=%lld guest_errno=%d native_errno=%d st_size=%lld expected=%lld\n",
|
|
descriptor, static_cast<long long>(written), guestError, nativeError,
|
|
static_cast<long long>(info.st_size), static_cast<long long>(Page));
|
|
_exit(6);
|
|
}
|
|
_exit(0);
|
|
}
|
|
int status = 0;
|
|
return waitpid(child, &status, 0) == child && WIFEXITED(status) && WEXITSTATUS(status) == 0;
|
|
}
|
|
|
|
bool ChildrenSucceeded(const pid_t* children, int count) {
|
|
bool okay = true;
|
|
for (int index = 0; index < count; ++index) {
|
|
int status = 0;
|
|
const auto result = waitpid(children[index], &status, 0);
|
|
okay = result == children[index] && WIFEXITED(status) && WEXITSTATUS(status) == 0 && okay;
|
|
}
|
|
return okay;
|
|
}
|
|
|
|
void Run() {
|
|
Object object;
|
|
const auto unlinkObject = reinterpret_cast<Unlink>(dlsym(RTLD_DEFAULT, "shm_unlink_nid_postfix"));
|
|
if (unlinkObject == nullptr) throw std::runtime_error("guest shm_unlink export is missing");
|
|
const int descriptor = shm_open_nid_postfix(object.name.c_str(), ReadWriteCreate, 0600);
|
|
if (descriptor < 0) throw std::runtime_error("shared object creation failed");
|
|
|
|
pid_t children[4]{};
|
|
int childCount = 0;
|
|
bool okay = true;
|
|
for (int index = 0; index < 4; ++index) {
|
|
const pid_t child = fork();
|
|
if (child < 0) {
|
|
okay = false;
|
|
break;
|
|
}
|
|
if (child == 0) {
|
|
const int own = (index & 1) ? shm_open_nid_postfix(object.name.c_str(), 2, 0600) : descriptor;
|
|
const bool childOkay = own >= 0 && Race(own);
|
|
if ((index & 1) && own >= 0) close_nid_postfix(own);
|
|
_exit(childOkay ? 0 : 1);
|
|
}
|
|
children[childCount++] = child;
|
|
}
|
|
okay = Race(descriptor) && okay;
|
|
okay = ChildrenSucceeded(children, childCount) && okay;
|
|
if (!okay) throw std::runtime_error("interleaved resize/write produced an invalid shared-object size");
|
|
if (!NoDescriptorFallback(descriptor))
|
|
throw std::runtime_error("EOF write with no spare descriptors grew the shared object or returned an unexpected error");
|
|
|
|
if (fchmod_nid_postfix(descriptor, 0400) != 0)
|
|
throw std::runtime_error("changing shared-object permissions to read-only failed");
|
|
*__error_nid_postfix() = 0;
|
|
if (unlinkObject(object.name.c_str()) != -1 || *__error_nid_postfix() != 13)
|
|
throw std::runtime_error("unlink of a read-only shared object did not return EACCES");
|
|
if (fchmod_nid_postfix(descriptor, 0600) != 0)
|
|
throw std::runtime_error("restoring shared-object permissions failed");
|
|
if (unlinkObject(object.name.c_str()) != 0)
|
|
throw std::runtime_error("unlink after restoring write permission failed");
|
|
if (close_nid_postfix(descriptor) != 0)
|
|
throw std::runtime_error("shared-object close failed");
|
|
std::puts("cross-process truncate/write, descriptor sharing and unlink permissions passed");
|
|
}
|
|
}
|
|
|
|
int main() {
|
|
try {
|
|
Run();
|
|
return 0;
|
|
} catch (const std::exception& error) {
|
|
std::fprintf(stderr, "%s\n", error.what());
|
|
return 1;
|
|
}
|
|
}
|