File: launch_context_win.cc

package info (click to toggle)
chromium 138.0.7204.183-1~deb12u1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm-proposed-updates
  • size: 6,080,960 kB
  • sloc: cpp: 34,937,079; ansic: 7,176,967; javascript: 4,110,704; python: 1,419,954; asm: 946,768; xml: 739,971; pascal: 187,324; sh: 89,623; perl: 88,663; objc: 79,944; sql: 50,304; cs: 41,786; fortran: 24,137; makefile: 21,811; php: 13,980; tcl: 13,166; yacc: 8,925; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (367 lines) | stat: -rw-r--r-- 13,966 bytes parent folder | download | duplicates (6)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/extensions/api/messaging/launch_context.h"

#include <windows.h>

#include <shellapi.h>

#include <string>
#include <utility>

#include "base/check.h"
#include "base/containers/span.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/numerics/safe_conversions.h"
#include "base/process/launch.h"
#include "base/process/process.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/time/time.h"
#include "base/win/registry.h"
#include "base/win/scoped_handle.h"
#include "build/branding_buildflags.h"
#include "crypto/random.h"
#include "net/base/file_stream.h"
#include "net/base/net_errors.h"

namespace extensions {

namespace {

// Reads path to the native messaging host manifest from a specific subkey in
// the registry. Returns false if the path isn't found.
bool GetManifestPathWithFlagsFromSubkey(HKEY root_key,
                                        DWORD flags,
                                        const wchar_t* subkey,
                                        const std::wstring& host_name,
                                        std::wstring* result) {
  base::win::RegKey key;

  return key.Open(root_key, subkey, KEY_QUERY_VALUE | flags) == ERROR_SUCCESS &&
         key.OpenKey(host_name.c_str(), KEY_QUERY_VALUE | flags) ==
             ERROR_SUCCESS &&
         key.ReadValue(nullptr, result) == ERROR_SUCCESS;
}

// Reads path to the native messaging host manifest from the registry. Returns
// false if the path isn't found.
bool GetManifestPathWithFlags(HKEY root_key,
                              DWORD flags,
                              const std::wstring& host_name,
                              std::wstring* result) {
#if BUILDFLAG(CHROMIUM_BRANDING)
  static constexpr wchar_t kChromiumNativeMessagingRegistryKey[] =
      L"SOFTWARE\\Chromium\\NativeMessagingHosts";

  // Try to read the path using the Chromium-specific registry for Chromium.
  // If that fails, fallback to Chrome-specific registry key below.
  if (GetManifestPathWithFlagsFromSubkey(root_key, flags,
                                         kChromiumNativeMessagingRegistryKey,
                                         host_name, result)) {
    return true;
  }
#endif

  static constexpr wchar_t kChromeNativeMessagingRegistryKey[] =
      L"SOFTWARE\\Google\\Chrome\\NativeMessagingHosts";

  return GetManifestPathWithFlagsFromSubkey(
      root_key, flags, kChromeNativeMessagingRegistryKey, host_name, result);
}

bool GetManifestPath(HKEY root_key,
                     const std::wstring& host_name,
                     std::wstring* result) {
  // First check 32-bit registry and then try 64-bit.
  return GetManifestPathWithFlags(root_key, KEY_WOW64_32KEY, host_name,
                                  result) ||
         GetManifestPathWithFlags(root_key, KEY_WOW64_64KEY, host_name, result);
}

// If the Host is an executable, we will invoke it directly to avoid problems
// if CMD.exe is unavailable due to OS policy or other configuration issues
// on the client. See https://crbug.com/335558 for details.
base::Process LaunchNativeExeDirectly(const std::wstring& command,
                                      base::LaunchOptions& options,
                                      const std::wstring& in_pipe_name,
                                      const std::wstring& out_pipe_name) {
  // When calling the Host executable directly, we must first wrap our Named
  // Pipes into HANDLEs returned from CreateFileW(). We must configure the
  // handle's security attributes to allow the file handle to be inherited
  // into the launched process.
  SECURITY_ATTRIBUTES sa_attr = {};
  sa_attr.nLength = sizeof(SECURITY_ATTRIBUTES);
  sa_attr.bInheritHandle = TRUE;
  sa_attr.lpSecurityDescriptor = nullptr;

  base::win::ScopedHandle stdout_file(
      ::CreateFileW(out_pipe_name.c_str(), FILE_WRITE_DATA | SYNCHRONIZE, 0,
                    &sa_attr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0));
  if (!stdout_file.IsValid()) {
    LOG(ERROR) << "Failed to open write handle for stdout.";
    return base::Process();
  }

  base::win::ScopedHandle stdin_file(
      ::CreateFileW(in_pipe_name.c_str(), FILE_READ_DATA | SYNCHRONIZE, 0,
                    &sa_attr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0));
  if (!stdin_file.IsValid()) {
    LOG(ERROR) << "Failed to open read handle for stdin.";
    return base::Process();
  }

  options.stdin_handle = stdin_file.Get();
  options.stdout_handle = stdout_file.Get();
  options.stderr_handle = ::GetStdHandle(STD_ERROR_HANDLE);
  options.handles_to_inherit.push_back(options.stdin_handle);
  options.handles_to_inherit.push_back(options.stdout_handle);

  // Inherit Chrome's STD_ERROR_HANDLE, if set, into the Native Host. If Chrome
  // was not started with standard error redirected, this value will be null.
  if (options.stderr_handle != NULL) {
    options.handles_to_inherit.push_back(options.stderr_handle);
  }

  return base::LaunchProcess(command, options);
}

// For non-executable Hosts, we will use the legacy approach whereby we
// invoke CMD.exe and instruct it to launch the Host, passing references to
// our Named Pipes.
base::Process LaunchNativeHostViaCmd(const std::wstring& command,
                                     base::LaunchOptions& options,
                                     const std::wstring& in_pipe_name,
                                     const std::wstring& out_pipe_name) {
  DWORD comspec_length = ::GetEnvironmentVariable(L"COMSPEC", NULL, 0);
  if (comspec_length == 0) {
    LOG(ERROR) << "COMSPEC is not set";
    return base::Process();
  }
  std::wstring comspec;
  ::GetEnvironmentVariable(
      L"COMSPEC", base::WriteInto(&comspec, comspec_length), comspec_length);

  return base::LaunchProcess(
      base::StrCat({comspec, L" /d /s /c \"", command, L"\" < ", in_pipe_name,
                    L" > ", out_pipe_name}),
      options);
}

}  // namespace

// static
base::FilePath LaunchContext::FindManifest(const std::string& host_name,
                                           bool allow_user_level_hosts,
                                           std::string& error_message) {
  std::wstring host_name_wide = base::UTF8ToWide(host_name);

  // If permitted, look in HKEY_CURRENT_USER first. If the manifest isn't found
  // there, then try HKEY_LOCAL_MACHINE. https://crbug.com/1034919#c6
  std::wstring path_str;
  bool found = false;
  if (allow_user_level_hosts) {
    found = GetManifestPath(HKEY_CURRENT_USER, host_name_wide, &path_str);
  }
  if (!found) {
    found = GetManifestPath(HKEY_LOCAL_MACHINE, host_name_wide, &path_str);
  }

  if (!found) {
    error_message =
        "Native messaging host " + host_name + " is not registered.";
    return base::FilePath();
  }

  base::FilePath manifest_path(path_str);
  if (!manifest_path.IsAbsolute()) {
    error_message = "Path to native messaging host manifest must be absolute.";
    return base::FilePath();
  }

  return manifest_path;
}

// static
std::optional<LaunchContext::ProcessState> LaunchContext::LaunchNativeProcess(
    const base::CommandLine& command_line,
    bool native_hosts_executables_launch_directly) {
  // Timeout for the IO pipes.
  const DWORD kTimeoutMs = 5000;

  // Windows will use default buffer size when 0 is passed to
  // CreateNamedPipeW().
  const DWORD kBufferSize = 0;

  if (!command_line.GetProgram().IsAbsolute()) {
    LOG(ERROR) << "Native Messaging host path must be absolute.";
    return std::nullopt;
  }

  uint64_t pipe_name_token;
  crypto::RandBytes(base::byte_span_from_ref(pipe_name_token));
  const std::wstring pipe_name_token_str =
      base::ASCIIToWide(base::StringPrintf("%llx", pipe_name_token));
  const std::wstring out_pipe_name =
      L"\\\\.\\pipe\\chrome.nativeMessaging.out." + pipe_name_token_str;
  const std::wstring in_pipe_name =
      L"\\\\.\\pipe\\chrome.nativeMessaging.in." + pipe_name_token_str;

  // Create the pipes to read and write from.
  base::win::ScopedHandle stdout_pipe(::CreateNamedPipeW(
      out_pipe_name.c_str(),
      PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED |
          FILE_FLAG_FIRST_PIPE_INSTANCE,
      PIPE_TYPE_BYTE, 1, kBufferSize, kBufferSize, kTimeoutMs, NULL));
  if (!stdout_pipe.IsValid()) {
    LOG(ERROR) << "Failed to create pipe " << out_pipe_name;
    return std::nullopt;
  }

  base::win::ScopedHandle stdin_pipe(::CreateNamedPipeW(
      in_pipe_name.c_str(),
      PIPE_ACCESS_OUTBOUND | FILE_FLAG_OVERLAPPED |
          FILE_FLAG_FIRST_PIPE_INSTANCE,
      PIPE_TYPE_BYTE, 1, kBufferSize, kBufferSize, kTimeoutMs, NULL));
  if (!stdin_pipe.IsValid()) {
    LOG(ERROR) << "Failed to create pipe " << in_pipe_name;
    return std::nullopt;
  }

  std::wstring command = command_line.GetCommandLineString();
  base::LaunchOptions options;
  options.current_directory = command_line.GetProgram().DirName();
  options.start_hidden = true;

  const bool use_direct_launch =
      native_hosts_executables_launch_directly &&
      command_line.GetProgram().MatchesFinalExtension(L".exe");

  base::Process launched_process;
  if (use_direct_launch) {
    // Compat: If the target is SUBSYSTEM_WINDOWS, then don't set |start_hidden|
    // in order to mimic legacy behavior: https://crbug.com/1442359.
    // A Windows executable will have LOWORD of 0x4550. A GUI executable will
    // have a non-Zero HIWORD while a console executable will have a 0 HIWORD.
    uintptr_t exe_type = ::SHGetFileInfoW(
        command_line.GetProgram().value().c_str(), 0, NULL, 0, SHGFI_EXETYPE);
    if ((LOWORD(exe_type) == 0x4550) && (HIWORD(exe_type) != 0)) {
      options.start_hidden = false;
    }

    launched_process =
        LaunchNativeExeDirectly(command, options, in_pipe_name, out_pipe_name);
  } else {
    launched_process =
        LaunchNativeHostViaCmd(command, options, in_pipe_name, out_pipe_name);
  }

  if (!launched_process.IsValid()) {
    LOG(ERROR) << "Error launching process "
               << command_line.GetProgram().MaybeAsASCII();
    return std::nullopt;
  }

  return ProcessState(std::move(launched_process), std::move(stdout_pipe),
                      std::move(stdin_pipe));
}

void LaunchContext::ConnectPipes(base::ScopedPlatformFile read_file,
                                 base::ScopedPlatformFile write_file) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CHECK(native_process_.IsValid());

  read_stream_ = std::make_unique<net::FileStream>(
      base::File(std::move(read_file), /*async=*/true),
      background_task_runner_);
  write_stream_ = std::make_unique<net::FileStream>(
      base::File(std::move(write_file), /*async=*/true),
      background_task_runner_);

  const int read_result = read_stream_->ConnectNamedPipe(
      base::BindOnce(&LaunchContext::OnReadStreamConnectResult, GetWeakPtr()));
  const int write_result = write_stream_->ConnectNamedPipe(
      base::BindOnce(&LaunchContext::OnWriteStreamConnectResult, GetWeakPtr()));

  read_pipe_connected_ = read_result == net::OK;
  write_pipe_connected_ = write_result == net::OK;

  if ((!read_pipe_connected_ && read_result != net::ERR_IO_PENDING) ||
      (!write_pipe_connected_ && write_result != net::ERR_IO_PENDING)) {
    // Failed connecting to one of the pipes to talk to the host.
    OnFailure(NativeProcessLauncher::RESULT_FAILED_TO_START);
    return;
  }

  if (read_pipe_connected_ && write_pipe_connected_) {
    // The host has already connected to both pipes.
    OnSuccess(base::kInvalidPlatformFile, std::move(read_stream_),
              std::move(write_stream_));
    return;
  }

  // Wait for calls to one or both of the StreamConnectResult methods once the
  // connect operation(s) complete. In the meantime, watch the host process to
  // make sure it doesn't tip over while waiting.
  process_watcher_.StartWatchingOnce(native_process_.Handle(), this);
}

void LaunchContext::OnReadStreamConnectResult(int net_error) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (net_error != net::OK) {
    // Failed to connect.
    OnFailure(NativeProcessLauncher::RESULT_FAILED_TO_START);
    return;
  }
  // The host has connected to the read pipe.
  read_pipe_connected_ = true;
  OnPipeConnected();
}

void LaunchContext::OnWriteStreamConnectResult(int net_error) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (net_error != net::OK) {
    // Failed to connect.
    OnFailure(NativeProcessLauncher::RESULT_FAILED_TO_START);
    return;
  }
  // The host has connected to the write pipe.
  write_pipe_connected_ = true;
  OnPipeConnected();
}

void LaunchContext::OnPipeConnected() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (!read_pipe_connected_ || !write_pipe_connected_) {
    return;  // At least one pipe's result is still outstanding.
  }
  process_watcher_.StopWatching();
  OnSuccess(base::kInvalidPlatformFile, std::move(read_stream_),
            std::move(write_stream_));
}

void LaunchContext::OnObjectSignaled(HANDLE object) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  // The host process has terminated unexpectedly.
  CHECK_EQ(object, native_process_.Handle());
  int exit_code = 0;  // EXIT_SUCCESS
  if (native_process_.WaitForExitWithTimeout({}, &exit_code)) {
    LOG(ERROR) << "Native Messaging host process exited with code "
               << exit_code;
  } else {
    LOG(ERROR) << "Native Messaging host process exited unexpectedly";
  }
  OnFailure(NativeProcessLauncher::RESULT_FAILED_TO_START);
}

}  // namespace extensions