Check imported dlls in CI

Co-authored-by: trevor-signal <131492920+trevor-signal@users.noreply.github.com>
This commit is contained in:
Fedor Indutny
2026-03-19 08:37:46 -07:00
committed by GitHub
parent a8118faf08
commit d34506f400
5 changed files with 178 additions and 23 deletions

View File

@@ -108,6 +108,7 @@ const NODE_PACKAGES = new Set([
'node-gyp-build',
'npm-run-all',
'p-limit',
'pe-library',
'pixelmatch',
'playwright',
'postcss',
@@ -116,7 +117,6 @@ const NODE_PACKAGES = new Set([
'prettier-plugin-tailwindcss',
'react-devtools',
'react-devtools-core',
'resedit',
'resolve-url-loader',
'sass',
'sass-loader',

View File

@@ -463,7 +463,7 @@ jobs:
continue-on-error: true
strategy:
matrix:
os: [ubuntu-22.04-8-cores, macos-latest]
os: [ubuntu-22.04-8-cores, macos-latest, windows-latest-8-cores]
runs-on: ${{ matrix.os }}
timeout-minutes: 30

View File

@@ -333,6 +333,7 @@
"node-gyp-build": "4.8.4",
"npm-run-all": "4.1.5",
"p-limit": "3.1.0",
"pe-library": "2.0.1",
"pixelmatch": "5.3.0",
"playwright": "1.58.2",
"pngjs": "7.0.0",
@@ -342,7 +343,6 @@
"prettier-plugin-tailwindcss": "0.7.2",
"react-devtools": "6.0.1",
"react-devtools-core": "6.0.1",
"resedit": "2.0.2",
"resolve-url-loader": "5.0.0",
"sass": "1.80.7",
"sass-loader": "16.0.3",

22
pnpm-lock.yaml generated
View File

@@ -729,6 +729,9 @@ importers:
p-limit:
specifier: 3.1.0
version: 3.1.0
pe-library:
specifier: 2.0.1
version: 2.0.1
pixelmatch:
specifier: 5.3.0
version: 5.3.0
@@ -756,9 +759,6 @@ importers:
react-devtools-core:
specifier: 6.0.1
version: 6.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10)
resedit:
specifier: 2.0.2
version: 2.0.2
resolve-url-loader:
specifier: 5.0.0
version: 5.0.0
@@ -8670,9 +8670,9 @@ packages:
resolution: {integrity: sha512-eRWB5LBz7PpDu4PUlwT0PhnQfTQJlDDdPa35urV4Osrm0t0AqQFGn+UIkU3klZvwJ8KPO3VbBFsXquA6p6kqZw==}
engines: {node: '>=12', npm: '>=6'}
pe-library@1.0.1:
resolution: {integrity: sha512-nh39Mo1eGWmZS7y+mK/dQIqg7S1lp38DpRxkyoHf0ZcUs/HDc+yyTjuOtTvSMZHmfSLuSQaX945u05Y2Q6UWZg==}
engines: {node: '>=14', npm: '>=7'}
pe-library@2.0.1:
resolution: {integrity: sha512-/qjYFqNSlq59B5DI36am++5/3gMgh02QnzpYigrwrW6s+QpU0mHf09/iA4wjTu21UUxodyV7ZCetV5MiDhaN/A==}
engines: {node: '>=20'}
pend@1.2.0:
resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==}
@@ -9359,10 +9359,6 @@ packages:
resolution: {integrity: sha512-vHjcY2MlAITJhC0eRD/Vv8Vlgmu9Sd3LX9zZvtGzU5ZImdTN3+d6e/4mnTyV8vEbyf1sgNIrWxhWlrys52OkEA==}
engines: {node: '>=12', npm: '>=6'}
resedit@2.0.2:
resolution: {integrity: sha512-UKTnq602iVe+W5SyRAQx/WdWMnlDiONfXBLFg/ur4QE4EQQ8eP7Jgm5mNXdK12kKawk1vvXPja2iXKqZiGDW6Q==}
engines: {node: '>=14', npm: '>=7'}
reselect@5.1.1:
resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==}
@@ -20417,7 +20413,7 @@ snapshots:
pe-library@0.4.1: {}
pe-library@1.0.1: {}
pe-library@2.0.1: {}
pend@1.2.0: {}
@@ -21231,10 +21227,6 @@ snapshots:
dependencies:
pe-library: 0.4.1
resedit@2.0.2:
dependencies:
pe-library: 1.0.1
reselect@5.1.1: {}
resolve-alpn@1.2.1: {}

View File

@@ -4,9 +4,11 @@
import { execFile as execFileCb } from 'node:child_process';
import { promisify } from 'node:util';
import { existsSync } from 'node:fs';
import { readFile } from 'node:fs/promises';
import { join, basename } from 'node:path';
import fastGlob from 'fast-glob';
import { gte } from 'semver';
import { Format, NtExecutable } from 'pe-library';
// Note: because we don't run under electron - this is a path to binary
import ELECTRON_BINARY from 'electron';
@@ -14,6 +16,8 @@ import ELECTRON_BINARY from 'electron';
import { drop } from '../util/drop.std.js';
import packageJson from '../util/packageJson.node.js';
const { ImageDosHeader, ImageNtHeaders, ImageDirectoryEntry } = Format;
const execFile = promisify(execFileCb);
// See https://en.wikipedia.org/wiki/Darwin_(operating_system)#Darwin_20_onwards
@@ -86,6 +90,164 @@ async function macosVersionCheck(file: string) {
);
}
// See: https://learn.microsoft.com/en-us/windows/win32/debug/pe-format?redirectedfrom=MSDN
// See: https://0xrick.github.io/win-internals/pe6/
const EMPTY_IMPORT_ENTRY = Buffer.alloc(4 * 5);
const EMPTY_DELAY_IMPORT_ENTRY = Buffer.alloc(4 * 8);
const DLL_TABLES = new Map([
// https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#import-directory-table
[ImageDirectoryEntry.Import, { empty: EMPTY_IMPORT_ENTRY, nameOffset: 12 }],
// https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#the-delay-load-directory-table
[
ImageDirectoryEntry.DelayImport,
{ empty: EMPTY_DELAY_IMPORT_ENTRY, nameOffset: 4 },
],
]);
const ALLOWED_DLLS = new Set([
'advapi32.dll',
'api-ms-win-core-handle-l1-1-0.dll',
'api-ms-win-core-realtime-l1-1-1.dll',
'api-ms-win-core-synch-l1-2-0.dll',
'api-ms-win-core-winrt-error-l1-1-1.dll',
'api-ms-win-core-winrt-l1-1-0.dll',
'api-ms-win-core-winrt-string-l1-1-0.dll',
'api-ms-win-power-base-l1-1-0.dll',
'api-ms-win-shcore-scaling-l1-1-1.dll',
'avrt.dll',
'bcrypt.dll',
'bcryptprimitives.dll',
'bthprops.cpl',
'cfgmgr32.dll',
'comctl32.dll',
'comdlg32.dll',
'crypt32.dll',
'd3d11.dll',
'd3d12.dll',
'dbghelp.dll',
'dcomp.dll',
'dhcpcsvc.dll',
'dwmapi.dll',
'dwrite.dll',
'dxgi.dll',
'ffmpeg.dll',
'fontsub.dll',
'gdi32.dll',
'hid.dll',
'iphlpapi.dll',
'kernel32.dll',
'mf.dll',
'mfplat.dll',
'mfreadwrite.dll',
'mmdevapi.dll',
'msdmo.dll',
'ncrypt.dll',
'node.exe',
'ntdll.dll',
'ole32.dll',
'oleacc.dll',
'oleaut32.dll',
'pdh.dll',
'powrprof.dll',
'propsys.dll',
'psapi.dll',
'rpcrt4.dll',
'secur32.dll',
'setupapi.dll',
'shell32.dll',
'shlwapi.dll',
'uiautomationcore.dll',
'urlmon.dll',
'user32.dll',
'userenv.dll',
'uxtheme.dll',
'version.dll',
'winhttp.dll',
'winmm.dll',
'winspool.drv',
'wintrust.dll',
'winusb.dll',
'ws2_32.dll',
'wtsapi32.dll',
]);
async function windowsDllImportCheck(file: string): Promise<void> {
console.log(`${file}: checking...`);
const fileData = await readFile(file);
const dosHeader = ImageDosHeader.from(fileData);
const ntHeaders = ImageNtHeaders.from(fileData, dosHeader.newHeaderAddress);
const ntExecutable = NtExecutable.from(fileData, {
ignoreCert: true,
});
function cstr(data: Buffer<ArrayBuffer>, offset: number): string {
for (let end = offset; end < data.length; end += 1) {
if (data[end] === 0) {
return data.subarray(offset, end).toString();
}
}
throw new Error('Invalid cstring');
}
const imports = new Set<string>();
for (const [entryType, { empty, nameOffset }] of DLL_TABLES) {
const section = ntExecutable.getSectionByEntry(entryType);
const imageDirectoryEntry =
ntHeaders.optionalHeaderDataDirectory.get(entryType);
if (section?.data == null || imageDirectoryEntry == null) {
console.warn(`${file}: no ${entryType} directory entry`);
continue;
}
// section contains the directory entry, but at offset determined by the
// image directory entry
const entryData = Buffer.from(section.data).subarray(
imageDirectoryEntry.virtualAddress - section.info.virtualAddress
);
for (let i = 0; i < entryData.byteLength; i += empty.byteLength) {
const entry = entryData.subarray(i, i + empty.byteLength);
// Empty descriptor indicates end of the array
if (entry.equals(empty)) {
break;
}
const name = entry.readInt32LE(nameOffset);
if (name <= 0) {
continue;
}
imports.add(
cstr(
fileData,
// `name` is offest relative to loaded section, translate it back
// to the file offset
name - section.info.virtualAddress + section.info.pointerToRawData
).toLowerCase()
);
}
}
let disallowed = 0;
for (const name of imports) {
if (ALLOWED_DLLS.has(name)) {
console.log(` Allowed: ${name}`);
} else {
console.error(` Disallowed: ${name}`);
disallowed += 1;
}
}
if (disallowed !== 0) {
throw new Error(`${basename(file)} contains disallowed dll imports`);
}
}
function padGlibcVersion(version: string) {
if (/^\d+\.\d+$/.test(version)) {
return `${version}.0`;
@@ -166,13 +328,14 @@ async function main() {
}
)),
];
if (process.platform === 'darwin') {
for (const file of BINARY_FILES) {
for (const file of BINARY_FILES) {
if (process.platform === 'darwin') {
// eslint-disable-next-line no-await-in-loop
await macosVersionCheck(file);
}
} else if (process.platform === 'linux') {
for (const file of BINARY_FILES) {
} else if (process.platform === 'win32') {
// eslint-disable-next-line no-await-in-loop
await windowsDllImportCheck(file);
} else if (process.platform === 'linux') {
// eslint-disable-next-line no-await-in-loop
await linuxVersionCheck(file);
}