Update remaining menuconfig items with approximate applet sizes
[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 (8.6 kb)"
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 #ifndef CLONE_NEWUTS
64 # define CLONE_NEWUTS  0x04000000
65 #endif
66 #ifndef CLONE_NEWIPC
67 # define CLONE_NEWIPC  0x08000000
68 #endif
69 #ifndef CLONE_NEWUSER
70 # define CLONE_NEWUSER 0x10000000
71 #endif
72 #ifndef CLONE_NEWPID
73 # define CLONE_NEWPID  0x20000000
74 #endif
75 #ifndef CLONE_NEWNET
76 # define CLONE_NEWNET  0x40000000
77 #endif
78
79 #include "libbb.h"
80
81 struct namespace_descr {
82         int flag;               /* value passed to setns() */
83         char ns_nsfile8[8];     /* "ns/" + namespace file in process' procfs entry */
84 };
85
86 struct namespace_ctx {
87         char *path;             /* optional path to a custom ns file */
88         int fd;                 /* opened namespace file descriptor */
89 };
90
91 enum {
92         OPT_user        = 1 << 0,
93         OPT_ipc         = 1 << 1,
94         OPT_uts         = 1 << 2,
95         OPT_network     = 1 << 3,
96         OPT_pid         = 1 << 4,
97         OPT_mount       = 1 << 5,
98         OPT_target      = 1 << 6,
99         OPT_setuid      = 1 << 7,
100         OPT_setgid      = 1 << 8,
101         OPT_root        = 1 << 9,
102         OPT_wd          = 1 << 10,
103         OPT_nofork      = 1 << 11,
104         OPT_prescred    = (1 << 12) * ENABLE_FEATURE_NSENTER_LONG_OPTS,
105 };
106 enum {
107         NS_USR_POS = 0,
108         NS_IPC_POS,
109         NS_UTS_POS,
110         NS_NET_POS,
111         NS_PID_POS,
112         NS_MNT_POS,
113         NS_COUNT,
114 };
115 /*
116  * The order is significant in nsenter.
117  * The user namespace comes first, so that it is entered first.
118  * This gives an unprivileged user the potential to enter other namespaces.
119  */
120 static const struct namespace_descr ns_list[] = {
121         { CLONE_NEWUSER, "ns/user", },
122         { CLONE_NEWIPC,  "ns/ipc",  },
123         { CLONE_NEWUTS,  "ns/uts",  },
124         { CLONE_NEWNET,  "ns/net",  },
125         { CLONE_NEWPID,  "ns/pid",  },
126         { CLONE_NEWNS,   "ns/mnt",  },
127 };
128 /*
129  * Upstream nsenter doesn't support the short option for --preserve-credentials
130  */
131 static const char opt_str[] ALIGN1 = "U::i::u::n::p::m::""t+S+G+r::w::F";
132
133 #if ENABLE_FEATURE_NSENTER_LONG_OPTS
134 static const char nsenter_longopts[] ALIGN1 =
135         "user\0"                        Optional_argument       "U"
136         "ipc\0"                         Optional_argument       "i"
137         "uts\0"                         Optional_argument       "u"
138         "network\0"                     Optional_argument       "n"
139         "pid\0"                         Optional_argument       "p"
140         "mount\0"                       Optional_argument       "m"
141         "target\0"                      Required_argument       "t"
142         "setuid\0"                      Required_argument       "S"
143         "setgid\0"                      Required_argument       "G"
144         "root\0"                        Optional_argument       "r"
145         "wd\0"                          Optional_argument       "w"
146         "no-fork\0"                     No_argument             "F"
147         "preserve-credentials\0"        No_argument             "\xff"
148         ;
149 #endif
150
151 /*
152  * Open a file and return the new descriptor. If a full path is provided in
153  * fs_path, then the file to which it points is opened. Otherwise (fd_path is
154  * NULL) the routine builds a path to a procfs file using the following
155  * template: '/proc/<target_pid>/<target_file>'.
156  */
157 static int open_by_path_or_target(const char *path,
158                                   pid_t target_pid, const char *target_file)
159 {
160         char proc_path_buf[sizeof("/proc/%u/1234567890") + sizeof(int)*3];
161
162         if (!path) {
163                 if (target_pid == 0) {
164                         /* Example:
165                          * "nsenter -p PROG" - neither -pFILE nor -tPID given.
166                          */
167                         bb_show_usage();
168                 }
169                 snprintf(proc_path_buf, sizeof(proc_path_buf),
170                          "/proc/%u/%s", (unsigned)target_pid, target_file);
171                 path = proc_path_buf;
172         }
173
174         return xopen(path, O_RDONLY);
175 }
176
177 int nsenter_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
178 int nsenter_main(int argc UNUSED_PARAM, char **argv)
179 {
180         int i;
181         unsigned int opts;
182         const char *root_dir_str = NULL;
183         const char *wd_str = NULL;
184         struct namespace_ctx ns_ctx_list[NS_COUNT];
185         int setgroups_failed;
186         int root_fd, wd_fd;
187         int target_pid = 0;
188         int uid = 0;
189         int gid = 0;
190
191         memset(ns_ctx_list, 0, sizeof(ns_ctx_list));
192
193         IF_FEATURE_NSENTER_LONG_OPTS(applet_long_options = nsenter_longopts);
194         opts = getopt32(argv, opt_str,
195                         &ns_ctx_list[NS_USR_POS].path,
196                         &ns_ctx_list[NS_IPC_POS].path,
197                         &ns_ctx_list[NS_UTS_POS].path,
198                         &ns_ctx_list[NS_NET_POS].path,
199                         &ns_ctx_list[NS_PID_POS].path,
200                         &ns_ctx_list[NS_MNT_POS].path,
201                         &target_pid, &uid, &gid,
202                         &root_dir_str, &wd_str
203         );
204         argv += optind;
205
206         root_fd = wd_fd = -1;
207         if (opts & OPT_root)
208                 root_fd = open_by_path_or_target(root_dir_str,
209                                                  target_pid, "root");
210         if (opts & OPT_wd)
211                 wd_fd = open_by_path_or_target(wd_str, target_pid, "cwd");
212
213         for (i = 0; i < NS_COUNT; i++) {
214                 const struct namespace_descr *ns = &ns_list[i];
215                 struct namespace_ctx *ns_ctx = &ns_ctx_list[i];
216
217                 ns_ctx->fd = -1;
218                 if (opts & (1 << i))
219                         ns_ctx->fd = open_by_path_or_target(ns_ctx->path,
220                                         target_pid, ns->ns_nsfile8);
221         }
222
223         /*
224          * Entering the user namespace without --preserve-credentials implies
225          * --setuid & --setgid and clearing root's groups.
226          */
227         setgroups_failed = 0;
228         if ((opts & OPT_user) && !(opts & OPT_prescred)) {
229                 opts |= (OPT_setuid | OPT_setgid);
230                 /*
231                  * We call setgroups() before and after setns() and only
232                  * bail-out if it fails twice.
233                  */
234                 setgroups_failed = (setgroups(0, NULL) < 0);
235         }
236
237         for (i = 0; i < NS_COUNT; i++) {
238                 const struct namespace_descr *ns = &ns_list[i];
239                 struct namespace_ctx *ns_ctx = &ns_ctx_list[i];
240
241                 if (ns_ctx->fd < 0)
242                         continue;
243                 if (setns(ns_ctx->fd, ns->flag)) {
244                         bb_perror_msg_and_die(
245                                 "setns(): can't reassociate to namespace '%s'",
246                                 ns->ns_nsfile8 + 3 /* skip over "ns/" */
247                         );
248                 }
249                 close(ns_ctx->fd); /* should close fds, to not confuse exec'ed PROG */
250                 /*ns_ctx->fd = -1;*/
251         }
252
253         if (root_fd >= 0) {
254                 if (wd_fd < 0) {
255                         /*
256                          * Save the current working directory if we're not
257                          * changing it.
258                          */
259                         wd_fd = xopen(".", O_RDONLY);
260                 }
261                 xfchdir(root_fd);
262                 xchroot(".");
263                 close(root_fd);
264                 /*root_fd = -1;*/
265         }
266
267         if (wd_fd >= 0) {
268                 xfchdir(wd_fd);
269                 close(wd_fd);
270                 /*wd_fd = -1;*/
271         }
272
273         /*
274          * Entering the pid namespace implies forking unless it's been
275          * explicitly requested by the user not to.
276          */
277         if (!(opts & OPT_nofork) && (opts & OPT_pid)) {
278                 xvfork_parent_waits_and_exits();
279                 /* Child continues */
280         }
281
282         if (opts & OPT_setgid) {
283                 if (setgroups(0, NULL) < 0 && setgroups_failed)
284                         bb_perror_msg_and_die("setgroups");
285                 xsetgid(gid);
286         }
287         if (opts & OPT_setuid)
288                 xsetuid(uid);
289
290         exec_prog_or_SHELL(argv);
291 }