mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-22 07:01:30 +00:00
[idd] helper: run interactive process as desktop user
Explorer file copies were invisible to the Helper because the service launched its child with a duplicate of the LocalSystem token and changed only TokenSessionId. The child therefore remained a System-integrity process. Windows filtered Explorer's file clipboard formats across that integrity boundary. Basic text and bitmap formats continued to work. Keep only the SCM service privileged. Obtain the active session user's primary token with WTSQueryUserToken. For elevated accounts, prefer the linked limited token. Validate its session and security properties. Build the user environment and launch the interactive Helper on WinSta0\Default. Gate Helper activation until the service has rechecked the active session and registered the clipboard authority. Use random lifetime, stop, and activation objects owned by the service. Give the target logon SID synchronization access only. Recheck the active console session and service state before activation. Make the lifetime mutex terminate the Helper if the service exits unexpectedly. Restart it when the active session, IDD host, or authority changes. Replace the old process-handle mapping transfer with a device-bound authority protocol on the LGIdd device interface. The service verifies the exact driver host instance, duplicates only section map rights into that process, and registers the session, mapping identifier, and handle. Bind authority lifetime to its WDF file object, revoke it synchronously on cleanup, and poll the driver host identity while the child is active. Restrict the device stack to SYSTEM and isolate LGIdd in a unique UMDF device group. Restrict the shared section to SYSTEM and the target logon SID. Apply a medium mandatory label that prevents low-integrity readers and writers. Map it with read/write rights instead of all access. Give each clipboard mapping a second random authority identifier. Store it only inside the logon-SID-protected mapping and send it in the mandatory HELLO. LGIdd matches it against the service-injected mapping. This authenticates the user Helper without the unsupported UMDF call to GetNamedPipeClientSessionId. Have the Helper verify that its pipe server is in session zero. Extend the pipe endpoint with bounded authentication reads, cancellable overlapped I/O, periodic authorization checks, and explicit disconnects. Serialize authority changes with clipboard attach and detach. Prevent stale cleanup from tearing down a replacement mapping. Disconnect the user pipe immediately when its owning authority is revoked. Run clipboard, OLE, display, configuration, and file access in the user's interactive process. Retain its process token for worker-thread file operations instead of querying and impersonating the desktop user from a System process. Store Helper logs in LocalAppData and grant only the registry rights needed by interactive configuration and UMDF. Keep immediate, stage-specific Win32 and HRESULT diagnostics throughout clipboard capture. Probe CF_HDROP while holding the Win32 clipboard and enumerate the OLE object's advertised file formats. Validate returned storage and fall back to Shell item paths when direct retrieval fails. Validate clipboard sequence changes and defer retries during contention without publishing incomplete clipboard state. Complete the 1 MiB transfer work with full-sized Windows copy buffers. Use full-sized FUSE reads and retain the named 64 KiB X11 chunk limit. Validate the user-writable mapping with CClipboardRing before attaching. The pipe, mapping, and authority protocols change together. LGIdd.dll, the INF, and LGIddHelper.exe must be rebuilt and installed as one matching set.
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -154,7 +154,8 @@ bool CClipboardChannel::Attach(HANDLE mapping, uint64_t epoch,
|
||||
|
||||
CSRWExclusiveLock lock(m_lifecycleLock);
|
||||
ClipboardMapping * view = static_cast<ClipboardMapping *>(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");
|
||||
|
||||
@@ -25,15 +25,18 @@
|
||||
#include <string.h>
|
||||
|
||||
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;
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include <Windows.h>
|
||||
#include <string>
|
||||
|
||||
#include <cerrno>
|
||||
#include <stdio.h>
|
||||
#include <malloc.h>
|
||||
#include <strsafe.h>
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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__)
|
||||
#define DEBUG_FATAL_HR(hr, fmt, ...) g_debug.LogHR(CDebug::LEVEL_FATAL, hr, __FUNCTION__, __LINE__, fmt, ##__VA_ARGS__)
|
||||
|
||||
@@ -26,11 +26,13 @@
|
||||
#include <stdint.h>
|
||||
#include <vector>
|
||||
|
||||
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<uint8_t> message(m_messageSize);
|
||||
DWORD bytesRead = 0;
|
||||
const PipeIoResult authResult = ReadMessage(
|
||||
pipe,
|
||||
ioEvent,
|
||||
message.data(),
|
||||
static_cast<DWORD>(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<unsigned long long>(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<uint8_t> 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<DWORD>(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<DWORD>(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()))
|
||||
{
|
||||
|
||||
@@ -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<bool> m_running { false };
|
||||
std::atomic<bool> m_connected { false };
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
#include <stdint.h>
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
@@ -81,6 +81,7 @@
|
||||
<ClInclude Include="CSRWLock.h" />
|
||||
<ClInclude Include="DefaultDisplayModes.h" />
|
||||
<ClInclude Include="InputPipeProtocol.h" />
|
||||
<ClInclude Include="LGIddAuthority.h" />
|
||||
<ClInclude Include="PipeMsg.h" />
|
||||
<ClInclude Include="RefreshRate.h" />
|
||||
<ClInclude Include="Seq.h" />
|
||||
|
||||
@@ -56,6 +56,9 @@
|
||||
<ClInclude Include="InputPipeProtocol.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="LGIddAuthority.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="PipeMsg.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
|
||||
76
idd/LGCommon/LGIddAuthority.h
Normal file
76
idd/LGCommon/LGIddAuthority.h
Normal file
@@ -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 <Windows.h>
|
||||
#include <winioctl.h>
|
||||
#include <stdint.h>
|
||||
|
||||
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");
|
||||
@@ -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");
|
||||
|
||||
@@ -27,11 +27,14 @@
|
||||
#include <wdf.h>
|
||||
#include <IddCx.h>
|
||||
#include <avrt.h>
|
||||
#include <bcrypt.h>
|
||||
#include <wrl.h>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#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<PUCHAR>(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<void **>(&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<const LGIddAuthorityRegistration *>(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<uint64_t>(
|
||||
(std::numeric_limits<uintptr_t>::max)()) ||
|
||||
registration->mappingHandle == static_cast<uint64_t>(
|
||||
reinterpret_cast<uintptr_t>(INVALID_HANDLE_VALUE)))
|
||||
{
|
||||
DEBUG_WARN(
|
||||
"Rejected invalid LGIdd authority registration data");
|
||||
status = STATUS_DATA_ERROR;
|
||||
break;
|
||||
}
|
||||
|
||||
HANDLE rawMapping = reinterpret_cast<HANDLE>(
|
||||
static_cast<uintptr_t>(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<const ClipboardMapping *>(
|
||||
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<const LGIddAuthorityClear *>(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
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "public.h"
|
||||
#include "LGIddAuthority.h"
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
|
||||
Binary file not shown.
@@ -91,7 +91,6 @@
|
||||
<ItemGroup>
|
||||
<ClInclude Include="Device.h" />
|
||||
<ClInclude Include="Driver.h" />
|
||||
<ClInclude Include="Public.h" />
|
||||
<ClInclude Include="Trace.h" />
|
||||
<ClInclude Include="input\IInputSink.h" />
|
||||
<ClInclude Include="ipc\CInputPipeServer.h" />
|
||||
@@ -279,6 +278,7 @@
|
||||
<PropertyGroup>
|
||||
<LGPackageToolOutDir Condition="'$(Platform)'=='x64'">$([MSBuild]::NormalizeDirectory('$(LGDriverSolutionDir)$(Platform)\$(LGBaseConfiguration)'))</LGPackageToolOutDir>
|
||||
<LGPackageToolOutDir Condition="'$(Platform)'=='Win32'">$([MSBuild]::NormalizeDirectory('$(LGDriverSolutionDir)$(LGBaseConfiguration)'))</LGPackageToolOutDir>
|
||||
<InfVerif_AdditionalOptions>/sw2084</InfVerif_AdditionalOptions>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
@@ -289,7 +289,7 @@
|
||||
<AdditionalIncludeDirectories>$(ProjectDir);$(SolutionDir)LGCommon;$(ProjectDir)..\..\repos\LGMP\lgmp\include;$(ProjectDir)..\..\vendor;$(ProjectDir)..\..\common\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies>%(AdditionalDependencies);OneCoreUAP.lib;avrt.lib;d3d12.lib;d3dcompiler.lib</AdditionalDependencies>
|
||||
<AdditionalDependencies>%(AdditionalDependencies);OneCoreUAP.lib;avrt.lib;bcrypt.lib;d3d12.lib;d3dcompiler.lib</AdditionalDependencies>
|
||||
</Link>
|
||||
<DriverSign>
|
||||
<FileDigestAlgorithm>SHA1</FileDigestAlgorithm>
|
||||
@@ -304,7 +304,7 @@
|
||||
<AdditionalIncludeDirectories>$(ProjectDir);$(SolutionDir)LGCommon;$(ProjectDir)..\..\repos\LGMP\lgmp\include;$(ProjectDir)..\..\vendor;$(ProjectDir)..\..\common\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies>%(AdditionalDependencies);OneCoreUAP.lib;avrt.lib;d3d12.lib;d3dcompiler.lib</AdditionalDependencies>
|
||||
<AdditionalDependencies>%(AdditionalDependencies);OneCoreUAP.lib;avrt.lib;bcrypt.lib;d3d12.lib;d3dcompiler.lib</AdditionalDependencies>
|
||||
</Link>
|
||||
<DriverSign>
|
||||
<FileDigestAlgorithm>SHA1</FileDigestAlgorithm>
|
||||
@@ -319,7 +319,7 @@
|
||||
<AdditionalIncludeDirectories>$(ProjectDir);$(SolutionDir)LGCommon;$(ProjectDir)..\..\repos\LGMP\lgmp\include;$(ProjectDir)..\..\vendor;$(ProjectDir)..\..\common\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies>%(AdditionalDependencies);OneCoreUAP.lib;avrt.lib;d3d12.lib;d3dcompiler.lib</AdditionalDependencies>
|
||||
<AdditionalDependencies>%(AdditionalDependencies);OneCoreUAP.lib;avrt.lib;bcrypt.lib;d3d12.lib;d3dcompiler.lib</AdditionalDependencies>
|
||||
</Link>
|
||||
<DriverSign>
|
||||
<FileDigestAlgorithm>SHA1</FileDigestAlgorithm>
|
||||
@@ -334,7 +334,7 @@
|
||||
<AdditionalIncludeDirectories>$(ProjectDir);$(SolutionDir)LGCommon;$(ProjectDir)..\..\repos\LGMP\lgmp\include;$(ProjectDir)..\..\vendor;$(ProjectDir)..\..\common\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies>%(AdditionalDependencies);OneCoreUAP.lib;avrt.lib;d3d12.lib;d3dcompiler.lib</AdditionalDependencies>
|
||||
<AdditionalDependencies>%(AdditionalDependencies);OneCoreUAP.lib;avrt.lib;bcrypt.lib;d3d12.lib;d3dcompiler.lib</AdditionalDependencies>
|
||||
</Link>
|
||||
<DriverSign>
|
||||
<FileDigestAlgorithm>SHA1</FileDigestAlgorithm>
|
||||
|
||||
@@ -58,9 +58,6 @@
|
||||
<ClInclude Include="Driver.h">
|
||||
<Filter>Driver</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Public.h">
|
||||
<Filter>Driver</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Trace.h">
|
||||
<Filter>Driver</Filter>
|
||||
</ClInclude>
|
||||
|
||||
@@ -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);
|
||||
@@ -19,29 +19,340 @@
|
||||
*/
|
||||
|
||||
#include "ipc/CPipeServer.h"
|
||||
#include "CClipboardRing.h"
|
||||
#include "CDebug.h"
|
||||
#include "CSRWLock.h"
|
||||
#include "display/CDeviceContext.h"
|
||||
|
||||
#include <sddl.h>
|
||||
#include <vector>
|
||||
|
||||
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<const ClipboardMapping *>(
|
||||
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<unsigned long long>(size),
|
||||
static_cast<unsigned long long>(sizeof(LGPipeMsg)));
|
||||
return false;
|
||||
}
|
||||
const LGPipeMsg& hello = *static_cast<const LGPipeMsg *>(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<unsigned>(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<const ClipboardMapping *>(
|
||||
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<LGPipeMsg> 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<HANDLE>(
|
||||
static_cast<uintptr_t>(msg.clipboardSetup.handle));
|
||||
HANDLE mapping = transferred;
|
||||
uint64_t epoch = 0;
|
||||
if (transferred &&
|
||||
msg.clipboardSetup.bytes == sizeof(ClipboardMapping))
|
||||
{
|
||||
ClipboardMapping * view = static_cast<ClipboardMapping *>(
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
|
||||
#include <ShlObj.h>
|
||||
#include <strsafe.h>
|
||||
#include <WtsApi32.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
@@ -41,7 +40,7 @@ namespace
|
||||
static constexpr uint64_t WINDOWS_EPOCH_TICKS =
|
||||
UINT64_C(116444736000000000);
|
||||
static constexpr size_t COPY_BUFFER_BYTES =
|
||||
static_cast<size_t>(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<Impl>(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)
|
||||
{
|
||||
|
||||
@@ -45,25 +45,6 @@ struct ClipboardRemoteFileEntry
|
||||
std::wstring name;
|
||||
};
|
||||
|
||||
class CClipboardUserImpersonation final
|
||||
{
|
||||
private:
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> 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:
|
||||
|
||||
@@ -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<unsigned long>(sequence));
|
||||
return false;
|
||||
return CLIPBOARD_FILE_CANDIDATE_NONE;
|
||||
}
|
||||
|
||||
ClipboardComScope<IEnumFORMATETC> formats;
|
||||
@@ -129,15 +130,17 @@ namespace
|
||||
"Failed to inspect local clipboard file formats: "
|
||||
"stage=IDataObject::EnumFormatEtc sequence=%lu",
|
||||
static_cast<unsigned long>(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<unsigned long>(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<unsigned long>(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<unsigned long>(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<unsigned long>(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<unsigned long>(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<unsigned long>(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<unsigned long>(sequence),
|
||||
rawFormatCount, static_cast<unsigned int>(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<unsigned long>(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<unsigned long>(sequence), rawFormatCount);
|
||||
return nullptr;
|
||||
}
|
||||
if (files)
|
||||
return files;
|
||||
@@ -3488,7 +3508,7 @@ CClipboardManager::CaptureClipboardFiles(DWORD sequence, int rawFormatCount,
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ClipboardDataObjectScope object;
|
||||
ClipboardComScope<IDataObject> 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<CLIPFORMAT>(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<unsigned long>(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<CLIPFORMAT>(CF_HDROP),
|
||||
nullptr,
|
||||
DVASPECT_CONTENT,
|
||||
-1,
|
||||
TYMED_HGLOBAL,
|
||||
};
|
||||
ClipboardStorageScope storage;
|
||||
hdropError = object.object->GetData(&format, &storage.medium);
|
||||
if (FAILED(hdropError))
|
||||
{
|
||||
std::shared_ptr<CLocalClipboardFiles> files =
|
||||
CLocalClipboardFiles::Capture(
|
||||
static_cast<HDROP>(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<unsigned long>(sequence), rawFormatCount,
|
||||
static_cast<unsigned int>(error));
|
||||
return nullptr;
|
||||
"stage=IDataObject::GetData(CF_HDROP) sequence=%lu rawFormats=%d",
|
||||
static_cast<unsigned long>(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<unsigned long>(sequence), rawFormatCount,
|
||||
static_cast<unsigned long>(storage.medium.tymed),
|
||||
storage.medium.hGlobal ? 1U : 0U);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
files = CLocalClipboardFiles::Capture(
|
||||
static_cast<HDROP>(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<unsigned long>(sequence), rawFormatCount,
|
||||
static_cast<unsigned int>(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<unsigned long>(sequence), rawFormatCount,
|
||||
static_cast<unsigned long>(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<unsigned long>(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<unsigned long>(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<unsigned long>(sequence));
|
||||
FreeClipboardDrop(drop, sequence);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
files = CLocalClipboardFiles::Capture(static_cast<HDROP>(drop), error);
|
||||
@@ -3749,16 +3782,7 @@ CClipboardManager::CaptureClipboardFiles(DWORD sequence, int rawFormatCount,
|
||||
"fileError=%u", static_cast<unsigned long>(sequence),
|
||||
static_cast<unsigned int>(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<unsigned long>(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<CClipboardSpool> 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<DWORD>(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)
|
||||
{
|
||||
|
||||
@@ -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<CLocalClipboardFiles> CaptureClipboardFiles(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<LGPipeMsg *>(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<ClipboardMapping *>(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<uint64_t>(reinterpret_cast<uintptr_t>(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<unsigned long long>(m_clipboardMappingId[0]),
|
||||
static_cast<unsigned long long>(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<ClipboardMapping *>(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)
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -41,7 +41,7 @@ public:
|
||||
CRegistrySettings();
|
||||
~CRegistrySettings();
|
||||
|
||||
LSTATUS open();
|
||||
LSTATUS open(bool writable = false);
|
||||
bool isOpen() { return !!hKey; }
|
||||
|
||||
std::vector<DisplayMode> getDefaultModes();
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
|
||||
@@ -73,6 +73,7 @@
|
||||
<TargetName>LGInput</TargetName>
|
||||
<DebuggerFlavor>DbgengRemoteDebugger</DebuggerFlavor>
|
||||
<Inf2CatUseLocalTime>true</Inf2CatUseLocalTime>
|
||||
<InfVerif_AdditionalOptions>/sw2084</InfVerif_AdditionalOptions>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
|
||||
Reference in New Issue
Block a user