nsenter,unshare: share common code; fix a bug of not closing all fds
[oweals/busybox.git] / util-linux / nsenter.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * Mini nsenter implementation for busybox.
4  *
5  * Copyright (C) 2016 by Bartosz Golaszewski <bartekgola@gmail.com>
6  *
7  * Licensed under GPLv2 or later, see file LICENSE in this source tree.
8  */
9
10 //config:config NSENTER
11 //config:       bool "nsenter"
12 //config:       default y
13 //config:       select PLATFORM_LINUX
14 //config:       help
15 //config:         Run program with namespaces of other processes.
16 //config:
17 //config:config FEATURE_NSENTER_LONG_OPTS
18 //config:       bool "Enable long options"
19 //config:       default y
20 //config:       depends on NSENTER && LONG_OPTS
21 //config:       help
22 //config:         Support long options for the nsenter applet. This makes
23 //config:         the busybox implementation more compatible with upstream.
24
25 //applet:IF_NSENTER(APPLET(nsenter, BB_DIR_USR_BIN, BB_SUID_DROP))
26
27 //kbuild:lib-$(CONFIG_NSENTER) += nsenter.o
28
29 //usage:#define nsenter_trivial_usage
30 //usage:       "[OPTIONS] [PROG [ARGS]]"
31 //usage:#if ENABLE_FEATURE_NSENTER_LONG_OPTS
32 //usage:#define nsenter_full_usage "\n"
33 //usage:     "\n        -t, --target=PID                Target process to get namespaces from"
34 //usage:     "\n        -m, --mount[=FILE]              Enter mount namespace"
35 //usage:     "\n        -u, --uts[=FILE]                Enter UTS namespace (hostname etc)"
36 //usage:     "\n        -i, --ipc[=FILE]                Enter System V IPC namespace"
37 //usage:     "\n        -n, --net[=FILE]                Enter network namespace"
38 //usage:     "\n        -p, --pid[=FILE]                Enter pid namespace"
39 //usage:     "\n        -U, --user[=FILE]               Enter user namespace"
40 //usage:     "\n        -S, --setuid=UID                Set uid in entered namespace"
41 //usage:     "\n        -G, --setgid=GID                Set gid in entered namespace"
42 //usage:     "\n        --preserve-credentials          Don't touch uids or gids"
43 //usage:     "\n        -r, --root[=DIR]                Set root directory"
44 //usage:     "\n        -w, --wd[=DIR]                  Set working directory"
45 //usage:     "\n        -F, --no-fork                   Don't fork before exec'ing PROG"
46 //usage:#else
47 //usage:#define nsenter_full_usage "\n"
48 //usage:     "\n        -t PID          Target process to get namespaces from"
49 //usage:     "\n        -m[FILE]        Enter mount namespace"
50 //usage:     "\n        -u[FILE]        Enter UTS namespace (hostname etc)"
51 //usage:     "\n        -i[FILE]        Enter System V IPC namespace"
52 //usage:     "\n        -n[FILE]        Enter network namespace"
53 //usage:     "\n        -p[FILE]        Enter pid namespace"
54 //usage:     "\n        -U[FILE]        Enter user namespace"
55 //usage:     "\n        -S UID          Set uid in entered namespace"
56 //usage:     "\n        -G GID          Set gid in entered namespace"
57 //usage:     "\n        -r[DIR]         Set root directory"
58 //usage:     "\n        -w[DIR]         Set working directory"
59 //usage:     "\n        -F              Don't fork before exec'ing PROG"
60 //usage:#endif
61
62 #include <sched.h>
63 #include "libbb.h"
64
65 struct namespace_descr {
66         int flag;               /* value passed to setns() */
67         char ns_nsfile8[8];     /* "ns/" + namespace file in process' procfs entry */
68 };
69
70 struct namespace_ctx {
71         char *path;             /* optional path to a custom ns file */
72         int fd;                 /* opened namespace file descriptor */
73 };
74
75 enum {
76         OPT_user        = 1 << 0,
77         OPT_ipc         = 1 << 1,
78         OPT_uts         = 1 << 2,
79         OPT_network     = 1 << 3,
80         OPT_pid         = 1 << 4,
81         OPT_mount       = 1 << 5,
82         OPT_target      = 1 << 6,
83         OPT_setuid      = 1 << 7,
84         OPT_setgid      = 1 << 8,
85         OPT_root        = 1 << 9,
86         OPT_wd          = 1 << 10,
87         OPT_nofork      = 1 << 11,
88         OPT_prescred    = (1 << 12) * ENABLE_FEATURE_NSENTER_LONG_OPTS,
89 };
90 enum {
91         NS_USR_POS = 0,
92         NS_IPC_POS,
93         NS_UTS_POS,
94         NS_NET_POS,
95         NS_PID_POS,
96         NS_MNT_POS,
97         NS_COUNT,
98 };
99 /*
100  * The order is significant in nsenter.
101  * The user namespace comes first, so that it is entered first.
102  * This gives an unprivileged user the potential to enter other namespaces.
103  */
104 static const struct namespace_descr ns_list[] = {
105         { CLONE_NEWUSER, "ns/user", },
106         { CLONE_NEWIPC,  "ns/ipc",  },
107         { CLONE_NEWUTS,  "ns/uts",  },
108         { CLONE_NEWNET,  "ns/net",  },
109         { CLONE_NEWPID,  "ns/pid",  },
110         { CLONE_NEWNS,   "ns/mnt",  },
111 };
112 /*
113  * Upstream nsenter doesn't support the short option for --preserve-credentials
114  */
115 static const char opt_str[] = "U::i::u::n::p::m::""t+S+G+r::w::F";
116
117 #if ENABLE_FEATURE_NSENTER_LONG_OPTS
118 static const char nsenter_longopts[] ALIGN1 =
119         "user\0"                        Optional_argument       "U"
120         "ipc\0"                         Optional_argument       "i"
121         "uts\0"                         Optional_argument       "u"
122         "network\0"                     Optional_argument       "n"
123         "pid\0"                         Optional_argument       "p"
124         "mount\0"                       Optional_argument       "m"
125         "target\0"                      Required_argument       "t"
126         "setuid\0"                      Required_argument       "S"
127         "setgid\0"                      Required_argument       "G"
128         "root\0"                        Optional_argument       "r"
129         "wd\0"                          Optional_argument       "w"
130         "no-fork\0"                     No_argument             "F"
131         "preserve-credentials\0"        No_argument             "\xff"
132         ;
133 #endif
134
135 /*
136  * Open a file and return the new descriptor. If a full path is provided in
137  * fs_path, then the file to which it points is opened. Otherwise (fd_path is
138  * NULL) the routine builds a path to a procfs file using the following
139  * template: '/proc/<target_pid>/<target_file>'.
140  */
141 static int open_by_path_or_target(const char *path,
142                                   pid_t target_pid, const char *target_file)
143 {
144         char proc_path_buf[sizeof("/proc/%u/1234567890") + sizeof(int)*3];
145
146         if (!path) {
147                 if (target_pid == 0) {
148                         /* Example:
149                          * "nsenter -p PROG" - neither -pFILE nor -tPID given.
150                          */
151                         bb_show_usage();
152                 }
153                 snprintf(proc_path_buf, sizeof(proc_path_buf),
154                          "/proc/%u/%s", (unsigned)target_pid, target_file);
155                 path = proc_path_buf;
156         }
157
158         return xopen(path, O_RDONLY);
159 }
160
161 int nsenter_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
162 int nsenter_main(int argc UNUSED_PARAM, char **argv)
163 {
164         int i;
165         unsigned int opts;
166         const char *root_dir_str = NULL;
167         const char *wd_str = NULL;
168         struct namespace_ctx ns_ctx_list[NS_COUNT];
169         int setgroups_failed;
170         int root_fd, wd_fd;
171         int target_pid = 0;
172         int uid = 0;
173         int gid = 0;
174
175         memset(ns_ctx_list, 0, sizeof(ns_ctx_list));
176
177         IF_FEATURE_NSENTER_LONG_OPTS(applet_long_options = nsenter_longopts);
178         opts = getopt32(argv, opt_str,
179                         &ns_ctx_list[NS_USR_POS].path,
180                         &ns_ctx_list[NS_IPC_POS].path,
181                         &ns_ctx_list[NS_UTS_POS].path,
182                         &ns_ctx_list[NS_NET_POS].path,
183                         &ns_ctx_list[NS_PID_POS].path,
184                         &ns_ctx_list[NS_MNT_POS].path,
185                         &target_pid, &uid, &gid,
186                         &root_dir_str, &wd_str
187         );
188         argv += optind;
189
190         root_fd = wd_fd = -1;
191         if (opts & OPT_root)
192                 root_fd = open_by_path_or_target(root_dir_str,
193                                                  target_pid, "root");
194         if (opts & OPT_wd)
195                 wd_fd = open_by_path_or_target(wd_str, target_pid, "cwd");
196
197         for (i = 0; i < NS_COUNT; i++) {
198                 const struct namespace_descr *ns = &ns_list[i];
199                 struct namespace_ctx *ns_ctx = &ns_ctx_list[i];
200
201                 ns_ctx->fd = -1;
202                 if (opts & (1 << i))
203                         ns_ctx->fd = open_by_path_or_target(ns_ctx->path,
204                                         target_pid, ns->ns_nsfile8);
205         }
206
207         /*
208          * Entering the user namespace without --preserve-credentials implies
209          * --setuid & --setgid and clearing root's groups.
210          */
211         setgroups_failed = 0;
212         if ((opts & OPT_user) && !(opts & OPT_prescred)) {
213                 opts |= (OPT_setuid | OPT_setgid);
214                 /*
215                  * We call setgroups() before and after setns() and only
216                  * bail-out if it fails twice.
217                  */
218                 setgroups_failed = (setgroups(0, NULL) < 0);
219         }
220
221         for (i = 0; i < NS_COUNT; i++) {
222                 const struct namespace_descr *ns = &ns_list[i];
223                 struct namespace_ctx *ns_ctx = &ns_ctx_list[i];
224
225                 if (ns_ctx->fd < 0)
226                         continue;
227                 if (setns(ns_ctx->fd, ns->flag)) {
228                         bb_perror_msg_and_die(
229                                 "setns(): can't reassociate to namespace '%s'",
230                                 ns->ns_nsfile8 + 3 /* skip over "ns/" */
231                         );
232                 }
233                 close(ns_ctx->fd); /* should close fds, to not confuse exec'ed PROG */
234                 /*ns_ctx->fd = -1;*/
235         }
236
237         if (root_fd >= 0) {
238                 if (wd_fd < 0) {
239                         /*
240                          * Save the current working directory if we're not
241                          * changing it.
242                          */
243                         wd_fd = xopen(".", O_RDONLY);
244                 }
245                 xfchdir(root_fd);
246                 xchroot(".");
247                 close(root_fd);
248                 /*root_fd = -1;*/
249         }
250
251         if (wd_fd >= 0) {
252                 xfchdir(wd_fd);
253                 close(wd_fd);
254                 /*wd_fd = -1;*/
255         }
256
257         /*
258          * Entering the pid namespace implies forking unless it's been
259          * explicitly requested by the user not to.
260          */
261         if (!(opts & OPT_nofork) && (opts & OPT_pid)) {
262                 xvfork_parent_waits_and_exits();
263                 /* Child continues */
264         }
265
266         if (opts & OPT_setgid) {
267                 if (setgroups(0, NULL) < 0 && setgroups_failed)
268                         bb_perror_msg_and_die("setgroups");
269                 xsetgid(gid);
270         }
271         if (opts & OPT_setuid)
272                 xsetuid(uid);
273
274         exec_prog_or_SHELL(argv);
275 }