1 // Copyright 2024 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "base/win/elevation_util.h"
6
7 #include <objbase.h>
8
9 #include <windows.h>
10
11 #include <shlobj.h>
12 #include <wrl/client.h>
13
14 #include <string>
15
16 #include "base/command_line.h"
17 #include "base/files/file_path.h"
18 #include "base/logging.h"
19 #include "base/process/launch.h"
20 #include "base/process/process.h"
21 #include "base/process/process_handle.h"
22 #include "base/process/process_info.h"
23 #include "base/win/access_token.h"
24 #include "base/win/scoped_bstr.h"
25 #include "base/win/scoped_process_information.h"
26 #include "base/win/scoped_variant.h"
27 #include "base/win/startup_information.h"
28 #include "third_party/abseil-cpp/absl/cleanup/cleanup.h"
29
30 namespace base::win {
31
GetExplorerPid()32 ProcessId GetExplorerPid() {
33 const HWND hwnd = ::GetShellWindow();
34 ProcessId pid = 0;
35 return hwnd && ::GetWindowThreadProcessId(hwnd, &pid) ? pid : kNullProcessId;
36 }
37
IsProcessRunningAtMediumOrLower(ProcessId process_id)38 bool IsProcessRunningAtMediumOrLower(ProcessId process_id) {
39 IntegrityLevel level = GetProcessIntegrityLevel(process_id);
40 return level != INTEGRITY_UNKNOWN && level <= MEDIUM_INTEGRITY;
41 }
42
43 // Based on
44 // https://learn.microsoft.com/en-us/archive/blogs/aaron_margosis/faq-how-do-i-start-a-program-as-the-desktop-user-from-an-elevated-app.
RunDeElevated(const CommandLine & command_line)45 Process RunDeElevated(const CommandLine& command_line) {
46 if (!::IsUserAnAdmin()) {
47 return LaunchProcess(command_line, {});
48 }
49
50 ProcessId explorer_pid = GetExplorerPid();
51 if (!explorer_pid || !IsProcessRunningAtMediumOrLower(explorer_pid)) {
52 return Process();
53 }
54
55 auto shell_process =
56 Process::OpenWithAccess(explorer_pid, PROCESS_QUERY_LIMITED_INFORMATION);
57 if (!shell_process.IsValid()) {
58 return Process();
59 }
60
61 auto token = AccessToken::FromProcess(
62 ::GetCurrentProcess(), /*impersonation=*/false, MAXIMUM_ALLOWED);
63 if (!token) {
64 return Process();
65 }
66 auto previous_impersonate = token->SetPrivilege(SE_IMPERSONATE_NAME, true);
67 if (!previous_impersonate) {
68 return Process();
69 }
70 absl::Cleanup restore_previous_privileges = [&] {
71 token->SetPrivilege(SE_IMPERSONATE_NAME, *previous_impersonate);
72 };
73
74 auto shell_token = AccessToken::FromProcess(
75 shell_process.Handle(), /*impersonation=*/false, TOKEN_DUPLICATE);
76 if (!shell_token) {
77 return Process();
78 }
79
80 auto duplicated_shell_token = shell_token->DuplicatePrimary(
81 TOKEN_QUERY | TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE |
82 TOKEN_ADJUST_DEFAULT | TOKEN_ADJUST_SESSIONID);
83 if (!duplicated_shell_token) {
84 return Process();
85 }
86
87 StartupInformation startupinfo;
88 PROCESS_INFORMATION pi = {};
89 if (!::CreateProcessWithTokenW(duplicated_shell_token->get(), 0,
90 command_line.GetProgram().value().c_str(),
91 command_line.GetCommandLineString().data(), 0,
92 nullptr, nullptr, startupinfo.startup_info(),
93 &pi)) {
94 return Process();
95 }
96 ScopedProcessInformation process_info(pi);
97 Process process(process_info.TakeProcessHandle());
98 const DWORD pid = process.Pid();
99 VLOG(1) << __func__ << ": Started process, PID: " << pid;
100
101 // Allow the spawned process to show windows in the foreground.
102 if (!::AllowSetForegroundWindow(pid)) {
103 VPLOG(1) << __func__ << ": ::AllowSetForegroundWindow failed";
104 }
105
106 return process;
107 }
108
RunDeElevatedNoWait(const CommandLine & command_line)109 HRESULT RunDeElevatedNoWait(const CommandLine& command_line) {
110 return RunDeElevatedNoWait(command_line.GetProgram().value(),
111 command_line.GetArgumentsString());
112 }
113
RunDeElevatedNoWait(const std::wstring & path,const std::wstring & parameters)114 HRESULT RunDeElevatedNoWait(const std::wstring& path,
115 const std::wstring& parameters) {
116 Microsoft::WRL::ComPtr<IShellWindows> shell;
117 HRESULT hr = ::CoCreateInstance(CLSID_ShellWindows, nullptr,
118 CLSCTX_LOCAL_SERVER, IID_PPV_ARGS(&shell));
119 if (FAILED(hr)) {
120 return hr;
121 }
122
123 LONG hwnd = 0;
124 Microsoft::WRL::ComPtr<IDispatch> dispatch;
125 hr = shell->FindWindowSW(ScopedVariant(CSIDL_DESKTOP).AsInput(),
126 ScopedVariant().AsInput(), SWC_DESKTOP, &hwnd,
127 SWFO_NEEDDISPATCH, &dispatch);
128 if (hr == S_FALSE || FAILED(hr)) {
129 return hr == S_FALSE ? E_FAIL : hr;
130 }
131
132 Microsoft::WRL::ComPtr<IServiceProvider> service;
133 hr = dispatch.As(&service);
134 if (FAILED(hr)) {
135 return hr;
136 }
137
138 Microsoft::WRL::ComPtr<IShellBrowser> browser;
139 hr = service->QueryService(SID_STopLevelBrowser, IID_PPV_ARGS(&browser));
140 if (FAILED(hr)) {
141 return hr;
142 }
143
144 Microsoft::WRL::ComPtr<IShellView> view;
145 hr = browser->QueryActiveShellView(&view);
146 if (FAILED(hr)) {
147 return hr;
148 }
149
150 hr = view->GetItemObject(SVGIO_BACKGROUND, IID_PPV_ARGS(&dispatch));
151 if (FAILED(hr)) {
152 return hr;
153 }
154
155 Microsoft::WRL::ComPtr<IShellFolderViewDual> folder;
156 hr = dispatch.As(&folder);
157 if (FAILED(hr)) {
158 return hr;
159 }
160
161 hr = folder->get_Application(&dispatch);
162 if (FAILED(hr)) {
163 return hr;
164 }
165
166 Microsoft::WRL::ComPtr<IShellDispatch2> shell_dispatch;
167 hr = dispatch.As(&shell_dispatch);
168 if (FAILED(hr)) {
169 return hr;
170 }
171
172 return shell_dispatch->ShellExecute(
173 ScopedBstr(path.c_str()).Get(), ScopedVariant(parameters.c_str()),
174 ScopedVariant::kEmptyVariant, ScopedVariant::kEmptyVariant,
175 ScopedVariant::kEmptyVariant);
176 }
177
178 } // namespace base::win
179