| 1 | /*++ |
| 2 | |
| 3 | Copyright (c) Microsoft. All rights reserved. |
| 4 | |
| 5 | Module Name: |
| 6 | |
| 7 | WslSecurity.cpp |
| 8 | |
| 9 | Abstract: |
| 10 | |
| 11 | This file contains WSL Core security function definitions. |
| 12 | |
| 13 | --*/ |
| 14 | |
| 15 | #include "precomp.h" |
| 16 | #include "WslSecurity.h" |
| 17 | |
| 18 | std::unique_ptr<wsl::windows::common::security::privilege_context> wsl::windows::common::security::AcquirePrivilege(_In_ LPCWSTR privilegeName) |
| 19 | { |
| 20 | // Open the token of the current process. |
| 21 | wil::unique_handle token; |
| 22 | THROW_IF_WIN32_BOOL_FALSE(::OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &token)); |
| 23 | |
| 24 | auto luid = EnableTokenPrivilege(token.get(), privilegeName); |
| 25 | return std::make_unique<wsl::windows::common::security::privilege_context>(std::move(token), luid); |
| 26 | } |
| 27 | |
| 28 | std::vector<std::unique_ptr<wsl::windows::common::security::privilege_context>> wsl::windows::common::security::AcquirePrivileges( |
| 29 | _In_ const std::vector<LPCWSTR>& privilegeNames) |
| 30 | { |
| 31 | std::vector<std::unique_ptr<wsl::windows::common::security::privilege_context>> context; |
| 32 | for (const auto* name : privilegeNames) |
| 33 | { |
| 34 | context.emplace_back(AcquirePrivilege(name)); |
| 35 | } |
| 36 | |
| 37 | return context; |
| 38 | } |
| 39 | |
| 40 | void wsl::windows::common::security::ApplyProcessMitigationPolicies() |
| 41 | { |
| 42 | PROCESS_MITIGATION_DYNAMIC_CODE_POLICY codePolicy{}; |
| 43 | codePolicy.AllowRemoteDowngrade = false; |
| 44 | codePolicy.AllowThreadOptOut = false; |
| 45 | codePolicy.ProhibitDynamicCode = true; |
| 46 | LOG_IF_WIN32_BOOL_FALSE(SetProcessMitigationPolicy(ProcessDynamicCodePolicy, &codePolicy, sizeof(codePolicy))); |
| 47 | |
| 48 | // Note: Enabling PROCESS_MITIGATION_SYSTEM_CALL_DISABLE_POLICY::DisallowWin32kSystemCalls |
| 49 | // breaks the service initialization logic (CoInitializeSecurity fails). |
| 50 | |
| 51 | PROCESS_MITIGATION_FONT_DISABLE_POLICY fontPolicy{}; |
| 52 | fontPolicy.DisableNonSystemFonts = true; |
| 53 | LOG_IF_WIN32_BOOL_FALSE(SetProcessMitigationPolicy(ProcessFontDisablePolicy, &fontPolicy, sizeof(fontPolicy))); |
| 54 | |
| 55 | PROCESS_MITIGATION_IMAGE_LOAD_POLICY loadPolicy{}; |
| 56 | loadPolicy.PreferSystem32Images = true; |
| 57 | LOG_IF_WIN32_BOOL_FALSE(SetProcessMitigationPolicy(ProcessImageLoadPolicy, &loadPolicy, sizeof(loadPolicy))); |
| 58 | } |
| 59 | |
| 60 | SECURITY_DESCRIPTOR wsl::windows::common::security::CreateSecurityDescriptor(_In_ PSID userSid) |
| 61 | { |
| 62 | SECURITY_DESCRIPTOR sd{}; |
| 63 | THROW_IF_WIN32_BOOL_FALSE(InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION)); |
| 64 | THROW_IF_WIN32_BOOL_FALSE(SetSecurityDescriptorOwner(&sd, userSid, false)); |
| 65 | return sd; |
| 66 | } |
| 67 | |
| 68 | wil::unique_handle wsl::windows::common::security::CreateRestrictedToken(_In_ HANDLE token) |
| 69 | { |
| 70 | // N.B. These operations must be done while impersonating the user to avoid |
| 71 | // accidentally raising the integrity level. |
| 72 | auto runAsUser = wil::impersonate_token(token); |
| 73 | |
| 74 | // Get the thread token with appropriate access rights. |
| 75 | wil::unique_handle newToken{}; |
| 76 | THROW_IF_WIN32_BOOL_FALSE(::OpenThreadToken( |
| 77 | ::GetCurrentThread(), (TOKEN_DUPLICATE | TOKEN_QUERY | TOKEN_ADJUST_DEFAULT | TOKEN_ASSIGN_PRIMARY), TRUE, &newToken)); |
| 78 | |
| 79 | // Create a restricted token with only the SeChangeNotifyPrivilege privilege. |
| 80 | wil::unique_handle restrictedToken{}; |
| 81 | THROW_IF_WIN32_BOOL_FALSE(::CreateRestrictedToken(newToken.get(), DISABLE_MAX_PRIVILEGE, 0, NULL, 0, NULL, 0, NULL, &restrictedToken)); |
| 82 | |
| 83 | // Drop the token down to medium integrity level. |
| 84 | auto [sid, sidBuffer] = wsl::windows::common::security::CreateSid(SECURITY_MANDATORY_LABEL_AUTHORITY, SECURITY_MANDATORY_MEDIUM_RID); |
| 85 | TOKEN_MANDATORY_LABEL tokenLabel{}; |
| 86 | tokenLabel.Label.Attributes = SE_GROUP_INTEGRITY; |
| 87 | tokenLabel.Label.Sid = sid; |
| 88 | THROW_IF_WIN32_BOOL_FALSE(::SetTokenInformation(restrictedToken.get(), TokenIntegrityLevel, &tokenLabel, sizeof(tokenLabel))); |
| 89 | return restrictedToken; |
| 90 | } |
| 91 | |
| 92 | void wsl::windows::common::security::ConfigureForCOMImpersonation(IUnknown* Instance) |
| 93 | { |
| 94 | wil::com_ptr_nothrow<IClientSecurity> clientSecurity; |
| 95 | THROW_IF_FAILED(Instance->QueryInterface(IID_PPV_ARGS(&clientSecurity))); |
| 96 | |
| 97 | // Get the current proxy blanket settings. |
| 98 | DWORD authnSvc, authzSvc, authnLvl, capabilites; |
| 99 | THROW_IF_FAILED(clientSecurity->QueryBlanket(Instance, &authnSvc, &authzSvc, NULL, &authnLvl, NULL, NULL, &capabilites)); |
| 100 | |
| 101 | // Make sure that dynamic cloaking is used. |
| 102 | WI_ClearFlag(capabilites, EOAC_STATIC_CLOAKING); |
| 103 | WI_SetFlag(capabilites, EOAC_DYNAMIC_CLOAKING); |
| 104 | THROW_IF_FAILED(clientSecurity->SetBlanket(Instance, authnSvc, authzSvc, NULL, authnLvl, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, capabilites)); |
| 105 | } |
| 106 | |
| 107 | LUID wsl::windows::common::security::EnableTokenPrivilege(_Inout_ HANDLE token, _In_ LPCWSTR privilegeName) |
| 108 | { |
| 109 | // Convert privilege name to an LUID. |
| 110 | LUID luid{}; |
| 111 | THROW_IF_WIN32_BOOL_FALSE(::LookupPrivilegeValueW(nullptr, privilegeName, &luid)); |
| 112 | |
| 113 | TOKEN_PRIVILEGES newState{}; |
| 114 | newState.PrivilegeCount = 1; |
| 115 | newState.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; |
| 116 | newState.Privileges[0].Luid = luid; |
| 117 | THROW_IF_WIN32_BOOL_FALSE(::AdjustTokenPrivileges(token, FALSE, &newState, 0, nullptr, nullptr)); |
| 118 | |
| 119 | return luid; |
| 120 | } |
| 121 | |
| 122 | DWORD wsl::windows::common::security::GetUserBasicIntegrityLevel(_In_ HANDLE token) |
| 123 | { |
| 124 | // Get the integrity level. |
| 125 | const auto label = wil::get_token_information<TOKEN_MANDATORY_LABEL>(token); |
| 126 | DWORD BasicIntegrityLevel = |
| 127 | *GetSidSubAuthority(label->Label.Sid, static_cast<UCHAR>(*::GetSidSubAuthorityCount(label->Label.Sid) - 1)); |
| 128 | |
| 129 | // Convert the range of medium integrity level to a single level. |
| 130 | if ((BasicIntegrityLevel >= SECURITY_MANDATORY_MEDIUM_RID) && (BasicIntegrityLevel < SECURITY_MANDATORY_HIGH_RID)) |
| 131 | { |
| 132 | BasicIntegrityLevel = SECURITY_MANDATORY_MEDIUM_RID; |
| 133 | } |
| 134 | |
| 135 | return BasicIntegrityLevel; |
| 136 | } |
| 137 | |
| 138 | bool wsl::windows::common::security::IsTokenElevated(_In_ HANDLE token) |
| 139 | { |
| 140 | return (GetUserBasicIntegrityLevel(token) == SECURITY_MANDATORY_HIGH_RID); |
| 141 | } |
| 142 | |
| 143 | wil::unique_handle wsl::windows::common::security::GetUserToken(_In_ TOKEN_TYPE tokenType, _In_ RPC_BINDING_HANDLE handle) |
| 144 | { |
| 145 | wil::unique_handle contextToken; |
| 146 | |
| 147 | // Start by impersonating the caller and getting their token-user data out. |
| 148 | { |
| 149 | std::variant<int, wil::unique_coreverttoself_call, unique_revert_to_self> runAsClient; |
| 150 | |
| 151 | if (handle == nullptr) |
| 152 | { |
| 153 | runAsClient = wil::CoImpersonateClient(); |
| 154 | } |
| 155 | else |
| 156 | { |
| 157 | runAsClient = RpcImpersonateCaller(handle); |
| 158 | } |
| 159 | |
| 160 | THROW_IF_WIN32_BOOL_FALSE(::OpenThreadToken(GetCurrentThread(), TOKEN_DUPLICATE | TOKEN_READ, TRUE, &contextToken)); |
| 161 | } |
| 162 | |
| 163 | wil::unique_handle newToken; |
| 164 | THROW_IF_WIN32_BOOL_FALSE(::DuplicateTokenEx( |
| 165 | contextToken.get(), |
| 166 | TOKEN_DUPLICATE | TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES | TOKEN_ADJUST_DEFAULT, |
| 167 | nullptr, |
| 168 | SecurityImpersonation, |
| 169 | tokenType, |
| 170 | &newToken)); |
| 171 | |
| 172 | // If the token integrity level is system, reduce it to high integrity level. The VM worker process runs at |
| 173 | // high integrity level and objects created with a higher integrity level token may be inaccessible. |
| 174 | if (GetUserBasicIntegrityLevel(newToken.get()) == SECURITY_MANDATORY_SYSTEM_RID) |
| 175 | { |
| 176 | auto [sid, sidBuffer] = wsl::windows::common::security::CreateSid(SECURITY_MANDATORY_LABEL_AUTHORITY, SECURITY_MANDATORY_HIGH_RID); |
| 177 | TOKEN_MANDATORY_LABEL tokenLabel{}; |
| 178 | tokenLabel.Label.Attributes = SE_GROUP_INTEGRITY; |
| 179 | tokenLabel.Label.Sid = sid; |
| 180 | THROW_IF_WIN32_BOOL_FALSE(::SetTokenInformation(newToken.get(), TokenIntegrityLevel, &tokenLabel, sizeof(tokenLabel))); |
| 181 | } |
| 182 | |
| 183 | return newToken; |
| 184 | } |
| 185 | |
| 186 | bool wsl::windows::common::security::IsTokenLocalSystem(_In_opt_ HANDLE token) |
| 187 | { |
| 188 | auto [sid, sidBuffer] = wsl::windows::common::security::CreateSid(SECURITY_NT_AUTHORITY, SECURITY_LOCAL_SYSTEM_RID); |
| 189 | |
| 190 | BOOL member{}; |
| 191 | THROW_IF_WIN32_BOOL_FALSE(::CheckTokenMembership(token, sid, &member)); |
| 192 | |
| 193 | return member ? true : false; |
| 194 | } |
| 195 | |
| 196 | wsl::windows::common::security::unique_revert_to_self wsl::windows::common::security::RpcImpersonateCaller(_In_ RPC_BINDING_HANDLE handle) |
| 197 | { |
| 198 | THROW_IF_WIN32_ERROR(static_cast<DWORD>(RpcImpersonateClient(handle))); |
| 199 | |
| 200 | return {}; |
| 201 | } |