feat(libkernel): implement sceKernelGetFsSandboxRandomWord

Returns the name of the directory under which a title's sandbox exposes
system files, which homebrew SDKs splice into paths such as
/<name>/common/cert/CA_LIST.cer and /<name>/common/lib/libc.sprx. The
name is fixed here; recorded in TechnicalDebt together with the fact
that nothing is mounted under it.

Registers guest_sandbox_random_word.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Jan Šlégl 2026-10-08 21:31:30 +02:00
parent b858fdc48e
commit 7785933807
4 changed files with 52 additions and 0 deletions

View File

@ -45,6 +45,7 @@ add_library(libkernel SHARED EXCLUDE_FROM_ALL
${CMAKE_CURRENT_SOURCE_DIR}/System/src/Sanitizer.cpp
${CMAKE_CURRENT_SOURCE_DIR}/System/src/Rtld.cpp
${CMAKE_CURRENT_SOURCE_DIR}/System/src/Process.cpp
${CMAKE_CURRENT_SOURCE_DIR}/System/src/Sandbox.cpp
${CMAKE_CURRENT_SOURCE_DIR}/System/src/SystemConfiguration.cpp
${CMAKE_CURRENT_SOURCE_DIR}/System/src/Signals.cpp
${CMAKE_CURRENT_SOURCE_DIR}/System/src/Notification.cpp
@ -471,4 +472,14 @@ if(BUILD_TESTING)
set_tests_properties(guest_file_vector PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
endif()
add_test_executable(guest_sandbox_random_word_tests ${LIBS_INCLUDE_DIR}/tests/GuestSandboxRandomWord.cpp)
target_include_directories(guest_sandbox_random_word_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_sandbox_random_word_tests PRIVATE libkernel libc)
configure_windows_unwind(guest_sandbox_random_word_tests)
add_test(NAME guest_sandbox_random_word COMMAND guest_sandbox_random_word_tests)
set_tests_properties(guest_sandbox_random_word PROPERTIES TIMEOUT 20)
if(WIN32)
set_tests_properties(guest_sandbox_random_word PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
endif()
endif()

View File

@ -0,0 +1,6 @@
#include "prx/libc/include/general/VabiMacros.hpp"
extern "C" const char* APS5_VABI sceKernelGetFsSandboxRandomWord() {
static constexpr char word[] = "gkbHtXc3Wq";
return word;
}

View File

@ -0,0 +1,34 @@
#include "prx/libc/include/general/VabiMacros.hpp"
#include <cctype>
#include <cstdlib>
#include <cstring>
#include <string>
#include <thread>
extern "C" {
const char* APS5_VABI sceKernelGetFsSandboxRandomWord();
int APS5_VABI open_nid_postfix(const char*, int, int);
int* APS5_VABI __error_nid_postfix();
}
static void Require(bool value) { if (!value) std::abort(); }
int main() {
const char* word = sceKernelGetFsSandboxRandomWord();
Require(word != nullptr);
const std::size_t length = std::strlen(word);
Require(length >= 1);
for (std::size_t index = 0; index < length; ++index)
Require(std::isalnum(static_cast<unsigned char>(word[index])));
Require(sceKernelGetFsSandboxRandomWord() == word);
const char* fromThread = nullptr;
std::thread([&] { fromThread = sceKernelGetFsSandboxRandomWord(); }).join();
Require(fromThread == word);
const std::string path = std::string("/") + word + "/common/lib/libc.sprx";
*__error_nid_postfix() = 0;
Require(open_nid_postfix(path.c_str(), 0, 0) == -1);
Require(*__error_nid_postfix() == 2);
return 0;
}

View File

@ -244,6 +244,7 @@ Throughout the project, every function at every stage either **does exactly what
- [sceNetSendmsg and sceNetRecvmsg](../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) copy the scatter/gather list through one host buffer and accept only the flags `sceNetSend`/`sceNetRecv` accept. Sending control data throws; a receive never returns control data (`msg_controllen` 0), since no socket option that queues it is supported
- [Additional content](../../core/libs/prx/libSceAppContent/Export.cpp) (libSceAppContent) is not installed or mounted: `sceAppContentAddcontMount` answers `NOT_FOUND` for every entitlement label and `sceAppContentAddcontUnmount` for every mount point, as no add-content mount is ever handed out; `sceAppContentGetAddcontInfo` answers `DRM_NO_ENTITLEMENT` for every label and `sceAppContentGetAddcontInfoList` reports 0 entries
- [sceKernelPread, sceKernelPwrite, sceKernelPreadv, sceKernelPwritev](../../core/libs/prx/libkernel/File/src/Stdio.cpp) (libkernel) on Windows transfer at the given offset with an overlapped `ReadFile`/`WriteFile` and restore the descriptor's file pointer afterwards, so a concurrent `read`/`write` on the same descriptor can see the moved pointer in between, unlike on the console. Handles that are not disk files return `ESPIPE`. `readv` on a pipe or console handle stops after the first iovec read, a short read the console is assumed to allow; each iovec is transferred separately, so a failure after the first transfer returns the bytes already done
- [sceKernelGetFsSandboxRandomWord](../../core/libs/prx/libkernel/System/src/Sandbox.cpp) (libkernel) returns a fixed directory name, `gkbHtXc3Wq`. On the console the name is the process's randomized path (syscall 602, `randomized_path`), and the sandbox exposes system files under it, such as `common/lib` and `common/cert`. Nothing is mounted under the name here, so `/gkbHtXc3Wq/common/...` resolves under the guest root like any other path and does not exist
- [sceKernelSync](../../core/libs/prx/libc/src/General.cpp) (libkernel) on Windows flushes only the host files and directories recorded through `RecordWrittenPath_nid_no_patch` (the libc, libkernel, save data and app content file functions) and their changed parent directories. A host write in a library that does not record its path is not flushed by `sceKernelSync`
- [sceKernelSync](../../core/libs/prx/libc/src/General.cpp) (libkernel) on Windows skips a recorded file it cannot open for writing (read-only, or held open by a handle that denies write sharing), since `FlushFileBuffers` needs a writable handle. Such a file's data may not reach the disk before a host crash; the guest reads the same data either way
- [sceKernelInstallExceptionHandler](../../core/libs/prx/libkernel/System/src/Exception.cpp) records the handler, but a host fault still ends the process: only signals raised with `sceKernelRaiseException` are delivered. Unity's PS5Util module installs a SIGUSR1 handler that reads `uc_mcontext.mc_rsp` (offset 0xf8 of the context) and raises SIGUSR1 on other threads to stop them for garbage collection