Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions core/libs/prx/libc/include/General.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ extern "C" int RenameGuestPath_nid_no_patch(const char* from, const char* to);
// directory instead of the run directory; the prefix matches whole path components only.
extern "C" void AddPathAlias_nid_no_patch(const char* guestPrefix, const char* hostPath);
extern "C" void RemovePathAlias_nid_no_patch(const char* guestPrefix);
using PathProvider = std::filesystem::path (*)(const std::filesystem::path& root);
extern "C" void AddPathProvider_nid_no_patch(const char* guestPath, PathProvider provide);
extern "C" void RecordWrittenPath_nid_no_patch(const std::filesystem::path& path);
extern "C" std::vector<std::filesystem::path> WrittenPaths_nid_no_patch();
extern "C" void SyncWrittenPaths_nid_no_patch();
Expand Down
37 changes: 37 additions & 0 deletions core/libs/prx/libc/src/General.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ struct PathAliases {
std::mutex mutex;
std::vector<std::pair<std::string, std::string>> entries;
std::vector<std::string> blocked;
std::vector<std::pair<std::string, PathProvider>> providers;
};

PathAliases& Aliases() {
Expand Down Expand Up @@ -170,8 +171,27 @@ WorkingDirectory& Directories() {
static auto* state = new WorkingDirectory;
return *state;
}
std::optional<std::filesystem::path> ProvidedPath(const WorkingDirectory& state, const std::filesystem::path& guest) {
const auto relative = TrimSlashes(guest.relative_path().generic_string().c_str());
PathProvider provide = nullptr;
{
auto& aliases = Aliases();
std::lock_guard lock(aliases.mutex);
for (const auto& [path, provider] : aliases.providers) {
if (SameName(relative, path)) provide = provider;
}
}
if (provide == nullptr) return std::nullopt;
const auto supplied = ResolveHostPath(state.root, guest.relative_path());
std::error_code error;
if (std::filesystem::exists(supplied, error) || error) return supplied;
auto provided = provide(state.root);
if (provided.empty()) return std::nullopt;
return provided.make_preferred();
}
std::filesystem::path HostPath(WorkingDirectory& state, const std::filesystem::path& guest, bool module = false) {
if (auto aliased = ResolveAlias(guest.relative_path().generic_string(), module)) return *aliased;
if (auto provided = ProvidedPath(state, guest)) return *provided;
return ResolveHostPath(state.root, guest.relative_path(), module).make_preferred();
}
void RequireHostDirectory(WorkingDirectory& state, const std::filesystem::path& guest) {
Expand Down Expand Up @@ -355,6 +375,23 @@ extern "C" void AddPathAlias_nid_no_patch(const char* guestPrefix, const char* h
aliases.entries.emplace_back(prefix, hostPath);
}

extern "C" void AddPathProvider_nid_no_patch(const char* guestPath, PathProvider provide) {
if (guestPath == nullptr || provide == nullptr) {
APS5_INVALID_ARG_EX;
}
const auto path = TrimSlashes(guestPath);
if (path.empty()) { APS5_INVALID_ARG_EX; }
auto& aliases = Aliases();
std::lock_guard lock(aliases.mutex);
for (auto& entry : aliases.providers) {
if (SameName(entry.first, path)) {
entry.second = provide;
return;
}
}
aliases.providers.emplace_back(path, provide);
}

extern "C" void RemovePathAlias_nid_no_patch(const char* guestPrefix) {
if (guestPrefix == nullptr) {
APS5_INVALID_ARG_EX;
Expand Down
23 changes: 22 additions & 1 deletion core/libs/prx/libkernel/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ add_library(libkernel SHARED EXCLUDE_FROM_ALL
${CMAKE_CURRENT_SOURCE_DIR}/File/src/SharedMemory.cpp
${CMAKE_CURRENT_SOURCE_DIR}/File/src/RandomDevice.cpp
${CMAKE_CURRENT_SOURCE_DIR}/File/src/HostStreams.cpp
${CMAKE_CURRENT_SOURCE_DIR}/File/src/SandboxCertificates.cpp
${CMAKE_CURRENT_SOURCE_DIR}/File/src/Stdio.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../libc/src/Rename.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Semaphore/src/Semaphore.cpp
Expand Down Expand Up @@ -82,7 +83,8 @@ target_include_directories(libkernel PRIVATE ${LIBS_INCLUDE_DIR}/nid/include)
target_link_libraries(libkernel PRIVATE ${CMAKE_DL_LIBS})
target_link_libraries(libkernel PRIVATE libc)
if(WIN32)
target_link_libraries(libkernel PRIVATE ws2_32 shell32 advapi32 wbemuuid ole32 oleaut32)
target_sources(libkernel PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/File/src/TrustedRoots.cpp)
target_link_libraries(libkernel PRIVATE ws2_32 shell32 advapi32 crypt32 wbemuuid ole32 oleaut32)
target_link_libraries(libkernel PRIVATE iphlpapi)
target_compile_definitions(libkernel PRIVATE _WIN32_WINNT=0x0602)
endif()
Expand Down Expand Up @@ -298,6 +300,25 @@ if(BUILD_TESTING)
set_tests_properties(guest_kernel_sync PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
endif()
add_test_executable(guest_sandbox_certificates_tests ${LIBS_INCLUDE_DIR}/tests/GuestSandboxCertificates.cpp)
target_include_directories(guest_sandbox_certificates_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_sandbox_certificates_tests PRIVATE libkernel libc)
configure_windows_unwind(guest_sandbox_certificates_tests)
set(sandboxCertificatesRun ${CMAKE_CURRENT_BINARY_DIR}/guest_sandbox_certificates)
file(MAKE_DIRECTORY ${sandboxCertificatesRun})
add_test(NAME guest_sandbox_certificates COMMAND guest_sandbox_certificates_tests WORKING_DIRECTORY ${sandboxCertificatesRun})
set_tests_properties(guest_sandbox_certificates PROPERTIES TIMEOUT 20)
if(WIN32)
add_test_executable(guest_sandbox_certificate_trust_tests ${LIBS_INCLUDE_DIR}/tests/GuestSandboxCertificateTrust.cpp)
target_include_directories(guest_sandbox_certificate_trust_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_sandbox_certificate_trust_tests PRIVATE libkernel libc crypt32)
configure_windows_unwind(guest_sandbox_certificate_trust_tests)
add_test(NAME guest_sandbox_certificate_trust COMMAND guest_sandbox_certificate_trust_tests)
set_tests_properties(guest_sandbox_certificate_trust PROPERTIES TIMEOUT 10 ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
set_tests_properties(guest_sandbox_certificates PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
endif()
add_test_executable(guest_kernel_rename_tests ${LIBS_INCLUDE_DIR}/tests/GuestKernelRename.cpp)
target_include_directories(guest_kernel_rename_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_kernel_rename_tests PRIVATE libkernel libc)
Expand Down
17 changes: 17 additions & 0 deletions core/libs/prx/libkernel/File/include/TrustedRoots.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#ifndef CORE_LIBS_PRX_LIBKERNEL_FILE_TRUSTEDROOTS_HPP
#define CORE_LIBS_PRX_LIBKERNEL_FILE_TRUSTEDROOTS_HPP

#ifndef NOMINMAX
#define NOMINMAX
#endif
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#include <wincrypt.h>
#include <string>

extern "C" std::string ExportHostRoots_nid_no_patch(HCERTSTORE roots, HCERTSTORE disallowed);
extern "C" std::string HostTrustedTlsRoots_nid_no_patch();

#endif
88 changes: 88 additions & 0 deletions core/libs/prx/libkernel/File/src/SandboxCertificates.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#include "prx/libc/include/General.hpp"

#include <array>
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <iterator>
#include <optional>
#include <stdexcept>
#include <string>
#include <system_error>

#ifdef _WIN32
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#include "prx/libkernel/File/include/TrustedRoots.hpp"
#else
#include <unistd.h>
#endif

extern "C" const char* APS5_VABI sceKernelGetFsSandboxRandomWord();

namespace {
constexpr char CertificateBegin[] = "-----BEGIN CERTIFICATE-----";

std::optional<std::string> ReadFile(const std::filesystem::path& path) {
std::ifstream file(path, std::ios::binary);
if (!file) return std::nullopt;
std::string text((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
if (file.bad()) throw std::runtime_error("Reading " + path.string() + " failed");
return text;
}

#ifdef _WIN32
std::string HostCertificates() {
return HostTrustedTlsRoots_nid_no_patch();
}

unsigned long ProcessId() { return GetCurrentProcessId(); }
#else
std::string HostCertificates() {
std::array<const char*, 5> candidates{std::getenv("SSL_CERT_FILE"), "/etc/ssl/certs/ca-certificates.crt", "/etc/pki/tls/certs/ca-bundle.crt",
"/etc/ssl/ca-bundle.pem", "/etc/ssl/cert.pem"};
for (const char* candidate : candidates) {
std::error_code error;
if (candidate == nullptr || *candidate == '\0' || !std::filesystem::is_regular_file(candidate, error)) continue;
if (auto text = ReadFile(candidate)) return *text;
}
return {};
}

unsigned long ProcessId() { return static_cast<unsigned long>(getpid()); }
#endif

std::filesystem::path WriteCertificateList(const std::filesystem::path& root) {
const auto bundle = HostCertificates();
if (bundle.find(CertificateBegin) == std::string::npos) return {};
const auto directory = root / "cert_cache";
std::filesystem::create_directories(directory);
const auto target = directory / "CA_LIST.cer";
if (ReadFile(target) == bundle) return target;
auto temporary = target;
temporary += "." + std::to_string(ProcessId()) + ".tmp";
{
std::ofstream file(temporary, std::ios::binary | std::ios::trunc);
file.write(bundle.data(), static_cast<std::streamsize>(bundle.size()));
if (!file.flush()) throw std::runtime_error("Writing " + temporary.string() + " failed");
}
using std::filesystem::perms;
std::filesystem::permissions(temporary, perms::owner_read | perms::group_read | perms::others_read);
std::error_code error;
if (std::filesystem::exists(target, error)) std::filesystem::permissions(target, perms::owner_write, std::filesystem::perm_options::add);
std::filesystem::rename(temporary, target);
return target;
}

std::filesystem::path CertificateList(const std::filesystem::path& root) {
static const std::filesystem::path list = WriteCertificateList(root);
return list;
}

const bool g_certificateListProvided = [] {
AddPathProvider_nid_no_patch((std::string("/") + sceKernelGetFsSandboxRandomWord() + "/common/cert/CA_LIST.cer").c_str(), &CertificateList);
return true;
}();
}
78 changes: 78 additions & 0 deletions core/libs/prx/libkernel/File/src/TrustedRoots.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#include "prx/libkernel/File/include/TrustedRoots.hpp"

#include <cstring>
#include <memory>
#include <stdexcept>
#include <system_error>
#include <vector>

namespace {

void RequireCrypto(bool success, const char* operation) {
if (!success) throw std::system_error(static_cast<int>(GetLastError()), std::system_category(), operation);
}

bool AllowsServerAuthentication(PCCERT_CONTEXT certificate) {
DWORD size = 0;
RequireCrypto(CertGetEnhancedKeyUsage(certificate, 0, nullptr, &size), "Reading root certificate usage size");
std::vector<unsigned char> storage(size);
auto* usage = reinterpret_cast<PCERT_ENHKEY_USAGE>(storage.data());
SetLastError(ERROR_SUCCESS);
RequireCrypto(CertGetEnhancedKeyUsage(certificate, 0, usage, &size), "Reading root certificate usage");
if (usage->cUsageIdentifier == 0) return GetLastError() == static_cast<DWORD>(CRYPT_E_NOT_FOUND);
for (DWORD index = 0; index < usage->cUsageIdentifier; ++index) {
const auto* identifier = usage->rgpszUsageIdentifier[index];
if (std::strcmp(identifier, szOID_PKIX_KP_SERVER_AUTH) == 0 || std::strcmp(identifier, szOID_ANY_ENHANCED_KEY_USAGE) == 0)
return true;
}
return false;
}

std::string Pem(PCCERT_CONTEXT certificate) {
DWORD size = 0;
RequireCrypto(CryptBinaryToStringA(certificate->pbCertEncoded, certificate->cbCertEncoded,
CRYPT_STRING_BASE64HEADER, nullptr, &size), "Measuring a PEM root certificate");
std::string pem(size, '\0');
RequireCrypto(CryptBinaryToStringA(certificate->pbCertEncoded, certificate->cbCertEncoded,
CRYPT_STRING_BASE64HEADER, pem.data(), &size), "Encoding a PEM root certificate");
pem.resize(size);
return pem;
}

}

extern "C" std::string ExportHostRoots_nid_no_patch(HCERTSTORE roots, HCERTSTORE disallowed) {
if (!roots || !disallowed) throw std::invalid_argument("ExportHostRoots: null certificate store");
std::string bundle;
using Certificate = std::unique_ptr<const CERT_CONTEXT, decltype(&CertFreeCertificateContext)>;
Certificate certificate(nullptr, &CertFreeCertificateContext);
for (;;) {
auto* next = CertEnumCertificatesInStore(roots, certificate.release());
if (!next) {
const auto error = GetLastError();
if (error != static_cast<DWORD>(CRYPT_E_NOT_FOUND))
throw std::system_error(static_cast<int>(error), std::system_category(), "Enumerating trusted roots");
break;
}
certificate.reset(next);
Certificate rejected(CertFindCertificateInStore(disallowed, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
0, CERT_FIND_EXISTING, certificate.get(), nullptr), &CertFreeCertificateContext);
if (rejected) continue;
const auto error = GetLastError();
if (error != static_cast<DWORD>(CRYPT_E_NOT_FOUND))
throw std::system_error(static_cast<int>(error), std::system_category(), "Checking distrusted roots");
if (AllowsServerAuthentication(certificate.get())) bundle += Pem(certificate.get());
}
return bundle;
}

extern "C" std::string HostTrustedTlsRoots_nid_no_patch() {
const auto closeStore = [](void* store) { CertCloseStore(store, 0); };
using Store = std::unique_ptr<void, decltype(closeStore)>;
const DWORD flags = CERT_SYSTEM_STORE_CURRENT_USER | CERT_STORE_READONLY_FLAG | CERT_STORE_OPEN_EXISTING_FLAG;
Store roots(CertOpenStore(CERT_STORE_PROV_SYSTEM_W, 0, 0, flags, L"ROOT"), closeStore);
RequireCrypto(roots != nullptr, "Opening the Windows trusted root store");
Store disallowed(CertOpenStore(CERT_STORE_PROV_SYSTEM_W, 0, 0, flags, L"Disallowed"), closeStore);
RequireCrypto(disallowed != nullptr, "Opening the Windows disallowed certificate store");
return ExportHostRoots_nid_no_patch(roots.get(), disallowed.get());
}
63 changes: 63 additions & 0 deletions core/libs/tests/GuestSandboxCertificateTrust.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#include "prx/libkernel/File/include/TrustedRoots.hpp"

#include <cstdio>
#include <cstdlib>
#include <vector>

static void Check(bool value, int line) {
if (!value) {
std::fprintf(stderr, "Certificate trust check failed at line %d\n", line);
std::abort();
}
}
#define Require(value) Check((value), __LINE__)

static constexpr char Certificate[] = R"(-----BEGIN CERTIFICATE-----
MIIBijCCATGgAwIBAgIUFMQYJ1in3pTJaCqjg6JVgSBNQgkwCgYIKoZIzj0EAwIw
GzEZMBcGA1UEAwwQQW55UFM1LVRlc3QtUm9vdDAeFw0yNjEwMTMwOTE2NTRaFw0z
NjEwMTAwOTE2NTRaMBsxGTAXBgNVBAMMEEFueVBTNS1UZXN0LVJvb3QwWTATBgcq
hkjOPQIBBggqhkjOPQMBBwNCAAQiHlYF9b/zaT5lFC26MOBndhdySsdLsuthNut0
fhQkEHlybL5nUrvOPZrkV1e/vEZ2ykmq7xMkSYeIHnNy4wxMo1MwUTAdBgNVHQ4E
FgQUJKW6nreyN5yBQn0axEJkZnkfZB4wHwYDVR0jBBgwFoAUJKW6nreyN5yBQn0a
xEJkZnkfZB4wDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNHADBEAiBfTER0
UmcQ0u8NzHTODNgVOHZHQFrYjcOyVjzZlSKomQIgDhztVM5obBElAnm22JgWEPk+
Bk30RAcx5xk3rjar3qs=
-----END CERTIFICATE-----
)";

static void CheckWindowsTrust() {
DWORD size = 0;
Require(CryptStringToBinaryA(Certificate, sizeof(Certificate) - 1, CRYPT_STRING_BASE64HEADER, nullptr, &size, nullptr, nullptr));
std::vector<unsigned char> der(size);
Require(CryptStringToBinaryA(Certificate, sizeof(Certificate) - 1, CRYPT_STRING_BASE64HEADER, der.data(), &size, nullptr, nullptr));
const auto roots = CertOpenStore(CERT_STORE_PROV_MEMORY, 0, 0, 0, nullptr);
const auto disallowed = CertOpenStore(CERT_STORE_PROV_MEMORY, 0, 0, 0, nullptr);
Require(roots && disallowed);
PCCERT_CONTEXT certificate = nullptr;
Require(CertAddEncodedCertificateToStore(roots, X509_ASN_ENCODING, der.data(), size, CERT_STORE_ADD_NEW, &certificate));
const auto unrestricted = ExportHostRoots_nid_no_patch(roots, disallowed);
Require(!unrestricted.empty() && unrestricted.find('\0') == std::string::npos);
DWORD decodedSize = size;
std::vector<unsigned char> decoded(decodedSize);
Require(CryptStringToBinaryA(unrestricted.c_str(), static_cast<DWORD>(unrestricted.size()), CRYPT_STRING_BASE64HEADER,
decoded.data(), &decodedSize, nullptr, nullptr));
Require(decoded == der);
CERT_ENHKEY_USAGE usage{};
Require(CertSetEnhancedKeyUsage(certificate, &usage));
Require(ExportHostRoots_nid_no_patch(roots, disallowed).empty());
char* email = const_cast<char*>(szOID_PKIX_KP_EMAIL_PROTECTION);
usage = {1, &email};
Require(CertSetEnhancedKeyUsage(certificate, &usage));
Require(ExportHostRoots_nid_no_patch(roots, disallowed).empty());
char* server = const_cast<char*>(szOID_PKIX_KP_SERVER_AUTH);
usage = {1, &server};
Require(CertSetEnhancedKeyUsage(certificate, &usage));
Require(ExportHostRoots_nid_no_patch(roots, disallowed) == unrestricted);
Require(CertAddCertificateContextToStore(disallowed, certificate, CERT_STORE_ADD_NEW, nullptr));
Require(ExportHostRoots_nid_no_patch(roots, disallowed).empty());
Require(CertFreeCertificateContext(certificate));
Require(CertCloseStore(disallowed, CERT_CLOSE_STORE_CHECK_FLAG));
Require(CertCloseStore(roots, CERT_CLOSE_STORE_CHECK_FLAG));
}

int main() { CheckWindowsTrust(); }
Loading
Loading