Skip to content

Commit da81930

Browse files
branchseercodebytere
authored andcommitted
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 317621b), 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: #33944 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: James M Snell <[email protected]>
1 parent c1664a9 commit da81930

File tree

4 files changed

+101
-1
lines changed

4 files changed

+101
-1
lines changed

src/node.cc

+4-1
Original file line numberDiff line numberDiff line change
@@ -737,7 +737,10 @@ void ResetStdio() {
737737
err = tcsetattr(fd, TCSANOW, &s.termios);
738738
while (err == -1 && errno == EINTR); // NOLINT
739739
CHECK_EQ(0, pthread_sigmask(SIG_UNBLOCK, &sa, nullptr));
740-
CHECK_EQ(0, err);
740+
741+
// Normally we expect err == 0. But if macOS App Sandbox is enabled,
742+
// tcsetattr will fail with err == -1 and errno == EPERM.
743+
CHECK_IMPLIES(err != 0, err == -1 && errno == EPERM);
741744
}
742745
}
743746
#endif // __POSIX__
+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>CFBundleExecutable</key>
6+
<string>node</string>
7+
<key>CFBundleIdentifier</key>
8+
<string>org.nodejs.test.node_sandboxed</string>
9+
<key>CFBundleInfoDictionaryVersion</key>
10+
<string>6.0</string>
11+
<key>CFBundleName</key>
12+
<string>node_sandboxed</string>
13+
<key>CFBundlePackageType</key>
14+
<string>APPL</string>
15+
<key>CFBundleShortVersionString</key>
16+
<string>1.0</string>
17+
<key>CFBundleSupportedPlatforms</key>
18+
<array>
19+
<string>MacOSX</string>
20+
</array>
21+
<key>CFBundleVersion</key>
22+
<string>1</string>
23+
</dict>
24+
</plist>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>com.apple.security.app-sandbox</key>
6+
<true/>
7+
</dict>
8+
</plist>
+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
'use strict';
2+
const common = require('../common');
3+
if (process.platform !== 'darwin')
4+
common.skip('App Sandbox is only avaliable on Darwin');
5+
6+
const fixtures = require('../common/fixtures');
7+
const tmpdir = require('../common/tmpdir');
8+
const assert = require('assert');
9+
const child_process = require('child_process');
10+
const path = require('path');
11+
const fs = require('fs');
12+
const os = require('os');
13+
14+
const nodeBinary = process.execPath;
15+
16+
tmpdir.refresh();
17+
18+
const appBundlePath = path.join(tmpdir.path, 'node_sandboxed.app');
19+
const appBundleContentPath = path.join(appBundlePath, 'Contents');
20+
const appExecutablePath = path.join(
21+
appBundleContentPath, 'MacOS', 'node');
22+
23+
// Construct the app bundle and put the node executable in it:
24+
// node_sandboxed.app/
25+
// └── Contents
26+
// ├── Info.plist
27+
// ├── MacOS
28+
// │ └── node
29+
fs.mkdirSync(appBundlePath);
30+
fs.mkdirSync(appBundleContentPath);
31+
fs.mkdirSync(path.join(appBundleContentPath, 'MacOS'));
32+
fs.copyFileSync(
33+
fixtures.path('macos-app-sandbox', 'Info.plist'),
34+
path.join(appBundleContentPath, 'Info.plist'));
35+
fs.copyFileSync(
36+
nodeBinary,
37+
appExecutablePath);
38+
39+
40+
// Sign the app bundle with sandbox entitlements:
41+
assert.strictEqual(
42+
child_process.spawnSync('/usr/bin/codesign', [
43+
'--entitlements', fixtures.path(
44+
'macos-app-sandbox', 'node_sandboxed.entitlements'),
45+
'-s', '-',
46+
appBundlePath
47+
]).status,
48+
0);
49+
50+
// Sandboxed app shouldn't be able to read the home dir
51+
assert.notStrictEqual(
52+
child_process.spawnSync(appExecutablePath, [
53+
'-e', 'fs.readdirSync(process.argv[1])', os.homedir()
54+
]).status,
55+
0);
56+
57+
if (process.stdin.isTTY) {
58+
// Run the sandboxed node instance with inherited tty stdin
59+
const spawnResult = child_process.spawnSync(
60+
appExecutablePath, ['-e', ''],
61+
{ stdio: 'inherit' }
62+
);
63+
64+
assert.strictEqual(spawnResult.signal, null);
65+
}

0 commit comments

Comments
 (0)