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