|
4 | 4 | * SPDX-License-Identifier: (Apache-2.0 OR MIT) |
5 | 5 | */ |
6 | 6 | #include "utils.h" |
| 7 | +#include <aclapi.h> |
| 8 | +#include <accctrl.h> |
7 | 9 | #include <errhandlingapi.h> |
8 | 10 | #include <fileapi.h> |
9 | 11 | #include <cstdio> |
|
12 | 14 | #include <minwinbase.h> |
13 | 15 | #include <minwindef.h> |
14 | 16 | #include <processenv.h> |
| 17 | +#include <processthreadsapi.h> |
| 18 | +#include <securitybaseapi.h> |
15 | 19 | #include <stringapiset.h> |
16 | 20 | #include <strsafe.h> |
17 | 21 | #include <winbase.h> |
@@ -926,6 +930,253 @@ char* findstr(char* search_str, const char* substr, size_t size) { |
926 | 930 | return nullptr; |
927 | 931 | } |
928 | 932 |
|
| 933 | +ScopedSid FileSecurity::GetCurrentUserSid() { |
| 934 | + HANDLE token_handle = nullptr; |
| 935 | + if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, |
| 936 | + &token_handle)) { |
| 937 | + return nullptr; |
| 938 | + } |
| 939 | + std::unique_ptr<void, decltype(&::CloseHandle)> const scoped_token( |
| 940 | + token_handle, &::CloseHandle); |
| 941 | + |
| 942 | + DWORD buffer_size = 0; |
| 943 | + ::GetTokenInformation(token_handle, TokenUser, nullptr, 0, &buffer_size); |
| 944 | + |
| 945 | + std::vector<char> buffer(buffer_size); |
| 946 | + auto* token_user = reinterpret_cast<PTOKEN_USER>(buffer.data()); |
| 947 | + |
| 948 | + if (!::GetTokenInformation(token_handle, TokenUser, token_user, buffer_size, |
| 949 | + &buffer_size)) { |
| 950 | + return nullptr; |
| 951 | + } |
| 952 | + |
| 953 | + DWORD const sid_len = ::GetLengthSid(token_user->User.Sid); |
| 954 | + void* sid_copy = std::malloc(sid_len); |
| 955 | + if (sid_copy) { |
| 956 | + ::CopySid(sid_len, sid_copy, token_user->User.Sid); |
| 957 | + return ScopedSid(sid_copy); |
| 958 | + } |
| 959 | + return nullptr; |
| 960 | +} |
| 961 | + |
| 962 | +/** |
| 963 | + * Determines whether a given file has a given set of file permissions |
| 964 | + * \param file to check permissions for |
| 965 | + * \param access_mask type of permissions to check for |
| 966 | + * \param sid user identifier to check for permissions in the context of |
| 967 | + * |
| 968 | + */ |
| 969 | +bool FileSecurity::AclHasAccess(const std::wstring& file_path, |
| 970 | + DWORD access_mask, PSID sid) { |
| 971 | + PACL dacl = nullptr; |
| 972 | + PSECURITY_DESCRIPTOR sd_raw = nullptr; |
| 973 | + DWORD result = ::GetNamedSecurityInfoW(file_path.c_str(), SE_FILE_OBJECT, |
| 974 | + DACL_SECURITY_INFORMATION, nullptr, |
| 975 | + nullptr, &dacl, nullptr, &sd_raw); |
| 976 | + |
| 977 | + if (result != ERROR_SUCCESS) |
| 978 | + return false; |
| 979 | + ScopedLocalInfo const scoped_sd(sd_raw); |
| 980 | + |
| 981 | + TRUSTEE_W trustee = {nullptr}; |
| 982 | + trustee.TrusteeForm = TRUSTEE_IS_SID; |
| 983 | + trustee.TrusteeType = TRUSTEE_IS_USER; |
| 984 | + trustee.ptstrName = static_cast<LPWSTR>(sid); |
| 985 | + |
| 986 | + ACCESS_MASK effective_rights = 0; |
| 987 | + result = ::GetEffectiveRightsFromAclW(dacl, &trustee, &effective_rights); |
| 988 | + |
| 989 | + if (result != ERROR_SUCCESS) |
| 990 | + return false; |
| 991 | + |
| 992 | + if ((access_mask & GENERIC_WRITE) && (effective_rights & FILE_WRITE_DATA)) |
| 993 | + return true; |
| 994 | + if ((access_mask & GENERIC_READ) && (effective_rights & FILE_READ_DATA)) |
| 995 | + return true; |
| 996 | + if ((access_mask & GENERIC_ALL) && (effective_rights & FILE_ALL_ACCESS)) |
| 997 | + return true; |
| 998 | + |
| 999 | + return (effective_rights & access_mask) == access_mask; |
| 1000 | +} |
| 1001 | + |
| 1002 | +/** |
| 1003 | + * \param file_path path to the file for which we are granting a permission |
| 1004 | + * \param access_mask the bitmask for the ACE permissions being requested |
| 1005 | + * \param sid pointer to the security identifier for a given trustee (typically the current user) |
| 1006 | + * \param out_old_sid output param, pointer to variable containing pointer to the pre-modified DACL |
| 1007 | + * useful to determine whether the security descriptor has been modified and |
| 1008 | + * provide a baseline sid |
| 1009 | + */ |
| 1010 | +bool FileSecurity::AddAccessControlEntry(const std::wstring& file_path, |
| 1011 | + DWORD access_mask, PSID sid, |
| 1012 | + PSECURITY_DESCRIPTOR* out_old_sd) { |
| 1013 | + PACL old_dacl = nullptr; |
| 1014 | + PSECURITY_DESCRIPTOR sd_raw = nullptr; |
| 1015 | + |
| 1016 | + DWORD result = ::GetNamedSecurityInfoW( |
| 1017 | + file_path.c_str(), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, |
| 1018 | + nullptr, &old_dacl, nullptr, &sd_raw); |
| 1019 | + |
| 1020 | + if (result != ERROR_SUCCESS) |
| 1021 | + return false; |
| 1022 | + |
| 1023 | + if (out_old_sd) |
| 1024 | + *out_old_sd = sd_raw; |
| 1025 | + ScopedLocalInfo const temp_sd_wrapper(out_old_sd ? nullptr : sd_raw); |
| 1026 | + |
| 1027 | + EXPLICIT_ACCESS_W ea = {0}; |
| 1028 | + ea.grfAccessPermissions = access_mask; |
| 1029 | + ea.grfAccessMode = GRANT_ACCESS; |
| 1030 | + ea.grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; |
| 1031 | + ea.Trustee.TrusteeForm = TRUSTEE_IS_SID; |
| 1032 | + ea.Trustee.TrusteeType = TRUSTEE_IS_USER; |
| 1033 | + ea.Trustee.ptstrName = static_cast<LPWSTR>(sid); |
| 1034 | + |
| 1035 | + PACL new_dacl = nullptr; |
| 1036 | + result = ::SetEntriesInAclW(1, &ea, old_dacl, &new_dacl); |
| 1037 | + if (result != ERROR_SUCCESS) |
| 1038 | + return false; |
| 1039 | + |
| 1040 | + ScopedLocalInfo const scoped_new_dacl(new_dacl); |
| 1041 | + result = ::SetNamedSecurityInfoW(const_cast<LPWSTR>(file_path.c_str()), |
| 1042 | + SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, |
| 1043 | + nullptr, nullptr, new_dacl, nullptr); |
| 1044 | + return (result == ERROR_SUCCESS); |
| 1045 | +} |
| 1046 | + |
| 1047 | +/** |
| 1048 | + * Applies security descriptor to file |
| 1049 | + * \param file_path file to apply SD to |
| 1050 | + * \param sd security descriptor to apply to file |
| 1051 | + */ |
| 1052 | +bool FileSecurity::SetAclFromDescriptor(const std::wstring& file_path, |
| 1053 | + PSECURITY_DESCRIPTOR sd) { |
| 1054 | + if (!sd) |
| 1055 | + return false; |
| 1056 | + BOOL present = FALSE; |
| 1057 | + BOOL defaulted = FALSE; |
| 1058 | + PACL dacl = nullptr; |
| 1059 | + if (!::GetSecurityDescriptorDacl(sd, &present, &dacl, &defaulted) || |
| 1060 | + !present || !dacl) |
| 1061 | + return false; |
| 1062 | + |
| 1063 | + return ::SetNamedSecurityInfoW(const_cast<LPWSTR>(file_path.c_str()), |
| 1064 | + SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, |
| 1065 | + nullptr, nullptr, dacl, |
| 1066 | + nullptr) == ERROR_SUCCESS; |
| 1067 | +} |
| 1068 | + |
| 1069 | +/** |
| 1070 | + * Obtain current file attributes for file indicated by file path |
| 1071 | + * \param file_path file to obtain permissions for |
| 1072 | + * \param out_attr output parameter to hold permissions |
| 1073 | + */ |
| 1074 | +bool FileSecurity::GetAttributes(const std::wstring& file_path, |
| 1075 | + DWORD* out_attr) { |
| 1076 | + DWORD const attr = ::GetFileAttributesW(file_path.c_str()); |
| 1077 | + if (attr == INVALID_FILE_ATTRIBUTES) |
| 1078 | + return false; |
| 1079 | + if (out_attr) |
| 1080 | + *out_attr = attr; |
| 1081 | + return true; |
| 1082 | +} |
| 1083 | + |
| 1084 | +/** |
| 1085 | + * Set file attribute attr on file indicated by file_path |
| 1086 | + * \param file_path file to set attributes for |
| 1087 | + * \param attr attributes to obtain |
| 1088 | + */ |
| 1089 | +bool FileSecurity::SetAttributes(const std::wstring& file_path, DWORD attr) { |
| 1090 | + return ::SetFileAttributesW(file_path.c_str(), attr) != 0; |
| 1091 | +} |
| 1092 | + |
| 1093 | +/** |
| 1094 | + * Construct FileAccess object |
| 1095 | + * \param file_path file on which we want to obtain permissions |
| 1096 | + * \param desired_acess access control permissions we want to obtain for the file |
| 1097 | + */ |
| 1098 | +ScopedFileAccess::ScopedFileAccess(std::wstring file_path, DWORD desired_access) |
| 1099 | + : file_path_(std::move(file_path)), |
| 1100 | + desired_access_(desired_access), |
| 1101 | + original_sd_(nullptr), |
| 1102 | + current_user_sid_(nullptr), |
| 1103 | + acl_needs_revert_(false), |
| 1104 | + original_attributes_(0), |
| 1105 | + attributes_changed_(false) { |
| 1106 | +} |
| 1107 | + |
| 1108 | +/** |
| 1109 | + * Obtain permissions established by constructor |
| 1110 | + * |
| 1111 | + */ |
| 1112 | +void ScopedFileAccess::Access() { |
| 1113 | + |
| 1114 | + |
| 1115 | + // We must ensure we have permissions *first* before we try to |
| 1116 | + // change the file attributes in Phase 2. |
| 1117 | + |
| 1118 | + current_user_sid_ = FileSecurity::GetCurrentUserSid(); |
| 1119 | + if (!current_user_sid_) { |
| 1120 | + throw std::system_error(static_cast<int>(::GetLastError()), |
| 1121 | + std::system_category(), "Failed to get SID"); |
| 1122 | + } |
| 1123 | + |
| 1124 | + // Check if we need to modify ACLs |
| 1125 | + if (!FileSecurity::AclHasAccess(file_path_, desired_access_, |
| 1126 | + current_user_sid_.get())) { |
| 1127 | + if (!FileSecurity::AddAccessControlEntry(file_path_, desired_access_, |
| 1128 | + current_user_sid_.get(), |
| 1129 | + &original_sd_)) { |
| 1130 | + throw std::system_error(static_cast<int>(::GetLastError()), |
| 1131 | + std::system_category(), |
| 1132 | + "Failed to grant ACL"); |
| 1133 | + } |
| 1134 | + acl_needs_revert_ = true; |
| 1135 | + } |
| 1136 | + |
| 1137 | + if (FileSecurity::GetAttributes(file_path_, &original_attributes_)) { |
| 1138 | + if (original_attributes_ & FILE_ATTRIBUTE_READONLY) { |
| 1139 | + // Remove the Read-Only bit |
| 1140 | + DWORD const new_attributes = |
| 1141 | + original_attributes_ & ~FILE_ATTRIBUTE_READONLY; |
| 1142 | + |
| 1143 | + if (FileSecurity::SetAttributes(file_path_, new_attributes)) { |
| 1144 | + attributes_changed_ = true; |
| 1145 | + } else { |
| 1146 | + // If we fail to remove Read-Only, we might still fail to write later. |
| 1147 | + // We throw here to be safe and consistent. |
| 1148 | + throw std::system_error(static_cast<int>(::GetLastError()), |
| 1149 | + std::system_category(), |
| 1150 | + "Failed to remove Read-Only attribute"); |
| 1151 | + } |
| 1152 | + } |
| 1153 | + } else { |
| 1154 | + throw std::system_error(static_cast<int>(::GetLastError()), |
| 1155 | + std::system_category(), |
| 1156 | + "Failed to get file attributes"); |
| 1157 | + } |
| 1158 | +} |
| 1159 | + |
| 1160 | +ScopedFileAccess::~ScopedFileAccess() { |
| 1161 | + // We must restore attributes *before* we revert ACLs, because reverting ACLs |
| 1162 | + // might remove our permission to write attributes. |
| 1163 | + if (attributes_changed_) { |
| 1164 | + // We ignore errors in destructors to prevent termination |
| 1165 | + FileSecurity::SetAttributes(file_path_, original_attributes_); |
| 1166 | + } |
| 1167 | + |
| 1168 | + if (acl_needs_revert_ && original_sd_) { |
| 1169 | + FileSecurity::SetAclFromDescriptor(file_path_, original_sd_); |
| 1170 | + ::LocalFree(original_sd_); |
| 1171 | + } |
| 1172 | +} |
| 1173 | + |
| 1174 | +bool ScopedFileAccess::IsAccessGranted() const { |
| 1175 | + |
| 1176 | + return FileSecurity::AclHasAccess(file_path_, desired_access_, |
| 1177 | + current_user_sid_.get()); |
| 1178 | +} |
| 1179 | + |
929 | 1180 | NameTooLongError::NameTooLongError(char const* const message) |
930 | 1181 | : std::runtime_error(message) {} |
931 | 1182 |
|
|
0 commit comments