Our last blog post explored ways to restrict access to the file system using Linux mount namespaces. In this post, we’ll show you how to restrict access to the network using Linux’s network namespaces. That’s basically a new instance of the Linux network stack. By default a new network namespace contains no network interfaces except a new instance of the loopback interface. The main use cases are Linux containers: Network namespaces allow a container having its own network configuration without the risk of compromising the host.
Consider a requirement where an application needs to ensure that it can no longer access the network or establish new connections. One way to achieve this is by creating a new network namespace.
When the application creates a new network namespace, it can no longer access the network nor the host itself. Access is not denied by the namespace mechanisms themselves, but by the way networking works. There is no network interface to the outside world; all the namespace has is a loopback interface. To connect the network namespace to the outside world the application either has to set up a virtual ethernet connection or move a physical network interface into the namespace. This is usually done by container engines and is a privileged operation. By not doing so, the application is network-wise isolated.
Similar to mount namespaces, creating a network namespace requires the
CAP_SYS_ADMIN capability. Therefore, an unprivileged application must first create a new user namespace to be able to create a network namespace. For simple unprivileged applications, utilizing the
unshare() system call is good enough to create a new user and network namespace to disassociate itself from the network. Thus, a call like
unshare(CLONE_NEWNET | CLONE_NEWUSER) is sufficient for unprivileged applications to isolate itself from the network. While the application has all capabilities in the freshly created user namespace and can alter the new network namespaces as it wishes, it has no way to establish a virtual network connection to the parent namespace, nor can it re-enter the parent namespace.
This is because it has no capabilities on the host side (the parent namespaces in this case).
If the application is privileged (i.e., it has
CAP_NET_ADMIN) then it is crucial to drop privileges inside the new user namespace to avoid escape.
Dropping capabilities is usually something you would do with the help of libcap or libcap-ng.
Here, we’re using libcap-ng.
The following lines of code outline the needed steps to create a new network namespace and drop privileges.
assert(unshare(CLONE_NEWUSER | CLONE_NEWNET) == 0);
assert(capng_lock() == 0);
assert(capng_apply(CAPNG_SELECT_BOTH) == 0);
assert(prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == 0);
Network namespaces come with one pitfall: By default the loopback interface is set to down state. If your new network namespace will contain applications that communicate via loopback, don’t forget to bring up the loopback interface. Otherwise, you may observe strange failures because most applications blindly assume loopback.
You can try this yourself using the
unshare tool in your favorite shell:
$ unshare -Unr
$ ping localhost
connect: Cannot assign requested address
$ ip link set up dev lo
$ ping localhost
PING localhost(localhost (::1)) 56 data bytes
64 bytes from localhost (::1): icmp_seq=1 ttl=64 time=0.055 ms
Please note that network namespaces, actually Linux namespaces in general, have no influence on existing file handles. Therefore, if your application possesses a file handle to a socket from another network namespace, it can use it in the new network namespace smoothly.
This is a useful feature as it allows creating network servers that can serve a listening socket but are disconnected from the outside world. If an attacker manages to overtake the application, they are unable to create a new socket to reach destinations outside the sandbox. Here you can find a sample application that outlines the idea.
Namespace are not only useful for building containers, but they can also be utilized to restrict access. In this case, we used network namespaces to deny access to the network. For more details on namespaces, see namespaces(7).