Merge pull request #5191 from p3n1cillin/claude/carry-chroot
feat(libkernel_sys): add chroot that keeps the guest root
This commit is contained in:
commit
b478fbbb1c
|
|
@ -369,6 +369,23 @@ int APS5_VABI fchdir_nid_postfix(int fd) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI chroot_nid_postfix(const char* path) {
|
||||
if (path == nullptr) return static_cast<int>(Fail(GuestEfault));
|
||||
if (*path == '\0') return static_cast<int>(Fail(GuestEnoent));
|
||||
if (std::strlen(path) >= GuestPathMax || HasLongComponent(path)) return static_cast<int>(Fail(GuestEnametoolong));
|
||||
std::filesystem::path native;
|
||||
if (const int error = ResolvePathError_nid_no_patch(path, native)) return static_cast<int>(Fail(error));
|
||||
std::error_code error;
|
||||
const auto status = std::filesystem::status(native, error);
|
||||
if (status.type() == std::filesystem::file_type::not_found) return static_cast<int>(Fail(HasNonDirectoryPrefix(native) ? GuestEnotdir : GuestEnoent));
|
||||
if (error) return static_cast<int>(Fail(GuestError(error)));
|
||||
if (!std::filesystem::is_directory(status)) return static_cast<int>(Fail(GuestEnotdir));
|
||||
const bool root = std::filesystem::equivalent(native, ResolvePath_nid_no_patch("/"), error);
|
||||
if (error) return static_cast<int>(Fail(GuestError(error)));
|
||||
if (root) return 0;
|
||||
throw std::runtime_error(std::string("chroot: changing the root directory to ") + path + " is not supported, the guest path resolver keeps a fixed root");
|
||||
}
|
||||
|
||||
int APS5_VABI symlink_nid_postfix(const char* target, const char* created) {
|
||||
if (target == nullptr || created == nullptr) return static_cast<int>(Fail(GuestEfault));
|
||||
if (*target == '\0' || *created == '\0') return static_cast<int>(Fail(GuestEnoent));
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ std::int64_t APS5_VABI readlink_nid_postfix(const char*, char*, std::size_t);
|
|||
std::int64_t APS5_VABI pathconf_nid_postfix(const char*, int);
|
||||
int APS5_VABI fchdir_nid_postfix(int);
|
||||
int APS5_VABI chdir_nid_postfix(const char*);
|
||||
int APS5_VABI chroot_nid_postfix(const char*);
|
||||
char* APS5_VABI getcwd_nid_postfix(char*, std::size_t);
|
||||
int APS5_VABI sceKernelOpen(const char*, int, std::uint16_t);
|
||||
int APS5_VABI sceKernelClose(int);
|
||||
|
|
@ -66,6 +67,15 @@ bool PathconfThrows(const char* path, int name) {
|
|||
return false;
|
||||
}
|
||||
|
||||
bool ChrootThrows(const std::string& path) {
|
||||
try {
|
||||
chroot_nid_postfix(path.c_str());
|
||||
} catch (const std::runtime_error& error) {
|
||||
return std::strstr(error.what(), path.c_str()) != nullptr;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void CheckPathconf(const std::filesystem::path& root, const std::string& file) {
|
||||
const auto directory = root.generic_string();
|
||||
for (const auto& path : {file, directory}) {
|
||||
|
|
@ -215,6 +225,39 @@ int main() {
|
|||
Require(sceKernelClose(directory) == 0);
|
||||
Require(fchdir_nid_postfix(directory) == -1 && Error() == 9);
|
||||
Require(chdir_nid_postfix("/") == 0);
|
||||
Require(chroot_nid_postfix(nullptr) == -1 && Error() == 14);
|
||||
Require(chroot_nid_postfix("") == -1 && Error() == 2);
|
||||
Require(chroot_nid_postfix((root / "missing").string().c_str()) == -1 && Error() == 2);
|
||||
Require(chroot_nid_postfix(file.c_str()) == -1 && Error() == 20);
|
||||
Require(chroot_nid_postfix((root / "file.txt" / "child").string().c_str()) == -1 && Error() == 20);
|
||||
Require(chroot_nid_postfix((file + "/").c_str()) == -1 && Error() == 20);
|
||||
Require(chroot_nid_postfix((root / "file.txt" / "..").string().c_str()) == -1 && Error() == 20);
|
||||
Require(chroot_nid_postfix((root / std::string(256, 'n')).string().c_str()) == -1 && Error() == 63);
|
||||
Require(chroot_nid_postfix(("/" + std::string(1024, 'p')).c_str()) == -1 && Error() == 63);
|
||||
Require(ChrootThrows(root.string()));
|
||||
Require(ChrootThrows("/" + root.filename().string() + "/."));
|
||||
const auto guestRoot = "/" + root.filename().string();
|
||||
const auto chrootFile = guestRoot + "/file.txt";
|
||||
const int top = sceKernelOpen("/", SCE_KERNEL_O_RDONLY | SCE_KERNEL_O_DIRECTORY, 0);
|
||||
Require(top >= 0);
|
||||
Require(chdir_nid_postfix(guestRoot.c_str()) == 0);
|
||||
Require(fchdir_nid_postfix(top) == 0 && chroot_nid_postfix(".") == 0);
|
||||
Require(getcwd_nid_postfix(directoryPath, sizeof(directoryPath)) == directoryPath && std::strcmp(directoryPath, "/") == 0);
|
||||
Require(sceKernelClose(top) == 0);
|
||||
Require(chroot_nid_postfix("/") == 0 && chroot_nid_postfix("/..") == 0 && chroot_nid_postfix("//./") == 0);
|
||||
Require(chdir_nid_postfix(guestRoot.c_str()) == 0);
|
||||
Require(chroot_nid_postfix("..") == 0 && chroot_nid_postfix("../..") == 0);
|
||||
Require(getcwd_nid_postfix(directoryPath, sizeof(directoryPath)) == directoryPath && guestRoot == directoryPath);
|
||||
Require(ChrootThrows("."));
|
||||
for (const auto& path : {std::string("file.txt"), chrootFile, "/../../.." + chrootFile, "../.." + chrootFile}) {
|
||||
const int opened = sceKernelOpen(path.c_str(), SCE_KERNEL_O_RDONLY, 0);
|
||||
char byte = 0;
|
||||
Require(opened >= 0 && sceKernelRead(opened, &byte, 1) == 1 && byte == 'x');
|
||||
Require(sceKernelClose(opened) == 0);
|
||||
}
|
||||
Require(chdir_nid_postfix("/") == 0);
|
||||
Require(getcwd_nid_postfix(directoryPath, sizeof(directoryPath)) == directoryPath && std::strcmp(directoryPath, "/") == 0);
|
||||
Require(readlink_nid_postfix(file.c_str(), buffer, sizeof(buffer)) == -1 && Error() == 22);
|
||||
const auto symbolic = (root / "symbolic").string();
|
||||
const int created = symlink_nid_postfix("original.bin", symbolic.c_str());
|
||||
if (created == 0) {
|
||||
|
|
|
|||
|
|
@ -11,3 +11,4 @@
|
|||
- [unlinkat](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) passes `AT_FDCWD` (-100) and absolute paths to `rmdir` (with `AT_REMOVEDIR`, 0x800) or `unlink`. Any other relative path is joined to the descriptor's host directory, found the same way as for `fchdir`, so guest path aliases and case-insensitive name matching do not apply to it, and a `..` component is not kept inside the guest root. It returns `EBADF` for a descriptor that is not open and `ENOTDIR` for one that is not a directory. Removing a directory without `AT_REMOVEDIR` fails with `EPERM`, removing `.` or `..` with `AT_REMOVEDIR` fails with `EINVAL`, and flags other than `AT_REMOVEDIR` fail with `EINVAL`. Console behaviour is unverified
|
||||
- [renameat](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) passes `AT_FDCWD` (-100) and absolute paths to `rename`. Any other relative path is joined to its descriptor's host directory, found the same way as for `fchdir`, so guest path aliases and case-insensitive name matching do not apply to relative descriptors, and a `..` component is not kept inside the guest root. It returns `EBADF` for a descriptor that is not open and `ENOTDIR` for one that is not a directory. Console behaviour is unverified
|
||||
- [fork](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) throws: creating a child process is not supported. PPSA19111 imports it and fails to load without it. Its engine calls `fork` in `Posix_Daemonize`, only with `-daemonize`, and in `Platform_ShellExecute` before `execvp("xdg-open")`, which the main menu's Previews button reaches, so pressing Previews ends in this throw. [setsid](../../../core/libs/prx/libkernel_sys/Export.cpp) fails with `EPERM`, as FreeBSD's does for a process group leader, since the guest is taken to lead its own process group (see `kill`). [umask](../../../core/libs/prx/libkernel_sys/Export.cpp) keeps the guest's mask (initially 022, masked to 07777) and returns the previous one, Linux named shared-memory creation applies it, but ordinary files are still created with the host process's mask
|
||||
- [chroot](../../../core/libs/prx/libkernel_sys/Export.cpp) (libkernel_sys) follows FreeBSD 11.4's [`sys_chroot`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/vfs_syscalls.c#L776-L807) and [`pwd_chroot`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_descrip.c#L3059-L3085) for a root caller: `EFAULT` for a null path, `ENOENT` for an empty or missing one, `ENAMETOOLONG` for a path of 1024 bytes or more or a component over 255, and `ENOTDIR` for a path that is not a directory or passes through a file. A directory that is the guest root (`/`, `/..`, or `.` after `fchdir` to a descriptor of `/`) succeeds and changes nothing, as the root stays the same directory; the guest is taken never to have changed its root, so open directory descriptors do not make it fail with `EPERM` (`kern.chroot_allow_open_directories` 1). Any other directory throws, naming the path: the guest path resolver has a fixed root and mount aliases (`/app0`, `/savedata0`) that do not move with it. On the console a title starts in a sandbox root, where `pwd_chroot` refuses with `EPERM` while a directory is open; whether that holds for titles, and whether a title holds `PRIV_VFS_CHROOT`, are unverified. Porpoise (PPSA99764) weak-imports it and calls it only as `fchdir` to its saved descriptor of `/` then `chroot(".")`, to put its sandbox root back after a jailbreak daemon replaced it ([`put_root_back`](https://github.com/elripalda/Porpoise-Dolphin-Emulator-for-PS5/blob/2ed1ab5a06995aa198f7bccd4da0f90c3fc29d39/src/porpoise_jailbreak.cpp#L303-L331))
|
||||
|
|
|
|||
Loading…
Reference in New Issue