mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-10-19 10:23:10 +08:00
[fix] restore remote input for elevated Windows apps
This commit is contained in:
@@ -238,8 +238,11 @@ bool KeyboardController::InjectRemoteKey(int key_code, bool is_down,
|
|||||||
SetLastError(ERROR_SUCCESS);
|
SetLastError(ERROR_SUCCESS);
|
||||||
const bool sent = owner_.devices_.SendKeyboardCommand(
|
const bool sent = owner_.devices_.SendKeyboardCommand(
|
||||||
key_code, is_down, scan_code, extended);
|
key_code, is_down, scan_code, extended);
|
||||||
|
const DWORD error = GetLastError();
|
||||||
|
const bool blocked =
|
||||||
|
error == ERROR_ACCESS_DENIED || error == ERROR_GEN_FAILURE;
|
||||||
return DesktopInputResult{
|
return DesktopInputResult{
|
||||||
sent, IsDesktopTransitionInputError(GetLastError())};
|
sent, IsDesktopTransitionInputError(error) || blocked, blocked};
|
||||||
},
|
},
|
||||||
[&]() -> DesktopInputResult {
|
[&]() -> DesktopInputResult {
|
||||||
const std::string response = SendCrossDeskSecureDesktopKeyInput(
|
const std::string response = SendCrossDeskSecureDesktopKeyInput(
|
||||||
|
|||||||
@@ -404,8 +404,11 @@ void PeerEventHandler::OnReceiveDataBuffer(
|
|||||||
SetLastError(ERROR_SUCCESS);
|
SetLastError(ERROR_SUCCESS);
|
||||||
const bool sent = runtime->devices_.SendMouseCommand(
|
const bool sent = runtime->devices_.SendMouseCommand(
|
||||||
remote_action, runtime->selected_display_);
|
remote_action, runtime->selected_display_);
|
||||||
|
const DWORD error = GetLastError();
|
||||||
|
const bool blocked =
|
||||||
|
error == ERROR_ACCESS_DENIED || error == ERROR_GEN_FAILURE;
|
||||||
return DesktopInputResult{
|
return DesktopInputResult{
|
||||||
sent, IsDesktopTransitionInputError(GetLastError())};
|
sent, IsDesktopTransitionInputError(error) || blocked, blocked};
|
||||||
},
|
},
|
||||||
[&]() -> DesktopInputResult {
|
[&]() -> DesktopInputResult {
|
||||||
int absolute_x = 0;
|
int absolute_x = 0;
|
||||||
|
|||||||
@@ -39,6 +39,9 @@ struct DesktopInputResult {
|
|||||||
// A pipe timeout may have happened after a click/key was applied: never
|
// A pipe timeout may have happened after a click/key was applied: never
|
||||||
// replay it.
|
// replay it.
|
||||||
bool not_injected = false;
|
bool not_injected = false;
|
||||||
|
// The ordinary desktop injector rejected its single input event (for
|
||||||
|
// example UIPI). The authorized service can retry even on the same desktop.
|
||||||
|
bool retry_with_service = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename ProbeUserDesktop, typename SendUser, typename SendSecure>
|
template <typename ProbeUserDesktop, typename SendUser, typename SendSecure>
|
||||||
@@ -48,6 +51,10 @@ bool DispatchDesktopInput(ProbeUserDesktop probe_user, SendUser send_user,
|
|||||||
const auto result = user ? send_user() : send_secure();
|
const auto result = user ? send_user() : send_secure();
|
||||||
if (result.delivered) return true;
|
if (result.delivered) return true;
|
||||||
if (!result.not_injected) return false;
|
if (!result.not_injected) return false;
|
||||||
|
if (user && result.retry_with_service) {
|
||||||
|
// Do not replay a failed/ambiguous service request through another path.
|
||||||
|
return send_secure().delivered;
|
||||||
|
}
|
||||||
const bool current_user = probe_user();
|
const bool current_user = probe_user();
|
||||||
if (current_user == user) return false;
|
if (current_user == user) return false;
|
||||||
// Handle a switch between the probe and injection exactly once. Ordering is
|
// Handle a switch between the probe and injection exactly once. Ordering is
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ constexpr wchar_t kCrossDeskClientProcessName[] = L"crossdesk.exe";
|
|||||||
constexpr DWORD kCrossDeskClientMonitorIntervalMs = 1000;
|
constexpr DWORD kCrossDeskClientMonitorIntervalMs = 1000;
|
||||||
constexpr ULONGLONG kCrossDeskClientMonitorStartupGraceMs = 5000;
|
constexpr ULONGLONG kCrossDeskClientMonitorStartupGraceMs = 5000;
|
||||||
constexpr ULONGLONG kSasSecureDesktopGraceMs = 15000;
|
constexpr ULONGLONG kSasSecureDesktopGraceMs = 15000;
|
||||||
|
constexpr ULONGLONG kUserDesktopInputIdleMs = 5000;
|
||||||
|
|
||||||
using SendSasFunction = VOID(WINAPI*)(BOOL);
|
using SendSasFunction = VOID(WINAPI*)(BOOL);
|
||||||
|
|
||||||
@@ -1057,6 +1058,7 @@ int CrossDeskServiceHost::InitializeRuntime() {
|
|||||||
session_helper_report_state_age_ms_ = 0;
|
session_helper_report_state_age_ms_ = 0;
|
||||||
session_helper_report_uptime_ms_ = 0;
|
session_helper_report_uptime_ms_ = 0;
|
||||||
secure_input_helper_started_at_tick_ = 0;
|
secure_input_helper_started_at_tick_ = 0;
|
||||||
|
user_desktop_input_until_tick_ = 0;
|
||||||
sas_secure_desktop_until_tick_ = 0;
|
sas_secure_desktop_until_tick_ = 0;
|
||||||
session_helper_process_handle_ = nullptr;
|
session_helper_process_handle_ = nullptr;
|
||||||
session_helper_stop_event_ = nullptr;
|
session_helper_stop_event_ = nullptr;
|
||||||
@@ -1460,6 +1462,7 @@ void CrossDeskServiceHost::RefreshSessionState() {
|
|||||||
ResetSessionHelperReportedStateLocked("session_changed", 0);
|
ResetSessionHelperReportedStateLocked("session_changed", 0);
|
||||||
sas_secure_desktop_until_tick_ = 0;
|
sas_secure_desktop_until_tick_ = 0;
|
||||||
sas_secure_desktop_seen_ = false;
|
sas_secure_desktop_seen_ = false;
|
||||||
|
user_desktop_input_until_tick_ = 0;
|
||||||
}
|
}
|
||||||
process_session_id_ = process_session;
|
process_session_id_ = process_session;
|
||||||
logon_ui_visible_ = logon_ui;
|
logon_ui_visible_ = logon_ui;
|
||||||
@@ -1574,7 +1577,8 @@ bool CrossDeskServiceHost::ShouldKeepSecureInputHelperLocked(
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return HasSecureInputUiLocked() || GetEffectiveSessionLockedLocked();
|
return HasSecureInputUiLocked() || GetEffectiveSessionLockedLocked() ||
|
||||||
|
GetTickCount64() < user_desktop_input_until_tick_;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string CrossDeskServiceHost::ResolveInteractiveStageLocked() const {
|
std::string CrossDeskServiceHost::ResolveInteractiveStageLocked() const {
|
||||||
@@ -1622,6 +1626,10 @@ std::string CrossDeskServiceHost::ResolveInteractiveDesktopLocked(
|
|||||||
return "Default";
|
return "Default";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (interactive_stage == "user-desktop") {
|
||||||
|
return "Default";
|
||||||
|
}
|
||||||
|
|
||||||
if (input_desktop_available_ && !input_desktop_name_.empty() &&
|
if (input_desktop_available_ && !input_desktop_name_.empty() &&
|
||||||
(interactive_stage == "credential-ui" || consent_ui_visible_)) {
|
(interactive_stage == "credential-ui" || consent_ui_visible_)) {
|
||||||
return input_desktop_name_;
|
return input_desktop_name_;
|
||||||
@@ -2174,8 +2182,12 @@ void CrossDeskServiceHost::RecordSessionEvent(DWORD event_type,
|
|||||||
ResetSessionHelperReportedStateLocked("session_changed", 0);
|
ResetSessionHelperReportedStateLocked("session_changed", 0);
|
||||||
sas_secure_desktop_until_tick_ = 0;
|
sas_secure_desktop_until_tick_ = 0;
|
||||||
sas_secure_desktop_seen_ = false;
|
sas_secure_desktop_seen_ = false;
|
||||||
|
user_desktop_input_until_tick_ = 0;
|
||||||
}
|
}
|
||||||
if (session_id == active_session_id_) {
|
if (session_id == active_session_id_) {
|
||||||
|
if (event_type == WTS_SESSION_LOGOFF) {
|
||||||
|
user_desktop_input_until_tick_ = 0;
|
||||||
|
}
|
||||||
// Do not let an older helper sample override a session notification.
|
// Do not let an older helper sample override a session notification.
|
||||||
ResetSessionHelperReportedStateLocked("session_event", 0);
|
ResetSessionHelperReportedStateLocked("session_event", 0);
|
||||||
last_session_transition_tick_ = GetTickCount64();
|
last_session_transition_tick_ = GetTickCount64();
|
||||||
@@ -2501,20 +2513,28 @@ std::string CrossDeskServiceHost::ResolveSecureInputTarget(
|
|||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(state_mutex_);
|
std::lock_guard<std::mutex> lock(state_mutex_);
|
||||||
target.session_id = active_session_id_;
|
target.session_id = active_session_id_;
|
||||||
|
if (target.session_id == 0xFFFFFFFF) {
|
||||||
|
return BuildErrorJson("no_active_console_session");
|
||||||
|
}
|
||||||
|
if (target.session_id != client_session_id ||
|
||||||
|
client_session_id != WTSGetActiveConsoleSessionId()) {
|
||||||
|
return BuildErrorJson("service_session_mismatch");
|
||||||
|
}
|
||||||
target.stage = ResolveInteractiveStageLocked();
|
target.stage = ResolveInteractiveStageLocked();
|
||||||
target.desktop = ResolveInteractiveDesktopLocked(target.stage);
|
target.desktop = ResolveInteractiveDesktopLocked(target.stage);
|
||||||
helper_running = secure_input_helper_running_ &&
|
helper_running = secure_input_helper_running_ &&
|
||||||
secure_input_helper_session_id_ == target.session_id;
|
secure_input_helper_session_id_ == target.session_id;
|
||||||
can_inject = GetEffectiveSessionLockedLocked() || HasSecureInputUiLocked();
|
can_inject = GetEffectiveSessionLockedLocked() || HasSecureInputUiLocked();
|
||||||
|
if (target.stage == "user-desktop") {
|
||||||
|
// Ordinary GUI input can be blocked by an elevated foreground window.
|
||||||
|
// Only a Windows-validated client in the active console session may
|
||||||
|
// start/retain this helper; do not misreport the desktop as UAC/locked.
|
||||||
|
user_desktop_input_until_tick_ =
|
||||||
|
GetTickCount64() + kUserDesktopInputIdleMs;
|
||||||
|
can_inject = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (target.session_id == 0xFFFFFFFF) {
|
|
||||||
return BuildErrorJson("no_active_console_session");
|
|
||||||
}
|
|
||||||
if (target.session_id != client_session_id ||
|
|
||||||
client_session_id != WTSGetActiveConsoleSessionId()) {
|
|
||||||
return BuildErrorJson("service_session_mismatch");
|
|
||||||
}
|
|
||||||
if (!can_inject) {
|
if (!can_inject) {
|
||||||
WakeSessionState();
|
WakeSessionState();
|
||||||
return BuildErrorJson("secure_input_not_active");
|
return BuildErrorJson("secure_input_not_active");
|
||||||
@@ -2799,8 +2819,14 @@ std::string QueryDesktopInput(const std::string& command, DWORD timeout_ms) {
|
|||||||
const std::string error = json.value("error", "");
|
const std::string error = json.value("error", "");
|
||||||
// Retry only explicit pre-injection rejections during helper startup.
|
// Retry only explicit pre-injection rejections during helper startup.
|
||||||
// Never replay an uncertain timeout or a partially injected mouse action.
|
// Never replay an uncertain timeout or a partially injected mouse action.
|
||||||
if (!IsDesktopInputSetupPending(error) || IsCurrentSessionUserDesktopActive())
|
// The SYSTEM helper may now be starting for an elevated application on
|
||||||
|
// Default too. Old services still reject that with secure_input_not_active;
|
||||||
|
// preserve their ordinary-desktop return instead of retrying indefinitely.
|
||||||
|
if (!IsDesktopInputSetupPending(error) ||
|
||||||
|
(error == "secure_input_not_active" &&
|
||||||
|
IsCurrentSessionUserDesktopActive())) {
|
||||||
return response;
|
return response;
|
||||||
|
}
|
||||||
const auto remaining = pipe_deadline_detail::Remaining(deadline);
|
const auto remaining = pipe_deadline_detail::Remaining(deadline);
|
||||||
if (remaining == 0) return response;
|
if (remaining == 0) return response;
|
||||||
Sleep((std::min)(DWORD{10}, remaining));
|
Sleep((std::min)(DWORD{10}, remaining));
|
||||||
|
|||||||
@@ -137,6 +137,9 @@ class CrossDeskServiceHost {
|
|||||||
ULONGLONG session_helper_report_state_age_ms_ = 0;
|
ULONGLONG session_helper_report_state_age_ms_ = 0;
|
||||||
ULONGLONG session_helper_report_uptime_ms_ = 0;
|
ULONGLONG session_helper_report_uptime_ms_ = 0;
|
||||||
ULONGLONG secure_input_helper_started_at_tick_ = 0;
|
ULONGLONG secure_input_helper_started_at_tick_ = 0;
|
||||||
|
// Short idle grace after a validated user-desktop input request. This keeps
|
||||||
|
// the SYSTEM helper available for elevated windows such as Task Manager.
|
||||||
|
ULONGLONG user_desktop_input_until_tick_ = 0;
|
||||||
ULONGLONG sas_secure_desktop_until_tick_ = 0;
|
ULONGLONG sas_secure_desktop_until_tick_ = 0;
|
||||||
bool session_locked_ = false;
|
bool session_locked_ = false;
|
||||||
bool logon_ui_visible_ = false;
|
bool logon_ui_visible_ = false;
|
||||||
|
|||||||
Reference in New Issue
Block a user