diff --git a/.github/workflows/test-asan.yml b/.github/workflows/test-asan.yml index 059018b74e5e58..fc8d38945dd397 100644 --- a/.github/workflows/test-asan.yml +++ b/.github/workflows/test-asan.yml @@ -45,6 +45,7 @@ jobs: CXX: clang++ LINK: clang++ CONFIG_FLAGS: --enable-asan + ASAN: true steps: - uses: actions/checkout@v3 with: diff --git a/test/common/index.js b/test/common/index.js index eabe50a0e033c5..7418b9b8c980ab 100644 --- a/test/common/index.js +++ b/test/common/index.js @@ -120,6 +120,7 @@ const isFreeBSD = process.platform === 'freebsd'; const isOpenBSD = process.platform === 'openbsd'; const isLinux = process.platform === 'linux'; const isOSX = process.platform === 'darwin'; +const isAsan = process.env.ASAN !== undefined; const isPi = (() => { try { // Normal Raspberry Pi detection is to find the `Raspberry Pi` string in @@ -898,6 +899,7 @@ const common = { invalidArgTypeHelper, isAIX, isAlive, + isAsan, isDumbTerminal, isFreeBSD, isLinux, diff --git a/test/parallel/test-strace-openat-openssl.js b/test/parallel/test-strace-openat-openssl.js new file mode 100644 index 00000000000000..f5202eea8a7b24 --- /dev/null +++ b/test/parallel/test-strace-openat-openssl.js @@ -0,0 +1,61 @@ +'use strict'; + +const common = require('../common'); +const { spawn, spawnSync } = require('node:child_process'); +const { createInterface } = require('node:readline'); +const assert = require('node:assert'); + +if (!common.hasCrypto) + common.skip('missing crypto'); +if (!common.isLinux) + common.skip('linux only'); +if (common.isAsan) + common.skip('strace does not work well with address sanitizer builds'); +if (spawnSync('strace').error !== undefined) { + common.skip('missing strace'); +} + +{ + const allowedOpenCalls = new Set([ + '/etc/ssl/openssl.cnf', + ]); + const strace = spawn('strace', [ + '-f', '-ff', + '-e', 'trace=open,openat', + '-s', '512', + '-D', process.execPath, '-e', 'require("crypto")', + ]); + + // stderr is the default for strace + const rl = createInterface({ input: strace.stderr }); + rl.on('line', (line) => { + if (!line.startsWith('open')) { + return; + } + + const file = line.match(/"(.*?)"/)[1]; + // skip .so reading attempt + if (file.match(/.+\.so(\.?)/) !== null) { + return; + } + // skip /proc/* + if (file.match(/\/proc\/.+/) !== null) { + return; + } + + assert(allowedOpenCalls.delete(file), `${file} is not in the list of allowed openat calls`); + }); + const debugOutput = []; + strace.stderr.setEncoding('utf8'); + strace.stderr.on('data', (chunk) => { + debugOutput.push(chunk.toString()); + }); + strace.on('error', common.mustNotCall()); + strace.on('exit', common.mustCall((code) => { + assert.strictEqual(code, 0, debugOutput); + const missingKeys = Array.from(allowedOpenCalls.keys()); + if (missingKeys.length) { + assert.fail(`The following openat call are missing: ${missingKeys.join(',')}`); + } + })); +}