]> git.proxmox.com Git - mirror_lxcfs.git/blame - pam/pam_cgfs.c
Add sysvinit to init systems for Debian and Ubuntu
[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)
1e5fe374 577 goto next;
df54106a
SH
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
1e5fe374 594next:
df54106a
SH
595 c = c->next;
596 }
597
598 return false;
599}
600
edd25678 601static bool cgfs_enter(const char *cg, bool skip_systemd)
df54106a
SH
602{
603 int i;
604
605 for (i = 0; i < MAXCONTROLLERS; i++) {
606 struct controller *c = controllers[i];
607
608 if (!c)
609 continue;
610
edd25678
SH
611 if (strcmp(c->name, "name=systemd") == 0) {
612 if (skip_systemd)
613 continue;
614 if (c->systemd_created)
615 continue;
616 }
617
df54106a
SH
618 if (!do_enter(c, cg))
619 return false;
620 }
621
622 return true;
623}
624
625static void cgfs_escape(void)
626{
edd25678 627 if (!cgfs_enter("/", true)) {
df54106a
SH
628 mysyslog(LOG_WARNING, "Failed to escape to init's cgroup\n");
629 }
630}
631
632static bool get_uid_gid(const char *user, uid_t *uid, gid_t *gid)
633{
634 struct passwd *pwent;
635
636 pwent = getpwnam(user);
637 if (!pwent)
638 return false;
639 *uid = pwent->pw_uid;
640 *gid = pwent->pw_gid;
641
642 return true;
643}
644
645#define DIRNAMSZ 200
646static int handle_login(const char *user)
647{
648 int idx = 0, ret;
649 bool existed;
650 uid_t uid = 0;
651 gid_t gid = 0;
652 char cg[MAXPATHLEN];
653
654 if (!get_uid_gid(user, &uid, &gid)) {
655 mysyslog(LOG_ERR, "Failed to get uid and gid for %s\n", user);
656 return PAM_SESSION_ERR;
657 }
658
659 cgfs_escape();
660
661 while (idx >= 0) {
662 ret = snprintf(cg, MAXPATHLEN, "/user/%s/%d", user, idx);
663 if (ret < 0 || ret >= MAXPATHLEN) {
664 mysyslog(LOG_ERR, "username too long\n");
665 return PAM_SESSION_ERR;
666 }
667
79ab1116 668 if (!cgfs_create(cg, uid, gid, &existed)) {
df54106a
SH
669 mysyslog(LOG_ERR, "Failed to create a cgroup for user %s\n", user);
670 return PAM_SESSION_ERR;
671 }
672
673 if (existed == 1) {
674 idx++;
675 continue;
676 }
677
edd25678 678 if (!cgfs_enter(cg, false)) {
df54106a
SH
679 mysyslog(LOG_ERR, "Failed to enter user cgroup %s for user %s\n", cg, user);
680 return PAM_SESSION_ERR;
681 }
682 break;
683 }
684
685 return PAM_SUCCESS;
686}
687
688int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc,
689 const char **argv)
690{
691 const char *PAM_user = NULL;
692 int ret;
693
694 if (!get_active_controllers()) {
695 mysyslog(LOG_ERR, "Failed to get list of controllers\n");
696 return PAM_SESSION_ERR;
697 }
698
699 if (argc > 1 && strcmp(argv[0], "-c") == 0)
700 filter_controllers(argv[1]);
701
702 ret = pam_get_user(pamh, &PAM_user, NULL);
703 if (ret != PAM_SUCCESS) {
704 mysyslog(LOG_ERR, "PAM-CGFS: couldn't get user\n");
705 return PAM_SESSION_ERR;
706 }
707
708 ret = handle_login(PAM_user);
709 return ret;
710}
711
712static void prune_empty_cgroups(struct controller *c, const char *user)
713{
714 while (c) {
715 if (!c->mount_path || !c->init_path)
716 goto next;
717 char *path = must_strcat(c->mount_path, c->init_path, "user/", user, NULL);
718#if DEBUG
719 fprintf(stderr, "Pruning %s\n", path);
720#endif
721 recursive_rmdir(path);
dec08471 722 free(path);
df54106a
SH
723next:
724 c = c->next;
725 }
726}
727
728/*
729 * Since we can't rely on kernel's autoremove, remove stale cgroups
730 * any time the user logs out.
731 */
732static void prune_user_cgs(const char *user)
733{
734 int i;
735
736 for (i = 0; i < MAXCONTROLLERS; i++)
737 prune_empty_cgroups(controllers[i], user);
738}
739
740int pam_sm_close_session(pam_handle_t *pamh, int flags, int argc,
741 const char **argv)
742{
743 const char *PAM_user = NULL;
744 int ret = pam_get_user(pamh, &PAM_user, NULL);
745
746 if (ret != PAM_SUCCESS) {
747 mysyslog(LOG_ERR, "PAM-CGFS: couldn't get user\n");
748 return PAM_SESSION_ERR;
749 }
750
751 if (!initialized) {
752 get_active_controllers();
753 if (argc > 1 && strcmp(argv[0], "-c") == 0)
754 filter_controllers(argv[1]);
755 }
756
757 prune_user_cgs(PAM_user);
758 return PAM_SUCCESS;
759}