From f9ffce528af15c2c662fbb4fb21f374cb42fe565 Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Sat, 15 Aug 2026 00:11:01 +1000 Subject: [PATCH] [idd] helper: run interactive process as desktop user Explorer file copies were invisible to the Helper because the service launched its child with a duplicate of the LocalSystem token and changed only TokenSessionId. The child therefore remained a System-integrity process. Windows filtered Explorer's file clipboard formats across that integrity boundary. Basic text and bitmap formats continued to work. Keep only the SCM service privileged. Obtain the active session user's primary token with WTSQueryUserToken. For elevated accounts, prefer the linked limited token. Validate its session and security properties. Build the user environment and launch the interactive Helper on WinSta0\Default. Gate Helper activation until the service has rechecked the active session and registered the clipboard authority. Use random lifetime, stop, and activation objects owned by the service. Give the target logon SID synchronization access only. Recheck the active console session and service state before activation. Make the lifetime mutex terminate the Helper if the service exits unexpectedly. Restart it when the active session, IDD host, or authority changes. Replace the old process-handle mapping transfer with a device-bound authority protocol on the LGIdd device interface. The service verifies the exact driver host instance, duplicates only section map rights into that process, and registers the session, mapping identifier, and handle. Bind authority lifetime to its WDF file object, revoke it synchronously on cleanup, and poll the driver host identity while the child is active. Restrict the device stack to SYSTEM and isolate LGIdd in a unique UMDF device group. Restrict the shared section to SYSTEM and the target logon SID. Apply a medium mandatory label that prevents low-integrity readers and writers. Map it with read/write rights instead of all access. Give each clipboard mapping a second random authority identifier. Store it only inside the logon-SID-protected mapping and send it in the mandatory HELLO. LGIdd matches it against the service-injected mapping. This authenticates the user Helper without the unsupported UMDF call to GetNamedPipeClientSessionId. Have the Helper verify that its pipe server is in session zero. Extend the pipe endpoint with bounded authentication reads, cancellable overlapped I/O, periodic authorization checks, and explicit disconnects. Serialize authority changes with clipboard attach and detach. Prevent stale cleanup from tearing down a replacement mapping. Disconnect the user pipe immediately when its owning authority is revoked. Run clipboard, OLE, display, configuration, and file access in the user's interactive process. Retain its process token for worker-thread file operations instead of querying and impersonating the desktop user from a System process. Store Helper logs in LocalAppData and grant only the registry rights needed by interactive configuration and UMDF. Keep immediate, stage-specific Win32 and HRESULT diagnostics throughout clipboard capture. Probe CF_HDROP while holding the Win32 clipboard and enumerate the OLE object's advertised file formats. Validate returned storage and fall back to Shell item paths when direct retrieval fails. Validate clipboard sequence changes and defer retries during contention without publishing incomplete clipboard state. Complete the 1 MiB transfer work with full-sized Windows copy buffers. Use full-sized FUSE reads and retain the named 64 KiB X11 chunk limit. Validate the user-writable mapping with CClipboardRing before attaching. The pipe, mapping, and authority protocols change together. LGIdd.dll, the INF, and LGIddHelper.exe must be rebuilt and installed as one matching set. --- client/src/clipboard_files.c | 22 +- client/tests/x11_clipboard_test.c | 5 +- idd/LGCommon/CClipboardChannel.cpp | 3 +- idd/LGCommon/CClipboardRing.cpp | 18 +- idd/LGCommon/CClipboardRing.h | 3 +- idd/LGCommon/CDebug.cpp | 34 +- idd/LGCommon/CDebug.h | 11 +- idd/LGCommon/CPipeEndpoint.cpp | 224 +++- idd/LGCommon/CPipeEndpoint.h | 54 +- idd/LGCommon/CSRWLock.h | 2 + idd/LGCommon/ClipboardRing.h | 5 +- idd/LGCommon/LGCommon.vcxproj | 1 + idd/LGCommon/LGCommon.vcxproj.filters | 3 + idd/LGCommon/LGIddAuthority.h | 76 ++ idd/LGCommon/PipeMsg.h | 16 +- idd/LGIdd/Device.cpp | 301 +++++- idd/LGIdd/Device.h | 2 +- idd/LGIdd/LGIdd.inf | Bin 4286 -> 4380 bytes idd/LGIdd/LGIdd.vcxproj | 10 +- idd/LGIdd/LGIdd.vcxproj.filters | 3 - idd/LGIdd/Public.h | 22 - idd/LGIdd/ipc/CPipeServer.cpp | 382 ++++++- idd/LGIdd/ipc/CPipeServer.h | 33 + idd/LGIddHelper/CClipboardFiles.cpp | 142 +-- idd/LGIddHelper/CClipboardFiles.h | 19 - idd/LGIddHelper/CClipboardManager.cpp | 352 ++++--- idd/LGIddHelper/CClipboardManager.h | 3 +- idd/LGIddHelper/CConfigWindow.cpp | 2 +- idd/LGIddHelper/CPipeClient.cpp | 251 +++-- idd/LGIddHelper/CPipeClient.h | 19 +- idd/LGIddHelper/CRegistrySettings.cpp | 7 +- idd/LGIddHelper/CRegistrySettings.h | 2 +- idd/LGIddHelper/main.cpp | 1360 +++++++++++++++++++++++-- idd/LGIddInstall/LGIddInstall.c | 80 +- idd/LGInput/LGInput.vcxproj | 1 + 35 files changed, 2803 insertions(+), 665 deletions(-) create mode 100644 idd/LGCommon/LGIddAuthority.h delete mode 100644 idd/LGIdd/Public.h diff --git a/client/src/clipboard_files.c b/client/src/clipboard_files.c index 81e4a924..7a08b7dd 100644 --- a/client/src/clipboard_files.c +++ b/client/src/clipboard_files.c @@ -62,6 +62,9 @@ #define FILE_URI_MIME "text/uri-list" #define FILE_KDE_CUT_MIME "application/x-kde-cutselection" +_Static_assert(FILE_REQUEST_MAX <= UINT32_MAX, + "clipboard FUSE read size exceeds the FUSE protocol field"); + enum RemoteReply { REMOTE_REPLY_INITIAL, @@ -1569,8 +1572,15 @@ static void fuseReleasedir(fuse_req_t request, fuse_ino_t ino, releaseRemoteHandle(request, info); } +static void fuseInit(void * userData, struct fuse_conn_info * connection) +{ + (void)userData; + connection->max_read = FILE_REQUEST_MAX; +} + static const struct fuse_lowlevel_ops fuseOps = { + .init = fuseInit, .lookup = fuseLookup, .forget = fuseForget, .forget_multi = fuseForgetMulti, @@ -3460,11 +3470,19 @@ bool lgClipboardFiles_init(void) goto failed; } chmod(files.mountpoint, 0700); + char mountOptions[160]; + const int optionsLength = snprintf(mountOptions, sizeof(mountOptions), + "ro,nodev,nosuid,noexec,default_permissions,auto_unmount,max_read=%u", + (unsigned)FILE_REQUEST_MAX); + if (optionsLength < 0 || (size_t)optionsLength >= sizeof(mountOptions)) + { + DEBUG_ERROR("Failed to format clipboard FUSE mount options"); + goto failed; + } struct fuse_args args = FUSE_ARGS_INIT(0, NULL); if (fuse_opt_add_arg(&args, "looking-glass-client") < 0 || fuse_opt_add_arg(&args, "-o") < 0 || - fuse_opt_add_arg(&args, - "ro,nodev,nosuid,noexec,default_permissions,auto_unmount") < 0) + fuse_opt_add_arg(&args, mountOptions) < 0) { fuse_opt_free_args(&args); goto failed; diff --git a/client/tests/x11_clipboard_test.c b/client/tests/x11_clipboard_test.c index bc85301a..c0500088 100644 --- a/client/tests/x11_clipboard_test.c +++ b/client/tests/x11_clipboard_test.c @@ -388,7 +388,8 @@ int XGetWindowProperty(Display * display, Window window, Atom property, CHECK(window != None); CHECK(property != None); CHECK(offset >= 0); - CHECK(length == ~0L || length == (64U * 1024U + 3U) / 4U); + CHECK(length == ~0L || length == + (KVMFR_CLIPBOARD_REPRESENTATION_BYTES + 3U) / 4U); CHECK(delete); CHECK(requestedType == AnyPropertyType); CHECK(rec.propPos < rec.propN); @@ -1215,7 +1216,7 @@ static void testIncrLargeProperty(void) property(window); CHECK(rec.streamChunkN == 2); - CHECK(rec.streamMaxChunk == 64U * 1024U); + CHECK(rec.streamMaxChunk == KVMFR_CLIPBOARD_REPRESENTATION_BYTES); CHECK(rec.dataN == 1); CHECK(rec.data[0].size == size); CHECK(memcmp(rec.data[0].data, data, size) == 0); diff --git a/idd/LGCommon/CClipboardChannel.cpp b/idd/LGCommon/CClipboardChannel.cpp index a5402f02..bcf0995b 100644 --- a/idd/LGCommon/CClipboardChannel.cpp +++ b/idd/LGCommon/CClipboardChannel.cpp @@ -154,7 +154,8 @@ bool CClipboardChannel::Attach(HANDLE mapping, uint64_t epoch, CSRWExclusiveLock lock(m_lifecycleLock); ClipboardMapping * view = static_cast(MapViewOfFile( - mapping, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(ClipboardMapping))); + mapping, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, + sizeof(ClipboardMapping))); if (!view) { DEBUG_ERROR_HR(GetLastError(), "Failed to map clipboard channel"); diff --git a/idd/LGCommon/CClipboardRing.cpp b/idd/LGCommon/CClipboardRing.cpp index b31ea888..e5500c25 100644 --- a/idd/LGCommon/CClipboardRing.cpp +++ b/idd/LGCommon/CClipboardRing.cpp @@ -25,15 +25,18 @@ #include void CClipboardRing::Initialize( - ClipboardMapping& mapping, uint64_t epoch) + ClipboardMapping& mapping, uint64_t epoch, + const uint64_t (&authorityId)[2]) { memset(&mapping, 0, sizeof(mapping)); - mapping.magic = LG_CLIPBOARD_MAPPING_MAGIC; - mapping.version = LG_CLIPBOARD_MAPPING_VERSION; - mapping.size = sizeof(mapping); - mapping.slotBytes = KVMFR_CLIPBOARD_DATA_BYTES; - mapping.slotCount = KVMFR_CLIPBOARD_SLOT_COUNT; - mapping.epoch = epoch; + mapping.magic = LG_CLIPBOARD_MAPPING_MAGIC; + mapping.version = LG_CLIPBOARD_MAPPING_VERSION; + mapping.size = sizeof(mapping); + mapping.slotBytes = KVMFR_CLIPBOARD_DATA_BYTES; + mapping.slotCount = KVMFR_CLIPBOARD_SLOT_COUNT; + mapping.epoch = epoch; + mapping.authorityId[0] = authorityId[0]; + mapping.authorityId[1] = authorityId[1]; } bool CClipboardRing::Valid( @@ -44,6 +47,7 @@ bool CClipboardRing::Valid( mapping.size != sizeof(mapping) || mapping.slotBytes != KVMFR_CLIPBOARD_DATA_BYTES || mapping.slotCount != KVMFR_CLIPBOARD_SLOT_COUNT || + !mapping.authorityId[0] || !mapping.authorityId[1] || !mapping.epoch || mapping.epoch != epoch) return false; diff --git a/idd/LGCommon/CClipboardRing.h b/idd/LGCommon/CClipboardRing.h index 975e651a..8ba7cfeb 100644 --- a/idd/LGCommon/CClipboardRing.h +++ b/idd/LGCommon/CClipboardRing.h @@ -32,7 +32,8 @@ enum class ClipboardRingReadResult class CClipboardRing { public: - static void Initialize(ClipboardMapping& mapping, uint64_t epoch); + static void Initialize(ClipboardMapping& mapping, uint64_t epoch, + const uint64_t (&authorityId)[2]); static bool Valid(const ClipboardMapping& mapping, uint64_t epoch); static ClipboardRingSlot * BeginWrite( diff --git a/idd/LGCommon/CDebug.cpp b/idd/LGCommon/CDebug.cpp index 954546fd..31a72605 100644 --- a/idd/LGCommon/CDebug.cpp +++ b/idd/LGCommon/CDebug.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -100,25 +101,39 @@ inline static void iso8601(wchar_t *buf, size_t count) wcsftime(buf, count, L"%Y-%m-%d %H:%M:%SZ", &utc); } -inline static std::wstring getLogPath() +inline static std::wstring getLogPath(CDebug::Location location) { PWSTR pszPath; - if (FAILED(SHGetKnownFolderPath(FOLDERID_ProgramData, 0, NULL, &pszPath))) + const KNOWNFOLDERID& folder = location == CDebug::Location::LocalAppData ? + FOLDERID_LocalAppData : FOLDERID_ProgramData; + const HRESULT folderResult = + SHGetKnownFolderPath(folder, KF_FLAG_CREATE, NULL, &pszPath); + if (FAILED(folderResult)) { - DEBUG_ERROR("Failed to get ProgramData path"); + DEBUG_ERROR_HR(folderResult, "Failed to get the log directory root"); return L""; } std::wstring result(pszPath); CoTaskMemFree(pszPath); - result += L"\\Looking Glass (IDD)\\"; + result += L"\\Looking Glass (IDD)"; + if (!CreateDirectoryW(result.c_str(), nullptr)) + { + const DWORD directoryError = GetLastError(); + if (directoryError != ERROR_ALREADY_EXISTS) + { + DEBUG_ERROR_HR(directoryError, "Failed to create the log directory"); + return L""; + } + } + result += L"\\"; return result; } -void CDebug::Init(const wchar_t * name) +void CDebug::Init(const wchar_t * name, Location location) { - m_logDir = getLogPath(); + m_logDir = getLogPath(location); // don't redirect the debug output if running under a debugger if (IsDebuggerPresent()) @@ -150,10 +165,13 @@ void CDebug::Init(const wchar_t * name) } /// open the new log file + errno = 0; std::ofstream stream(logFile, std::ios::out | std::ios::trunc); + const int openError = errno; if (!stream.is_open()) { - DEBUG_ERROR_HR(GetLastError(), "Failed to open the log file %s", logFile.c_str()); + DEBUG_ERROR(L"Failed to open the log file %s (errno=%d)", + logFile.c_str(), openError ? openError : EIO); return; } @@ -336,10 +354,12 @@ void CDebug::LogStrHR(CDebug::Level level, HRESULT hr, const char *function, int wchar_t *result; if (aswprintf(&result, wide ? L"%s (0x%08lX (%u): %s)" : L"%S (0x%08lX (%u): %s)", str, hr, hr, hrBuffer) < 0) { + LocalFree(hrBuffer); Write(L"Out of memory while logging"); return; } + LocalFree(hrBuffer); LogStr(level, function, line, true, result); free(result); } diff --git a/idd/LGCommon/CDebug.h b/idd/LGCommon/CDebug.h index 1f1bd82f..095cdab3 100644 --- a/idd/LGCommon/CDebug.h +++ b/idd/LGCommon/CDebug.h @@ -32,6 +32,12 @@ class CDebug public: + enum class Location + { + ProgramData, + LocalAppData, + }; + enum Level { LEVEL_NONE = 0, @@ -46,7 +52,8 @@ class CDebug }; const wchar_t *logDir() { return m_logDir.c_str(); } - void Init(const wchar_t * name); + void Init(const wchar_t * name, + Location location = Location::ProgramData); void Log_va(CDebug::Level level, const char *function, int line, const wchar_t *fmt, va_list args); void Log(CDebug::Level level, const char *function, int line, const wchar_t *fmt, ...); void Log_va(CDebug::Level level, const char *function, int line, const char *fmt, va_list args); @@ -86,4 +93,4 @@ extern CDebug g_debug; #define DEBUG_ERROR_HR(hr, fmt, ...) g_debug.LogHR(CDebug::LEVEL_ERROR, hr, __FUNCTION__, __LINE__, fmt, ##__VA_ARGS__) #define DEBUG_TRACE_HR(hr, fmt, ...) g_debug.LogHR(CDebug::LEVEL_TRACE, hr, __FUNCTION__, __LINE__, fmt, ##__VA_ARGS__) #define DEBUG_FIXME_HR(hr, fmt, ...) g_debug.LogHR(CDebug::LEVEL_FIXME, hr, __FUNCTION__, __LINE__, fmt, ##__VA_ARGS__) -#define DEBUG_FATAL_HR(hr, fmt, ...) g_debug.LogHR(CDebug::LEVEL_FATAL, hr, __FUNCTION__, __LINE__, fmt, ##__VA_ARGS__) \ No newline at end of file +#define DEBUG_FATAL_HR(hr, fmt, ...) g_debug.LogHR(CDebug::LEVEL_FATAL, hr, __FUNCTION__, __LINE__, fmt, ##__VA_ARGS__) diff --git a/idd/LGCommon/CPipeEndpoint.cpp b/idd/LGCommon/CPipeEndpoint.cpp index 440271af..0e7182a1 100644 --- a/idd/LGCommon/CPipeEndpoint.cpp +++ b/idd/LGCommon/CPipeEndpoint.cpp @@ -26,11 +26,13 @@ #include #include -const DWORD CPipeEndpoint::CLIENT_RETRY_INITIAL_MS = 100; -const DWORD CPipeEndpoint::CLIENT_RETRY_MAX_MS = 2000; -const DWORD CPipeEndpoint::SERVER_RETRY_MS = 1000; -const DWORD CPipeEndpoint::WRITE_TIMEOUT_MS = 250; -const DWORD CPipeEndpoint::WAIT_FIRST_OBJECT_VALUE = 0; +const DWORD CPipeEndpoint::CLIENT_RETRY_INITIAL_MS = 100; +const DWORD CPipeEndpoint::CLIENT_RETRY_MAX_MS = 2000; +const DWORD CPipeEndpoint::SERVER_RETRY_MS = 1000; +const DWORD CPipeEndpoint::AUTHENTICATION_TIMEOUT_MS = 2000; +const DWORD CPipeEndpoint::AUTHORIZATION_POLL_MS = 1000; +const DWORD CPipeEndpoint::WRITE_TIMEOUT_MS = 250; +const DWORD CPipeEndpoint::WAIT_FIRST_OBJECT_VALUE = 0; bool CPipeEndpoint::IsDisconnectedError(DWORD error) { @@ -55,7 +57,12 @@ CPipeEndpoint::PipeIoResult CPipeEndpoint::WaitForOverlapped( CancelIoEx(pipe, overlapped); if (GetOverlappedResult(pipe, overlapped, transferred, TRUE)) return PipeIoResult::Success; - DEBUG_WARN("Named pipe write timed out"); + const DWORD error = GetLastError(); + if (error == ERROR_OPERATION_ABORTED) + return PipeIoResult::TimedOut; + if (IsDisconnectedError(error)) + return PipeIoResult::Disconnected; + DEBUG_WARN_HR(error, "Failed to cancel timed-out named pipe I/O"); return PipeIoResult::Error; } @@ -94,7 +101,8 @@ CPipeEndpoint::PipeIoResult CPipeEndpoint::ReadMessage( HANDLE ioEvent, void * message, DWORD messageSize, - DWORD * bytesRead) + DWORD * bytesRead, + DWORD timeoutMs) { ResetEvent(ioEvent); OVERLAPPED overlapped = {}; @@ -106,7 +114,7 @@ CPipeEndpoint::PipeIoResult CPipeEndpoint::ReadMessage( const DWORD error = GetLastError(); if (error == ERROR_IO_PENDING) return WaitForOverlapped( - pipe, ioEvent, &overlapped, bytesRead); + pipe, ioEvent, &overlapped, bytesRead, timeoutMs); if (error == ERROR_OPERATION_ABORTED && WaitForSingleObject(m_stopEvent, 0) == WAIT_FIRST_OBJECT_VALUE) @@ -167,6 +175,8 @@ CPipeEndpoint::PipeIoResult CPipeEndpoint::WriteMessage( bytesWritten); result = PipeIoResult::Error; } + else if (result == PipeIoResult::TimedOut) + DEBUG_WARN("Named pipe write timed out"); return result; } @@ -179,27 +189,39 @@ CPipeEndpoint::~CPipeEndpoint() bool CPipeEndpoint::Start( const wchar_t * pipeName, Mode mode, - size_t messageSize) + size_t messageSize, + const SECURITY_ATTRIBUTES * serverSecurity, + DWORD serverOpenMode, + DWORD serverPipeMode) { Stop(); if (!pipeName || !*pipeName || !messageSize || messageSize > MAXDWORD) return false; - m_pipeName = pipeName; - m_mode = mode; - m_messageSize = messageSize; - m_stopEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr); - m_writeEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr); - if (!m_stopEvent || !m_writeEvent) + m_pipeName = pipeName; + m_mode = mode; + m_messageSize = messageSize; + m_hasServerSecurity = serverSecurity != nullptr; + m_serverSecurity = serverSecurity ? *serverSecurity : + SECURITY_ATTRIBUTES {}; + m_serverOpenMode = serverOpenMode; + m_serverPipeMode = serverPipeMode; + m_stopEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr); + if (!m_stopEvent) { - DEBUG_ERROR_HR(GetLastError(), "Failed to create named pipe events"); - if (m_writeEvent) - CloseHandle(m_writeEvent); - if (m_stopEvent) - CloseHandle(m_stopEvent); - m_writeEvent = nullptr; - m_stopEvent = nullptr; + const DWORD error = GetLastError(); + DEBUG_ERROR_HR(error, "Failed to create the named pipe stop event"); + return false; + } + + m_writeEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr); + if (!m_writeEvent) + { + const DWORD error = GetLastError(); + DEBUG_ERROR_HR(error, "Failed to create the named pipe write event"); + CloseHandle(m_stopEvent); + m_stopEvent = nullptr; return false; } @@ -287,6 +309,19 @@ void CPipeEndpoint::Stop() Atomic::Store(m_connected, false); } +void CPipeEndpoint::DisconnectClient() +{ + Atomic::Store(m_connected, false); + CSRWSharedLock lock(m_pipeLock); + if (m_pipe != INVALID_HANDLE_VALUE && + !CancelIoEx(m_pipe, nullptr)) + { + const DWORD error = GetLastError(); + if (error != ERROR_NOT_FOUND) + DEBUG_WARN_HR(error, "Failed to cancel named pipe client I/O"); + } +} + bool CPipeEndpoint::Send(const void * message, size_t size) { if (!message || size != m_messageSize || !IsRunning() || !IsConnected()) @@ -340,13 +375,14 @@ HANDLE CPipeEndpoint::CreateServerPipe() HANDLE pipe = CreateNamedPipeW( m_pipeName.c_str(), - PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, - PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, + PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | m_serverOpenMode, + PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT | + m_serverPipeMode, 1, bufferSize, bufferSize, 0, - nullptr); + m_hasServerSecurity ? &m_serverSecurity : nullptr); if (pipe == INVALID_HANDLE_VALUE) DEBUG_ERROR_HR(GetLastError(), "Failed to create named pipe %ls", m_pipeName.c_str()); return pipe; @@ -420,13 +456,79 @@ void CPipeEndpoint::RunServer() continue; } - if (!IsRunning()) - break; - if (!IsRunning() || WaitForSingleObject(m_stopEvent, 0) == WAIT_FIRST_OBJECT_VALUE) break; + bool authenticated = false; + if (m_handler && m_handler->PipeClientAuthenticationRequired()) + { + std::vector message(m_messageSize); + DWORD bytesRead = 0; + const PipeIoResult authResult = ReadMessage( + pipe, + ioEvent, + message.data(), + static_cast(message.size()), + &bytesRead, + AUTHENTICATION_TIMEOUT_MS); + if (authResult == PipeIoResult::Success && + bytesRead != message.size()) + { + DEBUG_WARN( + "Named pipe authentication frame has %lu bytes, expected %llu", + bytesRead, + static_cast(message.size())); + } + if (authResult != PipeIoResult::Success || + bytesRead != message.size() || + !m_handler->AuthenticatePipeClient( + pipe, message.data(), message.size())) + { + if (authResult == PipeIoResult::TimedOut) + DEBUG_WARN("Named pipe client authentication timed out"); + else if (authResult == PipeIoResult::Success) + DEBUG_WARN("Named pipe client authentication failed"); + if (!DisconnectNamedPipe(pipe)) + { + const DWORD error = GetLastError(); + if (error != ERROR_PIPE_NOT_CONNECTED) + { + DEBUG_WARN_HR(error, + "Failed to disconnect unauthenticated named pipe client"); + ClosePipe(pipe); + pipe = INVALID_HANDLE_VALUE; + } + } + if (authResult == PipeIoResult::Stopped || !IsRunning()) + break; + continue; + } + authenticated = true; + } + + if (!IsRunning() || + WaitForSingleObject(m_stopEvent, 0) == WAIT_FIRST_OBJECT_VALUE || + (m_handler && !m_handler->PipeClientStillAuthorized(pipe))) + { + if (authenticated && m_handler) + m_handler->OnPipeDisconnected(); + if (!DisconnectNamedPipe(pipe)) + { + const DWORD error = GetLastError(); + if (error != ERROR_PIPE_NOT_CONNECTED) + { + DEBUG_WARN_HR(error, + "Failed to disconnect unauthorized named pipe client"); + ClosePipe(pipe); + pipe = INVALID_HANDLE_VALUE; + } + } + if (!IsRunning()) + break; + continue; + } + Atomic::Store(m_connected, true); DEBUG_INFO("Named pipe client connected: %ls", m_pipeName.c_str()); if (m_handler) @@ -494,7 +596,8 @@ void CPipeEndpoint::RunClient() DWORD mode = PIPE_READMODE_MESSAGE; if (!SetNamedPipeHandleState(pipe, &mode, nullptr, nullptr)) { - DEBUG_WARN_HR(GetLastError(), "Failed to set named pipe message mode"); + const DWORD error = GetLastError(); + DEBUG_WARN_HR(error, "Failed to set named pipe message mode"); CloseHandle(pipe); if (!WaitForRetry(retryDelay)) break; @@ -502,6 +605,50 @@ void CPipeEndpoint::RunClient() continue; } + if (m_handler && !m_handler->PipeServerIsAuthorized(pipe)) + { + DEBUG_WARN("Rejected unauthorized named pipe server: %ls", + m_pipeName.c_str()); + CloseHandle(pipe); + if (!WaitForRetry(retryDelay)) + break; + retryDelay = (std::min)(retryDelay * 2, CLIENT_RETRY_MAX_MS); + continue; + } + + if (!IsRunning() || + WaitForSingleObject(m_stopEvent, 0) == WAIT_FIRST_OBJECT_VALUE) + { + CloseHandle(pipe); + break; + } + + if (m_handler && m_handler->PipeClientHelloRequired()) + { + std::vector hello(m_messageSize); + if (!m_handler->BuildPipeClientHello(hello.data(), hello.size())) + { + DEBUG_WARN("Failed to build named pipe client HELLO"); + CloseHandle(pipe); + if (!WaitForRetry(retryDelay)) + break; + retryDelay = (std::min)(retryDelay * 2, CLIENT_RETRY_MAX_MS); + continue; + } + + const PipeIoResult helloResult = WriteMessage( + pipe, hello.data(), static_cast(hello.size())); + if (helloResult != PipeIoResult::Success) + { + DEBUG_WARN("Failed to send named pipe client HELLO"); + CloseHandle(pipe); + if (!WaitForRetry(retryDelay)) + break; + retryDelay = (std::min)(retryDelay * 2, CLIENT_RETRY_MAX_MS); + continue; + } + } + if (!IsRunning() || WaitForSingleObject(m_stopEvent, 0) == WAIT_FIRST_OBJECT_VALUE) { @@ -549,7 +696,18 @@ bool CPipeEndpoint::ReadMessages(HANDLE pipe) ioEvent, message.data(), static_cast(message.size()), - &bytesRead); + &bytesRead, + m_handler && m_handler->PipeClientAuthenticationRequired() ? + AUTHORIZATION_POLL_MS : INFINITE); + if (result == PipeIoResult::TimedOut) + { + if (m_handler && !m_handler->PipeClientStillAuthorized(pipe)) + { + success = false; + break; + } + continue; + } if (result != PipeIoResult::Success) { success = result == PipeIoResult::Disconnected || @@ -571,6 +729,12 @@ bool CPipeEndpoint::ReadMessages(HANDLE pipe) WaitForSingleObject(m_stopEvent, 0) == WAIT_FIRST_OBJECT_VALUE) break; + if (m_handler && !m_handler->PipeClientStillAuthorized(pipe)) + { + success = false; + break; + } + if (m_handler && !m_handler->OnPipeMessage( message.data(), message.size())) { diff --git a/idd/LGCommon/CPipeEndpoint.h b/idd/LGCommon/CPipeEndpoint.h index 29a642a0..3345d12b 100644 --- a/idd/LGCommon/CPipeEndpoint.h +++ b/idd/LGCommon/CPipeEndpoint.h @@ -36,6 +36,36 @@ public: virtual void OnPipeConnected() {} virtual void OnPipeDisconnected() {} virtual bool ShouldReconnect() { return true; } + virtual bool PipeServerIsAuthorized(_In_ HANDLE pipe) + { + (void) pipe; + return true; + } + virtual bool PipeClientHelloRequired() const { return false; } + virtual bool BuildPipeClientHello( + _Out_writes_bytes_(size) void * message, + _In_ size_t size) + { + (void) message; + (void) size; + return true; + } + virtual bool PipeClientAuthenticationRequired() const { return false; } + virtual bool AuthenticatePipeClient( + _In_ HANDLE pipe, + _In_reads_bytes_(size) const void * message, + _In_ size_t size) + { + (void) pipe; + (void) message; + (void) size; + return true; + } + virtual bool PipeClientStillAuthorized(_In_ HANDLE pipe) + { + (void) pipe; + return true; + } virtual bool OnPipeMessage( _In_reads_bytes_(size) const void * message, _In_ size_t size) = 0; @@ -59,8 +89,12 @@ public: bool Start( _In_z_ const wchar_t * pipeName, _In_ Mode mode, - _In_ size_t messageSize); + _In_ size_t messageSize, + _In_opt_ const SECURITY_ATTRIBUTES * serverSecurity = nullptr, + _In_ DWORD serverOpenMode = 0, + _In_ DWORD serverPipeMode = 0); void Stop(); + void DisconnectClient(); bool Send( _In_reads_bytes_(size) const void * message, @@ -82,12 +116,15 @@ private: Success, Disconnected, Stopped, + TimedOut, Error, }; static const DWORD CLIENT_RETRY_INITIAL_MS; static const DWORD CLIENT_RETRY_MAX_MS; static const DWORD SERVER_RETRY_MS; + static const DWORD AUTHENTICATION_TIMEOUT_MS; + static const DWORD AUTHORIZATION_POLL_MS; static const DWORD WRITE_TIMEOUT_MS; static const DWORD WAIT_FIRST_OBJECT_VALUE; @@ -103,7 +140,8 @@ private: _In_ HANDLE ioEvent, _Out_writes_bytes_(messageSize) void * message, _In_ DWORD messageSize, - _Out_ DWORD * bytesRead); + _Out_ DWORD * bytesRead, + _In_ DWORD timeoutMs = INFINITE); PipeIoResult WriteMessage( _In_ HANDLE pipe, _In_reads_bytes_(messageSize) const void * message, @@ -120,10 +158,14 @@ private: void PublishPipe(_In_ HANDLE pipe); void ClosePipe(_In_ HANDLE pipe); - std::wstring m_pipeName; - size_t m_messageSize = 0; - Mode m_mode = Mode::Client; - IPipeEndpointHandler * m_handler = nullptr; + std::wstring m_pipeName; + size_t m_messageSize = 0; + Mode m_mode = Mode::Client; + IPipeEndpointHandler * m_handler = nullptr; + SECURITY_ATTRIBUTES m_serverSecurity = {}; + bool m_hasServerSecurity = false; + DWORD m_serverOpenMode = 0; + DWORD m_serverPipeMode = 0; std::atomic m_running { false }; std::atomic m_connected { false }; diff --git a/idd/LGCommon/CSRWLock.h b/idd/LGCommon/CSRWLock.h index 42eb0ad8..b26be4d8 100644 --- a/idd/LGCommon/CSRWLock.h +++ b/idd/LGCommon/CSRWLock.h @@ -61,6 +61,7 @@ public: if (!m_lock) return; + _Analysis_assume_lock_acquired_(*m_lock); ReleaseSRWLockShared(m_lock); m_lock = nullptr; } @@ -114,6 +115,7 @@ public: if (!m_lock) return; + _Analysis_assume_lock_acquired_(*m_lock); ReleaseSRWLockExclusive(m_lock); m_lock = nullptr; } diff --git a/idd/LGCommon/ClipboardRing.h b/idd/LGCommon/ClipboardRing.h index 23f44c02..4097ee5b 100644 --- a/idd/LGCommon/ClipboardRing.h +++ b/idd/LGCommon/ClipboardRing.h @@ -26,7 +26,7 @@ #include static constexpr uint32_t LG_CLIPBOARD_MAPPING_MAGIC = 0x4c474342U; -static constexpr uint32_t LG_CLIPBOARD_MAPPING_VERSION = 3U; +static constexpr uint32_t LG_CLIPBOARD_MAPPING_VERSION = 4U; struct ClipboardRingSlot { @@ -51,7 +51,8 @@ struct alignas(64) ClipboardMapping uint32_t slotCount; uint32_t reserved0; uint64_t epoch; - uint8_t reserved1[32]; + uint64_t authorityId[2]; + uint8_t reserved1[16]; ClipboardRing helperToIdd; ClipboardRing iddToHelper; }; diff --git a/idd/LGCommon/LGCommon.vcxproj b/idd/LGCommon/LGCommon.vcxproj index ddf6a839..c0a5bcd2 100644 --- a/idd/LGCommon/LGCommon.vcxproj +++ b/idd/LGCommon/LGCommon.vcxproj @@ -81,6 +81,7 @@ + diff --git a/idd/LGCommon/LGCommon.vcxproj.filters b/idd/LGCommon/LGCommon.vcxproj.filters index 6e1faf59..ba0ef89e 100644 --- a/idd/LGCommon/LGCommon.vcxproj.filters +++ b/idd/LGCommon/LGCommon.vcxproj.filters @@ -56,6 +56,9 @@ Header Files + + Header Files + Header Files diff --git a/idd/LGCommon/LGIddAuthority.h b/idd/LGCommon/LGIddAuthority.h new file mode 100644 index 00000000..cca1a5da --- /dev/null +++ b/idd/LGCommon/LGIddAuthority.h @@ -0,0 +1,76 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#pragma once + +#include +#include +#include + +static const GUID GUID_DEVINTERFACE_LGIdd = + { 0x997b0b66, 0xb74c, 0x4017, + { 0x9a, 0x89, 0xe4, 0xaa, 0xd4, 0x1d, 0x37, 0x80 } }; + +static constexpr uint32_t LG_IDD_AUTHORITY_VERSION = 1U; + +static constexpr DWORD IOCTL_LG_IDD_AUTHORITY_GET_HOST = + CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, + FILE_READ_ACCESS | FILE_WRITE_ACCESS); +static constexpr DWORD IOCTL_LG_IDD_AUTHORITY_REGISTER = + CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_BUFFERED, + FILE_READ_ACCESS | FILE_WRITE_ACCESS); +static constexpr DWORD IOCTL_LG_IDD_AUTHORITY_CLEAR = + CTL_CODE(FILE_DEVICE_UNKNOWN, 0x802, METHOD_BUFFERED, + FILE_READ_ACCESS | FILE_WRITE_ACCESS); + +struct LGIddAuthorityHost +{ + uint32_t size; + uint32_t version; + uint32_t processId; + uint32_t reserved; + uint64_t processCreated; + uint64_t instanceId[2]; +}; + +struct LGIddAuthorityRegistration +{ + uint32_t size; + uint32_t version; + uint32_t session; + uint32_t reserved; + uint64_t instanceId[2]; + uint64_t mappingId[2]; + uint64_t mappingHandle; +}; + +struct LGIddAuthorityClear +{ + uint32_t size; + uint32_t version; + uint64_t instanceId[2]; +}; + +static_assert(sizeof(LGIddAuthorityHost) == 40, + "LGIdd authority host layout changed"); +static_assert(sizeof(LGIddAuthorityRegistration) == 56, + "LGIdd authority registration layout changed"); +static_assert(sizeof(LGIddAuthorityClear) == 24, + "LGIdd authority clear layout changed"); diff --git a/idd/LGCommon/PipeMsg.h b/idd/LGCommon/PipeMsg.h index 376780c0..f9e11580 100644 --- a/idd/LGCommon/PipeMsg.h +++ b/idd/LGCommon/PipeMsg.h @@ -43,13 +43,15 @@ struct LGPipeMsg CLIPBOARD_SETUP, CLIPBOARD_READY, CLIPBOARD_KICK, - CLIPBOARD_RESET + CLIPBOARD_RESET, + HELLO } type; enum : uint32_t { - RECOVERY_ACTIVE = 0x1U + RECOVERY_ACTIVE = 0x1U, + PROTOCOL_VERSION = 2U }; union @@ -92,6 +94,7 @@ struct LGPipeMsg struct { + // Legacy setup carries a handle already duplicated into the receiver. uint64_t handle; uint32_t bytes; } @@ -117,8 +120,15 @@ struct LGPipeMsg uint32_t reason; } clipboardReset; + + struct + { + uint32_t version; + uint64_t authorityId[2]; + } + hello; }; }; #pragma pack(pop) -static_assert(sizeof(LGPipeMsg) == 20, "LGPipeMsg wire layout changed"); +static_assert(sizeof(LGPipeMsg) == 28, "LGPipeMsg wire layout changed"); diff --git a/idd/LGIdd/Device.cpp b/idd/LGIdd/Device.cpp index af9fdaae..1ea55138 100644 --- a/idd/LGIdd/Device.cpp +++ b/idd/LGIdd/Device.cpp @@ -27,11 +27,14 @@ #include #include #include +#include #include +#include #include #include #include "CDebug.h" +#include "ClipboardRing.h" #include "display/CDisplayConfiguration.h" #include "display/IddCxCompat.h" #include "display/CDeviceContext.h" @@ -42,7 +45,275 @@ WDFDEVICE l_wdfDevice = nullptr; -static const UINT IDDCX_VERSION_1_10 = 0x1A00; +static const UINT IDDCX_VERSION_1_10 = 0x1A00; +static uint64_t l_authorityInstanceId[2] = {}; +static uint64_t l_authorityProcessCreated = 0; + +static uint64_t FileTimeValue(const FILETIME& value) +{ + ULARGE_INTEGER result = {}; + result.LowPart = value.dwLowDateTime; + result.HighPart = value.dwHighDateTime; + return result.QuadPart; +} + +static NTSTATUS InitAuthorityIdentity() +{ + FILETIME created = {}; + FILETIME exited = {}; + FILETIME kernel = {}; + FILETIME user = {}; + if (!GetProcessTimes(GetCurrentProcess(), + &created, &exited, &kernel, &user)) + { + const DWORD error = GetLastError(); + DEBUG_ERROR_HR(error, + "Failed to query the LGIdd host process creation time"); + return STATUS_UNSUCCESSFUL; + } + + for (unsigned attempt = 0; attempt < 2; ++attempt) + { + const NTSTATUS status = BCryptGenRandom(nullptr, + reinterpret_cast(l_authorityInstanceId), + sizeof(l_authorityInstanceId), BCRYPT_USE_SYSTEM_PREFERRED_RNG); + if (!NT_SUCCESS(status)) + { + DEBUG_ERROR_HR(HRESULT_FROM_NT(status), + "Failed to generate the LGIdd authority instance identifier"); + return status; + } + if (l_authorityInstanceId[0] && l_authorityInstanceId[1]) + { + l_authorityProcessCreated = FileTimeValue(created); + return STATUS_SUCCESS; + } + } + + DEBUG_ERROR_HR(ERROR_INVALID_DATA, + "Generated an invalid LGIdd authority instance identifier"); + return STATUS_DATA_ERROR; +} + +static bool AuthorityInstanceMatches(const uint64_t (&instanceId)[2]) +{ + return instanceId[0] == l_authorityInstanceId[0] && + instanceId[1] == l_authorityInstanceId[1]; +} + +static void LGIddAuthorityFileCleanup(WDFFILEOBJECT fileObject) +{ + g_pipe.CloseClipboardAuthorityFile(fileObject); +} + +static void LGIddAuthorityIoDeviceControl(WDFDEVICE device, + WDFREQUEST request, size_t outputLength, size_t inputLength, + ULONG controlCode) +{ + UNREFERENCED_PARAMETER(device); + + NTSTATUS status = STATUS_INVALID_DEVICE_REQUEST; + ULONG_PTR information = 0; + WDFFILEOBJECT fileObject = WdfRequestGetFileObject(request); + if (!fileObject) + { + DEBUG_WARN( + "LGIdd authority request has no file object"); + WdfRequestComplete(request, STATUS_INVALID_HANDLE); + return; + } + + switch (controlCode) + { + case IOCTL_LG_IDD_AUTHORITY_GET_HOST: + { + if (inputLength || outputLength < sizeof(LGIddAuthorityHost)) + { + DEBUG_WARN( + "Rejected malformed LGIdd authority host request"); + status = STATUS_BUFFER_TOO_SMALL; + break; + } + + LGIddAuthorityHost * host = nullptr; + status = WdfRequestRetrieveOutputBuffer(request, sizeof(*host), + reinterpret_cast(&host), nullptr); + if (!NT_SUCCESS(status)) + { + DEBUG_WARN_HR(HRESULT_FROM_NT(status), + "Failed to retrieve the LGIdd authority host output buffer"); + break; + } + + ZeroMemory(host, sizeof(*host)); + host->size = sizeof(*host); + host->version = LG_IDD_AUTHORITY_VERSION; + host->processId = GetCurrentProcessId(); + host->processCreated = l_authorityProcessCreated; + host->instanceId[0] = l_authorityInstanceId[0]; + host->instanceId[1] = l_authorityInstanceId[1]; + information = sizeof(*host); + status = STATUS_SUCCESS; + break; + } + + case IOCTL_LG_IDD_AUTHORITY_REGISTER: + { + if (inputLength != sizeof(LGIddAuthorityRegistration) || outputLength) + { + DEBUG_WARN( + "Rejected malformed LGIdd authority registration request"); + status = STATUS_INFO_LENGTH_MISMATCH; + break; + } + + void * input = nullptr; + status = WdfRequestRetrieveInputBuffer(request, + sizeof(LGIddAuthorityRegistration), &input, nullptr); + if (!NT_SUCCESS(status)) + { + DEBUG_WARN_HR(HRESULT_FROM_NT(status), + "Failed to retrieve the LGIdd authority registration buffer"); + break; + } + const LGIddAuthorityRegistration * registration = + static_cast(input); + if (registration->size != sizeof(*registration) || + registration->version != LG_IDD_AUTHORITY_VERSION || + registration->reserved || + !AuthorityInstanceMatches(registration->instanceId) || + !registration->mappingId[0] || !registration->mappingId[1] || + !registration->mappingHandle || + registration->mappingHandle > + static_cast( + (std::numeric_limits::max)()) || + registration->mappingHandle == static_cast( + reinterpret_cast(INVALID_HANDLE_VALUE))) + { + DEBUG_WARN( + "Rejected invalid LGIdd authority registration data"); + status = STATUS_DATA_ERROR; + break; + } + + HANDLE rawMapping = reinterpret_cast( + static_cast(registration->mappingHandle)); + const auto closeInjectedMapping = [rawMapping]() + { + if (!CloseHandle(rawMapping)) + { + const DWORD error = GetLastError(); + DEBUG_WARN_HR(error, + "Failed to consume the injected clipboard authority handle"); + return false; + } + return true; + }; + const ClipboardMapping * view = static_cast( + MapViewOfFileFromApp(rawMapping, + FILE_MAP_READ | FILE_MAP_WRITE, 0, sizeof(ClipboardMapping))); + if (!view) + { + const DWORD error = GetLastError(); + DEBUG_WARN_HR(error, + "Failed to validate the injected clipboard authority handle"); + closeInjectedMapping(); + status = STATUS_INVALID_HANDLE; + break; + } + const uint64_t authorityId[2] = + { view->authorityId[0], view->authorityId[1] }; + if (!UnmapViewOfFile(view)) + { + const DWORD error = GetLastError(); + DEBUG_WARN_HR(error, + "Failed to unmap the injected clipboard authority handle"); + closeInjectedMapping(); + status = STATUS_UNSUCCESSFUL; + break; + } + if (!authorityId[0] || !authorityId[1]) + { + DEBUG_WARN("Rejected clipboard mapping without an authority ID"); + closeInjectedMapping(); + status = STATUS_DATA_ERROR; + break; + } + + HANDLE mapping = nullptr; + if (!DuplicateHandle(GetCurrentProcess(), rawMapping, + GetCurrentProcess(), &mapping, + SECTION_MAP_READ | SECTION_MAP_WRITE, FALSE, 0)) + { + const DWORD error = GetLastError(); + DEBUG_WARN_HR(error, + "Failed to privatize the injected clipboard authority handle"); + closeInjectedMapping(); + status = STATUS_INVALID_HANDLE; + break; + } + if (!closeInjectedMapping()) + { + CloseHandle(mapping); + status = STATUS_UNSUCCESSFUL; + break; + } + + if (!g_pipe.RegisterClipboardAuthority(fileObject, mapping, + registration->session, registration->mappingId, authorityId)) + { + CloseHandle(mapping); + status = STATUS_ACCESS_DENIED; + break; + } + + status = STATUS_SUCCESS; + break; + } + + case IOCTL_LG_IDD_AUTHORITY_CLEAR: + { + if (inputLength != sizeof(LGIddAuthorityClear) || outputLength) + { + DEBUG_WARN( + "Rejected malformed LGIdd authority clear request"); + status = STATUS_INFO_LENGTH_MISMATCH; + break; + } + + void * input = nullptr; + status = WdfRequestRetrieveInputBuffer(request, + sizeof(LGIddAuthorityClear), &input, nullptr); + if (!NT_SUCCESS(status)) + { + DEBUG_WARN_HR(HRESULT_FROM_NT(status), + "Failed to retrieve the LGIdd authority clear buffer"); + break; + } + const LGIddAuthorityClear * clear = + static_cast(input); + if (clear->size != sizeof(*clear) || + clear->version != LG_IDD_AUTHORITY_VERSION || + !AuthorityInstanceMatches(clear->instanceId)) + { + DEBUG_WARN( + "Rejected invalid LGIdd authority clear data"); + status = STATUS_DATA_ERROR; + break; + } + + if (!g_pipe.ClearClipboardAuthority(fileObject)) + { + status = STATUS_ACCESS_DENIED; + break; + } + status = STATUS_SUCCESS; + break; + } + } + + WdfRequestCompleteWithInformation(request, status, information); +} static bool LGIddCanUseIddCx110DDIs(UINT iddCxVersion) { @@ -283,6 +554,13 @@ NTSTATUS LGIddMonitorUnassignSwapChain(IDDCX_MONITOR monitor) NTSTATUS LGIddCreateDevice(_Inout_ PWDFDEVICE_INIT deviceInit) { NTSTATUS status; + if (!l_authorityInstanceId[0] || !l_authorityInstanceId[1]) + { + status = InitAuthorityIdentity(); + if (!NT_SUCCESS(status)) + return status; + } + IDARG_OUT_GETVERSION ver; status = IddCxGetVersion(&ver); if (FAILED(status)) @@ -302,6 +580,7 @@ NTSTATUS LGIddCreateDevice(_Inout_ PWDFDEVICE_INIT deviceInit) IDD_CX_CLIENT_CONFIG config; IDD_CX_CLIENT_CONFIG_INIT(&config); config.EvtIddCxAdapterInitFinished = LGIddAdapterInitFinished; + config.EvtIddCxDeviceIoControl = LGIddAuthorityIoDeviceControl; config.EvtIddCxMonitorGetDefaultDescriptionModes = LGIddMonitorGetDefaultModes; config.EvtIddCxMonitorAssignSwapChain = LGIddMonitorAssignSwapChain; config.EvtIddCxMonitorUnassignSwapChain = LGIddMonitorUnassignSwapChain; @@ -328,6 +607,16 @@ NTSTATUS LGIddCreateDevice(_Inout_ PWDFDEVICE_INIT deviceInit) if (!NT_SUCCESS(status)) return status; + WDF_FILEOBJECT_CONFIG fileConfig; + WDF_FILEOBJECT_CONFIG_INIT(&fileConfig, + WDF_NO_EVENT_CALLBACK, WDF_NO_EVENT_CALLBACK, + LGIddAuthorityFileCleanup); + WDF_OBJECT_ATTRIBUTES fileAttributes; + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE( + &fileAttributes, LGIddAuthorityFileContext); + WdfDeviceInitSetFileObjectConfig( + deviceInit, &fileConfig, &fileAttributes); + WDF_OBJECT_ATTRIBUTES deviceAttributes; WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&deviceAttributes, CDeviceContextWrapper); deviceAttributes.EvtCleanupCallback = [](WDFOBJECT object) @@ -335,6 +624,7 @@ NTSTATUS LGIddCreateDevice(_Inout_ PWDFDEVICE_INIT deviceInit) auto * wrapper = WdfObjectGet_CDeviceContextWrapper(object); if (wrapper) { + g_pipe.ClearClipboardAuthority(); g_pipe.SetDeviceContext(nullptr); wrapper->Cleanup(); } @@ -346,6 +636,15 @@ NTSTATUS LGIddCreateDevice(_Inout_ PWDFDEVICE_INIT deviceInit) if (!NT_SUCCESS(status)) return status; + status = WdfDeviceCreateDeviceInterface( + device, &GUID_DEVINTERFACE_LGIdd, nullptr); + if (!NT_SUCCESS(status)) + { + DEBUG_ERROR_HR(HRESULT_FROM_NT(status), + "Failed to create the LGIdd authority device interface"); + return status; + } + /* * Construct the device context and cache the WDF device BEFORE calling * IddCxDeviceInitialize. IddCxDeviceInitialize arms the IddCx callbacks, and diff --git a/idd/LGIdd/Device.h b/idd/LGIdd/Device.h index a28abbec..1a967129 100644 --- a/idd/LGIdd/Device.h +++ b/idd/LGIdd/Device.h @@ -20,7 +20,7 @@ #pragma once -#include "public.h" +#include "LGIddAuthority.h" #include diff --git a/idd/LGIdd/LGIdd.inf b/idd/LGIdd/LGIdd.inf index 47de49de34a9491a4c305d4abca6e2cfc4cb2bc5..36688cea84fb717a788850a7388b430c156fdf09 100644 GIT binary patch delta 92 zcmdm|I7ey2F;;&a1_cHshG2$NhGd3Ph9ZVch7yKKpa@7@l_7}1mBF1M9w;9L - @@ -279,6 +278,7 @@ $([MSBuild]::NormalizeDirectory('$(LGDriverSolutionDir)$(Platform)\$(LGBaseConfiguration)')) $([MSBuild]::NormalizeDirectory('$(LGDriverSolutionDir)$(LGBaseConfiguration)')) + /sw2084 @@ -289,7 +289,7 @@ $(ProjectDir);$(SolutionDir)LGCommon;$(ProjectDir)..\..\repos\LGMP\lgmp\include;$(ProjectDir)..\..\vendor;$(ProjectDir)..\..\common\include;%(AdditionalIncludeDirectories) - %(AdditionalDependencies);OneCoreUAP.lib;avrt.lib;d3d12.lib;d3dcompiler.lib + %(AdditionalDependencies);OneCoreUAP.lib;avrt.lib;bcrypt.lib;d3d12.lib;d3dcompiler.lib SHA1 @@ -304,7 +304,7 @@ $(ProjectDir);$(SolutionDir)LGCommon;$(ProjectDir)..\..\repos\LGMP\lgmp\include;$(ProjectDir)..\..\vendor;$(ProjectDir)..\..\common\include;%(AdditionalIncludeDirectories) - %(AdditionalDependencies);OneCoreUAP.lib;avrt.lib;d3d12.lib;d3dcompiler.lib + %(AdditionalDependencies);OneCoreUAP.lib;avrt.lib;bcrypt.lib;d3d12.lib;d3dcompiler.lib SHA1 @@ -319,7 +319,7 @@ $(ProjectDir);$(SolutionDir)LGCommon;$(ProjectDir)..\..\repos\LGMP\lgmp\include;$(ProjectDir)..\..\vendor;$(ProjectDir)..\..\common\include;%(AdditionalIncludeDirectories) - %(AdditionalDependencies);OneCoreUAP.lib;avrt.lib;d3d12.lib;d3dcompiler.lib + %(AdditionalDependencies);OneCoreUAP.lib;avrt.lib;bcrypt.lib;d3d12.lib;d3dcompiler.lib SHA1 @@ -334,7 +334,7 @@ $(ProjectDir);$(SolutionDir)LGCommon;$(ProjectDir)..\..\repos\LGMP\lgmp\include;$(ProjectDir)..\..\vendor;$(ProjectDir)..\..\common\include;%(AdditionalIncludeDirectories) - %(AdditionalDependencies);OneCoreUAP.lib;avrt.lib;d3d12.lib;d3dcompiler.lib + %(AdditionalDependencies);OneCoreUAP.lib;avrt.lib;bcrypt.lib;d3d12.lib;d3dcompiler.lib SHA1 diff --git a/idd/LGIdd/LGIdd.vcxproj.filters b/idd/LGIdd/LGIdd.vcxproj.filters index e547c041..f3f650b8 100644 --- a/idd/LGIdd/LGIdd.vcxproj.filters +++ b/idd/LGIdd/LGIdd.vcxproj.filters @@ -58,9 +58,6 @@ Driver - - Driver - Driver diff --git a/idd/LGIdd/Public.h b/idd/LGIdd/Public.h deleted file mode 100644 index 5573dae2..00000000 --- a/idd/LGIdd/Public.h +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Looking Glass - * Copyright © 2017-2026 The Looking Glass Authors - * https://looking-glass.io - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation; either version 2 of the License, or (at your option) - * any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., 59 - * Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -// {997b0b66-b74c-4017-9a89-e4aad41d3780} -DEFINE_GUID (GUID_DEVINTERFACE_LGIdd, 0x997b0b66,0xb74c,0x4017,0x9a,0x89,0xe4,0xaa,0xd4,0x1d,0x37,0x80); \ No newline at end of file diff --git a/idd/LGIdd/ipc/CPipeServer.cpp b/idd/LGIdd/ipc/CPipeServer.cpp index e05f50e2..b96fe1c5 100644 --- a/idd/LGIdd/ipc/CPipeServer.cpp +++ b/idd/LGIdd/ipc/CPipeServer.cpp @@ -19,29 +19,340 @@ */ #include "ipc/CPipeServer.h" +#include "CClipboardRing.h" #include "CDebug.h" #include "CSRWLock.h" #include "display/CDeviceContext.h" +#include +#include + CPipeServer g_pipe; +namespace +{ + static constexpr DWORD NO_CONSOLE_SESSION = 0xFFFFFFFFU; +} + bool CPipeServer::Init() { + DeInit(); + + // Only the driver identities may create/manage the endpoint. Interactive + // users receive client read/write access, then the first HELLO is matched + // against the SYSTEM service's device-bound clipboard authority. + static constexpr wchar_t PIPE_SECURITY[] = + L"D:P(A;;GA;;;SY)(A;;GA;;;LS)(A;;GA;;;NS)(A;;GA;;;UD)" + L"(A;;GRGW;;;IU)"; + if (!ConvertStringSecurityDescriptorToSecurityDescriptorW( + PIPE_SECURITY, SDDL_REVISION_1, + &m_pipeSecurityDescriptor, nullptr)) + { + DEBUG_ERROR_HR(GetLastError(), + "Failed to create named pipe security descriptor"); + return false; + } + m_pipeSecurity.nLength = sizeof(m_pipeSecurity); + m_pipeSecurity.lpSecurityDescriptor = m_pipeSecurityDescriptor; + m_pipeSecurity.bInheritHandle = FALSE; + m_endpoint.SetHandler(this); - return m_endpoint.Start( + if (m_endpoint.Start( LG_PIPE_NAME, CPipeEndpoint::Mode::Server, - sizeof(LGPipeMsg)); + sizeof(LGPipeMsg), + &m_pipeSecurity, + FILE_FLAG_FIRST_PIPE_INSTANCE, + PIPE_REJECT_REMOTE_CLIENTS)) + return true; + + LocalFree(m_pipeSecurityDescriptor); + m_pipeSecurityDescriptor = nullptr; + m_pipeSecurity = {}; + return false; } void CPipeServer::DeInit() { m_endpoint.Stop(); - m_clipboard.Detach(); + ClearClipboardAuthority(); + if (m_pipeSecurityDescriptor) + { + LocalFree(m_pipeSecurityDescriptor); + m_pipeSecurityDescriptor = nullptr; + m_pipeSecurity = {}; + } +} + +bool CPipeServer::RegisterClipboardAuthority(WDFFILEOBJECT owner, + HANDLE mapping, DWORD session, const uint64_t (&mappingId)[2], + const uint64_t (&authorityId)[2]) +{ + if (!owner || !mapping || mapping == INVALID_HANDLE_VALUE || + session == NO_CONSOLE_SESSION || !session || + !mappingId[0] || !mappingId[1] || + !authorityId[0] || !authorityId[1]) + { + DEBUG_WARN( + "Rejected invalid clipboard authority registration"); + return false; + } + + const ClipboardMapping * view = static_cast( + MapViewOfFileFromApp(mapping, FILE_MAP_READ | FILE_MAP_WRITE, 0, + sizeof(ClipboardMapping))); + if (!view) + { + const DWORD error = GetLastError(); + DEBUG_WARN_HR(error, + "Failed to map the clipboard authority section"); + return false; + } + if (!UnmapViewOfFile(view)) + { + const DWORD error = GetLastError(); + DEBUG_WARN_HR(error, + "Failed to unmap the clipboard authority section"); + return false; + } + + CSRWExclusiveLock lock(m_authorityLock); + const LGIddAuthorityFileContext * fileContext = + LGIddAuthorityGetFileContext(owner); + if (fileContext->closing) + { + DEBUG_WARN( + "Rejected clipboard authority registration for a closing file"); + return false; + } + if (m_authorityOwner || m_authorityMapping) + { + DEBUG_WARN( + "Clipboard authority is already registered"); + return false; + } + + m_authorityOwner = owner; + m_authorityMapping = mapping; + m_authoritySession = session; + m_authorityMappingId[0] = mappingId[0]; + m_authorityMappingId[1] = mappingId[1]; + m_authorityId[0] = authorityId[0]; + m_authorityId[1] = authorityId[1]; + DEBUG_INFO("Registered clipboard authority for session %lu", session); + return true; +} + +bool CPipeServer::ClearClipboardAuthority(WDFFILEOBJECT owner) +{ + return ClearClipboardAuthorityInternal(owner, false); +} + +void CPipeServer::CloseClipboardAuthorityFile(WDFFILEOBJECT owner) +{ + (void) ClearClipboardAuthorityInternal(owner, true); +} + +bool CPipeServer::ClearClipboardAuthorityInternal( + WDFFILEOBJECT owner, bool closing) +{ + HANDLE authority = nullptr; + HANDLE pending = nullptr; + { + CSRWExclusiveLock lock(m_authorityLock); + if (closing) + { + if (!owner) + { + DEBUG_WARN( + "Cannot close an unspecified clipboard authority file"); + return false; + } + LGIddAuthorityGetFileContext(owner)->closing = true; + if (m_authorityOwner != owner) + return true; + } + + if (owner && m_authorityOwner && m_authorityOwner != owner) + { + DEBUG_WARN( + "Rejected clipboard authority clear from a different file object"); + return false; + } + + authority = m_authorityMapping; + pending = m_pendingClipboardMapping; + + m_authorityOwner = nullptr; + m_authorityMapping = nullptr; + m_authoritySession = NO_CONSOLE_SESSION; + m_authorityMappingId[0] = 0; + m_authorityMappingId[1] = 0; + m_authorityId[0] = 0; + m_authorityId[1] = 0; + m_pendingClipboardMapping = nullptr; + m_pendingClipboardEpoch = 0; + m_clientAuthorityId[0] = 0; + m_clientAuthorityId[1] = 0; + + m_endpoint.DisconnectClient(); + m_clipboard.Detach(); + } + + if (pending) + CloseHandle(pending); + if (authority) + CloseHandle(authority); + if (authority || pending) + DEBUG_INFO("Cleared clipboard authority"); + return true; +} + +bool CPipeServer::AuthenticatePipeClient( + HANDLE pipe, const void * message, size_t size) +{ + (void)pipe; + if (size != sizeof(LGPipeMsg)) + { + DEBUG_WARN( + "Rejected Helper HELLO frame with %llu bytes, expected %llu", + static_cast(size), + static_cast(sizeof(LGPipeMsg))); + return false; + } + const LGPipeMsg& hello = *static_cast(message); + if (hello.size != sizeof(hello) || hello.type != LGPipeMsg::HELLO || + hello.hello.version != LGPipeMsg::PROTOCOL_VERSION || + !hello.hello.authorityId[0] || !hello.hello.authorityId[1]) + { + DEBUG_WARN( + "Rejected malformed Helper HELLO: size=%u type=%u version=%u", + hello.size, static_cast(hello.type), hello.hello.version); + return false; + } + + CSRWExclusiveLock lock(m_authorityLock); + if (!m_authorityOwner || !m_authorityMapping || + m_authorityId[0] != hello.hello.authorityId[0] || + m_authorityId[1] != hello.hello.authorityId[1]) + { + DEBUG_WARN( + "Named pipe client does not match the clipboard authority"); + return false; + } + + HANDLE mapping = nullptr; + if (!DuplicateHandle(GetCurrentProcess(), m_authorityMapping, + GetCurrentProcess(), &mapping, + SECTION_MAP_READ | SECTION_MAP_WRITE, FALSE, 0)) + { + const DWORD error = GetLastError(); + DEBUG_WARN_HR(error, + "Failed to duplicate the authorized clipboard mapping"); + return false; + } + + const ClipboardMapping * view = static_cast( + MapViewOfFileFromApp(mapping, FILE_MAP_READ | FILE_MAP_WRITE, 0, + sizeof(ClipboardMapping))); + if (!view) + { + const DWORD error = GetLastError(); + DEBUG_WARN_HR(error, + "Failed to validate the registered clipboard mapping"); + CloseHandle(mapping); + return false; + } + + const uint64_t epoch = view->epoch; + const bool valid = CClipboardRing::Valid(*view, epoch); + if (!UnmapViewOfFile(view)) + { + const DWORD error = GetLastError(); + DEBUG_WARN_HR(error, + "Failed to unmap the validated clipboard mapping"); + CloseHandle(mapping); + return false; + } + if (!valid || !epoch) + { + DEBUG_WARN( + "Rejected an uninitialized registered clipboard mapping"); + CloseHandle(mapping); + return false; + } + + if (m_pendingClipboardMapping) + CloseHandle(m_pendingClipboardMapping); + m_pendingClipboardMapping = mapping; + m_pendingClipboardEpoch = epoch; + m_clientAuthorityId[0] = hello.hello.authorityId[0]; + m_clientAuthorityId[1] = hello.hello.authorityId[1]; + DEBUG_INFO("Authenticated clipboard Helper for session %lu", + m_authoritySession); + return true; +} + +bool CPipeServer::PipeClientStillAuthorized(HANDLE pipe) +{ + (void)pipe; + CSRWSharedLock lock(m_authorityLock); + if (!m_authorityOwner || !m_authorityMapping || + !m_clientAuthorityId[0] || !m_clientAuthorityId[1]) + { + DEBUG_WARN( + "Named pipe client authorization state is incomplete"); + return false; + } + if (m_clientAuthorityId[0] != m_authorityId[0] || + m_clientAuthorityId[1] != m_authorityId[1]) + { + DEBUG_WARN( + "Named pipe client session authorization expired"); + return false; + } + return true; } void CPipeServer::OnPipeConnected() { + if (!PipeClientStillAuthorized(m_endpoint.NativeHandle())) + { + DEBUG_WARN("Named pipe client authorization expired before activation"); + return; + } + + uint64_t epoch = 0; + bool ready = false; + { + CSRWExclusiveLock lock(m_authorityLock); + HANDLE mapping = m_pendingClipboardMapping; + epoch = m_pendingClipboardEpoch; + m_pendingClipboardMapping = nullptr; + m_pendingClipboardEpoch = 0; + + const bool authorityMatches = m_authorityOwner && + m_authorityMapping && + m_authorityId[0] == m_clientAuthorityId[0] && + m_authorityId[1] == m_clientAuthorityId[1]; + if (authorityMatches && mapping) + ready = m_clipboard.Attach(mapping, epoch, false, *this); + else + { + if (mapping) + CloseHandle(mapping); + DEBUG_ERROR("Authenticated clipboard mapping is missing or expired"); + } + } + + LGPipeMsg clipboardReady = {}; + clipboardReady.size = sizeof(clipboardReady); + clipboardReady.type = LGPipeMsg::CLIPBOARD_READY; + clipboardReady.clipboardReady.epoch = epoch; + clipboardReady.clipboardReady.status = ready ? + ERROR_SUCCESS : ERROR_INVALID_DATA; + m_endpoint.Send(&clipboardReady, sizeof(clipboardReady)); + CSRWExclusiveLock lock(m_queueLock); std::vector queued; queued.swap(m_queue); @@ -63,7 +374,16 @@ void CPipeServer::OnPipeConnected() void CPipeServer::OnPipeDisconnected() { - m_clipboard.Detach(); + { + CSRWExclusiveLock lock(m_authorityLock); + m_clipboard.Detach(); + if (m_pendingClipboardMapping) + CloseHandle(m_pendingClipboardMapping); + m_pendingClipboardMapping = nullptr; + m_pendingClipboardEpoch = 0; + m_clientAuthorityId[0] = 0; + m_clientAuthorityId[1] = 0; + } } bool CPipeServer::OnPipeMessage(const void * message, size_t size) @@ -89,51 +409,23 @@ bool CPipeServer::OnPipeMessage(const void * message, size_t size) return true; case LGPipeMsg::CLIPBOARD_SETUP: - { - HANDLE transferred = reinterpret_cast( - static_cast(msg.clipboardSetup.handle)); - HANDLE mapping = transferred; - uint64_t epoch = 0; - if (transferred && - msg.clipboardSetup.bytes == sizeof(ClipboardMapping)) - { - ClipboardMapping * view = static_cast( - MapViewOfFile(mapping, FILE_MAP_READ, 0, 0, - sizeof(ClipboardMapping))); - if (view) - { - epoch = view->epoch; - UnmapViewOfFile(view); - } - } - else if (transferred) - { - CloseHandle(transferred); - mapping = nullptr; - } - - // The transferred handle has exactly one owner from this point: - // Attach consumes it on both success and failure paths. - const bool ready = m_clipboard.Attach( - mapping, epoch, false, *this); - LGPipeMsg reply = {}; - reply.size = sizeof(reply); - reply.type = LGPipeMsg::CLIPBOARD_READY; - reply.clipboardReady.epoch = epoch; - reply.clipboardReady.status = ready ? ERROR_SUCCESS : ERROR_INVALID_DATA; - m_endpoint.Send(&reply, sizeof(reply)); - return true; - } + // Legacy SETUP names a handle in this process. Never interpret a value + // supplied by an unprivileged client as a local driver handle. + DEBUG_WARN("Rejected legacy clipboard mapping setup"); + return false; case LGPipeMsg::CLIPBOARD_READY: // READY normally travels IDD to Helper. A failure in the reverse // direction reports that Helper activation failed after IDD attach. - if (msg.clipboardReady.status != ERROR_SUCCESS && - msg.clipboardReady.epoch == m_clipboard.Epoch()) + if (msg.clipboardReady.status != ERROR_SUCCESS) { - m_clipboard.Reset( - msg.clipboardReady.epoch, msg.clipboardReady.status); - m_clipboard.Detach(); + CSRWExclusiveLock lock(m_authorityLock); + if (msg.clipboardReady.epoch == m_clipboard.Epoch()) + { + m_clipboard.Reset( + msg.clipboardReady.epoch, msg.clipboardReady.status); + m_clipboard.Detach(); + } } return true; @@ -148,7 +440,7 @@ bool CPipeServer::OnPipeMessage(const void * message, size_t size) default: DEBUG_ERROR("Unknown message type %d", msg.type); - return true; + return false; } } diff --git a/idd/LGIdd/ipc/CPipeServer.h b/idd/LGIdd/ipc/CPipeServer.h index 3a2761f5..90ee0176 100644 --- a/idd/LGIdd/ipc/CPipeServer.h +++ b/idd/LGIdd/ipc/CPipeServer.h @@ -32,6 +32,14 @@ class CDeviceContext; +struct LGIddAuthorityFileContext +{ + bool closing; +}; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME( + LGIddAuthorityFileContext, LGIddAuthorityGetFileContext) + class CPipeServer : private IPipeEndpointHandler, public IClipboardChannelDoorbell { @@ -56,14 +64,33 @@ class CPipeServer : private IPipeEndpointHandler, void * m_recoveryOpaque = nullptr; uint64_t m_recoveryRoute = 0; + CSRWLock m_authorityLock; + WDFFILEOBJECT m_authorityOwner = nullptr; + HANDLE m_authorityMapping = nullptr; + DWORD m_authoritySession = 0xFFFFFFFFU; + uint64_t m_authorityMappingId[2] = {}; + uint64_t m_authorityId[2] = {}; + + PSECURITY_DESCRIPTOR m_pipeSecurityDescriptor = nullptr; + SECURITY_ATTRIBUTES m_pipeSecurity = {}; + HANDLE m_pendingClipboardMapping = nullptr; + uint64_t m_pendingClipboardEpoch = 0; + uint64_t m_clientAuthorityId[2] = {}; + void WriteMsg(const LGPipeMsg & msg); void QueueMsgLocked(const LGPipeMsg & msg); void HandleReloadSettings(); void HandleRecovery(const LGPipeMsg & msg); + bool ClearClipboardAuthorityInternal( + WDFFILEOBJECT owner, bool closing); void OnPipeConnected() override; void OnPipeDisconnected() override; + bool PipeClientAuthenticationRequired() const override { return true; } + bool AuthenticatePipeClient(HANDLE pipe, + const void * message, size_t size) override; + bool PipeClientStillAuthorized(HANDLE pipe) override; bool OnPipeMessage(const void * message, size_t size) override; public: @@ -76,6 +103,12 @@ class CPipeServer : private IPipeEndpointHandler, void SetRecoveryHandler(RecoveryHandler handler, void * opaque); void ClearRecoveryHandler(void * opaque); + bool RegisterClipboardAuthority(WDFFILEOBJECT owner, HANDLE mapping, + DWORD session, const uint64_t (&mappingId)[2], + const uint64_t (&authorityId)[2]); + bool ClearClipboardAuthority(WDFFILEOBJECT owner = nullptr); + void CloseClipboardAuthorityFile(WDFFILEOBJECT owner); + bool SetCursorPos(int32_t x, int32_t y); void SetDisplayMode( uint32_t width, uint32_t height, uint32_t refreshMilliHz); diff --git a/idd/LGIddHelper/CClipboardFiles.cpp b/idd/LGIddHelper/CClipboardFiles.cpp index e1a47618..bd1a784e 100644 --- a/idd/LGIddHelper/CClipboardFiles.cpp +++ b/idd/LGIddHelper/CClipboardFiles.cpp @@ -22,7 +22,6 @@ #include #include -#include #include #include @@ -41,7 +40,7 @@ namespace static constexpr uint64_t WINDOWS_EPOCH_TICKS = UINT64_C(116444736000000000); static constexpr size_t COPY_BUFFER_BYTES = - static_cast(64U) * 1024U; + KVMFR_CLIPBOARD_FILE_READ_BYTES; class CThreadImpersonation final { @@ -165,14 +164,6 @@ namespace { token = nullptr; winError = ERROR_SUCCESS; - DWORD sessionId = 0; - if (!ProcessIdToSessionId(GetCurrentProcessId(), &sessionId)) - { - winError = GetLastError(); - error = TokenError(winError); - return false; - } - HANDLE processToken = nullptr; if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_DUPLICATE, &processToken)) @@ -182,83 +173,11 @@ namespace return false; } - HANDLE brokerToken = nullptr; - const bool brokerDuplicated = DuplicateTokenEx(processToken, - TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES | TOKEN_IMPERSONATE, nullptr, - SecurityImpersonation, TokenImpersonation, &brokerToken) != FALSE; - const DWORD brokerError = brokerDuplicated ? ERROR_SUCCESS : - GetLastError(); - CloseHandle(processToken); - if (!brokerDuplicated) - { - winError = brokerError; - error = TokenError(brokerError); - return false; - } - - LUID privilege = {}; - if (!LookupPrivilegeValueW(nullptr, SE_TCB_NAME, &privilege)) - { - winError = GetLastError(); - CloseHandle(brokerToken); - error = TokenError(winError); - return false; - } - - TOKEN_PRIVILEGES privileges = {}; - privileges.PrivilegeCount = 1; - privileges.Privileges[0].Luid = privilege; - privileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; - SetLastError(ERROR_SUCCESS); - const bool adjusted = AdjustTokenPrivileges(brokerToken, FALSE, - &privileges, 0, nullptr, nullptr) != FALSE; - const DWORD adjustError = GetLastError(); - if (!adjusted || adjustError != ERROR_SUCCESS) - { - CloseHandle(brokerToken); - winError = adjustError ? adjustError : ERROR_ACCESS_DENIED; - error = TokenError(winError); - return false; - } - - CThreadImpersonation brokerImpersonation(brokerToken); - if (!brokerImpersonation.Active()) - { - const DWORD brokerImpersonationError = brokerImpersonation.Error(); - CloseHandle(brokerToken); - winError = brokerImpersonationError; - error = TokenError(winError); - return false; - } - - HANDLE sourceToken = nullptr; - const bool queried = WTSQueryUserToken(sessionId, &sourceToken) != FALSE; - const DWORD queryError = queried ? ERROR_SUCCESS : GetLastError(); - if (!brokerImpersonation.Finish()) - { - const DWORD restoreError = brokerImpersonation.Error(); - if (sourceToken) - CloseHandle(sourceToken); - CloseHandle(brokerToken); - winError = restoreError; - error = TokenError(winError); - return false; - } - CloseHandle(brokerToken); - if (!queried || !sourceToken) - { - if (sourceToken) - CloseHandle(sourceToken); - winError = queryError ? queryError : ERROR_ACCESS_DENIED; - error = TokenError(winError); - return false; - } - - const bool duplicated = DuplicateTokenEx(sourceToken, + const bool duplicated = DuplicateTokenEx(processToken, TOKEN_QUERY | TOKEN_IMPERSONATE, nullptr, SecurityImpersonation, TokenImpersonation, &token) != FALSE; const DWORD duplicateError = duplicated ? ERROR_SUCCESS : GetLastError(); - CloseHandle(sourceToken); + CloseHandle(processToken); if (!duplicated) { winError = duplicateError; @@ -1162,61 +1081,6 @@ namespace }; } -class CClipboardUserImpersonation::Impl -{ -public: - HANDLE token; - CThreadImpersonation impersonation; - - explicit Impl(HANDLE token) : - token(token), impersonation(token) - { - } - - ~Impl() - { - impersonation.Finish(); - CloseHandle(token); - } -}; - -CClipboardUserImpersonation::CClipboardUserImpersonation( - KVMFRClipboardFileError& error) -{ - HANDLE token = nullptr; - if (!CaptureUserToken(token, error, m_error)) - return; - try - { - m_impl = std::make_unique(token); - } - catch (const std::bad_alloc&) - { - CloseHandle(token); - m_error = ERROR_OUTOFMEMORY; - error = KVMFR_CLIPBOARD_FILE_ERROR_NO_MEMORY; - return; - } - if (!m_impl->impersonation.Active()) - { - m_error = m_impl->impersonation.Error(); - m_impl.reset(); - error = TokenError(m_error); - } -} - -CClipboardUserImpersonation::~CClipboardUserImpersonation() = default; - -bool CClipboardUserImpersonation::Active() const -{ - return m_impl && m_impl->impersonation.Active(); -} - -DWORD CClipboardUserImpersonation::Error() const -{ - return m_error; -} - CLocalClipboardFiles::CLocalClipboardFiles(HANDLE userToken) : m_userToken(userToken) { diff --git a/idd/LGIddHelper/CClipboardFiles.h b/idd/LGIddHelper/CClipboardFiles.h index 45237c7e..4dea7fbb 100644 --- a/idd/LGIddHelper/CClipboardFiles.h +++ b/idd/LGIddHelper/CClipboardFiles.h @@ -45,25 +45,6 @@ struct ClipboardRemoteFileEntry std::wstring name; }; -class CClipboardUserImpersonation final -{ -private: - class Impl; - std::unique_ptr m_impl; - DWORD m_error = ERROR_SUCCESS; - -public: - explicit CClipboardUserImpersonation(KVMFRClipboardFileError& error); - ~CClipboardUserImpersonation(); - - CClipboardUserImpersonation(const CClipboardUserImpersonation&) = delete; - CClipboardUserImpersonation& operator=( - const CClipboardUserImpersonation&) = delete; - - bool Active() const; - DWORD Error() const; -}; - class CLocalClipboardFiles final { public: diff --git a/idd/LGIddHelper/CClipboardManager.cpp b/idd/LGIddHelper/CClipboardManager.cpp index 2ae8a237..f3d7b7be 100644 --- a/idd/LGIddHelper/CClipboardManager.cpp +++ b/idd/LGIddHelper/CClipboardManager.cpp @@ -79,12 +79,6 @@ namespace ~ComScope() { if (initialized) CoUninitialize(); } }; - struct ClipboardDataObjectScope - { - IDataObject * object = nullptr; - ~ClipboardDataObjectScope() { if (object) object->Release(); } - }; - struct ClipboardStorageScope { STGMEDIUM medium = {}; @@ -106,7 +100,14 @@ namespace ~ClipboardTaskStringScope() { CoTaskMemFree(value); } }; - bool ClipboardDataObjectHasFileFormats(IDataObject * object, + enum ClipboardFileCandidate : uint32_t + { + CLIPBOARD_FILE_CANDIDATE_NONE = 0, + CLIPBOARD_FILE_CANDIDATE_HDROP = 1U << 0, + CLIPBOARD_FILE_CANDIDATE_SHELL = 1U << 1, + }; + + uint32_t ClipboardDataObjectFileCandidates(IDataObject * object, DWORD sequence, HRESULT& enumError) { enumError = E_INVALIDARG; @@ -116,7 +117,7 @@ namespace "Failed to inspect local clipboard file formats: " "stage=IDataObject sequence=%lu", static_cast(sequence)); - return false; + return CLIPBOARD_FILE_CANDIDATE_NONE; } ClipboardComScope formats; @@ -129,15 +130,17 @@ namespace "Failed to inspect local clipboard file formats: " "stage=IDataObject::EnumFormatEtc sequence=%lu", static_cast(sequence)); - return false; + return CLIPBOARD_FILE_CANDIDATE_NONE; } + HRESULT registrationError = S_OK; const UINT shellIDList = RegisterClipboardFormatW(CFSTR_SHELLIDLIST); if (!shellIDList) { const DWORD formatError = GetLastError(); - DEBUG_ERROR_HR(formatError ? HRESULT_FROM_WIN32(formatError) : - E_UNEXPECTED, + registrationError = formatError ? HRESULT_FROM_WIN32(formatError) : + E_UNEXPECTED; + DEBUG_ERROR_HR(registrationError, "Failed to inspect local clipboard file formats: " "stage=RegisterClipboardFormatW(CFSTR_SHELLIDLIST) sequence=%lu", static_cast(sequence)); @@ -147,39 +150,44 @@ namespace if (!fileDescriptor) { const DWORD formatError = GetLastError(); - DEBUG_ERROR_HR(formatError ? HRESULT_FROM_WIN32(formatError) : - E_UNEXPECTED, + const HRESULT formatHRESULT = formatError ? + HRESULT_FROM_WIN32(formatError) : E_UNEXPECTED; + if (SUCCEEDED(registrationError)) + registrationError = formatHRESULT; + DEBUG_ERROR_HR(formatHRESULT, "Failed to inspect local clipboard file formats: " "stage=RegisterClipboardFormatW(CFSTR_FILEDESCRIPTORW) sequence=%lu", static_cast(sequence)); } + + uint32_t candidates = CLIPBOARD_FILE_CANDIDATE_NONE; for (;;) { FORMATETC format = {}; ULONG fetched = 0; enumError = formats.object->Next(1, &format, &fetched); - const bool files = fetched == 1U && - (format.cfFormat == CF_HDROP || - (shellIDList && format.cfFormat == shellIDList) || - (fileDescriptor && format.cfFormat == fileDescriptor)); - CoTaskMemFree(format.ptd); - if (files) - { - enumError = S_OK; - return true; - } - if (enumError == S_FALSE) - { - enumError = S_OK; - return false; - } if (FAILED(enumError)) { DEBUG_ERROR_HR(enumError, "Failed to inspect local clipboard file formats: " "stage=IEnumFORMATETC::Next sequence=%lu", static_cast(sequence)); - return false; + CoTaskMemFree(format.ptd); + return candidates; + } + if (fetched == 1U) + { + if (format.cfFormat == CF_HDROP) + candidates |= CLIPBOARD_FILE_CANDIDATE_HDROP; + if ((shellIDList && format.cfFormat == shellIDList) || + (fileDescriptor && format.cfFormat == fileDescriptor)) + candidates |= CLIPBOARD_FILE_CANDIDATE_SHELL; + } + CoTaskMemFree(format.ptd); + if (enumError == S_FALSE) + { + enumError = registrationError; + return candidates; } if (!fetched) { @@ -188,11 +196,40 @@ namespace "Failed to inspect local clipboard file formats: " "stage=IEnumFORMATETC::Next sequence=%lu fetched=0", static_cast(sequence)); - return false; + return candidates; } } } + int CountClipboardFormatsLogged(const char * stage, DWORD sequence) + { + SetLastError(ERROR_SUCCESS); + const int count = CountClipboardFormats(); + if (count) + return count; + + const DWORD error = GetLastError(); + if (error) + DEBUG_ERROR_HR(HRESULT_FROM_WIN32(error), + "Failed to count clipboard formats: stage=%s sequence=%lu", + stage, static_cast(sequence)); + return 0; + } + + void FreeClipboardDrop(HGLOBAL memory, DWORD sequence) + { + SetLastError(ERROR_SUCCESS); + const HGLOBAL result = GlobalFree(memory); + if (!result) + return; + + const DWORD error = GetLastError(); + DEBUG_ERROR_HR(error ? HRESULT_FROM_WIN32(error) : E_UNEXPECTED, + "Failed to capture local clipboard files: " + "stage=GlobalFree(DROPFILES) sequence=%lu", + static_cast(sequence)); + } + HRESULT ClipboardFileHRESULT(KVMFRClipboardFileError error) { switch (error) @@ -3281,32 +3318,24 @@ void CClipboardManager::HandleFileDataObject(UIWork& work) m_ownedSequence = GetClipboardSequenceNumber(); } -bool CClipboardManager::OpenClipboardRetry(DWORD * error, - const char * stage, bool useWindow) const +HRESULT CClipboardManager::OpenClipboardRetry(const char * stage) const { + HRESULT result = CLIPBRD_E_CANT_OPEN; for (unsigned int attempt = 0; attempt != 8; ++attempt) { - if (OpenClipboard(useWindow ? m_hwnd : nullptr)) - { - if (error) - *error = ERROR_SUCCESS; - return true; - } + if (OpenClipboard(m_hwnd)) + return S_OK; const DWORD openError = GetLastError(); - DEBUG_ERROR_HR(openError ? HRESULT_FROM_WIN32(openError) : - CLIPBRD_E_CANT_OPEN, + result = openError ? HRESULT_FROM_WIN32(openError) : + CLIPBRD_E_CANT_OPEN; + DEBUG_ERROR_HR(result, "OpenClipboard failed: stage=%s attempt=%u", - stage ? stage : "unspecified", attempt + 1U); + stage, attempt + 1U); if (attempt + 1U == 8U) - { - if (error) - *error = openError; - SetLastError(openError); - return false; - } + return result; Sleep(5U << (std::min)(attempt, 5U)); } - return false; + return result; } bool CClipboardManager::IsOurClipboard() @@ -3323,7 +3352,8 @@ bool CClipboardManager::IsOurClipboard() } if (!m_formatOrigin || !m_remoteGeneration || - !IsClipboardFormatAvailable(m_formatOrigin) || !OpenClipboardRetry()) + !IsClipboardFormatAvailable(m_formatOrigin) || + FAILED(OpenClipboardRetry("IsOurClipboard"))) return false; bool ours = false; @@ -3383,26 +3413,10 @@ CClipboardManager::CaptureClipboardFiles(DWORD sequence, int rawFormatCount, return nullptr; } - CClipboardUserImpersonation user(error); - if (!user.Active()) - { - const DWORD userError = user.Error(); - oleError = userError ? HRESULT_FROM_WIN32(userError) : - ClipboardFileHRESULT(error); - DEBUG_ERROR_HR(oleError, - "Failed to capture local clipboard files: " - "stage=impersonate-interactive-user sequence=%lu rawFormats=%d " - "fileError=%u", static_cast(sequence), - rawFormatCount, static_cast(error)); - return nullptr; - } - // Acquire the clipboard before probing CF_HDROP. Explorer can still hold // the clipboard when WM_CLIPBOARDUPDATE is delivered, in which case an // unlocked IsClipboardFormatAvailable probe can observe no formats. - DWORD openError = ERROR_SUCCESS; - if (!OpenClipboardRetry(&openError, "CaptureClipboardFiles(CF_HDROP)", - false)) + if (FAILED(OpenClipboardRetry("CaptureClipboardFiles(CF_HDROP)"))) { oleError = CLIPBRD_E_CANT_OPEN; retryStage = "OpenClipboard"; @@ -3415,7 +3429,8 @@ CClipboardManager::CaptureClipboardFiles(DWORD sequence, int rawFormatCount, int openedRawFormatCount = 0; if (GetClipboardSequenceNumber() == sequence) { - openedRawFormatCount = CountClipboardFormats(); + openedRawFormatCount = CountClipboardFormatsLogged( + "CaptureClipboardFiles(CF_HDROP)", sequence); win32Candidate = IsClipboardFormatAvailable(CF_HDROP) != FALSE; if (win32Candidate) { @@ -3463,14 +3478,19 @@ CClipboardManager::CaptureClipboardFiles(DWORD sequence, int rawFormatCount, static_cast(GetClipboardSequenceNumber())); } - if (!CloseClipboard()) + const BOOL closed = CloseClipboard(); + if (!closed) { const DWORD closeError = GetLastError(); - DEBUG_ERROR_HR(closeError ? HRESULT_FROM_WIN32(closeError) : - CLIPBRD_E_CANT_CLOSE, + oleError = closeError ? HRESULT_FROM_WIN32(closeError) : + CLIPBRD_E_CANT_CLOSE; + error = closeError == ERROR_ACCESS_DENIED ? + KVMFR_CLIPBOARD_FILE_ERROR_ACCESS : KVMFR_CLIPBOARD_FILE_ERROR_IO; + DEBUG_ERROR_HR(oleError, "Failed to capture local clipboard files: stage=CloseClipboard " "sequence=%lu rawFormats=%d", static_cast(sequence), rawFormatCount); + return nullptr; } if (files) return files; @@ -3488,7 +3508,7 @@ CClipboardManager::CaptureClipboardFiles(DWORD sequence, int rawFormatCount, return nullptr; } - ClipboardDataObjectScope object; + ClipboardComScope object; oleError = OleGetClipboard(&object.object); if (FAILED(oleError) || !object.object) { @@ -3503,66 +3523,94 @@ CClipboardManager::CaptureClipboardFiles(DWORD sequence, int rawFormatCount, return nullptr; } - FORMATETC format = { - static_cast(CF_HDROP), - nullptr, - DVASPECT_CONTENT, - -1, - TYMED_HGLOBAL, - }; - ClipboardStorageScope storage; - const HRESULT hdropError = object.object->GetData( - &format, &storage.medium); - if (FAILED(hdropError)) + HRESULT candidateError = S_OK; + const uint32_t candidates = ClipboardDataObjectFileCandidates( + object.object, sequence, candidateError); + const bool optimistic = FAILED(candidateError); + if (!optimistic && !win32Candidate && + candidates == CLIPBOARD_FILE_CANDIDATE_NONE) { - DEBUG_ERROR_HR(hdropError, - "Failed to capture local clipboard files: " - "stage=IDataObject::GetData(CF_HDROP) sequence=%lu rawFormats=%d", - static_cast(sequence), rawFormatCount); - } - if (hdropError == CLIPBRD_E_CANT_OPEN) - { - oleError = hdropError; - retryStage = "IDataObject::GetData(CF_HDROP)"; + error = KVMFR_CLIPBOARD_FILE_ERROR_NONE; + oleError = S_FALSE; return nullptr; } - if (SUCCEEDED(hdropError)) + HRESULT hdropError = S_FALSE; + bool hdropAttempted = false; + if (optimistic || win32Candidate || + (candidates & CLIPBOARD_FILE_CANDIDATE_HDROP)) { - storage.acquired = true; - if (storage.medium.tymed == TYMED_HGLOBAL && storage.medium.hGlobal) + hdropAttempted = true; + FORMATETC format = { + static_cast(CF_HDROP), + nullptr, + DVASPECT_CONTENT, + -1, + TYMED_HGLOBAL, + }; + ClipboardStorageScope storage; + hdropError = object.object->GetData(&format, &storage.medium); + if (FAILED(hdropError)) { - std::shared_ptr files = - CLocalClipboardFiles::Capture( - static_cast(storage.medium.hGlobal), error); - if (files) - { - viaOLE = true; - oleError = S_OK; - return files; - } - oleError = ClipboardFileHRESULT(error); - DEBUG_ERROR_HR(oleError, + DEBUG_ERROR_HR(hdropError, "Failed to capture local clipboard files: " - "stage=CLocalClipboardFiles::Capture(CF_HDROP/ole) sequence=%lu " - "rawFormats=%d fileError=%u", - static_cast(sequence), rawFormatCount, - static_cast(error)); - return nullptr; + "stage=IDataObject::GetData(CF_HDROP) sequence=%lu rawFormats=%d", + static_cast(sequence), rawFormatCount); + if (hdropError == CLIPBRD_E_CANT_OPEN) + { + oleError = hdropError; + retryStage = "IDataObject::GetData(CF_HDROP)"; + return nullptr; + } + } + else + { + storage.acquired = true; + if (storage.medium.tymed != TYMED_HGLOBAL || + !storage.medium.hGlobal) + { + oleError = DV_E_TYMED; + error = KVMFR_CLIPBOARD_FILE_ERROR_INVALID; + DEBUG_ERROR_HR(oleError, + "Failed to capture local clipboard files: " + "stage=IDataObject::GetData(CF_HDROP)/STGMEDIUM sequence=%lu " + "rawFormats=%d tymed=0x%08lx hasHGlobal=%u", + static_cast(sequence), rawFormatCount, + static_cast(storage.medium.tymed), + storage.medium.hGlobal ? 1U : 0U); + return nullptr; + } + + files = CLocalClipboardFiles::Capture( + static_cast(storage.medium.hGlobal), error); + if (!files) + { + oleError = ClipboardFileHRESULT(error); + DEBUG_ERROR_HR(oleError, + "Failed to capture local clipboard files: " + "stage=CLocalClipboardFiles::Capture(CF_HDROP/ole) sequence=%lu " + "rawFormats=%d fileError=%u", + static_cast(sequence), rawFormatCount, + static_cast(error)); + return nullptr; + } + + viaOLE = true; + oleError = S_OK; + return files; } } - if (SUCCEEDED(hdropError)) + if (!optimistic && + !(candidates & CLIPBOARD_FILE_CANDIDATE_SHELL)) { - oleError = DV_E_TYMED; - error = KVMFR_CLIPBOARD_FILE_ERROR_INVALID; - DEBUG_ERROR_HR(oleError, - "Failed to capture local clipboard files: " - "stage=IDataObject::GetData(CF_HDROP)/STGMEDIUM sequence=%lu " - "rawFormats=%d tymed=0x%08lx hasHGlobal=%u", - static_cast(sequence), rawFormatCount, - static_cast(storage.medium.tymed), - storage.medium.hGlobal ? 1U : 0U); + if (hdropAttempted) + oleError = hdropError; + else + { + error = KVMFR_CLIPBOARD_FILE_ERROR_NONE; + oleError = S_FALSE; + } return nullptr; } @@ -3589,17 +3637,6 @@ CClipboardManager::CaptureClipboardFiles(DWORD sequence, int rawFormatCount, oleError = FAILED(shellError) ? shellError : E_UNEXPECTED; if (oleError == CLIPBRD_E_CANT_OPEN) retryStage = "SHCreateShellItemArrayFromDataObject"; - HRESULT enumError = S_OK; - const bool fileCandidate = win32Candidate || - hdropError != DV_E_FORMATETC || - ClipboardDataObjectHasFileFormats(object.object, sequence, enumError); - if (!fileCandidate && SUCCEEDED(enumError) && - oleError != CLIPBRD_E_CANT_OPEN) - { - error = KVMFR_CLIPBOARD_FILE_ERROR_NONE; - oleError = S_FALSE; - return nullptr; - } return nullptr; } @@ -3707,16 +3744,7 @@ CClipboardManager::CaptureClipboardFiles(DWORD sequence, int rawFormatCount, DEBUG_ERROR_HR(oleError, "Failed to capture local clipboard files: stage=GlobalLock(DROPFILES) " "sequence=%lu", static_cast(sequence)); - SetLastError(ERROR_SUCCESS); - HGLOBAL freeResult = GlobalFree(drop); - if (freeResult) - { - const DWORD freeError = GetLastError(); - DEBUG_ERROR_HR(freeError ? HRESULT_FROM_WIN32(freeError) : E_UNEXPECTED, - "Failed to capture local clipboard files: " - "stage=GlobalFree(DROPFILES) sequence=%lu", - static_cast(sequence)); - } + FreeClipboardDrop(drop, sequence); return nullptr; } header->pFiles = sizeof(*header); @@ -3733,10 +3761,15 @@ CClipboardManager::CaptureClipboardFiles(DWORD sequence, int rawFormatCount, const DWORD unlockError = unlocked ? ERROR_SUCCESS : GetLastError(); if (!unlocked && unlockError != ERROR_SUCCESS) { - DEBUG_ERROR_HR(HRESULT_FROM_WIN32(unlockError), + oleError = HRESULT_FROM_WIN32(unlockError); + error = unlockError == ERROR_ACCESS_DENIED ? + KVMFR_CLIPBOARD_FILE_ERROR_ACCESS : KVMFR_CLIPBOARD_FILE_ERROR_IO; + DEBUG_ERROR_HR(oleError, "Failed to capture local clipboard files: " "stage=GlobalUnlock(DROPFILES) sequence=%lu", static_cast(sequence)); + FreeClipboardDrop(drop, sequence); + return nullptr; } files = CLocalClipboardFiles::Capture(static_cast(drop), error); @@ -3749,16 +3782,7 @@ CClipboardManager::CaptureClipboardFiles(DWORD sequence, int rawFormatCount, "fileError=%u", static_cast(sequence), static_cast(error)); } - SetLastError(ERROR_SUCCESS); - HGLOBAL freeResult = GlobalFree(drop); - if (freeResult) - { - const DWORD freeError = GetLastError(); - DEBUG_ERROR_HR(freeError ? HRESULT_FROM_WIN32(freeError) : E_UNEXPECTED, - "Failed to capture local clipboard files: " - "stage=GlobalFree(DROPFILES) sequence=%lu", - static_cast(sequence)); - } + FreeClipboardDrop(drop, sequence); if (!files) return nullptr; viaOLE = true; @@ -3829,7 +3853,8 @@ void CClipboardManager::PublishLocalClipboard() const DWORD before = GetClipboardSequenceNumber(); uint32_t formats = EnumerateFormats(); const uint32_t recognizedFormats = formats; - const int rawFormatCount = CountClipboardFormats(); + const int rawFormatCount = CountClipboardFormatsLogged( + "PublishLocalClipboard", before); const DWORD after = GetClipboardSequenceNumber(); if (before != after) { @@ -4132,11 +4157,8 @@ bool CClipboardManager::ApplyRemoteOffer(uint32_t formats, m_oleClipboard->Release(); m_oleClipboard = nullptr; } - if (!OpenClipboardRetry()) - { - DEBUG_WARN_HR(GetLastError(), "Failed to open the clipboard"); + if (FAILED(OpenClipboardRetry("ApplyRemoteOffer"))) return false; - } m_applyingRemote = true; const bool emptied = EmptyClipboard() != FALSE; @@ -4200,7 +4222,8 @@ bool CClipboardManager::ApplyRemoteOffer(uint32_t formats, if (!error) error = ERROR_INVALID_DATA; SetLastError(error); - DEBUG_WARN_HR(error, "Failed to replace the clipboard"); + DEBUG_WARN_HR(HRESULT_FROM_WIN32(error), + "Failed to replace the clipboard"); return false; } m_ownedSequence = GetClipboardSequenceNumber(); @@ -4217,7 +4240,8 @@ void CClipboardManager::ClearOwnedClipboard() m_oleClipboard->Release(); m_oleClipboard = nullptr; } - if (m_hwnd && GetClipboardOwner() == m_hwnd && OpenClipboardRetry()) + if (m_hwnd && GetClipboardOwner() == m_hwnd && + SUCCEEDED(OpenClipboardRetry("ClearOwnedClipboard"))) { m_applyingRemote = true; EmptyClipboard(); @@ -4271,8 +4295,14 @@ std::shared_ptr CClipboardManager::CaptureFormat( SetLastError(ERROR_RETRY); return nullptr; } - if (!OpenClipboardRetry()) + const HRESULT openResult = OpenClipboardRetry("CaptureFormat"); + if (FAILED(openResult)) + { + const DWORD openError = HRESULT_FACILITY(openResult) == FACILITY_WIN32 ? + static_cast(HRESULT_CODE(openResult)) : ERROR_BUSY; + SetLastError(openError); return nullptr; + } if (GetClipboardSequenceNumber() != sequence) { @@ -4576,7 +4606,7 @@ void CClipboardManager::RenderFormat(UINT windowsFormat, uint64_t deadline) void CClipboardManager::RenderAllFormats() { - if (!OpenClipboardRetry()) + if (FAILED(OpenClipboardRetry("RenderAllFormats"))) return; if (GetClipboardOwner() != m_hwnd) { diff --git a/idd/LGIddHelper/CClipboardManager.h b/idd/LGIddHelper/CClipboardManager.h index de166823..21bae4a8 100644 --- a/idd/LGIddHelper/CClipboardManager.h +++ b/idd/LGIddHelper/CClipboardManager.h @@ -295,8 +295,7 @@ private: void RetryLocalClipboard(); void RetryRemoteOffer(); - bool OpenClipboardRetry(DWORD * error = nullptr, - const char * stage = nullptr, bool useWindow = true) const; + HRESULT OpenClipboardRetry(const char * stage) const; bool IsOurClipboard(); uint32_t EnumerateFormats() const; std::shared_ptr CaptureClipboardFiles( diff --git a/idd/LGIddHelper/CConfigWindow.cpp b/idd/LGIddHelper/CConfigWindow.cpp index 63d45bba..63636ecc 100644 --- a/idd/LGIddHelper/CConfigWindow.cpp +++ b/idd/LGIddHelper/CConfigWindow.cpp @@ -45,7 +45,7 @@ bool CConfigWindow::registerClass() CConfigWindow::CConfigWindow() : m_scale(1) { - LSTATUS error = m_settings.open(); + LSTATUS error = m_settings.open(true); if (error != ERROR_SUCCESS) DEBUG_ERROR_HR(error, "Failed to load settings"); else diff --git a/idd/LGIddHelper/CPipeClient.cpp b/idd/LGIddHelper/CPipeClient.cpp index 2072f8bb..d0b8200d 100644 --- a/idd/LGIddHelper/CPipeClient.cpp +++ b/idd/LGIddHelper/CPipeClient.cpp @@ -391,6 +391,12 @@ bool CPipeClient::Init() return false; } + { + CSRWExclusiveLock lock(m_clipboardSetupLock); + if (!PrepareClipboardMappingLocked()) + return false; + } + m_endpoint.SetHandler(this); return m_endpoint.Start( LG_PIPE_NAME, @@ -477,6 +483,44 @@ void CPipeClient::WriteMsg(const LGPipeMsg& msg) m_endpoint.Send(&msg, sizeof(msg)); } +bool CPipeClient::PipeServerIsAuthorized(HANDLE pipe) +{ + DWORD session = 0xFFFFFFFFU; + if (!GetNamedPipeServerSessionId(pipe, &session)) + { + const DWORD error = GetLastError(); + DEBUG_WARN_HR(error, "Failed to identify named pipe server session"); + return false; + } + if (session != 0) + { + DEBUG_WARN( + "Rejected named pipe server outside the service session"); + return false; + } + return true; +} + +bool CPipeClient::BuildPipeClientHello(void * message, size_t size) +{ + CSRWSharedLock setupLock(m_clipboardSetupLock); + if (!message || size != sizeof(LGPipeMsg) || + !m_clipboardMapping || !m_clipboardEpoch) + { + DEBUG_ERROR("Clipboard mapping was not prepared before pipe connection"); + return false; + } + + LGPipeMsg& hello = *static_cast(message); + hello = {}; + hello.size = sizeof(hello); + hello.type = LGPipeMsg::HELLO; + hello.hello.version = LGPipeMsg::PROTOCOL_VERSION; + hello.hello.authorityId[0] = m_clipboardAuthorityId[0]; + hello.hello.authorityId[1] = m_clipboardAuthorityId[1]; + return true; +} + void CPipeClient::OnPipeConnected() { bool hasStatus; @@ -489,82 +533,6 @@ void CPipeClient::OnPipeConnected() if (hasStatus) WriteMsg(status); - - if (!m_clipboardEnabled) - return; - - CSRWExclusiveLock setupLock(m_clipboardSetupLock); - ResetClipboardSetupLocked(); - - LARGE_INTEGER size = {}; - size.QuadPart = sizeof(ClipboardMapping); - m_clipboardMapping = CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, - PAGE_READWRITE, size.HighPart, size.LowPart, nullptr); - if (!m_clipboardMapping) - { - DEBUG_ERROR_HR(GetLastError(), - "Failed to create the clipboard mapping"); - return; - } - - ClipboardMapping * view = static_cast(MapViewOfFile( - m_clipboardMapping, FILE_MAP_ALL_ACCESS, 0, 0, - sizeof(ClipboardMapping))); - if (!view) - { - DEBUG_ERROR_HR(GetLastError(), - "Failed to initialize the clipboard mapping"); - ResetClipboardSetupLocked(); - return; - } - - ++m_clipboardEpochCounter; - if (!m_clipboardEpochCounter) - ++m_clipboardEpochCounter; - m_clipboardEpoch = m_clipboardEpochCounter; - CClipboardRing::Initialize(*view, m_clipboardEpoch); - UnmapViewOfFile(view); - - DWORD serverPid = 0; - if (!GetNamedPipeServerProcessId(m_endpoint.NativeHandle(), &serverPid)) - { - DEBUG_ERROR_HR(GetLastError(), - "Failed to identify the clipboard mapping target"); - ResetClipboardSetupLocked(); - return; - } - - HANDLE target = OpenProcess(PROCESS_DUP_HANDLE, FALSE, serverPid); - HANDLE remote = nullptr; - if (!target || !DuplicateHandle(GetCurrentProcess(), - m_clipboardMapping, target, &remote, 0, FALSE, - DUPLICATE_SAME_ACCESS)) - { - DEBUG_ERROR_HR(GetLastError(), - "Failed to share the clipboard mapping with the IDD"); - if (target) - CloseHandle(target); - ResetClipboardSetupLocked(); - return; - } - - LGPipeMsg setup = {}; - setup.size = sizeof(setup); - setup.type = LGPipeMsg::CLIPBOARD_SETUP; - setup.clipboardSetup.handle = - static_cast(reinterpret_cast(remote)); - setup.clipboardSetup.bytes = sizeof(ClipboardMapping); - const bool sent = m_endpoint.Send(&setup, sizeof(setup)); - if (!sent) - { - DEBUG_WARN("Failed to send clipboard mapping setup"); - HANDLE reclaimed = nullptr; - if (DuplicateHandle(target, remote, GetCurrentProcess(), &reclaimed, - 0, FALSE, DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE)) - CloseHandle(reclaimed); - ResetClipboardSetupLocked(); - } - CloseHandle(target); } void CPipeClient::OnPipeDisconnected() @@ -589,6 +557,12 @@ bool CPipeClient::ShouldReconnect() const bool attached = IsLGIddDeviceAttached(); if (!attached) DEBUG_INFO("Looking Glass Indirect Display Device was removed"); + else + { + CSRWExclusiveLock lock(m_clipboardSetupLock); + if (!m_clipboardMapping && !PrepareClipboardMappingLocked()) + DEBUG_WARN("Clipboard mapping is not ready for reconnection"); + } return attached; } @@ -840,13 +814,46 @@ bool CPipeClient::OnPipeMessage(const void * message, size_t size) return true; } - HANDLE mapping = nullptr; - if (!m_clipboardMapping || - !DuplicateHandle(GetCurrentProcess(), m_clipboardMapping, - GetCurrentProcess(), &mapping, 0, FALSE, DUPLICATE_SAME_ACCESS) || - !m_clipboard.Attach(mapping, m_clipboardEpoch, true, *this)) + if (!m_clipboardEnabled) + { + const uint64_t epoch = m_clipboardEpoch; + ResetClipboardSetupLocked(); + setupLock.Unlock(); + + LGPipeMsg failure = {}; + failure.size = sizeof(failure); + failure.type = LGPipeMsg::CLIPBOARD_READY; + failure.clipboardReady.epoch = epoch; + failure.clipboardReady.status = ERROR_NOT_SUPPORTED; + m_endpoint.Send(&failure, sizeof(failure)); + return true; + } + + HANDLE mapping = nullptr; + DWORD failureStatus = ERROR_SUCCESS; + if (!m_clipboardMapping) + { + failureStatus = ERROR_NOT_READY; + DEBUG_ERROR_HR(failureStatus, + "Service-owned clipboard mapping is unavailable"); + } + else if (!DuplicateHandle(GetCurrentProcess(), m_clipboardMapping, + GetCurrentProcess(), &mapping, 0, FALSE, DUPLICATE_SAME_ACCESS)) + { + failureStatus = GetLastError(); + DEBUG_ERROR_HR(failureStatus, + "Failed to duplicate the service-owned clipboard mapping"); + } + else if (!m_clipboard.Attach( + mapping, m_clipboardEpoch, true, *this)) + { + failureStatus = ERROR_INVALID_DATA; + DEBUG_ERROR_HR(failureStatus, + "Failed to activate the service-owned clipboard mapping"); + } + + if (failureStatus != ERROR_SUCCESS) { - DEBUG_ERROR("Failed to activate the clipboard mapping"); const uint64_t epoch = m_clipboardEpoch; ResetClipboardSetupLocked(); setupLock.Unlock(); @@ -857,7 +864,7 @@ bool CPipeClient::OnPipeMessage(const void * message, size_t size) failure.size = sizeof(failure); failure.type = LGPipeMsg::CLIPBOARD_READY; failure.clipboardReady.epoch = epoch; - failure.clipboardReady.status = ERROR_NOT_READY; + failure.clipboardReady.status = failureStatus; m_endpoint.Send(&failure, sizeof(failure)); } return true; @@ -897,13 +904,87 @@ void CPipeClient::ClipboardResetPeer(uint64_t epoch, uint32_t reason) m_endpoint.Send(&msg, sizeof(msg)); } +bool CPipeClient::PrepareClipboardMappingLocked() +{ + if (m_clipboardMapping) + return true; + if (!m_clipboardMappingId[0] || !m_clipboardMappingId[1]) + { + DEBUG_ERROR("Invalid service-owned clipboard mapping identifier"); + return false; + } + + wchar_t mappingName[128]; + const int nameResult = _snwprintf_s( + mappingName, _countof(mappingName), _TRUNCATE, + L"Global\\LookingGlassIDDClipboard-%016llx%016llx", + static_cast(m_clipboardMappingId[0]), + static_cast(m_clipboardMappingId[1])); + if (nameResult < 0) + { + DEBUG_ERROR_HR(ERROR_INSUFFICIENT_BUFFER, + "Failed to format service-owned clipboard mapping name"); + return false; + } + + m_clipboardMapping = OpenFileMappingW( + FILE_MAP_READ | FILE_MAP_WRITE, FALSE, mappingName); + if (!m_clipboardMapping) + { + const DWORD error = GetLastError(); + DEBUG_ERROR_HR(error, + "Failed to open service-owned clipboard mapping"); + return false; + } + + ClipboardMapping * view = static_cast(MapViewOfFile( + m_clipboardMapping, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, + sizeof(ClipboardMapping))); + if (!view) + { + const DWORD error = GetLastError(); + DEBUG_ERROR_HR(error, + "Failed to initialize service-owned clipboard mapping"); + ResetClipboardSetupLocked(); + return false; + } + + m_clipboardAuthorityId[0] = view->authorityId[0]; + m_clipboardAuthorityId[1] = view->authorityId[1]; + if (!m_clipboardAuthorityId[0] || !m_clipboardAuthorityId[1]) + { + DEBUG_ERROR("Invalid clipboard authority identifier"); + UnmapViewOfFile(view); + ResetClipboardSetupLocked(); + return false; + } + + ++m_clipboardEpochCounter; + if (!m_clipboardEpochCounter) + ++m_clipboardEpochCounter; + m_clipboardEpoch = m_clipboardEpochCounter; + CClipboardRing::Initialize( + *view, m_clipboardEpoch, m_clipboardAuthorityId); + if (!UnmapViewOfFile(view)) + { + const DWORD error = GetLastError(); + DEBUG_ERROR_HR(error, + "Failed to unmap initialized service-owned clipboard mapping"); + ResetClipboardSetupLocked(); + return false; + } + return true; +} + void CPipeClient::ResetClipboardSetupLocked() { m_clipboard.Detach(); if (m_clipboardMapping) CloseHandle(m_clipboardMapping); - m_clipboardMapping = nullptr; - m_clipboardEpoch = 0; + m_clipboardMapping = nullptr; + m_clipboardEpoch = 0; + m_clipboardAuthorityId[0] = 0; + m_clipboardAuthorityId[1] = 0; } void CPipeClient::HandleSetCursorPos(const LGPipeMsg& msg) diff --git a/idd/LGIddHelper/CPipeClient.h b/idd/LGIddHelper/CPipeClient.h index bd89fffc..2d877d1a 100644 --- a/idd/LGIddHelper/CPipeClient.h +++ b/idd/LGIddHelper/CPipeClient.h @@ -35,10 +35,12 @@ private: CPipeEndpoint m_endpoint; CClipboardChannel m_clipboard; CSRWLock m_clipboardSetupLock; - HANDLE m_clipboardMapping = nullptr; - uint64_t m_clipboardEpoch = 0; - uint64_t m_clipboardEpochCounter = 0; - bool m_clipboardEnabled = false; + HANDLE m_clipboardMapping = nullptr; + uint64_t m_clipboardEpoch = 0; + uint64_t m_clipboardEpochCounter = 0; + uint64_t m_clipboardMappingId[2] = {}; + uint64_t m_clipboardAuthorityId[2] = {}; + bool m_clipboardEnabled = false; CSRWLock m_displayLock; bool m_recoveryActive = false; @@ -58,11 +60,15 @@ private: void HandleGPUStatus(const LGPipeMsg& msg); void HandleResolutionRejected(const LGPipeMsg& msg); void HandleSetRecovery(const LGPipeMsg& msg); + bool PrepareClipboardMappingLocked(); void ResetClipboardSetupLocked(); void OnPipeConnected() override; void OnPipeDisconnected() override; bool ShouldReconnect() override; + bool PipeServerIsAuthorized(HANDLE pipe) override; + bool PipeClientHelloRequired() const override { return true; } + bool BuildPipeClientHello(void * message, size_t size) override; bool OnPipeMessage(const void * message, size_t size) override; public: @@ -72,6 +78,11 @@ public: bool Init(); void DeInit(); + void SetClipboardMappingId(uint64_t high, uint64_t low) + { + m_clipboardMappingId[0] = high; + m_clipboardMappingId[1] = low; + } bool IsRunning() { return m_endpoint.IsRunning(); } CClipboardChannel& Clipboard() { return m_clipboard; } diff --git a/idd/LGIddHelper/CRegistrySettings.cpp b/idd/LGIddHelper/CRegistrySettings.cpp index 9029a648..8272c74b 100644 --- a/idd/LGIddHelper/CRegistrySettings.cpp +++ b/idd/LGIddHelper/CRegistrySettings.cpp @@ -39,10 +39,13 @@ CRegistrySettings::~CRegistrySettings() RegCloseKey(hKey); } -LSTATUS CRegistrySettings::open() +LSTATUS CRegistrySettings::open(bool writable) { HKEY key; - LSTATUS result = RegOpenKeyEx(HKEY_LOCAL_MACHINE, LGIDD_REGKEY, 0, KEY_QUERY_VALUE | KEY_SET_VALUE, &key); + const REGSAM access = KEY_QUERY_VALUE | + (writable ? KEY_SET_VALUE : 0); + LSTATUS result = RegOpenKeyEx( + HKEY_LOCAL_MACHINE, LGIDD_REGKEY, 0, access, &key); if (result == ERROR_SUCCESS) hKey = key; diff --git a/idd/LGIddHelper/CRegistrySettings.h b/idd/LGIddHelper/CRegistrySettings.h index 0abc9bf7..cc0a2a7d 100644 --- a/idd/LGIddHelper/CRegistrySettings.h +++ b/idd/LGIddHelper/CRegistrySettings.h @@ -41,7 +41,7 @@ public: CRegistrySettings(); ~CRegistrySettings(); - LSTATUS open(); + LSTATUS open(bool writable = false); bool isOpen() { return !!hKey; } std::vector getDefaultModes(); diff --git a/idd/LGIddHelper/main.cpp b/idd/LGIddHelper/main.cpp index 7e894e7e..0651553d 100644 --- a/idd/LGIddHelper/main.cpp +++ b/idd/LGIddHelper/main.cpp @@ -22,8 +22,14 @@ #include #include #include +#include +#include +#include +#include +#include #include +#include #include #include @@ -35,10 +41,12 @@ using namespace Microsoft::WRL::Wrappers::HandleTraits; #include "CPipeClient.h" #include "CNotifyWindow.h" #include "CConfigWindow.h" +#include "ClipboardRing.h" +#include "LGIddAuthority.h" #include "common/array.h" -#define SVCNAME L"Looking Glass (IDD Helper)" +static constexpr wchar_t SVCNAME[] = L"Looking Glass (IDD Helper)"; static constexpr DWORD NO_CONSOLE_SESSION = 0xFFFFFFFFu; @@ -56,8 +64,13 @@ static void ReportSvcStatus(DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD d static std::wstring l_executable; static HandleT l_process; static HandleT l_childStopEvent; -static DWORD l_desiredSession = NO_CONSOLE_SESSION; -static DWORD l_childSession = NO_CONSOLE_SESSION; +static HandleT l_childActivationEvent; +static HandleT l_childLifetimeMutex; +static HandleT l_childClipboardMapping; +static HANDLE l_authorityDevice = INVALID_HANDLE_VALUE; +static LGIddAuthorityHost l_authorityHost = {}; +static DWORD l_desiredSession = NO_CONSOLE_SESSION; +static DWORD l_childSession = NO_CONSOLE_SESSION; struct OleScope { @@ -66,8 +79,13 @@ struct OleScope static bool Launch(DWORD sessionId); static bool StopChild(); +static void CloseChildLifetimeMutex(); +static bool RegisterClipboardAuthority(DWORD session, + const uint64_t (&mappingId)[2], HANDLE mapping); +static bool VerifyClipboardAuthority(); +static void ClearClipboardAuthority(); -void CALLBACK DestroyNotifyWindow(PVOID lpParam, BOOLEAN bTimedOut) +static void CALLBACK DestroyNotifyWindow(PVOID lpParam, BOOLEAN bTimedOut) { (void) bTimedOut; DEBUG_INFO("Helper shutdown requested, exiting..."); @@ -75,6 +93,59 @@ void CALLBACK DestroyNotifyWindow(PVOID lpParam, BOOLEAN bTimedOut) window->close(); } +struct LifetimeWaitContext +{ + HANDLE lifetime; + HANDLE childStop; + HANDLE cancel; + CNotifyWindow * window; +}; + +static DWORD WINAPI LifetimeWaitProc(void * opaque) +{ + const LifetimeWaitContext * context = + static_cast(opaque); + const HANDLE handles[] = + { context->lifetime, context->childStop, context->cancel }; + const DWORD result = WaitForMultipleObjects( + ARRAY_LENGTH(handles), handles, FALSE, INFINITE); + if (result == WAIT_OBJECT_0 || result == WAIT_ABANDONED_0 || + result == WAIT_OBJECT_0 + 1) + DestroyNotifyWindow(context->window, FALSE); + else if (result == WAIT_FAILED) + { + const DWORD error = GetLastError(); + DEBUG_ERROR_HR(error, "Failed to wait for the service lifetime mutex"); + DestroyNotifyWindow(context->window, FALSE); + } + return 0; +} + +static bool ParseMappingId( + const std::wstring& value, uint64_t (&mappingId)[2]) +{ + if (value.size() != 32) + return false; + mappingId[0] = 0; + mappingId[1] = 0; + for (size_t i = 0; i < value.size(); ++i) + { + const wchar_t c = value[i]; + uint64_t nibble; + if (c >= L'0' && c <= L'9') + nibble = static_cast(c - L'0'); + else if (c >= L'a' && c <= L'f') + nibble = static_cast(c - L'a' + 10); + else if (c >= L'A' && c <= L'F') + nibble = static_cast(c - L'A' + 10); + else + return false; + uint64_t& part = mappingId[i / 16]; + part = (part << 4) | nibble; + } + return mappingId[0] && mappingId[1]; +} + int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nShowCmd) { wchar_t buffer[MAX_PATH]; @@ -103,31 +174,70 @@ int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _ return EXIT_SUCCESS; } - if (argc != 2 && argc != 3) + if (argc != 5) return EXIT_FAILURE; // child process - g_debug.Init(L"looking-glass-idd-helper"); + g_debug.Init(L"looking-glass-idd-helper", CDebug::Location::LocalAppData); DEBUG_INFO("Looking Glass IDD Helper Process (" LG_VERSION_STR ")"); - HandleT hParent(OpenProcess(SYNCHRONIZE, FALSE, std::stoul(args[1]))); - if (!hParent.IsValid()) + HandleT hLifetime( + OpenMutexW(SYNCHRONIZE, FALSE, args[1].c_str())); + if (!hLifetime.IsValid()) { - DEBUG_ERROR_HR(GetLastError(), "Failed to open parent process"); + DEBUG_ERROR_HR(GetLastError(), "Failed to open the service lifetime mutex"); return EXIT_FAILURE; } - HandleT hStop; - if (argc == 3) + HandleT hStop( + OpenEventW(SYNCHRONIZE, FALSE, args[2].c_str())); + if (!hStop.IsValid()) { - hStop.Attach(OpenEvent(SYNCHRONIZE, FALSE, args[2].c_str())); - if (!hStop.IsValid()) - { - DEBUG_ERROR_HR(GetLastError(), "Failed to open the child stop event"); - return EXIT_FAILURE; - } + DEBUG_ERROR_HR(GetLastError(), "Failed to open the child stop event"); + return EXIT_FAILURE; } + HandleT hActivation( + OpenEventW(SYNCHRONIZE, FALSE, args[3].c_str())); + if (!hActivation.IsValid()) + { + DEBUG_ERROR_HR(GetLastError(), + "Failed to open the child activation event"); + return EXIT_FAILURE; + } + + const HANDLE activationHandles[] = + { hLifetime.Get(), hStop.Get(), hActivation.Get() }; + const DWORD activationResult = WaitForMultipleObjects( + ARRAY_LENGTH(activationHandles), activationHandles, FALSE, INFINITE); + if (activationResult == WAIT_OBJECT_0 || + activationResult == WAIT_ABANDONED_0 || + activationResult == WAIT_OBJECT_0 + 1) + { + DEBUG_INFO("Helper activation cancelled by the service"); + return EXIT_SUCCESS; + } + if (activationResult != WAIT_OBJECT_0 + 2) + { + if (activationResult == WAIT_FAILED) + { + const DWORD error = GetLastError(); + DEBUG_ERROR_HR(error, "Failed to wait for Helper activation"); + } + else + DEBUG_ERROR_HR(ERROR_INVALID_STATE, + "Helper activation wait returned an unexpected result"); + return EXIT_FAILURE; + } + + uint64_t mappingId[2]; + if (!ParseMappingId(args[4], mappingId)) + { + DEBUG_ERROR("Invalid service-owned clipboard mapping identifier"); + return EXIT_FAILURE; + } + g_pipe.SetClipboardMappingId(mappingId[0], mappingId[1]); + const HRESULT ole = OleInitialize(nullptr); if (FAILED(ole)) { @@ -136,15 +246,6 @@ int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _ } const OleScope oleScope; - const HRESULT security = CoInitializeSecurity(nullptr, 0, nullptr, nullptr, - RPC_C_AUTHN_LEVEL_NONE, RPC_C_IMP_LEVEL_IDENTIFY, nullptr, - EOAC_DYNAMIC_CLOAKING, nullptr); - if (FAILED(security)) - { - DEBUG_ERROR_HR(security, "Failed to initialize COM security"); - return EXIT_FAILURE; - } - if (!CNotifyWindow::registerClass()) { DEBUG_ERROR("Failed to register message window class"); @@ -177,16 +278,27 @@ int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _ return g_pipe.EnsureOnlyDisplay(); }); - HANDLE hParentWait = NULL; - if (!RegisterWaitForSingleObject(&hParentWait, hParent.Get(), - DestroyNotifyWindow, &window, INFINITE, WT_EXECUTEONLYONCE)) - DEBUG_ERROR_HR(GetLastError(), "Failed to RegisterWaitForSingleObject"); + HandleT lifetimeThreadStop( + CreateEventW(nullptr, TRUE, FALSE, nullptr)); + if (!lifetimeThreadStop.IsValid()) + { + DEBUG_ERROR_HR(GetLastError(), + "Failed to create the service lifetime thread event"); + g_pipe.DeInit(); + return EXIT_FAILURE; + } - HANDLE hStopWait = NULL; - if (hStop.IsValid() && - !RegisterWaitForSingleObject(&hStopWait, hStop.Get(), - DestroyNotifyWindow, &window, INFINITE, WT_EXECUTEONLYONCE)) - DEBUG_ERROR_HR(GetLastError(), "Failed to register the child stop wait"); + LifetimeWaitContext lifetimeContext = + { hLifetime.Get(), hStop.Get(), lifetimeThreadStop.Get(), &window }; + HandleT lifetimeThread(CreateThread( + nullptr, 0, LifetimeWaitProc, &lifetimeContext, 0, nullptr)); + if (!lifetimeThread.IsValid()) + { + DEBUG_ERROR_HR(GetLastError(), + "Failed to create the service lifetime wait thread"); + g_pipe.DeInit(); + return EXIT_FAILURE; + } MSG msg; while (GetMessage(&msg, NULL, 0, 0) > 0) @@ -199,10 +311,14 @@ int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _ } } - if (hParentWait) - (void) UnregisterWaitEx(hParentWait, INVALID_HANDLE_VALUE); - if (hStopWait) - (void) UnregisterWaitEx(hStopWait, INVALID_HANDLE_VALUE); + if (!SetEvent(lifetimeThreadStop.Get())) + DEBUG_ERROR_HR(GetLastError(), + "Failed to stop the service lifetime wait thread"); + const DWORD lifetimeWait = + WaitForSingleObject(lifetimeThread.Get(), INFINITE); + if (lifetimeWait == WAIT_FAILED) + DEBUG_ERROR_HR(GetLastError(), + "Failed to join the service lifetime wait thread"); DEBUG_INFO("Helper window destroyed."); g_pipe.DeInit(); @@ -326,6 +442,18 @@ static void WINAPI SvcMain(DWORD dwArgc, LPTSTR* lpszArgv) continue; } + if (l_process.IsValid() && !VerifyClipboardAuthority()) + { + DEBUG_WARN("LGIdd clipboard authority was lost, restarting Helper"); + if (!StopChild()) + { + running = false; + break; + } + nextLaunch = GetTickCount64() + 1000; + continue; + } + if (!l_process.IsValid() && l_desiredSession != NO_CONSOLE_SESSION && GetTickCount64() >= nextLaunch) @@ -348,13 +476,10 @@ static void WINAPI SvcMain(DWORD dwArgc, LPTSTR* lpszArgv) l_process.Get() }; DWORD count = 3; - DWORD duration = INFINITE; + DWORD duration = 1000; if (!l_process.IsValid()) - { count = 2; - duration = 1000; - } switch (WaitForMultipleObjects(count, waitOn, FALSE, duration)) { @@ -370,6 +495,7 @@ static void WINAPI SvcMain(DWORD dwArgc, LPTSTR* lpszArgv) // child application exited case WAIT_OBJECT_0 + 2: { + ClearClipboardAuthority(); DWORD code; if (!GetExitCodeProcess(l_process.Get(), &code)) DEBUG_ERROR_HR(GetLastError(), "GetExitCodeProcess Failed"); @@ -378,6 +504,7 @@ static void WINAPI SvcMain(DWORD dwArgc, LPTSTR* lpszArgv) l_process.Close(); l_childStopEvent.Close(); + CloseChildLifetimeMutex(); l_childSession = NO_CONSOLE_SESSION; nextLaunch = GetTickCount64() + 1000; break; @@ -391,6 +518,7 @@ static void WINAPI SvcMain(DWORD dwArgc, LPTSTR* lpszArgv) } (void) StopChild(); + ClearClipboardAuthority(); ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0); } @@ -432,7 +560,7 @@ static bool EnablePriv(LPCWSTR name) if (!LookupPrivilegeValue(NULL, name, &luid)) { - DEBUG_ERROR_HR(GetLastError(), "LookupPrivilegeValue %s", name); + DEBUG_ERROR_HR(GetLastError(), "LookupPrivilegeValue %ls", name); return false; } @@ -440,9 +568,17 @@ static bool EnablePriv(LPCWSTR name) tp.Privileges[0].Luid = luid; tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + SetLastError(ERROR_SUCCESS); if (!AdjustTokenPrivileges(hToken.Get(), FALSE, &tp, sizeof(tp), NULL, NULL)) { - DEBUG_ERROR_HR(GetLastError(), "AdjustTokenPrivileges %s", name); + DEBUG_ERROR_HR(GetLastError(), "AdjustTokenPrivileges %ls", name); + return false; + } + + const DWORD error = GetLastError(); + if (error != ERROR_SUCCESS) + { + DEBUG_ERROR_HR(error, "AdjustTokenPrivileges %ls", name); return false; } @@ -464,7 +600,7 @@ static void DisablePriv(LPCWSTR name) if (!LookupPrivilegeValue(NULL, name, &luid)) { - DEBUG_ERROR_HR(GetLastError(), "LookupPrivilegeValue %s", name); + DEBUG_ERROR_HR(GetLastError(), "LookupPrivilegeValue %ls", name); return; } @@ -473,7 +609,654 @@ static void DisablePriv(LPCWSTR name) tp.Privileges[0].Attributes = 0; if (!AdjustTokenPrivileges(hToken.Get(), FALSE, &tp, sizeof(tp), NULL, NULL)) - DEBUG_ERROR_HR(GetLastError(), "AdjustTokenPrivileges %s", name); + DEBUG_ERROR_HR(GetLastError(), "AdjustTokenPrivileges %ls", name); +} + +namespace +{ + bool TokenFailure(DWORD error, const char * stage) + { + DEBUG_ERROR_HR(error, "%s", stage); + SetLastError(error); + return false; + } + + bool GetTokenData(HANDLE token, TOKEN_INFORMATION_CLASS type, + std::vector& data) + { + DWORD bytes = 0; + const BOOL sized = + GetTokenInformation(token, type, nullptr, 0, &bytes); + const DWORD sizeError = sized ? ERROR_SUCCESS : GetLastError(); + if (sized || sizeError != ERROR_INSUFFICIENT_BUFFER || !bytes) + { + SetLastError(sized || !bytes ? ERROR_INVALID_DATA : sizeError); + return false; + } + + try + { + data.resize(bytes); + } + catch (...) + { + SetLastError(ERROR_OUTOFMEMORY); + return false; + } + if (!GetTokenInformation(token, type, data.data(), bytes, &bytes)) + { + const DWORD error = GetLastError(); + SetLastError(error); + return false; + } + return true; + } + + bool TokenHasEnabledGroup(HANDLE token, WELL_KNOWN_SID_TYPE type) + { + std::array sidBuffer = {}; + DWORD sidBytes = static_cast(sidBuffer.size()); + if (!CreateWellKnownSid(type, nullptr, sidBuffer.data(), &sidBytes)) + return false; + + std::vector data; + if (!GetTokenData(token, TokenGroups, data)) + return false; + const TOKEN_GROUPS * groups = + reinterpret_cast(data.data()); + for (DWORD i = 0; i < groups->GroupCount; ++i) + if (EqualSid(groups->Groups[i].Sid, sidBuffer.data()) && + (groups->Groups[i].Attributes & SE_GROUP_ENABLED) && + !(groups->Groups[i].Attributes & SE_GROUP_USE_FOR_DENY_ONLY)) + return true; + SetLastError(ERROR_ACCESS_DENIED); + return false; + } + + bool GetLogonSid(HANDLE token, std::vector& sid) + { + std::vector data; + if (!GetTokenData(token, TokenGroups, data)) + return false; + const TOKEN_GROUPS * groups = + reinterpret_cast(data.data()); + for (DWORD i = 0; i < groups->GroupCount; ++i) + if ((groups->Groups[i].Attributes & SE_GROUP_LOGON_ID) == + SE_GROUP_LOGON_ID) + { + const DWORD bytes = GetLengthSid(groups->Groups[i].Sid); + try + { + sid.resize(bytes); + } + catch (...) + { + SetLastError(ERROR_OUTOFMEMORY); + return false; + } + if (!CopySid(bytes, sid.data(), groups->Groups[i].Sid)) + return false; + return true; + } + SetLastError(ERROR_ACCESS_DENIED); + return false; + } + + bool ValidateLaunchToken(HANDLE token, DWORD expectedSession, + std::vector& logonSid) + { + DWORD bytes = 0; + TOKEN_TYPE tokenType = TokenImpersonation; + if (!GetTokenInformation(token, TokenType, &tokenType, + sizeof(tokenType), &bytes)) + return TokenFailure(GetLastError(), + "Failed to query interactive token type"); + if (tokenType != TokenPrimary) + return TokenFailure(ERROR_ACCESS_DENIED, + "Interactive token is not a primary token"); + + DWORD session = NO_CONSOLE_SESSION; + if (!GetTokenInformation(token, TokenSessionId, &session, + sizeof(session), &bytes)) + return TokenFailure(GetLastError(), + "Failed to query interactive token session"); + if (session != expectedSession) + return TokenFailure(ERROR_ACCESS_DENIED, + "Interactive token belongs to the wrong session"); + + DWORD appContainer = 0; + if (!GetTokenInformation(token, TokenIsAppContainer, &appContainer, + sizeof(appContainer), &bytes)) + return TokenFailure(GetLastError(), + "Failed to query interactive token AppContainer state"); + if (appContainer) + return TokenFailure(ERROR_ACCESS_DENIED, + "Interactive token is an AppContainer token"); + + TOKEN_ELEVATION_TYPE elevation = TokenElevationTypeDefault; + if (!GetTokenInformation(token, TokenElevationType, &elevation, + sizeof(elevation), &bytes)) + return TokenFailure(GetLastError(), + "Failed to query interactive token elevation"); + if (elevation == TokenElevationTypeFull) + return TokenFailure(ERROR_ACCESS_DENIED, + "Interactive token is an unfiltered elevated token"); + + std::vector userData; + if (!GetTokenData(token, TokenUser, userData)) + return TokenFailure(GetLastError(), + "Failed to query interactive token user SID"); + const PSID user = + reinterpret_cast(userData.data())->User.Sid; + if (IsWellKnownSid(user, WinLocalSystemSid) || + IsWellKnownSid(user, WinLocalServiceSid) || + IsWellKnownSid(user, WinNetworkServiceSid)) + { + return TokenFailure(ERROR_ACCESS_DENIED, + "Interactive token belongs to a service identity"); + } + + std::vector labelData; + if (!GetTokenData(token, TokenIntegrityLevel, labelData)) + return TokenFailure(GetLastError(), + "Failed to query interactive token integrity level"); + const PSID label = reinterpret_cast( + labelData.data())->Label.Sid; + if (!IsValidSid(label) || *GetSidSubAuthorityCount(label) == 0) + { + return TokenFailure(ERROR_INVALID_DATA, + "Interactive token has an invalid integrity label"); + } + const DWORD integrity = *GetSidSubAuthority(label, + *GetSidSubAuthorityCount(label) - 1); + if (integrity < SECURITY_MANDATORY_MEDIUM_RID || + integrity >= SECURITY_MANDATORY_SYSTEM_RID || + (integrity >= SECURITY_MANDATORY_HIGH_RID && + elevation != TokenElevationTypeDefault)) + { + return TokenFailure(ERROR_ACCESS_DENIED, + "Interactive token integrity level is not permitted"); + } + + if (!TokenHasEnabledGroup(token, WinInteractiveSid)) + return TokenFailure(GetLastError(), + "Interactive token lacks the enabled INTERACTIVE group"); + if (!GetLogonSid(token, logonSid)) + return TokenFailure(GetLastError(), + "Interactive token lacks a logon SID"); + return true; + } + + class CObjectSecurity + { + private: + BYTE m_systemSid[SECURITY_MAX_SID_SIZE] = {}; + BYTE m_mediumSid[SECURITY_MAX_SID_SIZE] = {}; + std::vector m_acl; + std::vector m_sacl; + SECURITY_DESCRIPTOR m_descriptor = {}; + SECURITY_ATTRIBUTES m_attributes = {}; + + public: + bool Init(PSID logonSid, DWORD systemAccess, DWORD userAccess, + bool mediumIntegrity = false) + { + DWORD systemSidBytes = sizeof(m_systemSid); + if (!CreateWellKnownSid(WinLocalSystemSid, nullptr, + m_systemSid, &systemSidBytes)) + return TokenFailure(GetLastError(), + "Failed to create SYSTEM SID for shared object security"); + + const DWORD systemAceBytes = sizeof(ACCESS_ALLOWED_ACE) - + sizeof(DWORD) + GetLengthSid(m_systemSid); + const DWORD userAceBytes = logonSid ? + sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD) + + GetLengthSid(logonSid) : 0; + try + { + m_acl.resize(sizeof(ACL) + systemAceBytes + userAceBytes); + } + catch (...) + { + return TokenFailure(ERROR_OUTOFMEMORY, + "Failed to allocate shared object DACL"); + } + + PACL acl = reinterpret_cast(m_acl.data()); + if (!InitializeAcl(acl, static_cast(m_acl.size()), + ACL_REVISION)) + return TokenFailure(GetLastError(), + "Failed to initialize shared object DACL"); + if (!AddAccessAllowedAceEx(acl, ACL_REVISION, 0, + systemAccess, m_systemSid)) + return TokenFailure(GetLastError(), + "Failed to grant SYSTEM access to shared object"); + if (logonSid && !AddAccessAllowedAceEx(acl, ACL_REVISION, 0, + userAccess, logonSid)) + return TokenFailure(GetLastError(), + "Failed to grant logon SID access to shared object"); + if (!InitializeSecurityDescriptor( + &m_descriptor, SECURITY_DESCRIPTOR_REVISION)) + return TokenFailure(GetLastError(), + "Failed to initialize shared object security descriptor"); + if (!SetSecurityDescriptorDacl( + &m_descriptor, TRUE, acl, FALSE)) + return TokenFailure(GetLastError(), + "Failed to set shared object DACL"); + + if (mediumIntegrity) + { + DWORD mediumSidBytes = sizeof(m_mediumSid); + if (!CreateWellKnownSid(WinMediumLabelSid, nullptr, + m_mediumSid, &mediumSidBytes)) + return TokenFailure(GetLastError(), + "Failed to create medium mandatory label SID"); + const DWORD mandatoryAceBytes = + sizeof(SYSTEM_MANDATORY_LABEL_ACE) - sizeof(DWORD) + + GetLengthSid(m_mediumSid); + try + { + m_sacl.resize(sizeof(ACL) + mandatoryAceBytes); + } + catch (...) + { + return TokenFailure(ERROR_OUTOFMEMORY, + "Failed to allocate shared object mandatory label ACL"); + } + PACL sacl = reinterpret_cast(m_sacl.data()); + if (!InitializeAcl(sacl, static_cast(m_sacl.size()), + ACL_REVISION)) + return TokenFailure(GetLastError(), + "Failed to initialize shared object mandatory label ACL"); + if (!AddMandatoryAce(sacl, ACL_REVISION, 0, + SYSTEM_MANDATORY_LABEL_NO_WRITE_UP | + SYSTEM_MANDATORY_LABEL_NO_READ_UP, + m_mediumSid)) + return TokenFailure(GetLastError(), + "Failed to add shared object medium mandatory label"); + if (!SetSecurityDescriptorSacl( + &m_descriptor, TRUE, sacl, FALSE)) + return TokenFailure(GetLastError(), + "Failed to set shared object mandatory label"); + } + + const SECURITY_DESCRIPTOR_CONTROL protectedParts = + static_cast(SE_DACL_PROTECTED | + (mediumIntegrity ? SE_SACL_PROTECTED : 0)); + if (!SetSecurityDescriptorControl( + &m_descriptor, protectedParts, protectedParts)) + return TokenFailure(GetLastError(), + "Failed to protect the shared object security descriptor"); + + m_attributes.nLength = sizeof(m_attributes); + m_attributes.lpSecurityDescriptor = &m_descriptor; + m_attributes.bInheritHandle = FALSE; + return true; + } + + SECURITY_ATTRIBUTES * Get() { return &m_attributes; } + }; + + bool GenerateRandomId(uint64_t (&random)[2], const char * stage) + { + for (unsigned attempt = 0; attempt < 2; ++attempt) + { + const NTSTATUS status = BCryptGenRandom(nullptr, + reinterpret_cast(random), sizeof(random), + BCRYPT_USE_SYSTEM_PREFERRED_RNG); + if (status < 0) + { + DEBUG_ERROR_HR(HRESULT_FROM_NT(status), "%s", stage); + return false; + } + if (random[0] && random[1]) + return true; + } + DEBUG_ERROR_HR(ERROR_INVALID_DATA, "%s", stage); + return false; + } + + bool GenerateChildObjectNames( + wchar_t * lifetimeName, size_t lifetimeCount, + wchar_t * stopName, size_t stopCount, + wchar_t * activationName, size_t activationCount) + { + uint64_t random[2]; + if (!GenerateRandomId(random, "Failed to generate child object names")) + return false; + + const int lifetimeResult = _snwprintf_s( + lifetimeName, lifetimeCount, _TRUNCATE, + L"Global\\LookingGlassIDDHelperLifetime-%016llx%016llx", + static_cast(random[0]), + static_cast(random[1])); + const int stopResult = _snwprintf_s( + stopName, stopCount, _TRUNCATE, + L"Global\\LookingGlassIDDHelperStop-%016llx%016llx", + static_cast(random[0]), + static_cast(random[1])); + const int activationResult = _snwprintf_s( + activationName, activationCount, _TRUNCATE, + L"Global\\LookingGlassIDDHelperActivate-%016llx%016llx", + static_cast(random[0]), + static_cast(random[1])); + if (lifetimeResult < 0 || stopResult < 0 || activationResult < 0) + { + DEBUG_ERROR_HR(ERROR_INSUFFICIENT_BUFFER, + "Failed to format child object names"); + return false; + } + return true; + } +} + +static uint64_t FileTimeValue(const FILETIME& value) +{ + ULARGE_INTEGER result = {}; + result.LowPart = value.dwLowDateTime; + result.HighPart = value.dwHighDateTime; + return result.QuadPart; +} + +static bool QueryAuthorityHost(HANDLE device, LGIddAuthorityHost& host) +{ + ZeroMemory(&host, sizeof(host)); + DWORD bytes = 0; + if (!DeviceIoControl(device, IOCTL_LG_IDD_AUTHORITY_GET_HOST, + nullptr, 0, &host, sizeof(host), &bytes, nullptr)) + { + const DWORD error = GetLastError(); + DEBUG_ERROR_HR(error, "Failed to query the LGIdd authority host"); + return false; + } + if (bytes != sizeof(host) || host.size != sizeof(host) || + host.version != LG_IDD_AUTHORITY_VERSION || host.reserved || + !host.processId || !host.processCreated || + !host.instanceId[0] || !host.instanceId[1]) + { + DEBUG_ERROR_HR(ERROR_INVALID_DATA, + "LGIdd returned invalid authority host information"); + return false; + } + return true; +} + +static HANDLE OpenAuthorityDevice(LGIddAuthorityHost& host) +{ + HDEVINFO devices = SetupDiGetClassDevsW(&GUID_DEVINTERFACE_LGIdd, + nullptr, nullptr, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); + if (devices == INVALID_HANDLE_VALUE) + { + const DWORD error = GetLastError(); + DEBUG_ERROR_HR(error, + "Failed to enumerate the LGIdd authority interface"); + return INVALID_HANDLE_VALUE; + } + + HANDLE device = INVALID_HANDLE_VALUE; + for (DWORD index = 0; ; ++index) + { + SP_DEVICE_INTERFACE_DATA interfaceData = {}; + interfaceData.cbSize = sizeof(interfaceData); + if (!SetupDiEnumDeviceInterfaces(devices, nullptr, + &GUID_DEVINTERFACE_LGIdd, index, &interfaceData)) + { + const DWORD error = GetLastError(); + if (error != ERROR_NO_MORE_ITEMS) + DEBUG_ERROR_HR(error, + "Failed to enumerate an LGIdd authority interface"); + break; + } + + DWORD detailBytes = 0; + const BOOL sized = SetupDiGetDeviceInterfaceDetailW( + devices, &interfaceData, nullptr, 0, &detailBytes, nullptr); + const DWORD detailSizeError = sized ? ERROR_SUCCESS : GetLastError(); + if (sized || detailSizeError != ERROR_INSUFFICIENT_BUFFER || + detailBytes < sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_W)) + { + DEBUG_ERROR_HR(detailSizeError ? detailSizeError : ERROR_INVALID_DATA, + "Failed to size the LGIdd authority interface path"); + continue; + } + + std::vector detailStorage; + try + { + detailStorage.resize(detailBytes); + } + catch (...) + { + DEBUG_ERROR_HR(ERROR_OUTOFMEMORY, + "Failed to allocate the LGIdd authority interface path"); + break; + } + SP_DEVICE_INTERFACE_DETAIL_DATA_W * detail = + reinterpret_cast( + detailStorage.data()); + detail->cbSize = sizeof(*detail); + if (!SetupDiGetDeviceInterfaceDetailW(devices, &interfaceData, + detail, detailBytes, nullptr, nullptr)) + { + const DWORD error = GetLastError(); + DEBUG_ERROR_HR(error, + "Failed to read the LGIdd authority interface path"); + continue; + } + + device = CreateFileW(detail->DevicePath, + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + if (device == INVALID_HANDLE_VALUE) + { + const DWORD error = GetLastError(); + DEBUG_ERROR_HR(error, "Failed to open the LGIdd authority interface"); + continue; + } + if (QueryAuthorityHost(device, host)) + break; + + CloseHandle(device); + device = INVALID_HANDLE_VALUE; + } + + if (!SetupDiDestroyDeviceInfoList(devices)) + { + const DWORD error = GetLastError(); + DEBUG_WARN_HR(error, + "Failed to release the LGIdd authority interface list"); + } + if (device == INVALID_HANDLE_VALUE) + DEBUG_ERROR_HR(ERROR_NOT_FOUND, + "No usable LGIdd authority interface was found"); + return device; +} + +static bool RegisterClipboardAuthority(DWORD session, + const uint64_t (&mappingId)[2], HANDLE mapping) +{ + ClearClipboardAuthority(); + + LGIddAuthorityHost host = {}; + HANDLE device = OpenAuthorityDevice(host); + if (device == INVALID_HANDLE_VALUE) + return false; + + HANDLE hostProcess = OpenProcess(PROCESS_DUP_HANDLE | + PROCESS_QUERY_LIMITED_INFORMATION | SYNCHRONIZE, + FALSE, host.processId); + if (!hostProcess) + { + const DWORD error = GetLastError(); + DEBUG_ERROR_HR(error, "Failed to open the LGIdd authority host process"); + CloseHandle(device); + return false; + } + + FILETIME created = {}; + FILETIME exited = {}; + FILETIME kernel = {}; + FILETIME user = {}; + if (!GetProcessTimes(hostProcess, &created, &exited, &kernel, &user)) + { + const DWORD error = GetLastError(); + DEBUG_ERROR_HR(error, + "Failed to verify the LGIdd authority host process"); + CloseHandle(hostProcess); + CloseHandle(device); + return false; + } + if (FileTimeValue(created) != host.processCreated) + { + DEBUG_ERROR_HR(ERROR_ACCESS_DENIED, + "LGIdd authority host process identity changed"); + CloseHandle(hostProcess); + CloseHandle(device); + return false; + } + const DWORD hostWait = WaitForSingleObject(hostProcess, 0); + if (hostWait == WAIT_FAILED) + { + const DWORD error = GetLastError(); + DEBUG_ERROR_HR(error, + "Failed to check the LGIdd authority host process"); + CloseHandle(hostProcess); + CloseHandle(device); + return false; + } + if (hostWait != WAIT_TIMEOUT) + { + DEBUG_ERROR_HR(ERROR_PROCESS_ABORTED, + "LGIdd authority host process exited during registration"); + CloseHandle(hostProcess); + CloseHandle(device); + return false; + } + + HANDLE remoteMapping = nullptr; + if (!DuplicateHandle(GetCurrentProcess(), mapping, + hostProcess, &remoteMapping, + SECTION_MAP_READ | SECTION_MAP_WRITE, FALSE, 0)) + { + const DWORD error = GetLastError(); + DEBUG_ERROR_HR(error, + "Failed to duplicate the clipboard mapping into LGIdd"); + CloseHandle(hostProcess); + CloseHandle(device); + return false; + } + + LGIddAuthorityRegistration registration = {}; + registration.size = sizeof(registration); + registration.version = LG_IDD_AUTHORITY_VERSION; + registration.session = session; + registration.instanceId[0] = host.instanceId[0]; + registration.instanceId[1] = host.instanceId[1]; + registration.mappingId[0] = mappingId[0]; + registration.mappingId[1] = mappingId[1]; + registration.mappingHandle = reinterpret_cast(remoteMapping); + + LGIddAuthorityHost confirmedHost = {}; + if (!QueryAuthorityHost(device, confirmedHost) || + memcmp(&confirmedHost, &host, sizeof(host)) != 0) + { + if (confirmedHost.size) + DEBUG_ERROR_HR(ERROR_ACCESS_DENIED, + "LGIdd authority host changed before registration"); + DEBUG_WARN("The unclaimed LGIdd mapping handle will be reclaimed when " + "the driver host exits"); + CloseHandle(hostProcess); + CloseHandle(device); + return false; + } + const DWORD confirmedWait = WaitForSingleObject(hostProcess, 0); + if (confirmedWait == WAIT_FAILED) + { + const DWORD error = GetLastError(); + DEBUG_ERROR_HR(error, + "Failed to recheck the LGIdd authority host process"); + DEBUG_WARN("The unclaimed LGIdd mapping handle will be reclaimed when " + "the driver host exits"); + CloseHandle(hostProcess); + CloseHandle(device); + return false; + } + if (confirmedWait != WAIT_TIMEOUT) + { + DEBUG_ERROR_HR(ERROR_PROCESS_ABORTED, + "LGIdd authority host exited before registration"); + CloseHandle(hostProcess); + CloseHandle(device); + return false; + } + + DWORD bytes = 0; + const BOOL registered = DeviceIoControl(device, + IOCTL_LG_IDD_AUTHORITY_REGISTER, + ®istration, sizeof(registration), nullptr, 0, &bytes, nullptr); + const DWORD registerError = registered ? ERROR_SUCCESS : GetLastError(); + CloseHandle(hostProcess); + if (!registered || bytes) + { + DEBUG_ERROR_HR(registered ? ERROR_INVALID_DATA : registerError, + "Failed to register clipboard authority with LGIdd"); + DEBUG_WARN("The unclaimed LGIdd mapping handle will be reclaimed when " + "the driver host exits"); + CloseHandle(device); + return false; + } + + l_authorityDevice = device; + l_authorityHost = host; + DEBUG_INFO("Registered clipboard authority with LGIdd process %lu", + host.processId); + return true; +} + +static bool VerifyClipboardAuthority() +{ + if (l_authorityDevice == INVALID_HANDLE_VALUE) + return false; + + LGIddAuthorityHost current = {}; + if (!QueryAuthorityHost(l_authorityDevice, current)) + return false; + if (memcmp(¤t, &l_authorityHost, sizeof(current)) != 0) + { + DEBUG_ERROR_HR(ERROR_ACCESS_DENIED, + "LGIdd authority host identity changed"); + return false; + } + return true; +} + +static void ClearClipboardAuthority() +{ + if (l_authorityDevice == INVALID_HANDLE_VALUE) + return; + + LGIddAuthorityClear clear = {}; + clear.size = sizeof(clear); + clear.version = LG_IDD_AUTHORITY_VERSION; + clear.instanceId[0] = l_authorityHost.instanceId[0]; + clear.instanceId[1] = l_authorityHost.instanceId[1]; + + DWORD bytes = 0; + if (!DeviceIoControl(l_authorityDevice, + IOCTL_LG_IDD_AUTHORITY_CLEAR, + &clear, sizeof(clear), nullptr, 0, &bytes, nullptr)) + { + const DWORD error = GetLastError(); + DEBUG_WARN_HR(error, "Failed to clear clipboard authority in LGIdd"); + } + else if (bytes) + DEBUG_WARN( + "LGIdd returned unexpected clipboard authority clear data"); + + CloseHandle(l_authorityDevice); + l_authorityDevice = INVALID_HANDLE_VALUE; + ZeroMemory(&l_authorityHost, sizeof(l_authorityHost)); } static bool Launch(DWORD sessionId) @@ -481,81 +1264,350 @@ static bool Launch(DWORD sessionId) if (l_process.IsValid()) return false; - HandleT sysToken; - if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_DUPLICATE | - TOKEN_ASSIGN_PRIMARY | TOKEN_ADJUST_SESSIONID | TOKEN_ADJUST_DEFAULT, - sysToken.GetAddressOf())) + ClearClipboardAuthority(); + + if (sessionId == NO_CONSOLE_SESSION || sessionId == 0 || + WTSGetActiveConsoleSessionId() != sessionId) { - DEBUG_ERROR_HR(GetLastError(), "OpenProcessToken failed"); + DEBUG_WARN("Refusing to launch outside the active console session"); return false; } + if (!EnablePriv(SE_TCB_NAME)) + { + DEBUG_ERROR("Failed to enable %ls", SE_TCB_NAME); + return false; + } + + HANDLE queriedRaw = nullptr; + const bool queried = WTSQueryUserToken(sessionId, &queriedRaw) != FALSE; + const DWORD queryError = queried ? ERROR_SUCCESS : GetLastError(); + DisablePriv(SE_TCB_NAME); + HandleT queriedToken(queriedRaw); + if (!queried || !queriedToken.IsValid()) + { + DEBUG_ERROR_HR(queryError ? queryError : ERROR_NO_TOKEN, + "WTSQueryUserToken failed"); + return false; + } + + HandleT linkedToken; + DWORD returnedLen = 0; + TOKEN_ELEVATION_TYPE elevation = TokenElevationTypeDefault; + if (!GetTokenInformation(queriedToken.Get(), TokenElevationType, + &elevation, sizeof(elevation), &returnedLen)) + { + DEBUG_ERROR_HR(GetLastError(), + "Failed to inspect the interactive user token elevation"); + return false; + } + HANDLE sourceToken = queriedToken.Get(); + if (elevation == TokenElevationTypeFull) + { + TOKEN_LINKED_TOKEN linked = {}; + if (!GetTokenInformation(queriedToken.Get(), TokenLinkedToken, + &linked, sizeof(linked), &returnedLen)) + { + const DWORD error = GetLastError(); + DEBUG_ERROR_HR(error, + "Failed to query the limited linked interactive token"); + return false; + } + if (!linked.LinkedToken) + { + DEBUG_ERROR_HR(ERROR_NO_TOKEN, + "Elevated interactive token has no limited linked token"); + return false; + } + linkedToken.Attach(linked.LinkedToken); + sourceToken = linkedToken.Get(); + } + HandleT token; - if (!DuplicateTokenEx(sysToken.Get(), 0, NULL, SecurityAnonymous, - TokenPrimary, token.GetAddressOf())) + const DWORD tokenAccess = TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | + TOKEN_QUERY; + if (!DuplicateTokenEx(sourceToken, tokenAccess, nullptr, + SecurityImpersonation, TokenPrimary, token.GetAddressOf())) { - DEBUG_ERROR_HR(GetLastError(), "DuplicateTokenEx failed"); + DEBUG_ERROR_HR(GetLastError(), + "Failed to duplicate the interactive user token"); return false; } - DWORD origSessionID, returnedLen; - if (!GetTokenInformation(token.Get(), TokenSessionId, &origSessionID, - sizeof(origSessionID), &returnedLen)) + std::vector logonSid; + if (!ValidateLaunchToken(token.Get(), sessionId, logonSid)) { - DEBUG_ERROR_HR(GetLastError(), "GetTokenInformation failed"); + const DWORD error = GetLastError(); + DEBUG_ERROR_HR(error, + "Rejected the interactive user token"); return false; } - if (origSessionID != sessionId) + CObjectSecurity lifetimeSecurity; + CObjectSecurity stopSecurity; + if (!lifetimeSecurity.Init(logonSid.data(), MUTEX_ALL_ACCESS, + SYNCHRONIZE) || + !stopSecurity.Init(logonSid.data(), EVENT_ALL_ACCESS, SYNCHRONIZE)) { - if (!SetTokenInformation(token.Get(), TokenSessionId, - &sessionId, sizeof(sessionId))) - { - DEBUG_ERROR_HR(GetLastError(), "SetTokenInformation failed"); - return false; - } - } - - LPVOID env = NULL; - if (!CreateEnvironmentBlock(&env, token.Get(), TRUE)) - { - DEBUG_ERROR_HR(GetLastError(), "CreateEnvironmentBlock failed"); + DEBUG_ERROR_HR(GetLastError(), + "Failed to create child lifecycle security descriptors"); return false; } - if (!EnablePriv(SE_INCREASE_QUOTA_NAME)) + wchar_t lifetimeName[128]; + wchar_t stopEventName[128]; + wchar_t activationName[128]; + if (!GenerateChildObjectNames(lifetimeName, ARRAY_LENGTH(lifetimeName), + stopEventName, ARRAY_LENGTH(stopEventName), + activationName, ARRAY_LENGTH(activationName))) + return false; + + SetLastError(ERROR_SUCCESS); + const HANDLE lifetime = CreateMutexW( + lifetimeSecurity.Get(), TRUE, lifetimeName); + const DWORD lifetimeError = GetLastError(); + if (!lifetime) { - DEBUG_ERROR("Failed to enable %s", SE_INCREASE_QUOTA_NAME); + DEBUG_ERROR_HR(lifetimeError, + "Failed to create the child lifetime mutex"); + return false; + } + if (lifetimeError == ERROR_ALREADY_EXISTS) + { + CloseHandle(lifetime); + DEBUG_ERROR("The child lifetime mutex already exists"); + return false; + } + l_childLifetimeMutex.Attach(lifetime); + + SetLastError(ERROR_SUCCESS); + const HANDLE stopEvent = CreateEventW( + stopSecurity.Get(), TRUE, FALSE, stopEventName); + const DWORD stopError = GetLastError(); + if (!stopEvent) + { + DEBUG_ERROR_HR(stopError, + "Failed to create the child stop event"); + CloseChildLifetimeMutex(); + return false; + } + if (stopError == ERROR_ALREADY_EXISTS) + { + CloseHandle(stopEvent); + DEBUG_ERROR("The child stop event already exists"); + CloseChildLifetimeMutex(); + return false; + } + l_childStopEvent.Attach(stopEvent); + + SetLastError(ERROR_SUCCESS); + const HANDLE activationEvent = CreateEventW( + stopSecurity.Get(), TRUE, FALSE, activationName); + const DWORD activationError = GetLastError(); + if (!activationEvent) + { + DEBUG_ERROR_HR(activationError, + "Failed to create the child activation event"); + l_childStopEvent.Close(); + CloseChildLifetimeMutex(); + return false; + } + if (activationError == ERROR_ALREADY_EXISTS) + { + CloseHandle(activationEvent); + DEBUG_ERROR("The child activation event already exists"); + l_childStopEvent.Close(); + CloseChildLifetimeMutex(); + return false; + } + l_childActivationEvent.Attach(activationEvent); + + CObjectSecurity mappingSecurity; + if (!mappingSecurity.Init(logonSid.data(), SECTION_ALL_ACCESS, + SECTION_MAP_READ | SECTION_MAP_WRITE, true)) + { + const DWORD error = GetLastError(); + DEBUG_ERROR_HR(error, + "Failed to create clipboard mapping security descriptor"); + l_childStopEvent.Close(); + CloseChildLifetimeMutex(); + return false; + } + + uint64_t mappingId[2]; + if (!GenerateRandomId(mappingId, + "Failed to generate clipboard mapping identifier")) + { + l_childStopEvent.Close(); + CloseChildLifetimeMutex(); + return false; + } + + uint64_t authorityId[2]; + if (!GenerateRandomId(authorityId, + "Failed to generate clipboard authority identifier")) + { + l_childStopEvent.Close(); + CloseChildLifetimeMutex(); + return false; + } + + wchar_t mappingName[128]; + const int mappingNameResult = _snwprintf_s( + mappingName, ARRAY_LENGTH(mappingName), _TRUNCATE, + L"Global\\LookingGlassIDDClipboard-%016llx%016llx", + static_cast(mappingId[0]), + static_cast(mappingId[1])); + if (mappingNameResult < 0) + { + DEBUG_ERROR_HR(ERROR_INSUFFICIENT_BUFFER, + "Failed to format clipboard mapping name"); + l_childStopEvent.Close(); + CloseChildLifetimeMutex(); + return false; + } + + LARGE_INTEGER mappingBytes = {}; + mappingBytes.QuadPart = sizeof(ClipboardMapping); + + const bool globalEnabled = EnablePriv(SE_CREATE_GLOBAL_NAME); + const bool securityEnabled = globalEnabled && EnablePriv(SE_SECURITY_NAME); + if (!globalEnabled || !securityEnabled) + { + DEBUG_ERROR("Failed to enable clipboard mapping privileges"); + if (globalEnabled) + DisablePriv(SE_CREATE_GLOBAL_NAME); + l_childStopEvent.Close(); + CloseChildLifetimeMutex(); + return false; + } + + SetLastError(ERROR_SUCCESS); + const HANDLE clipboardMapping = CreateFileMappingW(INVALID_HANDLE_VALUE, + mappingSecurity.Get(), PAGE_READWRITE, mappingBytes.HighPart, + mappingBytes.LowPart, mappingName); + const DWORD mappingError = GetLastError(); + DisablePriv(SE_SECURITY_NAME); + DisablePriv(SE_CREATE_GLOBAL_NAME); + if (!clipboardMapping) + { + DEBUG_ERROR_HR(mappingError, + "Failed to create service-owned clipboard mapping"); + l_childStopEvent.Close(); + CloseChildLifetimeMutex(); + return false; + } + if (mappingError == ERROR_ALREADY_EXISTS) + { + CloseHandle(clipboardMapping); + DEBUG_ERROR("The service-owned clipboard mapping already exists"); + l_childStopEvent.Close(); + CloseChildLifetimeMutex(); + return false; + } + l_childClipboardMapping.Attach(clipboardMapping); + + ClipboardMapping * initialMapping = static_cast( + MapViewOfFile(clipboardMapping, FILE_MAP_READ | FILE_MAP_WRITE, + 0, 0, sizeof(ClipboardMapping))); + if (!initialMapping) + { + const DWORD error = GetLastError(); + DEBUG_ERROR_HR(error, + "Failed to initialize the clipboard authority identifier"); + l_childStopEvent.Close(); + CloseChildLifetimeMutex(); + return false; + } + + ZeroMemory(initialMapping, sizeof(*initialMapping)); + initialMapping->authorityId[0] = authorityId[0]; + initialMapping->authorityId[1] = authorityId[1]; + if (!UnmapViewOfFile(initialMapping)) + { + const DWORD error = GetLastError(); + DEBUG_ERROR_HR(error, + "Failed to unmap the initialized clipboard authority section"); + l_childStopEvent.Close(); + CloseChildLifetimeMutex(); + return false; + } + + LPVOID env = nullptr; + if (!CreateEnvironmentBlock(&env, token.Get(), FALSE)) + { + const DWORD error = GetLastError(); + DEBUG_ERROR_HR(error, "CreateEnvironmentBlock failed"); + l_childClipboardMapping.Close(); + l_childStopEvent.Close(); + CloseChildLifetimeMutex(); + return false; + } + + bool quotaEnabled = EnablePriv(SE_INCREASE_QUOTA_NAME); + bool assignEnabled = quotaEnabled && EnablePriv(SE_ASSIGNPRIMARYTOKEN_NAME); + if (!quotaEnabled || !assignEnabled) + { + DEBUG_ERROR("Failed to enable process-launch privileges"); + if (quotaEnabled) + DisablePriv(SE_INCREASE_QUOTA_NAME); DestroyEnvironmentBlock(env); + l_childClipboardMapping.Close(); + l_childStopEvent.Close(); + CloseChildLifetimeMutex(); return false; } - PROCESS_INFORMATION pi = {0}; - STARTUPINFO si = {0}; + PROCESS_INFORMATION pi = {}; + STARTUPINFO si = {}; si.cb = sizeof(si); si.dwFlags = STARTF_USESHOWWINDOW; si.wShowWindow = SW_SHOW; - si.lpDesktop = (LPWSTR) L"WinSta0\\Default"; + si.lpDesktop = const_cast(L"WinSta0\\Default"); - wchar_t stopEventName[128]; - _snwprintf_s(stopEventName, ARRAY_LENGTH(stopEventName), _TRUNCATE, - L"Global\\LookingGlassIDDHelperStop-%lu-%lu-%" PRIu64, - GetCurrentProcessId(), sessionId, GetTickCount64()); - - l_childStopEvent.Attach(CreateEvent(NULL, TRUE, FALSE, stopEventName)); - if (!l_childStopEvent.IsValid()) + wchar_t cmdBuf[608]; + const int commandResult = _snwprintf_s( + cmdBuf, ARRAY_LENGTH(cmdBuf), _TRUNCATE, + L"\"LGIddHelper.exe\" %s %s %s %016llx%016llx", + lifetimeName, stopEventName, activationName, + static_cast(mappingId[0]), + static_cast(mappingId[1])); + if (commandResult < 0) { - DEBUG_ERROR_HR(GetLastError(), "Failed to create the child stop event"); + DEBUG_ERROR_HR(ERROR_INSUFFICIENT_BUFFER, + "Failed to build the child command line"); + DisablePriv(SE_ASSIGNPRIMARYTOKEN_NAME); DisablePriv(SE_INCREASE_QUOTA_NAME); DestroyEnvironmentBlock(env); + l_childClipboardMapping.Close(); + l_childStopEvent.Close(); + CloseChildLifetimeMutex(); return false; } - wchar_t cmdBuf[256]; - _snwprintf_s(cmdBuf, ARRAY_LENGTH(cmdBuf), _TRUNCATE, - L"LGIddHelper.exe %" PRIu32 L" %s", GetCurrentProcessId(), stopEventName); + if (WTSGetActiveConsoleSessionId() != sessionId) + { + DEBUG_WARN("Active console session changed before child launch"); + DisablePriv(SE_ASSIGNPRIMARYTOKEN_NAME); + DisablePriv(SE_INCREASE_QUOTA_NAME); + DestroyEnvironmentBlock(env); + l_childClipboardMapping.Close(); + l_childStopEvent.Close(); + CloseChildLifetimeMutex(); + return false; + } - const bool created = CreateProcessAsUser( + if (!RegisterClipboardAuthority(sessionId, mappingId, clipboardMapping)) + { + DisablePriv(SE_ASSIGNPRIMARYTOKEN_NAME); + DisablePriv(SE_INCREASE_QUOTA_NAME); + DestroyEnvironmentBlock(env); + l_childStopEvent.Close(); + CloseChildLifetimeMutex(); + return false; + } + + const bool created = CreateProcessAsUserW( token.Get(), l_executable.c_str(), cmdBuf, @@ -570,13 +1622,116 @@ static bool Launch(DWORD sessionId) ); const DWORD createError = created ? ERROR_SUCCESS : GetLastError(); + DisablePriv(SE_ASSIGNPRIMARYTOKEN_NAME); DisablePriv(SE_INCREASE_QUOTA_NAME); DestroyEnvironmentBlock(env); if (!created) { + ClearClipboardAuthority(); DEBUG_ERROR_HR(createError, "CreateProcessAsUser failed"); l_childStopEvent.Close(); + CloseChildLifetimeMutex(); + return false; + } + + const auto discardGatedChild = [&pi]() + { + ClearClipboardAuthority(); + if (!SetEvent(l_childStopEvent.Get())) + { + const DWORD error = GetLastError(); + DEBUG_ERROR_HR(error, + "Failed to cancel the gated child process"); + } + + DWORD waitResult = WaitForSingleObject(pi.hProcess, 5000); + if (waitResult == WAIT_FAILED) + { + const DWORD error = GetLastError(); + DEBUG_ERROR_HR(error, + "Failed to wait for the gated child process to stop"); + } + else if (waitResult == WAIT_TIMEOUT) + { + if (!TerminateProcess(pi.hProcess, EXIT_FAILURE)) + { + const DWORD error = GetLastError(); + DEBUG_ERROR_HR(error, + "Failed to terminate the gated child process"); + } + else + { + waitResult = WaitForSingleObject(pi.hProcess, 1000); + if (waitResult == WAIT_FAILED) + { + const DWORD error = GetLastError(); + DEBUG_ERROR_HR(error, + "Failed to wait for the terminated gated child process"); + } + else if (waitResult != WAIT_OBJECT_0) + DEBUG_ERROR_HR(ERROR_TIMEOUT, + "Terminated gated child process did not exit in time"); + } + } + + CloseHandle(pi.hThread); + CloseHandle(pi.hProcess); + ZeroMemory(&pi, sizeof(pi)); + l_childStopEvent.Close(); + CloseChildLifetimeMutex(); + }; + + DWORD finalTokenSession = NO_CONSOLE_SESSION; + DWORD finalTokenBytes = 0; + if (!GetTokenInformation(token.Get(), TokenSessionId, + &finalTokenSession, sizeof(finalTokenSession), &finalTokenBytes)) + { + const DWORD error = GetLastError(); + DEBUG_ERROR_HR(error, + "Failed to recheck the child token session before activation"); + discardGatedChild(); + return false; + } + if (finalTokenSession != sessionId) + { + DEBUG_WARN( + "Child token session changed before activation"); + discardGatedChild(); + return false; + } + + const DWORD finalActiveSession = WTSGetActiveConsoleSessionId(); + if (finalActiveSession != sessionId) + { + DEBUG_WARN( + "Active console session changed before child activation"); + discardGatedChild(); + return false; + } + + const DWORD stopResult = WaitForSingleObject(l_svcStopEvent.Get(), 0); + if (stopResult == WAIT_FAILED) + { + const DWORD error = GetLastError(); + DEBUG_ERROR_HR(error, + "Failed to check the service stop event before child activation"); + discardGatedChild(); + return false; + } + if (stopResult != WAIT_TIMEOUT) + { + DEBUG_WARN( + "Service stop requested before child activation"); + discardGatedChild(); + return false; + } + + if (!SetEvent(l_childActivationEvent.Get())) + { + const DWORD error = GetLastError(); + DEBUG_ERROR_HR(error, "Failed to activate the child process"); + discardGatedChild(); return false; } @@ -588,11 +1743,28 @@ static bool Launch(DWORD sessionId) return true; } +static void CloseChildLifetimeMutex() +{ + l_childClipboardMapping.Close(); + l_childActivationEvent.Close(); + if (!l_childLifetimeMutex.IsValid()) + return; + + if (!ReleaseMutex(l_childLifetimeMutex.Get())) + { + const DWORD error = GetLastError(); + DEBUG_ERROR_HR(error, "Failed to release the child lifetime mutex"); + } + l_childLifetimeMutex.Close(); +} + static bool StopChild() { + ClearClipboardAuthority(); if (!l_process.IsValid()) { l_childStopEvent.Close(); + CloseChildLifetimeMutex(); l_childSession = NO_CONSOLE_SESSION; return true; } @@ -611,7 +1783,16 @@ static bool StopChild() return false; } else + { result = WaitForSingleObject(l_process.Get(), 1000); + if (result == WAIT_FAILED) + { + const DWORD error = GetLastError(); + DEBUG_ERROR_HR(error, + "Failed to wait for the terminated child process"); + return false; + } + } } else if (result == WAIT_FAILED) { @@ -627,6 +1808,7 @@ static bool StopChild() l_process.Close(); l_childStopEvent.Close(); + CloseChildLifetimeMutex(); l_childSession = NO_CONSOLE_SESSION; return true; } diff --git a/idd/LGIddInstall/LGIddInstall.c b/idd/LGIddInstall/LGIddInstall.c index abd38bc9..b789877b 100644 --- a/idd/LGIddInstall/LGIddInstall.c +++ b/idd/LGIddInstall/LGIddInstall.c @@ -134,15 +134,16 @@ void debugWinError(const wchar_t *desc, HRESULT status) bool ensureKeyWithAce() { bool result = false; - const PCWSTR accountName = L"NT AUTHORITY\\USER MODE DRIVERS"; - HKEY hKey = NULL; - DWORD disp = 0; - REGSAM sam = KEY_READ | KEY_WRITE | WRITE_DAC | READ_CONTROL | KEY_WOW64_64KEY; - PACL oldDacl = NULL; - PSECURITY_DESCRIPTOR psd = NULL; - PACL newDacl = NULL; - PSID pSid = NULL; + HKEY hKey = NULL; + DWORD disp = 0; + REGSAM sam = KEY_READ | KEY_WRITE | WRITE_DAC | + READ_CONTROL | KEY_WOW64_64KEY; + PACL oldDacl = NULL; + PSECURITY_DESCRIPTOR psd = NULL; + PACL newDacl = NULL; + PSID driverSid = NULL; + PSID interactiveSid = NULL; DWORD ec = RegCreateKeyExW(HKEY_LOCAL_MACHINE, LGIDD_REGKEY, 0, NULL, 0, sam, NULL, &hKey, &disp); if (ec != ERROR_SUCCESS) @@ -158,22 +159,52 @@ bool ensureKeyWithAce() goto cleanup; } - pSid = malloc(SECURITY_MAX_SID_SIZE); - DWORD cbSid = SECURITY_MAX_SID_SIZE; - if (!CreateWellKnownSid(WinUserModeDriversSid, NULL, pSid, &cbSid)) + driverSid = malloc(SECURITY_MAX_SID_SIZE); + if (!driverSid) { - debugWinError(L"CreateWellKnownSid", GetLastError()); + debugWinError(L"malloc(USER MODE DRIVERS SID)", ERROR_OUTOFMEMORY); + goto cleanup; + } + DWORD cbSid = SECURITY_MAX_SID_SIZE; + if (!CreateWellKnownSid( + WinUserModeDriversSid, NULL, driverSid, &cbSid)) + { + debugWinError(L"CreateWellKnownSid(WinUserModeDriversSid)", + GetLastError()); goto cleanup; } - EXPLICIT_ACCESSW ea = {0}; - ea.grfAccessPermissions = KEY_ALL_ACCESS; - ea.grfAccessMode = GRANT_ACCESS; - ea.grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; - ea.Trustee.TrusteeForm = TRUSTEE_IS_SID; - ea.Trustee.ptstrName = (LPWSTR)pSid; + interactiveSid = malloc(SECURITY_MAX_SID_SIZE); + if (!interactiveSid) + { + debugWinError(L"malloc(INTERACTIVE SID)", ERROR_OUTOFMEMORY); + goto cleanup; + } + cbSid = SECURITY_MAX_SID_SIZE; + if (!CreateWellKnownSid( + WinInteractiveSid, NULL, interactiveSid, &cbSid)) + { + debugWinError(L"CreateWellKnownSid(WinInteractiveSid)", + GetLastError()); + goto cleanup; + } - ec = SetEntriesInAclW(1, &ea, oldDacl, &newDacl); + EXPLICIT_ACCESSW ea[2] = {0}; + ea[0].grfAccessPermissions = KEY_QUERY_VALUE | KEY_SET_VALUE; + ea[0].grfAccessMode = SET_ACCESS; + ea[0].grfInheritance = NO_INHERITANCE; + ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID; + ea[0].Trustee.ptstrName = (LPWSTR)driverSid; + + // The interactive Helper may update values on this exact key, but cannot + // create subkeys, delete it, or change its security descriptor. + ea[1].grfAccessPermissions = KEY_QUERY_VALUE | KEY_SET_VALUE; + ea[1].grfAccessMode = SET_ACCESS; + ea[1].grfInheritance = NO_INHERITANCE; + ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID; + ea[1].Trustee.ptstrName = (LPWSTR)interactiveSid; + + ec = SetEntriesInAclW(ARRAYSIZE(ea), ea, oldDacl, &newDacl); if (ec != ERROR_SUCCESS) { debugWinError(L"SetEntriesInAclW", ec); @@ -193,9 +224,14 @@ bool ensureKeyWithAce() result = true; cleanup: - if (newDacl) LocalFree(newDacl); - if (pSid) free(pSid); - if (psd) LocalFree(psd); + if (newDacl) + LocalFree(newDacl); + if (interactiveSid) + free(interactiveSid); + if (driverSid) + free(driverSid); + if (psd) + LocalFree(psd); RegCloseKey(hKey); return result; diff --git a/idd/LGInput/LGInput.vcxproj b/idd/LGInput/LGInput.vcxproj index 74bd4179..d9c98222 100644 --- a/idd/LGInput/LGInput.vcxproj +++ b/idd/LGInput/LGInput.vcxproj @@ -73,6 +73,7 @@ LGInput DbgengRemoteDebugger true + /sw2084