electron_api_utility_process.cc 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  1. // Copyright (c) 2022 Microsoft, Inc.
  2. // Use of this source code is governed by the MIT license that can be
  3. // found in the LICENSE file.
  4. #include "shell/browser/api/electron_api_utility_process.h"
  5. #include <map>
  6. #include <utility>
  7. #include "base/files/file_util.h"
  8. #include "base/functional/bind.h"
  9. #include "base/no_destructor.h"
  10. #include "base/process/kill.h"
  11. #include "base/process/launch.h"
  12. #include "base/process/process.h"
  13. #include "content/public/browser/child_process_host.h"
  14. #include "content/public/browser/service_process_host.h"
  15. #include "content/public/common/result_codes.h"
  16. #include "gin/handle.h"
  17. #include "gin/object_template_builder.h"
  18. #include "gin/wrappable.h"
  19. #include "mojo/public/cpp/bindings/pending_receiver.h"
  20. #include "shell/browser/api/message_port.h"
  21. #include "shell/browser/javascript_environment.h"
  22. #include "shell/common/gin_converters/callback_converter.h"
  23. #include "shell/common/gin_converters/file_path_converter.h"
  24. #include "shell/common/gin_helper/dictionary.h"
  25. #include "shell/common/gin_helper/object_template_builder.h"
  26. #include "shell/common/node_includes.h"
  27. #include "shell/common/v8_value_serializer.h"
  28. #include "third_party/blink/public/common/messaging/message_port_descriptor.h"
  29. #include "third_party/blink/public/common/messaging/transferable_message_mojom_traits.h"
  30. #if BUILDFLAG(IS_POSIX)
  31. #include "base/posix/eintr_wrapper.h"
  32. #endif
  33. #if BUILDFLAG(IS_WIN)
  34. #include <fcntl.h>
  35. #include <io.h>
  36. #include "base/win/windows_types.h"
  37. #endif
  38. namespace electron {
  39. base::IDMap<api::UtilityProcessWrapper*, base::ProcessId>&
  40. GetAllUtilityProcessWrappers() {
  41. static base::NoDestructor<
  42. base::IDMap<api::UtilityProcessWrapper*, base::ProcessId>>
  43. s_all_utility_process_wrappers;
  44. return *s_all_utility_process_wrappers;
  45. }
  46. namespace api {
  47. gin::WrapperInfo UtilityProcessWrapper::kWrapperInfo = {
  48. gin::kEmbedderNativeGin};
  49. UtilityProcessWrapper::UtilityProcessWrapper(
  50. node::mojom::NodeServiceParamsPtr params,
  51. std::u16string display_name,
  52. std::map<IOHandle, IOType> stdio,
  53. base::EnvironmentMap env_map,
  54. base::FilePath current_working_directory,
  55. bool use_plugin_helper) {
  56. #if BUILDFLAG(IS_WIN)
  57. base::win::ScopedHandle stdout_write(nullptr);
  58. base::win::ScopedHandle stderr_write(nullptr);
  59. #elif BUILDFLAG(IS_POSIX)
  60. base::FileHandleMappingVector fds_to_remap;
  61. #endif
  62. for (const auto& [io_handle, io_type] : stdio) {
  63. if (io_type == IOType::IO_PIPE) {
  64. #if BUILDFLAG(IS_WIN)
  65. HANDLE read = nullptr;
  66. HANDLE write = nullptr;
  67. // Ideally we would create with SECURITY_ATTRIBUTES.bInheritHandles
  68. // set to TRUE so that the write handle can be duplicated into the
  69. // child process for use,
  70. // See
  71. // https://learn.microsoft.com/en-us/windows/win32/procthread/inheritance#inheriting-handles
  72. // for inheritance behavior of child process. But we don't do it here
  73. // since base::Launch already takes of setting the
  74. // inherit attribute when configuring
  75. // `base::LaunchOptions::handles_to_inherit` Refs
  76. // https://source.chromium.org/chromium/chromium/src/+/main:base/process/launch_win.cc;l=303-332
  77. if (!::CreatePipe(&read, &write, nullptr, 0)) {
  78. PLOG(ERROR) << "pipe creation failed";
  79. return;
  80. }
  81. if (io_handle == IOHandle::STDOUT) {
  82. stdout_write.Set(write);
  83. stdout_read_handle_ = read;
  84. stdout_read_fd_ =
  85. _open_osfhandle(reinterpret_cast<intptr_t>(read), _O_RDONLY);
  86. } else if (io_handle == IOHandle::STDERR) {
  87. stderr_write.Set(write);
  88. stderr_read_handle_ = read;
  89. stderr_read_fd_ =
  90. _open_osfhandle(reinterpret_cast<intptr_t>(read), _O_RDONLY);
  91. }
  92. #elif BUILDFLAG(IS_POSIX)
  93. int pipe_fd[2];
  94. if (HANDLE_EINTR(pipe(pipe_fd)) < 0) {
  95. PLOG(ERROR) << "pipe creation failed";
  96. return;
  97. }
  98. if (io_handle == IOHandle::STDOUT) {
  99. fds_to_remap.emplace_back(pipe_fd[1], STDOUT_FILENO);
  100. stdout_read_fd_ = pipe_fd[0];
  101. } else if (io_handle == IOHandle::STDERR) {
  102. fds_to_remap.emplace_back(pipe_fd[1], STDERR_FILENO);
  103. stderr_read_fd_ = pipe_fd[0];
  104. }
  105. #endif
  106. } else if (io_type == IOType::IO_IGNORE) {
  107. #if BUILDFLAG(IS_WIN)
  108. HANDLE handle =
  109. CreateFileW(L"NUL", FILE_GENERIC_WRITE | FILE_READ_ATTRIBUTES,
  110. FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
  111. OPEN_EXISTING, 0, nullptr);
  112. if (handle == INVALID_HANDLE_VALUE) {
  113. PLOG(ERROR) << "Failed to create null handle";
  114. return;
  115. }
  116. if (io_handle == IOHandle::STDOUT) {
  117. stdout_write.Set(handle);
  118. } else if (io_handle == IOHandle::STDERR) {
  119. stderr_write.Set(handle);
  120. }
  121. #elif BUILDFLAG(IS_POSIX)
  122. int devnull = open("/dev/null", O_WRONLY);
  123. if (devnull < 0) {
  124. PLOG(ERROR) << "failed to open /dev/null";
  125. return;
  126. }
  127. if (io_handle == IOHandle::STDOUT) {
  128. fds_to_remap.emplace_back(devnull, STDOUT_FILENO);
  129. } else if (io_handle == IOHandle::STDERR) {
  130. fds_to_remap.emplace_back(devnull, STDERR_FILENO);
  131. }
  132. #endif
  133. }
  134. }
  135. mojo::PendingReceiver<node::mojom::NodeService> receiver =
  136. node_service_remote_.BindNewPipeAndPassReceiver();
  137. content::ServiceProcessHost::Launch(
  138. std::move(receiver),
  139. content::ServiceProcessHost::Options()
  140. .WithDisplayName(display_name.empty()
  141. ? std::u16string(u"Node Utility Process")
  142. : display_name)
  143. .WithExtraCommandLineSwitches(params->exec_args)
  144. .WithCurrentDirectory(current_working_directory)
  145. // Inherit parent process environment when there is no custom
  146. // environment provided by the user.
  147. .WithEnvironment(env_map,
  148. env_map.empty() ? false : true /*clear_environment*/)
  149. #if BUILDFLAG(IS_WIN)
  150. .WithStdoutHandle(std::move(stdout_write))
  151. .WithStderrHandle(std::move(stderr_write))
  152. #elif BUILDFLAG(IS_POSIX)
  153. .WithAdditionalFds(std::move(fds_to_remap))
  154. #endif
  155. #if BUILDFLAG(IS_MAC)
  156. .WithChildFlags(use_plugin_helper
  157. ? content::ChildProcessHost::CHILD_PLUGIN
  158. : content::ChildProcessHost::CHILD_NORMAL)
  159. #endif
  160. .WithProcessCallback(
  161. base::BindOnce(&UtilityProcessWrapper::OnServiceProcessLaunched,
  162. weak_factory_.GetWeakPtr()))
  163. .Pass());
  164. node_service_remote_.set_disconnect_with_reason_handler(
  165. base::BindOnce(&UtilityProcessWrapper::OnServiceProcessDisconnected,
  166. weak_factory_.GetWeakPtr()));
  167. // We use a separate message pipe to support postMessage API
  168. // instead of the existing receiver interface so that we can
  169. // support queuing of messages without having to block other
  170. // interfaces.
  171. blink::MessagePortDescriptorPair pipe;
  172. host_port_ = pipe.TakePort0();
  173. params->port = pipe.TakePort1();
  174. connector_ = std::make_unique<mojo::Connector>(
  175. host_port_.TakeHandleToEntangleWithEmbedder(),
  176. mojo::Connector::SINGLE_THREADED_SEND,
  177. base::SingleThreadTaskRunner::GetCurrentDefault());
  178. connector_->set_incoming_receiver(this);
  179. connector_->set_connection_error_handler(base::BindOnce(
  180. &UtilityProcessWrapper::CloseConnectorPort, weak_factory_.GetWeakPtr()));
  181. node_service_remote_->Initialize(std::move(params));
  182. }
  183. UtilityProcessWrapper::~UtilityProcessWrapper() = default;
  184. void UtilityProcessWrapper::OnServiceProcessLaunched(
  185. const base::Process& process) {
  186. DCHECK(node_service_remote_.is_connected());
  187. pid_ = process.Pid();
  188. GetAllUtilityProcessWrappers().AddWithID(this, pid_);
  189. if (stdout_read_fd_ != -1) {
  190. EmitWithoutEvent("stdout", stdout_read_fd_);
  191. }
  192. if (stderr_read_fd_ != -1) {
  193. EmitWithoutEvent("stderr", stderr_read_fd_);
  194. }
  195. // Emit 'spawn' event
  196. EmitWithoutEvent("spawn");
  197. }
  198. void UtilityProcessWrapper::OnServiceProcessDisconnected(
  199. uint32_t error_code,
  200. const std::string& description) {
  201. if (pid_ != base::kNullProcessId)
  202. GetAllUtilityProcessWrappers().Remove(pid_);
  203. CloseConnectorPort();
  204. // Emit 'exit' event
  205. EmitWithoutEvent("exit", error_code);
  206. Unpin();
  207. }
  208. void UtilityProcessWrapper::CloseConnectorPort() {
  209. if (!connector_closed_ && connector_->is_valid()) {
  210. host_port_.GiveDisentangledHandle(connector_->PassMessagePipe());
  211. connector_ = nullptr;
  212. host_port_.Reset();
  213. connector_closed_ = true;
  214. }
  215. }
  216. void UtilityProcessWrapper::Shutdown(int exit_code) {
  217. if (pid_ != base::kNullProcessId)
  218. GetAllUtilityProcessWrappers().Remove(pid_);
  219. node_service_remote_.reset();
  220. CloseConnectorPort();
  221. // Emit 'exit' event
  222. EmitWithoutEvent("exit", exit_code);
  223. Unpin();
  224. }
  225. void UtilityProcessWrapper::PostMessage(gin::Arguments* args) {
  226. if (!node_service_remote_.is_connected())
  227. return;
  228. blink::TransferableMessage transferable_message;
  229. v8::Local<v8::Value> message_value;
  230. if (args->GetNext(&message_value)) {
  231. if (!electron::SerializeV8Value(args->isolate(), message_value,
  232. &transferable_message)) {
  233. // SerializeV8Value sets an exception.
  234. return;
  235. }
  236. }
  237. v8::Local<v8::Value> transferables;
  238. std::vector<gin::Handle<MessagePort>> wrapped_ports;
  239. if (args->GetNext(&transferables)) {
  240. if (!gin::ConvertFromV8(args->isolate(), transferables, &wrapped_ports)) {
  241. gin_helper::ErrorThrower(args->isolate())
  242. .ThrowTypeError("Invalid value for transfer");
  243. return;
  244. }
  245. }
  246. bool threw_exception = false;
  247. transferable_message.ports = MessagePort::DisentanglePorts(
  248. args->isolate(), wrapped_ports, &threw_exception);
  249. if (threw_exception)
  250. return;
  251. mojo::Message mojo_message = blink::mojom::TransferableMessage::WrapAsMessage(
  252. std::move(transferable_message));
  253. connector_->Accept(&mojo_message);
  254. }
  255. bool UtilityProcessWrapper::Kill() const {
  256. if (pid_ == base::kNullProcessId)
  257. return false;
  258. base::Process process = base::Process::Open(pid_);
  259. bool result = process.Terminate(content::RESULT_CODE_NORMAL_EXIT, false);
  260. // Refs https://bugs.chromium.org/p/chromium/issues/detail?id=818244
  261. // Currently utility process is not sandboxed which
  262. // means Zygote is not used on linux, refs
  263. // content::UtilitySandboxedProcessLauncherDelegate::GetZygote.
  264. // If sandbox feature is enabled for the utility process, then the
  265. // process reap should be signaled through the zygote via
  266. // content::ZygoteCommunication::EnsureProcessTerminated.
  267. base::EnsureProcessTerminated(std::move(process));
  268. return result;
  269. }
  270. v8::Local<v8::Value> UtilityProcessWrapper::GetOSProcessId(
  271. v8::Isolate* isolate) const {
  272. if (pid_ == base::kNullProcessId)
  273. return v8::Undefined(isolate);
  274. return gin::ConvertToV8(isolate, pid_);
  275. }
  276. bool UtilityProcessWrapper::Accept(mojo::Message* mojo_message) {
  277. blink::TransferableMessage message;
  278. if (!blink::mojom::TransferableMessage::DeserializeFromMessage(
  279. std::move(*mojo_message), &message)) {
  280. return false;
  281. }
  282. v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
  283. v8::HandleScope handle_scope(isolate);
  284. v8::Local<v8::Value> message_value =
  285. electron::DeserializeV8Value(isolate, message);
  286. EmitWithoutEvent("message", message_value);
  287. return true;
  288. }
  289. // static
  290. raw_ptr<UtilityProcessWrapper> UtilityProcessWrapper::FromProcessId(
  291. base::ProcessId pid) {
  292. auto* utility_process_wrapper = GetAllUtilityProcessWrappers().Lookup(pid);
  293. return !!utility_process_wrapper ? utility_process_wrapper : nullptr;
  294. }
  295. // static
  296. gin::Handle<UtilityProcessWrapper> UtilityProcessWrapper::Create(
  297. gin::Arguments* args) {
  298. gin_helper::Dictionary dict;
  299. if (!args->GetNext(&dict)) {
  300. args->ThrowTypeError("Options must be an object.");
  301. return gin::Handle<UtilityProcessWrapper>();
  302. }
  303. std::u16string display_name;
  304. bool use_plugin_helper = false;
  305. std::map<IOHandle, IOType> stdio;
  306. base::FilePath current_working_directory;
  307. base::EnvironmentMap env_map;
  308. node::mojom::NodeServiceParamsPtr params =
  309. node::mojom::NodeServiceParams::New();
  310. dict.Get("modulePath", &params->script);
  311. if (dict.Has("args") && !dict.Get("args", &params->args)) {
  312. args->ThrowTypeError("Invalid value for args");
  313. return gin::Handle<UtilityProcessWrapper>();
  314. }
  315. gin_helper::Dictionary opts;
  316. if (dict.Get("options", &opts)) {
  317. if (opts.Has("env") && !opts.Get("env", &env_map)) {
  318. args->ThrowTypeError("Invalid value for env");
  319. return gin::Handle<UtilityProcessWrapper>();
  320. }
  321. if (opts.Has("execArgv") && !opts.Get("execArgv", &params->exec_args)) {
  322. args->ThrowTypeError("Invalid value for execArgv");
  323. return gin::Handle<UtilityProcessWrapper>();
  324. }
  325. opts.Get("serviceName", &display_name);
  326. opts.Get("cwd", &current_working_directory);
  327. std::vector<std::string> stdio_arr{"ignore", "inherit", "inherit"};
  328. opts.Get("stdio", &stdio_arr);
  329. for (size_t i = 0; i < 3; i++) {
  330. IOType type;
  331. if (stdio_arr[i] == "ignore")
  332. type = IOType::IO_IGNORE;
  333. else if (stdio_arr[i] == "inherit")
  334. type = IOType::IO_INHERIT;
  335. else if (stdio_arr[i] == "pipe")
  336. type = IOType::IO_PIPE;
  337. stdio.emplace(static_cast<IOHandle>(i), type);
  338. }
  339. #if BUILDFLAG(IS_MAC)
  340. opts.Get("allowLoadingUnsignedLibraries", &use_plugin_helper);
  341. #endif
  342. }
  343. auto handle = gin::CreateHandle(
  344. args->isolate(),
  345. new UtilityProcessWrapper(std::move(params), display_name,
  346. std::move(stdio), env_map,
  347. current_working_directory, use_plugin_helper));
  348. handle->Pin(args->isolate());
  349. return handle;
  350. }
  351. // static
  352. gin::ObjectTemplateBuilder UtilityProcessWrapper::GetObjectTemplateBuilder(
  353. v8::Isolate* isolate) {
  354. return gin_helper::EventEmitterMixin<
  355. UtilityProcessWrapper>::GetObjectTemplateBuilder(isolate)
  356. .SetMethod("postMessage", &UtilityProcessWrapper::PostMessage)
  357. .SetMethod("kill", &UtilityProcessWrapper::Kill)
  358. .SetProperty("pid", &UtilityProcessWrapper::GetOSProcessId);
  359. }
  360. const char* UtilityProcessWrapper::GetTypeName() {
  361. return "UtilityProcessWrapper";
  362. }
  363. } // namespace api
  364. } // namespace electron
  365. namespace {
  366. void Initialize(v8::Local<v8::Object> exports,
  367. v8::Local<v8::Value> unused,
  368. v8::Local<v8::Context> context,
  369. void* priv) {
  370. v8::Isolate* isolate = context->GetIsolate();
  371. gin_helper::Dictionary dict(isolate, exports);
  372. dict.SetMethod("_fork", &electron::api::UtilityProcessWrapper::Create);
  373. }
  374. } // namespace
  375. NODE_LINKED_BINDING_CONTEXT_AWARE(electron_browser_utility_process, Initialize)