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