Fix: Handle hang-up gracefully in run-as
[lttng-tools.git] / src / common / runas.c
1 /*
2 * Copyright (C) 2011 - David Goulet <david.goulet@polymtl.ca>
3 * Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License, version 2 only,
7 * as published by the Free Software Foundation.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12 * more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 */
18
19 #define _GNU_SOURCE
20 #define _LGPL_SOURCE
21 #include <errno.h>
22 #include <limits.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <sys/wait.h>
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <unistd.h>
30 #include <fcntl.h>
31 #include <sched.h>
32 #include <sys/signal.h>
33
34 #include <common/common.h>
35 #include <common/utils.h>
36 #include <common/compat/getenv.h>
37 #include <common/sessiond-comm/unix.h>
38
39 #include "runas.h"
40
41 struct run_as_data;
42 typedef int (*run_as_fct)(struct run_as_data *data);
43
44 struct run_as_mkdir_data {
45 char path[PATH_MAX];
46 mode_t mode;
47 };
48
49 struct run_as_open_data {
50 char path[PATH_MAX];
51 int flags;
52 mode_t mode;
53 };
54
55 struct run_as_unlink_data {
56 char path[PATH_MAX];
57 };
58
59 struct run_as_rmdir_recursive_data {
60 char path[PATH_MAX];
61 };
62
63 enum run_as_cmd {
64 RUN_AS_MKDIR,
65 RUN_AS_OPEN,
66 RUN_AS_UNLINK,
67 RUN_AS_RMDIR_RECURSIVE,
68 RUN_AS_MKDIR_RECURSIVE,
69 };
70
71 struct run_as_data {
72 enum run_as_cmd cmd;
73 union {
74 struct run_as_mkdir_data mkdir;
75 struct run_as_open_data open;
76 struct run_as_unlink_data unlink;
77 struct run_as_rmdir_recursive_data rmdir_recursive;
78 } u;
79 uid_t uid;
80 gid_t gid;
81 };
82
83 struct run_as_ret {
84 int ret;
85 int _errno;
86 };
87
88 struct run_as_worker {
89 pid_t pid; /* Worker PID. */
90 int sockpair[2];
91 char *procname;
92 };
93
94 /* Single global worker per process (for now). */
95 static struct run_as_worker *global_worker;
96 /* Lock protecting the worker. */
97 static pthread_mutex_t worker_lock = PTHREAD_MUTEX_INITIALIZER;
98
99 #ifdef VALGRIND
100 static
101 int use_clone(void)
102 {
103 return 0;
104 }
105 #else
106 static
107 int use_clone(void)
108 {
109 return !lttng_secure_getenv("LTTNG_DEBUG_NOCLONE");
110 }
111 #endif
112
113 LTTNG_HIDDEN
114 int _utils_mkdir_recursive_unsafe(const char *path, mode_t mode);
115
116 /*
117 * Create recursively directory using the FULL path.
118 */
119 static
120 int _mkdir_recursive(struct run_as_data *data)
121 {
122 const char *path;
123 mode_t mode;
124
125 path = data->u.mkdir.path;
126 mode = data->u.mkdir.mode;
127
128 /* Safe to call as we have transitioned to the requested uid/gid. */
129 return _utils_mkdir_recursive_unsafe(path, mode);
130 }
131
132 static
133 int _mkdir(struct run_as_data *data)
134 {
135 return mkdir(data->u.mkdir.path, data->u.mkdir.mode);
136 }
137
138 static
139 int _open(struct run_as_data *data)
140 {
141 return open(data->u.open.path, data->u.open.flags, data->u.open.mode);
142 }
143
144 static
145 int _unlink(struct run_as_data *data)
146 {
147 return unlink(data->u.unlink.path);
148 }
149
150 static
151 int _rmdir_recursive(struct run_as_data *data)
152 {
153 return utils_recursive_rmdir(data->u.rmdir_recursive.path);
154 }
155
156 static
157 run_as_fct run_as_enum_to_fct(enum run_as_cmd cmd)
158 {
159 switch (cmd) {
160 case RUN_AS_MKDIR:
161 return _mkdir;
162 case RUN_AS_OPEN:
163 return _open;
164 case RUN_AS_UNLINK:
165 return _unlink;
166 case RUN_AS_RMDIR_RECURSIVE:
167 return _rmdir_recursive;
168 case RUN_AS_MKDIR_RECURSIVE:
169 return _mkdir_recursive;
170 default:
171 ERR("Unknown command %d", (int) cmd)
172 return NULL;
173 }
174 }
175
176 static
177 int do_send_fd(struct run_as_worker *worker,
178 enum run_as_cmd cmd, int fd)
179 {
180 ssize_t len;
181
182 switch (cmd) {
183 case RUN_AS_OPEN:
184 break;
185 default:
186 return 0;
187 }
188 if (fd < 0) {
189 return 0;
190 }
191 len = lttcomm_send_fds_unix_sock(worker->sockpair[1], &fd, 1);
192 if (len < 0) {
193 PERROR("lttcomm_send_fds_unix_sock");
194 return -1;
195 }
196 if (close(fd) < 0) {
197 PERROR("close");
198 return -1;
199 }
200 return 0;
201 }
202
203 static
204 int do_recv_fd(struct run_as_worker *worker,
205 enum run_as_cmd cmd, int *fd)
206 {
207 ssize_t len;
208
209 switch (cmd) {
210 case RUN_AS_OPEN:
211 break;
212 default:
213 return 0;
214 }
215 if (*fd < 0) {
216 return 0;
217 }
218 len = lttcomm_recv_fds_unix_sock(worker->sockpair[0], fd, 1);
219 if (!len) {
220 return -1;
221 } else if (len < 0) {
222 PERROR("lttcomm_recv_fds_unix_sock");
223 return -1;
224 }
225 return 0;
226 }
227
228 /*
229 * Return < 0 on error, 0 if OK, 1 on hangup.
230 */
231 static
232 int handle_one_cmd(struct run_as_worker *worker)
233 {
234 int ret = 0;
235 struct run_as_data data;
236 ssize_t readlen, writelen;
237 struct run_as_ret sendret;
238 run_as_fct cmd;
239 uid_t prev_euid;
240
241 /* Read data */
242 readlen = lttcomm_recv_unix_sock(worker->sockpair[1], &data,
243 sizeof(data));
244 if (readlen == 0) {
245 /* hang up */
246 ret = 1;
247 goto end;
248 }
249 if (readlen < sizeof(data)) {
250 PERROR("lttcomm_recv_unix_sock error");
251 ret = -1;
252 goto end;
253 }
254
255 cmd = run_as_enum_to_fct(data.cmd);
256 if (!cmd) {
257 ret = -1;
258 goto end;
259 }
260
261 prev_euid = getuid();
262 if (data.gid != getegid()) {
263 ret = setegid(data.gid);
264 if (ret < 0) {
265 PERROR("setegid");
266 goto write_return;
267 }
268 }
269 if (data.uid != prev_euid) {
270 ret = seteuid(data.uid);
271 if (ret < 0) {
272 PERROR("seteuid");
273 goto write_return;
274 }
275 }
276 /*
277 * Also set umask to 0 for mkdir executable bit.
278 */
279 umask(0);
280 ret = (*cmd)(&data);
281
282 write_return:
283 sendret.ret = ret;
284 sendret._errno = errno;
285 /* send back return value */
286 writelen = lttcomm_send_unix_sock(worker->sockpair[1], &sendret,
287 sizeof(sendret));
288 if (writelen < sizeof(sendret)) {
289 PERROR("lttcomm_send_unix_sock error");
290 ret = -1;
291 goto end;
292 }
293 ret = do_send_fd(worker, data.cmd, ret);
294 if (ret) {
295 PERROR("do_send_fd error");
296 ret = -1;
297 goto end;
298 }
299 if (seteuid(prev_euid) < 0) {
300 PERROR("seteuid");
301 ret = -1;
302 goto end;
303 }
304 ret = 0;
305 end:
306 return ret;
307 }
308
309 static
310 int run_as_worker(struct run_as_worker *worker)
311 {
312 int ret;
313 ssize_t writelen;
314 struct run_as_ret sendret;
315 size_t proc_orig_len;
316
317 /*
318 * Initialize worker. Set a different process cmdline.
319 */
320 proc_orig_len = strlen(worker->procname);
321 memset(worker->procname, 0, proc_orig_len);
322 strncpy(worker->procname, DEFAULT_RUN_AS_WORKER_NAME, proc_orig_len);
323
324 ret = pthread_setname_np(pthread_self(), DEFAULT_RUN_AS_WORKER_NAME);
325 if (ret) {
326 errno = ret;
327 ret = -1;
328 PERROR("pthread_setname_np");
329 return EXIT_FAILURE;
330 }
331
332 sendret.ret = 0;
333 sendret._errno = 0;
334 writelen = lttcomm_send_unix_sock(worker->sockpair[1], &sendret,
335 sizeof(sendret));
336 if (writelen < sizeof(sendret)) {
337 PERROR("lttcomm_send_unix_sock error");
338 ret = EXIT_FAILURE;
339 goto end;
340 }
341
342 for (;;) {
343 ret = handle_one_cmd(worker);
344 if (ret < 0) {
345 ret = EXIT_FAILURE;
346 goto end;
347 } else if (ret > 0) {
348 break;
349 } else {
350 continue; /* Next command. */
351 }
352 }
353 ret = EXIT_SUCCESS;
354 end:
355 return ret;
356 }
357
358 static
359 int run_as_cmd(struct run_as_worker *worker,
360 enum run_as_cmd cmd,
361 struct run_as_data *data,
362 uid_t uid, gid_t gid)
363 {
364 ssize_t readlen, writelen;
365 struct run_as_ret recvret;
366
367 pthread_mutex_lock(&worker_lock);
368 /*
369 * If we are non-root, we can only deal with our own uid.
370 */
371 if (geteuid() != 0) {
372 if (uid != geteuid()) {
373 recvret.ret = -1;
374 recvret._errno = EPERM;
375 ERR("Client (%d)/Server (%d) UID mismatch (and sessiond is not root)",
376 uid, geteuid());
377 goto end;
378 }
379 }
380
381 data->cmd = cmd;
382 data->uid = uid;
383 data->gid = gid;
384
385 writelen = lttcomm_send_unix_sock(worker->sockpair[0], data,
386 sizeof(*data));
387 if (writelen < sizeof(*data)) {
388 PERROR("Error writing message to run_as");
389 recvret.ret = -1;
390 recvret._errno = errno;
391 goto end;
392 }
393
394 /* receive return value */
395 readlen = lttcomm_recv_unix_sock(worker->sockpair[0], &recvret,
396 sizeof(recvret));
397 if (!readlen) {
398 ERR("Run-as worker has hung-up during run_as_cmd");
399 recvret.ret = -1;
400 recvret._errno = EIO;
401 goto end;
402 } else if (readlen < sizeof(recvret)) {
403 PERROR("Error reading response from run_as");
404 recvret.ret = -1;
405 recvret._errno = errno;
406 }
407 if (do_recv_fd(worker, cmd, &recvret.ret)) {
408 recvret.ret = -1;
409 recvret._errno = EIO;
410 }
411
412 end:
413 pthread_mutex_unlock(&worker_lock);
414 errno = recvret._errno;
415 return recvret.ret;
416 }
417
418 /*
419 * This is for debugging ONLY and should not be considered secure.
420 */
421 static
422 int run_as_noworker(enum run_as_cmd cmd,
423 struct run_as_data *data, uid_t uid, gid_t gid)
424 {
425 int ret, saved_errno;
426 mode_t old_mask;
427 run_as_fct fct;
428
429 fct = run_as_enum_to_fct(cmd);
430 if (!fct) {
431 errno = -ENOSYS;
432 ret = -1;
433 goto end;
434 }
435 old_mask = umask(0);
436 ret = fct(data);
437 saved_errno = errno;
438 umask(old_mask);
439 errno = saved_errno;
440 end:
441 return ret;
442 }
443
444 static
445 int run_as(struct run_as_worker *worker,
446 enum run_as_cmd cmd,
447 struct run_as_data *data, uid_t uid, gid_t gid)
448 {
449 int ret;
450
451 if (worker) {
452 DBG("Using run_as worker");
453 ret = run_as_cmd(worker, cmd, data, uid, gid);
454 } else {
455 DBG("Using run_as without worker");
456 ret = run_as_noworker(cmd, data, uid, gid);
457 }
458 return ret;
459 }
460
461 LTTNG_HIDDEN
462 int run_as_mkdir_recursive(const char *path, mode_t mode, uid_t uid, gid_t gid)
463 {
464 struct run_as_worker *worker = global_worker;
465 struct run_as_data data;
466
467 DBG3("mkdir() recursive %s with mode %d for uid %d and gid %d",
468 path, mode, uid, gid);
469 strncpy(data.u.mkdir.path, path, PATH_MAX - 1);
470 data.u.mkdir.path[PATH_MAX - 1] = '\0';
471 data.u.mkdir.mode = mode;
472 return run_as(worker, RUN_AS_MKDIR_RECURSIVE, &data, uid, gid);
473 }
474
475 LTTNG_HIDDEN
476 int run_as_mkdir(const char *path, mode_t mode, uid_t uid, gid_t gid)
477 {
478 struct run_as_worker *worker = global_worker;
479 struct run_as_data data;
480
481 DBG3("mkdir() %s with mode %d for uid %d and gid %d",
482 path, mode, uid, gid);
483 strncpy(data.u.mkdir.path, path, PATH_MAX - 1);
484 data.u.mkdir.path[PATH_MAX - 1] = '\0';
485 data.u.mkdir.mode = mode;
486 return run_as(worker, RUN_AS_MKDIR, &data, uid, gid);
487 }
488
489 /*
490 * Note: open_run_as is currently not working. We'd need to pass the fd
491 * opened in the child to the parent.
492 */
493 LTTNG_HIDDEN
494 int run_as_open(const char *path, int flags, mode_t mode, uid_t uid, gid_t gid)
495 {
496 struct run_as_worker *worker = global_worker;
497 struct run_as_data data;
498
499 DBG3("open() %s with flags %X mode %d for uid %d and gid %d",
500 path, flags, mode, uid, gid);
501 strncpy(data.u.open.path, path, PATH_MAX - 1);
502 data.u.open.path[PATH_MAX - 1] = '\0';
503 data.u.open.flags = flags;
504 data.u.open.mode = mode;
505 return run_as(worker, RUN_AS_OPEN, &data, uid, gid);
506 }
507
508 LTTNG_HIDDEN
509 int run_as_unlink(const char *path, uid_t uid, gid_t gid)
510 {
511 struct run_as_worker *worker = global_worker;
512 struct run_as_data data;
513
514 DBG3("unlink() %s with for uid %d and gid %d",
515 path, uid, gid);
516 strncpy(data.u.unlink.path, path, PATH_MAX - 1);
517 data.u.unlink.path[PATH_MAX - 1] = '\0';
518 return run_as(worker, RUN_AS_UNLINK, &data, uid, gid);
519 }
520
521 LTTNG_HIDDEN
522 int run_as_rmdir_recursive(const char *path, uid_t uid, gid_t gid)
523 {
524 struct run_as_worker *worker = global_worker;
525 struct run_as_data data;
526
527 DBG3("rmdir_recursive() %s with for uid %d and gid %d",
528 path, uid, gid);
529 strncpy(data.u.rmdir_recursive.path, path, PATH_MAX - 1);
530 data.u.rmdir_recursive.path[PATH_MAX - 1] = '\0';
531 return run_as(worker, RUN_AS_RMDIR_RECURSIVE, &data, uid, gid);
532 }
533
534 LTTNG_HIDDEN
535 int run_as_create_worker(char *procname)
536 {
537 pid_t pid;
538 int i, ret = 0;
539 ssize_t readlen;
540 struct run_as_ret recvret;
541 struct run_as_worker *worker;
542
543 if (!use_clone()) {
544 ret = 0;
545 goto end;
546 }
547 worker = zmalloc(sizeof(*worker));
548 if (!worker) {
549 ret = -ENOMEM;
550 goto end;
551 }
552 worker->procname = procname;
553 /* Create unix socket. */
554 if (lttcomm_create_anon_unix_socketpair(worker->sockpair) < 0) {
555 ret = -1;
556 goto error_sock;
557 }
558 /* Fork worker. */
559 pid = fork();
560 if (pid < 0) {
561 PERROR("fork");
562 ret = -1;
563 goto error_fork;
564 } else if (pid == 0) {
565 /* Child */
566
567 /* Just close, no shutdown. */
568 if (close(worker->sockpair[0])) {
569 PERROR("close");
570 exit(EXIT_FAILURE);
571 }
572 worker->sockpair[0] = -1;
573 ret = run_as_worker(worker);
574 if (lttcomm_close_unix_sock(worker->sockpair[1])) {
575 PERROR("close");
576 ret = -1;
577 }
578 worker->sockpair[1] = -1;
579 exit(ret ? EXIT_FAILURE : EXIT_SUCCESS);
580 } else {
581 /* Parent */
582
583 /* Just close, no shutdown. */
584 if (close(worker->sockpair[1])) {
585 PERROR("close");
586 ret = -1;
587 goto error_fork;
588 }
589 worker->sockpair[1] = -1;
590 worker->pid = pid;
591 /* Wait for worker to become ready. */
592 readlen = lttcomm_recv_unix_sock(worker->sockpair[0],
593 &recvret, sizeof(recvret));
594 if (readlen < sizeof(recvret)) {
595 ERR("readlen: %zd", readlen);
596 PERROR("Error reading response from run_as at creation");
597 ret = -1;
598 goto error_fork;
599 }
600 global_worker = worker;
601 }
602 end:
603 return ret;
604
605 /* Error handling. */
606 error_fork:
607 for (i = 0; i < 2; i++) {
608 if (worker->sockpair[i] < 0) {
609 continue;
610 }
611 if (lttcomm_close_unix_sock(worker->sockpair[i])) {
612 PERROR("close");
613 }
614 worker->sockpair[i] = -1;
615 }
616 error_sock:
617 free(worker);
618 return ret;
619 }
620
621 LTTNG_HIDDEN
622 void run_as_destroy_worker(void)
623 {
624 struct run_as_worker *worker = global_worker;
625 int status;
626 pid_t pid;
627
628 if (!worker) {
629 return;
630 }
631 /* Close unix socket */
632 if (lttcomm_close_unix_sock(worker->sockpair[0])) {
633 PERROR("close");
634 }
635 worker->sockpair[0] = -1;
636 /* Wait for worker. */
637 pid = waitpid(worker->pid, &status, 0);
638 if (pid < 0 || !WIFEXITED(status) || WEXITSTATUS(status) != 0) {
639 PERROR("wait");
640 }
641 free(worker);
642 global_worker = NULL;
643 }
This page took 0.04363 seconds and 6 git commands to generate.