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