Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

unshare mount namespace not working as expected

Tags:

c

linux

When I call the Linux system function unshare(CLONE_NEWNS), it returns 0 indicating success. But, it doesn't seem to work as I was expecting. Specifically when I then add a new mount such as a tmpfs one, it is globally visible. Therefore it is in fact not a private mount namespace as expected.

Here is an example program that demonstrates the issue. Compile this up and run it in one terminal. Then open another terminal and check if the path written by the example program is visible. It shouldn't be but is. It is behaving as though the unshare call did nothing. What I was expecting was that from that moment on, any subsequent mounts performed by this program will not be visible to other processes.

/* Run this program as root.  As mount and unshare requires higher privileges. */

#define _GNU_SOURCE
#include <sched.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/mount.h>
#include <stdlib.h>
#include <stdio.h>

#define errExit(msg)    do { perror(msg); exit(EXIT_FAILURE); \
    } while (0)

int main(int argc, char *argv[])
{
    // Create a temporary directory at /tmp/unshare
    mkdir("/tmp/unshare", S_IRWXG);
    if (unshare(CLONE_NEWNS) == -1)
        errExit("unshare");

    if (mount("none", "/tmp/unshare", "tmpfs", 0, "mode=0700") == -1)
        errExit("unshare");

    FILE* fp = fopen("/tmp/unshare/test", "w");
    fprintf(fp, "This file should not be seen by other processes right?\n");
    fclose(fp);

    // Pause
    printf("Now open another shell.  As the root user, verify the file /tmp/unshare/test is not seen\n.Press enter end finish\n");
    char c = getchar();

    if (umount("/tmp/unshare") == -1)
        errExit("umount");
}

I should point out that the mount manpage suggests this should work. Specifically section labeled "Per-process namespaces".

i.e.

A process can obtain a private mount namespace if ... 
it calls unshare(2)  with  the  CLONE_NEWNS  flag,  which
causes  the  caller's  mount  namespace to obtain a private copy of the
namespace that it was previously sharing with other processes, so  that
future  mounts  and  unmounts by the caller are invisible to other pro‐
cesses (except child processes that the  caller  subsequently  creates)
and vice versa.

If you use the unshare terminal command, it works. But that also forks another process. But the man page suggests there is no need to fork or clone when using the unshare system call. What am I doing wrong here?

like image 588
hookenz Avatar asked Jan 10 '17 04:01

hookenz


1 Answers

After running strace I found the answer.

\> strace unmount -m true
...
unshare(CLONE_NEWNS)                    = 0
mount("none", "/", NULL, MS_REC|MS_PRIVATE, NULL) = 0
execve("/home/matt/.nvm/versions/node/v6.9.1/bin/true", ["true"], [/* 29 vars */]) = -1 ENOENT (No such file or directory)
...

Note the mount following the unshare. This mount call would appear to recursively mark all subsequent changes to mounts as private. And looking at this sandbox code: https://github.com/swetland/mkbox The author is doing just that also.

So here is the working version.

/* Run this program as root.  As mount and unshare requires higher privileges. */

#define _GNU_SOURCE
#include <sched.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/mount.h>
#include <stdlib.h>
#include <stdio.h>

#define errExit(msg)    do { perror(msg); exit(EXIT_FAILURE); \
    } while (0)

int main(int argc, char *argv[])
{
    // Create a temporary directory at /tmp/unshare
    mkdir("/tmp/unshare", S_IRWXG);
    if (unshare(CLONE_NEWNS | CLONE_FS | CLONE_THREAD) == -1)
        errExit("unshare");

    /* ensure that changes to our mount namespace do not "leak" to
     * outside namespaces (what mount --make-rprivate / does)
     */
    if (mount("none", "/", NULL, MS_REC|MS_PRIVATE, NULL) == -1)
        errExit("mount1");

    if (mount("none", "/tmp/unshare", "tmpfs", 0, NULL) == -1)
        errExit("mount2");

    // if (mount("none", "/tmp/unshare", NULL, MS_PRIVATE, NULL) == -1)
    //  errExit("mount2");

    FILE* fp = fopen("/tmp/unshare/test", "w");
    fprintf(fp, "This file should not be seen\n");
    fclose(fp);

    // Pause
    printf("Now open another shell.  As the root user, verify the file /tmp/unshare/test is not seen\n.Press enter end finish\n");
    char c = getchar();

    if (umount("/tmp/unshare") == -1)
        errExit("umount");
}
like image 192
hookenz Avatar answered Nov 05 '22 19:11

hookenz