]> git.proxmox.com Git - mirror_lxcfs.git/blame - pam/pam_cgfs.c
pid_from_ns_wrapper: remove the loop
[mirror_lxcfs.git] / pam / pam_cgfs.c
CommitLineData
df54106a
SH
1/* pam-cgfs
2 *
3 * Copyright © 2016 Canonical, Inc
4 * Author: Serge Hallyn <serge.hallyn@ubuntu.com>
5 *
edd25678
SH
6 * When a user logs in, this pam module will create cgroups which the user
7 * may administer, either for all controllers or for any controllers listed
8 * on the command line (if any are listed).
df54106a
SH
9 *
10 * The cgroup created will be "user/$user/0" for the first session,
11 * "user/$user/1" for the second, etc.
12 *
edd25678
SH
13 * name=systemd is handled specially. If the host is an upstart system,
14 * the logged in user may not get a cgroup created. On a systemd system,
15 * one is created but not chowned to the user. In the former case, we
16 * create one as usual, in the latter case we simply chown whatever cgroup
17 * the user is in.
18 *
df54106a
SH
19 * All requested cgroups must be mounted under /sys/fs/cgroup/$controller,
20 * no messing around with finding mountpoints.
21 *
22 * See COPYING file for details.
23 */
24
25#include <stdio.h>
26#include <stdlib.h>
27#include <unistd.h>
28#include <syslog.h>
29#include <stdarg.h>
30#include <errno.h>
31#include <sys/mount.h>
32#include <sys/types.h>
33#include <sys/stat.h>
34#include <sys/param.h>
35#include <pwd.h>
36#include <stdbool.h>
37#include <dirent.h>
38
39#define PAM_SM_SESSION
40#include <security/_pam_macros.h>
41#include <security/pam_modules.h>
42
43#include <linux/unistd.h>
44
45static bool initialized;
46
47static void mysyslog(int err, const char *format, ...)
48{
49 va_list args;
50
51 va_start(args, format);
52 openlog("PAM-CGFS", LOG_CONS|LOG_PID, LOG_AUTH);
53 vsyslog(err, format, args);
54 va_end(args);
55 closelog();
56}
57
58static char *must_strcat(const char *first, ...) __attribute__((sentinel));
59
60static char *must_strcat(const char *first, ...)
61{
62 va_list args;
63 char *dest, *cur, *new;
64 size_t len;
65
66 do {
67 dest = strdup(first);
68 } while (!dest);
69 len = strlen(dest);
70
71 va_start(args, first);
72
73 while ((cur = va_arg(args, char *)) != NULL) {
74 size_t newlen = len + strlen(cur);
75 do {
76 new = realloc(dest, newlen + 1);
77 } while (!new);
78 dest = new;
79 strcat(dest, cur);
80 len = newlen;
81 }
82 va_end(args);
83
84 return dest;
85}
86
87static bool exists(const char *path)
88{
89 struct stat sb;
90 int ret;
91
92 ret = stat(path, &sb);
93 return ret == 0;
94}
95
96static bool is_dir(const char *path)
97{
98 struct stat sb;
99
100 if (stat(path, &sb) < 0)
101 return false;
102 if (S_ISDIR(sb.st_mode))
103 return true;
104 return false;
105}
106
107static bool mkdir_p(const char *root, char *path)
108{
109 char *b, orig, *e;
110
111 if (strlen(path) < strlen(root))
112 return false;
113 if (strlen(path) == strlen(root))
114 return true;
115
116 b = path + strlen(root) + 1;
117 while (1) {
118 while (*b && *b == '/')
119 b++;
120 if (!*b)
121 return true;
122 e = b + 1;
123 while (*e && *e != '/')
124 e++;
125 orig = *e;
126 if (orig)
127 *e = '\0';
128 if (exists(path))
129 goto next;
130 if (mkdir(path, 0755) < 0) {
131#if DEBUG
132 fprintf(stderr, "Failed to create %s: %m\n", path);
133#endif
134 return false;
135 }
136next:
137 if (!orig)
138 return true;
139 *e = orig;
140 b = e + 1;
141 }
142
143}
144
145struct controller {
146 struct controller *next;
147 int id;
edd25678 148 bool systemd_created;
df54106a
SH
149 char *name;
150 char *mount_path;
151 char *init_path;
edd25678 152 char *cur_path;
df54106a
SH
153};
154
155#define MAXCONTROLLERS 20
156static struct controller *controllers[MAXCONTROLLERS];
157
2be80971
SH
158/*
159 * if cpu and cpuacct are comounted, it's possible a mount
160 * exists for only one. Find it.
161 */
162static char *find_controller_path(struct controller *c)
163{
164 while (c) {
165 char *path = must_strcat("/sys/fs/cgroup/", c->name, NULL);
166 if (exists(path))
167 return path;
168 free(path);
edd25678
SH
169 if (strncmp(c->name, "name=", 5) == 0) {
170 path = must_strcat("/sys/fs/cgroup/", c->name + 5, NULL);
171 if (exists(path))
172 return path;
173 free(path);
174 }
2be80971
SH
175 c = c->next;
176 }
177 return NULL;
178}
179
df54106a
SH
180/* Find the path at which each controller is mounted. */
181static void get_mounted_paths(void)
182{
183 int i;
184 struct controller *c;
185 char *path;
186
187 for (i = 0; i < MAXCONTROLLERS; i++) {
188 c = controllers[i];
189 if (!c || c->mount_path)
190 continue;
2be80971
SH
191 path = find_controller_path(c);
192 if (!path)
df54106a 193 continue;
df54106a
SH
194 while (c) {
195 c->mount_path = path;
196 c = c->next;
197 }
198 }
199}
200
edd25678 201static void add_controller(int id, char *tok, char *cur_path)
df54106a
SH
202{
203 struct controller *c;
204
215cfad6
SH
205 do {
206 c = malloc(sizeof(struct controller));
207 } while (!c);
208 do {
209 c->name = strdup(tok);
210 } while (!c->name);
edd25678
SH
211 do {
212 c->cur_path = strdup(cur_path);
213 } while (!c->cur_path);
df54106a 214 c->id = id;
df54106a
SH
215 c->next = controllers[id];
216 c->mount_path = NULL;
217 c->init_path = NULL;
218 controllers[id] = c;
df54106a
SH
219}
220
221static void drop_controller(int which)
222{
223 struct controller *c = controllers[which];
224
225 if (c) {
226 free(c->init_path); // all comounts share this
227 free(c->mount_path);
228 }
229 while (c) {
230 struct controller *tmp = c->next;
231 free(c->name);
edd25678 232 free(c->cur_path);
df54106a
SH
233 free(c);
234 c = tmp;
235 }
236 controllers[which] = NULL;
237}
238
239static bool single_in_filter(char *c, const char *filter)
240{
241 char *dup = strdupa(filter), *tok;
242 for (tok = strtok(dup, ","); tok; tok = strtok(NULL, ",")) {
243 if (strcmp(c, tok) == 0)
244 return true;
245 }
246 return false;
247}
248
249static bool controller_in_filter(struct controller *controller, const char *filter)
250{
251 struct controller *c;
252
253 for (c = controller; c; c = c->next) {
254 if (single_in_filter(c->name, filter))
255 return true;
256 }
257 return false;
258}
259
260/*
261 * Passed a comma-delimited list of requested controllers.
262 * Pulls any controllers not in the list out of the
263 * list of controllers
264 */
265static void filter_controllers(const char *filter)
266{
267 int i;
268 for (i = 0; i < MAXCONTROLLERS; i++) {
269 if (!controllers[i])
270 continue;
271 if (filter && !controller_in_filter(controllers[i], filter))
272 drop_controller(i);
273 }
274}
275
276#define INIT_SCOPE "/init.scope"
277static void prune_init_scope(char *cg)
278{
279 char *point;
826297d7 280 size_t cg_len, initscope_len;
df54106a
SH
281
282 if (!cg)
283 return;
284
826297d7
SF
285 cg_len = strlen(cg);
286 initscope_len = strlen(INIT_SCOPE);
287 if (cg_len < initscope_len)
df54106a 288 return;
826297d7
SF
289
290 point = cg + cg_len - initscope_len;
df54106a
SH
291 if (strcmp(point, INIT_SCOPE) == 0) {
292 if (point == cg)
293 *(point+1) = '\0';
294 else
295 *point = '\0';
296 }
297}
298
299static bool fill_in_init_paths(void)
300{
301 FILE *f;
302 char *line = NULL;
303 size_t len = 0;
304 struct controller *c;
c65c5956 305 bool ret = false;
df54106a
SH
306
307 f = fopen("/proc/1/cgroup", "r");
308 if (!f)
309 return false;
310 while (getline(&line, &len, f) != -1) {
311 int id;
312 char *subsystems, *ip;
313 if (sscanf(line, "%d:%m[^:]:%ms", &id, &subsystems, &ip) != 3) {
314 mysyslog(LOG_ERR, "Corrupt /proc/1/cgroup\n");
c65c5956 315 goto out;
df54106a
SH
316 }
317 free(subsystems);
318 if (id < 0 || id > 20) {
319 mysyslog(LOG_ERR, "Too many subsystems\n");
320 free(ip);
c65c5956 321 goto out;
df54106a
SH
322 }
323 if (ip[0] != '/') {
324 free(ip);
325 mysyslog(LOG_ERR, "ERROR: init cgroup path is not absolute!\n");
c65c5956 326 goto out;
df54106a
SH
327 }
328 prune_init_scope(ip);
edd25678
SH
329 for (c = controllers[id]; c; c = c->next) {
330 if (strcmp(c->name, "name=systemd") == 0)
331 c->systemd_created = strcmp(ip, c->cur_path) != 0;
df54106a 332 c->init_path = ip;
edd25678 333 }
df54106a 334 }
c65c5956
SH
335 ret = true;
336out:
df54106a 337 fclose(f);
c65c5956
SH
338 free(line);
339 return ret;
df54106a
SH
340}
341
342#if DEBUG
343static void print_found_controllers(void) {
344 struct controller *c;
345 int i;
346
347 for (i = 0; i < MAXCONTROLLERS; i++) {
348 c = controllers[i];
349 if (!c) {
350 fprintf(stderr, "Nothing in controller %d\n", i);
351 continue;
352 }
353 fprintf(stderr, "Controller %d:\n", i);
354 while (c) {
355 fprintf(stderr, " Next mount: index %d name %s\n", c->id, c->name);
356 fprintf(stderr, " mount path %s\n", c->mount_path ? c->mount_path : "(none)");
357 fprintf(stderr, " init task path %s\n", c->init_path);
edd25678 358 fprintf(stderr, " login task path %s\n", c->cur_path);
df54106a
SH
359 c = c->next;
360 }
361 }
362}
363#else
364static inline void print_found_controllers(void) { };
365#endif
366/*
367 * Get the list of cgroup controllers currently mounted.
368 * This includes both kernel and named subsystems, so get the list from
369 * /proc/self/cgroup rather than /proc/cgroups.
370 */
371static bool get_active_controllers(void)
372{
373 FILE *f;
edd25678 374 char *line = NULL, *tok, *cur_path;
df54106a
SH
375 size_t len = 0;
376
377 f = fopen("/proc/self/cgroup", "r");
378 if (!f)
379 return false;
380 while (getline(&line, &len, f) != -1) {
381 int id;
382 char *subsystems;
edd25678 383 if (sscanf(line, "%d:%m[^:]:%ms", &id, &subsystems, &cur_path) != 3) {
df54106a
SH
384 mysyslog(LOG_ERR, "Corrupt /proc/self/cgroup\n");
385 fclose(f);
c65c5956 386 free(line);
df54106a
SH
387 return false;
388 }
389 if (id < 0 || id > 20) {
390 mysyslog(LOG_ERR, "Too many subsystems\n");
391 free(subsystems);
edd25678 392 free(cur_path);
df54106a 393 fclose(f);
c65c5956 394 free(line);
df54106a
SH
395 return false;
396 }
df54106a 397 for (tok = strtok(subsystems, ","); tok; tok = strtok(NULL, ","))
edd25678 398 add_controller(id, tok, cur_path);
df54106a 399 free(subsystems);
edd25678 400 free(cur_path);
df54106a
SH
401 }
402 fclose(f);
c65c5956 403 free(line);
df54106a
SH
404
405 get_mounted_paths();
406
407 if (!fill_in_init_paths()) {
408 mysyslog(LOG_ERR, "Failed finding cgroups for init task\n");
409 return false;
410 }
411
412 print_found_controllers();
413
414 initialized = true;
415
416 return true;
417}
418
edd25678
SH
419/*
420 * Handle systemd creation. Return true if all's done. Returns false if
421 * the caller needs to create=chown a cgroup
422 */
423static bool handle_systemd_create(const struct controller *c, uid_t uid, gid_t gid)
424{
425 char *user_path;
426
427 if (!c->systemd_created)
428 return false;
429
430 user_path = must_strcat(c->mount_path, c->cur_path, NULL);
431
432 // a name=systemd cgroup has already been created, just chown it
433 if (chown(user_path, uid, gid) < 0)
434 mysyslog(LOG_WARNING, "Failed to chown %s to %d:%d: %m\n",
435 user_path, (int)uid, (int)gid);
436 free(user_path);
437 return true;
438}
439
79ab1116 440static bool cgfs_create_forone(const struct controller *c, uid_t uid, gid_t gid, const char *cg, bool *existed)
df54106a
SH
441{
442 while (c) {
443 if (!c->mount_path || !c->init_path)
444 goto next;
edd25678
SH
445
446 if (strcmp(c->name, "name=systemd") == 0 && handle_systemd_create(c, uid, gid))
447 return true;
448
df54106a
SH
449 char *path = must_strcat(c->mount_path, c->init_path, cg, NULL);
450#if DEBUG
451 fprintf(stderr, "Creating %s for %s\n", path, c->name);
452#endif
453 if (exists(path)) {
454 free(path);
455 *existed = true;
456#if DEBUG
edd25678 457 fprintf(stderr, "%s existed\n", path);
df54106a
SH
458#endif
459 return true;
460 }
edd25678 461
df54106a
SH
462 bool pass = mkdir_p(c->mount_path, path);
463#if DEBUG
464 fprintf(stderr, "Creating %s %s\n", path, pass ? "succeeded" : "failed");
465#endif
79ab1116
SH
466 if (pass) {
467 if (chown(path, uid, gid) < 0)
468 mysyslog(LOG_WARNING, "Failed to chown %s to %d:%d: %m\n",
469 path, (int)uid, (int)gid);
470 }
df54106a
SH
471 free(path);
472 if (pass)
473 return true;
474next:
475 c = c->next;
476 }
477 return false;
478}
479
480static void recursive_rmdir(const char *path)
481{
482 struct dirent *direntp;
483 DIR *dir;
484
485 dir = opendir(path);
486 if (!dir)
487 return;
488 while ((direntp = readdir(dir))!= NULL) {
489 if (!strcmp(direntp->d_name, ".") ||
490 !strcmp(direntp->d_name, ".."))
491 continue;
492
493 char *dpath = must_strcat(path, "/", direntp->d_name, NULL);
494 if (is_dir(dpath)) {
495 recursive_rmdir(dpath);
496#if DEBUG
497 fprintf(stderr, "attempting to remove %s\n", dpath);
498#endif
499 if (rmdir(dpath) < 0) {
500#if DEBUG
501 fprintf(stderr, "Failed removing %s: %m\n", dpath);
502#endif
503 }
504 }
505 free(dpath);
506 }
507
508 closedir(dir);
509}
510
511/*
512 * Try to remove a cgroup in a controller to cleanup during failure.
513 * All mounts of comounted controllers are the same, so we just look
514 * for the first mount which exists, try to remove the directory, and
515 * return.
516 */
517static void cgfs_remove_forone(int idx, const char *cg)
518{
519 struct controller *c = controllers[idx];
520 char *path;
521
522 while (c) {
523 if (c->mount_path) {
524 path = must_strcat(c->mount_path, cg, NULL);
525 recursive_rmdir(path);
526 free(path);
527 }
528 c = c->next;
529 }
530}
531
79ab1116 532static bool cgfs_create(const char *cg, uid_t uid, gid_t gid, bool *existed)
df54106a
SH
533{
534 *existed = false;
535 int i, j;
536
537#if DEBUG
538 fprintf(stderr, "creating %s\n", cg);
539#endif
540 for (i = 0; i < MAXCONTROLLERS; i++) {
541 struct controller *c = controllers[i];
542
543 if (!c)
544 continue;
545
79ab1116 546 if (!cgfs_create_forone(c, uid, gid, cg, existed)) {
df54106a
SH
547 for (j = 0; j < i; j++)
548 cgfs_remove_forone(j, cg);
549 return false;
550 }
551 }
552
553 return true;
554}
555
df54106a
SH
556static bool write_int(char *path, int v)
557{
558 FILE *f = fopen(path, "w");
e9597a70
SH
559 bool ret = true;
560
df54106a
SH
561 if (!f)
562 return false;
e9597a70
SH
563 if (fprintf(f, "%d\n", v) < 0)
564 ret = false;
565 if (fclose(f) != 0)
566 ret = false;
567 return ret;
df54106a
SH
568}
569
570static bool do_enter(struct controller *c, const char *cg)
571{
572 char *path;
573 bool pass;
574
575 while (c) {
576 if (!c->mount_path || !c->init_path)
577 continue;
578 path = must_strcat(c->mount_path, c->init_path, cg, "/cgroup.procs", NULL);
579 if (!exists(path)) {
580 free(path);
581 path = must_strcat(c->mount_path, c->init_path, cg, "/tasks", NULL);
582 }
583#if DEBUG
584 fprintf(stderr, "Attempting to enter %s:%s using %s\n", c->name, cg, path);
585#endif
586 pass = write_int(path, (int)getpid());
587 free(path);
588 if (pass) /* only have to enter one of the comounts */
589 return true;
590#if DEBUG
591 if (!pass)
592 fprintf(stderr, "Failed to enter %s:%s\n", c->name, cg);
593#endif
594 c = c->next;
595 }
596
597 return false;
598}
599
edd25678 600static bool cgfs_enter(const char *cg, bool skip_systemd)
df54106a
SH
601{
602 int i;
603
604 for (i = 0; i < MAXCONTROLLERS; i++) {
605 struct controller *c = controllers[i];
606
607 if (!c)
608 continue;
609
edd25678
SH
610 if (strcmp(c->name, "name=systemd") == 0) {
611 if (skip_systemd)
612 continue;
613 if (c->systemd_created)
614 continue;
615 }
616
df54106a
SH
617 if (!do_enter(c, cg))
618 return false;
619 }
620
621 return true;
622}
623
624static void cgfs_escape(void)
625{
edd25678 626 if (!cgfs_enter("/", true)) {
df54106a
SH
627 mysyslog(LOG_WARNING, "Failed to escape to init's cgroup\n");
628 }
629}
630
631static bool get_uid_gid(const char *user, uid_t *uid, gid_t *gid)
632{
633 struct passwd *pwent;
634
635 pwent = getpwnam(user);
636 if (!pwent)
637 return false;
638 *uid = pwent->pw_uid;
639 *gid = pwent->pw_gid;
640
641 return true;
642}
643
644#define DIRNAMSZ 200
645static int handle_login(const char *user)
646{
647 int idx = 0, ret;
648 bool existed;
649 uid_t uid = 0;
650 gid_t gid = 0;
651 char cg[MAXPATHLEN];
652
653 if (!get_uid_gid(user, &uid, &gid)) {
654 mysyslog(LOG_ERR, "Failed to get uid and gid for %s\n", user);
655 return PAM_SESSION_ERR;
656 }
657
658 cgfs_escape();
659
660 while (idx >= 0) {
661 ret = snprintf(cg, MAXPATHLEN, "/user/%s/%d", user, idx);
662 if (ret < 0 || ret >= MAXPATHLEN) {
663 mysyslog(LOG_ERR, "username too long\n");
664 return PAM_SESSION_ERR;
665 }
666
79ab1116 667 if (!cgfs_create(cg, uid, gid, &existed)) {
df54106a
SH
668 mysyslog(LOG_ERR, "Failed to create a cgroup for user %s\n", user);
669 return PAM_SESSION_ERR;
670 }
671
672 if (existed == 1) {
673 idx++;
674 continue;
675 }
676
edd25678 677 if (!cgfs_enter(cg, false)) {
df54106a
SH
678 mysyslog(LOG_ERR, "Failed to enter user cgroup %s for user %s\n", cg, user);
679 return PAM_SESSION_ERR;
680 }
681 break;
682 }
683
684 return PAM_SUCCESS;
685}
686
687int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc,
688 const char **argv)
689{
690 const char *PAM_user = NULL;
691 int ret;
692
693 if (!get_active_controllers()) {
694 mysyslog(LOG_ERR, "Failed to get list of controllers\n");
695 return PAM_SESSION_ERR;
696 }
697
698 if (argc > 1 && strcmp(argv[0], "-c") == 0)
699 filter_controllers(argv[1]);
700
701 ret = pam_get_user(pamh, &PAM_user, NULL);
702 if (ret != PAM_SUCCESS) {
703 mysyslog(LOG_ERR, "PAM-CGFS: couldn't get user\n");
704 return PAM_SESSION_ERR;
705 }
706
707 ret = handle_login(PAM_user);
708 return ret;
709}
710
711static void prune_empty_cgroups(struct controller *c, const char *user)
712{
713 while (c) {
714 if (!c->mount_path || !c->init_path)
715 goto next;
716 char *path = must_strcat(c->mount_path, c->init_path, "user/", user, NULL);
717#if DEBUG
718 fprintf(stderr, "Pruning %s\n", path);
719#endif
720 recursive_rmdir(path);
dec08471 721 free(path);
df54106a
SH
722next:
723 c = c->next;
724 }
725}
726
727/*
728 * Since we can't rely on kernel's autoremove, remove stale cgroups
729 * any time the user logs out.
730 */
731static void prune_user_cgs(const char *user)
732{
733 int i;
734
735 for (i = 0; i < MAXCONTROLLERS; i++)
736 prune_empty_cgroups(controllers[i], user);
737}
738
739int pam_sm_close_session(pam_handle_t *pamh, int flags, int argc,
740 const char **argv)
741{
742 const char *PAM_user = NULL;
743 int ret = pam_get_user(pamh, &PAM_user, NULL);
744
745 if (ret != PAM_SUCCESS) {
746 mysyslog(LOG_ERR, "PAM-CGFS: couldn't get user\n");
747 return PAM_SESSION_ERR;
748 }
749
750 if (!initialized) {
751 get_active_controllers();
752 if (argc > 1 && strcmp(argv[0], "-c") == 0)
753 filter_controllers(argv[1]);
754 }
755
756 prune_user_cgs(PAM_user);
757 return PAM_SUCCESS;
758}