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):