tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

agent_win.cc (17152B)


      1 // Copyright 2022 The Chromium Authors.
      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 <sstream>
      6 #include <utility>
      7 #include <vector>
      8 
      9 #include <windows.h>
     10 #include <sddl.h>
     11 
     12 #include "common/utils_win.h"
     13 
     14 #include "agent_utils_win.h"
     15 #include "agent_win.h"
     16 #include "event_win.h"
     17 
     18 namespace content_analysis {
     19 namespace sdk {
     20 
     21 // The minimum number of pipe in listening mode.  This is greater than one to
     22 // handle the case of multiple instance of Google Chrome browser starting
     23 // at the same time.
     24 const DWORD kMinNumListeningPipeInstances = 2;
     25 
     26 // The minimum number of handles to wait on.  This is the minimum number
     27 // of pipes in listening mode plus the stop event.
     28 const DWORD kMinNumWaitHandles = kMinNumListeningPipeInstances + 1;
     29 
     30 // static
     31 std::unique_ptr<Agent> Agent::Create(
     32    Config config,
     33    std::unique_ptr<AgentEventHandler> handler,
     34    ResultCode* rc) {
     35  auto agent = std::make_unique<AgentWin>(std::move(config), std::move(handler), rc);
     36  return *rc == ResultCode::OK ? std::move(agent) : nullptr;
     37 }
     38 
     39 AgentWin::Connection::Connection(const std::string& pipename,
     40                                 bool user_specific,
     41                                 AgentEventHandler* handler,
     42                                 bool is_first_pipe,
     43                                 ResultCode* rc)
     44    : handler_(handler)  {
     45  *rc = ResultCode::OK;
     46  memset(&overlapped_, 0, sizeof(overlapped_));
     47  // Create a manual reset event as specified for overlapped IO.
     48  // Use default security attriutes and no name since this event is not
     49  // shared with other processes.
     50  overlapped_.hEvent = CreateEvent(/*securityAttr=*/nullptr,
     51                                   /*manualReset=*/TRUE,
     52                                   /*initialState=*/FALSE,
     53                                   /*name=*/nullptr);
     54  if (!overlapped_.hEvent) {
     55    *rc = ResultCode::ERR_CANNOT_CREATE_CHANNEL_IO_EVENT;
     56    return;
     57  }
     58 
     59  *rc = ResetInternal(pipename, user_specific, is_first_pipe);
     60 }
     61 
     62 AgentWin::Connection::~Connection() {
     63  Cleanup();
     64 
     65  if (handle_ != INVALID_HANDLE_VALUE) {
     66    CloseHandle(handle_);
     67  }
     68 
     69  // Invalid event handles are represented as null.
     70  if (overlapped_.hEvent) {
     71    CloseHandle(overlapped_.hEvent);
     72  }
     73 }
     74 
     75 ResultCode AgentWin::Connection::Reset(
     76    const std::string& pipename,
     77    bool user_specific) {
     78  return NotifyIfError("ConnectionReset",
     79                       ResetInternal(pipename, user_specific, false));
     80 }
     81 
     82 ResultCode AgentWin::Connection::HandleEvent(HANDLE handle) {
     83  auto rc = ResultCode::OK;
     84  DWORD count;
     85  BOOL success = GetOverlappedResult(handle, &overlapped_, &count,
     86                                     /*wait=*/FALSE);
     87  if (!is_connected_) {
     88    // This connection is currently listing for a new connection from a Google
     89    // Chrome browser.  If the result is a success, this means the browser has
     90    // connected as expected.  Otherwise an error occured so report it to the
     91    // caller.
     92    if (success) {
     93      // A Google Chrome browser connected to the agent.  Reset this
     94      // connection object to handle communication with the browser and then
     95      // tell the handler about it.
     96 
     97      is_connected_ = true;
     98      buffer_.resize(internal::kBufferSize);
     99 
    100      rc = BuildBrowserInfo();
    101      if (rc == ResultCode::OK) {
    102        handler_->OnBrowserConnected(browser_info_);
    103      }
    104    } else {
    105      rc = ErrorToResultCode(GetLastError());
    106      NotifyIfError("GetOverlappedResult", rc);
    107    }
    108  } else {
    109    // Some data has arrived from Google Chrome. This data is (part of) an
    110    // instance of the proto message `ChromeToAgent`.
    111    //
    112    // If the message is small it is received in by one call to ReadFile().
    113    // If the message is larger it is received in by multiple calls to
    114    // ReadFile().
    115    //
    116    // `success` is true if the data just read is the last bytes for a message.
    117    // Otherwise it is false.
    118    rc = OnReadFile(success, count);
    119  }
    120 
    121  // If all data has been read, queue another read.
    122  if (rc == ResultCode::OK || rc == ResultCode::ERR_MORE_DATA) {
    123    rc = QueueReadFile(rc == ResultCode::OK);
    124  }
    125 
    126  if (rc != ResultCode::OK && rc != ResultCode::ERR_IO_PENDING &&
    127      rc != ResultCode::ERR_MORE_DATA) {
    128    Cleanup();
    129  } else {
    130    // Don't propagate all the "success" error codes to the called to keep
    131    // this simpler.
    132    rc = ResultCode::OK;
    133  }
    134 
    135  return rc;
    136 }
    137 
    138 void AgentWin::Connection::AppendDebugString(std::stringstream& state) const {
    139  state << "{handle=" << handle_;
    140  state << " connected=" << is_connected_;
    141  state << " pid=" << browser_info_.pid;
    142  state << " rsize=" << read_size_;
    143  state << " fsize=" << final_size_;
    144  state << "}";
    145 }
    146 
    147 ResultCode AgentWin::Connection::ConnectPipe() {
    148  // In overlapped mode, connecting to a named pipe always returns false.
    149  if (ConnectNamedPipe(handle_, &overlapped_)) {
    150    return ErrorToResultCode(GetLastError());
    151  }
    152 
    153  DWORD err = GetLastError();
    154  if (err == ERROR_IO_PENDING) {
    155    // Waiting for a Google Chrome Browser to connect.
    156    return ResultCode::OK;
    157  } else if (err == ERROR_PIPE_CONNECTED) {
    158    // A Google Chrome browser is already connected.  Make sure event is in
    159    // signaled state in order to process the connection.
    160    if (SetEvent(overlapped_.hEvent)) {
    161      err = ERROR_SUCCESS;
    162    } else {
    163      err = GetLastError();
    164    }
    165  }
    166 
    167  return ErrorToResultCode(err);
    168 }
    169 
    170 ResultCode AgentWin::Connection::ResetInternal(const std::string& pipename,
    171                                               bool user_specific,
    172                                               bool is_first_pipe) {
    173  auto rc = ResultCode::OK;
    174 
    175  // If this is the not the first time, disconnect from any existing Google
    176  // Chrome browser.  Otherwise creater a new pipe.
    177  if (handle_ != INVALID_HANDLE_VALUE) {
    178    if (!DisconnectNamedPipe(handle_)) {
    179      rc = ErrorToResultCode(GetLastError());
    180    }
    181  } else {
    182    rc = ErrorToResultCode(
    183        internal::CreatePipe(pipename, user_specific, is_first_pipe, &handle_));
    184  }
    185 
    186  // Make sure event starts in reset state.
    187  if (rc == ResultCode::OK && !ResetEvent(overlapped_.hEvent)) {
    188    rc = ErrorToResultCode(GetLastError());
    189  }
    190 
    191  if (rc == ResultCode::OK) {
    192    rc = ConnectPipe();
    193  }
    194 
    195  if (rc != ResultCode::OK) {
    196    Cleanup();
    197    handle_ = INVALID_HANDLE_VALUE;
    198  }
    199 
    200  return rc;
    201 }
    202 
    203 void AgentWin::Connection::Cleanup() {
    204  if (is_connected_ && handler_) {
    205    handler_->OnBrowserDisconnected(browser_info_);
    206  }
    207 
    208  is_connected_ = false;
    209  browser_info_ = BrowserInfo();
    210  buffer_.clear();
    211  cursor_ = nullptr;
    212  read_size_ = 0;
    213  final_size_ = 0;
    214 
    215  if (handle_ != INVALID_HANDLE_VALUE) {
    216    // Cancel all outstanding IO requests on this pipe by using a null for
    217    // overlapped.
    218    CancelIoEx(handle_, /*overlapped=*/nullptr);
    219  }
    220 
    221  // This function does not close `handle_` or the event in `overlapped` so
    222  // that the server can resuse the pipe with an new Google Chrome browser
    223  // instance.
    224 }
    225 
    226 ResultCode AgentWin::Connection::QueueReadFile(bool reset_cursor) {
    227  if (reset_cursor) {
    228    cursor_ = buffer_.data();
    229    read_size_ = buffer_.size();
    230    final_size_ = 0;
    231  }
    232 
    233  // When this function is called there are the following possiblities:
    234  //
    235  // 1/ Data is already available and the buffer is filled in.  ReadFile()
    236  //    return TRUE and the event is set.
    237  // 2/ Data is not avaiable yet.  ReadFile() returns FALSE and the last error
    238  //    is ERROR_IO_PENDING(997) and the event is reset.
    239  // 3/ Some error occurred, like for example Google Chrome stops.  ReadFile()
    240  //    returns FALSE and the last error is something other than
    241  //    ERROR_IO_PENDING, for example ERROR_BROKEN_PIPE(109).  The event
    242  //    state is unchanged.
    243  auto rc = ResultCode::OK;
    244  DWORD count;
    245  if (!ReadFile(handle_, cursor_, read_size_, &count, &overlapped_)) {
    246    DWORD err = GetLastError();
    247    rc = ErrorToResultCode(err);
    248 
    249    // IO pending is not an error so don't notify.
    250    //
    251    // Ignore broken pipes for notifications since that happens when the Google
    252    // Chrome browser shuts down.  The agent will be notified of a browser
    253    // disconnect in that case.
    254    //
    255    // More data means that `buffer_` was too small to read the entire message
    256    // from the browser.  The buffer has already been resized.  Another call to
    257    // ReadFile() is needed to get the remainder.
    258    if (rc != ResultCode::ERR_IO_PENDING &&
    259        rc != ResultCode::ERR_BROKEN_PIPE &&
    260        rc != ResultCode::ERR_MORE_DATA) {
    261      NotifyIfError("QueueReadFile", rc, err);
    262    }
    263  }
    264 
    265  return rc;
    266 }
    267 
    268 ResultCode AgentWin::Connection::OnReadFile(BOOL done_reading, DWORD count) {
    269  final_size_ += count;
    270 
    271  // If `done_reading` is TRUE, this means the full message has been read.
    272  // Call the appropriate handler method.
    273  if (done_reading) {
    274    return CallHandler();
    275  }
    276 
    277  // Otherwise there are two possibilities:
    278  //
    279  // 1/ The last error is ERROR_MORE_DATA(234).  This means there are more
    280  //    bytes to read before the request message is complete.  Resize the
    281  //    buffer and adjust the cursor.  The caller will queue up another read
    282  //    and wait.  don't notify the handler since this is not an error.
    283  // 2/ Some error occured.  In this case notify the handler and return the
    284  //    error.
    285 
    286  DWORD err = GetLastError();
    287  if (err == ERROR_MORE_DATA) {
    288    read_size_ = internal::kBufferSize;
    289    buffer_.resize(buffer_.size() + read_size_);
    290    cursor_ = buffer_.data() + buffer_.size() - read_size_;
    291    return ErrorToResultCode(err);
    292  }
    293 
    294  return NotifyIfError("OnReadFile", ErrorToResultCode(err));
    295 }
    296 
    297 ResultCode AgentWin::Connection::CallHandler() {
    298  ChromeToAgent message;
    299  if (!message.ParseFromArray(buffer_.data(), final_size_)) {
    300    // Malformed message.
    301    return NotifyIfError("ParseChromeToAgent",
    302                         ResultCode::ERR_INVALID_REQUEST_FROM_BROWSER);
    303  }
    304 
    305  auto rc = ResultCode::OK;
    306 
    307  if (message.has_request()) {
    308    // This is a request from Google Chrome to perform a content analysis
    309    // request.
    310    //
    311    // Move the request from `message` to the event to reduce the amount
    312    // of memory allocation/copying and also because the the handler takes
    313    // ownership of the event.
    314    auto event = std::make_unique<ContentAnalysisEventWin>(
    315        handle_, browser_info_, std::move(*message.mutable_request()));
    316    rc = event->Init();
    317    if (rc == ResultCode::OK) {
    318      handler_->OnAnalysisRequested(std::move(event));
    319    } else {
    320      NotifyIfError("RequestValidation", rc);
    321    }
    322  } else if (message.has_ack()) {
    323    // This is an ack from Google Chrome that it has received a content
    324    // analysis response from the agent.
    325    handler_->OnResponseAcknowledged(message.ack());
    326  } else if (message.has_cancel()) {
    327    // Google Chrome is informing the agent that the content analysis
    328    // request(s) associated with the given user action id have been
    329    // canceled by the user.
    330    handler_->OnCancelRequests(message.cancel());
    331  } else {
    332    // Malformed message.
    333    rc = NotifyIfError("NoRequestOrAck",
    334                       ResultCode::ERR_INVALID_REQUEST_FROM_BROWSER);
    335  }
    336 
    337  return rc;
    338 }
    339 
    340 ResultCode AgentWin::Connection::BuildBrowserInfo() {
    341  if (!GetNamedPipeClientProcessId(handle_, &browser_info_.pid)) {
    342    return NotifyIfError("BuildBrowserInfo",
    343                         ResultCode::ERR_CANNOT_GET_BROWSER_PID);
    344  }
    345 
    346  if (!internal::GetProcessPath(browser_info_.pid,
    347                                &browser_info_.binary_path)) {
    348    return NotifyIfError("BuildBrowserInfo",
    349                         ResultCode::ERR_CANNOT_GET_BROWSER_BINARY_PATH);
    350  }
    351 
    352  return ResultCode::OK;
    353 }
    354 
    355 ResultCode AgentWin::Connection::NotifyIfError(
    356    const char* context,
    357    ResultCode rc,
    358    DWORD err) {
    359  if (handler_ && rc != ResultCode::OK) {
    360    std::stringstream stm;
    361    stm << context << " pid=" << browser_info_.pid;
    362    if (err != ERROR_SUCCESS) {
    363      stm << context << " err=" << err;
    364    }
    365 
    366    handler_->OnInternalError(stm.str().c_str(), rc);
    367  }
    368  return rc;
    369 }
    370 
    371 AgentWin::AgentWin(
    372    Config config,
    373    std::unique_ptr<AgentEventHandler> event_handler,
    374    ResultCode* rc)
    375  : AgentBase(std::move(config), std::move(event_handler)) {
    376  *rc = ResultCode::OK;
    377  if (handler() == nullptr) {
    378    *rc = ResultCode::ERR_AGENT_EVENT_HANDLER_NOT_SPECIFIED;
    379    return;
    380  }
    381 
    382  stop_event_ = CreateEvent(/*securityAttr=*/nullptr,
    383                            /*manualReset=*/TRUE,
    384                            /*initialState=*/FALSE,
    385                            /*name=*/nullptr);
    386  if (stop_event_ == nullptr) {
    387    *rc = ResultCode::ERR_CANNOT_CREATE_AGENT_STOP_EVENT;
    388    return;
    389  }
    390 
    391  std::string pipename =
    392      internal::GetPipeNameForAgent(configuration().name,
    393                                    configuration().user_specific);
    394  if (pipename.empty()) {
    395    *rc = ResultCode::ERR_INVALID_CHANNEL_NAME;
    396    return;
    397  }
    398 
    399  pipename_ = pipename;
    400 
    401  connections_.reserve(kMinNumListeningPipeInstances);
    402  for (DWORD i = 0; i < kMinNumListeningPipeInstances; ++i) {
    403    connections_.emplace_back(
    404        std::make_unique<Connection>(pipename_, configuration().user_specific,
    405                                     handler(), i == 0, rc));
    406    if (*rc != ResultCode::OK || !connections_.back()->IsValid()) {
    407      Shutdown();
    408      break;
    409    }
    410  }
    411 }
    412 
    413 AgentWin::~AgentWin() {
    414  Shutdown();
    415 }
    416 
    417 ResultCode AgentWin::HandleEvents() {
    418  std::vector<HANDLE> wait_handles;
    419  auto rc = ResultCode::OK;
    420  bool stopped = false;
    421  while (!stopped && rc == ResultCode::OK) {
    422    rc = HandleOneEvent(wait_handles, &stopped);
    423  }
    424 
    425  return rc;
    426 }
    427 
    428 ResultCode AgentWin::Stop() {
    429  SetEvent(stop_event_);
    430  return AgentBase::Stop();
    431 }
    432 
    433 std::string AgentWin::DebugString() const {
    434  std::stringstream state;
    435  state.setf(std::ios::boolalpha);
    436  state << "AgentWin{pipe=\"" << pipename_;
    437  state << "\" stop=" << stop_event_;
    438 
    439  for (size_t i = 0; i < connections_.size(); ++i) {
    440    state << " conn@" << i;
    441    connections_[i]->AppendDebugString(state);
    442  }
    443 
    444  state << "}" << std::ends;
    445  return state.str();
    446 }
    447 
    448 void AgentWin::GetHandles(std::vector<HANDLE>& wait_handles) const {
    449  // Reserve enough space in the handles vector to include the stop event plus
    450  // all connections.
    451  wait_handles.clear();
    452  wait_handles.reserve(1 + connections_.size());
    453 
    454  for (auto& state : connections_) {
    455    HANDLE wait_handle = state->GetWaitHandle();
    456    if (!wait_handle) {
    457      wait_handles.clear();
    458      break;
    459    }
    460    wait_handles.push_back(wait_handle);
    461  }
    462 
    463  // Push the stop event last so that connections_ index calculations in
    464  // HandleOneEvent() don't have to account for this handle.
    465  wait_handles.push_back(stop_event_);
    466 }
    467 
    468 ResultCode AgentWin::HandleOneEventForTesting() {
    469  std::vector<HANDLE> wait_handles;
    470  bool stopped;
    471  return HandleOneEvent(wait_handles, &stopped);
    472 }
    473 
    474 bool AgentWin::IsAClientConnectedForTesting() {
    475  for (const auto& state : connections_) {
    476    if (state->IsConnected()) {
    477      return true;
    478    }
    479  }
    480  return false;
    481 }
    482 
    483 ResultCode AgentWin::HandleOneEvent(
    484    std::vector<HANDLE>& wait_handles,
    485    bool* stopped) {
    486  *stopped = false;
    487 
    488  // Wait on the specified handles for an event to occur.
    489  GetHandles(wait_handles);
    490  if (wait_handles.size() < kMinNumWaitHandles) {
    491    return NotifyError("GetHandles", ResultCode::ERR_AGENT_NOT_INITIALIZED);
    492  }
    493 
    494  DWORD index = WaitForMultipleObjects(
    495      wait_handles.size(), wait_handles.data(),
    496      /*waitAll=*/FALSE, /*timeoutMs=*/INFINITE);
    497  if (index == WAIT_FAILED) {
    498    return NotifyError("WaitForMultipleObjects",
    499                       ErrorToResultCode(GetLastError()));
    500  }
    501 
    502  // If the index of signaled handle is the last one in wait_handles, then the
    503  // stop event was signaled.
    504  index -= WAIT_OBJECT_0;
    505  if (index == wait_handles.size() - 1) {
    506    *stopped = true;
    507    return ResultCode::OK;
    508  }
    509 
    510  auto& connection = connections_[index];
    511  bool was_listening = !connection->IsConnected();
    512  auto rc = connection->HandleEvent(wait_handles[index]);
    513  if (rc != ResultCode::OK) {
    514    // If `connection` was not listening and there are more than
    515    // kMinNumListeningPipeInstances pipes, delete this connection.  Otherwise
    516    // reset it so that it becomes a listener.
    517    if (!was_listening &&
    518      connections_.size() > kMinNumListeningPipeInstances) {
    519      connections_.erase(connections_.begin() + index);
    520    } else {
    521      rc = connection->Reset(pipename_, configuration().user_specific);
    522    }
    523  }
    524 
    525  // If `connection` was listening and is now connected, create a new
    526  // one so that there are always kMinNumListeningPipeInstances listening.
    527  if (rc == ResultCode::OK && was_listening && connection->IsConnected()) {
    528    connections_.emplace_back(
    529        std::make_unique<Connection>(pipename_, configuration().user_specific,
    530                                     handler(), false, &rc));
    531  }
    532 
    533  return ResultCode::OK;
    534 }
    535 
    536 void AgentWin::Shutdown() {
    537  connections_.clear();
    538  pipename_.clear();
    539  if (stop_event_ != nullptr) {
    540    CloseHandle(stop_event_);
    541    stop_event_ = nullptr;
    542  }
    543 }
    544 
    545 }  // namespace sdk
    546 }  // namespace content_analysis