1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
|
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// This file implements the Windows service controlling Me2Me host processes
// running within user sessions.
#include "remoting/host/win/unprivileged_process_delegate.h"
#include <sddl.h>
#include <utility>
#include "base/command_line.h"
#include "base/files/file.h"
#include "base/logging.h"
#include "base/rand_util.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/string16.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/synchronization/lock.h"
#include "base/win/scoped_handle.h"
#include "base/win/windows_version.h"
#include "ipc/attachment_broker.h"
#include "ipc/ipc_channel.h"
#include "ipc/ipc_channel_proxy.h"
#include "ipc/ipc_message.h"
#include "remoting/base/typed_buffer.h"
#include "remoting/host/ipc_util.h"
#include "remoting/host/switches.h"
#include "remoting/host/win/launch_process_with_token.h"
#include "remoting/host/win/security_descriptor.h"
#include "remoting/host/win/window_station_and_desktop.h"
#include "sandbox/win/src/restricted_token.h"
using base::win::ScopedHandle;
namespace remoting {
namespace {
// The security descriptors below are used to lock down access to the worker
// process launched by UnprivilegedProcessDelegate. UnprivilegedProcessDelegate
// assumes that it runs under SYSTEM. The worker process is launched under
// a different account and attaches to a newly created window station. If UAC is
// supported by the OS, the worker process is started at low integrity level.
// UnprivilegedProcessDelegate replaces the first printf parameter in
// the strings below by the logon SID assigned to the worker process.
// Security descriptor of the desktop the worker process attaches to. It gives
// SYSTEM and the logon SID full access to the desktop.
const char kDesktopSdFormat[] = "O:SYG:SYD:(A;;0xf01ff;;;SY)(A;;0xf01ff;;;%s)";
// Mandatory label specifying low integrity level.
const char kLowIntegrityMandatoryLabel[] = "S:(ML;CIOI;NW;;;LW)";
// Security descriptor of the window station the worker process attaches to. It
// gives SYSTEM and the logon SID full access the window station. The child
// containers and objects inherit ACE giving SYSTEM and the logon SID full
// access to them as well.
const char kWindowStationSdFormat[] = "O:SYG:SYD:(A;CIOIIO;GA;;;SY)"
"(A;CIOIIO;GA;;;%s)(A;NP;0xf037f;;;SY)(A;NP;0xf037f;;;%s)";
// Security descriptor of the worker process. It gives access SYSTEM full access
// to the process. It gives READ_CONTROL, SYNCHRONIZE, PROCESS_QUERY_INFORMATION
// and PROCESS_TERMINATE rights to the built-in administrators group.
const char kWorkerProcessSd[] = "O:SYG:SYD:(A;;GA;;;SY)(A;;0x120401;;;BA)";
// Security descriptor of the worker process threads. It gives access SYSTEM
// full access to the threads. It gives READ_CONTROL, SYNCHRONIZE,
// THREAD_QUERY_INFORMATION and THREAD_TERMINATE rights to the built-in
// administrators group.
const char kWorkerThreadSd[] = "O:SYG:SYD:(A;;GA;;;SY)(A;;0x120801;;;BA)";
// Creates a token with limited access that will be used to run the worker
// process.
bool CreateRestrictedToken(ScopedHandle* token_out) {
// Create a token representing LocalService account.
HANDLE temp_handle;
if (!LogonUser(L"LocalService", L"NT AUTHORITY", nullptr,
LOGON32_LOGON_SERVICE, LOGON32_PROVIDER_DEFAULT,
&temp_handle)) {
return false;
}
ScopedHandle token(temp_handle);
sandbox::RestrictedToken restricted_token;
if (restricted_token.Init(token.Get()) != ERROR_SUCCESS)
return false;
if (base::win::GetVersion() >= base::win::VERSION_VISTA) {
// "SeChangeNotifyPrivilege" is needed to access the machine certificate
// (including its private key) in the "Local Machine" cert store. This is
// needed for HTTPS client third-party authentication . But the presence of
// "SeChangeNotifyPrivilege" also allows it to open and manipulate objects
// owned by the same user. This risk is only mitigated by setting the
// process integrity level to Low, which is why it is unsafe to enable
// "SeChangeNotifyPrivilege" on Windows XP where we don't have process
// integrity to protect us.
std::vector<base::string16> exceptions;
exceptions.push_back(base::string16(L"SeChangeNotifyPrivilege"));
// Remove privileges in the token.
if (restricted_token.DeleteAllPrivileges(&exceptions) != ERROR_SUCCESS)
return false;
// Set low integrity level if supported by the OS.
if (restricted_token.SetIntegrityLevel(sandbox::INTEGRITY_LEVEL_LOW)
!= ERROR_SUCCESS) {
return false;
}
} else {
// Remove all privileges in the token.
// Since "SeChangeNotifyPrivilege" is among the privileges being removed,
// the network process won't be able to acquire certificates from the local
// machine store. This means third-party authentication won't work.
if (restricted_token.DeleteAllPrivileges(nullptr) != ERROR_SUCCESS)
return false;
}
// Return the resulting token.
DWORD result = restricted_token.GetRestrictedToken(token_out);
if (result != ERROR_SUCCESS) {
LOG(ERROR) << "Failed to get the restricted token: " << result;
return false;
}
return true;
}
// Creates a window station with a given name and the default desktop giving
// the complete access to |logon_sid|.
bool CreateWindowStationAndDesktop(ScopedSid logon_sid,
WindowStationAndDesktop* handles_out) {
// Convert the logon SID into a string.
std::string logon_sid_string = ConvertSidToString(logon_sid.get());
if (logon_sid_string.empty()) {
PLOG(ERROR) << "Failed to convert a SID to string";
return false;
}
// Format the security descriptors in SDDL form.
std::string desktop_sddl =
base::StringPrintf(kDesktopSdFormat, logon_sid_string.c_str());
std::string window_station_sddl =
base::StringPrintf(kWindowStationSdFormat, logon_sid_string.c_str(),
logon_sid_string.c_str());
// The worker runs at low integrity level. Make sure it will be able to attach
// to the window station and desktop.
if (base::win::GetVersion() >= base::win::VERSION_VISTA) {
desktop_sddl += kLowIntegrityMandatoryLabel;
window_station_sddl += kLowIntegrityMandatoryLabel;
}
// Create the desktop and window station security descriptors.
ScopedSd desktop_sd = ConvertSddlToSd(desktop_sddl);
ScopedSd window_station_sd = ConvertSddlToSd(window_station_sddl);
if (!desktop_sd || !window_station_sd) {
PLOG(ERROR) << "Failed to create a security descriptor.";
return false;
}
// GetProcessWindowStation() returns the current handle which does not need to
// be freed.
HWINSTA current_window_station = GetProcessWindowStation();
// Generate a unique window station name.
std::string window_station_name = base::StringPrintf(
"chromoting-%d-%d",
base::GetCurrentProcId(),
base::RandInt(1, std::numeric_limits<int>::max()));
// Make sure that a new window station will be created instead of opening
// an existing one.
DWORD window_station_flags = 0;
if (base::win::GetVersion() >= base::win::VERSION_VISTA)
window_station_flags = CWF_CREATE_ONLY;
// Request full access because this handle will be inherited by the worker
// process which needs full access in order to attach to the window station.
DWORD desired_access =
WINSTA_ALL_ACCESS | DELETE | READ_CONTROL | WRITE_DAC | WRITE_OWNER;
SECURITY_ATTRIBUTES security_attributes = {0};
security_attributes.nLength = sizeof(security_attributes);
security_attributes.lpSecurityDescriptor = window_station_sd.get();
security_attributes.bInheritHandle = TRUE;
WindowStationAndDesktop handles;
handles.SetWindowStation(CreateWindowStation(
base::UTF8ToUTF16(window_station_name).c_str(), window_station_flags,
desired_access, &security_attributes));
if (!handles.window_station()) {
PLOG(ERROR) << "CreateWindowStation() failed";
return false;
}
// Switch to the new window station and create a desktop on it.
if (!SetProcessWindowStation(handles.window_station())) {
PLOG(ERROR) << "SetProcessWindowStation() failed";
return false;
}
desired_access = DESKTOP_READOBJECTS | DESKTOP_CREATEWINDOW |
DESKTOP_CREATEMENU | DESKTOP_HOOKCONTROL | DESKTOP_JOURNALRECORD |
DESKTOP_JOURNALPLAYBACK | DESKTOP_ENUMERATE | DESKTOP_WRITEOBJECTS |
DESKTOP_SWITCHDESKTOP | DELETE | READ_CONTROL | WRITE_DAC | WRITE_OWNER;
security_attributes.nLength = sizeof(security_attributes);
security_attributes.lpSecurityDescriptor = desktop_sd.get();
security_attributes.bInheritHandle = TRUE;
// The default desktop of the interactive window station is called "Default".
// Name the created desktop the same way in case any code relies on that.
// The desktop name should not make any difference though.
handles.SetDesktop(CreateDesktop(L"Default", nullptr, nullptr, 0,
desired_access, &security_attributes));
// Switch back to the original window station.
if (!SetProcessWindowStation(current_window_station)) {
PLOG(ERROR) << "SetProcessWindowStation() failed";
return false;
}
if (!handles.desktop()) {
PLOG(ERROR) << "CreateDesktop() failed";
return false;
}
handles.Swap(*handles_out);
return true;
}
} // namespace
UnprivilegedProcessDelegate::UnprivilegedProcessDelegate(
scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,
scoped_ptr<base::CommandLine> target_command)
: io_task_runner_(io_task_runner),
target_command_(std::move(target_command)),
event_handler_(nullptr) {}
UnprivilegedProcessDelegate::~UnprivilegedProcessDelegate() {
DCHECK(CalledOnValidThread());
DCHECK(!channel_);
DCHECK(!worker_process_.IsValid());
}
void UnprivilegedProcessDelegate::LaunchProcess(
WorkerProcessLauncher* event_handler) {
DCHECK(CalledOnValidThread());
DCHECK(!event_handler_);
event_handler_ = event_handler;
scoped_ptr<IPC::ChannelProxy> server;
// Create a restricted token that will be used to run the worker process.
ScopedHandle token;
if (!CreateRestrictedToken(&token)) {
PLOG(ERROR) << "Failed to create a restricted LocalService token";
ReportFatalError();
return;
}
// Determine our logon SID, so we can grant it access to our window station
// and desktop.
ScopedSid logon_sid = GetLogonSid(token.Get());
if (!logon_sid) {
PLOG(ERROR) << "Failed to retrieve the logon SID";
ReportFatalError();
return;
}
// Create the process and thread security descriptors.
ScopedSd process_sd = ConvertSddlToSd(kWorkerProcessSd);
ScopedSd thread_sd = ConvertSddlToSd(kWorkerThreadSd);
if (!process_sd || !thread_sd) {
PLOG(ERROR) << "Failed to create a security descriptor";
ReportFatalError();
return;
}
SECURITY_ATTRIBUTES process_attributes;
process_attributes.nLength = sizeof(process_attributes);
process_attributes.lpSecurityDescriptor = process_sd.get();
process_attributes.bInheritHandle = FALSE;
SECURITY_ATTRIBUTES thread_attributes;
thread_attributes.nLength = sizeof(thread_attributes);
thread_attributes.lpSecurityDescriptor = thread_sd.get();
thread_attributes.bInheritHandle = FALSE;
ScopedHandle worker_process;
{
// Take a lock why any inheritable handles are open to make sure that only
// one process inherits them.
base::AutoLock lock(g_inherit_handles_lock.Get());
// Create a connected IPC channel.
base::File client;
if (!CreateConnectedIpcChannel(io_task_runner_, this, &client, &server)) {
ReportFatalError();
return;
}
// Convert the handle value into a decimal integer. Handle values are 32bit
// even on 64bit platforms.
std::string pipe_handle = base::StringPrintf(
"%d", reinterpret_cast<ULONG_PTR>(client.GetPlatformFile()));
// Pass the IPC channel via the command line.
base::CommandLine command_line(target_command_->argv());
command_line.AppendSwitchASCII(kDaemonPipeSwitchName, pipe_handle);
// Create our own window station and desktop accessible by |logon_sid|.
WindowStationAndDesktop handles;
if (!CreateWindowStationAndDesktop(std::move(logon_sid), &handles)) {
PLOG(ERROR) << "Failed to create a window station and desktop";
ReportFatalError();
return;
}
// Try to launch the worker process. The launched process inherits
// the window station, desktop and pipe handles, created above.
ScopedHandle worker_thread;
if (!LaunchProcessWithToken(
command_line.GetProgram(), command_line.GetCommandLineString(),
token.Get(), &process_attributes, &thread_attributes, true, 0,
nullptr, &worker_process, &worker_thread)) {
ReportFatalError();
return;
}
}
channel_ = std::move(server);
IPC::AttachmentBroker::GetGlobal()->RegisterCommunicationChannel(
channel_.get());
ReportProcessLaunched(std::move(worker_process));
}
void UnprivilegedProcessDelegate::Send(IPC::Message* message) {
DCHECK(CalledOnValidThread());
if (channel_) {
channel_->Send(message);
} else {
delete message;
}
}
void UnprivilegedProcessDelegate::CloseChannel() {
DCHECK(CalledOnValidThread());
if (!channel_)
return;
IPC::AttachmentBroker::GetGlobal()->DeregisterCommunicationChannel(
channel_.get());
channel_.reset();
}
void UnprivilegedProcessDelegate::KillProcess() {
DCHECK(CalledOnValidThread());
CloseChannel();
event_handler_ = nullptr;
if (worker_process_.IsValid()) {
TerminateProcess(worker_process_.Get(), CONTROL_C_EXIT);
worker_process_.Close();
}
}
bool UnprivilegedProcessDelegate::OnMessageReceived(
const IPC::Message& message) {
DCHECK(CalledOnValidThread());
return event_handler_->OnMessageReceived(message);
}
void UnprivilegedProcessDelegate::OnChannelConnected(int32_t peer_pid) {
DCHECK(CalledOnValidThread());
DWORD pid = GetProcessId(worker_process_.Get());
if (pid != static_cast<DWORD>(peer_pid)) {
LOG(ERROR) << "The actual client PID " << pid
<< " does not match the one reported by the client: "
<< peer_pid;
ReportFatalError();
return;
}
event_handler_->OnChannelConnected(peer_pid);
}
void UnprivilegedProcessDelegate::OnChannelError() {
DCHECK(CalledOnValidThread());
event_handler_->OnChannelError();
}
void UnprivilegedProcessDelegate::ReportFatalError() {
DCHECK(CalledOnValidThread());
CloseChannel();
WorkerProcessLauncher* event_handler = event_handler_;
event_handler_ = nullptr;
event_handler->OnFatalError();
}
void UnprivilegedProcessDelegate::ReportProcessLaunched(
base::win::ScopedHandle worker_process) {
DCHECK(CalledOnValidThread());
DCHECK(!worker_process_.IsValid());
worker_process_ = std::move(worker_process);
// Report a handle that can be used to wait for the worker process completion,
// query information about the process and duplicate handles.
DWORD desired_access =
SYNCHRONIZE | PROCESS_DUP_HANDLE | PROCESS_QUERY_INFORMATION;
HANDLE temp_handle;
if (!DuplicateHandle(GetCurrentProcess(), worker_process_.Get(),
GetCurrentProcess(), &temp_handle, desired_access, FALSE,
0)) {
PLOG(ERROR) << "Failed to duplicate a handle";
ReportFatalError();
return;
}
ScopedHandle limited_handle(temp_handle);
event_handler_->OnProcessLaunched(std::move(limited_handle));
}
} // namespace remoting
|