From ba8706336855816164f3dbd1ed1d0f4c82ee6c96 Mon Sep 17 00:00:00 2001 From: John Parent Date: Fri, 30 Jan 2026 18:55:38 -0500 Subject: [PATCH 1/4] Utils: Add file permissions Some packages install themselves as readonly, or with other file permissions that conflict with what we're doing with this compiler wrapper. Thus, we need the ability to override (and restore) those settings. For this compiler wrapper to perform it's duties, it needs to be able to read and write the various binaries artifacts from a build. We thus need to be able to: 1. Obtain and store current permissions 2. Grant ourselves the required permissions 3. Do our reading/writing 4. Restore previous permissions On Windows, file access is handled by the read/write bit, as on other platforms, but also by Access Control Lists (ACLs) which are populated by Discretionary Access Control Lists (DACLs) which are the atomic units responsible for granting rights. This PR adds an interface to inspect, store, update, and restore the read/write bits and DACLs of a given file. This functionality is wrapped up in a RAII based class that essentially provides scoped permissions to a file for scope of the permissions object. Note: this assumes the user creating the permissions is the user driving this wrapper, or this user has admin rights, otherwise this will fail (gracefully, but still a failure) Signed-off-by: John Parent --- src/utils.cxx | 251 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/utils.h | 67 ++++++++++++++ 2 files changed, 318 insertions(+) diff --git a/src/utils.cxx b/src/utils.cxx index 76f9783..f5a72e9 100644 --- a/src/utils.cxx +++ b/src/utils.cxx @@ -4,6 +4,8 @@ * SPDX-License-Identifier: (Apache-2.0 OR MIT) */ #include "utils.h" +#include +#include #include #include #include @@ -12,6 +14,8 @@ #include #include #include +#include +#include #include #include #include @@ -926,6 +930,253 @@ char* findstr(char* search_str, const char* substr, size_t size) { return nullptr; } +ScopedSid FileSecurity::GetCurrentUserSid() { + HANDLE token_handle = nullptr; + if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, + &token_handle)) { + return nullptr; + } + std::unique_ptr const scoped_token( + token_handle, &::CloseHandle); + + DWORD buffer_size = 0; + ::GetTokenInformation(token_handle, TokenUser, nullptr, 0, &buffer_size); + + std::vector buffer(buffer_size); + auto* token_user = reinterpret_cast(buffer.data()); + + if (!::GetTokenInformation(token_handle, TokenUser, token_user, buffer_size, + &buffer_size)) { + return nullptr; + } + + DWORD const sid_len = ::GetLengthSid(token_user->User.Sid); + void* sid_copy = std::malloc(sid_len); + if (sid_copy) { + ::CopySid(sid_len, sid_copy, token_user->User.Sid); + return ScopedSid(sid_copy); + } + return nullptr; +} + +/** + * Determines whether a given file has a given set of file permissions + * \param file to check permissions for + * \param access_mask type of permissions to check for + * \param sid user identifier to check for permissions in the context of + * + */ +bool FileSecurity::HasPermission(const std::wstring& file_path, + DWORD access_mask, PSID sid) { + PACL dacl = nullptr; + PSECURITY_DESCRIPTOR sd_raw = nullptr; + DWORD result = ::GetNamedSecurityInfoW(file_path.c_str(), SE_FILE_OBJECT, + DACL_SECURITY_INFORMATION, nullptr, + nullptr, &dacl, nullptr, &sd_raw); + + if (result != ERROR_SUCCESS) + return false; + ScopedLocalInfo const scoped_sd(sd_raw); + + TRUSTEE_W trustee = {nullptr}; + trustee.TrusteeForm = TRUSTEE_IS_SID; + trustee.TrusteeType = TRUSTEE_IS_USER; + trustee.ptstrName = static_cast(sid); + + ACCESS_MASK effective_rights = 0; + result = ::GetEffectiveRightsFromAclW(dacl, &trustee, &effective_rights); + + if (result != ERROR_SUCCESS) + return false; + + if ((access_mask & GENERIC_WRITE) && (effective_rights & FILE_WRITE_DATA)) + return true; + if ((access_mask & GENERIC_READ) && (effective_rights & FILE_READ_DATA)) + return true; + if ((access_mask & GENERIC_ALL) && (effective_rights & FILE_ALL_ACCESS)) + return true; + + return (effective_rights & access_mask) == access_mask; +} + +/** + * \param file_path path to the file for which we are granting a permission + * \param access_mask the bitmask for the ACE permissions being requested + * \param sid pointer to the security identifier for a given trustee (typically the current user) + * \param out_old_sid output param, pointer to variable containing pointer to the pre-modified DACL + * useful to determine whether the security descriptor has been modified and + * provide a baseline sid + */ +bool FileSecurity::GrantPermission(const std::wstring& file_path, + DWORD access_mask, PSID sid, + PSECURITY_DESCRIPTOR* out_old_sd) { + PACL old_dacl = nullptr; + PSECURITY_DESCRIPTOR sd_raw = nullptr; + + DWORD result = ::GetNamedSecurityInfoW( + file_path.c_str(), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, + nullptr, &old_dacl, nullptr, &sd_raw); + + if (result != ERROR_SUCCESS) + return false; + + if (out_old_sd) + *out_old_sd = sd_raw; + ScopedLocalInfo const temp_sd_wrapper(out_old_sd ? nullptr : sd_raw); + + EXPLICIT_ACCESS_W ea = {0}; + ea.grfAccessPermissions = access_mask; + ea.grfAccessMode = GRANT_ACCESS; + ea.grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; + ea.Trustee.TrusteeForm = TRUSTEE_IS_SID; + ea.Trustee.TrusteeType = TRUSTEE_IS_USER; + ea.Trustee.ptstrName = static_cast(sid); + + PACL new_dacl = nullptr; + result = ::SetEntriesInAclW(1, &ea, old_dacl, &new_dacl); + if (result != ERROR_SUCCESS) + return false; + + ScopedLocalInfo const scoped_new_dacl(new_dacl); + result = ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), + SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, + nullptr, nullptr, new_dacl, nullptr); + return (result == ERROR_SUCCESS); +} + +/** + * Applies security descriptor to file + * \param file_path file to apply SD to + * \param sd security descriptor to apply to file + */ +bool FileSecurity::ApplyDescriptor(const std::wstring& file_path, + PSECURITY_DESCRIPTOR sd) { + if (!sd) + return false; + BOOL present = FALSE; + BOOL defaulted = FALSE; + PACL dacl = nullptr; + if (!::GetSecurityDescriptorDacl(sd, &present, &dacl, &defaulted) || + !present || !dacl) + return false; + + return ::SetNamedSecurityInfoW(const_cast(file_path.c_str()), + SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, + nullptr, nullptr, dacl, + nullptr) == ERROR_SUCCESS; +} + +/** + * Obtain current file attributes for file indicated by file path + * \param file_path file to obtain permissions for + * \param out_attr output parameter to hold permissions + */ +bool FileSecurity::GetAttributes(const std::wstring& file_path, + DWORD* out_attr) { + DWORD const attr = ::GetFileAttributesW(file_path.c_str()); + if (attr == INVALID_FILE_ATTRIBUTES) + return false; + if (out_attr) + *out_attr = attr; + return true; +} + +/** + * Set file attribute attr on file indicated by file_path + * \param file_path file to set attributes for + * \param attr attributes to obtain + */ +bool FileSecurity::SetAttributes(const std::wstring& file_path, DWORD attr) { + return ::SetFileAttributesW(file_path.c_str(), attr) != 0; +} + +/** + * Construct FileAccess object + * \param file_path file on which we want to obtain permissions + * \param desired_acess access control permissions we want to obtain for the file + */ +ScopedFileAccess::ScopedFileAccess(std::wstring file_path, DWORD desired_access) + : file_path_(std::move(file_path)), + desired_access_(desired_access), + original_sd_(nullptr), + current_user_sid_(nullptr), + acl_needs_revert_(false), + original_attributes_(0), + attributes_changed_(false) { +} + +/** + * Obtain permissions established by constructor + * + */ +void ScopedFileAccess::Access() { + + + // We must ensure we have permissions *first* before we try to + // change the file attributes in Phase 2. + + current_user_sid_ = FileSecurity::GetCurrentUserSid(); + if (!current_user_sid_) { + throw std::system_error(static_cast(::GetLastError()), + std::system_category(), "Failed to get SID"); + } + + // Check if we need to modify ACLs + if (!FileSecurity::HasPermission(file_path_, desired_access_, + current_user_sid_.get())) { + if (!FileSecurity::GrantPermission(file_path_, desired_access_, + current_user_sid_.get(), + &original_sd_)) { + throw std::system_error(static_cast(::GetLastError()), + std::system_category(), + "Failed to grant ACL"); + } + acl_needs_revert_ = true; + } + + if (FileSecurity::GetAttributes(file_path_, &original_attributes_)) { + if (original_attributes_ & FILE_ATTRIBUTE_READONLY) { + // Remove the Read-Only bit + DWORD const new_attributes = + original_attributes_ & ~FILE_ATTRIBUTE_READONLY; + + if (FileSecurity::SetAttributes(file_path_, new_attributes)) { + attributes_changed_ = true; + } else { + // If we fail to remove Read-Only, we might still fail to write later. + // We throw here to be safe and consistent. + throw std::system_error(static_cast(::GetLastError()), + std::system_category(), + "Failed to remove Read-Only attribute"); + } + } + } else { + throw std::system_error(static_cast(::GetLastError()), + std::system_category(), + "Failed to get file attributes"); + } +} + +ScopedFileAccess::~ScopedFileAccess() { + // We must restore attributes *before* we revert ACLs, because reverting ACLs + // might remove our permission to write attributes. + if (attributes_changed_) { + // We ignore errors in destructors to prevent termination + FileSecurity::SetAttributes(file_path_, original_attributes_); + } + + if (acl_needs_revert_ && original_sd_) { + FileSecurity::ApplyDescriptor(file_path_, original_sd_); + ::LocalFree(original_sd_); + } +} + +bool ScopedFileAccess::IsAccessGranted() const { + + return FileSecurity::HasPermission(file_path_, desired_access_, + current_user_sid_.get()); +} + NameTooLongError::NameTooLongError(char const* const message) : std::runtime_error(message) {} diff --git a/src/utils.h b/src/utils.h index 5dda860..87738d8 100644 --- a/src/utils.h +++ b/src/utils.h @@ -234,6 +234,21 @@ int SafeHandleCleanup(HANDLE& handle); // System Helpers // std::string reportLastError(); +struct LocalFreeDeleter { + void operator()(void* p) const { + if (p) + ::LocalFree(p); + } +}; + +// Custom deleter for Standard C pointers. +struct FreeDeleter { + void operator()(void* p) const { + if (p) + std::free(p); + } +}; + // Data helpers // // Converts big endian data to little endian form @@ -278,6 +293,58 @@ class LibraryFinder { void EvalSearchPaths(); }; +using ScopedLocalInfo = std::unique_ptr; + +using ScopedSid = std::unique_ptr; + +class FileSecurity { + public: + FileSecurity() = delete; + + static ScopedSid GetCurrentUserSid(); + + static bool HasPermission(const std::wstring& file_path, DWORD access_mask, + PSID sid); + + static bool GrantPermission(const std::wstring& file_path, + DWORD access_mask, PSID sid, + PSECURITY_DESCRIPTOR* out_old_sd); + + static bool ApplyDescriptor(const std::wstring& file_path, + PSECURITY_DESCRIPTOR sd); + + // Retrieves file attributes (e.g., ReadOnly, Hidden). + // Returns false if the file cannot be accessed. + static bool GetAttributes(const std::wstring& file_path, DWORD* out_attr); + + // Sets file attributes. + // Returns false if the operation fails. + static bool SetAttributes(const std::wstring& file_path, DWORD attr); +}; + +class ScopedFileAccess { + public: + explicit ScopedFileAccess(std::wstring file_path, + DWORD desired_access = GENERIC_WRITE); + ~ScopedFileAccess(); + void Access(); + + bool IsAccessGranted() const; + + private: + std::wstring file_path_; + DWORD desired_access_; + + // ACL State + PSECURITY_DESCRIPTOR original_sd_; + ScopedSid current_user_sid_; + bool acl_needs_revert_; + + // Attribute State + DWORD original_attributes_; + bool attributes_changed_; +}; + const std::map special_character_to_path{{'|', '\\'}, {';', ':'}}; const std::map path_to_special_characters{{'\\', '|'}, From be53f69c1b14cb97091822f2d16b8706db5f01cd Mon Sep 17 00:00:00 2001 From: John Parent Date: Wed, 8 Apr 2026 15:53:51 -0400 Subject: [PATCH 2/4] Rename: FileSecurity::GrantPermission -> AddPermission Signed-off-by: John Parent --- src/utils.cxx | 4 ++-- src/utils.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/utils.cxx b/src/utils.cxx index f5a72e9..e3c766f 100644 --- a/src/utils.cxx +++ b/src/utils.cxx @@ -1007,7 +1007,7 @@ bool FileSecurity::HasPermission(const std::wstring& file_path, * useful to determine whether the security descriptor has been modified and * provide a baseline sid */ -bool FileSecurity::GrantPermission(const std::wstring& file_path, +bool FileSecurity::AddPermission(const std::wstring& file_path, DWORD access_mask, PSID sid, PSECURITY_DESCRIPTOR* out_old_sd) { PACL old_dacl = nullptr; @@ -1124,7 +1124,7 @@ void ScopedFileAccess::Access() { // Check if we need to modify ACLs if (!FileSecurity::HasPermission(file_path_, desired_access_, current_user_sid_.get())) { - if (!FileSecurity::GrantPermission(file_path_, desired_access_, + if (!FileSecurity::AddPermission(file_path_, desired_access_, current_user_sid_.get(), &original_sd_)) { throw std::system_error(static_cast(::GetLastError()), diff --git a/src/utils.h b/src/utils.h index 87738d8..4537cb6 100644 --- a/src/utils.h +++ b/src/utils.h @@ -306,7 +306,7 @@ class FileSecurity { static bool HasPermission(const std::wstring& file_path, DWORD access_mask, PSID sid); - static bool GrantPermission(const std::wstring& file_path, + static bool AddPermission(const std::wstring& file_path, DWORD access_mask, PSID sid, PSECURITY_DESCRIPTOR* out_old_sd); From d8edb2bee36dd916ce4a695db882ae79b994f33f Mon Sep 17 00:00:00 2001 From: John Parent Date: Wed, 8 Apr 2026 17:07:19 -0400 Subject: [PATCH 3/4] Clarify method intent with rename Signed-off-by: John Parent --- src/utils.cxx | 8 ++++---- src/utils.h | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/utils.cxx b/src/utils.cxx index e3c766f..de749c7 100644 --- a/src/utils.cxx +++ b/src/utils.cxx @@ -1007,7 +1007,7 @@ bool FileSecurity::HasPermission(const std::wstring& file_path, * useful to determine whether the security descriptor has been modified and * provide a baseline sid */ -bool FileSecurity::AddPermission(const std::wstring& file_path, +bool FileSecurity::AddAccessControlEntry(const std::wstring& file_path, DWORD access_mask, PSID sid, PSECURITY_DESCRIPTOR* out_old_sd) { PACL old_dacl = nullptr; @@ -1049,7 +1049,7 @@ bool FileSecurity::AddPermission(const std::wstring& file_path, * \param file_path file to apply SD to * \param sd security descriptor to apply to file */ -bool FileSecurity::ApplyDescriptor(const std::wstring& file_path, +bool FileSecurity::SetAclFromDescriptor(const std::wstring& file_path, PSECURITY_DESCRIPTOR sd) { if (!sd) return false; @@ -1124,7 +1124,7 @@ void ScopedFileAccess::Access() { // Check if we need to modify ACLs if (!FileSecurity::HasPermission(file_path_, desired_access_, current_user_sid_.get())) { - if (!FileSecurity::AddPermission(file_path_, desired_access_, + if (!FileSecurity::AddAccessControlEntry(file_path_, desired_access_, current_user_sid_.get(), &original_sd_)) { throw std::system_error(static_cast(::GetLastError()), @@ -1166,7 +1166,7 @@ ScopedFileAccess::~ScopedFileAccess() { } if (acl_needs_revert_ && original_sd_) { - FileSecurity::ApplyDescriptor(file_path_, original_sd_); + FileSecurity::SetAclFromDescriptor(file_path_, original_sd_); ::LocalFree(original_sd_); } } diff --git a/src/utils.h b/src/utils.h index 4537cb6..87d25d9 100644 --- a/src/utils.h +++ b/src/utils.h @@ -306,11 +306,11 @@ class FileSecurity { static bool HasPermission(const std::wstring& file_path, DWORD access_mask, PSID sid); - static bool AddPermission(const std::wstring& file_path, + static bool AddAccessControlEntry(const std::wstring& file_path, DWORD access_mask, PSID sid, PSECURITY_DESCRIPTOR* out_old_sd); - static bool ApplyDescriptor(const std::wstring& file_path, + static bool SetAclFromDescriptor(const std::wstring& file_path, PSECURITY_DESCRIPTOR sd); // Retrieves file attributes (e.g., ReadOnly, Hidden). From d4e08d85cd63ea8b5f80f7f7747e725865460c20 Mon Sep 17 00:00:00 2001 From: John Parent Date: Wed, 8 Apr 2026 17:18:06 -0400 Subject: [PATCH 4/4] Use aclhasaccess Signed-off-by: John Parent --- src/utils.cxx | 6 +++--- src/utils.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/utils.cxx b/src/utils.cxx index de749c7..25809b4 100644 --- a/src/utils.cxx +++ b/src/utils.cxx @@ -966,7 +966,7 @@ ScopedSid FileSecurity::GetCurrentUserSid() { * \param sid user identifier to check for permissions in the context of * */ -bool FileSecurity::HasPermission(const std::wstring& file_path, +bool FileSecurity::AclHasAccess(const std::wstring& file_path, DWORD access_mask, PSID sid) { PACL dacl = nullptr; PSECURITY_DESCRIPTOR sd_raw = nullptr; @@ -1122,7 +1122,7 @@ void ScopedFileAccess::Access() { } // Check if we need to modify ACLs - if (!FileSecurity::HasPermission(file_path_, desired_access_, + if (!FileSecurity::AclHasAccess(file_path_, desired_access_, current_user_sid_.get())) { if (!FileSecurity::AddAccessControlEntry(file_path_, desired_access_, current_user_sid_.get(), @@ -1173,7 +1173,7 @@ ScopedFileAccess::~ScopedFileAccess() { bool ScopedFileAccess::IsAccessGranted() const { - return FileSecurity::HasPermission(file_path_, desired_access_, + return FileSecurity::AclHasAccess(file_path_, desired_access_, current_user_sid_.get()); } diff --git a/src/utils.h b/src/utils.h index 87d25d9..f0eb71b 100644 --- a/src/utils.h +++ b/src/utils.h @@ -303,7 +303,7 @@ class FileSecurity { static ScopedSid GetCurrentUserSid(); - static bool HasPermission(const std::wstring& file_path, DWORD access_mask, + static bool AclHasAccess(const std::wstring& file_path, DWORD access_mask, PSID sid); static bool AddAccessControlEntry(const std::wstring& file_path,