Merge pull request #3436 from MarcZX100/codex/rmdir-dot-einval-3116

fix(filesystem): reject final dot removal paths
This commit is contained in:
Adrià 2026-10-11 13:42:56 +02:00 committed by GitHub
commit ea31cff19c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 39 additions and 0 deletions

View File

@ -15,6 +15,7 @@ extern "C" bool StopAtPrefixLetter_nid_no_patch(const char* str, char** endptr,
extern "C" std::filesystem::path ResolvePath_nid_no_patch(const char* path);
extern "C" int SetWorkingDirectory_nid_no_patch(const char* resolved);
extern "C" bool FinalPathComponentIsDot_nid_no_patch(const char* path);
// Guest path aliases: a guest prefix such as "/savedata0" (a save-data mount point) resolves to a host
// 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);

View File

@ -75,6 +75,15 @@ extern "C" int APS5_VABI remove_nid_postfix(const char* path) {
if (!*path) { errno = 2; return -1; }
try {
const auto resolved = ResolvePath_nid_no_patch(path);
if (FinalPathComponentIsDot_nid_no_patch(path)) {
std::error_code statusError;
const auto status = std::filesystem::status(resolved, statusError);
if (statusError) { errno = FilesystemError(statusError); return -1; }
if (!std::filesystem::exists(status)) { errno = 2; return -1; }
if (!std::filesystem::is_directory(status)) { errno = 20; return -1; }
errno = 22;
return -1;
}
std::error_code error;
#ifdef _WIN32
const DWORD attributes = GetFileAttributesW(resolved.c_str());

View File

@ -328,6 +328,14 @@ extern "C" int SetWorkingDirectory_nid_no_patch(const char* resolved) {
}
}
extern "C" bool FinalPathComponentIsDot_nid_no_patch(const char* path) {
std::string_view text(path);
while (!text.empty() && (text.back() == '/' || text.back() == '\\')) text.remove_suffix(1);
const auto separator = text.find_last_of("/\\");
const auto component = text.substr(separator == std::string_view::npos ? 0 : separator + 1);
return component == ".";
}
extern "C" int APS5_VABI chdir_nid_postfix(const char* path) {
if (!path) { errno = 14; return -1; }
if (!*path) { errno = 2; return -1; }

View File

@ -1018,6 +1018,7 @@ int APS5_VABI sceKernelRmdir(const char* path) {
const bool blockedByFile = HasNonDirectoryPrefix(native) || std::filesystem::exists(WithoutTrailingSeparator(native), error);
return SceErrorFromErrno(blockedByFile ? GUEST_ENOTDIR : GUEST_ENOENT);
}
if (FinalPathComponentIsDot_nid_no_patch(path)) return SceErrorFromErrno(GUEST_EINVAL);
if (!std::filesystem::is_empty(native, error)) return SceErrorFromErrno(GUEST_ENOTEMPTY);
if (!RemoveEmptyDirectory(native)) return SceErrorFromErrno(GUEST_EIO);
RecordWrittenPath_nid_no_patch(native);

View File

@ -460,6 +460,25 @@ int main() {
Require(rmdir_nid_postfix((missingName + "/").c_str()) == -1 && *__error_nid_postfix() == 2);
Require(sceKernelRmdir((presentName + "/").c_str()) == static_cast<int>(0x80020014u));
Require(sceKernelRmdir((missingName + "/").c_str()) == static_cast<int>(0x80020002u));
const auto dotDirectory = root / "dot-directory";
Require(std::filesystem::create_directory(dotDirectory));
const auto dotDirectoryName = dotDirectory.string() + "/.";
const auto dotDirectoryTrailingName = dotDirectoryName + "/";
Require(rmdir_nid_postfix(dotDirectoryName.c_str()) == -1 && *__error_nid_postfix() == 22);
Require(sceKernelRmdir(dotDirectoryName.c_str()) == static_cast<int>(0x80020016u));
Require(remove_nid_postfix(dotDirectoryName.c_str()) == -1 && *__error_nid_postfix() == 22);
Require(rmdir_nid_postfix(dotDirectoryTrailingName.c_str()) == -1 && *__error_nid_postfix() == 22);
Require(sceKernelRmdir(dotDirectoryTrailingName.c_str()) == static_cast<int>(0x80020016u));
Require(remove_nid_postfix(dotDirectoryTrailingName.c_str()) == -1 && *__error_nid_postfix() == 22);
Require(std::filesystem::is_directory(dotDirectory));
const auto dotFileName = presentName + "/.";
Require(rmdir_nid_postfix(dotFileName.c_str()) == -1 && *__error_nid_postfix() == 20);
Require(sceKernelRmdir(dotFileName.c_str()) == static_cast<int>(0x80020014u));
Require(remove_nid_postfix(dotFileName.c_str()) == -1 && *__error_nid_postfix() == 20);
const auto dotMissingName = missingName + "/.";
Require(rmdir_nid_postfix(dotMissingName.c_str()) == -1 && *__error_nid_postfix() == 2);
Require(sceKernelRmdir(dotMissingName.c_str()) == static_cast<int>(0x80020002u));
Require(remove_nid_postfix(dotMissingName.c_str()) == -1 && *__error_nid_postfix() == 2);
const auto missingParentLeaf = rootName + "/missing/leaf";
Require(sceKernelMkdir(missingParentLeaf.c_str(), 0700) == static_cast<int>(0x80020002u));
const auto invalidNameUnderFile = presentName + "/a:b/x";
@ -500,6 +519,7 @@ int main() {
const auto empty = root / "empty";
Require(std::filesystem::create_directory(empty));
Require(rmdir_nid_postfix(empty.string().c_str()) == 0 && !std::filesystem::exists(empty));
Require(rmdir_nid_postfix(dotDirectory.string().c_str()) == 0 && !std::filesystem::exists(dotDirectory));
Require(remove_nid_postfix(root.string().c_str()) == 0);
Require(!std::filesystem::exists(root));
}