Repository: electron/electron
Pull request: fix: correct parsing of second-instance additionalData

### Description

Fixes two parsing issues in the `app.requestSingleInstanceLock` / `second-instance` data path:

**POSIX** (`process_singleton_posix.cc`): The socket message parser did not check the return value of `base::StringToSizeT` when reading the argv count and additional-data length fields, and did not validate those counts against the actual number of tokens received. A malformed message from a second instance could produce incorrect slicing.

**Windows** (`chrome_process_finder.cc` / `process_singleton_win.cc`): `additionalData` bytes were `reinterpret_cast` directly into the null-delimited `wchar_t` buffer used for `WM_COPYDATA`. V8's serialization format regularly contains `0x00` bytes (varint-encoded zero lengths, etc.), and any aligned `0x0000` pair terminated the field early on the receive side. The result: complex `additionalData` objects could arrive truncated and fail to deserialize, while simple ones happened to work. Now base64-encoded before embedding.

### Checklist

- [x] PR description included
- [x] `npm test` passes (`app.requestSingleInstanceLock` suite — 11/11)
- [x] Follows the PR guidelines

### Release Notes

Notes: Fixed an issue where `additionalData` passed to `app.requestSingleInstanceLock` on Windows could be truncated or fail to deserialize in the primary instance's `second-instance` event.

Changed files:
filename: patches/chromium/feat_add_data_parameter_to_processsingleton.patch
patch: @@ -65,7 +65,7 @@ index f076d0f783e2c0f6b5444002f756001adf2729bd..a03d99f929e2d354cdba969567d78156
  #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..0c423610fc2c5514693d33b527088cf839a404f2 100644
+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
@@ -106,22 +106,41 @@ index 5a88dfda5eb2c4bf5b547a76eef81b530f3ea96e..0c423610fc2c5514693d33b527088cf8
    const size_t kMinMessageLength = kStartToken.length() + 4;
    if (bytes_read_ < kMinMessageLength) {
      buf_[bytes_read_] = 0;
-@@ -745,10 +751,26 @@ void ProcessSingleton::LinuxWatcher::SocketReader::
+@@ -745,10 +751,45 @@ void ProcessSingleton::LinuxWatcher::SocketReader::
    tokens.erase(tokens.begin());
    tokens.erase(tokens.begin());
  
 +  size_t num_args;
-+  base::StringToSizeT(tokens[0], &num_args);
-+  std::vector<std::string> command_line(tokens.begin() + 1, tokens.begin() + 1 + 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;
-+  if (tokens.size() >= 3 + num_args) {
++  // 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;
-+    base::StringToSizeT(tokens[1 + num_args], &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));
-+    const auto adspan = base::as_byte_span(remaining_args).first(additional_data_size);
++    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());
 +  }
 +
@@ -134,7 +153,7 @@ index 5a88dfda5eb2c4bf5b547a76eef81b530f3ea96e..0c423610fc2c5514693d33b527088cf8
    fd_watch_controller_.reset();
  
    // LinuxWatcher::HandleMessage() is in charge of destroying this SocketReader
-@@ -777,8 +799,10 @@ void ProcessSingleton::LinuxWatcher::SocketReader::FinishWithACK(
+@@ -777,8 +818,10 @@ void ProcessSingleton::LinuxWatcher::SocketReader::FinishWithACK(
  //
  ProcessSingleton::ProcessSingleton(
      const base::FilePath& user_data_dir,
@@ -145,7 +164,7 @@ index 5a88dfda5eb2c4bf5b547a76eef81b530f3ea96e..0c423610fc2c5514693d33b527088cf8
        current_pid_(base::GetCurrentProcId()) {
    socket_path_ = user_data_dir.Append(chrome::kSingletonSocketFilename);
    lock_path_ = user_data_dir.Append(chrome::kSingletonLockFilename);
-@@ -899,7 +923,8 @@ ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcessWithTimeout(
+@@ -899,7 +942,8 @@ ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcessWithTimeout(
               sizeof(socket_timeout));
  
    // Found another process, prepare our command line
@@ -155,7 +174,7 @@ index 5a88dfda5eb2c4bf5b547a76eef81b530f3ea96e..0c423610fc2c5514693d33b527088cf8
    std::string to_send(kStartToken);
    to_send.push_back(kTokenDelimiter);
  
-@@ -909,11 +934,21 @@ ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcessWithTimeout(
+@@ -909,11 +953,21 @@ ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcessWithTimeout(
    to_send.append(current_dir.value());
  
    const std::vector<std::string>& argv = cmd_line.argv();
@@ -178,10 +197,18 @@ index 5a88dfda5eb2c4bf5b547a76eef81b530f3ea96e..0c423610fc2c5514693d33b527088cf8
    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..d93c7e8487ab1a2bbb5f56f2ca44868f947e6bfc 100644
+index ae659d84a5ae2f2e87ce288477506575f8d86839..274887d62ff8d008bb86815a11205fcaa5f2c2ff 100644
 --- a/chrome/browser/process_singleton_win.cc
 +++ b/chrome/browser/process_singleton_win.cc
-@@ -81,10 +81,12 @@ BOOL CALLBACK BrowserWindowEnumeration(HWND window, LPARAM param) {
+@@ -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,
@@ -196,7 +223,7 @@ index ae659d84a5ae2f2e87ce288477506575f8d86839..d93c7e8487ab1a2bbb5f56f2ca44868f
    static const int min_message_size = 7;
    if (cds->cbData < min_message_size * sizeof(wchar_t) ||
        cds->cbData % sizeof(wchar_t) != 0) {
-@@ -134,6 +136,23 @@ bool ParseCommandLine(const COPYDATASTRUCT* cds,
+@@ -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);
@@ -209,18 +236,20 @@ index ae659d84a5ae2f2e87ce288477506575f8d86839..d93c7e8487ab1a2bbb5f56f2ca44868f
 +      return true;
 +    }
 +
-+    // Get the actual additional data.
-+    const std::wstring additional_data =
-+        msg.substr(third_null + 1, fourth_null - third_null);
-+    base::span<const uint8_t> additional_data_bytes =
-+        base::as_byte_span(additional_data);
-+    *parsed_additional_data = std::vector<uint8_t>(
-+        additional_data_bytes.begin(), additional_data_bytes.end());
++    // 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 +174,14 @@ bool ProcessLaunchNotification(
+@@ -155,13 +177,14 @@ bool ProcessLaunchNotification(
  
    base::CommandLine parsed_command_line(base::CommandLine::NO_PROGRAM);
    base::FilePath current_directory;
@@ -238,7 +267,7 @@ index ae659d84a5ae2f2e87ce288477506575f8d86839..d93c7e8487ab1a2bbb5f56f2ca44868f
    return true;
  }
  
-@@ -265,9 +285,11 @@ bool ProcessSingleton::EscapeVirtualization(
+@@ -265,9 +288,11 @@ bool ProcessSingleton::EscapeVirtualization(
  ProcessSingleton::ProcessSingleton(
      const std::string& program_name,
      const base::FilePath& user_data_dir,
@@ -250,7 +279,7 @@ index ae659d84a5ae2f2e87ce288477506575f8d86839..d93c7e8487ab1a2bbb5f56f2ca44868f
        program_name_(program_name),
        is_app_sandboxed_(is_app_sandboxed),
        is_virtualized_(false),
-@@ -294,7 +316,7 @@ ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcess() {
+@@ -294,7 +319,7 @@ ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcess() {
      return PROCESS_NONE;
    }
  
@@ -260,10 +289,18 @@ index ae659d84a5ae2f2e87ce288477506575f8d86839..d93c7e8487ab1a2bbb5f56f2ca44868f
        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..5a1dde19a4bc2bf728eba4c738f831c3e5b73942 100644
+index 594f3bc08a4385c177fb488123cef79448e94850..28e5a18a19718b2e748ada6882341413a1ab0705 100644
 --- a/chrome/browser/win/chrome_process_finder.cc
 +++ b/chrome/browser/win/chrome_process_finder.cc
-@@ -39,7 +39,9 @@ HWND FindRunningChromeWindow(const base::FilePath& user_data_dir) {
+@@ -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());
  }
  
@@ -274,7 +311,7 @@ index 594f3bc08a4385c177fb488123cef79448e94850..5a1dde19a4bc2bf728eba4c738f831c3
    TRACE_EVENT0("startup", "AttemptToNotifyRunningChrome");
  
    DCHECK(remote_window);
-@@ -70,12 +72,24 @@ NotifyChromeResult AttemptToNotifyRunningChrome(HWND 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.
@@ -286,14 +323,12 @@ index 594f3bc08a4385c177fb488123cef79448e94850..5a1dde19a4bc2bf728eba4c738f831c3
         std::wstring_view{L"\0", 1}, new_command_line.GetCommandLineString(),
         std::wstring_view{L"\0", 1}});
  
-+  size_t additional_data_size = additional_data.size_bytes();
-+  if (additional_data_size) {
-+    size_t padded_size = additional_data_size / sizeof(wchar_t);
-+    if (additional_data_size % sizeof(wchar_t) != 0) {
-+      padded_size++;
-+    }
-+    to_send.append(reinterpret_cast<const wchar_t*>(additional_data.data()),
-+                   padded_size);
++  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.
 +  }
 +