From a107d1e3330d62912438e580dd2c42ab55c8f89e Mon Sep 17 00:00:00 2001 From: nmzik Date: Mon, 28 Sep 2026 18:39:03 +0400 Subject: [PATCH] filesystem: keep unmapped guest paths out of the host namespace --- src/common/platform/sysLinuxFileIO.cpp | 2 +- src/kernel/fileSystem.cpp | 12 +++++-- src/kernel/fileSystem.h | 1 + tests/KernelFileSystemTests.cpp | 47 ++++++++++++++++++++++++-- 4 files changed, 56 insertions(+), 6 deletions(-) diff --git a/src/common/platform/sysLinuxFileIO.cpp b/src/common/platform/sysLinuxFileIO.cpp index 7605010e6..cb4b1f6ab 100644 --- a/src/common/platform/sysLinuxFileIO.cpp +++ b/src/common/platform/sysLinuxFileIO.cpp @@ -54,7 +54,7 @@ struct sys_file_t { #endif static std::filesystem::path get_internal_name(const std::filesystem::path& name) { - return name.is_absolute() ? name : (std::filesystem::path(".") / name); + return name.empty() || name.is_absolute() ? name : (std::filesystem::path(".") / name); } // Pass access-pattern hints to the host. diff --git a/src/kernel/fileSystem.cpp b/src/kernel/fileSystem.cpp index 196c4c8e6..999240e50 100644 --- a/src/kernel/fileSystem.cpp +++ b/src/kernel/fileSystem.cpp @@ -358,7 +358,8 @@ std::filesystem::path MountPoints::ResolvePath(const std::string& mounted_name) #endif } - return mounted_name; + // Unmounted guest paths must not fall through to the host filesystem. + return {}; } void Initialize() { @@ -461,6 +462,10 @@ int KYTY_SYSV_ABI KernelOpen(const char* path, int flags, uint16_t mode) { } file->real_name = g_mount_points->ResolvePath(file->name); + if (file->real_name.empty()) { + g_files->DeleteDescriptor(descriptor); + return KERNEL_ERROR_ENOENT; + } if (trunc && rw_mode == Common::File::Mode::Read) { g_files->DeleteDescriptor(descriptor); @@ -1255,7 +1260,7 @@ int KYTY_SYSV_ABI KernelRename(const char* from, const char* to) { auto real_from = g_mount_points->ResolvePath(from_path); auto real_to = g_mount_points->ResolvePath(to_path); - if (!Common::File::IsFileExisting(real_from)) { + if (real_to.empty() || !Common::File::IsFileExisting(real_from)) { return KERNEL_ERROR_ENOENT; } @@ -1335,6 +1340,9 @@ int KYTY_SYSV_ABI KernelMkdir(const char* path, uint16_t mode) { path, mode); auto real_name = g_mount_points->ResolvePath(std::string(path)); + if (real_name.empty()) { + return KERNEL_ERROR_ENOENT; + } if (Common::File::IsDirectoryExisting(real_name)) { return KERNEL_ERROR_EEXIST; diff --git a/src/kernel/fileSystem.h b/src/kernel/fileSystem.h index 6c0790101..5ac13fedf 100644 --- a/src/kernel/fileSystem.h +++ b/src/kernel/fileSystem.h @@ -54,6 +54,7 @@ struct Lifecycle { void Mount(const std::filesystem::path& folder, const std::string& point); void Umount(const std::string& folder_or_point); +// Returns an empty path when the guest path has no mounted filesystem. std::filesystem::path GetRealFilename(const std::string& mounted_file_name); int KYTY_SYSV_ABI KernelOpen(const char* path, int flags, uint16_t mode); diff --git a/tests/KernelFileSystemTests.cpp b/tests/KernelFileSystemTests.cpp index 4722a82d9..5b66afbd8 100644 --- a/tests/KernelFileSystemTests.cpp +++ b/tests/KernelFileSystemTests.cpp @@ -132,7 +132,7 @@ void CheckMountRoot(const std::filesystem::path &root) { FileSystem::Mount(root, "/app0"); Check(FileSystem::GetRealFilename("/app0/rpf.cache") == root / "rpf.cache", "resolve mount descendant"); - Check(FileSystem::GetRealFilename("/app01/rpf.cache") == "/app01/rpf.cache", + Check(FileSystem::GetRealFilename("/app01/rpf.cache").empty(), "mount prefix must end at a path component"); for (const char *path : {"/app0", "/app0/"}) { @@ -162,18 +162,58 @@ void CheckMountRoot(const std::filesystem::path &root) { } } FileSystem::Umount("/app0"); - Check(FileSystem::GetRealFilename("/app0/rpf.cache") == "/app0/rpf.cache", + Check(FileSystem::GetRealFilename("/app0/rpf.cache").empty(), "unmount by guest path"); for (const auto &folder : {root, root / ""}) { for (const auto &host : {root, root / ""}) { FileSystem::Mount(folder, "/app0"); FileSystem::Umount(Common::PathToGenericString(host)); - Check(FileSystem::GetRealFilename("/app0/rpf.cache") == "/app0/rpf.cache", + Check(FileSystem::GetRealFilename("/app0/rpf.cache").empty(), "unmount by host path with or without trailing separator"); } } } +void CheckUnmappedPaths(const std::filesystem::path &root) { + const auto host_file = root / "host-only.dat"; + const auto host_path = Common::PathToGenericString(host_file); + Common::File fixture; + Check(fixture.Create(host_file), "create unmapped host file"); + fixture.Close(); + + FileSystem::FileStat stat {}; + Check(FileSystem::GetRealFilename(host_path).empty() && + FileSystem::KernelOpen(host_path.c_str(), 0, 0) == + Libs::LibKernel::KERNEL_ERROR_ENOENT && + FileSystem::KernelStat(host_path.c_str(), &stat) == + Libs::LibKernel::KERNEL_ERROR_ENOENT && + FileSystem::KernelCheckReachability(host_path.c_str()) == + Libs::LibKernel::KERNEL_ERROR_ENOENT, + "existing host files are absent from the guest namespace"); + Check(FileSystem::KernelUnlink(host_path.c_str()) == + Libs::LibKernel::KERNEL_ERROR_ENOENT && + FileSystem::KernelRmdir(Common::PathToGenericString(root).c_str()) == + Libs::LibKernel::KERNEL_ERROR_ENOENT && + std::filesystem::exists(host_file), + "unmapped host files and directories cannot be removed"); + + const auto missing = Common::PathToGenericString(root / "unmapped-create"); + Check(FileSystem::KernelOpen(missing.c_str(), 0x601, 0777) == + Libs::LibKernel::KERNEL_ERROR_ENOENT && + FileSystem::KernelMkdir(missing.c_str(), 0777) == + Libs::LibKernel::KERNEL_ERROR_ENOENT && + !std::filesystem::exists(root / "unmapped-create"), + "creation requires a mounted guest destination"); + + FileSystem::Mount(root, "/app0"); + Check(FileSystem::KernelRename("/app0/host-only.dat", missing.c_str()) == + Libs::LibKernel::KERNEL_ERROR_ENOENT && + std::filesystem::exists(host_file) && + !std::filesystem::exists(root / "unmapped-create"), + "rename to an unmapped destination preserves the source"); + FileSystem::Umount("/app0"); +} + void CheckUnicodePaths(const std::filesystem::path &root) { constexpr std::string_view HostDirectory = "Test\xc3\xa9-\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e"; @@ -548,6 +588,7 @@ int main() { TempDirectory temporary; FileSystem::Initialize(); CheckMountRoot(temporary.Path()); + CheckUnmappedPaths(temporary.Path()); CheckUnicodePaths(temporary.Path()); CheckUnicodeLogPath(temporary.Path()); CheckDirectoryStream(temporary.Path());