Skip to content

Commit fa8bed0

Browse files
Add a --netns FD option for preexisting namespaces
Implement `--netns FD` to allow bubblewrap to join preexisting network namespaces via an open file descriptor. This mimics the behavior of `--userns` and `--pidns`. Enforce mutual exclusivity between `--netns` and `--unshare-net`. Add documentation to `bwrap.xml`, update shell completions, and add a new integration test case (`test-specifying-netns.sh`). Co-authored-by: rarensu <40872193+rarensu@users.noreply.github.com> Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> Signed-off-by: Richard Lawrence <rlawrence@tamu.edu>
1 parent 2f55bae commit fa8bed0

6 files changed

Lines changed: 91 additions & 0 deletions

File tree

bubblewrap.c

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ static char *opt_args_data = NULL; /* owned */
9090
static int opt_userns_fd = -1;
9191
static int opt_userns2_fd = -1;
9292
static int opt_pidns_fd = -1;
93+
static int opt_netns_fd = -1;
9394
static int opt_tmp_overlay_count = 0;
9495
static int next_perms = -1;
9596
static size_t next_size_arg = 0;
@@ -303,6 +304,7 @@ usage (int ecode, FILE *out)
303304
" --disable-userns Disable further use of user namespaces inside sandbox\n"
304305
" --assert-userns-disabled Fail unless further use of user namespace inside sandbox is disabled\n"
305306
" --pidns FD Use this pid namespace (as parent namespace if using --unshare-pid)\n"
307+
" --netns FD Use this net namespace (cannot combine with --unshare-net)\n"
306308
" --uid UID Custom uid in the sandbox (requires --unshare-user or --userns)\n"
307309
" --gid GID Custom gid in the sandbox (requires --unshare-user or --userns)\n"
308310
" --hostname NAME Custom hostname in the sandbox (requires --unshare-uts)\n"
@@ -2210,6 +2212,26 @@ parse_args_recurse (int *argcp,
22102212

22112213
opt_pidns_fd = the_fd;
22122214

2215+
argv += 1;
2216+
argc -= 1;
2217+
}
2218+
else if (strcmp (arg, "--netns") == 0)
2219+
{
2220+
int the_fd;
2221+
char *endptr;
2222+
2223+
if (argc < 2)
2224+
die ("--netns takes an argument");
2225+
2226+
if (opt_netns_fd != -1)
2227+
warn_only_last_option ("--netns");
2228+
2229+
the_fd = strtol (argv[1], &endptr, 10);
2230+
if (argv[1][0] == 0 || endptr[0] != 0 || the_fd < 0)
2231+
die ("Invalid fd: %s", argv[1]);
2232+
2233+
opt_netns_fd = the_fd;
2234+
22132235
argv += 1;
22142236
argc -= 1;
22152237
}
@@ -2656,6 +2678,9 @@ main (int argc,
26562678
if (opt_userns_fd != -1 && opt_unshare_user_try)
26572679
die ("--userns is not compatible with --unshare-user-try");
26582680

2681+
if (opt_netns_fd != -1 && opt_unshare_net)
2682+
die ("--netns is not compatible with --unshare-net");
2683+
26592684
if (opt_disable_userns && !opt_unshare_user)
26602685
die ("--disable-userns requires --unshare-user");
26612686

@@ -2798,6 +2823,9 @@ main (int argc,
27982823
die_with_error ("Joining specified user namespace failed");
27992824
}
28002825

2826+
if (opt_netns_fd != -1 && setns (opt_netns_fd, CLONE_NEWNET) != 0)
2827+
die_with_error ("Joining specified net namespace failed");
2828+
28012829
/* Sometimes we have uninteresting intermediate pids during the setup, set up code to pass the real pid down */
28022830
if (opt_pidns_fd != -1)
28032831
{
@@ -2938,6 +2966,10 @@ main (int argc,
29382966
*/
29392967
switch_to_user_with_privs ();
29402968

2969+
/* If a pre-existing network namespace is specified via --netns, we assume
2970+
* that it already contains a loopback interface, so we do not call
2971+
* loopback_setup().
2972+
*/
29412973
if (opt_unshare_net)
29422974
loopback_setup (); /* Will exit if unsuccessful */
29432975

bwrap.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,10 @@
197197
<listitem><para>Use an existing pid namespace instead of creating one. This is often used with --userns, because the pid namespace must be owned by the same user namespace that bwrap uses. </para>
198198
<para>Note that this can be combined with --unshare-pid, and in that case it means that the sandbox will be in its own pid namespace, which is a child of the passed in one.</para></listitem>
199199
</varlistentry>
200+
<varlistentry>
201+
<term><option>--netns <arg choice="plain">FD</arg></option></term>
202+
<listitem><para>Use an existing net namespace instead of creating one. This cannot be combined with <option>--unshare-net</option>.</para></listitem>
203+
</varlistentry>
200204
<varlistentry>
201205
<term><option>--uid <arg choice="plain">UID</arg></option></term>
202206
<listitem><para>Use a custom user id in the sandbox (requires <option>--unshare-user</option>)</para></listitem>

completions/bash/bwrap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ _bwrap() {
5151
--hostname
5252
--info-fd
5353
--lock-file
54+
--netns
5455
--overlay
5556
--overlay-src
5657
--perms

completions/zsh/_bwrap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ _bwrap_args=(
5454
'--lock-file[Take a lock on DEST while sandbox is running]:lock file:_files'
5555
'--mqueue[Mount new mqueue on DEST]:mount point for mqueue:_files -/'
5656
'--new-session[Create a new terminal session]'
57+
'--netns[Use this net namespace (cannot combine with --unshare-net)]: :'
5758
'--perms[Set permissions for next action argument]: :_guard "[0-7]#" "permissions in octal": :->after_perms'
5859
'--pidns[Use this user namespace (as parent namespace if using --unshare-pid)]: :'
5960
'--proc[Mount new procfs on DEST]:mount point for procfs:_files -/'

tests/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ executable(
1818
test_scripts = [
1919
'test-run.sh',
2020
'test-seccomp.py',
21+
'test-specifying-netns.sh',
2122
'test-specifying-pidns.sh',
2223
'test-specifying-userns.sh',
2324
]

tests/test-specifying-netns.sh

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#!/usr/bin/env bash
2+
3+
set -xeuo pipefail
4+
5+
srcd=$(cd $(dirname "$0") && pwd)
6+
. "${srcd}/libtest.sh"
7+
8+
echo "1..1"
9+
10+
# This test needs user namespaces
11+
if test -n "${bwrap_is_suid:-}"; then
12+
echo "ok - # SKIP no setuid support for --unshare-user"
13+
else
14+
# Start the server inside a bubblewrap sandbox with a new user and net namespace
15+
$RUN --info-fd 42 --unshare-user --unshare-net python3 -c "
16+
import socket
17+
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
18+
s.bind(('127.0.0.1', 0))
19+
port = s.getsockname()[1]
20+
with open('server-port.txt', 'w') as f:
21+
f.write(str(port))
22+
s.listen(1)
23+
conn, addr = s.accept()
24+
conn.sendall(b'hello from netns\n')
25+
conn.close()
26+
" 42>info.json &
27+
28+
# Wait for the server to write the port file
29+
while ! test -f server-port.txt; do sleep 0.1; done
30+
SANDBOX1PID=$(extract_child_pid info.json)
31+
32+
# Run the client inside another bubblewrap sandbox, joining the user and net namespaces of the first
33+
ASAN_OPTIONS=detect_leaks=0 LSAN_OPTIONS=detect_leaks=0 \
34+
$RUN --userns 11 --netns 12 python3 -c "
35+
import socket
36+
with open('server-port.txt', 'r') as f:
37+
port = int(f.read().strip())
38+
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
39+
s.connect(('127.0.0.1', port))
40+
data = s.recv(1024)
41+
print(data.decode().strip())
42+
s.close()
43+
" 11< /proc/$SANDBOX1PID/ns/user 12< /proc/$SANDBOX1PID/ns/net > client-output.txt
44+
45+
# Verify the output
46+
echo "hello from netns" > expected.txt
47+
assert_files_equal expected.txt client-output.txt
48+
49+
rm -f info.json server-port.txt client-output.txt expected.txt
50+
51+
echo "ok - Test --netns"
52+
fi

0 commit comments

Comments
 (0)