diff --git a/BUILD.gn b/BUILD.gn index 6d932a1ebb9f..1c799d73a125 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -518,6 +518,10 @@ source_set("electron_lib") { "//v8:v8_libplatform", ] + if (v8_use_external_startup_data && use_v8_context_snapshot) { + deps += [ ":mksnapshot_checksum_gen" ] + } + public_deps = [ "//base", "//base:i18n", @@ -772,6 +776,14 @@ source_set("electron_lib") { } } +action("mksnapshot_checksum_gen") { + script = "build/checksum_header.py" + outputs = [ "$target_gen_dir/snapshot_checksum.h" ] + inputs = [ "$root_out_dir/$v8_context_snapshot_filename" ] + args = rebase_path(inputs) + rebase_path(outputs) + deps = [ "//tools/v8_context_snapshot" ] +} + electron_paks("packed_resources") { if (is_mac) { output_dir = "$root_gen_dir/electron_repack" diff --git a/build/checksum_header.py b/build/checksum_header.py new file mode 100644 index 000000000000..9f98c510ee8e --- /dev/null +++ b/build/checksum_header.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 + +import os +import sys +import hashlib + +dir_path = os.path.dirname(os.path.realpath(__file__)) + +TEMPLATE_H = """ +#ifndef ELECTRON_SNAPSHOT_CHECKSUM_H_ +#define ELECTRON_SNAPSHOT_CHECKSUM_H_ + +namespace electron::snapshot_checksum { + +const std::string kChecksum = "{checksum}"; + +} // namespace electron::snapshot_checksum + +#endif // ELECTRON_SNAPSHOT_CHECKSUM_H_ +""" + +def calculate_sha256(filepath): + sha256_hash = hashlib.sha256() + with open(filepath, "rb") as f: + for byte_block in iter(lambda: f.read(4096), b""): + sha256_hash.update(byte_block) + return sha256_hash.hexdigest() + +input_file = sys.argv[1] +output_file = sys.argv[2] + +checksum = calculate_sha256(input_file) + +checksum_h = TEMPLATE_H.replace("{checksum}", checksum) + +with open(output_file, 'w') as f: + f.write(checksum_h) diff --git a/patches/chromium/.patches b/patches/chromium/.patches index bb43b0165cf6..264539be3965 100644 --- a/patches/chromium/.patches +++ b/patches/chromium/.patches @@ -137,3 +137,4 @@ build_set_mac_sdk_minimum_to_10.patch fix_add_macos_memory_query_fallback_to_avoid_crash.patch fix_resolve_dynamic_background_material_update_issue_on_windows_11.patch chore_restore_some_deprecated_wrapper_utility_in_gin.patch +feat_add_support_for_embedder_snapshot_validation.patch diff --git a/patches/chromium/feat_add_support_for_embedder_snapshot_validation.patch b/patches/chromium/feat_add_support_for_embedder_snapshot_validation.patch new file mode 100644 index 000000000000..e05de08f594b --- /dev/null +++ b/patches/chromium/feat_add_support_for_embedder_snapshot_validation.patch @@ -0,0 +1,67 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Samuel Attard +Date: Fri, 15 Aug 2025 14:58:12 -0700 +Subject: feat: add support for embedder snapshot validation + +IsValid is not exposed despite being commented as for embedders, this exposes something that works for us. + +diff --git a/gin/v8_initializer.cc b/gin/v8_initializer.cc +index f83461b3a1aff229164358e53847065ddae5ddf1..12faa71c2e14d2f13ab612526f349e9b027e5d3d 100644 +--- a/gin/v8_initializer.cc ++++ b/gin/v8_initializer.cc +@@ -76,11 +76,23 @@ bool GenerateEntropy(unsigned char* buffer, size_t amount) { + return true; + } + ++static base::RepeatingCallback& SnapshotValidator() { ++ static base::NoDestructor> ++ validator( ++ base::BindRepeating([](v8::StartupData* data) -> void { /* empty */ })); ++ return *validator; ++} ++ ++void SetV8SnapshotValidatorInner(const base::RepeatingCallback& callback) { ++ SnapshotValidator() = std::move(callback); ++} ++ + void GetMappedFileData(base::MemoryMappedFile* mapped_file, + v8::StartupData* data) { + if (mapped_file) { + data->data = reinterpret_cast(mapped_file->data()); + data->raw_size = static_cast(mapped_file->length()); ++ SnapshotValidator().Run(data); + } else { + data->data = nullptr; + data->raw_size = 0; +@@ -225,6 +237,10 @@ constexpr std::string_view kV8FlagParam = "V8FlagParam"; + + } // namespace + ++void SetV8SnapshotValidator(const base::RepeatingCallback& callback) { ++ SetV8SnapshotValidatorInner(std::move(callback)); ++} ++ + class V8FeatureVisitor : public base::FeatureVisitor { + public: + void Visit(const std::string& feature_name, +diff --git a/gin/v8_initializer.h b/gin/v8_initializer.h +index 6f7382cd600cd34916d9382878aee4b469dae5d0..61ed0f46437d2e1abbcebcfb64df06d17c8d9139 100644 +--- a/gin/v8_initializer.h ++++ b/gin/v8_initializer.h +@@ -11,6 +11,7 @@ + + #include "base/files/file.h" + #include "base/files/memory_mapped_file.h" ++#include "base/functional/callback.h" + #include "build/build_config.h" + #include "gin/array_buffer.h" + #include "gin/gin_export.h" +@@ -28,6 +29,8 @@ class StartupData; + + namespace gin { + ++void SetV8SnapshotValidator(const base::RepeatingCallback& callback); ++ + class GIN_EXPORT V8Initializer { + public: + // This should be called by IsolateHolder::Initialize(). diff --git a/shell/app/electron_main_delegate.cc b/shell/app/electron_main_delegate.cc index a5116fc8969f..e4ba443e43b4 100644 --- a/shell/app/electron_main_delegate.cc +++ b/shell/app/electron_main_delegate.cc @@ -19,15 +19,20 @@ #include "base/logging.h" #include "base/path_service.h" #include "base/strings/cstring_view.h" +#include "base/strings/string_number_conversions.cc" +#include "base/strings/string_util_internal.h" #include "chrome/common/chrome_paths.h" #include "chrome/common/chrome_switches.h" #include "components/content_settings/core/common/content_settings_pattern.h" #include "content/public/app/initialize_mojo_core.h" #include "content/public/common/content_switches.h" +#include "crypto/hash.h" #include "electron/buildflags/buildflags.h" #include "electron/fuses.h" #include "electron/mas.h" +#include "electron/snapshot_checksum.h" #include "extensions/common/constants.h" +#include "gin/v8_initializer.h" #include "sandbox/policy/switches.h" #include "services/tracing/public/cpp/stack_sampling/tracing_sampler_profiler.h" #include "shell/app/command_line_args.h" @@ -49,6 +54,7 @@ #include "third_party/abseil-cpp/absl/types/variant.h" #include "ui/base/resource/resource_bundle.h" #include "ui/base/ui_base_switches.h" +#include "v8/include/v8-snapshot.h" #if BUILDFLAG(IS_MAC) #include "shell/app/electron_main_delegate_mac.h" @@ -207,6 +213,20 @@ void RegisterPathProvider() { PATH_END); } +void ValidateV8Snapshot(v8::StartupData* data) { + if (data->data && + electron::fuses::IsEmbeddedAsarIntegrityValidationEnabled()) { + CHECK_GT(data->raw_size, 0); + UNSAFE_BUFFERS({ + base::span span_data( + data->data, static_cast(data->raw_size)); + CHECK(base::ToLowerASCII(base::HexEncode( + crypto::hash::Sha256(base::as_bytes(span_data)))) == + electron::snapshot_checksum::kChecksum); + }) + } +} + } // namespace std::string LoadResourceBundle(const std::string& locale) { @@ -230,7 +250,9 @@ std::string LoadResourceBundle(const std::string& locale) { return loaded_locale; } -ElectronMainDelegate::ElectronMainDelegate() = default; +ElectronMainDelegate::ElectronMainDelegate() { + gin::SetV8SnapshotValidator(base::BindRepeating(&ValidateV8Snapshot)); +} ElectronMainDelegate::~ElectronMainDelegate() = default;