1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
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 "chrome/browser/extensions/api/messaging/native_process_launcher.h"
6
7 #include <windows.h>
8 #include <stdint.h>
9
10 #include "base/command_line.h"
11 #include "base/logging.h"
12 #include "base/process/launch.h"
13 #include "base/process/process.h"
14 #include "base/strings/string16.h"
15 #include "base/strings/string_number_conversions.h"
16 #include "base/strings/stringprintf.h"
17 #include "base/strings/utf_string_conversions.h"
18 #include "base/win/registry.h"
19 #include "base/win/scoped_handle.h"
20 #include "crypto/random.h"
21
22 namespace extensions {
23
24 const wchar_t kNativeMessagingRegistryKey[] =
25 L"SOFTWARE\\Google\\Chrome\\NativeMessagingHosts";
26
27 namespace {
28
29 // Reads path to the native messaging host manifest from the registry. Returns
30 // false if the path isn't found.
GetManifestPathWithFlags(HKEY root_key,DWORD flags,const base::string16 & host_name,base::string16 * result)31 bool GetManifestPathWithFlags(HKEY root_key,
32 DWORD flags,
33 const base::string16& host_name,
34 base::string16* result) {
35 base::win::RegKey key;
36
37 if (key.Open(root_key, kNativeMessagingRegistryKey,
38 KEY_QUERY_VALUE | flags) != ERROR_SUCCESS ||
39 key.OpenKey(host_name.c_str(),
40 KEY_QUERY_VALUE | flags) != ERROR_SUCCESS ||
41 key.ReadValue(NULL, result) != ERROR_SUCCESS) {
42 return false;
43 }
44
45 return true;
46 }
47
GetManifestPath(HKEY root_key,const base::string16 & host_name,base::string16 * result)48 bool GetManifestPath(HKEY root_key,
49 const base::string16& host_name,
50 base::string16* result) {
51 // First check 32-bit registry and then try 64-bit.
52 return GetManifestPathWithFlags(
53 root_key, KEY_WOW64_32KEY, host_name, result) ||
54 GetManifestPathWithFlags(
55 root_key, KEY_WOW64_64KEY, host_name, result);
56 }
57
58 } // namespace
59
60 // static
FindManifest(const std::string & host_name,bool allow_user_level_hosts,std::string * error_message)61 base::FilePath NativeProcessLauncher::FindManifest(
62 const std::string& host_name,
63 bool allow_user_level_hosts,
64 std::string* error_message) {
65 base::string16 host_name_wide = base::UTF8ToUTF16(host_name);
66
67 // If permitted, look in HKEY_CURRENT_USER first. If the manifest isn't found
68 // there, then try HKEY_LOCAL_MACHINE. https://crbug.com/1034919#c6
69 base::string16 path_str;
70 bool found = false;
71 if (allow_user_level_hosts)
72 found = GetManifestPath(HKEY_CURRENT_USER, host_name_wide, &path_str);
73 if (!found)
74 found = GetManifestPath(HKEY_LOCAL_MACHINE, host_name_wide, &path_str);
75
76 if (!found) {
77 *error_message =
78 "Native messaging host " + host_name + " is not registered.";
79 return base::FilePath();
80 }
81
82 base::FilePath manifest_path(path_str);
83 if (!manifest_path.IsAbsolute()) {
84 *error_message = "Path to native messaging host manifest must be absolute.";
85 return base::FilePath();
86 }
87
88 return manifest_path;
89 }
90
91 // static
LaunchNativeProcess(const base::CommandLine & command_line,base::Process * process,base::File * read_file,base::File * write_file)92 bool NativeProcessLauncher::LaunchNativeProcess(
93 const base::CommandLine& command_line,
94 base::Process* process,
95 base::File* read_file,
96 base::File* write_file) {
97 // Timeout for the IO pipes.
98 const DWORD kTimeoutMs = 5000;
99
100 // Windows will use default buffer size when 0 is passed to
101 // CreateNamedPipeW().
102 const DWORD kBufferSize = 0;
103
104 if (!command_line.GetProgram().IsAbsolute()) {
105 LOG(ERROR) << "Native Messaging host path must be absolute.";
106 return false;
107 }
108
109 uint64_t pipe_name_token;
110 crypto::RandBytes(&pipe_name_token, sizeof(pipe_name_token));
111 base::string16 out_pipe_name = base::StringPrintf(
112 L"\\\\.\\pipe\\chrome.nativeMessaging.out.%llx", pipe_name_token);
113 base::string16 in_pipe_name = base::StringPrintf(
114 L"\\\\.\\pipe\\chrome.nativeMessaging.in.%llx", pipe_name_token);
115
116 // Create the pipes to read and write from.
117 base::win::ScopedHandle stdout_pipe(
118 CreateNamedPipeW(out_pipe_name.c_str(),
119 PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED |
120 FILE_FLAG_FIRST_PIPE_INSTANCE,
121 PIPE_TYPE_BYTE, 1, kBufferSize, kBufferSize,
122 kTimeoutMs, NULL));
123 if (!stdout_pipe.IsValid()) {
124 LOG(ERROR) << "Failed to create pipe " << out_pipe_name;
125 return false;
126 }
127
128 base::win::ScopedHandle stdin_pipe(
129 CreateNamedPipeW(in_pipe_name.c_str(),
130 PIPE_ACCESS_OUTBOUND | FILE_FLAG_OVERLAPPED |
131 FILE_FLAG_FIRST_PIPE_INSTANCE,
132 PIPE_TYPE_BYTE, 1, kBufferSize, kBufferSize,
133 kTimeoutMs, NULL));
134 if (!stdin_pipe.IsValid()) {
135 LOG(ERROR) << "Failed to create pipe " << in_pipe_name;
136 return false;
137 }
138
139 DWORD comspec_length = ::GetEnvironmentVariable(L"COMSPEC", NULL, 0);
140 if (comspec_length == 0) {
141 LOG(ERROR) << "COMSPEC is not set";
142 return false;
143 }
144 std::unique_ptr<wchar_t[]> comspec(new wchar_t[comspec_length]);
145 ::GetEnvironmentVariable(L"COMSPEC", comspec.get(), comspec_length);
146
147 base::string16 command_line_string = command_line.GetCommandLineString();
148
149 base::string16 command = base::StringPrintf(
150 L"%ls /d /c %ls < %ls > %ls", comspec.get(), command_line_string.c_str(),
151 in_pipe_name.c_str(), out_pipe_name.c_str());
152
153 base::LaunchOptions options;
154 options.start_hidden = true;
155 options.current_directory = command_line.GetProgram().DirName();
156 base::Process cmd_process = base::LaunchProcess(command, options);
157 if (!cmd_process.IsValid()) {
158 LOG(ERROR) << "Error launching process "
159 << command_line.GetProgram().MaybeAsASCII();
160 return false;
161 }
162
163 bool stdout_connected = ConnectNamedPipe(stdout_pipe.Get(), NULL) ?
164 TRUE : GetLastError() == ERROR_PIPE_CONNECTED;
165 bool stdin_connected = ConnectNamedPipe(stdin_pipe.Get(), NULL) ?
166 TRUE : GetLastError() == ERROR_PIPE_CONNECTED;
167 if (!stdout_connected || !stdin_connected) {
168 cmd_process.Terminate(0, false);
169 LOG(ERROR) << "Failed to connect IO pipes when starting "
170 << command_line.GetProgram().MaybeAsASCII();
171 return false;
172 }
173
174 *process = std::move(cmd_process);
175 *read_file = base::File(std::move(stdout_pipe), true /* async */);
176 *write_file = base::File(std::move(stdin_pipe), true /* async */);
177 return true;
178 }
179
180 } // namespace extensions
181