Fix launching apps when Sunshine is running as admin (#659)
This commit is contained in:
@@ -188,6 +188,61 @@ HANDLE duplicate_shell_token() {
|
|||||||
return new_token;
|
return new_token;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PTOKEN_USER get_token_user(HANDLE token) {
|
||||||
|
DWORD return_length;
|
||||||
|
if(GetTokenInformation(token, TokenUser, NULL, 0, &return_length) || GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
|
||||||
|
auto winerr = GetLastError();
|
||||||
|
BOOST_LOG(error) << "Failed to get token information size: "sv << winerr;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto user = (PTOKEN_USER)HeapAlloc(GetProcessHeap(), 0, return_length);
|
||||||
|
if(!user) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!GetTokenInformation(token, TokenUser, user, return_length, &return_length)) {
|
||||||
|
auto winerr = GetLastError();
|
||||||
|
BOOST_LOG(error) << "Failed to get token information: "sv << winerr;
|
||||||
|
HeapFree(GetProcessHeap(), 0, user);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
void free_token_user(PTOKEN_USER user) {
|
||||||
|
HeapFree(GetProcessHeap(), 0, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_token_same_user_as_process(HANDLE other_token) {
|
||||||
|
HANDLE process_token;
|
||||||
|
if(!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &process_token)) {
|
||||||
|
auto winerr = GetLastError();
|
||||||
|
BOOST_LOG(error) << "Failed to open process token: "sv << winerr;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto process_user = get_token_user(process_token);
|
||||||
|
CloseHandle(process_token);
|
||||||
|
if(!process_user) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto token_user = get_token_user(other_token);
|
||||||
|
if(!token_user) {
|
||||||
|
free_token_user(process_user);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ret = EqualSid(process_user->User.Sid, token_user->User.Sid);
|
||||||
|
|
||||||
|
free_token_user(process_user);
|
||||||
|
free_token_user(token_user);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
bool merge_user_environment_block(bp::environment &env, HANDLE shell_token) {
|
bool merge_user_environment_block(bp::environment &env, HANDLE shell_token) {
|
||||||
// Get the target user's environment block
|
// Get the target user's environment block
|
||||||
PVOID env_block;
|
PVOID env_block;
|
||||||
@@ -339,40 +394,57 @@ bp::child run_unprivileged(const std::string &cmd, boost::filesystem::path &work
|
|||||||
NULL);
|
NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Impersonate the user when launching the process. This will ensure that appropriate access
|
// If we're running with the same user account as the shell, just use CreateProcess().
|
||||||
// checks are done against the user token, not our SYSTEM token. It will also allow network
|
// This will launch the child process elevated if Sunshine is elevated.
|
||||||
// shares and mapped network drives to be used as launch targets, since those credentials
|
|
||||||
// are stored per-user.
|
|
||||||
if(!ImpersonateLoggedOnUser(shell_token)) {
|
|
||||||
auto winerror = GetLastError();
|
|
||||||
BOOST_LOG(error) << "Failed to impersonate user: "sv << winerror;
|
|
||||||
ec = std::make_error_code(std::errc::permission_denied);
|
|
||||||
return bp::child();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Launch the process with the duplicated shell token.
|
|
||||||
// Set CREATE_BREAKAWAY_FROM_JOB to avoid the child being killed if SunshineSvc.exe is terminated.
|
|
||||||
// Set CREATE_NEW_CONSOLE to avoid writing stdout to Sunshine's log if 'file' is not specified.
|
|
||||||
PROCESS_INFORMATION process_info;
|
PROCESS_INFORMATION process_info;
|
||||||
BOOL ret = CreateProcessAsUserW(shell_token,
|
BOOL ret;
|
||||||
NULL,
|
if(!is_token_same_user_as_process(shell_token)) {
|
||||||
(LPWSTR)wcmd.c_str(),
|
// Impersonate the user when launching the process. This will ensure that appropriate access
|
||||||
NULL,
|
// checks are done against the user token, not our SYSTEM token. It will also allow network
|
||||||
NULL,
|
// shares and mapped network drives to be used as launch targets, since those credentials
|
||||||
!!(startup_info.StartupInfo.dwFlags & STARTF_USESTDHANDLES),
|
// are stored per-user.
|
||||||
EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT | CREATE_NEW_CONSOLE | CREATE_BREAKAWAY_FROM_JOB,
|
if(!ImpersonateLoggedOnUser(shell_token)) {
|
||||||
env_block.data(),
|
auto winerror = GetLastError();
|
||||||
start_dir.empty() ? NULL : start_dir.c_str(),
|
BOOST_LOG(error) << "Failed to impersonate user: "sv << winerror;
|
||||||
(LPSTARTUPINFOW)&startup_info,
|
ec = std::make_error_code(std::errc::permission_denied);
|
||||||
&process_info);
|
return bp::child();
|
||||||
|
}
|
||||||
|
|
||||||
// End impersonation of the logged on user. If this fails (which is extremely unlikely),
|
// Launch the process with the duplicated shell token.
|
||||||
// we will be running with an unknown user token. The only safe thing to do in that case
|
// Set CREATE_BREAKAWAY_FROM_JOB to avoid the child being killed if SunshineSvc.exe is terminated.
|
||||||
// is terminate ourselves.
|
// Set CREATE_NEW_CONSOLE to avoid writing stdout to Sunshine's log if 'file' is not specified.
|
||||||
if(!RevertToSelf()) {
|
ret = CreateProcessAsUserW(shell_token,
|
||||||
auto winerror = GetLastError();
|
NULL,
|
||||||
BOOST_LOG(fatal) << "Failed to revert to self after impersonation: "sv << winerror;
|
(LPWSTR)wcmd.c_str(),
|
||||||
std::abort();
|
NULL,
|
||||||
|
NULL,
|
||||||
|
!!(startup_info.StartupInfo.dwFlags & STARTF_USESTDHANDLES),
|
||||||
|
EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT | CREATE_NEW_CONSOLE | CREATE_BREAKAWAY_FROM_JOB,
|
||||||
|
env_block.data(),
|
||||||
|
start_dir.empty() ? NULL : start_dir.c_str(),
|
||||||
|
(LPSTARTUPINFOW)&startup_info,
|
||||||
|
&process_info);
|
||||||
|
|
||||||
|
// End impersonation of the logged on user. If this fails (which is extremely unlikely),
|
||||||
|
// we will be running with an unknown user token. The only safe thing to do in that case
|
||||||
|
// is terminate ourselves.
|
||||||
|
if(!RevertToSelf()) {
|
||||||
|
auto winerror = GetLastError();
|
||||||
|
BOOST_LOG(fatal) << "Failed to revert to self after impersonation: "sv << winerror;
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ret = CreateProcessW(NULL,
|
||||||
|
(LPWSTR)wcmd.c_str(),
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
!!(startup_info.StartupInfo.dwFlags & STARTF_USESTDHANDLES),
|
||||||
|
EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT | CREATE_NEW_CONSOLE | CREATE_BREAKAWAY_FROM_JOB,
|
||||||
|
env_block.data(),
|
||||||
|
start_dir.empty() ? NULL : start_dir.c_str(),
|
||||||
|
(LPSTARTUPINFOW)&startup_info,
|
||||||
|
&process_info);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(ret) {
|
if(ret) {
|
||||||
|
|||||||
Reference in New Issue
Block a user