[pkg/dart2native] Handle cases where linker-signed is not an option.

Instead of assuming that linker-signed is a valid option flag for
codesign, only attempt to use it on macOS 11.0 or later, where
signatures on linker-signed binaries need not be force overwritten.
Otherwise, fall back to a regular signature that must be force
overwritten.

Bug: https://github.com/dart-lang/sdk/issues/49010
Cq-Include-Trybots: luci.dart.try:pkg-mac-release-arm64-try,pkg-mac-release-try
Change-Id: Ia2bff2f501dbe37b3269a2a2f16ce9a29026664e
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/245000
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Tess Strickland <sstrickl@google.com>
This commit is contained in:
Tess Strickland
2022-05-17 11:21:30 +00:00
committed by Commit Bot
parent 6ef2b237e0
commit 4e520ec7a7
2 changed files with 87 additions and 8 deletions
+54 -6
View File
@@ -265,6 +265,32 @@ Future<int> pipeStream(RandomAccessFile from, RandomAccessFile to,
return numWritten;
}
class _MacOSVersion {
final int? _major;
final int? _minor;
static final _regexp = RegExp(r'Version (?<major>\d+).(?<minor>\d+)');
static const _parseFailure = 'Could not determine macOS version';
const _MacOSVersion._internal(this._major, this._minor);
static const _unknown = _MacOSVersion._internal(null, null);
factory _MacOSVersion() {
if (!Platform.isMacOS) return _unknown;
final match =
_regexp.matchAsPrefix(Platform.operatingSystemVersion) as RegExpMatch?;
if (match == null) return _unknown;
final minor = int.tryParse(match.namedGroup('minor')!);
final major = int.tryParse(match.namedGroup('major')!);
return _MacOSVersion._internal(major, minor);
}
bool get isValid => _major != null;
int get major => _major ?? (throw _parseFailure);
int get minor => _minor ?? (throw _parseFailure);
}
// Writes an "appended" dart runtime + script snapshot file in a format
// compatible with MachO executables.
Future writeAppendedMachOExecutable(
@@ -312,17 +338,39 @@ Future writeAppendedMachOExecutable(
await stream.close();
if (machOFile.hasCodeSignature) {
if (!Platform.isMacOS) {
throw 'Cannot sign MachO binary on non-macOS platform';
}
// After writing the modified file, we perform ad-hoc signing (no identity)
// similar to the linker (the linker-signed option flag) to ensure that any
// LC_CODE_SIGNATURE block has the correct CD hashes. This is necessary for
// platforms where signature verification is always on (e.g., OS X on M1).
// to ensure that any LC_CODE_SIGNATURE block has the correct CD hashes.
// This is necessary for platforms where signature verification is always on
// (e.g., OS X on M1).
//
// We use the `-f` flag to force signature overwriting as the official
// Dart binaries (including dartaotruntime) are fully signed.
final signingProcess = await Process.run(
'codesign', ['-f', '-o', 'linker-signed', '-s', '-', outputPath]);
final args = ['-f', '-s', '-', outputPath];
// If running on macOS >=11.0, then the linker-signed option flag can be
// used to create a signature that does not need to be force overridden.
final version = _MacOSVersion();
if (version.isValid && version.major >= 11) {
final signingProcess =
await Process.run('codesign', ['-o', 'linker-signed', ...args]);
if (signingProcess.exitCode == 0) {
return;
}
print('Failed to add a linker signed signature, '
'adding a regular signature instead.');
}
// If that fails or we're running on an older or undetermined version of
// macOS, we fall back to signing without the linker-signed option flag.
// Thus, to sign the binary, the developer must force signature overwriting.
final signingProcess = await Process.run('codesign', args);
if (signingProcess.exitCode != 0) {
print('Subcommand terminated with exit code ${signingProcess.exitCode}.');
print('Failed to replace the dartaotruntime signature, ');
print('subcommand terminated with exit code ${signingProcess.exitCode}.');
if (signingProcess.stdout.isNotEmpty) {
print('Subcommand stdout:');
print(signingProcess.stdout);
+33 -2
View File
@@ -187,7 +187,7 @@ void defineCompileTests() {
expect(result.stdout, contains('2: foo'));
});
test('Compile and run executable', () async {
Future<void> basicCompileTest() async {
final p = project(mainSrc: 'void main() { print("I love executables"); }');
final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath));
final outFile = path.canonicalize(path.join(p.dirPath, 'lib', 'main.exe'));
@@ -213,7 +213,9 @@ void defineCompileTests() {
expect(result.stderr, isEmpty);
expect(result.exitCode, 0);
expect(result.stdout, contains('I love executables'));
}, skip: isRunningOnIA32);
}
test('Compile and run executable', basicCompileTest, skip: isRunningOnIA32);
test('Compile to executable disabled on IA32', () async {
final p = project(mainSrc: 'void main() { print("I love executables"); }');
@@ -1112,4 +1114,33 @@ void main() {
expect(result.stderr, contains('Warning:'));
expect(result.exitCode, 0);
});
if (Platform.isMacOS) {
test('Compile and run executable from signed dartaotruntime', () async {
// Either the locally built dartaotruntime is already linker signed
// (on M1) or it is unsigned (on X64). For this test, sign the
// dartaotruntime executable with a non-linker signed adhoc signature,
// which won't cause issues with any other tests that use it. This
// ensures the code signing path in dart2native is exercised on X64
// (macOS <11.0), and also mimics the case for end users that are using
// the published Dart SDK (which is fully signed, not linker signed).
final Directory binDir = File(Platform.resolvedExecutable).parent;
final String originalRuntimePath =
path.join(binDir.path, 'dartaotruntime');
final codeSigningProcess = await Process.start('codesign', [
'-o',
'runtime',
'-s',
'-',
originalRuntimePath,
]);
final signingResult = await codeSigningProcess.exitCode;
expect(signingResult, 0);
// Now perform the same basic compile and run test with the signed
// dartaotruntime.
await basicCompileTest();
}, skip: isRunningOnIA32);
}
}