diff --git a/BUILD.gn b/BUILD.gn index 1e5061a0c53b..47a5fb2ecec3 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 19a987b9e479..a4f7394d6710 100644 --- a/patches/chromium/.patches +++ b/patches/chromium/.patches @@ -149,3 +149,4 @@ do_not_check_the_order_of_display_id_order_on_windows.patch make_focus_methods_in_webcontentsviewchildframe_notimplemented.patch cherry-pick-f1e6422a355c.patch fix_resolve_dynamic_background_material_update_issue_on_windows_11.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..4ac1276a4d6b --- /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 e07bdaeccecc8015462e35d5cf4606335e2e962c..28b133626cf3488dee6f43bfd0ac9b6b14ed3980 100644 +--- a/gin/v8_initializer.cc ++++ b/gin/v8_initializer.cc +@@ -75,11 +75,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; +@@ -223,6 +235,10 @@ constexpr std::string_view kV8FlagFeaturePrefix = "V8Flag_"; + + } // 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 9c34d5ca7b77..13ceb935c700 100644 --- a/shell/app/electron_main_delegate.cc +++ b/shell/app/electron_main_delegate.cc @@ -23,10 +23,13 @@ #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 "ipc/ipc_buildflags.h" #include "sandbox/policy/switches.h" #include "services/tracing/public/cpp/stack_sampling/tracing_sampler_profiler.h" @@ -49,6 +52,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" @@ -206,6 +210,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) { @@ -229,7 +247,9 @@ std::string LoadResourceBundle(const std::string& locale) { return loaded_locale; } -ElectronMainDelegate::ElectronMainDelegate() = default; +ElectronMainDelegate::ElectronMainDelegate() { + gin::SetV8SnapshotValidator(base::BindRepeating(&ValidateV8Snapshot)); +} ElectronMainDelegate::~ElectronMainDelegate() = default;