unshare: support the switching of namespaces

In addition to the unshare syscall, there exists the setns syscall, which
allows processes to migrate to the namepsaces of other processes.  Add this
functionality into the unshare command, as they operate in a fairly simmilar
fashion.

Note: There was discussion of adding a path based namespace argument to unshare
in the origional discussion thread, but I opted to leave that out as it didn't
seem to fit in nicely with the current argument pattern.  I figure we can always
add that in later if we need to

[kzak@redhat.com: - fix optional arguments
                  - do not call unshare if no flag specified
                  - use O_CLOEXEC
                  - codding style cleanup]

Signed-off-by: Neil Horman <nhorman@tuxdriver.com>
CC: Karel Zak <kzak@redhat.com>
Signed-off-by: Karel Zak <kzak@redhat.com>
This commit is contained in:
Neil Horman 2012-12-28 11:22:18 -05:00 committed by Karel Zak
parent 91984e938e
commit 4bbe809939
3 changed files with 83 additions and 29 deletions

View File

@ -287,6 +287,7 @@ if BUILD_UNSHARE
usrbin_exec_PROGRAMS += unshare
dist_man_MANS += sys-utils/unshare.1
unshare_SOURCES = sys-utils/unshare.c
unshare_LDADD = $(LDADD) libcommon.la
endif
if BUILD_ARCH

View File

@ -3,15 +3,15 @@
.\"
.TH UNSHARE 1 "October 2008" "util-linux" "User Commands"
.SH NAME
unshare \- run program with some namespaces unshared from parent
unshare \- run program with some namespaces unshared or changed from parent
.SH SYNOPSIS
.B unshare
.RI [ options ]
program
.RI [ arguments ]
.SH DESCRIPTION
Unshares specified namespaces from parent process and then executes specified
program. Unshareable namespaces are:
Unshares or migrates specified namespaces from parent process and then executes specified
program. Available namespaces are:
.TP
.BR "mount namespace"
mounting and unmounting filesystems will not affect rest of the system
@ -33,31 +33,43 @@ etc. (\fBCLONE_NEWNET\fP flag).
.TP
See the \fBclone\fR(2) for exact semantics of the flags.
.SH OPTIONS
Note when specifying the optional \fB<pid>\fP argument, the string of option,
equal sign (=), and the pid must not contain any blanks or other white space.
The correct form is for example --ipc=123 or -i=123.
.TP
.BR \-h , " \-\-help"
Print a help message,
.TP
.BR \-m , " \-\-mount"
Unshare the mount namespace,
.BR \-m , " \-\-mount " \fI[=pid]\fP
Unshare the mount namespace, or, when a pid is specified, migrate the mount
namespace to the one attached to the specified pid.
.TP
.BR \-u , " \-\-uts"
Unshare the UTC namespace,
.BR \-u , " \-\-uts " \fI[=pid]\fP
Unshare the UTC namespace, or, when a pid is specified, migrate the uts
namespace to the one attached to the specified pid
.TP
.BR \-i , " \-\-ipc"
Unshare the IPC namespace,
.BR \-i , " \-\-ipc " \fI[=pid]\fP
Unshare the IPC namespace, or, when a pid is specified, migrate the ipc
namespace to the one attached to the specified pid
.TP
.BR \-n , " \-\-net"
Unshare the network namespace.
.BR \-n , " \-\-net " \fI[=pid]\fP
Unshare the network namespace, or, when a pid is specified, migrate the net
namespace to the one attached to the specified pid
.SH NOTES
The unshare command drops potential privileges before executing the
target program. This allows to setuid unshare.
.P
Support for migrating processes between mount and pid namespace is available in
kernels 3.8 and later
.SH SEE ALSO
.BR unshare (2),
.BR setns (2),
.BR clone (2)
.SH BUGS
None known so far.
.SH AUTHOR
.SH AUTHORS
Mikhail Gusarov <dottedmag@dottedmag.net>
Neil Horman <nhorman@tuxdriver.com>
.SH AVAILABILITY
The unshare command is part of the util-linux package and is available from
ftp://ftp.kernel.org/pub/linux/utils/util-linux/.

View File

@ -24,10 +24,12 @@
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <getopt.h>
#include "nls.h"
#include "c.h"
#include "closestream.h"
#include "strutils.h"
#ifndef CLONE_NEWSNS
# define CLONE_NEWNS 0x00020000
@ -60,10 +62,10 @@ static void usage(int status)
_(" %s [options] <program> [args...]\n"), program_invocation_short_name);
fputs(USAGE_OPTIONS, out);
fputs(_(" -m, --mount unshare mounts namespace\n"
" -u, --uts unshare UTS namespace (hostname etc)\n"
" -i, --ipc unshare System V IPC namespace\n"
" -n, --net unshare network namespace\n"), out);
fputs(_(" -m, --mount [=<pid>] unshare or migrate mounts namespace\n"
" -u, --uts [=<pid>] unshare or migrate UTS namespace (hostname etc)\n"
" -i, --ipc [=<pid>] unshare or migrate System V IPC namespace\n"
" -n, --net [=<pid>] unshare or migrate network namespace\n"), out);
fputs(USAGE_SEPARATOR, out);
fputs(USAGE_HELP, out);
@ -76,17 +78,18 @@ static void usage(int status)
int main(int argc, char *argv[])
{
static const struct option longopts[] = {
{ "help", no_argument, 0, 'h' },
{ "help", no_argument, 0, 'h' },
{ "version", no_argument, 0, 'V'},
{ "mount", no_argument, 0, 'm' },
{ "uts", no_argument, 0, 'u' },
{ "ipc", no_argument, 0, 'i' },
{ "net", no_argument, 0, 'n' },
{ "mount", optional_argument, 0, 'm' },
{ "uts", optional_argument, 0, 'u' },
{ "ipc", optional_argument, 0, 'i' },
{ "net", optional_argument, 0, 'n' },
{ NULL, 0, 0, 0 }
};
int namespaces[128]; /* /proc/#/ns/<name> file descriptors */
size_t i, nscount = 0; /* number of used namespaces[] */
int unshare_flags = 0;
int c;
setlocale(LC_MESSAGES, "");
@ -94,7 +97,13 @@ int main(int argc, char *argv[])
textdomain(PACKAGE);
atexit(close_stdout);
while((c = getopt_long(argc, argv, "hVmuin", longopts, NULL)) != -1) {
memset(namespaces, 0, sizeof(namespaces));
while((c = getopt_long(argc, argv,
"hVm::u::i::n::", longopts, NULL)) != -1) {
const char *ns = NULL;
switch(c) {
case 'h':
usage(EXIT_SUCCESS);
@ -102,26 +111,58 @@ int main(int argc, char *argv[])
printf(UTIL_LINUX_VERSION);
return EXIT_SUCCESS;
case 'm':
unshare_flags |= CLONE_NEWNS;
ns = "mnt";
if (!optarg)
unshare_flags |= CLONE_NEWNS;
break;
case 'u':
unshare_flags |= CLONE_NEWUTS;
ns = "uts";
if (!optarg)
unshare_flags |= CLONE_NEWUTS;
break;
case 'i':
unshare_flags |= CLONE_NEWIPC;
ns = "ipc";
if (!optarg)
unshare_flags |= CLONE_NEWIPC;
break;
case 'n':
unshare_flags |= CLONE_NEWNET;
ns = "net";
if (!optarg)
unshare_flags |= CLONE_NEWNET;
break;
default:
usage(EXIT_FAILURE);
}
if (ns && optarg) {
pid_t pid;
char path[512];
if (nscount >= ARRAY_SIZE(namespaces))
err(EXIT_FAILURE, _("too many new namespaces specified"));
if (*optarg == '=')
optarg++;
pid = strtoul_or_err(optarg, _("failed to parse pid argument"));
sprintf(path, "/proc/%lu/ns/%s", (unsigned long) pid, ns);
namespaces[nscount] = open(path, O_RDONLY | O_CLOEXEC);
if (namespaces[nscount] < 0)
err(EXIT_FAILURE, _("cannot open %s"), path);
nscount++;
}
}
if(optind >= argc)
if (optind >= argc)
usage(EXIT_FAILURE);
if(-1 == unshare(unshare_flags))
for (i = 0; i < nscount; i++) {
if (setns(namespaces[i], 0) != 0)
err(EXIT_FAILURE, _("setns failed"));
}
if (unshare_flags && unshare(unshare_flags) != 0)
err(EXIT_FAILURE, _("unshare failed"));
/* drop potential root euid/egid if we had been setuid'd */