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