diff options
-rwxr-xr-x | scripts/bootstrap/compile.sh | 2 | ||||
-rw-r--r-- | src/main/tools/BUILD | 17 | ||||
-rw-r--r-- | src/main/tools/namespace-sandbox.c | 46 | ||||
-rw-r--r-- | src/main/tools/network-tools.c | 46 | ||||
-rw-r--r-- | src/main/tools/network-tools.h | 21 | ||||
-rwxr-xr-x | src/test/shell/bazel/namespace-runner_test.sh | 22 |
6 files changed, 139 insertions, 15 deletions
diff --git a/scripts/bootstrap/compile.sh b/scripts/bootstrap/compile.sh index 523dab5499..f9605b5b1e 100755 --- a/scripts/bootstrap/compile.sh +++ b/scripts/bootstrap/compile.sh @@ -332,7 +332,7 @@ run_silent "${CC}" -o ${OUTPUT_DIR}/process-wrapper -std=c99 src/main/tools/proc log "Compiling namespace-sandbox..." if [[ $PLATFORM == "linux" ]]; then - run_silent "${CC}" -o ${OUTPUT_DIR}/namespace-sandbox -std=c99 src/main/tools/namespace-sandbox.c src/main/tools/process-tools.c -lm + run_silent "${CC}" -o ${OUTPUT_DIR}/namespace-sandbox -std=c99 src/main/tools/namespace-sandbox.c src/main/tools/network-tools.c src/main/tools/process-tools.c -lm else run_silent "${CC}" -o ${OUTPUT_DIR}/namespace-sandbox -std=c99 src/main/tools/namespace-sandbox-dummy.c -lm fi diff --git a/src/main/tools/BUILD b/src/main/tools/BUILD index 67b3888d15..4a87f650a2 100644 --- a/src/main/tools/BUILD +++ b/src/main/tools/BUILD @@ -1,6 +1,14 @@ package(default_visibility = ["//src:__subpackages__"]) cc_library( + name = "network-tools", + srcs = ["network-tools.c"], + hdrs = ["network-tools.h"], + copts = ["-std=c99"], + deps = [":process-tools"], +) + +cc_library( name = "process-tools", srcs = ["process-tools.c"], hdrs = ["process-tools.h"], @@ -29,7 +37,14 @@ cc_binary( }), copts = ["-std=c99"], linkopts = ["-lm"], - deps = [":process-tools"], + deps = select({ + "//src:darwin": [], + "//src:freebsd": [], + "//conditions:default": [ + ":process-tools", + ":network-tools", + ], + }), ) filegroup( diff --git a/src/main/tools/namespace-sandbox.c b/src/main/tools/namespace-sandbox.c index 4380e933c9..ef13213674 100644 --- a/src/main/tools/namespace-sandbox.c +++ b/src/main/tools/namespace-sandbox.c @@ -32,6 +32,7 @@ #include <sys/wait.h> #include <unistd.h> +#include "network-tools.h" #include "process-tools.h" #define PRINT_DEBUG(...) \ @@ -65,6 +66,8 @@ struct Options { int num_mounts; // How many mounts were specified char **create_dirs; // empty dirs to create (-d) int num_create_dirs; // How many empty dirs to create were specified + int fake_root; // Pretend to be root inside the namespace. + int create_netns; // If 1, create a new network namespace. }; // Child function used by CheckNamespacesSupported() in call to clone(). @@ -92,7 +95,7 @@ static int CheckNamespacesSupported() { // spend time sleeping and retrying here until it eventually works (or not). CHECK_CALL(pid = clone(CheckNamespacesSupportedChild, stackTop, CLONE_NEWUSER | CLONE_NEWNS | CLONE_NEWUTS | - CLONE_NEWIPC | SIGCHLD, + CLONE_NEWIPC | CLONE_NEWNET | SIGCHLD, NULL)); CHECK_CALL(waitpid(pid, NULL, 0)); @@ -134,9 +137,12 @@ static void Usage(int argc, char *const *argv, const char *fmt, ...) { " The -M option specifies which directory to mount, the -m option " "specifies where to\n" " mount it in the sandbox.\n" + " -n if set, a new network namespace will be created\n" + " -r if set, make the uid/gid be root, otherwise use nobody\n" " -D if set, debug info will be printed\n" " -l <file> redirect stdout to a file\n" - " -L <file> redirect stderr to a file\n"); + " -L <file> redirect stderr to a file\n" + ); exit(EXIT_FAILURE); } @@ -147,7 +153,7 @@ static void ParseCommandLine(int argc, char *const *argv, struct Options *opt) { extern int optind, optopt; int c; - while ((c = getopt(argc, argv, ":CDS:W:t:T:d:M:m:l:L:")) != -1) { + while ((c = getopt(argc, argv, ":CDd:l:L:m:M:nrt:T:S:W:")) != -1) { switch (c) { case 'C': // Shortcut for the "does this system support sandboxing" check. @@ -222,6 +228,12 @@ static void ParseCommandLine(int argc, char *const *argv, struct Options *opt) { } opt->mount_targets[opt->num_mounts++] = optarg; break; + case 'n': + opt->create_netns = 1; + break; + case 'r': + opt->fake_root = 1; + break; case 'D': global_debug = true; break; @@ -268,7 +280,7 @@ static void ParseCommandLine(int argc, char *const *argv, struct Options *opt) { } } -static void CreateNamespaces() { +static void CreateNamespaces(int create_netns) { // This weird workaround is necessary due to unshare seldomly failing with // EINVAL due to a race condition in the Linux kernel (see // https://lkml.org/lkml/2015/7/28/833). An alternative would be to use @@ -277,7 +289,8 @@ static void CreateNamespaces() { int tries = 0; const int max_tries = 100; while (tries++ < max_tries) { - if (unshare(CLONE_NEWUSER | CLONE_NEWNS | CLONE_NEWUTS | CLONE_NEWIPC) == + if (unshare(CLONE_NEWUSER | CLONE_NEWNS | CLONE_NEWUTS | CLONE_NEWIPC | + (create_netns ? CLONE_NEWNET : 0)) == 0) { PRINT_DEBUG("unshare succeeded after %d tries\n", tries); return; @@ -455,7 +468,7 @@ static int WriteFile(const char *filename, const char *fmt, ...) { return r; } -static void SetupUserNamespace(int uid, int gid) { +static void SetupUserNamespace(int uid, int gid, int new_uid, int new_gid) { // Disable needs for CAP_SETGID int r = WriteFile("/proc/self/setgroups", "deny"); if (r < 0 && errno != ENOENT) { @@ -471,11 +484,11 @@ static void SetupUserNamespace(int uid, int gid) { // We can't be root in the child, because some code may assume that running as // root grants it certain capabilities that it doesn't in fact have. It's // safer to let the child think that it is just a normal user. - CHECK_CALL(WriteFile("/proc/self/uid_map", "%d %d 1\n", kNobodyUid, uid)); - CHECK_CALL(WriteFile("/proc/self/gid_map", "%d %d 1\n", kNobodyGid, gid)); + CHECK_CALL(WriteFile("/proc/self/uid_map", "%d %d 1\n", new_uid, uid)); + CHECK_CALL(WriteFile("/proc/self/gid_map", "%d %d 1\n", new_gid, gid)); - CHECK_CALL(setresuid(kNobodyUid, kNobodyUid, kNobodyUid)); - CHECK_CALL(setresgid(kNobodyGid, kNobodyGid, kNobodyGid)); + CHECK_CALL(setresuid(new_uid, new_uid, new_uid)); + CHECK_CALL(setresgid(new_gid, new_gid, new_gid)); } static void ChangeRoot(struct Options *opt) { @@ -588,14 +601,23 @@ int main(int argc, char *const argv[]) { PRINT_DEBUG("working dir is %s\n", (opt.working_dir != NULL) ? opt.working_dir : "/ (default)"); - CreateNamespaces(); + CreateNamespaces(opt.create_netns); + if (opt.create_netns) { + // Enable the loopback interface because some application may want + // to use it. + BringupInterface("lo"); + } // Make our mount namespace private, so that further mounts do not affect the // outside environment. CHECK_CALL(mount("none", "/", NULL, MS_REC | MS_PRIVATE, NULL)); SetupDirectories(&opt); - SetupUserNamespace(uid, gid); + if (opt.fake_root) { + SetupUserNamespace(uid, gid, 0, 0); + } else { + SetupUserNamespace(uid, gid, kNobodyUid, kNobodyGid); + } ChangeRoot(&opt); SpawnCommand(opt.args, opt.timeout_secs); diff --git a/src/main/tools/network-tools.c b/src/main/tools/network-tools.c new file mode 100644 index 0000000000..6e088d4cea --- /dev/null +++ b/src/main/tools/network-tools.c @@ -0,0 +1,46 @@ +// Copyright 2015 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#define _GNU_SOURCE + +#include <net/if.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <unistd.h> + +#include "process-tools.h" +#include "network-tools.h" + +void BringupInterface(const char *name) { + int fd; + + struct ifreq ifr; + + CHECK_CALL(fd = socket(AF_INET, SOCK_DGRAM, 0)); + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, name, IF_NAMESIZE); + + CHECK_CALL(ioctl(fd, SIOCGIFINDEX, &ifr)); + + // Enable the interface + ifr.ifr_flags |= IFF_UP; + CHECK_CALL(ioctl(fd, SIOCSIFFLAGS, &ifr)); + + CHECK_CALL(close(fd)); +} diff --git a/src/main/tools/network-tools.h b/src/main/tools/network-tools.h new file mode 100644 index 0000000000..9c90aabb25 --- /dev/null +++ b/src/main/tools/network-tools.h @@ -0,0 +1,21 @@ +// Copyright 2015 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef NETWORK_TOOLS_H__ +#define NETWORK_TOOLS_H__ + +// Bring up the given network interface like "lo". +void BringupInterface(const char *name); + +#endif // NETWORK_TOOLS_H__ diff --git a/src/test/shell/bazel/namespace-runner_test.sh b/src/test/shell/bazel/namespace-runner_test.sh index 4e8f91ae8b..2b52012941 100755 --- a/src/test/shell/bazel/namespace-runner_test.sh +++ b/src/test/shell/bazel/namespace-runner_test.sh @@ -59,7 +59,7 @@ function check_sandbox_allowed { #define _GNU_SOURCE #include <sched.h> int main() { - return unshare(CLONE_NEWNS | CLONE_NEWUTS | CLONE_NEWIPC | CLONE_NEWUSER); + return unshare(CLONE_NEWNS | CLONE_NEWUTS | CLONE_NEWIPC | CLONE_NEWUSER | CLONE_NEWNET); } EOF cat <<'EOF' >test/BUILD @@ -93,6 +93,26 @@ function test_basic_functionality() { assert_output "hi there" "" } +function test_default_user_is_nobody() { + $WRAPPER $WRAPPER_DEFAULT_OPTS -l $OUT -L $ERR -- /usr/bin/id || fail + assert_output "uid=65534 gid=65534 groups=65534" "" +} + +function test_user_switched_to_root() { + $WRAPPER $WRAPPER_DEFAULT_OPTS -r -l $OUT -L $ERR -- /usr/bin/id || fail + assert_output "uid=0 gid=0 groups=65534,0" "" +} + +function test_network_namespace() { + $WRAPPER $WRAPPER_DEFAULT_OPTS -n -l $OUT -L $ERR -- /bin/ip link ls || fail + assert_contains "LOOPBACK,UP" "$OUT" +} + +function test_ping_loopback() { + $WRAPPER $WRAPPER_DEFAULT_OPTS -n -r -l $OUT -L $ERR -- /bin/ping -c 1 127.0.0.1 || fail + assert_contains "1 received" "$OUT" +} + function test_to_stderr() { $WRAPPER $WRAPPER_DEFAULT_OPTS -l $OUT -L $ERR -- /bin/bash -c "/bin/echo hi there >&2" || fail assert_output "" "hi there" |