Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/build/darwin/verify-macho.ts
3520 views
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 assert from 'assert';
7
import path from 'path';
8
import { open, stat, readdir, realpath } from 'fs/promises';
9
import { spawn, ExitCodeError } from '@malept/cross-spawn-promise';
10
import minimatch from 'minimatch';
11
12
const MACHO_PREFIX = 'Mach-O ';
13
const MACHO_64_MAGIC_LE = 0xfeedfacf;
14
const MACHO_UNIVERSAL_MAGIC_LE = 0xbebafeca;
15
const MACHO_ARM64_CPU_TYPE = new Set([
16
0x0c000001,
17
0x0100000c,
18
]);
19
const MACHO_X86_64_CPU_TYPE = new Set([
20
0x07000001,
21
0x01000007,
22
]);
23
24
// Files to skip during architecture validation
25
const FILES_TO_SKIP = [
26
// MSAL runtime files are only present in ARM64 builds
27
'**/extensions/microsoft-authentication/dist/libmsalruntime.dylib',
28
'**/extensions/microsoft-authentication/dist/msal-node-runtime.node',
29
];
30
31
function isFileSkipped(file: string): boolean {
32
return FILES_TO_SKIP.some(pattern => minimatch(file, pattern));
33
}
34
35
async function read(file: string, buf: Buffer, offset: number, length: number, position: number) {
36
let filehandle;
37
try {
38
filehandle = await open(file);
39
await filehandle.read(buf, offset, length, position);
40
} finally {
41
await filehandle?.close();
42
}
43
}
44
45
async function checkMachOFiles(appPath: string, arch: string) {
46
const visited = new Set();
47
const invalidFiles: string[] = [];
48
const header = Buffer.alloc(8);
49
const file_header_entry_size = 20;
50
const checkx86_64Arch = (arch === 'x64');
51
const checkArm64Arch = (arch === 'arm64');
52
const checkUniversalArch = (arch === 'universal');
53
const traverse = async (p: string) => {
54
p = await realpath(p);
55
if (visited.has(p)) {
56
return;
57
}
58
visited.add(p);
59
60
const info = await stat(p);
61
if (info.isSymbolicLink()) {
62
return;
63
}
64
if (info.isFile()) {
65
let fileOutput = '';
66
try {
67
fileOutput = await spawn('file', ['--brief', '--no-pad', p]);
68
} catch (e) {
69
if (e instanceof ExitCodeError) {
70
/* silently accept error codes from "file" */
71
} else {
72
throw e;
73
}
74
}
75
if (fileOutput.startsWith(MACHO_PREFIX)) {
76
console.log(`Verifying architecture of ${p}`);
77
read(p, header, 0, 8, 0).then(_ => {
78
const header_magic = header.readUInt32LE();
79
if (header_magic === MACHO_64_MAGIC_LE) {
80
const cpu_type = header.readUInt32LE(4);
81
if (checkUniversalArch) {
82
invalidFiles.push(p);
83
} else if (checkArm64Arch && !MACHO_ARM64_CPU_TYPE.has(cpu_type)) {
84
invalidFiles.push(p);
85
} else if (checkx86_64Arch && !MACHO_X86_64_CPU_TYPE.has(cpu_type)) {
86
invalidFiles.push(p);
87
}
88
} else if (header_magic === MACHO_UNIVERSAL_MAGIC_LE) {
89
const num_binaries = header.readUInt32BE(4);
90
assert.equal(num_binaries, 2);
91
const file_entries_size = file_header_entry_size * num_binaries;
92
const file_entries = Buffer.alloc(file_entries_size);
93
read(p, file_entries, 0, file_entries_size, 8).then(_ => {
94
for (let i = 0; i < num_binaries; i++) {
95
const cpu_type = file_entries.readUInt32LE(file_header_entry_size * i);
96
if (!MACHO_ARM64_CPU_TYPE.has(cpu_type) && !MACHO_X86_64_CPU_TYPE.has(cpu_type)) {
97
invalidFiles.push(p);
98
}
99
}
100
});
101
}
102
});
103
}
104
}
105
106
if (info.isDirectory()) {
107
for (const child of await readdir(p)) {
108
await traverse(path.resolve(p, child));
109
}
110
}
111
};
112
await traverse(appPath);
113
return invalidFiles;
114
}
115
116
const archToCheck = process.argv[2];
117
assert(process.env['APP_PATH'], 'APP_PATH not set');
118
assert(archToCheck === 'x64' || archToCheck === 'arm64' || archToCheck === 'universal', `Invalid architecture ${archToCheck} to check`);
119
checkMachOFiles(process.env['APP_PATH'], archToCheck).then(invalidFiles => {
120
// Filter out files that should be skipped
121
const actualInvalidFiles = invalidFiles.filter(file => !isFileSkipped(file));
122
if (actualInvalidFiles.length > 0) {
123
console.error('\x1b[31mThese files are built for the wrong architecture:\x1b[0m');
124
actualInvalidFiles.forEach(file => console.error(`\x1b[31m${file}\x1b[0m`));
125
process.exit(1);
126
} else {
127
console.log('\x1b[32mAll files are valid\x1b[0m');
128
}
129
}).catch(err => {
130
console.error(err);
131
process.exit(1);
132
});
133
134