// Copyright (c) 2016 Ryan Prichard // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to // deal in the Software without restriction, including without limitation the // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. #include "WindowsSecurity.h" #include #include "DebugClient.h" #include "OsModule.h" #include "OwnedHandle.h" #include "StringBuilder.h" #include "WindowsVersion.h" #include "WinptyAssert.h" #include "WinptyException.h" namespace { struct LocalFreer { void operator()(void *ptr) { if (ptr != nullptr) { LocalFree(reinterpret_cast(ptr)); } } }; typedef std::unique_ptr PointerLocal; template SecurityItem localItem(typename T::type v) { typedef typename T::type P; struct Impl : SecurityItem::Impl { P m_v; Impl(P v) : m_v(v) {} virtual ~Impl() { LocalFree(reinterpret_cast(m_v)); } }; return SecurityItem(v, std::unique_ptr(new Impl { v })); } Sid allocatedSid(PSID v) { struct Impl : Sid::Impl { PSID m_v; Impl(PSID v) : m_v(v) {} virtual ~Impl() { if (m_v != nullptr) { FreeSid(m_v); } } }; return Sid(v, std::unique_ptr(new Impl { v })); } } // anonymous namespace // Returns a handle to the thread's effective security token. If the thread // is impersonating another user, its token is returned, and otherwise, the // process' security token is opened. The handle is opened with TOKEN_QUERY. static OwnedHandle openSecurityTokenForQuery() { HANDLE token = nullptr; // It is unclear to me whether OpenAsSelf matters for winpty, or what the // most appropriate value is. if (!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, /*OpenAsSelf=*/FALSE, &token)) { if (GetLastError() != ERROR_NO_TOKEN) { throwWindowsError(L"OpenThreadToken failed"); } if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) { throwWindowsError(L"OpenProcessToken failed"); } } ASSERT(token != nullptr && "OpenThreadToken/OpenProcessToken token is NULL"); return OwnedHandle(token); } // Returns the TokenOwner of the thread's effective security token. Sid getOwnerSid() { struct Impl : Sid::Impl { std::unique_ptr buffer; }; OwnedHandle token = openSecurityTokenForQuery(); DWORD actual = 0; BOOL success; success = GetTokenInformation(token.get(), TokenOwner, nullptr, 0, &actual); if (success) { throwWinptyException(L"getOwnerSid: GetTokenInformation: " L"expected ERROR_INSUFFICIENT_BUFFER"); } else if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { throwWindowsError(L"getOwnerSid: GetTokenInformation: " L"expected ERROR_INSUFFICIENT_BUFFER"); } std::unique_ptr impl(new Impl); impl->buffer = std::unique_ptr(new char[actual]); success = GetTokenInformation(token.get(), TokenOwner, impl->buffer.get(), actual, &actual); if (!success) { throwWindowsError(L"getOwnerSid: GetTokenInformation"); } TOKEN_OWNER tmp; ASSERT(actual >= sizeof(tmp)); std::copy( impl->buffer.get(), impl->buffer.get() + sizeof(tmp), reinterpret_cast(&tmp)); return Sid(tmp.Owner, std::move(impl)); } Sid wellKnownSid( const wchar_t *debuggingName, SID_IDENTIFIER_AUTHORITY authority, BYTE authorityCount, DWORD subAuthority0/*=0*/, DWORD subAuthority1/*=0*/) { PSID psid = nullptr; if (!AllocateAndInitializeSid(&authority, authorityCount, subAuthority0, subAuthority1, 0, 0, 0, 0, 0, 0, &psid)) { const auto err = GetLastError(); const auto msg = std::wstring(L"wellKnownSid: error getting ") + debuggingName + L" SID"; throwWindowsError(msg.c_str(), err); } return allocatedSid(psid); } Sid builtinAdminsSid() { // S-1-5-32-544 SID_IDENTIFIER_AUTHORITY authority = { SECURITY_NT_AUTHORITY }; return wellKnownSid(L"BUILTIN\\Administrators group", authority, 2, SECURITY_BUILTIN_DOMAIN_RID, // 32 DOMAIN_ALIAS_RID_ADMINS); // 544 } Sid localSystemSid() { // S-1-5-18 SID_IDENTIFIER_AUTHORITY authority = { SECURITY_NT_AUTHORITY }; return wellKnownSid(L"LocalSystem account", authority, 1, SECURITY_LOCAL_SYSTEM_RID); // 18 } Sid everyoneSid() { // S-1-1-0 SID_IDENTIFIER_AUTHORITY authority = { SECURITY_WORLD_SID_AUTHORITY }; return wellKnownSid(L"Everyone account", authority, 1, SECURITY_WORLD_RID); // 0 } static SecurityDescriptor finishSecurityDescriptor( size_t daclEntryCount, EXPLICIT_ACCESSW *daclEntries, Acl &outAcl) { { PACL aclRaw = nullptr; DWORD aclError = SetEntriesInAclW(daclEntryCount, daclEntries, nullptr, &aclRaw); if (aclError != ERROR_SUCCESS) { WStringBuilder sb(64); sb << L"finishSecurityDescriptor: " << L"SetEntriesInAcl failed: " << aclError; throwWinptyException(sb.c_str()); } outAcl = localItem(aclRaw); } const PSECURITY_DESCRIPTOR sdRaw = reinterpret_cast( LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH)); if (sdRaw == nullptr) { throwWinptyException(L"finishSecurityDescriptor: LocalAlloc failed"); } SecurityDescriptor sd = localItem(sdRaw); if (!InitializeSecurityDescriptor(sdRaw, SECURITY_DESCRIPTOR_REVISION)) { throwWindowsError( L"finishSecurityDescriptor: InitializeSecurityDescriptor"); } if (!SetSecurityDescriptorDacl(sdRaw, TRUE, outAcl.get(), FALSE)) { throwWindowsError( L"finishSecurityDescriptor: SetSecurityDescriptorDacl"); } return std::move(sd); } // Create a security descriptor that grants full control to the local system // account, built-in administrators, and the owner. SecurityDescriptor createPipeSecurityDescriptorOwnerFullControl() { struct Impl : SecurityDescriptor::Impl { Sid localSystem; Sid builtinAdmins; Sid owner; std::array daclEntries = {}; Acl dacl; SecurityDescriptor value; }; std::unique_ptr impl(new Impl); impl->localSystem = localSystemSid(); impl->builtinAdmins = builtinAdminsSid(); impl->owner = getOwnerSid(); for (auto &ea : impl->daclEntries) { ea.grfAccessPermissions = GENERIC_ALL; ea.grfAccessMode = SET_ACCESS; ea.grfInheritance = NO_INHERITANCE; ea.Trustee.TrusteeForm = TRUSTEE_IS_SID; } impl->daclEntries[0].Trustee.ptstrName = reinterpret_cast(impl->localSystem.get()); impl->daclEntries[1].Trustee.ptstrName = reinterpret_cast(impl->builtinAdmins.get()); impl->daclEntries[2].Trustee.ptstrName = reinterpret_cast(impl->owner.get()); impl->value = finishSecurityDescriptor( impl->daclEntries.size(), impl->daclEntries.data(), impl->dacl); const auto retValue = impl->value.get(); return SecurityDescriptor(retValue, std::move(impl)); } SecurityDescriptor createPipeSecurityDescriptorOwnerFullControlEveryoneWrite() { struct Impl : SecurityDescriptor::Impl { Sid localSystem; Sid builtinAdmins; Sid owner; Sid everyone; std::array daclEntries = {}; Acl dacl; SecurityDescriptor value; }; std::unique_ptr impl(new Impl); impl->localSystem = localSystemSid(); impl->builtinAdmins = builtinAdminsSid(); impl->owner = getOwnerSid(); impl->everyone = everyoneSid(); for (auto &ea : impl->daclEntries) { ea.grfAccessPermissions = GENERIC_ALL; ea.grfAccessMode = SET_ACCESS; ea.grfInheritance = NO_INHERITANCE; ea.Trustee.TrusteeForm = TRUSTEE_IS_SID; } impl->daclEntries[0].Trustee.ptstrName = reinterpret_cast(impl->localSystem.get()); impl->daclEntries[1].Trustee.ptstrName = reinterpret_cast(impl->builtinAdmins.get()); impl->daclEntries[2].Trustee.ptstrName = reinterpret_cast(impl->owner.get()); impl->daclEntries[3].Trustee.ptstrName = reinterpret_cast(impl->everyone.get()); // Avoid using FILE_GENERIC_WRITE because it includes FILE_APPEND_DATA, // which is equal to FILE_CREATE_PIPE_INSTANCE. Instead, include all the // flags that comprise FILE_GENERIC_WRITE, except for the one. impl->daclEntries[3].grfAccessPermissions = FILE_GENERIC_READ | FILE_WRITE_ATTRIBUTES | FILE_WRITE_DATA | FILE_WRITE_EA | STANDARD_RIGHTS_WRITE | SYNCHRONIZE; impl->value = finishSecurityDescriptor( impl->daclEntries.size(), impl->daclEntries.data(), impl->dacl); const auto retValue = impl->value.get(); return SecurityDescriptor(retValue, std::move(impl)); } SecurityDescriptor getObjectSecurityDescriptor(HANDLE handle) { PACL dacl = nullptr; PSECURITY_DESCRIPTOR sd = nullptr; const DWORD errCode = GetSecurityInfo(handle, SE_KERNEL_OBJECT, OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION, nullptr, nullptr, &dacl, nullptr, &sd); if (errCode != ERROR_SUCCESS) { throwWindowsError(L"GetSecurityInfo failed"); } return localItem(sd); } // The (SID/SD)<->string conversion APIs are useful for testing/debugging, so // create convenient accessor functions for them. They're too slow for // ordinary use. The APIs exist in XP and up, but the MinGW headers only // declare the SID<->string APIs, not the SD APIs. MinGW also gets the // prototype wrong for ConvertStringSidToSidW (LPWSTR instead of LPCWSTR) and // requires WINVER to be defined. MSVC and MinGW-w64 get everything right, but // for consistency, use LoadLibrary/GetProcAddress for all four APIs. typedef BOOL WINAPI ConvertStringSidToSidW_t( LPCWSTR StringSid, PSID *Sid); typedef BOOL WINAPI ConvertSidToStringSidW_t( PSID Sid, LPWSTR *StringSid); typedef BOOL WINAPI ConvertStringSecurityDescriptorToSecurityDescriptorW_t( LPCWSTR StringSecurityDescriptor, DWORD StringSDRevision, PSECURITY_DESCRIPTOR *SecurityDescriptor, PULONG SecurityDescriptorSize); typedef BOOL WINAPI ConvertSecurityDescriptorToStringSecurityDescriptorW_t( PSECURITY_DESCRIPTOR SecurityDescriptor, DWORD RequestedStringSDRevision, SECURITY_INFORMATION SecurityInformation, LPWSTR *StringSecurityDescriptor, PULONG StringSecurityDescriptorLen); #define GET_MODULE_PROC(mod, funcName) \ const auto p##funcName = \ reinterpret_cast( \ mod.proc(#funcName)); \ if (p##funcName == nullptr) { \ throwWinptyException( \ L"" L ## #funcName L" API is missing from ADVAPI32.DLL"); \ } const DWORD kSDDL_REVISION_1 = 1; std::wstring sidToString(PSID sid) { OsModule advapi32(L"advapi32.dll"); GET_MODULE_PROC(advapi32, ConvertSidToStringSidW); wchar_t *sidString = NULL; BOOL success = pConvertSidToStringSidW(sid, &sidString); if (!success) { throwWindowsError(L"ConvertSidToStringSidW failed"); } PointerLocal freer(sidString); return std::wstring(sidString); } Sid stringToSid(const std::wstring &str) { // Cast the string from const wchar_t* to LPWSTR because the function is // incorrectly prototyped in the MinGW sddl.h header. The API does not // modify the string -- it is correctly prototyped as taking LPCWSTR in // MinGW-w64, MSVC, and MSDN. OsModule advapi32(L"advapi32.dll"); GET_MODULE_PROC(advapi32, ConvertStringSidToSidW); PSID psid = nullptr; BOOL success = pConvertStringSidToSidW(const_cast(str.c_str()), &psid); if (!success) { const auto err = GetLastError(); throwWindowsError( (std::wstring(L"ConvertStringSidToSidW failed on \"") + str + L'"').c_str(), err); } return localItem(psid); } SecurityDescriptor stringToSd(const std::wstring &str) { OsModule advapi32(L"advapi32.dll"); GET_MODULE_PROC(advapi32, ConvertStringSecurityDescriptorToSecurityDescriptorW); PSECURITY_DESCRIPTOR desc = nullptr; if (!pConvertStringSecurityDescriptorToSecurityDescriptorW( str.c_str(), kSDDL_REVISION_1, &desc, nullptr)) { const auto err = GetLastError(); throwWindowsError( (std::wstring(L"ConvertStringSecurityDescriptorToSecurityDescriptorW failed on \"") + str + L'"').c_str(), err); } return localItem(desc); } std::wstring sdToString(PSECURITY_DESCRIPTOR sd) { OsModule advapi32(L"advapi32.dll"); GET_MODULE_PROC(advapi32, ConvertSecurityDescriptorToStringSecurityDescriptorW); wchar_t *sdString = nullptr; if (!pConvertSecurityDescriptorToStringSecurityDescriptorW( sd, kSDDL_REVISION_1, OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION, &sdString, nullptr)) { throwWindowsError( L"ConvertSecurityDescriptorToStringSecurityDescriptor failed"); } PointerLocal freer(sdString); return std::wstring(sdString); } // Vista added a useful flag to CreateNamedPipe, PIPE_REJECT_REMOTE_CLIENTS, // that rejects remote connections. Return this flag on Vista, or return 0 // otherwise. DWORD rejectRemoteClientsPipeFlag() { if (isAtLeastWindowsVista()) { // MinGW lacks this flag; MinGW-w64 has it. const DWORD kPIPE_REJECT_REMOTE_CLIENTS = 8; return kPIPE_REJECT_REMOTE_CLIENTS; } else { trace("Omitting PIPE_REJECT_REMOTE_CLIENTS on pre-Vista OS"); return 0; } } typedef BOOL WINAPI GetNamedPipeClientProcessId_t( HANDLE Pipe, PULONG ClientProcessId); std::tuple getNamedPipeClientProcessId(HANDLE serverPipe) { OsModule kernel32(L"kernel32.dll"); const auto pGetNamedPipeClientProcessId = reinterpret_cast( kernel32.proc("GetNamedPipeClientProcessId")); if (pGetNamedPipeClientProcessId == nullptr) { return std::make_tuple( GetNamedPipeClientProcessId_Result::UnsupportedOs, 0, 0); } ULONG pid = 0; if (!pGetNamedPipeClientProcessId(serverPipe, &pid)) { return std::make_tuple( GetNamedPipeClientProcessId_Result::Failure, 0, GetLastError()); } return std::make_tuple( GetNamedPipeClientProcessId_Result::Success, static_cast(pid), 0); }