Skip to content

Commit 51cfc9e

Browse files
authored
ci: add checks to verify mach-o objects architecture (microsoft#230598)
1 parent 55e66e7 commit 51cfc9e

File tree

6 files changed

+258
-15
lines changed

6 files changed

+258
-15
lines changed

build/azure-pipelines/darwin/product-build-darwin-universal.yml

+7
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,13 @@ steps:
6666
DEBUG=* node build/darwin/create-universal-app.js $(agent.builddirectory)
6767
displayName: Create Universal App
6868
69+
- script: |
70+
set -e
71+
APP_ROOT="$(Agent.BuildDirectory)/VSCode-darwin-$(VSCODE_ARCH)"
72+
APP_NAME="`ls $APP_ROOT | head -n 1`"
73+
APP_PATH="$APP_ROOT/$APP_NAME" node build/darwin/verify-macho.js universal
74+
displayName: Verify arch of Mach-O objects
75+
6976
- script: |
7077
set -e
7178
security create-keychain -p pwd $(agent.tempdirectory)/buildagent.keychain

build/azure-pipelines/darwin/product-build-darwin.yml

+8
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,14 @@ steps:
188188
chmod +x "$APP_PATH/Contents/Resources/app/bin/$CLI_APP_NAME"
189189
displayName: Make CLI executable
190190
191+
- script: |
192+
set -e
193+
APP_ROOT="$(Agent.BuildDirectory)/VSCode-darwin-$(VSCODE_ARCH)"
194+
APP_NAME="`ls $APP_ROOT | head -n 1`"
195+
APP_PATH="$APP_ROOT/$APP_NAME" node build/darwin/verify-macho.js $(VSCODE_ARCH)
196+
APP_PATH="$(Agent.BuildDirectory)/vscode-server-darwin-$(VSCODE_ARCH)" node build/darwin/verify-macho.js $(VSCODE_ARCH)
197+
displayName: Verify arch of Mach-O objects
198+
191199
# Setting hardened entitlements is a requirement for:
192200
# * Apple notarization
193201
# * Running tests on Big Sur (because Big Sur has additional security precautions)

build/darwin/create-universal-app.js

-7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

build/darwin/create-universal-app.ts

-8
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import * as path from 'path';
77
import * as fs from 'fs';
88
import * as minimatch from 'minimatch';
99
import { makeUniversalApp } from 'vscode-universal-bundler';
10-
import { spawn } from '@malept/cross-spawn-promise';
1110

1211
const root = path.dirname(path.dirname(__dirname));
1312

@@ -54,13 +53,6 @@ async function main(buildDir?: string) {
5453
darwinUniversalAssetId: 'darwin-universal'
5554
});
5655
fs.writeFileSync(productJsonPath, JSON.stringify(productJson, null, '\t'));
57-
58-
// Verify if native module architecture is correct
59-
const findOutput = await spawn('find', [outAppPath, '-name', 'kerberos.node']);
60-
const lipoOutput = await spawn('lipo', ['-archs', findOutput.replace(/\n$/, '')]);
61-
if (lipoOutput.replace(/\n$/, '') !== 'x86_64 arm64') {
62-
throw new Error(`Invalid arch, got : ${lipoOutput}`);
63-
}
6456
}
6557

6658
if (require.main === module) {

build/darwin/verify-macho.js

+123
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

build/darwin/verify-macho.ts

+120
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import * as assert from 'assert';
7+
import * as path from 'path';
8+
import { open, stat, readdir, realpath } from 'fs/promises';
9+
import { spawn, ExitCodeError } from '@malept/cross-spawn-promise';
10+
11+
const MACHO_PREFIX = 'Mach-O ';
12+
const MACHO_64_MAGIC_LE = 0xfeedfacf;
13+
const MACHO_UNIVERSAL_MAGIC_LE = 0xbebafeca;
14+
const MACHO_ARM64_CPU_TYPE = new Set([
15+
0x0c000001,
16+
0x0100000c,
17+
]);
18+
const MACHO_X86_64_CPU_TYPE = new Set([
19+
0x07000001,
20+
0x01000007,
21+
]);
22+
23+
async function read(file: string, buf: Buffer, offset: number, length: number, position: number) {
24+
let filehandle;
25+
try {
26+
filehandle = await open(file);
27+
await filehandle.read(buf, offset, length, position);
28+
} finally {
29+
await filehandle?.close();
30+
}
31+
}
32+
33+
async function checkMachOFiles(appPath: string, arch: string) {
34+
const visited = new Set();
35+
const invalidFiles: string[] = [];
36+
const header = Buffer.alloc(8);
37+
const file_header_entry_size = 20;
38+
const checkx86_64Arch = (arch === 'x64');
39+
const checkArm64Arch = (arch === 'arm64');
40+
const checkUniversalArch = (arch === 'universal');
41+
const traverse = async (p: string) => {
42+
p = await realpath(p);
43+
if (visited.has(p)) {
44+
return;
45+
}
46+
visited.add(p);
47+
48+
const info = await stat(p);
49+
if (info.isSymbolicLink()) {
50+
return;
51+
}
52+
if (info.isFile()) {
53+
let fileOutput = '';
54+
try {
55+
fileOutput = await spawn('file', ['--brief', '--no-pad', p]);
56+
} catch (e) {
57+
if (e instanceof ExitCodeError) {
58+
/* silently accept error codes from "file" */
59+
} else {
60+
throw e;
61+
}
62+
}
63+
if (fileOutput.startsWith(MACHO_PREFIX)) {
64+
console.log(`Verifying architecture of ${p}`);
65+
read(p, header, 0, 8, 0).then(_ => {
66+
const header_magic = header.readUInt32LE();
67+
if (header_magic === MACHO_64_MAGIC_LE) {
68+
const cpu_type = header.readUInt32LE(4);
69+
if (checkUniversalArch) {
70+
invalidFiles.push(p);
71+
} else if (checkArm64Arch && !MACHO_ARM64_CPU_TYPE.has(cpu_type)) {
72+
invalidFiles.push(p);
73+
} else if (checkx86_64Arch && !MACHO_X86_64_CPU_TYPE.has(cpu_type)) {
74+
invalidFiles.push(p);
75+
}
76+
} else if (header_magic === MACHO_UNIVERSAL_MAGIC_LE) {
77+
const num_binaries = header.readUInt32BE(4);
78+
assert.equal(num_binaries, 2);
79+
const file_entries_size = file_header_entry_size * num_binaries;
80+
const file_entries = Buffer.alloc(file_entries_size);
81+
read(p, file_entries, 0, file_entries_size, 8).then(_ => {
82+
for (let i = 0; i < num_binaries; i++) {
83+
const cpu_type = file_entries.readUInt32LE(file_header_entry_size * i);
84+
if (!MACHO_ARM64_CPU_TYPE.has(cpu_type) && !MACHO_X86_64_CPU_TYPE.has(cpu_type)) {
85+
invalidFiles.push(p);
86+
}
87+
}
88+
});
89+
}
90+
});
91+
}
92+
}
93+
94+
if (info.isDirectory()) {
95+
for (const child of await readdir(p)) {
96+
await traverse(path.resolve(p, child));
97+
}
98+
}
99+
};
100+
await traverse(appPath);
101+
return invalidFiles;
102+
}
103+
104+
const archToCheck = process.argv[2];
105+
assert(process.env['APP_PATH'], 'APP_PATH not set');
106+
assert(archToCheck === 'x64' || archToCheck === 'arm64' || archToCheck === 'universal', `Invalid architecture ${archToCheck} to check`);
107+
checkMachOFiles(process.env['APP_PATH'], archToCheck).then(invalidFiles => {
108+
if (invalidFiles.length > 0) {
109+
console.error('\x1b[31mThe following files are built for the wrong architecture:\x1b[0m');
110+
for (const file of invalidFiles) {
111+
console.error(`\x1b[31m${file}\x1b[0m`);
112+
}
113+
process.exit(1);
114+
} else {
115+
console.log('\x1b[32mAll files are valid\x1b[0m');
116+
}
117+
}).catch(err => {
118+
console.error(err);
119+
process.exit(1);
120+
});

0 commit comments

Comments
 (0)