tor-browser

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

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