[fix] correct Windows LAN identity file ownership

This commit is contained in:
dijunkun
2026-10-26 15:52:21 +08:00
parent c3ea4eb8fc
commit 8fd7ca1e8d
+65 -37
View File
@@ -58,9 +58,8 @@ bool IsHex(const std::string& text, size_t size) {
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'); return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f');
}); });
} }
void PrivateWrite(const std::filesystem::path& path, const std::string& text) {
const auto temporary = path.parent_path() / (".write-" + RandomHex(16));
#ifdef _WIN32 #ifdef _WIN32
std::vector<unsigned char> CurrentUser() {
HANDLE token = nullptr; HANDLE token = nullptr;
Require(OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token), Require(OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token),
"Cannot open user token"); "Cannot open user token");
@@ -70,25 +69,39 @@ void PrivateWrite(const std::filesystem::path& path, const std::string& text) {
bool ok = GetTokenInformation(token, TokenUser, data.data(), size, &size); bool ok = GetTokenInformation(token, TokenUser, data.data(), size, &size);
CloseHandle(token); CloseHandle(token);
Require(ok, "Cannot read user token"); Require(ok, "Cannot read user token");
return data;
}
PSECURITY_DESCRIPTOR PrivateSecurityDescriptor(bool directory) {
auto user = CurrentUser();
LPWSTR sid = nullptr; LPWSTR sid = nullptr;
Require(ConvertSidToStringSidW( Require(ConvertSidToStringSidW(
reinterpret_cast<TOKEN_USER*>(data.data())->User.Sid, &sid), reinterpret_cast<TOKEN_USER*>(user.data())->User.Sid, &sid),
"Cannot read user SID"); "Cannot read user SID");
std::wstring acl = L"D:P(A;;FA;;;" + std::wstring(sid) + L")"; // An elevated token can default to Administrators as the owner even when
// its DACL grants access only to TokenUser. Always set both explicitly.
const auto acl = L"O:" + std::wstring(sid) + L"D:P(A;" +
(directory ? L"OICI" : L"") + L";FA;;;" + sid + L")";
LocalFree(sid); LocalFree(sid);
PSECURITY_DESCRIPTOR descriptor = nullptr; PSECURITY_DESCRIPTOR descriptor = nullptr;
Require(ConvertStringSecurityDescriptorToSecurityDescriptorW( Require(ConvertStringSecurityDescriptorToSecurityDescriptorW(
acl.c_str(), SDDL_REVISION_1, &descriptor, nullptr), acl.c_str(), SDDL_REVISION_1, &descriptor, nullptr),
"Cannot protect LAN file"); "Cannot protect LAN file");
return descriptor;
}
#endif
void PrivateWrite(const std::filesystem::path& path, const std::string& text) {
const auto temporary = path.parent_path() / (".write-" + RandomHex(16));
#ifdef _WIN32
auto descriptor = PrivateSecurityDescriptor(false);
SECURITY_ATTRIBUTES security{sizeof(security), descriptor, FALSE}; SECURITY_ATTRIBUTES security{sizeof(security), descriptor, FALSE};
HANDLE file = CreateFileW(temporary.c_str(), GENERIC_WRITE, 0, &security, HANDLE file = CreateFileW(temporary.c_str(), GENERIC_WRITE, 0, &security,
CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr); CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr);
LocalFree(descriptor); LocalFree(descriptor);
Require(file != INVALID_HANDLE_VALUE, "Cannot create LAN file"); Require(file != INVALID_HANDLE_VALUE, "Cannot create LAN file");
DWORD written = 0; DWORD written = 0;
ok = WriteFile(file, text.data(), static_cast<DWORD>(text.size()), &written, bool ok = WriteFile(file, text.data(), static_cast<DWORD>(text.size()),
nullptr) && &written, nullptr) &&
written == text.size() && FlushFileBuffers(file); written == text.size() && FlushFileBuffers(file);
CloseHandle(file); CloseHandle(file);
if (!ok || !MoveFileExW(temporary.c_str(), path.c_str(), if (!ok || !MoveFileExW(temporary.c_str(), path.c_str(),
MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH)) { MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH)) {
@@ -114,34 +127,42 @@ void PrivateWrite(const std::filesystem::path& path, const std::string& text) {
#endif #endif
} }
#ifdef _WIN32 #ifdef _WIN32
std::vector<unsigned char> CurrentUser() { using FileHandle = std::unique_ptr<void, decltype(&CloseHandle)>;
HANDLE token = nullptr; FileHandle OpenLanPath(const std::filesystem::path& path, DWORD access) {
Require(OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token), // Keep the object open without FILE_SHARE_DELETE so validation and legacy
"Cannot open user token"); // owner repair cannot operate on a replacement at the same path.
DWORD size = 0; HANDLE file = CreateFileW(
GetTokenInformation(token, TokenUser, nullptr, 0, &size); path.c_str(), access, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
std::vector<unsigned char> data(size); OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS,
bool ok = GetTokenInformation(token, TokenUser, data.data(), size, &size); nullptr);
CloseHandle(token); Require(file != INVALID_HANDLE_VALUE, "Cannot open LAN path");
Require(ok, "Cannot read user token"); return {file, CloseHandle};
return data;
} }
void CheckPrivateWindowsPath(const std::filesystem::path& path) { void CheckPrivateWindowsPath(const std::filesystem::path& path) {
const auto attributes = GetFileAttributesW(path.c_str()); auto file = OpenLanPath(path, READ_CONTROL);
Require(attributes != INVALID_FILE_ATTRIBUTES && BY_HANDLE_FILE_INFORMATION info{};
!(attributes & FILE_ATTRIBUTE_REPARSE_POINT), Require(GetFileInformationByHandle(file.get(), &info) &&
!(info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT),
"LAN path cannot be a reparse point"); "LAN path cannot be a reparse point");
auto user = CurrentUser(); auto user = CurrentUser();
auto sid = reinterpret_cast<TOKEN_USER*>(user.data())->User.Sid; auto sid = reinterpret_cast<TOKEN_USER*>(user.data())->User.Sid;
PSID owner = nullptr; PSID owner = nullptr;
PACL acl = nullptr; PACL acl = nullptr;
PSECURITY_DESCRIPTOR descriptor = nullptr; PSECURITY_DESCRIPTOR descriptor = nullptr;
Require(GetNamedSecurityInfoW( Require(GetSecurityInfo(
const_cast<wchar_t*>(path.c_str()), SE_FILE_OBJECT, file.get(), SE_FILE_OBJECT,
OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION, &owner, OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION, &owner,
nullptr, &acl, nullptr, &descriptor) == ERROR_SUCCESS, nullptr, &acl, nullptr, &descriptor) == ERROR_SUCCESS,
"Cannot read LAN file permissions"); "Cannot read LAN file permissions");
bool ok = owner && EqualSid(owner, sid) && acl && acl->AceCount > 0; std::unique_ptr<void, decltype(&LocalFree)> security(descriptor, LocalFree);
bool ok = acl && acl->AceCount > 0;
// Only the exact protected, full-access DACL written by older versions is
// eligible for owner repair. Never adopt a shared or foreign-user profile.
SECURITY_DESCRIPTOR_CONTROL control{};
DWORD revision = 0;
bool legacy = owner && IsWellKnownSid(owner, WinBuiltinAdministratorsSid) &&
GetSecurityDescriptorControl(descriptor, &control, &revision) &&
(control & SE_DACL_PROTECTED) && acl && acl->AceCount == 1;
for (DWORD i = 0; ok && i < acl->AceCount; ++i) { for (DWORD i = 0; ok && i < acl->AceCount; ++i) {
void* raw = nullptr; void* raw = nullptr;
if (!GetAce(acl, i, &raw)) { if (!GetAce(acl, i, &raw)) {
@@ -151,23 +172,30 @@ void CheckPrivateWindowsPath(const std::filesystem::path& path) {
const auto* ace = static_cast<ACCESS_ALLOWED_ACE*>(raw); const auto* ace = static_cast<ACCESS_ALLOWED_ACE*>(raw);
ok = ace->Header.AceType == ACCESS_ALLOWED_ACE_TYPE && ok = ace->Header.AceType == ACCESS_ALLOWED_ACE_TYPE &&
EqualSid(const_cast<DWORD*>(&ace->SidStart), sid); EqualSid(const_cast<DWORD*>(&ace->SidStart), sid);
const BYTE flags = (info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
? OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE
: 0;
legacy =
legacy && ace->Mask == FILE_ALL_ACCESS && ace->Header.AceFlags == flags;
}
Require(ok && owner && (EqualSid(owner, sid) || legacy),
"LAN files must be accessible only by the current user");
if (legacy) {
auto writable = OpenLanPath(path, WRITE_OWNER);
BY_HANDLE_FILE_INFORMATION current{};
Require(GetFileInformationByHandle(writable.get(), &current) &&
current.dwVolumeSerialNumber == info.dwVolumeSerialNumber &&
current.nFileIndexHigh == info.nFileIndexHigh &&
current.nFileIndexLow == info.nFileIndexLow &&
SetSecurityInfo(writable.get(), SE_FILE_OBJECT,
OWNER_SECURITY_INFORMATION, sid, nullptr,
nullptr, nullptr) == ERROR_SUCCESS,
"Cannot repair LAN file owner");
} }
LocalFree(descriptor);
Require(ok, "LAN files must be accessible only by the current user");
} }
void CreatePrivateDirectory(const std::filesystem::path& path) { void CreatePrivateDirectory(const std::filesystem::path& path) {
if (!std::filesystem::exists(path)) { if (!std::filesystem::exists(path)) {
auto user = CurrentUser(); auto descriptor = PrivateSecurityDescriptor(true);
LPWSTR sid = nullptr;
Require(ConvertSidToStringSidW(
reinterpret_cast<TOKEN_USER*>(user.data())->User.Sid, &sid),
"Cannot read user SID");
const auto acl = L"D:P(A;OICI;FA;;;" + std::wstring(sid) + L")";
LocalFree(sid);
PSECURITY_DESCRIPTOR descriptor = nullptr;
Require(ConvertStringSecurityDescriptorToSecurityDescriptorW(
acl.c_str(), SDDL_REVISION_1, &descriptor, nullptr),
"Cannot protect LAN directory");
SECURITY_ATTRIBUTES security{sizeof(security), descriptor, FALSE}; SECURITY_ATTRIBUTES security{sizeof(security), descriptor, FALSE};
bool ok = CreateDirectoryW(path.c_str(), &security); bool ok = CreateDirectoryW(path.c_str(), &security);
LocalFree(descriptor); LocalFree(descriptor);