diff --git a/src/node.cc b/src/node.cc index b7435d67f839ea..438b08391dc49c 100644 --- a/src/node.cc +++ b/src/node.cc @@ -737,7 +737,10 @@ void ResetStdio() { err = tcsetattr(fd, TCSANOW, &s.termios); while (err == -1 && errno == EINTR); // NOLINT CHECK_EQ(0, pthread_sigmask(SIG_UNBLOCK, &sa, nullptr)); - CHECK_EQ(0, err); + + // Normally we expect err == 0. But if macOS App Sandbox is enabled, + // tcsetattr will fail with err == -1 and errno == EPERM. + CHECK_IMPLIES(err != 0, err == -1 && errno == EPERM); } } #endif // __POSIX__ diff --git a/test/fixtures/macos-app-sandbox/Info.plist b/test/fixtures/macos-app-sandbox/Info.plist new file mode 100644 index 00000000000000..38362085af4bf8 --- /dev/null +++ b/test/fixtures/macos-app-sandbox/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleExecutable + node + CFBundleIdentifier + org.nodejs.test.node_sandboxed + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + node_sandboxed + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSupportedPlatforms + + MacOSX + + CFBundleVersion + 1 + + \ No newline at end of file diff --git a/test/fixtures/macos-app-sandbox/node_sandboxed.entitlements b/test/fixtures/macos-app-sandbox/node_sandboxed.entitlements new file mode 100644 index 00000000000000..852fa1a4728ae4 --- /dev/null +++ b/test/fixtures/macos-app-sandbox/node_sandboxed.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/test/parallel/test-macos-app-sandbox.js b/test/parallel/test-macos-app-sandbox.js new file mode 100644 index 00000000000000..f7fcf5e5728815 --- /dev/null +++ b/test/parallel/test-macos-app-sandbox.js @@ -0,0 +1,65 @@ +'use strict'; +const common = require('../common'); +if (process.platform !== 'darwin') + common.skip('App Sandbox is only avaliable on Darwin'); + +const fixtures = require('../common/fixtures'); +const tmpdir = require('../common/tmpdir'); +const assert = require('assert'); +const child_process = require('child_process'); +const path = require('path'); +const fs = require('fs'); +const os = require('os'); + +const nodeBinary = process.execPath; + +tmpdir.refresh(); + +const appBundlePath = path.join(tmpdir.path, 'node_sandboxed.app'); +const appBundleContentPath = path.join(appBundlePath, 'Contents'); +const appExecutablePath = path.join( + appBundleContentPath, 'MacOS', 'node'); + +// Construct the app bundle and put the node executable in it: +// node_sandboxed.app/ +// └── Contents +// ├── Info.plist +// ├── MacOS +// │ └── node +fs.mkdirSync(appBundlePath); +fs.mkdirSync(appBundleContentPath); +fs.mkdirSync(path.join(appBundleContentPath, 'MacOS')); +fs.copyFileSync( + fixtures.path('macos-app-sandbox', 'Info.plist'), + path.join(appBundleContentPath, 'Info.plist')); +fs.copyFileSync( + nodeBinary, + appExecutablePath); + + +// Sign the app bundle with sandbox entitlements: +assert.strictEqual( + child_process.spawnSync('/usr/bin/codesign', [ + '--entitlements', fixtures.path( + 'macos-app-sandbox', 'node_sandboxed.entitlements'), + '-s', '-', + appBundlePath + ]).status, + 0); + +// Sandboxed app shouldn't be able to read the home dir +assert.notStrictEqual( + child_process.spawnSync(appExecutablePath, [ + '-e', 'fs.readdirSync(process.argv[1])', os.homedir() + ]).status, + 0); + +if (process.stdin.isTTY) { + // Run the sandboxed node instance with inherited tty stdin + const spawnResult = child_process.spawnSync( + appExecutablePath, ['-e', ''], + { stdio: 'inherit' } + ); + + assert.strictEqual(spawnResult.signal, null); +}