Skip to content

Commit 9a13051

Browse files
crydell-ericssoncopybara-github
authored andcommitted
Functionality for pseudoterminals in linux sandbox
As brought up in issue #5373 , the Linux sandbox does not allow processes that run inside it to open pseudoterminals. These changes enable this by addressing the two main underlying issues: - `/dev/pts` can not be read-only if a new pseudoterminal is to be created. These changes make `dev/pts` writable when remounting file systems during sandbox initialization. - The group associated with pseudoterminals is "tty". After creating a new pseudoterminal, its gid has to be changed. If there is no gid mapping in the user namespace that corresponds to "tty", this group will not be known inside the sandbox. This causes issues in some Linux distributions, since they do not allow changing the group of a file to one that is not known inside the current user namespace. These changes map the gid of the user to the one corresponding to "tty" inside the sandbox in order to avoid this issue. These changes introduce the `-P` flag to `linux-sandbox` in order to control whether or not the changes are applied, and the `--sandbox-explicit-pseudoterminal` to `bazel` in order to set this when calling bazel. Closes #14072. PiperOrigin-RevId: 481889631 Change-Id: I5d686769096003a80d4ceffe0ccfcd19c6a7d174
1 parent 3e83fbe commit 9a13051

7 files changed

Lines changed: 73 additions & 1 deletion

File tree

src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxUtil.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ public static class CommandLineBuilder {
7272
private boolean createNetworkNamespace = false;
7373
private boolean useFakeRoot = false;
7474
private boolean useFakeUsername = false;
75+
private boolean enablePseudoterminal = false;
7576
private boolean useDebugMode = false;
7677
private boolean sigintSendsSigterm = false;
7778

@@ -188,6 +189,16 @@ public CommandLineBuilder setUseFakeUsername(boolean useFakeUsername) {
188189
return this;
189190
}
190191

192+
/**
193+
* Sets whether to set group to 'tty' and make /dev/pts writable inside the sandbox in order to
194+
* enable the use of pseudoterminals.
195+
*/
196+
@CanIgnoreReturnValue
197+
public CommandLineBuilder setEnablePseudoterminal(boolean enablePseudoterminal) {
198+
this.enablePseudoterminal = enablePseudoterminal;
199+
return this;
200+
}
201+
191202
/** Sets whether to enable debug mode (e.g. to print debugging messages). */
192203
@CanIgnoreReturnValue
193204
public CommandLineBuilder setUseDebugMode(boolean useDebugMode) {
@@ -262,6 +273,9 @@ public ImmutableList<String> build() {
262273
if (useFakeUsername) {
263274
commandLineBuilder.add("-U");
264275
}
276+
if (enablePseudoterminal) {
277+
commandLineBuilder.add("-P");
278+
}
265279
if (useDebugMode) {
266280
commandLineBuilder.add("-D");
267281
}

src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedSpawnRunner.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@ protected SandboxedSpawn prepareSpawn(Spawn spawn, SpawnExecutionContext context
229229
.setTmpfsDirectories(ImmutableSet.copyOf(getSandboxOptions().sandboxTmpfsPath))
230230
.setBindMounts(getBindMounts(blazeDirs, sandboxExecRoot, sandboxTmp))
231231
.setUseFakeHostname(getSandboxOptions().sandboxFakeHostname)
232+
.setEnablePseudoterminal(getSandboxOptions().sandboxExplicitPseudoterminal)
232233
.setCreateNetworkNamespace(
233234
!(allowNetwork
234235
|| Spawns.requiresNetwork(

src/main/java/com/google/devtools/build/lib/sandbox/SandboxOptions.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,18 @@ public String getTypeDescription() {
131131
help = "Change the current username to 'nobody' for sandboxed actions.")
132132
public boolean sandboxFakeUsername;
133133

134+
@Option(
135+
name = "sandbox_explicit_pseudoterminal",
136+
defaultValue = "false",
137+
documentationCategory = OptionDocumentationCategory.EXECUTION_STRATEGY,
138+
effectTags = {OptionEffectTag.EXECUTION},
139+
help =
140+
"Explicitly enable the creation of pseudoterminals for sandboxed actions."
141+
+ " Some linux distributions require setting the group id of the process to 'tty'"
142+
+ " inside the sandbox in order for pseudoterminals to function. If this is"
143+
+ " causing issues, this flag can be disabled to enable other groups to be used.")
144+
public boolean sandboxExplicitPseudoterminal;
145+
134146
@Option(
135147
name = "sandbox_block_path",
136148
allowMultiple = true,

src/main/tools/linux-sandbox-options.cc

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ static void Usage(char *program_name, const char *fmt, ...) {
7272
" -N if set, a new network namespace will be created\n"
7373
" -R if set, make the uid/gid be root\n"
7474
" -U if set, make the uid/gid be nobody\n"
75+
" -P if set, make the gid be tty and make /dev/pts writable\n"
7576
" -D if set, debug info will be printed\n"
7677
" -h <sandbox-dir> if set, chroot to sandbox-dir and only "
7778
" mount whats been specified with -M/-m for improved hermeticity. "
@@ -97,7 +98,7 @@ static void ParseCommandLine(unique_ptr<vector<char *>> args) {
9798
bool source_specified = false;
9899

99100
while ((c = getopt(args->size(), args->data(),
100-
":W:T:t:il:L:w:e:M:m:S:h:HNRUD")) != -1) {
101+
":W:T:t:il:L:w:e:M:m:S:h:HNRUPD")) != -1) {
101102
if (c != 'M' && c != 'm') source_specified = false;
102103
switch (c) {
103104
case 'W':
@@ -215,6 +216,9 @@ static void ParseCommandLine(unique_ptr<vector<char *>> args) {
215216
}
216217
opt.fake_username = true;
217218
break;
219+
case 'P':
220+
opt.enable_pty = true;
221+
break;
218222
case 'D':
219223
opt.debug = true;
220224
break;

src/main/tools/linux-sandbox-options.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ struct Options {
5252
bool fake_root;
5353
// Set the username inside the sandbox to 'nobody' (-U)
5454
bool fake_username;
55+
// Enable writing to /dev/pts and map the user's gid to tty to enable
56+
// pseudoterminals (-P)
57+
bool enable_pty;
5558
// Print debugging messages (-D)
5659
bool debug;
5760
// Improved hermetic build using whitelisting strategy (-h)

src/main/tools/linux-sandbox-pid1.cc

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
#include <errno.h>
2323
#include <fcntl.h>
24+
#include <grp.h>
2425
#include <libgen.h>
2526
#include <math.h>
2627
#include <mntent.h>
@@ -242,6 +243,19 @@ static void SetupUserNamespace() {
242243
inner_uid = global_outer_uid;
243244
inner_gid = global_outer_gid;
244245
}
246+
if (opt.enable_pty) {
247+
// Change the group to "tty" regardless of what was previously set
248+
struct group grp;
249+
char buf[256];
250+
size_t buflen = sizeof(buf);
251+
struct group *result;
252+
getgrnam_r("tty", &grp, buf, buflen, &result);
253+
if (result == nullptr) {
254+
DIE("getgrnam_r");
255+
}
256+
inner_gid = grp.gr_gid;
257+
}
258+
245259
WriteFile("/proc/self/uid_map", "%u %u 1\n", inner_uid, global_outer_uid);
246260
WriteFile("/proc/self/gid_map", "%u %u 1\n", inner_gid, global_outer_gid);
247261
}
@@ -325,6 +339,10 @@ static bool ShouldBeWritable(const std::string &mnt_dir) {
325339
return true;
326340
}
327341

342+
if (opt.enable_pty && mnt_dir == "/dev/pts") {
343+
return true;
344+
}
345+
328346
for (const std::string &writable_file : opt.writable_files) {
329347
if (mnt_dir == writable_file) {
330348
return true;

src/test/shell/bazel/bazel_sandboxing_test.sh

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,26 @@ EOF
645645
bazel test --nocache_test_results --sandbox_fake_username --test_output=errors :test || fail "test did not pass"
646646
}
647647

648+
# Tests that a pseudoterminal can be opened in linux when --sandbox_explicit_pseudoterminal is active
649+
function test_can_enable_pseudoterminals() {
650+
if [[ "$(uname -s)" != Linux ]]; then
651+
echo "Skipping test: flag intended for linux systems"
652+
return 0
653+
fi
654+
655+
cat > test.py <<'EOF'
656+
import pty
657+
pty.openpty()
658+
EOF
659+
cat > BUILD <<'EOF'
660+
py_test(
661+
name = "test",
662+
srcs = ["test.py"],
663+
)
664+
EOF
665+
bazel test --sandbox_explicit_pseudoterminal :test || fail "test did not pass"
666+
}
667+
648668
# Tests that /proc/self == /proc/$$. This should always be true unless the PID namespace is active without /proc being remounted correctly.
649669
function test_sandbox_proc_self() {
650670
if [[ ! -d /proc/self ]]; then

0 commit comments

Comments
 (0)