]>
Commit | Line | Data |
---|---|---|
cc73685d | 1 | /* SPDX-License-Identifier: LGPL-2.1+ */ |
81c75799 | 2 | |
1160ce89 CB |
3 | #include "config.h" |
4 | ||
5eacdc3d CB |
5 | #include <errno.h> |
6 | #include <fcntl.h> | |
7 | #include <stdio.h> | |
3d5e9f48 | 8 | #include <stdlib.h> |
9267beba | 9 | #include <string.h> |
5eacdc3d | 10 | #include <sys/ioctl.h> |
ba2be1a8 | 11 | #include <sys/stat.h> |
5eacdc3d CB |
12 | #include <sys/types.h> |
13 | #include <sys/wait.h> | |
d38dd64a CB |
14 | #include <termios.h> |
15 | #include <unistd.h> | |
5eacdc3d | 16 | |
12ae2a33 | 17 | #include "lxc.h" |
7adff31c | 18 | |
81c75799 | 19 | #include "arguments.h" |
1b36d9e9 | 20 | #include "attach.h" |
21 | #include "caps.h" | |
22 | #include "confile.h" | |
23 | #include "log.h" | |
7b854e37 | 24 | #ifdef ENFORCE_MEMFD_REXEC |
d3a9befc | 25 | #include "rexec.h" |
7b854e37 | 26 | #endif |
1b36d9e9 | 27 | #include "utils.h" |
28 | ||
29 | lxc_log_define(lxc_attach, lxc); | |
6ff05e18 | 30 | |
d3a9befc CB |
31 | /** |
32 | * This function will copy any binary that calls liblxc into a memory file and | |
33 | * will use the memfd to rexecute the binary. This is done to prevent attacks | |
34 | * through the /proc/self/exe symlink to corrupt the host binary when host and | |
35 | * container are in the same user namespace or have set up an identity id | |
36 | * mapping: CVE-2019-5736. | |
37 | */ | |
38 | #ifdef ENFORCE_MEMFD_REXEC | |
7a80606d | 39 | __attribute__((constructor)) static void lxc_attach_rexec(void) |
d3a9befc | 40 | { |
7a80606d | 41 | if (!getenv("LXC_MEMFD_REXEC") && lxc_rexec("lxc-attach")) { |
d3a9befc CB |
42 | fprintf(stderr, "Failed to re-execute lxc-attach via memory file descriptor\n"); |
43 | _exit(EXIT_FAILURE); | |
44 | } | |
45 | } | |
46 | #endif | |
47 | ||
81b46065 | 48 | static int my_parser(struct lxc_arguments *args, int c, char *arg); |
49 | static int add_to_simple_array(char ***array, ssize_t *capacity, char *value); | |
50 | static bool stdfd_is_pty(void); | |
51 | static int lxc_attach_create_log_file(const char *log_file); | |
52 | ||
d253a09f | 53 | static unsigned int elevated_privileges; |
81b46065 | 54 | static signed long new_personality = -1; |
55 | static int namespace_flags = -1; | |
56 | static int remount_sys_proc; | |
57 | static lxc_attach_env_policy_t env_policy = LXC_ATTACH_KEEP_ENV; | |
58 | static char **extra_env; | |
59 | static ssize_t extra_env_size; | |
60 | static char **extra_keep; | |
61 | static ssize_t extra_keep_size; | |
8455e39e | 62 | static char *selinux_context = NULL; |
81b46065 | 63 | |
81c75799 | 64 | static const struct option my_longopts[] = { |
4d69b293 | 65 | {"elevated-privileges", optional_argument, 0, 'e'}, |
cb014488 | 66 | {"arch", required_argument, 0, 'a'}, |
e13eeea2 | 67 | {"namespaces", required_argument, 0, 's'}, |
7a0b0b56 | 68 | {"remount-sys-proc", no_argument, 0, 'R'}, |
799f96fd CS |
69 | /* TODO: decide upon short option names */ |
70 | {"clear-env", no_argument, 0, 500}, | |
71 | {"keep-env", no_argument, 0, 501}, | |
3d5e9f48 CS |
72 | {"keep-var", required_argument, 0, 502}, |
73 | {"set-var", required_argument, 0, 'v'}, | |
da41561c | 74 | {"pty-log", required_argument, 0, 'L'}, |
a7ae6ce4 | 75 | {"rcfile", required_argument, 0, 'f'}, |
ddd51fdb CB |
76 | {"uid", required_argument, 0, 'u'}, |
77 | {"gid", required_argument, 0, 'g'}, | |
8455e39e | 78 | {"context", required_argument, 0, 'c'}, |
81c75799 DL |
79 | LXC_COMMON_OPTIONS |
80 | }; | |
81 | ||
81b46065 | 82 | static struct lxc_arguments my_args = { |
83 | .progname = "lxc-attach", | |
84 | .help = "\ | |
85 | --name=NAME [-- COMMAND]\n\ | |
86 | \n\ | |
87 | Execute the specified COMMAND - enter the container NAME\n\ | |
88 | \n\ | |
89 | Options :\n\ | |
90 | -n, --name=NAME NAME of the container\n\ | |
91 | -e, --elevated-privileges=PRIVILEGES\n\ | |
92 | Use elevated privileges instead of those of the\n\ | |
93 | container. If you don't specify privileges to be\n\ | |
94 | elevated as OR'd list: CAP, CGROUP and LSM (capabilities,\n\ | |
95 | cgroup and restrictions, respectively) then all of them\n\ | |
96 | will be elevated.\n\ | |
97 | WARNING: This may leak privileges into the container.\n\ | |
98 | Use with care.\n\ | |
99 | -a, --arch=ARCH Use ARCH for program instead of container's own\n\ | |
100 | architecture.\n\ | |
101 | -s, --namespaces=FLAGS\n\ | |
102 | Don't attach to all the namespaces of the container\n\ | |
103 | but just to the following OR'd list of flags:\n\ | |
104 | MOUNT, PID, UTSNAME, IPC, USER or NETWORK.\n\ | |
105 | WARNING: Using -s implies -e with all privileges\n\ | |
106 | elevated, it may therefore leak privileges into the\n\ | |
107 | container. Use with care.\n\ | |
108 | -R, --remount-sys-proc\n\ | |
109 | Remount /sys and /proc if not attaching to the\n\ | |
110 | mount namespace when using -s in order to properly\n\ | |
111 | reflect the correct namespace context. See the\n\ | |
112 | lxc-attach(1) manual page for details.\n\ | |
113 | --clear-env Clear all environment variables before attaching.\n\ | |
114 | The attached shell/program will start with only\n\ | |
115 | container=lxc set.\n\ | |
116 | --keep-env Keep all current environment variables. This\n\ | |
117 | is the current default behaviour, but is likely to\n\ | |
118 | change in the future.\n\ | |
119 | -L, --pty-log=FILE\n\ | |
120 | Log pty output to FILE\n\ | |
121 | -v, --set-var Set an additional variable that is seen by the\n\ | |
122 | attached program in the container. May be specified\n\ | |
123 | multiple times.\n\ | |
124 | --keep-var Keep an additional environment variable. Only\n\ | |
125 | applicable if --clear-env is specified. May be used\n\ | |
126 | multiple times.\n\ | |
127 | -f, --rcfile=FILE\n\ | |
128 | Load configuration file FILE\n\ | |
ddd51fdb CB |
129 | -u, --uid=UID Execute COMMAND with UID inside the container\n\ |
130 | -g, --gid=GID Execute COMMAND with GID inside the container\n\ | |
8455e39e MB |
131 | -c, --context=context\n\ |
132 | SELinux Context to transition into\n\ | |
81b46065 | 133 | ", |
134 | .options = my_longopts, | |
135 | .parser = my_parser, | |
136 | .checker = NULL, | |
137 | .log_priority = "ERROR", | |
138 | .log_file = "none", | |
cc94aaf3 CB |
139 | .uid = LXC_INVALID_UID, |
140 | .gid = LXC_INVALID_GID, | |
81b46065 | 141 | }; |
cb014488 | 142 | |
a13daf8e | 143 | static int my_parser(struct lxc_arguments *args, int c, char *arg) |
cb014488 | 144 | { |
e13eeea2 CS |
145 | int ret; |
146 | ||
cb014488 | 147 | switch (c) { |
4d69b293 NK |
148 | case 'e': |
149 | ret = lxc_fill_elevated_privileges(arg, &elevated_privileges); | |
150 | if (ret) | |
151 | return -1; | |
152 | break; | |
7a0b0b56 | 153 | case 'R': remount_sys_proc = 1; break; |
cb014488 | 154 | case 'a': |
7c43fa56 CB |
155 | ret = lxc_config_parse_arch(arg, &new_personality); |
156 | if (ret < 0) { | |
1b36d9e9 | 157 | ERROR("Invalid architecture specified: %s", arg); |
cb014488 CS |
158 | return -1; |
159 | } | |
160 | break; | |
e13eeea2 CS |
161 | case 's': |
162 | namespace_flags = 0; | |
9420e0c2 | 163 | |
2d33090f | 164 | if (lxc_namespace_2_std_identifiers(arg) < 0) |
165 | return -1; | |
9420e0c2 | 166 | |
e13eeea2 CS |
167 | ret = lxc_fill_namespace_flags(arg, &namespace_flags); |
168 | if (ret) | |
169 | return -1; | |
2d33090f | 170 | |
e13eeea2 | 171 | /* -s implies -e */ |
4d69b293 | 172 | lxc_fill_elevated_privileges(NULL, &elevated_privileges); |
e13eeea2 | 173 | break; |
d028235d SG |
174 | case 500: /* clear-env */ |
175 | env_policy = LXC_ATTACH_CLEAR_ENV; | |
176 | break; | |
177 | case 501: /* keep-env */ | |
178 | env_policy = LXC_ATTACH_KEEP_ENV; | |
179 | break; | |
3d5e9f48 CS |
180 | case 502: /* keep-var */ |
181 | ret = add_to_simple_array(&extra_keep, &extra_keep_size, arg); | |
182 | if (ret < 0) { | |
1b36d9e9 | 183 | ERROR("Failed to alloc memory"); |
3d5e9f48 CS |
184 | return -1; |
185 | } | |
186 | break; | |
187 | case 'v': | |
188 | ret = add_to_simple_array(&extra_env, &extra_env_size, arg); | |
189 | if (ret < 0) { | |
1b36d9e9 | 190 | ERROR("Failed to alloc memory"); |
3d5e9f48 CS |
191 | return -1; |
192 | } | |
193 | break; | |
da41561c CB |
194 | case 'L': |
195 | args->console_log = arg; | |
196 | break; | |
2c34c8f2 CB |
197 | case 'f': |
198 | args->rcfile = arg; | |
199 | break; | |
ddd51fdb CB |
200 | case 'u': |
201 | if (lxc_safe_uint(arg, &args->uid) < 0) | |
202 | return -1; | |
203 | break; | |
204 | case 'g': | |
205 | if (lxc_safe_uint(arg, &args->gid) < 0) | |
206 | return -1; | |
207 | break; | |
8455e39e MB |
208 | case 'c': |
209 | selinux_context = arg; | |
210 | break; | |
cb014488 CS |
211 | } |
212 | ||
213 | return 0; | |
214 | } | |
215 | ||
81b46065 | 216 | static int add_to_simple_array(char ***array, ssize_t *capacity, char *value) |
217 | { | |
218 | ssize_t count = 0; | |
219 | ||
220 | if (!array) | |
221 | return -1; | |
222 | ||
223 | if (*array) | |
224 | for (; (*array)[count]; count++); | |
225 | ||
226 | /* we have to reallocate */ | |
227 | if (count >= *capacity - 1) { | |
228 | ssize_t new_capacity = ((count + 1) / 32 + 1) * 32; | |
229 | ||
230 | char **new_array = realloc((void*)*array, sizeof(char *) * new_capacity); | |
231 | if (!new_array) | |
232 | return -1; | |
233 | ||
234 | memset(&new_array[count], 0, sizeof(char*)*(new_capacity - count)); | |
235 | ||
236 | *array = new_array; | |
237 | *capacity = new_capacity; | |
238 | } | |
239 | ||
240 | if (!(*array)) | |
241 | return -1; | |
242 | ||
243 | (*array)[count] = value; | |
244 | return 0; | |
245 | } | |
81c75799 | 246 | |
ba2be1a8 | 247 | static bool stdfd_is_pty(void) |
5eacdc3d | 248 | { |
ba2be1a8 CB |
249 | if (isatty(STDIN_FILENO)) |
250 | return true; | |
81b46065 | 251 | |
ba2be1a8 CB |
252 | if (isatty(STDOUT_FILENO)) |
253 | return true; | |
81b46065 | 254 | |
ba2be1a8 CB |
255 | if (isatty(STDERR_FILENO)) |
256 | return true; | |
5eacdc3d | 257 | |
ba2be1a8 | 258 | return false; |
5eacdc3d CB |
259 | } |
260 | ||
a13daf8e | 261 | static int lxc_attach_create_log_file(const char *log_file) |
5eacdc3d | 262 | { |
ba2be1a8 | 263 | int fd; |
5e5129d7 | 264 | |
ba2be1a8 CB |
265 | fd = open(log_file, O_CLOEXEC | O_RDWR | O_CREAT | O_APPEND, 0600); |
266 | if (fd < 0) { | |
1b36d9e9 | 267 | ERROR("Failed to open log file \"%s\"", log_file); |
5eacdc3d | 268 | return -1; |
5eacdc3d CB |
269 | } |
270 | ||
ba2be1a8 | 271 | return fd; |
c87524b7 CB |
272 | } |
273 | ||
5eacdc3d CB |
274 | int main(int argc, char *argv[]) |
275 | { | |
81b46065 | 276 | int ret = -1; |
478dda76 | 277 | int wexit = 0; |
9c4693b8 | 278 | lxc_attach_options_t attach_options = LXC_ATTACH_OPTIONS_DEFAULT; |
5eacdc3d | 279 | lxc_attach_command_t command = (lxc_attach_command_t){.program = NULL}; |
647df91d CB |
280 | pid_t pid; |
281 | struct lxc_container *c; | |
282 | struct lxc_log log; | |
81c75799 | 283 | |
81b46065 | 284 | if (lxc_caps_init()) |
5eacdc3d | 285 | exit(EXIT_FAILURE); |
c7029344 | 286 | |
81b46065 | 287 | if (lxc_arguments_parse(&my_args, argc, argv)) |
5eacdc3d | 288 | exit(EXIT_FAILURE); |
81c75799 | 289 | |
7cde4e41 CB |
290 | log.name = my_args.name; |
291 | log.file = my_args.log_file; | |
292 | log.level = my_args.log_priority; | |
293 | log.prefix = my_args.progname; | |
294 | log.quiet = my_args.quiet; | |
295 | log.lxcpath = my_args.lxcpath[0]; | |
f6d79ec1 | 296 | |
81b46065 | 297 | if (lxc_log_init(&log)) |
298 | exit(EXIT_FAILURE); | |
81c75799 | 299 | |
81b46065 | 300 | if (geteuid()) |
37180208 | 301 | if (access(my_args.lxcpath[0], O_RDONLY) < 0) { |
1b36d9e9 | 302 | ERROR("You lack access to %s", my_args.lxcpath[0]); |
9bd91876 | 303 | exit(EXIT_FAILURE); |
5eacdc3d | 304 | } |
5eacdc3d | 305 | |
647df91d | 306 | c = lxc_container_new(my_args.name, my_args.lxcpath[0]); |
5eacdc3d CB |
307 | if (!c) |
308 | exit(EXIT_FAILURE); | |
309 | ||
a7ae6ce4 WB |
310 | if (my_args.rcfile) { |
311 | c->clear_config(c); | |
312 | if (!c->load_config(c, my_args.rcfile)) { | |
1b36d9e9 | 313 | ERROR("Failed to load rcfile"); |
a7ae6ce4 WB |
314 | lxc_container_put(c); |
315 | exit(EXIT_FAILURE); | |
316 | } | |
a13daf8e | 317 | |
6118210e WB |
318 | c->configfile = strdup(my_args.rcfile); |
319 | if (!c->configfile) { | |
1b36d9e9 | 320 | ERROR("Out of memory setting new config filename"); |
6118210e WB |
321 | lxc_container_put(c); |
322 | exit(EXIT_FAILURE); | |
323 | } | |
a7ae6ce4 WB |
324 | } |
325 | ||
5eacdc3d | 326 | if (!c->may_control(c)) { |
1b36d9e9 | 327 | ERROR("Insufficent privileges to control %s", c->name); |
5eacdc3d CB |
328 | lxc_container_put(c); |
329 | exit(EXIT_FAILURE); | |
330 | } | |
331 | ||
9c4693b8 CS |
332 | if (remount_sys_proc) |
333 | attach_options.attach_flags |= LXC_ATTACH_REMOUNT_PROC_SYS; | |
a13daf8e | 334 | |
8c5c30d1 MB |
335 | if (elevated_privileges) { |
336 | if ((elevated_privileges & LXC_ATTACH_LSM_EXEC)) { | |
337 | if (selinux_context) { | |
338 | ERROR("Cannot combine elevated LSM privileges while requesting LSM profile"); | |
339 | goto out; | |
340 | } | |
341 | ||
342 | /* | |
343 | * While most LSM flags are off by default let's still | |
344 | * make sure they are stripped when elevated LSM | |
345 | * privileges are requested. | |
346 | */ | |
347 | elevated_privileges |= LXC_ATTACH_LSM; | |
348 | } | |
349 | ||
4d69b293 | 350 | attach_options.attach_flags &= ~(elevated_privileges); |
8c5c30d1 | 351 | } |
a13daf8e | 352 | |
ba2be1a8 | 353 | if (stdfd_is_pty()) |
9e84479f | 354 | attach_options.attach_flags |= LXC_ATTACH_TERMINAL; |
a13daf8e | 355 | |
7cde4e41 CB |
356 | attach_options.namespaces = namespace_flags; |
357 | attach_options.personality = new_personality; | |
358 | attach_options.env_policy = env_policy; | |
359 | attach_options.extra_env_vars = extra_env; | |
360 | attach_options.extra_keep_env = extra_keep; | |
81c75799 | 361 | |
5eacdc3d | 362 | if (my_args.argc > 0) { |
a9d02bb9 | 363 | command.program = my_args.argv[0]; |
7cde4e41 | 364 | command.argv = (char**)my_args.argv; |
5eacdc3d CB |
365 | } |
366 | ||
ba2be1a8 CB |
367 | if (my_args.console_log) { |
368 | attach_options.log_fd = lxc_attach_create_log_file(my_args.console_log); | |
369 | if (attach_options.log_fd < 0) | |
c87524b7 | 370 | goto out; |
c8f7c563 CS |
371 | } |
372 | ||
cc94aaf3 | 373 | if (my_args.uid != LXC_INVALID_UID) |
ddd51fdb CB |
374 | attach_options.uid = my_args.uid; |
375 | ||
cc94aaf3 | 376 | if (my_args.gid != LXC_INVALID_GID) |
ddd51fdb CB |
377 | attach_options.gid = my_args.gid; |
378 | ||
8455e39e | 379 | // selinux_context will be NULL if not set |
9becf309 MB |
380 | if (selinux_context) { |
381 | attach_options.attach_flags |= LXC_ATTACH_LSM_LABEL; | |
382 | attach_options.lsm_label = selinux_context; | |
383 | } | |
8455e39e | 384 | |
8cddb10f CB |
385 | if (command.program) { |
386 | ret = c->attach_run_wait(c, &attach_options, command.program, | |
387 | (const char **)command.argv); | |
388 | if (ret < 0) | |
389 | goto out; | |
390 | } else { | |
ba2be1a8 | 391 | ret = c->attach(c, lxc_attach_run_shell, NULL, &attach_options, &pid); |
8cddb10f CB |
392 | if (ret < 0) |
393 | goto out; | |
2b30b861 | 394 | |
8cddb10f CB |
395 | ret = lxc_wait_for_pid_status(pid); |
396 | if (ret < 0) | |
397 | goto out; | |
8cddb10f | 398 | } |
576b950f AT |
399 | if (WIFEXITED(ret)) |
400 | wexit = WEXITSTATUS(ret); | |
1b36d9e9 | 401 | |
478dda76 CB |
402 | out: |
403 | lxc_container_put(c); | |
404 | if (ret >= 0) | |
405 | exit(wexit); | |
a13daf8e | 406 | |
5eacdc3d | 407 | exit(EXIT_FAILURE); |
81c75799 | 408 | } |