diff --git a/build/toolchain/mac/BUILD.gn b/build/toolchain/mac/BUILD.gn
index 9b2d7be6d3c..8391a4f80a8 100644
--- a/build/toolchain/mac/BUILD.gn
+++ b/build/toolchain/mac/BUILD.gn
@@ -14,6 +14,7 @@ assert(host_os == "mac")
import("//build/config/sysroot.gni")
import("//build/toolchain/goma.gni")
import("//build/toolchain/rbe.gni")
+import("//build/toolchain/signing.gni")
if (use_goma) {
assembler_prefix = "$goma_dir/gomacc "
@@ -189,20 +190,44 @@ template("mac_toolchain") {
stripped_outfile = "{{root_out_dir}}/exe.stripped/$exename"
}
- command = "$ld $sysroot_flags $toolchain_flags {{ldflags}} -Xlinker -rpath -Xlinker @executable_path/Frameworks -o $outfile -Wl,-filelist,$rspfile {{solibs}} {{libs}} {{frameworks}}"
+ commands = [ "$ld $sysroot_flags $toolchain_flags {{ldflags}} -Xlinker -rpath -Xlinker @executable_path/Frameworks -o $outfile -Wl,-filelist,$rspfile {{solibs}} {{libs}} {{frameworks}}" ]
symbolizer_script =
rebase_path("//runtime/tools/dart_profiler_symbols.py")
- symbolize_command =
- "$symbolizer_script --nm $nm --output $symfile --binary $outfile"
- command += " && $symbolize_command"
+ commands +=
+ [ "$symbolizer_script --nm $nm --output $symfile --binary $outfile" ]
if (defined(invoker.strip)) {
strip = invoker.strip
- strip_command = "${strip} -x -o $stripped_outfile $outfile"
- command += " && " + strip_command
+ commands += [ "${strip} -x -o $stripped_outfile $outfile" ]
}
+ if (codesigning_identity != "") {
+ # codesign tool performs signing in-place. This does not fit very well
+ # into the overall build: we would have to produce unsigned binary with
+ # some suffix (e.g. dart_unsigned), then copy it to the final location
+ # and sign. To avoid this dance we choose to perform signing here
+ # at the link step. Unfortunately this also comes with some limitations:
+ # executable target can't push arbitrary configuration variables down
+ # to the link step. Which means we can't specify per target
+ # entitlement files - and instead rely on dart_codesign.py script to
+ # match binaries to their entitlement files by name.
+ signing_script = rebase_path("//runtime/tools/dart_codesign.py")
+ binaries_to_sign = [
+ "--binary",
+ outfile,
+ ]
+ if (defined(stripped_outfile)) {
+ binaries_to_sign += [
+ "--binary",
+ stripped_outfile,
+ ]
+ }
+ commands += [ "$signing_script --identity $codesigning_identity " +
+ string_join(" ", binaries_to_sign) ]
+ }
+
+ command = string_join(" && ", commands)
description = "LINK $outfile"
rspfile_content = "{{inputs_newline}}"
outputs = [
diff --git a/build/toolchain/signing.gni b/build/toolchain/signing.gni
new file mode 100644
index 00000000000..2124b2f7034
--- /dev/null
+++ b/build/toolchain/signing.gni
@@ -0,0 +1,16 @@
+# Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+# Defines code signing configuration for Mac builds.
+
+declare_args() {
+ # If codesigning_identity is not empty then all executables will be
+ # signed using the specified identity and using corresponding entitlements
+ # from runtime/tools/entitlements/${binary_name}.plist.
+ #
+ # You can specify codesigning_identity = "-" to use ad-hoc codesigning.
+ #
+ # See also runtime/tools/dart_codesigning.py script.
+ codesigning_identity = ""
+}
diff --git a/runtime/tools/dart_codesign.py b/runtime/tools/dart_codesign.py
new file mode 100755
index 00000000000..70ad9c29140
--- /dev/null
+++ b/runtime/tools/dart_codesign.py
@@ -0,0 +1,63 @@
+#!/usr/bin/env python3
+#
+# Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+#
+# Sign given binaries with using the specified signing identity and
+# using entitlements from runtime/tools/entitlement/${binary_name}.plist
+# if any.
+#
+
+import optparse
+import os
+import subprocess
+
+SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
+
+
+def SignBinary(identity, binary):
+ codesign_args = [
+ "--deep", "--force", "--verify", "--verbose", "--timestamp",
+ "--options", "runtime", "--sign", identity
+ ]
+
+ name = os.path.basename(binary)
+
+ # Check if we have a matching entitlements file and apply it.
+ # It would be simpler if we could specify it from outside but
+ # GN does not give us tools for doing that: executable target can't
+ # push arbitrary configuration down to the link tool where
+ # we would like to perform code signing.
+ entitlements_file = os.path.join(SCRIPT_DIR, "entitlements",
+ name + ".plist")
+ if os.path.exists(entitlements_file):
+ codesign_args += ["--entitlements", entitlements_file]
+ cmd = ["codesign"] + codesign_args + [binary]
+ result = subprocess.run(cmd, capture_output=True, encoding="utf8")
+ if result.returncode != 0:
+ print("failed to run: " + " ".join(cmd))
+ print(f"exit code: {result.returncode}")
+ print("stdout:")
+ print(result.stdout)
+ print("stdout:")
+ print(result.stderr)
+ raise Exception("failed to codesign")
+
+
+parser = optparse.OptionParser()
+parser.add_option("--identity", type="string", help="Code signing identity")
+parser.add_option("--binary",
+ type="string",
+ action="append",
+ help="Binary to sign")
+options = parser.parse_args()[0]
+
+if not options.identity:
+ raise Exception("Missing code signing identity (--identity)")
+
+if not options.binary:
+ raise Exception("Missing binaries to sign (--binary)")
+
+for binary in options.binary:
+ SignBinary(options.identity, binary)
diff --git a/runtime/tools/entitlements/README.md b/runtime/tools/entitlements/README.md
new file mode 100644
index 00000000000..ef0d294e352
--- /dev/null
+++ b/runtime/tools/entitlements/README.md
@@ -0,0 +1,6 @@
+These entitlement configurations are used when GN arg codesigning_identity
+is set to non-empty string.
+
+They must be kept in sync with [entitlements][1] used for codesigning releases:
+
+[1]: https://dart.googlesource.com/recipes/+/refs/heads/main/recipes/release/sign-mac.resources/Entitlements_dart.plist
\ No newline at end of file
diff --git a/runtime/tools/entitlements/dart.plist b/runtime/tools/entitlements/dart.plist
new file mode 100644
index 00000000000..eeda4be95dc
--- /dev/null
+++ b/runtime/tools/entitlements/dart.plist
@@ -0,0 +1,10 @@
+
+
+
+
+ com.apple.security.cs.allow-jit
+
+ com.apple.security.cs.disable-library-validation
+
+
+
\ No newline at end of file
diff --git a/runtime/tools/entitlements/dart_precompiled_runtime.plist b/runtime/tools/entitlements/dart_precompiled_runtime.plist
new file mode 100644
index 00000000000..3322fe106b9
--- /dev/null
+++ b/runtime/tools/entitlements/dart_precompiled_runtime.plist
@@ -0,0 +1,10 @@
+
+
+
+
+ com.apple.security.cs.allow-unsigned-executable-memory
+
+ com.apple.security.cs.disable-library-validation
+
+
+
\ No newline at end of file
diff --git a/runtime/tools/entitlements/dart_precompiled_runtime_product.plist b/runtime/tools/entitlements/dart_precompiled_runtime_product.plist
new file mode 100644
index 00000000000..3322fe106b9
--- /dev/null
+++ b/runtime/tools/entitlements/dart_precompiled_runtime_product.plist
@@ -0,0 +1,10 @@
+
+
+
+
+ com.apple.security.cs.allow-unsigned-executable-memory
+
+ com.apple.security.cs.disable-library-validation
+
+
+
\ No newline at end of file
diff --git a/runtime/tools/entitlements/gen_snapshot.plist b/runtime/tools/entitlements/gen_snapshot.plist
new file mode 100644
index 00000000000..694b88bfa7e
--- /dev/null
+++ b/runtime/tools/entitlements/gen_snapshot.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ com.apple.security.cs.allow-jit
+
+
+
\ No newline at end of file
diff --git a/runtime/tools/entitlements/gen_snapshot_product.plist b/runtime/tools/entitlements/gen_snapshot_product.plist
new file mode 100644
index 00000000000..694b88bfa7e
--- /dev/null
+++ b/runtime/tools/entitlements/gen_snapshot_product.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ com.apple.security.cs.allow-jit
+
+
+
\ No newline at end of file
diff --git a/runtime/tools/entitlements/run_vm_tests.plist b/runtime/tools/entitlements/run_vm_tests.plist
new file mode 100644
index 00000000000..eeda4be95dc
--- /dev/null
+++ b/runtime/tools/entitlements/run_vm_tests.plist
@@ -0,0 +1,10 @@
+
+
+
+
+ com.apple.security.cs.allow-jit
+
+ com.apple.security.cs.disable-library-validation
+
+
+
\ No newline at end of file
diff --git a/runtime/vm/heap/freelist_test.cc b/runtime/vm/heap/freelist_test.cc
index eacd0efc75a..4a038f7b225 100644
--- a/runtime/vm/heap/freelist_test.cc
+++ b/runtime/vm/heap/freelist_test.cc
@@ -199,7 +199,7 @@ static void TestRegress38528(intptr_t header_overlap) {
const uword page = VirtualMemory::PageSize();
std::unique_ptr blob(VirtualMemory::Allocate(
2 * page,
- /*is_executable=*/false, /*is_compressed*/ false, "test"));
+ /*is_executable=*/true, /*is_compressed=*/false, "test"));
const intptr_t remainder_size = page / 2;
const intptr_t alloc_size = page - header_overlap * kObjectAlignment;
void* const other_code =
diff --git a/runtime/vm/virtual_memory_test.cc b/runtime/vm/virtual_memory_test.cc
index 2dc4b995dcb..629dc4b12ea 100644
--- a/runtime/vm/virtual_memory_test.cc
+++ b/runtime/vm/virtual_memory_test.cc
@@ -109,8 +109,18 @@ VM_UNIT_TEST_CASE(DuplicateRXVirtualMemory) {
reinterpret_cast(page_start), 2 * page_size);
EXPECT_NE(nullptr, vm);
+#if defined(DART_HOST_OS_MACOS) && !defined(DART_PRECOMPILED_RUNTIME)
+ // If we are not going to use vm_remap then we need to pass
+ // is_executable=true so that pages get allocated with MAP_JIT flag if
+ // necessary. Otherwise OS will kill us with a codesigning violation if
+ // hardened runtime is enabled.
+ const bool is_executable = true;
+#else
+ const bool is_executable = false;
+#endif
+
VirtualMemory* vm2 = VirtualMemory::AllocateAligned(
- vm->size(), kPageSize, /*is_executable=*/false,
+ vm->size(), kPageSize, is_executable,
/*is_compressed=*/false, "FfiCallbackMetadata::TrampolinePage");
bool ok = vm->DuplicateRX(vm2);
EXPECT_EQ(true, ok);
diff --git a/tools/gn.py b/tools/gn.py
index aae350a3181..7820c6ec526 100755
--- a/tools/gn.py
+++ b/tools/gn.py
@@ -330,6 +330,9 @@ def ToGnArgs(args, mode, arch, target_os, sanitizer, verify_sdk_hash):
gn_args['verify_sdk_hash'] = verify_sdk_hash
+ if args.codesigning_identity != '':
+ gn_args['codesigning_identity'] = args.codesigning_identity
+
return gn_args
@@ -518,6 +521,10 @@ def AddCommonGnOptionArgs(parser):
default=False,
dest='use_mallinfo2',
action='store_true')
+ parser.add_argument('--codesigning-identity',
+ help='Sign executables using the given identity.',
+ default='',
+ type=str)
def AddCommonConfigurationArgs(parser):