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 47de49de..36688cea 100644 Binary files a/idd/LGIdd/LGIdd.inf and b/idd/LGIdd/LGIdd.inf differ diff --git a/idd/LGIdd/LGIdd.vcxproj b/idd/LGIdd/LGIdd.vcxproj index c1aca813..30e89d46 100644 --- a/idd/LGIdd/LGIdd.vcxproj +++ b/idd/LGIdd/LGIdd.vcxproj @@ -91,7 +91,6 @@ - @@ -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