Skip to content

Commit

Permalink
Introduce two new options to Linux sandbox wrapper:
Browse files Browse the repository at this point in the history
* -n: Create a new network namespace with only loopback interface.
* -r: set the uid/gid inside the sandbox to be root (instead of nobody)
   so that setuid programs like ping can still run when needed.

--
Change-Id: I8ab434e47e0f6933ee9de02e135c8daec39fe73f
Reviewed-on: https://bazel-review.googlesource.com/#/c/2101/
MOS_MIGRATED_REVID=104858163
  • Loading branch information
mzhaom authored and hanwen committed Oct 8, 2015
1 parent fdc46c9 commit 1940933
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 15 deletions.
2 changes: 1 addition & 1 deletion scripts/bootstrap/compile.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
17 changes: 16 additions & 1 deletion src/main/tools/BUILD
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
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"],
Expand Down Expand Up @@ -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(
Expand Down
46 changes: 34 additions & 12 deletions src/main/tools/namespace-sandbox.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include <sys/wait.h>
#include <unistd.h>

#include "network-tools.h"
#include "process-tools.h"

#define PRINT_DEBUG(...) \
Expand Down Expand Up @@ -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().
Expand Down Expand Up @@ -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));

Expand Down Expand Up @@ -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);
}

Expand All @@ -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.
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand All @@ -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;
Expand Down Expand Up @@ -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) {
Expand All @@ -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) {
Expand Down Expand Up @@ -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);
Expand Down
46 changes: 46 additions & 0 deletions src/main/tools/network-tools.c
Original file line number Diff line number Diff line change
@@ -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));
}
21 changes: 21 additions & 0 deletions src/main/tools/network-tools.h
Original file line number Diff line number Diff line change
@@ -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__
22 changes: 21 additions & 1 deletion src/test/shell/bazel/namespace-runner_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"
Expand Down

0 comments on commit 1940933

Please sign in to comment.