client_win.cc (14848B)
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 <windows.h> 6 #include <winternl.h> 7 8 #include <cstring> 9 #include <memory> 10 #include <utility> 11 #include <vector> 12 13 #include "common/utils_win.h" 14 15 #include "client_win.h" 16 17 namespace content_analysis { 18 namespace sdk { 19 20 // Increased to a larger size to help with issues with analyzing a lot of 21 // files at once - see bug 1948884. 22 const DWORD kBufferSize = 65536; 23 24 // Use the same default timeout value (50ms) as CreateNamedPipeA(), expressed 25 // in 100ns intervals. 26 constexpr LONGLONG kDefaultTimeout = 500000; 27 28 // The following #defines and struct are copied from the official Microsoft 29 // Windows Driver Kit headers because they are not available in the official 30 // Microsoft Windows user mode SDK headers. 31 32 #define FSCTL_PIPE_WAIT 0x110018 33 #define STATUS_SUCCESS ((NTSTATUS)0) 34 #define STATUS_PIPE_NOT_AVAILABLE ((NTSTATUS)0xc00000ac) 35 #define STATUS_IO_TIMEOUT ((NTSTATUS)0xc00000b5) 36 37 typedef struct _FILE_PIPE_WAIT_FOR_BUFFER { 38 LARGE_INTEGER Timeout; 39 ULONG NameLength; 40 BOOLEAN TimeoutSpecified; 41 WCHAR Name[1]; 42 } FILE_PIPE_WAIT_FOR_BUFFER, *PFILE_PIPE_WAIT_FOR_BUFFER; 43 44 namespace { 45 46 using NtCreateFileFn = decltype(&::NtCreateFile); 47 48 NtCreateFileFn GetNtCreateFileFn() { 49 static NtCreateFileFn fnNtCreateFile = []() { 50 NtCreateFileFn fn = nullptr; 51 HMODULE h = LoadLibraryA("NtDll.dll"); 52 if (h != nullptr) { 53 fn = reinterpret_cast<NtCreateFileFn>(GetProcAddress(h, "NtCreateFile")); 54 FreeLibrary(h); 55 } 56 return fn; 57 }(); 58 59 return fnNtCreateFile; 60 } 61 62 63 using NtFsControlFileFn = NTSTATUS (NTAPI *)( 64 HANDLE FileHandle, 65 HANDLE Event, 66 PIO_APC_ROUTINE ApcRoutine, 67 PVOID ApcContext, 68 PIO_STATUS_BLOCK IoStatusBlock, 69 ULONG IoControlCode, 70 PVOID InputBuffer, 71 ULONG InputBufferLength, 72 PVOID OutputBuffer, 73 ULONG OutputBufferLength); 74 75 NtFsControlFileFn GetNtFsControlFileFn() { 76 static NtFsControlFileFn fnNtFsControlFile = []() { 77 NtFsControlFileFn fn = nullptr; 78 HMODULE h = LoadLibraryA("NtDll.dll"); 79 if (h != nullptr) { 80 fn = reinterpret_cast<NtFsControlFileFn>(GetProcAddress(h, "NtFsControlFile")); 81 FreeLibrary(h); 82 } 83 return fn; 84 }(); 85 86 return fnNtFsControlFile; 87 } 88 89 NTSTATUS WaitForPipeAvailability(const UNICODE_STRING& path) { 90 NtCreateFileFn fnNtCreateFile = GetNtCreateFileFn(); 91 if (fnNtCreateFile == nullptr) { 92 return false; 93 } 94 NtFsControlFileFn fnNtFsControlFile = GetNtFsControlFileFn(); 95 if (fnNtFsControlFile == nullptr) { 96 return false; 97 } 98 99 // Build the device name. This is the initial part of `path` which is 100 // assumed to start with the string `kPipePrefixForClient`. The `Length` 101 // field is measured in bytes, not characters, and does not include the null 102 // terminator. It's important that the device name ends with a trailing 103 // backslash. 104 size_t device_name_char_length = std::strlen(internal::kPipePrefixForClient); 105 UNICODE_STRING device_name; 106 device_name.Buffer = path.Buffer; 107 device_name.Length = device_name_char_length * sizeof(wchar_t); 108 device_name.MaximumLength = device_name.Length; 109 110 // Build the pipe name. This is the remaining part of `path` after the device 111 // name. 112 UNICODE_STRING pipe_name; 113 pipe_name.Buffer = path.Buffer + device_name_char_length; 114 pipe_name.Length = path.Length - device_name.Length; 115 pipe_name.MaximumLength = pipe_name.Length; 116 117 // Build the ioctl input buffer. This buffer is the size of 118 // FILE_PIPE_WAIT_FOR_BUFFER plus the length of the pipe name. Since 119 // FILE_PIPE_WAIT_FOR_BUFFER includes one WCHAR this includes space for 120 // the terminating null character of the name which wcsncpy() copies. 121 size_t buffer_size = sizeof(FILE_PIPE_WAIT_FOR_BUFFER) + pipe_name.Length; 122 std::vector<char> buffer(buffer_size); 123 FILE_PIPE_WAIT_FOR_BUFFER* wait_buffer = 124 reinterpret_cast<FILE_PIPE_WAIT_FOR_BUFFER*>(buffer.data()); 125 wait_buffer->Timeout.QuadPart = kDefaultTimeout; 126 wait_buffer->NameLength = pipe_name.Length; 127 wait_buffer->TimeoutSpecified = TRUE; 128 std::wcsncpy(wait_buffer->Name, pipe_name.Buffer, wait_buffer->NameLength / 129 sizeof(wchar_t)); 130 131 OBJECT_ATTRIBUTES attr; 132 InitializeObjectAttributes(&attr, &device_name, OBJ_CASE_INSENSITIVE, nullptr, 133 nullptr); 134 135 IO_STATUS_BLOCK io; 136 HANDLE h = INVALID_HANDLE_VALUE; 137 NTSTATUS sts = fnNtCreateFile(&h, GENERIC_READ | GENERIC_WRITE | SYNCHRONIZE, 138 &attr, &io, /*AllocationSize=*/nullptr, FILE_ATTRIBUTE_NORMAL, 139 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, 140 FILE_SYNCHRONOUS_IO_NONALERT, /*EaBuffer=*/nullptr, /*EaLength=*/0); 141 if (sts != STATUS_SUCCESS) { 142 return false; 143 } 144 145 IO_STATUS_BLOCK io2; 146 sts = fnNtFsControlFile(h, /*Event=*/nullptr, /*ApcRoutine=*/nullptr, 147 /*ApcContext*/nullptr, &io2, FSCTL_PIPE_WAIT, buffer.data(), 148 buffer.size(), nullptr, 0); 149 CloseHandle(h); 150 return sts; 151 } 152 153 // Reads the next message from the pipe and returns a buffer of chars. 154 // This function is synchronous. 155 std::vector<char> ReadNextMessageFromPipe( 156 HANDLE pipe, 157 OVERLAPPED* overlapped) { 158 DWORD err = ERROR_SUCCESS; 159 std::vector<char> buffer(kBufferSize); 160 char* p = buffer.data(); 161 int final_size = 0; 162 while (true) { 163 DWORD read; 164 165 // Even though the pipe is opened for overlapped IO, the read operation 166 // could still completely synchronously. For example, a server's response 167 // message could already be available in the pipe's internal buffer. 168 // If ReadFile() does complete synchronously, TRUE is returned. In this 169 // case update the final size and exit the loop. 170 if (ReadFile(pipe, p, kBufferSize, &read, overlapped)) { 171 final_size += read; 172 break; 173 } else { 174 // Reaching here means that ReadFile() will either complete async or 175 // an error has occurred. The former case is detected if the error code 176 // is "IO pending", in which case GetOverlappedResult() is called to wait 177 // for the IO to complete. If that function returns TRUE then the read 178 // operation completed successfully and the code simply updates the final 179 // size and exits the loop. 180 err = GetLastError(); 181 if (err == ERROR_IO_PENDING) { 182 if (GetOverlappedResult(pipe, overlapped, &read, /*wait=*/TRUE)) { 183 final_size += read; 184 break; 185 } else { 186 err = GetLastError(); 187 } 188 } 189 190 // Reaching here means an error has occurred. One error is recoverable: 191 // "more data". For any other type of error break out of the loop. 192 if (err != ERROR_MORE_DATA) { 193 final_size = 0; 194 break; 195 } 196 197 // Reaching here means the error is "more data", that is, the buffer 198 // specified in ReadFile() was too small to contain the entire response 199 // message from the server. ReadFile() has placed the start of the 200 // message in the specified buffer but ReadFile() needs to be called 201 // again to read the remaining part. 202 // 203 // The buffer size is increased and the current pointer into the buffer 204 // `p` is adjusted so that when the loop re-runs, it calls ReadFile() 205 // with the correct point in the buffer. It's possible that this loop 206 // might have to run many times if the response message is rather large. 207 buffer.resize(buffer.size() + kBufferSize); 208 p = buffer.data() + buffer.size() - kBufferSize; 209 } 210 } 211 212 buffer.resize(final_size); 213 return buffer; 214 } 215 216 // Writes a string to the pipe. Returns true if successful, false otherwise. 217 // This function is synchronous. 218 bool WriteMessageToPipe( 219 HANDLE pipe, 220 const std::string& message, 221 OVERLAPPED* overlapped) { 222 if (message.empty()) 223 return false; 224 225 // Even though the pipe is opened for overlapped IO, the write operation 226 // could still completely synchronously. If it does, TRUE is returned. 227 // In this case the function is done. 228 bool ok = WriteFile(pipe, message.data(), message.size(), nullptr, overlapped); 229 if (!ok) { 230 // Reaching here means that WriteFile() will either complete async or 231 // an error has occurred. The former case is detected if the error code 232 // is "IO pending", in which case GetOverlappedResult() is called to wait 233 // for the IO to complete. Whether the operation completes sync or async, 234 // return true if the operation succeeded and false otherwise. 235 DWORD err = GetLastError(); 236 if (err == ERROR_IO_PENDING) { 237 DWORD written; 238 ok = GetOverlappedResult(pipe, overlapped, &written, /*wait=*/TRUE); 239 } 240 } 241 242 return ok; 243 } 244 245 } // namespace 246 247 // static 248 std::unique_ptr<Client> Client::Create(Config config) { 249 int rc; 250 auto client = std::make_unique<ClientWin>(std::move(config), &rc); 251 return rc == 0 ? std::move(client) : nullptr; 252 } 253 254 ClientWin::ClientWin(Config config, int* rc) : ClientBase(std::move(config)) { 255 *rc = -1; 256 257 std::string pipename = 258 internal::GetPipeNameForClient(configuration().name, 259 configuration().user_specific); 260 if (!pipename.empty()) { 261 unsigned long pid = 0; 262 if (ConnectToPipe(pipename, &hPipe_) == ERROR_SUCCESS && 263 GetNamedPipeServerProcessId(hPipe_, &pid)) { 264 agent_info().pid = pid; 265 266 // Getting the process path is best effort. 267 *rc = 0; 268 std::string binary_path; 269 if (internal::GetProcessPath(pid, &binary_path)) { 270 agent_info().binary_path = std::move(binary_path); 271 } 272 } 273 } 274 275 if (*rc != 0) { 276 Shutdown(); 277 } 278 } 279 280 ClientWin::~ClientWin() { 281 Shutdown(); 282 } 283 284 int ClientWin::Send(ContentAnalysisRequest request, 285 ContentAnalysisResponse* response) { 286 ChromeToAgent chrome_to_agent; 287 *chrome_to_agent.mutable_request() = std::move(request); 288 289 internal::ScopedOverlapped overlapped; 290 if (!overlapped.is_valid()) { 291 return -1; 292 } 293 294 bool success = WriteMessageToPipe(hPipe_, 295 chrome_to_agent.SerializeAsString(), 296 overlapped); 297 if (success) { 298 std::vector<char> buffer = ReadNextMessageFromPipe(hPipe_, overlapped); 299 AgentToChrome agent_to_chrome; 300 success = buffer.size() > 0 && 301 agent_to_chrome.ParseFromArray(buffer.data(), buffer.size()); 302 if (success) { 303 *response = std::move(*agent_to_chrome.mutable_response()); 304 } 305 } 306 307 return success ? 0 : -1; 308 } 309 310 int ClientWin::Acknowledge(const ContentAnalysisAcknowledgement& ack) { 311 // TODO: could avoid a copy by changing argument to be 312 // `ContentAnalysisAcknowledgement ack` and then using std::move() below and 313 // at call site. 314 ChromeToAgent chrome_to_agent; 315 *chrome_to_agent.mutable_ack() = ack; 316 317 internal::ScopedOverlapped overlapped; 318 if (!overlapped.is_valid()) { 319 return -1; 320 } 321 322 return WriteMessageToPipe(hPipe_, chrome_to_agent.SerializeAsString(), overlapped) 323 ? 0 : -1; 324 } 325 326 int ClientWin::CancelRequests(const ContentAnalysisCancelRequests& cancel) { 327 // TODO: could avoid a copy by changing argument to be 328 // `ContentAnalysisCancelRequests cancel` and then using std::move() below and 329 // at call site. 330 ChromeToAgent chrome_to_agent; 331 *chrome_to_agent.mutable_cancel() = cancel; 332 333 internal::ScopedOverlapped overlapped; 334 if (!overlapped.is_valid()) { 335 return -1; 336 } 337 338 return WriteMessageToPipe(hPipe_, chrome_to_agent.SerializeAsString(), overlapped) 339 ? 0 : -1; 340 } 341 342 // static 343 DWORD ClientWin::ConnectToPipe(const std::string& pipename, HANDLE* handle) { 344 // Get pointers to the Ntxxx functions. This is required to use absolute 345 // pipe names from the Windows NT Object Manager's namespace. This protects 346 // against the "\\.\pipe" symlink being redirected. 347 348 NtCreateFileFn fnNtCreateFile = GetNtCreateFileFn(); 349 if (fnNtCreateFile == nullptr) { 350 return ERROR_INVALID_FUNCTION; 351 } 352 353 // Convert the path to a wchar_t string. Pass pipename.size() as the 354 // `cbMultiByte` argument instead of -1 since the terminating null should not 355 // be counted. NtCreateFile() does not expect the object name to be 356 // terminated. Note that `buffer` and hence `name` created from it are both 357 // unterminated strings. 358 int wlen = MultiByteToWideChar(CP_ACP, 0, pipename.c_str(), pipename.size(), 359 nullptr, 0); 360 if (wlen == 0) { 361 return GetLastError(); 362 } 363 std::vector<wchar_t> buffer(wlen); 364 MultiByteToWideChar(CP_ACP, 0, pipename.c_str(), pipename.size(), 365 buffer.data(), wlen); 366 367 UNICODE_STRING name; 368 name.Buffer = buffer.data(); 369 name.Length = wlen * sizeof(wchar_t); // Length in bytes, not characters. 370 name.MaximumLength = name.Length; 371 372 OBJECT_ATTRIBUTES attr; 373 InitializeObjectAttributes(&attr, &name, OBJ_CASE_INSENSITIVE, nullptr, 374 nullptr); 375 376 // Open the named pipe for overlapped IO, i.e. do not specify either of the 377 // FILE_SYNCHRONOUS_IO_xxxALERT in the creation option flags. If the pipe 378 // is not opened for overlapped IO, then the Send() method will block if 379 // called from different threads since only one read or write operation would 380 // be allowed at a time. 381 IO_STATUS_BLOCK io; 382 HANDLE h = INVALID_HANDLE_VALUE; 383 NTSTATUS sts = STATUS_IO_TIMEOUT; 384 while (sts == STATUS_IO_TIMEOUT) { 385 sts = fnNtCreateFile(&h, GENERIC_READ | GENERIC_WRITE | 386 SYNCHRONIZE, &attr, &io, /*AllocationSize=*/nullptr, 387 FILE_ATTRIBUTE_NORMAL, /*ShareAccess=*/0, FILE_OPEN, 388 FILE_NON_DIRECTORY_FILE, 389 /*EaBuffer=*/nullptr, /*EaLength=*/0); 390 if (sts != STATUS_SUCCESS) { 391 if (sts != STATUS_PIPE_NOT_AVAILABLE) { 392 break; 393 } 394 395 sts = WaitForPipeAvailability(name); 396 if (sts != STATUS_SUCCESS && sts != STATUS_IO_TIMEOUT) { 397 break; 398 } 399 } 400 } 401 402 if (sts != STATUS_SUCCESS) { 403 return ERROR_PIPE_NOT_CONNECTED; 404 } 405 406 // Change to message read mode to match server side. Max connection count 407 // and timeout must be null if client and server are on the same machine. 408 DWORD mode = PIPE_READMODE_MESSAGE; 409 if (!SetNamedPipeHandleState(h, &mode, 410 /*maxCollectionCount=*/nullptr, 411 /*connectionTimeout=*/nullptr)) { 412 DWORD err = GetLastError(); 413 CloseHandle(h); 414 return err; 415 } 416 417 *handle = h; 418 return ERROR_SUCCESS; 419 } 420 421 void ClientWin::Shutdown() { 422 if (hPipe_ != INVALID_HANDLE_VALUE) { 423 // TODO: This trips the LateWriteObserver. We could move this earlier 424 // (before the LateWriteObserver is created) or just remove it, although 425 // the later could mean an ACK message is not processed by the agent 426 // in time. 427 // FlushFileBuffers(hPipe_); 428 CloseHandle(hPipe_); 429 hPipe_ = INVALID_HANDLE_VALUE; 430 } 431 } 432 433 } // namespace sdk 434 } // namespace content_analysis