From b57778ff26a45d6422f52de24a41bca9028f84de Mon Sep 17 00:00:00 2001 From: patr0nus Date: Thu, 18 Jun 2020 22:41:56 +0800 Subject: [PATCH] src: tolerate EPERM returned from tcsetattr macOS app sandbox makes tcsetattr return EPERM. The CHECK_EQ(0, err) here would fail when a sandboxed Node.js process is exiting. This commit fixes this issue. * test: add test for running in macOS app sandbox Bare-bone command-line executables cannot run directly in the app sandbox. To test that Node.js is able to run in the sandbox (and to test the fix in 317621b4a12562eb75055a67bb2c5556f53fe017), this commit creates a typical Cocoa app bundle, puts the node executable in it and calles Apple's codesign command to enable sandbox. * test: use process.execPath to get path of testing node PR-URL: https://github.com/nodejs/node/pull/33944 Reviewed-By: Anna Henningsen Reviewed-By: James M Snell --- src/node.cc | 5 +- test/fixtures/macos-app-sandbox/Info.plist | 24 +++++++ .../node_sandboxed.entitlements | 8 +++ test/parallel/test-macos-app-sandbox.js | 65 +++++++++++++++++++ 4 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 test/fixtures/macos-app-sandbox/Info.plist create mode 100644 test/fixtures/macos-app-sandbox/node_sandboxed.entitlements create mode 100644 test/parallel/test-macos-app-sandbox.js diff --git a/src/node.cc b/src/node.cc index 4ff7824b001168..728785d5d2773d 100644 --- a/src/node.cc +++ b/src/node.cc @@ -634,7 +634,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); +}