From fbb786d4acc4cf5c3c6fbbf4a0fad0ba8b27b339 Mon Sep 17 00:00:00 2001 From: Anders Blomdell Date: Mon, 28 Aug 2017 17:21:53 +0200 Subject: [PATCH 01/10] First iteration of mimicing unshare(1) namespace saving --- unshare.c | 292 ++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 242 insertions(+), 50 deletions(-) diff --git a/unshare.c b/unshare.c index 81d0851..83bb0dc 100644 --- a/unshare.c +++ b/unshare.c @@ -22,15 +22,223 @@ #ifndef _GNU_SOURCE # define _GNU_SOURCE #endif +#define _GNU_SOURCE #include +#include +#include +#include +#include +#include +#include +#include -static PyObject * _unshare(PyObject *self, PyObject *args) { - int flags, ret; - if (!PyArg_ParseTuple(args, "i", &flags)) +struct mapping { + char **value; + char *kw_name; + int mask; + char *mask_name; + char *format; + int is_mounted; +}; + +static struct mask_list_entry { + char *name; + int mask; +} mask_list[] = { + { "CLONE_FILES", CLONE_FILES }, + { "CLONE_FS", CLONE_FS }, + { "CLONE_NEWCGROUP", CLONE_NEWCGROUP }, + { "CLONE_NEWIPC", CLONE_NEWIPC }, + { "CLONE_NEWNET", CLONE_NEWNET }, + { "CLONE_NEWNS", CLONE_NEWNS }, + { "CLONE_NEWPID", CLONE_NEWPID }, + { "CLONE_NEWUSER", CLONE_NEWUSER }, + { "CLONE_NEWUTS", CLONE_NEWUTS }, + { "CLONE_SIGHAND", CLONE_SIGHAND }, + { "CLONE_SYSVSEM", CLONE_SYSVSEM }, + { "CLONE_THREAD", CLONE_THREAD }, + { "CLONE_VM", CLONE_VM }, + { NULL, 0 } +}; + +static int ns_bind_mount(pid_t bpid, struct mapping *mapping) { + return 0; +} + +static PyObject * _unshare(PyObject *self, PyObject *args, PyObject *keywds) +{ + int ret; + + int v_flags = 0; + static char *empty = ""; + char *v_cgroup = empty; + char *v_ipc = empty; + char *v_mount = empty; + char *v_net = empty; + char *v_pid = empty; + char *v_user = empty; + char *v_uts = empty; + static char *kwlist[] = { "flags", "cgroup", "ipc", "mount", "net", "pid", "user", "uts", NULL }; + struct mapping mapping[] = { + { NULL, kwlist[0], 0, NULL, NULL, 0 }, + { &v_cgroup, kwlist[1], CLONE_NEWCGROUP, "CLONE_NEWCGROUP", "/proc/%d/ns/cgroup", 0 }, + { &v_ipc, kwlist[2], CLONE_NEWIPC, "CLONE_NEWIPC", "/proc/%d/ns/ipc", 0 }, + { &v_mount, kwlist[3], CLONE_NEWNS, "CLONE_NEWNS", "/proc/%d/ns/mnt", 0 }, + { &v_net, kwlist[4], CLONE_NEWNET, "CLONE_NEWNET", "/proc/%d/ns/net", 0 }, + { &v_pid, kwlist[5], CLONE_NEWPID, "CLONE_NEWPID", "/proc/%d/ns/pid", 0 }, + { &v_user, kwlist[6], CLONE_NEWUSER, "CLONE_NEWUSER", "/proc/%d/ns/user", 0 }, + { &v_uts, kwlist[7], CLONE_NEWUTS, "CLONE_NEWUTS", "/proc/%d/ns/uts", 0 }, + { NULL, NULL, 0, NULL, NULL, 0 } + }; + + if (!PyArg_ParseTupleAndKeywords(args, keywds, "|izzzzzzz", kwlist, + &v_flags, &v_cgroup, &v_ipc, &v_mount, &v_net, &v_pid, &v_user, &v_uts)) return NULL; - ret = unshare(flags); - if(ret == -1) - return PyErr_SetFromErrno(PyExc_OSError); + + fprintf(stderr, "-- flags=%d cgroup=%s ipc=%s mount=%s net=%s pid=%s user=%s uts=%s\n", + v_flags, v_cgroup, v_ipc, v_mount, v_net, v_pid, v_user, v_uts); + + /* Sanity check parameters and collect bind info */ + struct mapping * m; + int needed_mounts = 0; + int flags = v_flags; + for ( m = mapping + 1 ; m->value ; m++) {; + if (*m->value != empty) { + flags |= m->mask; + if (*m->value) needed_mounts++; + if ((v_flags != 0) && ((v_flags & m->mask) == 0)) { + char msg[1024]; + snprintf(msg, sizeof msg, "%s keyword given, but %s (%x) not in flags (%x)", + m->kw_name, m->mask_name, m->mask, v_flags); + PyErr_SetString(PyExc_OSError, msg); + return NULL; + } + } + } + + if (needed_mounts == 0) { + /* Simple case */ + ret = unshare(flags); + if (ret == -1) + return PyErr_SetFromErrno(PyExc_OSError); + } else { + /* Check that only restorable namespaces are specified when needing bind mounts */ + if (needed_mounts >= 0) { + int bind_flags = 0; + pid_t pid = getpid(); + for ( m = mapping + 1 ; m->value ; m++) { + char ns[PATH_MAX]; + struct stat stat_buf; + snprintf(ns, sizeof(ns), m->format, pid); + if (stat(ns, &stat_buf) == 0) { + bind_flags |= m->mask; + } + } + int bad = flags & ~bind_flags; + if (bad != 0) { + int pos = 0; + char msg[PATH_MAX]; + struct mask_list_entry *ml; + + ret = snprintf(msg, sizeof(msg), "Unrestorable namespace detected [ "); + if (ret < 0) + return PyErr_SetFromErrno(PyExc_OSError); + pos = ret; + for (ml = mask_list ; ml->name ; ml++) { + if (bad & ml->mask) { + ret = snprintf(msg + pos, sizeof(msg) - pos, " %s |", ml->name); + if (ret < 0) + return PyErr_SetFromErrno(PyExc_OSError); + pos += ret; + } + } + ret = snprintf(msg + pos - 1, sizeof(msg) - pos + 1, "] when saving namespace"); + if (ret < 0) + return PyErr_SetFromErrno(PyExc_OSError); + PyErr_SetString(PyExc_OSError, msg); + return NULL; + } + } + fprintf(stderr, "XXX\n"); + int fd[2]; + ret = socketpair(AF_UNIX, SOCK_STREAM, 0, fd); + if(ret == -1) + return PyErr_SetFromErrno(PyExc_OSError); + pid_t pid = fork(); + if (pid < 0) { + close(fd[0]); + close(fd[1]); + return PyErr_SetFromErrno(PyExc_OSError); + } else if (pid == 0) { + /* Child */ + close(fd[1]); + unsigned char result = 0; + ret = unshare(flags); + if (ret != 0) { + result = errno < 255 ? errno : EINVAL; + } + if (write(fd[0], &result, 1) != 1) { + exit(1); + } + ret = read(fd[0], &result, 1); + fprintf(stderr, "EXIT %d %d\n", result, ret); + if (ret != 1 || result != 'Q') { + exit(1); + } + exit(0); + } else { + /* Parent */ + unsigned char result; + + close(fd[0]); + ret = read(fd[1], &result, 1); + if (ret == -1) { + close(fd[1]); + return PyErr_SetFromErrno(PyExc_OSError); + } + if (result != 0) { + errno = result; + return PyErr_SetFromErrno(PyExc_OSError); + } + fprintf(stderr, "MOUNT %d\n", result); + for ( m = mapping + 1 ; m->value ; m++) {; + if (*m->value != NULL && *m->value != empty) { + + char ns[PATH_MAX]; + snprintf(ns, sizeof(ns), m->format, pid); + fprintf(stderr, "BIND %s %s\n", ns, *m->value); + ret = mount(ns, *m->value, NULL, MS_BIND, NULL); + if (ret == -1) { + close(fd[1]); + fprintf(stderr, "TODO cleanup succeded mounts"); + return PyErr_SetFromErrno(PyExc_OSError); + } else { + m->is_mounted = 1; + } + } + } + fprintf(stderr, "NSENTER %d\n", result); + for ( m = mapping + 1 ; m->value ; m++) { + if (v_flags & m->mask) { + char ns[PATH_MAX]; + int fd; + snprintf(ns, sizeof(ns), m->format, pid); + fd = open(ns, O_RDONLY); + ret = setns(fd, m->mask); + close(fd); + if (ret == -1) + return PyErr_SetFromErrno(PyExc_OSError); + } + } + + if (write(fd[1], "Q", 1) != 1) { + close(fd[1]); + return PyErr_SetFromErrno(PyExc_OSError); + } + close(fd[1]); + wait(pid); + } + } Py_RETURN_NONE; } @@ -45,31 +253,31 @@ static PyObject * _setns(PyObject *self, PyObject *args) { } static PyMethodDef methods[] = { - {"unshare", _unshare, METH_VARARGS, - "unshare(flags)\n\n" - "Disassociate parts of the process execution context.\n" - "flags is a bitmask that specifies which parts to unshare.\n\n" - "Possible values for flags:\n" - " CLONE_VM CLONE_FS CLONE_FILES CLONE_SIGHAND CLONE_THREAD " - "CLONE_NEWNS\n" - " CLONE_SYSVSEM CLONE_NEWUTS CLONE_NEWIPC CLONE_NEWUSER " - "CLONE_NEWPID\n" - " CLONE_NEWNET\n" + {"unshare", (PyCFunction)_unshare, METH_VARARGS | METH_KEYWORDS, + "unshare(flags, **kwargs)\n\n" + "Disassociate parts of the process execution context.\n" + "flags is a bitmask that specifies which parts to unshare.\n\n" + "Possible values for flags:\n" + " CLONE_VM CLONE_FS CLONE_FILES CLONE_SIGHAND CLONE_THREAD " + "CLONE_NEWNS\n" + " CLONE_SYSVSEM CLONE_NEWUTS CLONE_NEWIPC CLONE_NEWUSER " + "CLONE_NEWPID\n" + " CLONE_NEWNET\n" }, {"setns", _setns, METH_VARARGS, - "setns(fd, nstype)\n\n" - "Reassociate the calling thread with a new namespace.\n" - "fd is a filedescriptor referring to a namespace.\n" - "nstype specifies which type of namespace the calling thread\n" - "may be reassociated with.\n\n" - "Possible values for nstype:\n" - " 0 Allow any type of namespace to be joined.\n" - " CLONE_NEWIPC fd must refer to an IPC namespace.\n" - " CLONE_NEWNET fd must refer to a network namespace.\n" - " CLONE_NEWNS fd must refer to a mount namespace.\n" - " CLONE_NEWPID fd must refer to a descendant PID namespace.\n" - " CLONE_NEWUSER fd must refer to a user namespace.\n" - " CLONE_NEWUTS fd must refer to a UTS namespace.\n" + "setns(fd, nstype)\n\n" + "Reassociate the calling thread with a new namespace.\n" + "fd is a filedescriptor referring to a namespace.\n" + "nstype specifies which type of namespace the calling thread\n" + "may be reassociated with.\n\n" + "Possible values for nstype:\n" + " 0 Allow any type of namespace to be joined.\n" + " CLONE_NEWIPC fd must refer to an IPC namespace.\n" + " CLONE_NEWNET fd must refer to a network namespace.\n" + " CLONE_NEWNS fd must refer to a mount namespace.\n" + " CLONE_NEWPID fd must refer to a descendant PID namespace.\n" + " CLONE_NEWUSER fd must refer to a user namespace.\n" + " CLONE_NEWUTS fd must refer to a UTS namespace.\n" }, {NULL, NULL, 0, NULL} }; @@ -80,26 +288,10 @@ PyMODINIT_FUNC initunshare(void) { if (m == NULL) return; - /* Currently (2.6.33) not-implemented: CLONE_VM, CLONE_SIGHAND, - * CLONE_THREAD, CLONE_NEWUSER, CLONE_NEWPID */ - - PyModule_AddIntConstant(m, "CLONE_VM", CLONE_VM); - /* No CAP_* needed */ - PyModule_AddIntConstant(m, "CLONE_FS", CLONE_FS); - /* No CAP_* needed */ - PyModule_AddIntConstant(m, "CLONE_FILES", CLONE_FILES); - PyModule_AddIntConstant(m, "CLONE_SIGHAND", CLONE_SIGHAND); - PyModule_AddIntConstant(m, "CLONE_THREAD", CLONE_THREAD); - /* CAP_SYS_ADMIN */ - PyModule_AddIntConstant(m, "CLONE_NEWNS", CLONE_NEWNS); - PyModule_AddIntConstant(m, "CLONE_SYSVSEM", CLONE_SYSVSEM); - /* CAP_SYS_ADMIN */ - PyModule_AddIntConstant(m, "CLONE_NEWUTS", CLONE_NEWUTS); - /* CAP_SYS_ADMIN */ - PyModule_AddIntConstant(m, "CLONE_NEWIPC", CLONE_NEWIPC); - PyModule_AddIntConstant(m, "CLONE_NEWUSER", CLONE_NEWUSER); - PyModule_AddIntConstant(m, "CLONE_NEWPID", CLONE_NEWPID); - /* CAP_SYS_ADMIN */ - PyModule_AddIntConstant(m, "CLONE_NEWNET", CLONE_NEWNET); + struct mask_list_entry *ml; + + for (ml = mask_list ; ml->name ; ml++) { + PyModule_AddIntConstant(m, ml->name, ml->mask); + } } From 895c977c7e5c6042fb77e47b1af9c0cfc20c8944 Mon Sep 17 00:00:00 2001 From: Anders Blomdell Date: Tue, 29 Aug 2017 00:17:50 +0200 Subject: [PATCH 02/10] Cleanup of child/parent logic --- unshare.c | 154 ++++++++++++++++++++++++------------------------------ 1 file changed, 69 insertions(+), 85 deletions(-) diff --git a/unshare.c b/unshare.c index 83bb0dc..9afa782 100644 --- a/unshare.c +++ b/unshare.c @@ -22,7 +22,6 @@ #ifndef _GNU_SOURCE # define _GNU_SOURCE #endif -#define _GNU_SOURCE #include #include #include @@ -33,7 +32,7 @@ #include struct mapping { - char **value; + char **path; char *kw_name; int mask; char *mask_name; @@ -61,10 +60,6 @@ static struct mask_list_entry { { NULL, 0 } }; -static int ns_bind_mount(pid_t bpid, struct mapping *mapping) { - return 0; -} - static PyObject * _unshare(PyObject *self, PyObject *args, PyObject *keywds) { int ret; @@ -92,74 +87,37 @@ static PyObject * _unshare(PyObject *self, PyObject *args, PyObject *keywds) }; if (!PyArg_ParseTupleAndKeywords(args, keywds, "|izzzzzzz", kwlist, - &v_flags, &v_cgroup, &v_ipc, &v_mount, &v_net, &v_pid, &v_user, &v_uts)) + &v_flags, &v_cgroup, &v_ipc, &v_mount, + &v_net, &v_pid, &v_user, &v_uts)) { return NULL; - - fprintf(stderr, "-- flags=%d cgroup=%s ipc=%s mount=%s net=%s pid=%s user=%s uts=%s\n", - v_flags, v_cgroup, v_ipc, v_mount, v_net, v_pid, v_user, v_uts); - - /* Sanity check parameters and collect bind info */ - struct mapping * m; - int needed_mounts = 0; - int flags = v_flags; - for ( m = mapping + 1 ; m->value ; m++) {; - if (*m->value != empty) { - flags |= m->mask; - if (*m->value) needed_mounts++; - if ((v_flags != 0) && ((v_flags & m->mask) == 0)) { - char msg[1024]; - snprintf(msg, sizeof msg, "%s keyword given, but %s (%x) not in flags (%x)", - m->kw_name, m->mask_name, m->mask, v_flags); - PyErr_SetString(PyExc_OSError, msg); - return NULL; - } - } } - - if (needed_mounts == 0) { - /* Simple case */ - ret = unshare(flags); - if (ret == -1) - return PyErr_SetFromErrno(PyExc_OSError); - } else { - /* Check that only restorable namespaces are specified when needing bind mounts */ - if (needed_mounts >= 0) { - int bind_flags = 0; - pid_t pid = getpid(); - for ( m = mapping + 1 ; m->value ; m++) { + + int flags_child = 0; + int flags_parent = v_flags; + pid_t my_pid = getpid(); + struct mapping *m; + for ( m = mapping + 1 ; m->path ; m++) { + if (*m->path != empty) { + if (*m->path != NULL) { + /* Bind mount needed, unshare in child */ char ns[PATH_MAX]; struct stat stat_buf; - snprintf(ns, sizeof(ns), m->format, pid); - if (stat(ns, &stat_buf) == 0) { - bind_flags |= m->mask; - } - } - int bad = flags & ~bind_flags; - if (bad != 0) { - int pos = 0; - char msg[PATH_MAX]; - struct mask_list_entry *ml; - - ret = snprintf(msg, sizeof(msg), "Unrestorable namespace detected [ "); - if (ret < 0) - return PyErr_SetFromErrno(PyExc_OSError); - pos = ret; - for (ml = mask_list ; ml->name ; ml++) { - if (bad & ml->mask) { - ret = snprintf(msg + pos, sizeof(msg) - pos, " %s |", ml->name); - if (ret < 0) - return PyErr_SetFromErrno(PyExc_OSError); - pos += ret; - } + snprintf(ns, sizeof(ns), m->format, my_pid); + ret = stat(ns, &stat_buf); + if (ret == -1) { + PyErr_SetFromErrnoWithFilename(PyExc_OSError, ns); + goto err_unbindable_namespace; } - ret = snprintf(msg + pos - 1, sizeof(msg) - pos + 1, "] when saving namespace"); - if (ret < 0) - return PyErr_SetFromErrno(PyExc_OSError); - PyErr_SetString(PyExc_OSError, msg); - return NULL; + flags_child |= m->mask; + flags_parent &= ~m->mask; + } else { + /* Bind mount not needed, unshare in parent */ + flags_parent |= m->mask; } } - fprintf(stderr, "XXX\n"); + } + + if (flags_child) { int fd[2]; ret = socketpair(AF_UNIX, SOCK_STREAM, 0, fd); if(ret == -1) @@ -173,23 +131,25 @@ static PyObject * _unshare(PyObject *self, PyObject *args, PyObject *keywds) /* Child */ close(fd[1]); unsigned char result = 0; - ret = unshare(flags); + ret = unshare(flags_child); if (ret != 0) { result = errno < 255 ? errno : EINVAL; } if (write(fd[0], &result, 1) != 1) { + close(fd[0]); exit(1); } ret = read(fd[0], &result, 1); - fprintf(stderr, "EXIT %d %d\n", result, ret); if (ret != 1 || result != 'Q') { + close(fd[0]); exit(1); } + close(fd[0]); exit(0); } else { /* Parent */ unsigned char result; - + close(fd[0]); ret = read(fd[1], &result, 1); if (ret == -1) { @@ -200,46 +160,62 @@ static PyObject * _unshare(PyObject *self, PyObject *args, PyObject *keywds) errno = result; return PyErr_SetFromErrno(PyExc_OSError); } - fprintf(stderr, "MOUNT %d\n", result); - for ( m = mapping + 1 ; m->value ; m++) {; - if (*m->value != NULL && *m->value != empty) { - + for ( m = mapping + 1 ; m->path ; m++) {; + if (*m->path != NULL && *m->path != empty) { + char ns[PATH_MAX]; snprintf(ns, sizeof(ns), m->format, pid); - fprintf(stderr, "BIND %s %s\n", ns, *m->value); - ret = mount(ns, *m->value, NULL, MS_BIND, NULL); + ret = mount(ns, *m->path, NULL, MS_BIND, NULL); if (ret == -1) { close(fd[1]); - fprintf(stderr, "TODO cleanup succeded mounts"); - return PyErr_SetFromErrno(PyExc_OSError); + PyErr_SetFromErrnoWithFilename(PyExc_OSError, *m->path); + goto err_cleanup_mounts; } else { m->is_mounted = 1; } } } - fprintf(stderr, "NSENTER %d\n", result); - for ( m = mapping + 1 ; m->value ; m++) { - if (v_flags & m->mask) { + for ( m = mapping + 1 ; m->path ; m++) { + if (flags_child & m->mask) { char ns[PATH_MAX]; int fd; snprintf(ns, sizeof(ns), m->format, pid); fd = open(ns, O_RDONLY); ret = setns(fd, m->mask); close(fd); - if (ret == -1) - return PyErr_SetFromErrno(PyExc_OSError); + if (ret == -1) { + PyErr_SetFromErrnoWithFilename(PyExc_OSError, ns); + goto err_setns_failed; + } } } - + if (write(fd[1], "Q", 1) != 1) { close(fd[1]); return PyErr_SetFromErrno(PyExc_OSError); } close(fd[1]); - wait(pid); + waitpid(pid, NULL, 0); } } + ret = unshare(flags_parent); + if (ret == -1) { + /* If flags_child != 0 we are in serious trouble */ + return PyErr_SetFromErrno(PyExc_OSError); + } Py_RETURN_NONE; + +err_setns_failed: +err_cleanup_mounts: + for ( m = mapping + 1 ; m->path ; m++) {; + if (m->is_mounted) { + umount(*m->path); + m->is_mounted = 0; + } + } + +err_unbindable_namespace: + return NULL; } static PyObject * _setns(PyObject *self, PyObject *args) { @@ -263,6 +239,14 @@ static PyMethodDef methods[] = { " CLONE_SYSVSEM CLONE_NEWUTS CLONE_NEWIPC CLONE_NEWUSER " "CLONE_NEWPID\n" " CLONE_NEWNET\n" + "Possible values for kwargs are (PATH == None is equivalent to FLAG):\n" + " cgroup=PATH save new cgroup namespace to PATH (CLONE_NEWCGROUP)\n" + " ipc=PATH save new ipc namespace to PATH (CLONE_NEWIPC)\n" + " mount=PATH save new mount namespace to PATH (CLONE_NEWNS)\n" + " net=PATH save new net namespace to PATH (CLONE_NEWNET)\n" + " pid=PATH save new pid namespace to PATH (CLONE_NEWPID)\n" + " user=PATH save new user namespace to PATH (CLONE_NEWUSER)\n" + " uts=PATH save new uts namespa Date: Tue, 29 Aug 2017 20:16:09 +0200 Subject: [PATCH 03/10] Added python3 support --- unshare.c | 40 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/unshare.c b/unshare.c index 9afa782..d12d8fd 100644 --- a/unshare.c +++ b/unshare.c @@ -266,16 +266,48 @@ static PyMethodDef methods[] = { {NULL, NULL, 0, NULL} }; -PyMODINIT_FUNC initunshare(void) { - PyObject *m; - m = Py_InitModule("unshare", methods); +#if PY_MAJOR_VERSION >= 3 + +static struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, + "unshare", + NULL, + 0, + methods, + NULL, + NULL, + NULL, + NULL +}; + +#define INITERROR return NULL + +PyMODINIT_FUNC +PyInit_unshare(void) + +#else + +#define INITERROR return + +PyMODINIT_FUNC initunshare(void) +#endif +{ + +#if PY_MAJOR_VERSION >= 3 + PyObject *m = PyModule_Create(&moduledef); +#else + PyObject *m = Py_InitModule("unshare", methods); +#endif if (m == NULL) - return; + INITERROR; struct mask_list_entry *ml; for (ml = mask_list ; ml->name ; ml++) { PyModule_AddIntConstant(m, ml->name, ml->mask); } +#if PY_MAJOR_VERSION >= 3 + return m; +#endif } From 69d3301e3bfb735d313973c2c0412d64f39f4644 Mon Sep 17 00:00:00 2001 From: Anders Blomdell Date: Wed, 30 Aug 2017 09:23:22 +0200 Subject: [PATCH 04/10] Fix for undefined CLONE_NEWCGROUP (CentOS-7) --- unshare.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/unshare.c b/unshare.c index d12d8fd..8685217 100644 --- a/unshare.c +++ b/unshare.c @@ -40,6 +40,10 @@ struct mapping { int is_mounted; }; +#ifndef CLONE_NEWCGROUP +# define CLONE_NEWCGROUP 0x02000000 /* New cgroup namespace. */ +#endif + static struct mask_list_entry { char *name; int mask; From bc2647072416c964c20485e258cbe9c3b4ecefeb Mon Sep 17 00:00:00 2001 From: Anders Blomdell Date: Wed, 30 Aug 2017 14:43:27 +0200 Subject: [PATCH 05/10] Add some error checking and more namespace magic (which gdm needed) --- unshare.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/unshare.c b/unshare.c index 8685217..b695ff8 100644 --- a/unshare.c +++ b/unshare.c @@ -185,6 +185,17 @@ static PyObject * _unshare(PyObject *self, PyObject *args, PyObject *keywds) int fd; snprintf(ns, sizeof(ns), m->format, pid); fd = open(ns, O_RDONLY); + if (fd < 0) { + PyErr_SetFromErrnoWithFilename(PyExc_OSError, ns); + goto err_open_ns_failed; + } + /* Temporarily unshare namespace before entering + child namespace (gdm's use of pam needs this) */ + ret = unshare(m->mask); + if (ret == -1) { + PyErr_SetFromErrnoWithFilename(PyExc_OSError, ns); + goto err_unshare_ns_failed; + } ret = setns(fd, m->mask); close(fd); if (ret == -1) { @@ -209,6 +220,8 @@ static PyObject * _unshare(PyObject *self, PyObject *args, PyObject *keywds) } Py_RETURN_NONE; +err_open_ns_failed: +err_unshare_ns_failed: err_setns_failed: err_cleanup_mounts: for ( m = mapping + 1 ; m->path ; m++) {; From f0d91d69afbb073f447908e5adc3887e65a1a8dc Mon Sep 17 00:00:00 2001 From: Anders Blomdell Date: Sun, 3 Sep 2017 12:33:01 +0200 Subject: [PATCH 06/10] Add test program --- test | 20 +++++++++ test.py | 125 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 145 insertions(+) create mode 100755 test create mode 100755 test.py diff --git a/test b/test new file mode 100755 index 0000000..ac2aec4 --- /dev/null +++ b/test @@ -0,0 +1,20 @@ +#!/bin/sh + +set -x + +NS_ROOT=${1?:Root not specified} +N=$2 + +python setup.py build || exit 1 +if [ ! -d ${NS_ROOT} ] ; then + sudo mkdir ${NS_ROOT} +fi +if [ "$(findmnt -n -o PROPAGATION ${NS_ROOT})" == "" ] ; then + sudo mount --bind ${NS_ROOT} ${NS_ROOT} +fi +if [ "$(findmnt -n -o PROPAGATION ${NS_ROOT})" == "shared" ] ; then + sudo mount --make-private ${NS_ROOT} +fi + +sudo PYTHONPATH=build/lib.linux-x86_64-2.7/ ./test.py ${NS_ROOT} ${N} +ls -li ${NS_ROOT} diff --git a/test.py b/test.py new file mode 100755 index 0000000..0d16168 --- /dev/null +++ b/test.py @@ -0,0 +1,125 @@ +#!/usr/bin/python + +import unshare +import time +import sys +import os +import itertools +import pickle + +kw_ns = dict(cgroup=unshare.CLONE_NEWCGROUP, + ipc=unshare.CLONE_NEWIPC, + mount=unshare.CLONE_NEWNS, + net=unshare.CLONE_NEWNET, + pid=unshare.CLONE_NEWPID, + user=unshare.CLONE_NEWUSER, + uts=unshare.CLONE_NEWUTS) + +flag_ns = (unshare.CLONE_FILES, + unshare.CLONE_FS, + unshare.CLONE_NEWCGROUP, + unshare.CLONE_NEWIPC, + unshare.CLONE_NEWNET, + unshare.CLONE_NEWNS, + unshare.CLONE_NEWPID, + unshare.CLONE_NEWUSER, + unshare.CLONE_NEWUTS, + unshare.CLONE_SIGHAND, + unshare.CLONE_SYSVSEM, + unshare.CLONE_THREAD, + unshare.CLONE_VM) + +def test(v): + flags = 0 + kwpath = {} + for a in v: + if len(a) == 1: + flags |= a[0] + else: + kwpath[a[0]] = a[1]; + for k in kwpath: + while os.path.exists(kwpath[k]): + try: + unshare.unbind(kwpath[k], kw_ns[k]) + except Exception, e: + break + open(kwpath[k], "w").close() + r,w = os.pipe() + r = os.fdopen(r) + w = os.fdopen(w, 'w') + pid = os.fork() + if pid != 0: + w.close() + pid, status = os.waitpid(pid, 0) + if status != 0: + print "%x %s failed" % (flags, kwpath) + raise(Exception) + else: + ns1 = pickle.load(r) + ns2 = dict([ (k, os.stat("/proc/self/ns/%s" % (k)).st_ino) + for k in os.listdir("/proc/self/ns") ]) + for k in ns1: + if k == 'pid': + continue + def getpath(): + if k in kwpath: + return kwpath[k] + if k == 'mnt' and 'mount' in kwpath: + return kwpath['mount'] + if k == 'pid_for_children' and 'pid' in kwpath: + return kwpath['pid'] + return None + def getflags(): + if k in kw_ns: + return kw_ns[k] + if k == 'mnt': + return kw_ns['mount'] + if k == 'pid_for_children': + return kw_ns['pid'] + raise Exception(k) + path = getpath() + def error(msg): + raise Exception("%s \n %s \n %s \n%x %s" % ( + msg, ns1, ns2, flags, kwpath)) + if path != None: + if ns1[k] != os.stat(path).st_ino: + error("Wrong namespace saved in %s %d" % ( + path, os.stat(path).st_ino)) + if ns1[k] == ns2[k]: + raise Exception("%s %s %s" % (k, ns1, ns2)) + elif getflags() & flags == 0: + if ns1[k] != ns2[k]: + error("Namespaces not equal %s" % (k)) + else: + if ns1[k] == ns2[k]: + error("Namespaces equal %s" % (k)) + else: + r.close() + try: + unshare.unshare(flags, **kwpath) + ns = dict([ (k, os.stat("/proc/self/ns/%s" % (k)).st_ino) + for k in os.listdir("/proc/self/ns") ]) + pickle.dump(ns, w) + w.close() + os._exit(0) + except Exception, e: + print>>sys.stderr, e + raise + os._exit(1) + +if __name__ == '__main__': + args = list([ (n, os.path.join(sys.argv[1], n)) for n in kw_ns ] + + [ (n,) for n in flag_ns ]) + if len(sys.argv) >=3: + N = int(sys.argv[2]) + else: + N = len(args) + for n in range(1, N + 1): + combinations = list(itertools.combinations(args, n)) + print "N=%d (%d combinations)" % (n, len(combinations)) + for v in combinations: + try: + test(v) + except: + print "test(%s) failed" % (v) + raise From 4f292226abdd38f267df38f6ecf792ad00312b03 Mon Sep 17 00:00:00 2001 From: Anders Blomdell Date: Sun, 3 Sep 2017 12:34:10 +0200 Subject: [PATCH 07/10] Logic cleanup. Added unbind function --- unshare.c | 240 +++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 174 insertions(+), 66 deletions(-) diff --git a/unshare.c b/unshare.c index b695ff8..ab220f6 100644 --- a/unshare.c +++ b/unshare.c @@ -38,6 +38,8 @@ struct mapping { char *mask_name; char *format; int is_mounted; + int ns_old; + int ns_new; }; #ifndef CLONE_NEWCGROUP @@ -66,6 +68,7 @@ static struct mask_list_entry { static PyObject * _unshare(PyObject *self, PyObject *args, PyObject *keywds) { + PyObject *result = NULL; int ret; int v_flags = 0; @@ -80,13 +83,13 @@ static PyObject * _unshare(PyObject *self, PyObject *args, PyObject *keywds) static char *kwlist[] = { "flags", "cgroup", "ipc", "mount", "net", "pid", "user", "uts", NULL }; struct mapping mapping[] = { { NULL, kwlist[0], 0, NULL, NULL, 0 }, - { &v_cgroup, kwlist[1], CLONE_NEWCGROUP, "CLONE_NEWCGROUP", "/proc/%d/ns/cgroup", 0 }, - { &v_ipc, kwlist[2], CLONE_NEWIPC, "CLONE_NEWIPC", "/proc/%d/ns/ipc", 0 }, - { &v_mount, kwlist[3], CLONE_NEWNS, "CLONE_NEWNS", "/proc/%d/ns/mnt", 0 }, - { &v_net, kwlist[4], CLONE_NEWNET, "CLONE_NEWNET", "/proc/%d/ns/net", 0 }, - { &v_pid, kwlist[5], CLONE_NEWPID, "CLONE_NEWPID", "/proc/%d/ns/pid", 0 }, - { &v_user, kwlist[6], CLONE_NEWUSER, "CLONE_NEWUSER", "/proc/%d/ns/user", 0 }, - { &v_uts, kwlist[7], CLONE_NEWUTS, "CLONE_NEWUTS", "/proc/%d/ns/uts", 0 }, + { &v_cgroup, kwlist[1], CLONE_NEWCGROUP, "CLONE_NEWCGROUP", "/proc/%d/ns/cgroup", 0, -1, -1 }, + { &v_ipc, kwlist[2], CLONE_NEWIPC, "CLONE_NEWIPC", "/proc/%d/ns/ipc", 0, -1, -1 }, + { &v_mount, kwlist[3], CLONE_NEWNS, "CLONE_NEWNS", "/proc/%d/ns/mnt", 0, -1, -1 }, + { &v_net, kwlist[4], CLONE_NEWNET, "CLONE_NEWNET", "/proc/%d/ns/net", 0, -1, -1 }, + { &v_pid, kwlist[5], CLONE_NEWPID, "CLONE_NEWPID", "/proc/%d/ns/pid_for_children", 0, -1, -1 }, + { &v_user, kwlist[6], CLONE_NEWUSER, "CLONE_NEWUSER", "/proc/%d/ns/user", 0, -1, -1 }, + { &v_uts, kwlist[7], CLONE_NEWUTS, "CLONE_NEWUTS", "/proc/%d/ns/uts", 0, -1, -1 }, { NULL, NULL, 0, NULL, NULL, 0 } }; @@ -95,10 +98,11 @@ static PyObject * _unshare(PyObject *self, PyObject *args, PyObject *keywds) &v_net, &v_pid, &v_user, &v_uts)) { return NULL; } - + int flags_child = 0; int flags_parent = v_flags; pid_t my_pid = getpid(); + struct mapping *m; for ( m = mapping + 1 ; m->path ; m++) { if (*m->path != empty) { @@ -110,7 +114,7 @@ static PyObject * _unshare(PyObject *self, PyObject *args, PyObject *keywds) ret = stat(ns, &stat_buf); if (ret == -1) { PyErr_SetFromErrnoWithFilename(PyExc_OSError, ns); - goto err_unbindable_namespace; + goto err_file_missing; } flags_child |= m->mask; flags_parent &= ~m->mask; @@ -120,119 +124,217 @@ static PyObject * _unshare(PyObject *self, PyObject *args, PyObject *keywds) } } } - + + int fd[2] = { -1, -1 }; if (flags_child) { - int fd[2]; ret = socketpair(AF_UNIX, SOCK_STREAM, 0, fd); - if(ret == -1) - return PyErr_SetFromErrno(PyExc_OSError); + if(ret == -1) { + PyErr_SetFromErrno(PyExc_OSError); + goto err_socketpair; + } pid_t pid = fork(); if (pid < 0) { - close(fd[0]); - close(fd[1]); - return PyErr_SetFromErrno(PyExc_OSError); + PyErr_SetFromErrno(PyExc_OSError); + goto err_fork; } else if (pid == 0) { /* Child */ close(fd[1]); unsigned char result = 0; ret = unshare(flags_child); + if (flags_child & CLONE_NEWPID) { + /* Force pid_for_children to appear */ + pid_t pid = fork(); + if (pid == 0) { + exit(0); + } + waitpid(pid, NULL, 0); + } if (ret != 0) { result = errno < 255 ? errno : EINVAL; } if (write(fd[0], &result, 1) != 1) { - close(fd[0]); - exit(1); + goto exit_1; } ret = read(fd[0], &result, 1); if (ret != 1 || result != 'Q') { - close(fd[0]); - exit(1); + goto exit_1; } close(fd[0]); exit(0); + exit_1: + close(fd[0]); + exit(1); } else { /* Parent */ unsigned char result; - + close(fd[0]); + fd[0] = -1; ret = read(fd[1], &result, 1); if (ret == -1) { - close(fd[1]); - return PyErr_SetFromErrno(PyExc_OSError); + PyErr_SetFromErrno(PyExc_OSError); + goto out; } if (result != 0) { errno = result; - return PyErr_SetFromErrno(PyExc_OSError); + PyErr_SetFromErrno(PyExc_OSError); + goto out; } for ( m = mapping + 1 ; m->path ; m++) {; if (*m->path != NULL && *m->path != empty) { - char ns[PATH_MAX]; + /* Save a restoration namespace in case of error */ + + snprintf(ns, sizeof(ns), m->format, my_pid); + m->ns_old = open(ns, O_RDONLY); + if (m->ns_old == -1) { + PyErr_SetFromErrnoWithFilename(PyExc_OSError, ns); + goto err_ns_old_open; + } snprintf(ns, sizeof(ns), m->format, pid); + m->ns_new = open(ns, O_RDONLY); + if (m->ns_new == -1) { + PyErr_SetFromErrnoWithFilename(PyExc_OSError, ns); + goto err_ns_new_open; + } ret = mount(ns, *m->path, NULL, MS_BIND, NULL); if (ret == -1) { close(fd[1]); PyErr_SetFromErrnoWithFilename(PyExc_OSError, *m->path); - goto err_cleanup_mounts; + goto err_ns_mount; } else { m->is_mounted = 1; } } } - for ( m = mapping + 1 ; m->path ; m++) { - if (flags_child & m->mask) { - char ns[PATH_MAX]; - int fd; - snprintf(ns, sizeof(ns), m->format, pid); - fd = open(ns, O_RDONLY); - if (fd < 0) { - PyErr_SetFromErrnoWithFilename(PyExc_OSError, ns); - goto err_open_ns_failed; - } - /* Temporarily unshare namespace before entering - child namespace (gdm's use of pam needs this) */ - ret = unshare(m->mask); - if (ret == -1) { - PyErr_SetFromErrnoWithFilename(PyExc_OSError, ns); - goto err_unshare_ns_failed; - } - ret = setns(fd, m->mask); - close(fd); - if (ret == -1) { - PyErr_SetFromErrnoWithFilename(PyExc_OSError, ns); - goto err_setns_failed; - } - } - } - if (write(fd[1], "Q", 1) != 1) { - close(fd[1]); - return PyErr_SetFromErrno(PyExc_OSError); + PyErr_SetFromErrno(PyExc_OSError); + goto err_child_terminate; } - close(fd[1]); waitpid(pid, NULL, 0); } } - ret = unshare(flags_parent); - if (ret == -1) { - /* If flags_child != 0 we are in serious trouble */ - return PyErr_SetFromErrno(PyExc_OSError); + if (flags_parent & ~CLONE_NEWUSER) { + ret = unshare(flags_parent & ~CLONE_NEWUSER); + if (ret == -1) { + PyErr_SetFromErrno(PyExc_OSError); + goto err_unshare_failed; + } } - Py_RETURN_NONE; + for ( m = mapping + 1 ; m->path ; m++) { + if (m->ns_new != -1) { + ret = setns(m->ns_new, m->mask); + if (ret == -1) { + PyErr_SetFromErrno(PyExc_OSError); + goto err_setns_failed; + } + } + } + if (flags_parent & CLONE_NEWUSER) { + ret = unshare(CLONE_NEWUSER); + if (ret == -1) { + PyErr_SetFromErrno(PyExc_OSError); + goto err_unshare_newuser_failed; + } + } + if (flags_parent & CLONE_NEWPID) { + /* Force pid_for_children to appear */ + pid_t pid = fork(); + if (pid == 0) { + exit(0); + } + waitpid(pid, NULL, 0); + } + result = Py_None; + goto out; -err_open_ns_failed: -err_unshare_ns_failed: + +err_child_terminate: +err_unshare_newuser_failed: err_setns_failed: -err_cleanup_mounts: + /* restore old namespaces */ + for ( m = mapping + 1 ; m->path ; m++) { + if (m->ns_old != -1) { + ret = setns(m->ns_old, m->mask); + } + } +err_unshare_failed: +err_ns_mount: +err_ns_new_open: +err_ns_old_open: + /* Remove (now invalid) ns mounts */ for ( m = mapping + 1 ; m->path ; m++) {; if (m->is_mounted) { umount(*m->path); m->is_mounted = 0; } } - -err_unbindable_namespace: - return NULL; +err_fork: +err_socketpair: +err_file_missing: + +out: + for ( m = mapping + 1 ; m->path ; m++) {; + if (m->ns_old != -1) { + close(m->ns_old); + m->ns_old = -1; + } + if (m->ns_new != -1) { + close(m->ns_new); + m->ns_new = -1; + } + } + if (fd[0] != -1) { close(fd[0]); } + if (fd[1] != -1) { close(fd[1]); } + if (result == NULL) { + return NULL; + } else { + Py_RETURN_NONE; + } +} + +static PyObject * _unbind(PyObject *self, PyObject *args) { + char *path; + int nstype, ret; + if (!PyArg_ParseTuple(args, "si", &path, &nstype)) + return NULL; + pid_t pid = fork(); + if (pid < 0) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL;; + } else if (pid == 0) { + /* Child */ + int fd; + fd = open(path, O_RDONLY); + if (fd == -1) { + exit(1); + } + ret = setns(fd, nstype); + if (ret == -1) { + close(fd); + exit(1); + } + close(fd); + exit(0); + } else { + /* Parent */ + int status, res; + res = waitpid(pid, &status, 0); + if (res == -1) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + if (status != 0) { + PyErr_Format(PyExc_OSError, "%s does not refer to namspace of kind %x", path, nstype); + return NULL; + } + res = umount(path); + if (res == -1) { + PyErr_SetFromErrnoWithFilename(PyExc_OSError, path); + return NULL; + } + } + Py_RETURN_NONE; } static PyObject * _setns(PyObject *self, PyObject *args) { @@ -265,6 +367,12 @@ static PyMethodDef methods[] = { " user=PATH save new user namespace to PATH (CLONE_NEWUSER)\n" " uts=PATH save new uts namespa Date: Sun, 3 Sep 2017 16:20:03 +0200 Subject: [PATCH 08/10] Add get_nstype call --- unshare.c | 176 +++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 133 insertions(+), 43 deletions(-) diff --git a/unshare.c b/unshare.c index ab220f6..1196d4f 100644 --- a/unshare.c +++ b/unshare.c @@ -30,6 +30,7 @@ #include #include #include +#include struct mapping { char **path; @@ -49,21 +50,22 @@ struct mapping { static struct mask_list_entry { char *name; int mask; + int bindable; } mask_list[] = { - { "CLONE_FILES", CLONE_FILES }, - { "CLONE_FS", CLONE_FS }, - { "CLONE_NEWCGROUP", CLONE_NEWCGROUP }, - { "CLONE_NEWIPC", CLONE_NEWIPC }, - { "CLONE_NEWNET", CLONE_NEWNET }, - { "CLONE_NEWNS", CLONE_NEWNS }, - { "CLONE_NEWPID", CLONE_NEWPID }, - { "CLONE_NEWUSER", CLONE_NEWUSER }, - { "CLONE_NEWUTS", CLONE_NEWUTS }, - { "CLONE_SIGHAND", CLONE_SIGHAND }, - { "CLONE_SYSVSEM", CLONE_SYSVSEM }, - { "CLONE_THREAD", CLONE_THREAD }, - { "CLONE_VM", CLONE_VM }, - { NULL, 0 } + { "CLONE_FILES", CLONE_FILES, 0 }, + { "CLONE_FS", CLONE_FS, 0 }, + { "CLONE_NEWCGROUP", CLONE_NEWCGROUP, 1 }, + { "CLONE_NEWIPC", CLONE_NEWIPC, 1 }, + { "CLONE_NEWNET", CLONE_NEWNET, 1 }, + { "CLONE_NEWNS", CLONE_NEWNS, 1 }, + { "CLONE_NEWPID", CLONE_NEWPID, 1 }, + { "CLONE_NEWUSER", CLONE_NEWUSER, 1 }, + { "CLONE_NEWUTS", CLONE_NEWUTS, 1 }, + { "CLONE_SIGHAND", CLONE_SIGHAND, 0 }, + { "CLONE_SYSVSEM", CLONE_SYSVSEM, 0 }, + { "CLONE_THREAD", CLONE_THREAD, 0 }, + { "CLONE_VM", CLONE_VM, 0 }, + { NULL, 0, 0 } }; static PyObject * _unshare(PyObject *self, PyObject *args, PyObject *keywds) @@ -293,46 +295,129 @@ static PyObject * _unshare(PyObject *self, PyObject *args, PyObject *keywds) } } -static PyObject * _unbind(PyObject *self, PyObject *args) { - char *path; - int nstype, ret; - if (!PyArg_ParseTuple(args, "si", &path, &nstype)) - return NULL; +static int get_nstype_or_zero_or_errno(char *path) { + int result = 0; + int saved_errno = 0; + int fd[2] = { -1, -1 }; + int path_fd = open(path, O_RDONLY); + if (path_fd == -1) { + goto out_errno; + } + int path_nstype = 0; +#ifdef NS_GET_NSTYPE + /* Compiled on system that knows about ioctl_ns */ + path_nstype = ioctl(path_fd, NS_GET_NSTYPE); + if (path_nstype != -1) { + result = path_nstype; + goto out_close; + } else if (path_nstype == -1 && errno == ENOTTY) { + /* Not a namespace path */ + goto out_close; + } else if (path_nstype == -1 && errno != EINVAL) { + /* Unexpected, propagate errno */ + goto out_errno; + } + /* Running on Linux older than 4.11 */ +#else + /* Compiled on Linux older than 4.11 */ +#endif + /* Expensive pre Linux 4.11 path */ + if (pipe(fd) == -1) { + goto out_errno; + } pid_t pid = fork(); if (pid < 0) { - PyErr_SetFromErrno(PyExc_OSError); - return NULL;; + goto out_errno; } else if (pid == 0) { - /* Child */ - int fd; - fd = open(path, O_RDONLY); - if (fd == -1) { - exit(1); + /* Child, test all possible known namespace kinds */ + close(fd[0]); + struct mask_list_entry *ml; + for (ml = mask_list ; ml->name ; ml++) { + if (ml-> bindable) { + int ret = setns(path_fd, ml->mask); + if (ret == 0) { + ret = write(fd[1], &ml->mask, sizeof(ml->mask)); + if (ret != sizeof(ml->mask)) { + exit(1); + } + exit(0); + } + } } - ret = setns(fd, nstype); - if (ret == -1) { - close(fd); + int zero = 0; + int ret = write(fd[1], &zero, sizeof(zero)); + if (ret != sizeof(zero)) { exit(1); } - close(fd); exit(0); } else { /* Parent */ - int status, res; - res = waitpid(pid, &status, 0); - if (res == -1) { - PyErr_SetFromErrno(PyExc_OSError); - return NULL; - } - if (status != 0) { - PyErr_Format(PyExc_OSError, "%s does not refer to namspace of kind %x", path, nstype); - return NULL; + close(fd[1]); + int ret = read(fd[0], &result, sizeof(result)); + if (ret == -1) { + goto out_errno; + } else if (ret != sizeof(result)) { + errno = EIO; + goto out_errno; } - res = umount(path); - if (res == -1) { - PyErr_SetFromErrnoWithFilename(PyExc_OSError, path); - return NULL; + int status; + ret = waitpid(pid, &status, 0); + if (ret == -1) { + goto out_errno; + } else if (status != 0) { + errno = EIO; + goto out_errno; } + goto out; + } + +out_errno: + result = -1; + saved_errno = errno; +out_close: + if (path_fd != -1) { close(path_fd); } + if (fd[0] != -1) { close(fd[0]); } + if (fd[1] != -1) { close(fd[1]); } +out: + if (saved_errno != 0) { errno = saved_errno; } + return result; +} + +static PyObject * _get_nstype(PyObject *self, PyObject *args) { + char *path; + if (!PyArg_ParseTuple(args, "s", &path)) + return NULL; + + int path_nstype = get_nstype_or_zero_or_errno(path); + if (path_nstype == -1) { + PyErr_SetFromErrnoWithFilename(PyExc_OSError, path); + return NULL; + } else if (path_nstype == 0) { + Py_RETURN_NONE; + } else { + return Py_BuildValue("i", path_nstype); + } +} + +static PyObject * _unbind(PyObject *self, PyObject *args) { + char *path; + int nstype, res; + if (!PyArg_ParseTuple(args, "si", &path, &nstype)) + return NULL; + + int path_nstype = get_nstype_or_zero_or_errno(path); + if (path_nstype == -1) { + PyErr_SetFromErrnoWithFilename(PyExc_OSError, path); + return NULL; + } else if (path_nstype == 0 || nstype != path_nstype) { + PyErr_Format(PyExc_OSError, "%s does not refer to namspace of kind %x", + path, nstype); + return NULL; + } + res = umount2(path, MNT_DETACH); + if (res == -1) { + PyErr_SetFromErrnoWithFilename(PyExc_OSError, path); + return NULL; } Py_RETURN_NONE; } @@ -367,6 +452,11 @@ static PyMethodDef methods[] = { " user=PATH save new user namespace to PATH (CLONE_NEWUSER)\n" " uts=PATH save new uts namespa nstype\n\n" + "Return the nstype for path\n\n" + "None is returned if path does not refer to a namespace\n" + }, {"unbind", _unbind, METH_VARARGS, "unbind(path, nstype)\n\n" "Unbind path from its current namespace.\n" From 38591bd191042652ac5286fea58a7d4eaeb76493 Mon Sep 17 00:00:00 2001 From: Anders Blomdell Date: Sun, 3 Sep 2017 16:22:53 +0200 Subject: [PATCH 09/10] Use new get_nstype call to unly unmount when needed. Run test in new mount namespace. --- test.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/test.py b/test.py index 0d16168..4fe9a4d 100755 --- a/test.py +++ b/test.py @@ -38,11 +38,8 @@ def test(v): else: kwpath[a[0]] = a[1]; for k in kwpath: - while os.path.exists(kwpath[k]): - try: - unshare.unbind(kwpath[k], kw_ns[k]) - except Exception, e: - break + if os.path.exists(kwpath[k]) and unshare.get_nstype(kwpath[k]) != None: + unshare.unbind(kwpath[k], kw_ns[k]) open(kwpath[k], "w").close() r,w = os.pipe() r = os.fdopen(r) @@ -50,14 +47,14 @@ def test(v): pid = os.fork() if pid != 0: w.close() + ns1 = pickle.load(r) + ns2 = dict([ (k, os.stat("/proc/self/ns/%s" % (k)).st_ino) + for k in os.listdir("/proc/self/ns") ]) pid, status = os.waitpid(pid, 0) if status != 0: print "%x %s failed" % (flags, kwpath) raise(Exception) else: - ns1 = pickle.load(r) - ns2 = dict([ (k, os.stat("/proc/self/ns/%s" % (k)).st_ino) - for k in os.listdir("/proc/self/ns") ]) for k in ns1: if k == 'pid': continue @@ -114,12 +111,13 @@ def error(msg): N = int(sys.argv[2]) else: N = len(args) + unshare.unshare(unshare.CLONE_NEWNS) # Isolate test from systemd for n in range(1, N + 1): combinations = list(itertools.combinations(args, n)) - print "N=%d (%d combinations)" % (n, len(combinations)) + print "n=%d/N=%d (%d combinations)" % (n, N, len(combinations)) for v in combinations: try: test(v) except: - print "test(%s) failed" % (v) + print "test(%s) failed" % (str(v)) raise From a67b48e632ccf8c84197375a9900d85826f61c9a Mon Sep 17 00:00:00 2001 From: Anders Blomdell Date: Sun, 3 Sep 2017 16:46:11 +0200 Subject: [PATCH 10/10] Use _exit() after fork. Run test with unbuffered I/O --- test.py | 3 +-- unshare.c | 16 ++++++++-------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/test.py b/test.py index 4fe9a4d..b4c1fe9 100755 --- a/test.py +++ b/test.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/python -u import unshare import time @@ -101,7 +101,6 @@ def error(msg): os._exit(0) except Exception, e: print>>sys.stderr, e - raise os._exit(1) if __name__ == '__main__': diff --git a/unshare.c b/unshare.c index 1196d4f..dd33670 100644 --- a/unshare.c +++ b/unshare.c @@ -147,7 +147,7 @@ static PyObject * _unshare(PyObject *self, PyObject *args, PyObject *keywds) /* Force pid_for_children to appear */ pid_t pid = fork(); if (pid == 0) { - exit(0); + _exit(0); } waitpid(pid, NULL, 0); } @@ -162,10 +162,10 @@ static PyObject * _unshare(PyObject *self, PyObject *args, PyObject *keywds) goto exit_1; } close(fd[0]); - exit(0); + _exit(0); exit_1: close(fd[0]); - exit(1); + _exit(1); } else { /* Parent */ unsigned char result; @@ -243,7 +243,7 @@ static PyObject * _unshare(PyObject *self, PyObject *args, PyObject *keywds) /* Force pid_for_children to appear */ pid_t pid = fork(); if (pid == 0) { - exit(0); + _exit(0); } waitpid(pid, NULL, 0); } @@ -338,18 +338,18 @@ static int get_nstype_or_zero_or_errno(char *path) { if (ret == 0) { ret = write(fd[1], &ml->mask, sizeof(ml->mask)); if (ret != sizeof(ml->mask)) { - exit(1); + _exit(1); } - exit(0); + _exit(0); } } } int zero = 0; int ret = write(fd[1], &zero, sizeof(zero)); if (ret != sizeof(zero)) { - exit(1); + _exit(1); } - exit(0); + _exit(0); } else { /* Parent */ close(fd[1]);