mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-18 23:07:07 -05:00
fix(libkernel): report absolute Windows symbolic links
lstat and readlink aborted on an absolute symbolic link because only a relative reparse point was accepted. Read the link text, and still refuse reparse points that are not symbolic links.
This commit is contained in:
1 parent
e09dbc2b42
commit
6ad392dad3
5 files changed
+33
-35
No files matched your search
@@ -26,8 +26,22 @@ class WindowsSymbolicLink {
|
||||
};
|
||||
static constexpr ULONG relativeFlag = 1;
|
||||
|
||||
static std::wstring TextAt(const std::byte* data, DWORD size, USHORT offset, USHORT length, const char* operation) {
|
||||
const std::size_t begin = sizeof(ReparseData) + offset;
|
||||
if (length % sizeof(wchar_t) != 0 || begin + length > size)
|
||||
throw std::runtime_error(std::string(operation) + ": malformed symbolic link reparse data");
|
||||
std::wstring name(length / sizeof(wchar_t), L'\0');
|
||||
if (!name.empty()) std::memcpy(name.data(), data + begin, name.size() * sizeof(wchar_t));
|
||||
return name;
|
||||
}
|
||||
|
||||
static std::wstring WithoutNtPrefix(std::wstring name) {
|
||||
if (name.rfind(L"\\??\\", 0) == 0 || name.rfind(L"\\\\?\\", 0) == 0) name.erase(0, 4);
|
||||
return name;
|
||||
}
|
||||
|
||||
public:
|
||||
static bool ReadRelative(const std::filesystem::path& native, std::string& target, const char* operation) {
|
||||
static bool Read(const std::filesystem::path& native, std::string& target, const char* operation) {
|
||||
const HANDLE handle = CreateFileW(native.c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,
|
||||
OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, nullptr);
|
||||
if (handle == INVALID_HANDLE_VALUE)
|
||||
@@ -42,12 +56,13 @@ public:
|
||||
ReparseData header{};
|
||||
if (size < sizeof(header)) return false;
|
||||
std::memcpy(&header, data.data(), sizeof(header));
|
||||
if (header.tag != IO_REPARSE_TAG_SYMLINK || (header.flags & relativeFlag) == 0) return false;
|
||||
const std::size_t begin = sizeof(header) + header.substituteNameOffset;
|
||||
if (begin + header.substituteNameLength > size)
|
||||
throw std::runtime_error(std::string(operation) + ": malformed symbolic link reparse data");
|
||||
std::wstring name(header.substituteNameLength / sizeof(wchar_t), L'\0');
|
||||
std::memcpy(name.data(), data.data() + begin, name.size() * sizeof(wchar_t));
|
||||
if (header.tag != IO_REPARSE_TAG_SYMLINK) return false;
|
||||
const bool relative = (header.flags & relativeFlag) != 0;
|
||||
std::wstring name = relative
|
||||
? TextAt(data.data(), size, header.substituteNameOffset, header.substituteNameLength, operation)
|
||||
: TextAt(data.data(), size, header.printNameOffset, header.printNameLength, operation);
|
||||
if (!relative && name.empty())
|
||||
name = WithoutNtPrefix(TextAt(data.data(), size, header.substituteNameOffset, header.substituteNameLength, operation));
|
||||
target = std::filesystem::path(name).generic_string();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -51,8 +51,8 @@ static int DoLstat(const std::filesystem::path& p, NativeStat* st) {
|
||||
const auto attributes = GetFileAttributesW(p.c_str());
|
||||
if (attributes == INVALID_FILE_ATTRIBUTES || (attributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0) return DoStat(p, st);
|
||||
std::string target;
|
||||
if (!File::WindowsSymbolicLink::ReadRelative(p, target, "lstat"))
|
||||
NotImplemented_nid_no_patch("lstat of a Windows reparse point other than a relative symbolic link");
|
||||
if (!File::WindowsSymbolicLink::Read(p, target, "lstat"))
|
||||
NotImplemented_nid_no_patch("lstat of a Windows reparse point other than a symbolic link");
|
||||
const auto handle = CreateFileW(p.c_str(), FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
||||
nullptr, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, nullptr);
|
||||
if (handle == INVALID_HANDLE_VALUE) return File::WindowsFileTime::Failure(GetLastError());
|
||||
|
||||
@@ -130,8 +130,8 @@ std::int64_t ReadNativeLink(const std::filesystem::path& native, char* buffer, s
|
||||
const DWORD attributes = GetFileAttributesW(native.wstring().c_str());
|
||||
if (attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0) {
|
||||
std::string target;
|
||||
if (!File::WindowsSymbolicLink::ReadRelative(native, target, "readlink")) {
|
||||
NotImplemented_nid_no_patch("readlink of a Windows reparse point other than a relative symbolic link");
|
||||
if (!File::WindowsSymbolicLink::Read(native, target, "readlink")) {
|
||||
NotImplemented_nid_no_patch("readlink of a Windows reparse point other than a symbolic link");
|
||||
return -1;
|
||||
}
|
||||
return CopyLinkTarget(target, buffer, size);
|
||||
|
||||
@@ -713,24 +713,10 @@ int main() {
|
||||
Require(stat_nid_postfix(fileLink.string().c_str(), &followed) == 0 && (followed.st_mode & 0170000) == 0100000);
|
||||
Require(followed.st_ino == status.st_ino && linkStatus.st_ino != status.st_ino && linkStatus.st_dev == status.st_dev);
|
||||
Require(stat_nid_postfix(directoryLink.string().c_str(), &followed) == 0 && (followed.st_mode & 0170000) == 0040000);
|
||||
const auto checkUnsupportedLink = [](const std::filesystem::path& link) {
|
||||
FileStat untouched;
|
||||
std::memset(&untouched, 0x5a, sizeof(untouched));
|
||||
constexpr int sentinelError = 123;
|
||||
*__error_nid_postfix() = sentinelError;
|
||||
bool rejected = false;
|
||||
try {
|
||||
lstat_nid_postfix(link.string().c_str(), &untouched);
|
||||
} catch (const std::runtime_error& error) {
|
||||
rejected = std::strcmp(error.what(), "lstat of a Windows reparse point other than a relative symbolic link not implemented") == 0;
|
||||
}
|
||||
const bool errorUnchanged = *__error_nid_postfix() == sentinelError;
|
||||
const auto* bytes = reinterpret_cast<const unsigned char*>(&untouched);
|
||||
const bool statusUnchanged = std::all_of(bytes, bytes + sizeof(untouched), [](unsigned char value) { return value == 0x5a; });
|
||||
return rejected && errorUnchanged && statusUnchanged;
|
||||
};
|
||||
Require(stat_nid_postfix(fileLink.string().c_str(), &status) == 0 && status.st_size == 5);
|
||||
Require(checkUnsupportedLink(absoluteLink));
|
||||
const auto absoluteTarget = std::filesystem::absolute(present).generic_string();
|
||||
Require(checkRelativeLink(absoluteLink, static_cast<std::int64_t>(absoluteTarget.size())));
|
||||
Require(stat_nid_postfix(absoluteLink.string().c_str(), &followed) == 0 && followed.st_ino == status.st_ino);
|
||||
Require(std::filesystem::remove(fileLink) && std::filesystem::remove(directoryLink) && DeleteFileW(danglingLink.c_str()) != 0 &&
|
||||
std::filesystem::remove(absoluteLink) && std::filesystem::remove(targetDirectory));
|
||||
}
|
||||
|
||||
@@ -287,15 +287,12 @@ int main() {
|
||||
Require(symlink_nid_postfix("sub", directoryLink.c_str()) == 0);
|
||||
Require(readlink_nid_postfix(directoryLink.c_str(), buffer, sizeof(buffer)) == 3 && std::memcmp(buffer, "sub", 3) == 0);
|
||||
const auto absolute = std::filesystem::absolute(root / "absolute");
|
||||
const auto absoluteTarget = std::filesystem::absolute(original).generic_string();
|
||||
Require(CreateSymbolicLinkW(absolute.wstring().c_str(), std::filesystem::absolute(original).wstring().c_str(),
|
||||
SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE) != 0);
|
||||
bool refused = false;
|
||||
try {
|
||||
readlink_nid_postfix((root / "absolute").string().c_str(), buffer, sizeof(buffer));
|
||||
} catch (const std::runtime_error& failure) {
|
||||
refused = std::string(failure.what()) == "readlink of a Windows reparse point other than a relative symbolic link not implemented";
|
||||
}
|
||||
Require(refused);
|
||||
std::memset(buffer, '#', sizeof(buffer));
|
||||
Require(readlink_nid_postfix((root / "absolute").string().c_str(), buffer, sizeof(buffer)) == static_cast<std::int64_t>(absoluteTarget.size()));
|
||||
Require(std::memcmp(buffer, absoluteTarget.data(), absoluteTarget.size()) == 0 && buffer[absoluteTarget.size()] == '#');
|
||||
#else
|
||||
Require(std::filesystem::is_symlink(std::filesystem::symlink_status(symbolic)));
|
||||
Require(readlink_nid_postfix(symbolic.c_str(), buffer, sizeof(buffer)) == 12 && std::memcmp(buffer, "original.bin", 12) == 0);
|
||||
|
||||
Reference in new issue
Block a user