diff options
Diffstat (limited to 'remoting/host/win/unprivileged_process_delegate.cc')
-rw-r--r-- | remoting/host/win/unprivileged_process_delegate.cc | 308 |
1 files changed, 270 insertions, 38 deletions
diff --git a/remoting/host/win/unprivileged_process_delegate.cc b/remoting/host/win/unprivileged_process_delegate.cc index a129d62..6c2b740 100644 --- a/remoting/host/win/unprivileged_process_delegate.cc +++ b/remoting/host/win/unprivileged_process_delegate.cc @@ -1,3 +1,4 @@ + // 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. @@ -7,38 +8,216 @@ #include "remoting/host/win/unprivileged_process_delegate.h" +#include <sddl.h> + #include "base/base_switches.h" #include "base/command_line.h" #include "base/logging.h" +#include "base/process_util.h" +#include "base/rand_util.h" #include "base/single_thread_task_runner.h" +#include "base/string16.h" #include "base/stringprintf.h" +#include "base/synchronization/lock.h" #include "base/utf_string_conversions.h" #include "base/win/scoped_handle.h" +#include "base/win/windows_version.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/host_exit_codes.h" #include "remoting/host/ipc_constants.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 { +namespace remoting { -// The security descriptor used to protect the named pipe in between -// CreateNamedPipe() and CreateFile() calls before it will be passed to -// the network process. It gives full access to LocalSystem and denies access by -// anyone else. -const char kDaemonIpcSecurityDescriptor[] = "O:SYG:SYD:(A;;GA;;;SY)"; +namespace { // The command line parameters that should be copied from the service's command // line to the host process. const char* kCopiedSwitchNames[] = { "host-config", switches::kV, switches::kVModule }; -} // 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. -namespace remoting { +// Security descriptor used to protect the named pipe in between +// CreateNamedPipe() and CreateFile() calls before it is passed to the network +// process. It gives full access to LocalSystem and denies access by anyone +// else. +const char kDaemonIpcSd[] = "O:SYG:SYD:(A;;GA;;;SY)"; + +// 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;;;%1$s)(A;NP;0xf037f;;;SY)(A;NP;0xf037f;;;%1$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. + ScopedHandle token; + if (!LogonUser(L"LocalService", L"NT AUTHORITY", NULL, LOGON32_LOGON_SERVICE, + LOGON32_PROVIDER_DEFAULT, token.Receive())) { + return false; + } + + sandbox::RestrictedToken restricted_token; + if (restricted_token.Init(token) != ERROR_SUCCESS) + return false; + + // Remove all privileges in the token. + if (restricted_token.DeleteAllPrivileges(NULL) != ERROR_SUCCESS) + return false; + + // Set low integrity level if supported by the OS. + if (base::win::GetVersion() >= base::win::VERSION_VISTA) { + if (restricted_token.SetIntegrityLevel(sandbox::INTEGRITY_LEVEL_LOW) + != ERROR_SUCCESS) { + return false; + } + } + + // Return the resulting token. + return restricted_token.GetRestrictedTokenHandle(token_out->Receive()) == + ERROR_SUCCESS; +} + +// 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()) { + LOG_GETLASTERROR(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()); + + // 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) { + LOG_GETLASTERROR(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( + UTF8ToUTF16(window_station_name).c_str(), window_station_flags, + desired_access, &security_attributes)); + if (!handles.window_station()) { + LOG_GETLASTERROR(ERROR) << "CreateWindowStation() failed"; + return false; + } + + // Switch to the new window station and create a desktop on it. + if (!SetProcessWindowStation(handles.window_station())) { + LOG_GETLASTERROR(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", NULL, NULL, 0, desired_access, + &security_attributes)); + + // Switch back to the original window station. + if (!SetProcessWindowStation(current_window_station)) { + LOG_GETLASTERROR(ERROR) << "SetProcessWindowStation() failed"; + return false; + } + + if (!handles.desktop()) { + LOG_GETLASTERROR(ERROR) << "CreateDesktop() failed"; + return false; + } + + handles.Swap(*handles_out); + return true; +} + +} // namespace UnprivilegedProcessDelegate::UnprivilegedProcessDelegate( scoped_refptr<base::SingleThreadTaskRunner> main_task_runner, @@ -98,45 +277,98 @@ bool UnprivilegedProcessDelegate::LaunchProcess( IPC::Listener* delegate, ScopedHandle* process_exit_event_out) { DCHECK(main_task_runner_->BelongsToCurrentThread()); + + scoped_ptr<IPC::ChannelProxy> server; + // Generate a unique name for the channel. std::string channel_name = IPC::Channel::GenerateUniqueRandomChannelID(); - // Create a connected IPC channel. - ScopedHandle client; - scoped_ptr<IPC::ChannelProxy> server; - if (!CreateConnectedIpcChannel(channel_name, kDaemonIpcSecurityDescriptor, - io_task_runner_, delegate, &client, &server)) { + // Create a restricted token that will be used to run the worker process. + ScopedHandle token; + if (!CreateRestrictedToken(&token)) { + LOG_GETLASTERROR(ERROR) + << "Failed to create a restricted LocalService token"; + return false; + } + + // Determine our logon SID, so we can grant it access to our window station + // and desktop. + ScopedSid logon_sid = GetLogonSid(token); + if (!logon_sid) { + LOG_GETLASTERROR(ERROR) << "Failed to retrieve the logon SID"; return false; } - // 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.Get())); - - // Create the command line passing the name of the IPC channel to use and - // copying known switches from the caller's command line. - CommandLine command_line(binary_path_); - command_line.AppendSwitchASCII(kDaemonPipeSwitchName, pipe_handle); - command_line.CopySwitchesFrom(*CommandLine::ForCurrentProcess(), - kCopiedSwitchNames, - arraysize(kCopiedSwitchNames)); - - // Try to launch the process. - // TODO(alexeypa): Pass a restricted process token. - // See http://crbug.com/134694. - ScopedHandle worker_thread; - worker_process_.Close(); - if (!LaunchProcessWithToken(command_line.GetProgram(), - command_line.GetCommandLineString(), - NULL, - true, - 0, - &worker_process_, - &worker_thread)) { + // Create the process and thread security descriptors. + ScopedSd process_sd = ConvertSddlToSd(kWorkerProcessSd); + ScopedSd thread_sd = ConvertSddlToSd(kWorkerThreadSd); + if (!process_sd || !thread_sd) { + LOG_GETLASTERROR(ERROR) << "Failed to create a security descriptor"; return false; } + 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; + + { + // 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. + ScopedHandle client; + if (!CreateConnectedIpcChannel(channel_name, kDaemonIpcSd, io_task_runner_, + delegate, &client, &server)) { + return false; + } + + // 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.Get())); + + // Create the command line passing the name of the IPC channel to use and + // copying known switches from the caller's command line. + CommandLine command_line(binary_path_); + command_line.AppendSwitchASCII(kDaemonPipeSwitchName, pipe_handle); + command_line.CopySwitchesFrom(*CommandLine::ForCurrentProcess(), + kCopiedSwitchNames, + arraysize(kCopiedSwitchNames)); + + + // Create our own window station and desktop accessible by |logon_sid|. + WindowStationAndDesktop handles; + if (!CreateWindowStationAndDesktop(logon_sid.Pass(), &handles)) { + LOG_GETLASTERROR(ERROR) + << "Failed to create a window station and desktop"; + return false; + } + + // Try to launch the worker process. The launched process inherits + // the window station, desktop and pipe handles, created above. + ScopedHandle worker_thread; + worker_process_.Close(); + if (!LaunchProcessWithToken(command_line.GetProgram(), + command_line.GetCommandLineString(), + token, + &process_attributes, + &thread_attributes, + true, + 0, + NULL, + &worker_process_, + &worker_thread)) { + return false; + } + } + // Return a handle that the caller can wait on to get notified when // the process terminates. ScopedHandle process_exit_event; |