bubblewrap icon indicating copy to clipboard operation
bubblewrap copied to clipboard

Feature request: join network namespace created by 'ip netns create'

Open zackw opened this issue 8 years ago • 7 comments

The ip utility has a subcommand to create persistent network namespaces, and to run processes in these namespaces. It would be really handy if bubblewrap could put processes into network namespaces created in this manner. I specifically want this because I have scripts that bring up VPN tunnels and associate them with these namespaces, and then run specific programs in those namespaces (so, at the same time, process A can be talking to VPN proxy X, process B to proxy Y, etc) and I'd like to have reliable sandboxing for these processes.

For more information on the ip feature:

zackw avatar Jan 19 '17 21:01 zackw

Looks like a dup of https://github.com/projectatomic/bubblewrap/issues/61 ?

cgwalters avatar Jan 19 '17 22:01 cgwalters

Pretty much, yeah. I'll comment there about things ip netns exec does that might not be obvious.

zackw avatar Jan 19 '17 22:01 zackw

+1 to this feature request

ngortheone avatar Feb 13 '20 07:02 ngortheone

Another +1 to thie feature request. Being able to run flatpak in an existing network namespace would be perfect, for example for running a specific firefox session through VPN.

Pretty much, yeah. I'll comment there about things ip netns exec does that might not be obvious.

Right. I found this to be a clean and minimal stand-alone example of what netns exec does: https://github.com/minus7/netns-switch/blob/master/netns-switch.c

vmedea avatar Feb 13 '21 19:02 vmedea

https://github.com/rootless-containers/slirp4netns may solve this issue.

crocket avatar Sep 07 '21 07:09 crocket

Here is a patch I come up that add a switch --netns for suid bubblewrap to switch into a netns created by ip netns create.

NOTE:

  1. There is absolutely no warranty. While I do make some effort to avoid common pitfalls about parsing filenames, it may still introduce vulnerabilities and it is on your own.
  2. No access control is performed --- Now all users with access to bwrap binary will be able to enter any netns under /run/netns, which may not be desired.
  3. It does not bind /etc/netns/NAME/whatever to /etc/whatever as ip netns exec does, you can do your own, through.
  4. ip netns exec remounts a new sysfs on /sys to reflect netns change (for example, entries under /sys/class/net should reflect network interfaces in current netns), but there is currently no way to mount a new sysfs in bubblewrap
  5. It will not work well with ip-vrf(8)
--- bubblewrap-0.5.0.orig/bubblewrap.c	2022-01-10 19:47:22.733904138 +0800
+++ bubblewrap-0.5.0/bubblewrap.c	2022-01-10 21:08:26.937312403 +0800
@@ -18,6 +18,7 @@
 
 #include "config.h"
 
+#include <dirent.h>
 #include <poll.h>
 #include <sched.h>
 #include <pwd.h>
@@ -38,6 +39,8 @@
 #include "network.h"
 #include "bind-mount.h"
 
+#define IPROUTE_NETNS_DIR "/run/netns"
+
 #ifndef CLONE_NEWCGROUP
 #define CLONE_NEWCGROUP 0x02000000 /* New cgroup namespace */
 #endif
@@ -86,6 +89,7 @@
 int opt_seccomp_fd = -1;
 const char *opt_sandbox_hostname = NULL;
 char *opt_args_data = NULL;  /* owned */
+const char *opt_netns_name = NULL;
 int opt_userns_fd = -1;
 int opt_userns2_fd = -1;
 int opt_pidns_fd = -1;
@@ -240,6 +244,7 @@
            "    --userns FD                  Use this user namespace (cannot combine with --unshare-user)\n"
            "    --userns2 FD                 After setup switch to this user namespace, only useful with --userns\n"
            "    --pidns FD                   Use this user namespace (as parent namespace if using --unshare-pid)\n"
+           "    --netns NAME                 Use this network namespace (in " IPROUTE_NETNS_DIR ")\n"
            "    --uid UID                    Custom uid in the sandbox (requires --unshare-user or --userns)\n"
            "    --gid GID                    Custom gid in the sandbox (requires --unshare-user or --userns)\n"
            "    --hostname NAME              Custom hostname in the sandbox (requires --unshare-uts)\n"
@@ -2101,6 +2106,19 @@
           argv += 1;
           argc -= 1;
         }
+      else if (strcmp (arg, "--netns") == 0)
+        {
+          if (argc < 2)
+            die ("--netns takes an argument");
+
+          if (argv[1][0] == '\0')
+            die ("Invalid netns name: %s", argv[1]);
+
+          opt_netns_name = argv[1];
+
+          argv += 1;
+          argc -= 1;
+        }
       else if (strcmp (arg, "--clearenv") == 0)
         {
           xclearenv ();
@@ -2396,6 +2414,21 @@
     }
 }
 
+static int
+run_netns_filter (const struct dirent * dir)
+{
+  const unsigned char type = dir->d_type;
+  const char * name = dir->d_name;
+
+  if (!(type & (DT_REG | DT_UNKNOWN)))
+    return 0;
+
+  if (strcmp(opt_netns_name, name) == 0)
+    return 1;
+
+  return 0;
+}
+
 int
 main (int    argc,
       char **argv)
@@ -2419,6 +2452,7 @@
   struct sock_fprog seccomp_prog;
   cleanup_free char *args_data = NULL;
   int intermediate_pids_sockets[2] = {-1, -1};
+  int netns_fd = -1;
 
   /* Handle --version early on before we try to acquire/drop
    * any capabilities so it works in a build environment;
@@ -2475,6 +2509,9 @@
   if (opt_userns_fd != -1 && opt_unshare_user_try)
     die ("--userns not compatible --unshare-user-try");
 
+  if (opt_netns_name && opt_unshare_net)
+    die ("--netns not compatible --unshare-net");
+
   /* Technically using setns() is probably safe even in the privileged
    * case, because we got passed in a file descriptor to the
    * namespace, and that can only be gotten if you have ptrace
@@ -2504,6 +2541,27 @@
     opt_unshare_user = TRUE;
 #endif
 
+  if (opt_netns_name)
+    {
+      if (!is_privileged)
+        die ("--netns works only in setuid mode");
+
+      struct dirent **nsnamelist;
+      int nsdirfd = -1;
+
+      if ((nsdirfd = open(IPROUTE_NETNS_DIR, O_DIRECTORY | O_PATH)) == -1)
+        die ("Failed query netns dir: %s", strerror(errno));
+
+      if (scandirat(nsdirfd, ".", &nsnamelist, run_netns_filter, alphasort) != 1)
+        die ("Invalid netns name: %s", opt_netns_name);
+
+      if ((netns_fd = openat(nsdirfd, nsnamelist[0]->d_name, O_RDONLY)) == -1)
+        die ("Failed open netns: %s", strerror(errno));
+
+      free(nsnamelist);
+      close(nsdirfd);
+    }
+
   if (opt_unshare_user_try &&
       stat ("/proc/self/ns/user", &sbuf) == 0)
     {
@@ -2758,6 +2816,10 @@
       close (intermediate_pids_sockets[1]);
     }
 
+  if (netns_fd > 0 && setns (netns_fd, CLONE_NEWNET) != 0)
+    die_with_error("Setting netns failed");
+  close (netns_fd);
+
   /* Child, in sandbox, privileged in the parent or in the user namespace (if --unshare-user).
    *
    * Note that for user namespaces we run as euid 0 during clone(), so

OceanS2000 avatar Jan 10 '22 13:01 OceanS2000

slirp4netns may solve this issue.

Docker's rootless mode uses slirp4netns, and network namespaces accept their own firewall rules, both without privileges.

If bubblewrap made it easy to use them (perhaps with setup done in advance or automated by something like flatpak) the resulting network access control would be a great improvement. For example, Steam games could be allowed to reach their servers but forbidden from scanning the local network.

foresto avatar Jan 21 '22 07:01 foresto