mirror of
https://github.com/electron/electron.git
synced 2026-04-10 03:01:51 -04:00
- POSIX: validate StringToSizeT result and token count when splitting the socket message into argv and additionalData; previously a malformed message could produce incorrect slicing. - Windows: base64-encode additionalData before embedding in the null-delimited wchar_t buffer. The prior reinterpret_cast approach dropped everything after the first aligned 0x0000 in the serialized payload, so complex objects could arrive truncated.
361 lines
16 KiB
Diff
361 lines
16 KiB
Diff
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
From: Raymond Zhao <raymondzhao@microsoft.com>
|
|
Date: Tue, 7 Sep 2021 14:54:25 -0700
|
|
Subject: feat: Add data parameter to ProcessSingleton
|
|
|
|
This patch adds an additional_data parameter to the constructor of
|
|
ProcessSingleton, so that the second instance can send additional
|
|
data over to the first instance while requesting the ProcessSingleton
|
|
lock.
|
|
|
|
On the Electron side, we then expose an extra parameter to the
|
|
app.requestSingleInstanceLock API so that users can pass in a JSON
|
|
object for the second instance to send to the first instance.
|
|
|
|
diff --git a/chrome/browser/process_singleton.h b/chrome/browser/process_singleton.h
|
|
index f076d0f783e2c0f6b5444002f756001adf2729bd..a03d99f929e2d354cdba969567d781561656261d 100644
|
|
--- a/chrome/browser/process_singleton.h
|
|
+++ b/chrome/browser/process_singleton.h
|
|
@@ -18,7 +18,8 @@
|
|
#include "base/functional/callback.h"
|
|
#include "base/memory/scoped_refptr.h"
|
|
#include "base/process/process.h"
|
|
-
|
|
+#include "base/containers/span.h"
|
|
+#include "base/memory/raw_span.h"
|
|
#if BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_ANDROID)
|
|
#include "base/files/scoped_temp_dir.h"
|
|
#endif
|
|
@@ -99,21 +100,24 @@ class ProcessSingleton {
|
|
// should handle it (i.e., because the current process is shutting down).
|
|
using NotificationCallback =
|
|
base::RepeatingCallback<bool(base::CommandLine command_line,
|
|
- const base::FilePath& current_directory)>;
|
|
+ const base::FilePath& current_directory,
|
|
+ const std::vector<uint8_t> additional_data)>;
|
|
|
|
#if BUILDFLAG(IS_WIN)
|
|
ProcessSingleton(const std::string& program_name,
|
|
const base::FilePath& user_data_dir,
|
|
+ const base::raw_span<const uint8_t> additional_data,
|
|
bool is_sandboxed,
|
|
const NotificationCallback& notification_callback);
|
|
#else
|
|
ProcessSingleton(const base::FilePath& user_data_dir,
|
|
+ const base::raw_span<const uint8_t> additional_data,
|
|
const NotificationCallback& notification_callback);
|
|
+#endif
|
|
|
|
ProcessSingleton(const ProcessSingleton&) = delete;
|
|
ProcessSingleton& operator=(const ProcessSingleton&) = delete;
|
|
|
|
-#endif
|
|
~ProcessSingleton();
|
|
|
|
// Notify another process, if available. Otherwise sets ourselves as the
|
|
@@ -177,7 +181,10 @@ class ProcessSingleton {
|
|
#endif
|
|
|
|
private:
|
|
+ // A callback to run when the first instance receives data from the second.
|
|
NotificationCallback notification_callback_; // Handler for notifications.
|
|
+ // Custom data to pass to the other instance during notify.
|
|
+ base::raw_span<const uint8_t> additional_data_;
|
|
|
|
#if BUILDFLAG(IS_WIN)
|
|
bool EscapeVirtualization(const base::FilePath& user_data_dir);
|
|
diff --git a/chrome/browser/process_singleton_posix.cc b/chrome/browser/process_singleton_posix.cc
|
|
index 5a88dfda5eb2c4bf5b547a76eef81b530f3ea96e..1204affca14f73d84b57c1b3092079464a4d5430 100644
|
|
--- a/chrome/browser/process_singleton_posix.cc
|
|
+++ b/chrome/browser/process_singleton_posix.cc
|
|
@@ -619,6 +619,7 @@ class ProcessSingleton::LinuxWatcher
|
|
// |reader| is for sending back ACK message.
|
|
void HandleMessage(const std::string& current_dir,
|
|
const std::vector<std::string>& argv,
|
|
+ const std::vector<uint8_t> additional_data,
|
|
SocketReader* reader);
|
|
|
|
// Called when the ProcessSingleton that owns this class is about to be
|
|
@@ -678,13 +679,17 @@ void ProcessSingleton::LinuxWatcher::StartListening(int socket) {
|
|
}
|
|
|
|
void ProcessSingleton::LinuxWatcher::HandleMessage(
|
|
- const std::string& current_dir, const std::vector<std::string>& argv,
|
|
+ const std::string& current_dir,
|
|
+ const std::vector<std::string>& argv,
|
|
+ const std::vector<uint8_t> additional_data,
|
|
SocketReader* reader) {
|
|
DCHECK(ui_task_runner_->BelongsToCurrentThread());
|
|
DCHECK(reader);
|
|
|
|
if (parent_ && parent_->notification_callback_.Run(
|
|
- base::CommandLine(argv), base::FilePath(current_dir))) {
|
|
+ base::CommandLine(argv),
|
|
+ base::FilePath(current_dir),
|
|
+ std::move(additional_data))) {
|
|
// Send back "ACK" message to prevent the client process from starting up.
|
|
reader->FinishWithACK(kACKToken);
|
|
} else {
|
|
@@ -714,7 +719,8 @@ void ProcessSingleton::LinuxWatcher::SocketReader::
|
|
bytes_read_ += ReadFromSocketWithTimeout(
|
|
fd_, base::span(buf_).subspan(bytes_read_), base::Seconds(0));
|
|
|
|
- // Validate the message. The shortest message is kStartToken\0x\0x
|
|
+ // Validate the message. The shortest message kStartToken\0\00
|
|
+ // The shortest message with additional data is kStartToken\0\00\00\0.
|
|
const size_t kMinMessageLength = kStartToken.length() + 4;
|
|
if (bytes_read_ < kMinMessageLength) {
|
|
buf_[bytes_read_] = 0;
|
|
@@ -745,10 +751,45 @@ void ProcessSingleton::LinuxWatcher::SocketReader::
|
|
tokens.erase(tokens.begin());
|
|
tokens.erase(tokens.begin());
|
|
|
|
+ size_t num_args;
|
|
+ if (!base::StringToSizeT(tokens[0], &num_args) ||
|
|
+ num_args > tokens.size() - 1) {
|
|
+ LOG(ERROR) << "Invalid num_args in socket message";
|
|
+ CleanupAndDeleteSelf();
|
|
+ return;
|
|
+ }
|
|
+ std::vector<std::string> command_line(tokens.begin() + 1,
|
|
+ tokens.begin() + 1 + num_args);
|
|
+
|
|
+ std::vector<uint8_t> additional_data;
|
|
+ // After consuming [num_args, argv...], two more tokens are needed for
|
|
+ // additional data: [size, payload]. Subtract to avoid overflow when
|
|
+ // num_args is large.
|
|
+ if (tokens.size() - 1 - num_args >= 2) {
|
|
+ size_t additional_data_size;
|
|
+ if (!base::StringToSizeT(tokens[1 + num_args], &additional_data_size)) {
|
|
+ LOG(ERROR) << "Invalid additional_data_size in socket message";
|
|
+ CleanupAndDeleteSelf();
|
|
+ return;
|
|
+ }
|
|
+ std::string remaining_args = base::JoinString(
|
|
+ base::span(tokens).subspan(2 + num_args),
|
|
+ std::string(1, kTokenDelimiter));
|
|
+ if (additional_data_size > remaining_args.size()) {
|
|
+ LOG(ERROR) << "additional_data_size exceeds payload length";
|
|
+ CleanupAndDeleteSelf();
|
|
+ return;
|
|
+ }
|
|
+ const auto adspan =
|
|
+ base::as_byte_span(remaining_args).first(additional_data_size);
|
|
+ additional_data.assign(adspan.begin(), adspan.end());
|
|
+ }
|
|
+
|
|
// Return to the UI thread to handle opening a new browser tab.
|
|
ui_task_runner_->PostTask(
|
|
FROM_HERE, base::BindOnce(&ProcessSingleton::LinuxWatcher::HandleMessage,
|
|
- parent_, current_dir, tokens, this));
|
|
+ parent_, current_dir, command_line,
|
|
+ std::move(additional_data), this));
|
|
fd_watch_controller_.reset();
|
|
|
|
// LinuxWatcher::HandleMessage() is in charge of destroying this SocketReader
|
|
@@ -777,8 +818,10 @@ void ProcessSingleton::LinuxWatcher::SocketReader::FinishWithACK(
|
|
//
|
|
ProcessSingleton::ProcessSingleton(
|
|
const base::FilePath& user_data_dir,
|
|
+ const base::raw_span<const uint8_t> additional_data,
|
|
const NotificationCallback& notification_callback)
|
|
: notification_callback_(notification_callback),
|
|
+ additional_data_(additional_data),
|
|
current_pid_(base::GetCurrentProcId()) {
|
|
socket_path_ = user_data_dir.Append(chrome::kSingletonSocketFilename);
|
|
lock_path_ = user_data_dir.Append(chrome::kSingletonLockFilename);
|
|
@@ -899,7 +942,8 @@ ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcessWithTimeout(
|
|
sizeof(socket_timeout));
|
|
|
|
// Found another process, prepare our command line
|
|
- // format is "START\0<current dir>\0<argv[0]>\0...\0<argv[n]>".
|
|
+ // format is "START\0<current-dir>\0<n-args>\0<argv[0]>\0...\0<argv[n]>
|
|
+ // \0<additional-data-length>\0<additional-data>".
|
|
std::string to_send(kStartToken);
|
|
to_send.push_back(kTokenDelimiter);
|
|
|
|
@@ -909,11 +953,21 @@ ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcessWithTimeout(
|
|
to_send.append(current_dir.value());
|
|
|
|
const std::vector<std::string>& argv = cmd_line.argv();
|
|
+ to_send.push_back(kTokenDelimiter);
|
|
+ to_send.append(base::NumberToString(argv.size()));
|
|
for (auto it = argv.begin(); it != argv.end(); ++it) {
|
|
to_send.push_back(kTokenDelimiter);
|
|
to_send.append(*it);
|
|
}
|
|
|
|
+ size_t data_to_send_size = additional_data_.size_bytes();
|
|
+ if (data_to_send_size) {
|
|
+ to_send.push_back(kTokenDelimiter);
|
|
+ to_send.append(base::NumberToString(data_to_send_size));
|
|
+ to_send.push_back(kTokenDelimiter);
|
|
+ to_send.append(reinterpret_cast<const char*>(additional_data_.data()), data_to_send_size);
|
|
+ }
|
|
+
|
|
// Send the message
|
|
if (!WriteToSocket(socket.fd(), to_send)) {
|
|
// Try to kill the other process, because it might have been dead.
|
|
diff --git a/chrome/browser/process_singleton_win.cc b/chrome/browser/process_singleton_win.cc
|
|
index ae659d84a5ae2f2e87ce288477506575f8d86839..274887d62ff8d008bb86815a11205fcaa5f2c2ff 100644
|
|
--- a/chrome/browser/process_singleton_win.cc
|
|
+++ b/chrome/browser/process_singleton_win.cc
|
|
@@ -9,6 +9,7 @@
|
|
#include <shellapi.h>
|
|
#include <stddef.h>
|
|
|
|
+#include "base/base64.h"
|
|
#include "base/base_paths.h"
|
|
#include "base/command_line.h"
|
|
#include "base/files/file_path.h"
|
|
@@ -81,10 +82,12 @@ BOOL CALLBACK BrowserWindowEnumeration(HWND window, LPARAM param) {
|
|
|
|
bool ParseCommandLine(const COPYDATASTRUCT* cds,
|
|
base::CommandLine* parsed_command_line,
|
|
- base::FilePath* current_directory) {
|
|
+ base::FilePath* current_directory,
|
|
+ std::vector<uint8_t>* parsed_additional_data) {
|
|
// We should have enough room for the shortest command (min_message_size)
|
|
// and also be a multiple of wchar_t bytes. The shortest command
|
|
- // possible is L"START\0\0" (empty current directory and command line).
|
|
+ // possible is L"START\0\0" (empty command line, current directory,
|
|
+ // and additional data).
|
|
static const int min_message_size = 7;
|
|
if (cds->cbData < min_message_size * sizeof(wchar_t) ||
|
|
cds->cbData % sizeof(wchar_t) != 0) {
|
|
@@ -134,6 +137,25 @@ bool ParseCommandLine(const COPYDATASTRUCT* cds,
|
|
const std::wstring cmd_line =
|
|
msg.substr(second_null + 1, third_null - second_null);
|
|
*parsed_command_line = base::CommandLine::FromString(cmd_line);
|
|
+
|
|
+ const std::wstring::size_type fourth_null =
|
|
+ msg.find_first_of(L'\0', third_null + 1);
|
|
+ if (fourth_null == std::wstring::npos ||
|
|
+ fourth_null == msg.length()) {
|
|
+ // No additional data was provided.
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ // Get the actual additional data. It is base64-encoded so it can
|
|
+ // safely traverse the null-delimited wchar_t buffer.
|
|
+ const std::wstring encoded_w =
|
|
+ msg.substr(third_null + 1, fourth_null - third_null - 1);
|
|
+ std::string encoded = base::WideToASCII(encoded_w);
|
|
+ std::optional<std::vector<uint8_t>> decoded = base::Base64Decode(encoded);
|
|
+ if (decoded) {
|
|
+ *parsed_additional_data = std::move(*decoded);
|
|
+ }
|
|
+
|
|
return true;
|
|
}
|
|
return false;
|
|
@@ -155,13 +177,14 @@ bool ProcessLaunchNotification(
|
|
|
|
base::CommandLine parsed_command_line(base::CommandLine::NO_PROGRAM);
|
|
base::FilePath current_directory;
|
|
- if (!ParseCommandLine(cds, &parsed_command_line, ¤t_directory)) {
|
|
+ std::vector<uint8_t> additional_data;
|
|
+ if (!ParseCommandLine(cds, &parsed_command_line, ¤t_directory, &additional_data)) {
|
|
*result = TRUE;
|
|
return true;
|
|
}
|
|
|
|
- *result = notification_callback.Run(parsed_command_line, current_directory) ?
|
|
- TRUE : FALSE;
|
|
+ *result = notification_callback.Run(parsed_command_line,
|
|
+ current_directory, std::move(additional_data)) ? TRUE : FALSE;
|
|
return true;
|
|
}
|
|
|
|
@@ -265,9 +288,11 @@ bool ProcessSingleton::EscapeVirtualization(
|
|
ProcessSingleton::ProcessSingleton(
|
|
const std::string& program_name,
|
|
const base::FilePath& user_data_dir,
|
|
+ const base::raw_span<const uint8_t> additional_data,
|
|
bool is_app_sandboxed,
|
|
const NotificationCallback& notification_callback)
|
|
: notification_callback_(notification_callback),
|
|
+ additional_data_(additional_data),
|
|
program_name_(program_name),
|
|
is_app_sandboxed_(is_app_sandboxed),
|
|
is_virtualized_(false),
|
|
@@ -294,7 +319,7 @@ ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcess() {
|
|
return PROCESS_NONE;
|
|
}
|
|
|
|
- switch (AttemptToNotifyRunningChrome(remote_window_)) {
|
|
+ switch (AttemptToNotifyRunningChrome(remote_window_, additional_data_)) {
|
|
case NotifyChromeResult::kSuccess:
|
|
return PROCESS_NOTIFIED;
|
|
case NotifyChromeResult::kFailed:
|
|
diff --git a/chrome/browser/win/chrome_process_finder.cc b/chrome/browser/win/chrome_process_finder.cc
|
|
index 594f3bc08a4385c177fb488123cef79448e94850..28e5a18a19718b2e748ada6882341413a1ab0705 100644
|
|
--- a/chrome/browser/win/chrome_process_finder.cc
|
|
+++ b/chrome/browser/win/chrome_process_finder.cc
|
|
@@ -11,6 +11,7 @@
|
|
#include <string>
|
|
#include <string_view>
|
|
|
|
+#include "base/base64.h"
|
|
#include "base/check.h"
|
|
#include "base/command_line.h"
|
|
#include "base/files/file_path.h"
|
|
@@ -39,7 +40,9 @@ HWND FindRunningChromeWindow(const base::FilePath& user_data_dir) {
|
|
return base::win::MessageWindow::FindWindow(user_data_dir.value());
|
|
}
|
|
|
|
-NotifyChromeResult AttemptToNotifyRunningChrome(HWND remote_window) {
|
|
+NotifyChromeResult AttemptToNotifyRunningChrome(
|
|
+ HWND remote_window,
|
|
+ const base::raw_span<const uint8_t> additional_data) {
|
|
TRACE_EVENT0("startup", "AttemptToNotifyRunningChrome");
|
|
|
|
DCHECK(remote_window);
|
|
@@ -70,12 +73,22 @@ NotifyChromeResult AttemptToNotifyRunningChrome(HWND remote_window) {
|
|
new_command_line.AppendSwitch(switches::kSourceAppId);
|
|
}
|
|
// Send the command line to the remote chrome window.
|
|
- // Format is "START\0<<<current directory>>>\0<<<commandline>>>".
|
|
+ // Format is
|
|
+ // "START\0<current-directory>\0<command-line>\0<additional-data>".
|
|
std::wstring to_send = base::StrCat(
|
|
{std::wstring_view{L"START\0", 6}, cur_dir.value(),
|
|
std::wstring_view{L"\0", 1}, new_command_line.GetCommandLineString(),
|
|
std::wstring_view{L"\0", 1}});
|
|
|
|
+ if (!additional_data.empty()) {
|
|
+ // Base64-encode so the payload survives the null-delimited wchar_t
|
|
+ // framing; raw serialized bytes can contain 0x0000 sequences which
|
|
+ // would otherwise terminate the field early.
|
|
+ std::string encoded = base::Base64Encode(additional_data);
|
|
+ to_send.append(base::ASCIIToWide(encoded));
|
|
+ to_send.append(L"\0", 1); // Null separator.
|
|
+ }
|
|
+
|
|
// Allow the current running browser window to make itself the foreground
|
|
// window (otherwise it will just flash in the taskbar).
|
|
::AllowSetForegroundWindow(process_id);
|
|
diff --git a/chrome/browser/win/chrome_process_finder.h b/chrome/browser/win/chrome_process_finder.h
|
|
index 62e232f41189a557534e0b01d912469b2ca26148..3a4dfb027cdc690ee7561fc8b632f35cb6b8f4be 100644
|
|
--- a/chrome/browser/win/chrome_process_finder.h
|
|
+++ b/chrome/browser/win/chrome_process_finder.h
|
|
@@ -7,6 +7,7 @@
|
|
|
|
#include <windows.h>
|
|
|
|
+#include "base/memory/raw_span.h"
|
|
#include "base/time/time.h"
|
|
|
|
namespace base {
|
|
@@ -25,7 +26,9 @@ HWND FindRunningChromeWindow(const base::FilePath& user_data_dir);
|
|
// Attempts to send the current command line to an already running instance of
|
|
// Chrome via a WM_COPYDATA message.
|
|
// Returns true if a running Chrome is found and successfully notified.
|
|
-NotifyChromeResult AttemptToNotifyRunningChrome(HWND remote_window);
|
|
+NotifyChromeResult AttemptToNotifyRunningChrome(
|
|
+ HWND remote_window,
|
|
+ const base::raw_span<const uint8_t> additional_data);
|
|
|
|
// Changes the notification timeout to |new_timeout|, returns the old timeout.
|
|
base::TimeDelta SetNotificationTimeoutForTesting(base::TimeDelta new_timeout);
|