Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -690,6 +690,7 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h
install(TARGETS dxgi-info RUNTIME DESTINATION "tools" COMPONENT dxgi)
install(TARGETS audio-info RUNTIME DESTINATION "tools" COMPONENT audio)
install(TARGETS sunshinesvc RUNTIME DESTINATION "tools" COMPONENT sunshinesvc)
install(TARGETS elevator RUNTIME DESTINATION "tools" COMPONENT elevator)

# Mandatory tools
install(TARGETS ddprobe RUNTIME DESTINATION "tools" COMPONENT application)
Expand Down Expand Up @@ -801,6 +802,12 @@ if(WIN32) # see options at: https://cmake.org/cmake/help/latest/cpack_gen/nsis.h
set(CPACK_COMPONENT_AUDIO_DESCRIPTION "CLI tool providing information about sound devices.")
set(CPACK_COMPONENT_AUDIO_GROUP "tools")

# elevation tool
set(CPACK_COMPONENT_ELEVATION_DISPLAY_NAME "elevator")
set(CPACK_COMPONENT_ELEVATION_DESCRIPTION "CLI tool that assists with elevating \
commands when permissions have been denied.")
set(CPACK_COMPONENT_ELEVATION_GROUP "tools")

# display tool
set(CPACK_COMPONENT_DXGI_DISPLAY_NAME "dxgi-info")
set(CPACK_COMPONENT_DXGI_DESCRIPTION "CLI tool providing information about graphics cards and displays.")
Expand Down
40 changes: 35 additions & 5 deletions src/platform/windows/misc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,22 @@

// prevent clang format from "optimizing" the header include order
// clang-format off
#include <winsock2.h>
#include <dwmapi.h>
#include <iphlpapi.h>
#include <iterator>
#include <timeapi.h>
#include <userenv.h>
#include <winsock2.h>
#include <windows.h>
#include <winuser.h>
#include <ws2tcpip.h>
#include <userenv.h>
#include <dwmapi.h>
#include <timeapi.h>
#include <wlanapi.h>
#include <ws2tcpip.h>
// clang-format on

#include "src/main.h"
#include "src/platform/common.h"
#include "src/utility.h"
#include <iterator>

// UDP_SEND_MSG_SIZE was added in the Windows 10 20H1 SDK
#ifndef UDP_SEND_MSG_SIZE
Expand Down Expand Up @@ -464,6 +466,34 @@ bp::child run_unprivileged(const std::string &cmd, boost::filesystem::path &work
(LPSTARTUPINFOW)&startup_info,
&process_info);

if(!ret) {
auto error = GetLastError();

if(error == 740) {
BOOST_LOG(info) << "Could not execute previous command because it required elevation. Running the command again with elevation, for security reasons this will prompt user interaction."sv;
startup_info.StartupInfo.wShowWindow = SW_HIDE;
startup_info.StartupInfo.dwFlags = startup_info.StartupInfo.dwFlags | STARTF_USESHOWWINDOW;
std::wstring elevated_command = L"tools\\elevator.exe ";
elevated_command += wcmd;

// For security reasons, Windows enforces that an application can have only one "interactive thread" responsible for processing user input and managing the user interface (UI).
// Since UAC prompts are interactive, we cannot have a UAC prompt while Sunshine is already running because it would block the thread.
// To work around this issue, we will launch a separate process that will elevate the command, which will prompt the user to confirm the elevation.
// This is our intended behavior: to require interaction before elevating the command.
ret = CreateProcessAsUserW(shell_token,
nullptr,
(LPWSTR)elevated_command.c_str(),
nullptr,
nullptr,
!!(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() ? nullptr : 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.
Expand Down
8 changes: 8 additions & 0 deletions tools/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ target_link_libraries(dxgi-info
${PLATFORM_LIBRARIES})
target_compile_options(dxgi-info PRIVATE ${SUNSHINE_COMPILE_OPTIONS})

add_executable(elevator elevator.cpp)
set_target_properties(elevator PROPERTIES CXX_STANDARD 17)
target_link_libraries(elevator
shell32
${PLATFORM_LIBRARIES})
target_compile_options(elevator PRIVATE ${SUNSHINE_COMPILE_OPTIONS})

add_executable(audio-info audio.cpp)
set_target_properties(audio-info PROPERTIES CXX_STANDARD 17)
target_link_libraries(audio-info
Expand All @@ -36,3 +43,4 @@ target_link_libraries(ddprobe
d3d11
${PLATFORM_LIBRARIES})
target_compile_options(ddprobe PRIVATE ${SUNSHINE_COMPILE_OPTIONS})

70 changes: 70 additions & 0 deletions tools/elevator.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#include <Windows.h>
#include <iostream>
#include <string>

/**
* @file elevator.cpp
* @brief A simple command line utility to run a given command with administrative privileges.
*
* This utility helps run a command with administrative privileges on Windows
* by leveraging the ShellExecuteExW function. The program accepts a command
* and optional arguments, then attempts to run the command with elevated
* privileges. If successful, it waits for the process to complete and
* returns the exit code of the launched process.
*
* @example
* To run the command prompt with administrative privileges, execute the following command:
* elevator.exe cmd
*
* To run a command, such as 'ipconfig /flushdns', with administrative privileges, execute:
* elevator.exe cmd /C "ipconfig /flushdns"
*/
int main(int argc, char *argv[]) {
// Check if the user provided at least one argument (the command to run)
if(argc < 2) {
std::cout << "Usage: " << argv[0] << " <command> [arguments]" << std::endl;
return 1;
}

// Convert the command and arguments from char* to wstring for use with ShellExecuteExW
std::wstring command = std::wstring(argv[1], argv[1] + strlen(argv[1]));
std::wstring arguments;

// Concatenate the remaining arguments (if any) into a single wstring
for(int i = 2; i < argc; ++i) {
arguments += std::wstring(argv[i], argv[i] + strlen(argv[i]));
if(i < argc - 1) {
arguments += L" ";
}
}

// Prepare the SHELLEXECUTEINFOW structure with the necessary information
SHELLEXECUTEINFOW info = { sizeof(SHELLEXECUTEINFOW) };
info.lpVerb = L"runas"; // Request elevation
info.lpFile = command.c_str();
info.lpParameters = arguments.empty() ? nullptr : arguments.c_str();
info.nShow = SW_SHOW;
info.fMask = SEE_MASK_NOCLOSEPROCESS; // So we can wait for the process to finish

// Attempt to execute the command with elevation
if(!ShellExecuteExW(&info)) {
std::cout << "Error: ShellExecuteExW failed with code " << GetLastError() << std::endl;
return 1;
}

// Wait for the launched process to finish
WaitForSingleObject(info.hProcess, INFINITE);

DWORD exitCode = 0;

// Retrieve the exit code of the launched process
if(!GetExitCodeProcess(info.hProcess, &exitCode)) {
std::cout << "Error: GetExitCodeProcess failed with code " << GetLastError() << std::endl;
}

// Close the process handle
CloseHandle(info.hProcess);

// Return the exit code of the launched process
return exitCode;
}