Do not run elevated by default (#884)

Make the runner not run as elevated by default. Add a setting for
"run PowerToys as elevated" and buttons to restart the process
with the different elevation levels.
This commit is contained in:
Bartosz Sosnowski
2019-12-16 18:36:52 +01:00
committed by GitHub
parent fd8fc679be
commit 619ed234a9
17 changed files with 351 additions and 81 deletions

View File

@@ -171,49 +171,49 @@ WindowState get_window_state(HWND hwnd) {
return RESTORED;
}
bool is_process_elevated() {
HANDLE token = nullptr;
bool elevated = false;
if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) {
TOKEN_ELEVATION elevation;
DWORD size;
if (GetTokenInformation(token, TokenElevation, &elevation, sizeof(elevation), &size)) {
elevated = (elevation.TokenIsElevated != 0);
}
}
if (token) {
CloseHandle(token);
}
return elevated;
}
bool drop_elevated_privileges() {
HANDLE token = nullptr;
LPCTSTR lpszPrivilege = SE_SECURITY_NAME;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_DEFAULT | WRITE_OWNER, &token)) {
return false;
}
PSID medium_sid = NULL;
if (!::ConvertStringSidToSid(SDDL_ML_MEDIUM, &medium_sid)) {
return false;
}
TOKEN_MANDATORY_LABEL label = { 0 };
label.Label.Attributes = SE_GROUP_INTEGRITY;
label.Label.Sid = medium_sid;
DWORD size = (DWORD)sizeof(TOKEN_MANDATORY_LABEL) + ::GetLengthSid(medium_sid);
BOOL result = SetTokenInformation(token, TokenIntegrityLevel, &label, size);
LocalFree(medium_sid);
CloseHandle(token);
return result;
}
bool is_process_elevated() {
HANDLE token = nullptr;
bool elevated = false;
if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) {
TOKEN_ELEVATION elevation;
DWORD size;
if (GetTokenInformation(token, TokenElevation, &elevation, sizeof(elevation), &size)) {
elevated = (elevation.TokenIsElevated != 0);
}
}
if (token) {
CloseHandle(token);
}
return elevated;
}
bool drop_elevated_privileges() {
HANDLE token = nullptr;
LPCTSTR lpszPrivilege = SE_SECURITY_NAME;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_DEFAULT | WRITE_OWNER, &token)) {
return false;
}
PSID medium_sid = NULL;
if (!::ConvertStringSidToSid(SDDL_ML_MEDIUM, &medium_sid)) {
return false;
}
TOKEN_MANDATORY_LABEL label = { 0 };
label.Label.Attributes = SE_GROUP_INTEGRITY;
label.Label.Sid = medium_sid;
DWORD size = (DWORD)sizeof(TOKEN_MANDATORY_LABEL) + ::GetLengthSid(medium_sid);
BOOL result = SetTokenInformation(token, TokenIntegrityLevel, &label, size);
LocalFree(medium_sid);
CloseHandle(token);
return result;
}
std::wstring get_process_path(DWORD pid) noexcept {
auto process = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, TRUE, pid);
std::wstring name;
@@ -225,12 +225,93 @@ std::wstring get_process_path(DWORD pid) noexcept {
}
name.resize(name_length);
CloseHandle(process);
}
}
return name;
}
std::wstring get_process_path(HWND window) noexcept {
const static std::wstring app_frame_host = L"ApplicationFrameHost.exe";
bool run_elevated(const std::wstring& file, const std::wstring& params) {
SHELLEXECUTEINFOW exec_info = { 0 };
exec_info.cbSize = sizeof(SHELLEXECUTEINFOW);
exec_info.lpVerb = L"runas";
exec_info.lpFile = file.c_str();
exec_info.lpParameters = params.c_str();
exec_info.hwnd = 0;
exec_info.fMask = SEE_MASK_NOCLOSEPROCESS;
exec_info.lpDirectory = 0;
exec_info.hInstApp = 0;
if (ShellExecuteExW(&exec_info)) {
return exec_info.hProcess != nullptr;
} else {
return false;
}
}
bool run_non_elevated(const std::wstring& file, const std::wstring& params) {
auto executable_args = file;
if (!params.empty()) {
executable_args += L" " + params;
}
HWND hwnd = GetShellWindow();
if (!hwnd) {
return false;
}
DWORD pid;
GetWindowThreadProcessId(hwnd, &pid);
winrt::handle process{ OpenProcess(PROCESS_CREATE_PROCESS, FALSE, pid) };
if (!process) {
return false;
}
SIZE_T size = 0;
InitializeProcThreadAttributeList(nullptr, 1, 0, &size);
auto pproc_buffer = std::make_unique<char[]>(size);
auto pptal = reinterpret_cast<PPROC_THREAD_ATTRIBUTE_LIST>(pproc_buffer.get());
if (!InitializeProcThreadAttributeList(pptal, 1, 0, &size)) {
return false;
}
HANDLE process_handle = process.get();
if (!pptal || !UpdateProcThreadAttribute(pptal,
0,
PROC_THREAD_ATTRIBUTE_PARENT_PROCESS,
&process_handle,
sizeof(process_handle),
nullptr,
nullptr)) {
return false;
}
STARTUPINFOEX siex = { 0 };
siex.lpAttributeList = pptal;
siex.StartupInfo.cb = sizeof(siex);
PROCESS_INFORMATION process_info = { 0 };
auto succedded = CreateProcessW(file.c_str(),
const_cast<LPWSTR>(executable_args.c_str()),
nullptr,
nullptr,
FALSE,
EXTENDED_STARTUPINFO_PRESENT,
nullptr,
nullptr,
&siex.StartupInfo,
&process_info);
if (process_info.hProcess) {
CloseHandle(process_info.hProcess);
}
if (process_info.hThread) {
CloseHandle(process_info.hThread);
}
return succedded;
}
std::wstring get_process_path(HWND window) noexcept {
const static std::wstring app_frame_host = L"ApplicationFrameHost.exe";
DWORD pid{};
GetWindowThreadProcessId(window, &pid);
auto name = get_process_path(pid);
@@ -254,19 +335,29 @@ std::wstring get_process_path(HWND window) noexcept {
if (new_pid != pid) {
return get_process_path(new_pid);
}
}
}
return name;
}
std::wstring get_product_version() {
static std::wstring version = std::to_wstring(VERSION_MAJOR) +
L"." + std::to_wstring(VERSION_MINOR) +
L"." + std::to_wstring(VERSION_REVISION) +
L"." + std::to_wstring(VERSION_BUILD);
return version;
}
std::wstring get_product_version() {
static std::wstring version = std::to_wstring(VERSION_MAJOR) +
L"." + std::to_wstring(VERSION_MINOR) +
L"." + std::to_wstring(VERSION_REVISION) +
L"." + std::to_wstring(VERSION_BUILD);
return version;
}
std::wstring get_resource_string(UINT resource_id, HINSTANCE instance, const wchar_t* fallback) {
wchar_t* text_ptr;
auto length = LoadStringW(instance, resource_id, reinterpret_cast<wchar_t*>(&text_ptr), 0);
if (length == 0) {
return fallback;
} else {
return { text_ptr, static_cast<std::size_t>(length) };
}
}
std::wstring get_module_filename(HMODULE mod)
{
wchar_t buffer[MAX_PATH + 1];

View File

@@ -1,5 +1,6 @@
#pragma once
#include <optional>
#include <string>
#include <Windows.h>
#include <string>
@@ -51,6 +52,12 @@ bool is_process_elevated();
// Drops the elevated privilages if present
bool drop_elevated_privileges();
// Run command as elevated user, returns true if succeeded
bool run_elevated(const std::wstring& file, const std::wstring& params);
// Run command as non-elevated user, returns true if succeeded
bool run_non_elevated(const std::wstring& file, const std::wstring& params);
// Get the executable path or module name for modern apps
std::wstring get_process_path(DWORD pid) noexcept;
// Get the executable path or module name for modern apps
@@ -60,3 +67,11 @@ std::wstring get_product_version();
std::wstring get_module_filename(HMODULE mod = nullptr);
std::wstring get_module_folderpath(HMODULE mod = nullptr);
// Get a string from the resource file
std::wstring get_resource_string(UINT resource_id, HINSTANCE instance, const wchar_t* fallback);
// Wrapper for getting a string from the resource file. Returns the resource id text when fails.
// Requires that
// extern "C" IMAGE_DOS_HEADER __ImageBase;
// is added to the .cpp file.
#define GET_RESOURCE_STRING(resource_id) get_resource_string(resource_id, reinterpret_cast<HINSTANCE>(&__ImageBase), L#resource_id)

View File

@@ -6,6 +6,7 @@
#include <common/windows_colors.h>
static std::wstring settings_theme = L"system";
static bool run_as_elevated = false;
json::JsonObject load_general_settings() {
auto loaded = PTSettingsHelper::load_general_settings();
@@ -13,6 +14,7 @@ json::JsonObject load_general_settings() {
if (settings_theme != L"dark" && settings_theme != L"light") {
settings_theme = L"system";
}
run_as_elevated = loaded.GetNamedBoolean(L"run_elevated", false);
return loaded;
}
@@ -27,6 +29,9 @@ json::JsonObject get_general_settings() {
}
result.SetNamedValue(L"enabled", std::move(enabled));
bool is_elevated = is_process_elevated();
result.SetNamedValue(L"is_elevated", json::value(is_elevated));
result.SetNamedValue(L"run_elevated", json::value(run_as_elevated));
result.SetNamedValue(L"theme", json::value(settings_theme));
result.SetNamedValue(L"system_theme", json::value(WindowsColors::is_dark_mode() ? L"dark" : L"light"));
result.SetNamedValue(L"powertoys_version", json::value(get_product_version()));
@@ -68,6 +73,7 @@ void apply_general_settings(const json::JsonObject& general_configs) {
}
}
}
run_as_elevated = general_configs.GetNamedBoolean(L"run_elevated", false);
if (json::has(general_configs, L"theme", json::JsonValueType::String)) {
settings_theme = general_configs.GetNamedString(L"theme");
}

View File

@@ -2,6 +2,7 @@
#include <common/json.h>
json::JsonObject load_general_settings();
json::JsonObject get_general_settings();
void apply_general_settings(const json::JsonObject & general_configs);
void start_initial_powertoys();

View File

@@ -7,6 +7,8 @@
#include "lowlevel_keyboard_event.h"
#include "trace.h"
#include "general_settings.h"
#include "restart_elevated.h"
#include "resource.h"
#include <common/dpi_aware.h>
@@ -14,6 +16,9 @@
#include "unhandled_exception_handler.h"
#endif
extern "C" IMAGE_DOS_HEADER __ImageBase;
void chdir_current_executable() {
// Change current directory to the path of the executable.
WCHAR executable_path[MAX_PATH];
@@ -24,16 +29,7 @@ void chdir_current_executable() {
}
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
WCHAR username[UNLEN + 1];
DWORD username_length = UNLEN + 1;
GetUserNameW(username, &username_length);
auto runner_mutex = CreateMutexW(NULL, TRUE, (std::wstring(L"Local\\PowerToyRunMutex") + username).c_str());
if (runner_mutex == NULL || GetLastError() == ERROR_ALREADY_EXISTS) {
// The app is already running
return 0;
}
int runner() {
DPIAware::EnableDPIAwarenessForThisProcess();
#if _DEBUG && _WIN64
@@ -46,12 +42,6 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
start_tray_icon();
int result;
try {
// Singletons initialization order needs to be preserved, first events and
// then modules to guarantee the reverse destruction order.
powertoys_events();
modules();
chdir_current_executable();
// Load Powertyos DLLS
// For now only load known DLLs
@@ -76,11 +66,59 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
Trace::EventLaunch(get_product_version());
result = run_message_loop();
} catch (std::runtime_error& err) {
} catch (std::runtime_error & err) {
std::string err_what = err.what();
MessageBoxW(NULL, std::wstring(err_what.begin(),err_what.end()).c_str(), L"Error", MB_OK | MB_ICONERROR);
MessageBoxW(NULL, std::wstring(err_what.begin(), err_what.end()).c_str(), L"Error", MB_OK | MB_ICONERROR);
result = -1;
}
Trace::UnregisterProvider();
return result;
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
WCHAR username[UNLEN + 1];
DWORD username_length = UNLEN + 1;
GetUserNameW(username, &username_length);
auto runner_mutex = CreateMutexW(NULL, TRUE, (std::wstring(L"Local\\PowerToyRunMutex") + username).c_str());
if (runner_mutex == NULL || GetLastError() == ERROR_ALREADY_EXISTS) {
// The app is already running
return 0;
}
int result = 0;
try {
// Singletons initialization order needs to be preserved, first events and
// then modules to guarantee the reverse destruction order.
SystemMenuHelperInstace();
powertoys_events();
modules();
auto general_settings = load_general_settings();
int rvalue = 0;
if (is_process_elevated() ||
general_settings.GetNamedBoolean(L"run_elevated", false) == false ||
strcmp(lpCmdLine, "--dont-elevate") == 0) {
result = runner();
}
else {
schedule_restart_as_elevated();
result = 0;
}
}
catch (std::runtime_error & err) {
std::string err_what = err.what();
MessageBoxW(NULL, std::wstring(err_what.begin(), err_what.end()).c_str(), GET_RESOURCE_STRING(IDS_ERROR).c_str(), MB_OK | MB_ICONERROR);
result = -1;
}
ReleaseMutex(runner_mutex);
CloseHandle(runner_mutex);
if (is_restart_scheduled()) {
if (restart_if_scheduled() == false) {
auto text = is_process_elevated() ? GET_RESOURCE_STRING(IDS_COULDNOT_RESTART_NONELEVATED) :
GET_RESOURCE_STRING(IDS_COULDNOT_RESTART_ELEVATED);
MessageBoxW(NULL, text.c_str(), GET_RESOURCE_STRING(IDS_ERROR).c_str(), MB_OK | MB_ICONERROR);
result = -1;
}
}
stop_tray_icon();
return result;
}

View File

@@ -1,5 +1,8 @@
#define APPICON 101
#define ID_TRAY_MENU 102
#define IDS_COULDNOT_RESTART_NONELEVATED 103
#define IDS_COULDNOT_RESTART_ELEVATED 104
#define IDS_ERROR 105
#define ID_EXIT_MENU_COMMAND 40001
#define ID_SETTINGS_MENU_COMMAND 40002
#define ID_ABOUT_MENU_COMMAND 40003

View File

@@ -0,0 +1,37 @@
#include "pch.h"
#include "restart_elevated.h"
#include "common/common.h"
enum State {
None,
RestartAsElevated,
RestartAsNonElevated
};
static State state = None;
void schedule_restart_as_elevated() {
state = RestartAsElevated;
}
void schedule_restart_as_non_elevated() {
state = RestartAsNonElevated;
}
bool is_restart_scheduled() {
return state != None;
}
bool restart_if_scheduled() {
// Make sure we have enough room, even for the long (\\?\) paths
constexpr DWORD exe_path_size = 0xFFFF;
auto exe_path = std::make_unique<wchar_t[]>(exe_path_size);
GetModuleFileNameW(nullptr, exe_path.get(), exe_path_size);
switch (state) {
case RestartAsElevated:
return run_elevated(exe_path.get(), {});
case RestartAsNonElevated:
return run_non_elevated(exe_path.get(), L"--dont-elevate");
default:
return false;
}
}

View File

@@ -0,0 +1,5 @@
#pragma once
void schedule_restart_as_elevated();
void schedule_restart_as_non_elevated();
bool is_restart_scheduled();
bool restart_if_scheduled();

Binary file not shown.

View File

@@ -63,7 +63,7 @@
<PreprocessorDefinitions>_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<UACExecutionLevel>HighestAvailable</UACExecutionLevel>
<UACExecutionLevel>AsInvoker</UACExecutionLevel>
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
</Link>
<Manifest>
@@ -88,7 +88,7 @@
<Link>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<UACExecutionLevel>HighestAvailable</UACExecutionLevel>
<UACExecutionLevel>AsInvoker</UACExecutionLevel>
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
</Link>
<Manifest>
@@ -106,6 +106,7 @@
<ClCompile Include="powertoys_events.cpp" />
<ClCompile Include="powertoy_module.cpp" />
<ClCompile Include="main.cpp" />
<ClCompile Include="restart_elevated.cpp" />
<ClCompile Include="settings_window.cpp" />
<ClCompile Include="system_menu_helper.cpp" />
<ClCompile Include="trace.cpp" />
@@ -121,6 +122,7 @@
<ClInclude Include="powertoys_events.h" />
<ClInclude Include="powertoy_module.h" />
<ClInclude Include="resource.h" />
<ClInclude Include="restart_elevated.h" />
<ClInclude Include="settings_window.h" />
<ClInclude Include="system_menu_helper.h" />
<ClInclude Include="trace.h" />

View File

@@ -36,6 +36,9 @@
<ClCompile Include="system_menu_helper.cpp">
<Filter>Utils</Filter>
</ClCompile>
<ClCompile Include="restart_elevated.cpp">
<Filter>Utils</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
@@ -73,6 +76,9 @@
<ClInclude Include="system_menu_helper.h">
<Filter>Utils</Filter>
</ClInclude>
<ClInclude Include="restart_elevated.h">
<Filter>Utils</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Filter Include="Utils">

View File

@@ -10,6 +10,8 @@
#include "tray_icon.h"
#include "general_settings.h"
#include "common/windows_colors.h"
#include "common/common.h"
#include "restart_elevated.h"
#include <common/json.h>
@@ -48,10 +50,25 @@ void dispatch_json_action_to_module(const json::JsonObject& powertoys_configs)
for (const auto& powertoy_element : powertoys_configs)
{
const std::wstring name{ powertoy_element.Key().c_str() };
if (modules().find(name) != modules().end())
// Currently, there is only one custom action in the general settings screen,
// so it has to be the "restart as (non-)elevated" button.
if (name == L"general")
{
if (is_process_elevated())
{
schedule_restart_as_non_elevated();
PostQuitMessage(0);
}
else
{
schedule_restart_as_elevated();
PostQuitMessage(0);
}
}
else if (modules().find(name) != modules().end())
{
const auto element = powertoy_element.Value().Stringify();
modules().at(name).call_custom_action(element.c_str());
modules().at(name).call_custom_action(element.c_str());
}
}
}

View File

@@ -54,7 +54,10 @@ LRESULT __stdcall tray_icon_window_proc(HWND window, UINT message, WPARAM wparam
}
break;
case WM_DESTROY:
Shell_NotifyIcon(NIM_DELETE, &tray_icon_data);
if (tray_icon_created) {
Shell_NotifyIcon(NIM_DELETE, &tray_icon_data);
tray_icon_created = false;
}
PostQuitMessage(0);
break;
case WM_CLOSE:
@@ -171,3 +174,9 @@ void start_tray_icon() {
tray_icon_created = Shell_NotifyIcon(NIM_ADD, &tray_icon_data) == TRUE;
}
}
void stop_tray_icon() {
if (tray_icon_created) {
SendMessage(tray_icon_hwnd, WM_CLOSE, 0, 0);
}
}

View File

@@ -1,6 +1,8 @@
#pragma once
// Start the Tray Icon
void start_tray_icon();
// Stop the Tray Icon
void stop_tray_icon();
// Open the Settings Window
void open_settings_window();
// Callback type to be called by the tray icon loop

View File

@@ -29,7 +29,11 @@ export class CustomActionSettingsControl extends BaseSettingsControl {
public render(): JSX.Element {
return (
<Stack>
<Label>{this.state.property_values.display_name}</Label>
{
this.state.property_values.display_name ?
<Label>{this.state.property_values.display_name}</Label>
: null
}
{
this.state.property_values.value ?
<Text styles ={{

View File

@@ -3,16 +3,21 @@ import { Stack, Text, PrimaryButton, Label, Link, loadTheme } from 'office-ui-fa
import { BoolToggleSettingsControl } from './BoolToggleSettingsControl'
import { ChoiceGroupSettingsControl } from './ChoiceGroupSettingsControl'
import { Separator } from 'office-ui-fabric-react/lib/Separator';
import { CustomActionSettingsControl } from './CustomActionSettingsControl';
export class GeneralSettings extends React.Component <any, any> {
references: any = {};
startup_reference: any;
elevated_reference: any;
restart_reference: any;
theme_reference: any;
parent_on_change: Function;
constructor(props: any) {
super(props);
this.references={};
this.startup_reference=null;
this.elevated_reference=null;
this.restart_reference=null;
this.parent_on_change = props.on_change;
this.state = {
settings_key: props.settings_key,
@@ -38,6 +43,7 @@ export class GeneralSettings extends React.Component <any, any> {
let result : any = {};
result[this.state.settings_key]= {
startup: this.startup_reference.get_value().value,
run_elevated: this.elevated_reference.get_value().value,
theme: this.theme_reference.get_value().value,
enabled: enabled
};
@@ -120,6 +126,34 @@ export class GeneralSettings extends React.Component <any, any> {
on_change={this.parent_on_change}
ref={(input) => {this.startup_reference=input;}}
/>
<BoolToggleSettingsControl
setting={{display_name: 'By default, run PowerToys with elevated privileges', value: this.state.settings.general.run_elevated}}
on_change={this.parent_on_change}
ref={(input) => {this.elevated_reference=input;}}
/>
<CustomActionSettingsControl
setting={{
display_name: '',
value: this.state.settings.general.is_elevated ?
'PowerToys is currently running with elevated privileges. You can restart it to run non-elevated only for this session, without changing the default setting.' :
'PowerToys is currently running without elevated privileges. You can restart it to run elevated only for this session, without changing the default setting.',
button_text: this.state.settings.general.is_elevated ?
'Restart without elevated privileges' :
'Restart with elevated privileges'
}}
action_name={'restart_elevation'}
action_callback={(action_name: any, value:any) => {
(window as any).output_from_webview(JSON.stringify({
action: {
general: {
action_name,
value
}
}
}));
}}
ref={(input) => {this.restart_reference=input;}}
/>
<ChoiceGroupSettingsControl
setting={{display_name: 'Chose Settings color',
value: this.state.settings.general.theme,

File diff suppressed because one or more lines are too long