From 9a294cf9550a2710d590d2e1ed8c0a9da4c5225f Mon Sep 17 00:00:00 2001 From: J Reece Wilson Date: Sat, 16 Sep 2023 01:13:41 +0100 Subject: [PATCH] [*] (Mostly Linux) Harden FS apis --- Source/IO/FS/FS.NT.cpp | 106 +++++++++++++++++---- Source/IO/FS/FS.Unix.cpp | 159 +++++++++++++++++++++++++++---- Source/IO/FS/FileStream.Unix.cpp | 112 +++++++++++++++------- 3 files changed, 304 insertions(+), 73 deletions(-) diff --git a/Source/IO/FS/FS.NT.cpp b/Source/IO/FS/FS.NT.cpp index 97287e41..dbb275e2 100644 --- a/Source/IO/FS/FS.NT.cpp +++ b/Source/IO/FS/FS.NT.cpp @@ -87,6 +87,12 @@ namespace Aurora::IO::FS AUKN_SYM AuSPtr ReadDir(const AuString &string) { + if (string.empty()) + { + SysPushErrorArg("Cannot open an IO handle to the provided empty path"); + return {}; + } + auto pObj = AuMakeShared(); if (!pObj) { @@ -143,12 +149,6 @@ namespace Aurora::IO::FS AUKN_SYM bool DirsInDirectory(const AuString &string, AuList &dirs) { - if (string.empty()) - { - SysPushErrorArg("Cannot open an IO handle to the provided empty path"); - return false; - } - auto itr = ReadDir(string); if (!itr) { @@ -346,16 +346,35 @@ namespace Aurora::IO::FS return false; } - if (::DeleteFileW(translatedPath.c_str())) + DWORD dwAttrib = ::GetFileAttributesW(translatedPath.c_str()); + + if (dwAttrib == INVALID_FILE_ATTRIBUTES) { - return true; + SysPushErrorIO("Couldn't open file ({}) for removal", path); + return false; } - if (::RemoveDirectoryW(translatedPath.c_str())) + if (dwAttrib & FILE_ATTRIBUTE_READONLY) { - return true; + SysPushErrorIO("Couldn't open file ({}) for removal (read-only)", path); + return false; } + if (dwAttrib & FILE_ATTRIBUTE_DIRECTORY) + { + if (::RemoveDirectoryW(translatedPath.c_str())) + { + return true; + } + } + else + { + if (::DeleteFileW(translatedPath.c_str())) + { + return true; + } + } + return false; } catch (...) @@ -369,12 +388,28 @@ namespace Aurora::IO::FS { try { - auto srcPathNormalized = NormalizePathRet(src); - auto destPathNormalized = NormalizePathRet(dest); - CreateDirectories(destPathNormalized, true); + if (src.empty() || + dest.empty()) + { + SysPushErrorArg("Cannot open an IO handle to the provided empty path"); + return false; + } - return ::MoveFileW(Locale::ConvertFromUTF8(srcPathNormalized).c_str(), - Locale::ConvertFromUTF8(destPathNormalized).c_str()); + auto pathSrcExpanded = Locale::ConvertFromUTF8(NormalizePathRet(src)); + auto pathExpandedA = NormalizePathRet(dest); + auto pathExpanded = Locale::ConvertFromUTF8(pathExpandedA); + + if (pathSrcExpanded.empty() || + pathExpanded.empty()) + { + SysPushErrorMemory(); + return false; + } + + CreateDirectories(pathExpandedA, true); + + return ::MoveFileW(pathSrcExpanded.c_str(), + pathExpanded.c_str()); } catch (...) { @@ -386,12 +421,28 @@ namespace Aurora::IO::FS { try { - auto srcPathNormalized = NormalizePathRet(src); - auto destPathNormalized = NormalizePathRet(dest); - CreateDirectories(destPathNormalized, true); + if (src.empty() || + dest.empty()) + { + SysPushErrorArg("Cannot open an IO handle to the provided empty path"); + return false; + } - return ::CopyFileW(Locale::ConvertFromUTF8(srcPathNormalized).c_str(), - Locale::ConvertFromUTF8(destPathNormalized).c_str(), true); + auto pathSrcExpanded = Locale::ConvertFromUTF8(NormalizePathRet(src)); + auto pathExpandedA = NormalizePathRet(dest); + auto pathExpanded = Locale::ConvertFromUTF8(pathExpandedA); + + if (pathSrcExpanded.empty() || + pathExpanded.empty()) + { + SysPushErrorMemory(); + return false; + } + + CreateDirectories(pathExpandedA, true); + + return ::CopyFileW(pathSrcExpanded.c_str(), + pathExpanded.c_str(), true); } catch (...) { @@ -406,7 +457,20 @@ namespace Aurora::IO::FS stat = {}; - if (!::GetFileAttributesExW(Locale::ConvertFromUTF8(NormalizePathRet(path)).c_str(), GetFileExInfoStandard, &data)) + if (path.empty()) + { + SysPushErrorArg("Cannot open an IO handle to the provided empty path"); + return false; + } + + auto translatedPath = Locale::ConvertFromUTF8(NormalizePathRet(path)); + if (translatedPath.empty()) + { + SysPushErrorMemory(); + return false; + } + + if (!::GetFileAttributesExW(translatedPath.c_str(), GetFileExInfoStandard, &data)) { stat.bExists = false; return false; diff --git a/Source/IO/FS/FS.Unix.cpp b/Source/IO/FS/FS.Unix.cpp index 66ab5451..e769dbc2 100755 --- a/Source/IO/FS/FS.Unix.cpp +++ b/Source/IO/FS/FS.Unix.cpp @@ -126,6 +126,12 @@ namespace Aurora::IO::FS static AuSPtr ReadDirEx(const AuString &string, bool bFast) { + if (string.empty()) + { + SysPushErrorArg("Cannot open an IO handle to the provided empty path"); + return {}; + } + auto pObj = AuMakeShared(); if (!pObj) { @@ -216,6 +222,12 @@ namespace Aurora::IO::FS { AuMemoryViewWrite writeView; + if (path.empty()) + { + SysPushErrorArg("Cannot open an IO handle to the provided empty path"); + return false; + } + bool bIsStupidFD = AuStartsWith(path, "/proc/") || AuStartsWith(path, "/sys/") || @@ -281,41 +293,124 @@ namespace Aurora::IO::FS AUKN_SYM bool FileExists(const AuString &path) { - return UnixExists(NormalizePathRet(path), false); + if (path.empty()) + { + SysPushErrorArg("Cannot open an IO handle to the provided empty path"); + return false; + } + + auto pathExpanded = NormalizePathRet(path); + if (pathExpanded.empty()) + { + SysPushErrorMemory(); + return false; + } + + return UnixExists(pathExpanded, false); } AUKN_SYM bool DirExists(const AuString &path) { - return UnixExists(NormalizePathRet(path), true); + if (path.empty()) + { + SysPushErrorArg("Cannot open an IO handle to the provided empty path"); + return false; + } + + auto pathExpanded = NormalizePathRet(path); + if (pathExpanded.empty()) + { + SysPushErrorMemory(); + return false; + } + + return UnixExists(pathExpanded, true); } AUKN_SYM bool DirMk(const AuString &path) { - return CreateDirectories(NormalizePathRet(path), false); + if (path.empty()) + { + SysPushErrorArg("Cannot open an IO handle to the provided empty path"); + return false; + } + + auto pathExpanded = NormalizePathRet(path); + if (pathExpanded.empty()) + { + SysPushErrorMemory(); + return false; + } + + return CreateDirectories(pathExpanded, false); } AUKN_SYM bool Remove(const AuString &path) { - return remove(NormalizePathRet(path).c_str()) != -1; + if (path.empty()) + { + SysPushErrorArg("Cannot open an IO handle to the provided empty path"); + return false; + } + + auto pathExpanded = NormalizePathRet(path); + if (pathExpanded.empty()) + { + SysPushErrorMemory(); + return false; + } + + return remove(pathExpanded.c_str()) != -1; } AUKN_SYM bool Relink(const AuString &src, const AuString &dest) { - auto normalizedDestPath = NormalizePathRet(dest); - CreateDirectories(normalizedDestPath, true); - return ::rename(NormalizePathRet(src).c_str(), normalizedDestPath.c_str()) != -1; + if (src.empty() || + dest.empty()) + { + SysPushErrorArg("Cannot open an IO handle to the provided empty path"); + return false; + } + + auto pathSrcExpanded = NormalizePathRet(src); + auto pathExpanded = NormalizePathRet(dest); + + if (pathSrcExpanded.empty() || + pathExpanded.empty()) + { + SysPushErrorMemory(); + return false; + } + + return ::rename(pathSrcExpanded.c_str(), pathExpanded.c_str()) != -1; } #if defined(AURORA_IS_LINUX_DERIVED) AUKN_SYM bool Copy(const AuString &src, const AuString &dest) { - auto normalizedSrcPath = NormalizePathRet(src); - auto normalizedDestPath = NormalizePathRet(dest); + if (src.empty() || + dest.empty()) + { + SysPushErrorArg("Cannot open an IO handle to the provided empty path"); + return false; + } + + auto pathSrcExpanded = NormalizePathRet(src); + auto pathExpanded = NormalizePathRet(dest); + + if (pathSrcExpanded.empty() || + pathExpanded.empty()) + { + SysPushErrorMemory(); + return false; + } + + CreateDirectories(pathExpanded, true); int input, output; - if ((input = ::open(normalizedSrcPath.c_str(), O_RDONLY | O_CLOEXEC)) == -1) + if ((input = ::open(pathSrcExpanded.c_str(), O_RDONLY | O_CLOEXEC)) == -1) { return false; } @@ -324,12 +419,14 @@ namespace Aurora::IO::FS if (::fstat(input, &fileinfo) != 0) { ::close(input); + SysPushErrorIO("fstat failed"); return false; } - if ((output = ::creat(normalizedDestPath.c_str(), fileinfo.st_mode)) == -1) + if ((output = ::creat(pathExpanded.c_str(), fileinfo.st_mode)) == -1) { ::close(input); + SysPushErrorIO("creat failed"); return false; } @@ -338,21 +435,35 @@ namespace Aurora::IO::FS ::close(input); ::close(output); - return result; + return bytesCopied == fileinfo.st_size; } #elif defined(AURORA_IS_BSD_DERIVED) AUKN_SYM bool Copy(const AuString &src, const AuString &dest) { - auto normalizedSrcPath = NormalizePathRet(src); - auto normalizedDestPath = NormalizePathRet(dest); + if (src.empty() || + dest.empty()) + { + SysPushErrorArg("Cannot open an IO handle to the provided empty path"); + return false; + } - CreateDirectories(normalizedDestPath, true); + auto pathSrcExpanded = NormalizePathRet(src); + auto pathExpanded = NormalizePathRet(dest); + + if (pathSrcExpanded.empty() || + pathExpanded.empty()) + { + SysPushErrorMemory(); + return false; + } + + CreateDirectories(pathExpanded, true); int input, output; - if ((input = ::open(normalizedSrcPath.c_str(), O_RDONLY | O_CLOEXEC)) == -1) + if ((input = ::open(pathSrcExpanded.c_str(), O_RDONLY | O_CLOEXEC)) == -1) { return false; } @@ -361,12 +472,14 @@ namespace Aurora::IO::FS if (::fstat(input, &fileinfo) != 0) { close(input) + SysPushErrorIO("fstat failed"); return false; } - if ((output = ::creat(normalizedDestPath.c_str(), fileinfo.st_mode)) == -1) + if ((output = ::creat(pathExpanded.c_str(), fileinfo.st_mode)) == -1) { close(input); + SysPushErrorIO("creat failed"); return false; } @@ -390,7 +503,19 @@ namespace Aurora::IO::FS AUKN_SYM bool StatFile(const AuString &pathRel, Stat &stat) { stat = {}; + + if (pathRel.empty()) + { + SysPushErrorArg("Cannot open an IO handle to the provided empty path"); + return false; + } + auto path = NormalizePathRet(pathRel); + if (path.empty()) + { + SysPushErrorMemory(); + return false; + } struct stat s; auto err = ::stat(path.c_str(), &s); diff --git a/Source/IO/FS/FileStream.Unix.cpp b/Source/IO/FS/FileStream.Unix.cpp index f07126be..d86254e1 100755 --- a/Source/IO/FS/FileStream.Unix.cpp +++ b/Source/IO/FS/FileStream.Unix.cpp @@ -137,22 +137,29 @@ namespace Aurora::IO::FS { AU_LOCK_GUARD(this->spinlock_); - auto iOptSafe = this->pHandle_->GetOSWriteHandleSafe(); - if (!iOptSafe) + int fd {}; + if (auto pHandle = this->pHandle_) { - iOptSafe = this->pHandle_->GetOSReadHandleSafe(); - } + if (!pHandle->IsFile()) + { + SysPushErrorIOResourceRejected(); + return 0; + } - if (!iOptSafe) - { - return false; + if (auto opt = pHandle->GetOSHandleSafe()) + { + fd = opt.Value(); + } + else + { + SysPushErrorInvalidFd(); + return 0; + } } - - auto fd = (int)iOptSafe.Value(); - if (fd == -1) + else { SysPushErrorUninitialized(); - return false; + return 0; } return PosixGetOffset(fd); @@ -162,24 +169,31 @@ namespace Aurora::IO::FS { AU_LOCK_GUARD(this->spinlock_); - auto iOptSafe = this->pHandle_->GetOSWriteHandleSafe(); - if (!iOptSafe) - { - iOptSafe = this->pHandle_->GetOSReadHandleSafe(); - } - - if (!iOptSafe) + int fd {}; + if (auto pHandle = this->pHandle_) { - return false; + if (!pHandle->IsFile()) + { + SysPushErrorIOResourceRejected(); + return 0; + } + + if (auto opt = pHandle->GetOSHandleSafe()) + { + fd = opt.Value(); + } + else + { + SysPushErrorInvalidFd(); + return false; + } } - - auto fd = (int)iOptSafe.Value(); - if (fd == -1) + else { SysPushErrorUninitialized(); return false; } - + return PosixSetOffset(fd, offset); } @@ -187,17 +201,33 @@ namespace Aurora::IO::FS { AU_LOCK_GUARD(this->spinlock_); - auto iOptSafe = this->pHandle_->GetOSReadHandleSafe(); - if (!iOptSafe) + int fd {}; + if (auto pHandle = this->pHandle_) { - return false; - } + if (!pHandle->IsFile()) + { + // Intentionally suppressed (for the moment) + // I don't want to fill error stacks up under paths that can handle char-devs by reading until EoS + // If we do, we could hit an always-throw on AuErrorStack-catch + // Some users are completely unaware they can access/test the underlying handle + // Returning zero will do + return 0; + } - auto fd = (int)iOptSafe.Value(); - if (fd == -1) + if (auto opt = pHandle->GetOSHandleSafe()) + { + fd = opt.Value(); + } + else + { + SysPushErrorInvalidFd(); + return 0; + } + } + else { SysPushErrorUninitialized(); - return false; + return 0; } return PosixGetLength(fd); @@ -259,7 +289,7 @@ namespace Aurora::IO::FS return 0; } - int fd = this->GetUnixHandle(); + int fd = this->pHandle_->GetOSWriteHandleSafe().ValueOr((AuUInt)-1); if (fd == -1) { SysPushErrorUninitialized(); @@ -317,24 +347,36 @@ namespace Aurora::IO::FS void PosixFileStream::Flush() { - int fd = this->GetUnixHandle(); - if (fd == -1) + if (!this->pHandle_) { SysPushErrorUninitialized(); return; } + int fd = this->pHandle_->GetOSWriteHandleSafe().ValueOr((AuUInt)-1); + if (fd == -1) + { + SysPushErrorIOResourceRejected(); + return; + } + ::fsync(fd); } void PosixFileStream::WriteEoS() { - int fd = this->GetUnixHandle(); - if (fd == -1) + if (!this->pHandle_) { SysPushErrorUninitialized(); return; } + + int fd = this->pHandle_->GetOSWriteHandleSafe().ValueOr((AuUInt)-1); + if (fd == -1) + { + SysPushErrorIOResourceRejected(); + return; + } ::ftruncate(fd, GetOffset()); }