]> git.proxmox.com Git - mirror_lxcfs.git/blame - pam/pam_cgfs.c
add pam module
[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
151/* Find the path at which each controller is mounted. */
152static void get_mounted_paths(void)
153{
154 int i;
155 struct controller *c;
156 char *path;
157
158 for (i = 0; i < MAXCONTROLLERS; i++) {
159 c = controllers[i];
160 if (!c || c->mount_path)
161 continue;
162 path = must_strcat("/sys/fs/cgroup/", c->name, NULL);
163 if (!exists(path)) {
164 free(path);
165 continue;
166 }
167 while (c) {
168 c->mount_path = path;
169 c = c->next;
170 }
171 }
172}
173
174static bool add_controller(int id, char *tok)
175{
176 struct controller *c;
177
178 if ((c = malloc(sizeof(struct controller))) == NULL)
179 return false;
180 c->id = id;
181 if ((c->name = strdup(tok)) == NULL)
182 return false;
183 c->next = controllers[id];
184 c->mount_path = NULL;
185 c->init_path = NULL;
186 controllers[id] = c;
187 return true;
188}
189
190static void drop_controller(int which)
191{
192 struct controller *c = controllers[which];
193
194 if (c) {
195 free(c->init_path); // all comounts share this
196 free(c->mount_path);
197 }
198 while (c) {
199 struct controller *tmp = c->next;
200 free(c->name);
201 free(c);
202 c = tmp;
203 }
204 controllers[which] = NULL;
205}
206
207static bool single_in_filter(char *c, const char *filter)
208{
209 char *dup = strdupa(filter), *tok;
210 for (tok = strtok(dup, ","); tok; tok = strtok(NULL, ",")) {
211 if (strcmp(c, tok) == 0)
212 return true;
213 }
214 return false;
215}
216
217static bool controller_in_filter(struct controller *controller, const char *filter)
218{
219 struct controller *c;
220
221 for (c = controller; c; c = c->next) {
222 if (single_in_filter(c->name, filter))
223 return true;
224 }
225 return false;
226}
227
228/*
229 * Passed a comma-delimited list of requested controllers.
230 * Pulls any controllers not in the list out of the
231 * list of controllers
232 */
233static void filter_controllers(const char *filter)
234{
235 int i;
236 for (i = 0; i < MAXCONTROLLERS; i++) {
237 if (!controllers[i])
238 continue;
239 if (filter && !controller_in_filter(controllers[i], filter))
240 drop_controller(i);
241 }
242}
243
244#define INIT_SCOPE "/init.scope"
245static void prune_init_scope(char *cg)
246{
247 char *point;
248
249 if (!cg)
250 return;
251
252 point = cg + strlen(cg) - strlen(INIT_SCOPE);
253 if (point < cg)
254 return;
255 if (strcmp(point, INIT_SCOPE) == 0) {
256 if (point == cg)
257 *(point+1) = '\0';
258 else
259 *point = '\0';
260 }
261}
262
263static bool fill_in_init_paths(void)
264{
265 FILE *f;
266 char *line = NULL;
267 size_t len = 0;
268 struct controller *c;
269
270 f = fopen("/proc/1/cgroup", "r");
271 if (!f)
272 return false;
273 while (getline(&line, &len, f) != -1) {
274 int id;
275 char *subsystems, *ip;
276 if (sscanf(line, "%d:%m[^:]:%ms", &id, &subsystems, &ip) != 3) {
277 mysyslog(LOG_ERR, "Corrupt /proc/1/cgroup\n");
278 fclose(f);
279 return false;
280 }
281 free(subsystems);
282 if (id < 0 || id > 20) {
283 mysyslog(LOG_ERR, "Too many subsystems\n");
284 free(ip);
285 fclose(f);
286 return false;
287 }
288 if (ip[0] != '/') {
289 free(ip);
290 mysyslog(LOG_ERR, "ERROR: init cgroup path is not absolute!\n");
291 return false;
292 }
293 prune_init_scope(ip);
294 for (c = controllers[id]; c; c = c->next)
295 c->init_path = ip;
296 }
297 fclose(f);
298 return true;
299}
300
301#if DEBUG
302static void print_found_controllers(void) {
303 struct controller *c;
304 int i;
305
306 for (i = 0; i < MAXCONTROLLERS; i++) {
307 c = controllers[i];
308 if (!c) {
309 fprintf(stderr, "Nothing in controller %d\n", i);
310 continue;
311 }
312 fprintf(stderr, "Controller %d:\n", i);
313 while (c) {
314 fprintf(stderr, " Next mount: index %d name %s\n", c->id, c->name);
315 fprintf(stderr, " mount path %s\n", c->mount_path ? c->mount_path : "(none)");
316 fprintf(stderr, " init task path %s\n", c->init_path);
317 c = c->next;
318 }
319 }
320}
321#else
322static inline void print_found_controllers(void) { };
323#endif
324/*
325 * Get the list of cgroup controllers currently mounted.
326 * This includes both kernel and named subsystems, so get the list from
327 * /proc/self/cgroup rather than /proc/cgroups.
328 */
329static bool get_active_controllers(void)
330{
331 FILE *f;
332 char *line = NULL, *tok;
333 size_t len = 0;
334
335 f = fopen("/proc/self/cgroup", "r");
336 if (!f)
337 return false;
338 while (getline(&line, &len, f) != -1) {
339 int id;
340 char *subsystems;
341 if (sscanf(line, "%d:%m[^:]:", &id, &subsystems) != 2) {
342 mysyslog(LOG_ERR, "Corrupt /proc/self/cgroup\n");
343 fclose(f);
344 return false;
345 }
346 if (id < 0 || id > 20) {
347 mysyslog(LOG_ERR, "Too many subsystems\n");
348 free(subsystems);
349 fclose(f);
350 return false;
351 }
352 if (strcmp(subsystems, "name=systemd") == 0)
353 goto next;
354 for (tok = strtok(subsystems, ","); tok; tok = strtok(NULL, ","))
355 add_controller(id, tok);
356next:
357 free(subsystems);
358 }
359 fclose(f);
360
361 get_mounted_paths();
362
363 if (!fill_in_init_paths()) {
364 mysyslog(LOG_ERR, "Failed finding cgroups for init task\n");
365 return false;
366 }
367
368 print_found_controllers();
369
370 initialized = true;
371
372 return true;
373}
374
375static bool cgfs_create_forone(const struct controller *c, const char *cg, bool *existed)
376{
377 while (c) {
378 if (!c->mount_path || !c->init_path)
379 goto next;
380 char *path = must_strcat(c->mount_path, c->init_path, cg, NULL);
381#if DEBUG
382 fprintf(stderr, "Creating %s for %s\n", path, c->name);
383#endif
384 if (exists(path)) {
385 free(path);
386 *existed = true;
387#if DEBUG
388 fprintf(stderr, "%s existed\n", path);
389#endif
390 return true;
391 }
392 bool pass = mkdir_p(c->mount_path, path);
393#if DEBUG
394 fprintf(stderr, "Creating %s %s\n", path, pass ? "succeeded" : "failed");
395#endif
396 free(path);
397 if (pass)
398 return true;
399next:
400 c = c->next;
401 }
402 return false;
403}
404
405static void recursive_rmdir(const char *path)
406{
407 struct dirent *direntp;
408 DIR *dir;
409
410 dir = opendir(path);
411 if (!dir)
412 return;
413 while ((direntp = readdir(dir))!= NULL) {
414 if (!strcmp(direntp->d_name, ".") ||
415 !strcmp(direntp->d_name, ".."))
416 continue;
417
418 char *dpath = must_strcat(path, "/", direntp->d_name, NULL);
419 if (is_dir(dpath)) {
420 recursive_rmdir(dpath);
421#if DEBUG
422 fprintf(stderr, "attempting to remove %s\n", dpath);
423#endif
424 if (rmdir(dpath) < 0) {
425#if DEBUG
426 fprintf(stderr, "Failed removing %s: %m\n", dpath);
427#endif
428 }
429 }
430 free(dpath);
431 }
432
433 closedir(dir);
434}
435
436/*
437 * Try to remove a cgroup in a controller to cleanup during failure.
438 * All mounts of comounted controllers are the same, so we just look
439 * for the first mount which exists, try to remove the directory, and
440 * return.
441 */
442static void cgfs_remove_forone(int idx, const char *cg)
443{
444 struct controller *c = controllers[idx];
445 char *path;
446
447 while (c) {
448 if (c->mount_path) {
449 path = must_strcat(c->mount_path, cg, NULL);
450 recursive_rmdir(path);
451 free(path);
452 }
453 c = c->next;
454 }
455}
456
457static bool cgfs_create(const char *cg, bool *existed)
458{
459 *existed = false;
460 int i, j;
461
462#if DEBUG
463 fprintf(stderr, "creating %s\n", cg);
464#endif
465 for (i = 0; i < MAXCONTROLLERS; i++) {
466 struct controller *c = controllers[i];
467
468 if (!c)
469 continue;
470
471 if (!cgfs_create_forone(c, cg, existed)) {
472 for (j = 0; j < i; j++)
473 cgfs_remove_forone(j, cg);
474 return false;
475 }
476 }
477
478 return true;
479}
480
481static bool cgfs_chown(const char *cg, uid_t uid, gid_t gid)
482{
483 /* TODO */
484 return true;
485}
486
487static bool write_int(char *path, int v)
488{
489 FILE *f = fopen(path, "w");
490 if (!f)
491 return false;
492 fprintf(f, "%d\n", v);
493 fclose(f);
494 return true;
495}
496
497static bool do_enter(struct controller *c, const char *cg)
498{
499 char *path;
500 bool pass;
501
502 while (c) {
503 if (!c->mount_path || !c->init_path)
504 continue;
505 path = must_strcat(c->mount_path, c->init_path, cg, "/cgroup.procs", NULL);
506 if (!exists(path)) {
507 free(path);
508 path = must_strcat(c->mount_path, c->init_path, cg, "/tasks", NULL);
509 }
510#if DEBUG
511 fprintf(stderr, "Attempting to enter %s:%s using %s\n", c->name, cg, path);
512#endif
513 pass = write_int(path, (int)getpid());
514 free(path);
515 if (pass) /* only have to enter one of the comounts */
516 return true;
517#if DEBUG
518 if (!pass)
519 fprintf(stderr, "Failed to enter %s:%s\n", c->name, cg);
520#endif
521 c = c->next;
522 }
523
524 return false;
525}
526
527static bool cgfs_enter(const char *cg)
528{
529 int i;
530
531 for (i = 0; i < MAXCONTROLLERS; i++) {
532 struct controller *c = controllers[i];
533
534 if (!c)
535 continue;
536
537 if (!do_enter(c, cg))
538 return false;
539 }
540
541 return true;
542}
543
544static void cgfs_escape(void)
545{
546 if (!cgfs_enter("/")) {
547 mysyslog(LOG_WARNING, "Failed to escape to init's cgroup\n");
548 }
549}
550
551static bool get_uid_gid(const char *user, uid_t *uid, gid_t *gid)
552{
553 struct passwd *pwent;
554
555 pwent = getpwnam(user);
556 if (!pwent)
557 return false;
558 *uid = pwent->pw_uid;
559 *gid = pwent->pw_gid;
560
561 return true;
562}
563
564#define DIRNAMSZ 200
565static int handle_login(const char *user)
566{
567 int idx = 0, ret;
568 bool existed;
569 uid_t uid = 0;
570 gid_t gid = 0;
571 char cg[MAXPATHLEN];
572
573 if (!get_uid_gid(user, &uid, &gid)) {
574 mysyslog(LOG_ERR, "Failed to get uid and gid for %s\n", user);
575 return PAM_SESSION_ERR;
576 }
577
578 cgfs_escape();
579
580 while (idx >= 0) {
581 ret = snprintf(cg, MAXPATHLEN, "/user/%s/%d", user, idx);
582 if (ret < 0 || ret >= MAXPATHLEN) {
583 mysyslog(LOG_ERR, "username too long\n");
584 return PAM_SESSION_ERR;
585 }
586
587 if (!cgfs_create(cg, &existed)) {
588 mysyslog(LOG_ERR, "Failed to create a cgroup for user %s\n", user);
589 return PAM_SESSION_ERR;
590 }
591
592 if (existed == 1) {
593 idx++;
594 continue;
595 }
596
597 if (!cgfs_chown(cg, uid, gid)) {
598 mysyslog(LOG_ERR, "Warning: failed to chown %s for user %s\n", cg, user);
599 }
600
601 if (!cgfs_enter(cg)) {
602 mysyslog(LOG_ERR, "Failed to enter user cgroup %s for user %s\n", cg, user);
603 return PAM_SESSION_ERR;
604 }
605 break;
606 }
607
608 return PAM_SUCCESS;
609}
610
611int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc,
612 const char **argv)
613{
614 const char *PAM_user = NULL;
615 int ret;
616
617 if (!get_active_controllers()) {
618 mysyslog(LOG_ERR, "Failed to get list of controllers\n");
619 return PAM_SESSION_ERR;
620 }
621
622 if (argc > 1 && strcmp(argv[0], "-c") == 0)
623 filter_controllers(argv[1]);
624
625 ret = pam_get_user(pamh, &PAM_user, NULL);
626 if (ret != PAM_SUCCESS) {
627 mysyslog(LOG_ERR, "PAM-CGFS: couldn't get user\n");
628 return PAM_SESSION_ERR;
629 }
630
631 ret = handle_login(PAM_user);
632 return ret;
633}
634
635static void prune_empty_cgroups(struct controller *c, const char *user)
636{
637 while (c) {
638 if (!c->mount_path || !c->init_path)
639 goto next;
640 char *path = must_strcat(c->mount_path, c->init_path, "user/", user, NULL);
641#if DEBUG
642 fprintf(stderr, "Pruning %s\n", path);
643#endif
644 recursive_rmdir(path);
645next:
646 c = c->next;
647 }
648}
649
650/*
651 * Since we can't rely on kernel's autoremove, remove stale cgroups
652 * any time the user logs out.
653 */
654static void prune_user_cgs(const char *user)
655{
656 int i;
657
658 for (i = 0; i < MAXCONTROLLERS; i++)
659 prune_empty_cgroups(controllers[i], user);
660}
661
662int pam_sm_close_session(pam_handle_t *pamh, int flags, int argc,
663 const char **argv)
664{
665 const char *PAM_user = NULL;
666 int ret = pam_get_user(pamh, &PAM_user, NULL);
667
668 if (ret != PAM_SUCCESS) {
669 mysyslog(LOG_ERR, "PAM-CGFS: couldn't get user\n");
670 return PAM_SESSION_ERR;
671 }
672
673 if (!initialized) {
674 get_active_controllers();
675 if (argc > 1 && strcmp(argv[0], "-c") == 0)
676 filter_controllers(argv[1]);
677 }
678
679 prune_user_cgs(PAM_user);
680 return PAM_SUCCESS;
681}