chromium/remoting/host/win/launch_process_with_token.cc

// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "remoting/host/win/launch_process_with_token.h"

#include <windows.h>

#include <utility>

#include "base/logging.h"
#include "base/win/scoped_handle.h"
#include "base/win/scoped_process_information.h"
#include "base/win/startup_information.h"

using base::win::ScopedHandle;

namespace {

// Copies the process token making it a primary impersonation token.
// The returned handle will have |desired_access| rights.
bool CopyProcessToken(DWORD desired_access, ScopedHandle* token_out) {
  HANDLE temp_handle;
  if (!OpenProcessToken(GetCurrentProcess(), TOKEN_DUPLICATE | desired_access,
                        &temp_handle)) {
    PLOG(ERROR) << "Failed to open process token";
    return false;
  }
  ScopedHandle process_token(temp_handle);

  if (!DuplicateTokenEx(process_token.Get(), desired_access, nullptr,
                        SecurityImpersonation, TokenPrimary, &temp_handle)) {
    PLOG(ERROR) << "Failed to duplicate the process token";
    return false;
  }

  token_out->Set(temp_handle);
  return true;
}

// Creates a copy of the current process with SE_TCB_NAME privilege enabled.
bool CreatePrivilegedToken(ScopedHandle* token_out) {
  ScopedHandle privileged_token;
  DWORD desired_access = TOKEN_ADJUST_PRIVILEGES | TOKEN_IMPERSONATE |
                         TOKEN_DUPLICATE | TOKEN_QUERY;
  if (!CopyProcessToken(desired_access, &privileged_token)) {
    return false;
  }

  // Get the LUID for the SE_TCB_NAME privilege.
  TOKEN_PRIVILEGES state;
  state.PrivilegeCount = 1;
  state.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
  if (!LookupPrivilegeValue(nullptr, SE_TCB_NAME, &state.Privileges[0].Luid)) {
    PLOG(ERROR) << "Failed to lookup the LUID for the SE_TCB_NAME privilege";
    return false;
  }

  // Enable the SE_TCB_NAME privilege.
  if (!AdjustTokenPrivileges(privileged_token.Get(), FALSE, &state, 0, nullptr,
                             0)) {
    PLOG(ERROR) << "Failed to enable SE_TCB_NAME privilege in a token";
    return false;
  }

  *token_out = std::move(privileged_token);
  return true;
}

}  // namespace

namespace remoting {

// Creates a copy of the current process token for the given |session_id| so
// it can be used to launch a process in that session.
bool CreateSessionToken(uint32_t session_id, ScopedHandle* token_out) {
  ScopedHandle session_token;
  DWORD desired_access = TOKEN_ADJUST_DEFAULT | TOKEN_ADJUST_SESSIONID |
                         TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_QUERY;
  if (!CopyProcessToken(desired_access, &session_token)) {
    return false;
  }

  // Temporarily enable the SE_TCB_NAME privilege as it is required by
  // SetTokenInformation(TokenSessionId).
  ScopedHandle privileged_token;
  if (!CreatePrivilegedToken(&privileged_token)) {
    return false;
  }
  if (!ImpersonateLoggedOnUser(privileged_token.Get())) {
    PLOG(ERROR) << "Failed to impersonate the privileged token";
    return false;
  }

  // Change the session ID of the token.
  DWORD new_session_id = session_id;
  if (!SetTokenInformation(session_token.Get(), TokenSessionId, &new_session_id,
                           sizeof(new_session_id))) {
    PLOG(ERROR) << "Failed to change session ID of a token";

    // Revert to the default token.
    CHECK(RevertToSelf());
    return false;
  }

  // Revert to the default token.
  CHECK(RevertToSelf());

  *token_out = std::move(session_token);
  return true;
}

bool LaunchProcessWithToken(
    const base::FilePath& binary,
    const base::CommandLine::StringType& command_line,
    HANDLE user_token,
    SECURITY_ATTRIBUTES* process_attributes,
    SECURITY_ATTRIBUTES* thread_attributes,
    const base::HandlesToInheritVector& handles_to_inherit,
    DWORD creation_flags,
    const wchar_t* desktop_name,
    ScopedHandle* process_out,
    ScopedHandle* thread_out) {
  base::FilePath::StringType application_name = binary.value();

  base::win::StartupInformation startup_info_wrapper;
  STARTUPINFO* startup_info = startup_info_wrapper.startup_info();
  if (desktop_name) {
    startup_info->lpDesktop = const_cast<wchar_t*>(desktop_name);
  }

  bool inherit_handles = false;
  if (!handles_to_inherit.empty()) {
    if (handles_to_inherit.size() >
        std::numeric_limits<DWORD>::max() / sizeof(HANDLE)) {
      DLOG(ERROR) << "Too many handles to inherit.";
      return false;
    }

    // Ensure the handles can be inherited.
    for (HANDLE handle : handles_to_inherit) {
      BOOL result = SetHandleInformation(handle, HANDLE_FLAG_INHERIT,
                                         HANDLE_FLAG_INHERIT);
      PCHECK(result);
    }

    if (!startup_info_wrapper.InitializeProcThreadAttributeList(
            /* attribute_count= */ 1)) {
      PLOG(ERROR) << "InitializeProcThreadAttributeList()";
      return false;
    }

    if (!startup_info_wrapper.UpdateProcThreadAttribute(
            PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
            const_cast<HANDLE*>(&handles_to_inherit.at(0)),
            static_cast<DWORD>(handles_to_inherit.size() * sizeof(HANDLE)))) {
      PLOG(ERROR) << "UpdateProcThreadAttribute()";
      return false;
    }

    inherit_handles = true;
    creation_flags |= EXTENDED_STARTUPINFO_PRESENT;
  }
  PROCESS_INFORMATION temp_process_info = {};
  BOOL result = CreateProcessAsUser(user_token, application_name.c_str(),
                                    const_cast<LPWSTR>(command_line.c_str()),
                                    process_attributes, thread_attributes,
                                    inherit_handles, creation_flags, nullptr,
                                    nullptr, startup_info, &temp_process_info);

  if (!result) {
    PLOG(ERROR) << "Failed to launch a process with a user token";
    return false;
  }

  base::win::ScopedProcessInformation process_info(temp_process_info);

  CHECK(process_info.IsValid());
  process_out->Set(process_info.TakeProcessHandle());
  thread_out->Set(process_info.TakeThreadHandle());
  return true;
}

}  // namespace remoting