]>
git.proxmox.com Git - mirror_lxcfs.git/blob - pam/pam_cgfs.c
3 * Copyright © 2016 Canonical, Inc
4 * Author: Serge Hallyn <serge.hallyn@ubuntu.com>
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
11 * The cgroup created will be "user/$user/0" for the first session,
12 * "user/$user/1" for the second, etc.
14 * All requested cgroups must be mounted under /sys/fs/cgroup/$controller,
15 * no messing around with finding mountpoints.
17 * See COPYING file for details.
26 #include <sys/mount.h>
27 #include <sys/types.h>
29 #include <sys/param.h>
34 #define PAM_SM_SESSION
35 #include <security/_pam_macros.h>
36 #include <security/pam_modules.h>
38 #include <linux/unistd.h>
40 static bool initialized
;
42 static void mysyslog(int err
, const char *format
, ...)
46 va_start(args
, format
);
47 openlog("PAM-CGFS", LOG_CONS
|LOG_PID
, LOG_AUTH
);
48 vsyslog(err
, format
, args
);
53 static char *must_strcat(const char *first
, ...) __attribute__((sentinel
));
55 static char *must_strcat(const char *first
, ...)
58 char *dest
, *cur
, *new;
66 va_start(args
, first
);
68 while ((cur
= va_arg(args
, char *)) != NULL
) {
69 size_t newlen
= len
+ strlen(cur
);
71 new = realloc(dest
, newlen
+ 1);
82 static bool exists(const char *path
)
87 ret
= stat(path
, &sb
);
91 static bool is_dir(const char *path
)
95 if (stat(path
, &sb
) < 0)
97 if (S_ISDIR(sb
.st_mode
))
102 static bool mkdir_p(const char *root
, char *path
)
106 if (strlen(path
) < strlen(root
))
108 if (strlen(path
) == strlen(root
))
111 b
= path
+ strlen(root
) + 1;
113 while (*b
&& *b
== '/')
118 while (*e
&& *e
!= '/')
125 if (mkdir(path
, 0755) < 0) {
127 fprintf(stderr
, "Failed to create %s: %m\n", path
);
141 struct controller
*next
;
148 #define MAXCONTROLLERS 20
149 static struct controller
*controllers
[MAXCONTROLLERS
];
151 /* Find the path at which each controller is mounted. */
152 static void get_mounted_paths(void)
155 struct controller
*c
;
158 for (i
= 0; i
< MAXCONTROLLERS
; i
++) {
160 if (!c
|| c
->mount_path
)
162 path
= must_strcat("/sys/fs/cgroup/", c
->name
, NULL
);
168 c
->mount_path
= path
;
174 static void add_controller(int id
, char *tok
)
176 struct controller
*c
;
179 c
= malloc(sizeof(struct controller
));
182 c
->name
= strdup(tok
);
185 c
->next
= controllers
[id
];
186 c
->mount_path
= NULL
;
191 static void drop_controller(int which
)
193 struct controller
*c
= controllers
[which
];
196 free(c
->init_path
); // all comounts share this
200 struct controller
*tmp
= c
->next
;
205 controllers
[which
] = NULL
;
208 static bool single_in_filter(char *c
, const char *filter
)
210 char *dup
= strdupa(filter
), *tok
;
211 for (tok
= strtok(dup
, ","); tok
; tok
= strtok(NULL
, ",")) {
212 if (strcmp(c
, tok
) == 0)
218 static bool controller_in_filter(struct controller
*controller
, const char *filter
)
220 struct controller
*c
;
222 for (c
= controller
; c
; c
= c
->next
) {
223 if (single_in_filter(c
->name
, filter
))
230 * Passed a comma-delimited list of requested controllers.
231 * Pulls any controllers not in the list out of the
232 * list of controllers
234 static void filter_controllers(const char *filter
)
237 for (i
= 0; i
< MAXCONTROLLERS
; i
++) {
240 if (filter
&& !controller_in_filter(controllers
[i
], filter
))
245 #define INIT_SCOPE "/init.scope"
246 static void prune_init_scope(char *cg
)
253 point
= cg
+ strlen(cg
) - strlen(INIT_SCOPE
);
256 if (strcmp(point
, INIT_SCOPE
) == 0) {
264 static bool fill_in_init_paths(void)
269 struct controller
*c
;
271 f
= fopen("/proc/1/cgroup", "r");
274 while (getline(&line
, &len
, f
) != -1) {
276 char *subsystems
, *ip
;
277 if (sscanf(line
, "%d:%m[^:]:%ms", &id
, &subsystems
, &ip
) != 3) {
278 mysyslog(LOG_ERR
, "Corrupt /proc/1/cgroup\n");
283 if (id
< 0 || id
> 20) {
284 mysyslog(LOG_ERR
, "Too many subsystems\n");
291 mysyslog(LOG_ERR
, "ERROR: init cgroup path is not absolute!\n");
294 prune_init_scope(ip
);
295 for (c
= controllers
[id
]; c
; c
= c
->next
)
303 static void print_found_controllers(void) {
304 struct controller
*c
;
307 for (i
= 0; i
< MAXCONTROLLERS
; i
++) {
310 fprintf(stderr
, "Nothing in controller %d\n", i
);
313 fprintf(stderr
, "Controller %d:\n", i
);
315 fprintf(stderr
, " Next mount: index %d name %s\n", c
->id
, c
->name
);
316 fprintf(stderr
, " mount path %s\n", c
->mount_path
? c
->mount_path
: "(none)");
317 fprintf(stderr
, " init task path %s\n", c
->init_path
);
323 static inline void print_found_controllers(void) { };
326 * Get the list of cgroup controllers currently mounted.
327 * This includes both kernel and named subsystems, so get the list from
328 * /proc/self/cgroup rather than /proc/cgroups.
330 static bool get_active_controllers(void)
333 char *line
= NULL
, *tok
;
336 f
= fopen("/proc/self/cgroup", "r");
339 while (getline(&line
, &len
, f
) != -1) {
342 if (sscanf(line
, "%d:%m[^:]:", &id
, &subsystems
) != 2) {
343 mysyslog(LOG_ERR
, "Corrupt /proc/self/cgroup\n");
347 if (id
< 0 || id
> 20) {
348 mysyslog(LOG_ERR
, "Too many subsystems\n");
353 if (strcmp(subsystems
, "name=systemd") == 0)
355 for (tok
= strtok(subsystems
, ","); tok
; tok
= strtok(NULL
, ","))
356 add_controller(id
, tok
);
364 if (!fill_in_init_paths()) {
365 mysyslog(LOG_ERR
, "Failed finding cgroups for init task\n");
369 print_found_controllers();
376 static bool cgfs_create_forone(const struct controller
*c
, uid_t uid
, gid_t gid
, const char *cg
, bool *existed
)
379 if (!c
->mount_path
|| !c
->init_path
)
381 char *path
= must_strcat(c
->mount_path
, c
->init_path
, cg
, NULL
);
383 fprintf(stderr
, "Creating %s for %s\n", path
, c
->name
);
389 fprintf(stderr
, "%s existed\n", path
);
393 bool pass
= mkdir_p(c
->mount_path
, path
);
395 fprintf(stderr
, "Creating %s %s\n", path
, pass
? "succeeded" : "failed");
398 if (chown(path
, uid
, gid
) < 0)
399 mysyslog(LOG_WARNING
, "Failed to chown %s to %d:%d: %m\n",
400 path
, (int)uid
, (int)gid
);
411 static void recursive_rmdir(const char *path
)
413 struct dirent
*direntp
;
419 while ((direntp
= readdir(dir
))!= NULL
) {
420 if (!strcmp(direntp
->d_name
, ".") ||
421 !strcmp(direntp
->d_name
, ".."))
424 char *dpath
= must_strcat(path
, "/", direntp
->d_name
, NULL
);
426 recursive_rmdir(dpath
);
428 fprintf(stderr
, "attempting to remove %s\n", dpath
);
430 if (rmdir(dpath
) < 0) {
432 fprintf(stderr
, "Failed removing %s: %m\n", dpath
);
443 * Try to remove a cgroup in a controller to cleanup during failure.
444 * All mounts of comounted controllers are the same, so we just look
445 * for the first mount which exists, try to remove the directory, and
448 static void cgfs_remove_forone(int idx
, const char *cg
)
450 struct controller
*c
= controllers
[idx
];
455 path
= must_strcat(c
->mount_path
, cg
, NULL
);
456 recursive_rmdir(path
);
463 static bool cgfs_create(const char *cg
, uid_t uid
, gid_t gid
, bool *existed
)
469 fprintf(stderr
, "creating %s\n", cg
);
471 for (i
= 0; i
< MAXCONTROLLERS
; i
++) {
472 struct controller
*c
= controllers
[i
];
477 if (!cgfs_create_forone(c
, uid
, gid
, cg
, existed
)) {
478 for (j
= 0; j
< i
; j
++)
479 cgfs_remove_forone(j
, cg
);
487 static bool write_int(char *path
, int v
)
489 FILE *f
= fopen(path
, "w");
492 fprintf(f
, "%d\n", v
);
497 static bool do_enter(struct controller
*c
, const char *cg
)
503 if (!c
->mount_path
|| !c
->init_path
)
505 path
= must_strcat(c
->mount_path
, c
->init_path
, cg
, "/cgroup.procs", NULL
);
508 path
= must_strcat(c
->mount_path
, c
->init_path
, cg
, "/tasks", NULL
);
511 fprintf(stderr
, "Attempting to enter %s:%s using %s\n", c
->name
, cg
, path
);
513 pass
= write_int(path
, (int)getpid());
515 if (pass
) /* only have to enter one of the comounts */
519 fprintf(stderr
, "Failed to enter %s:%s\n", c
->name
, cg
);
527 static bool cgfs_enter(const char *cg
)
531 for (i
= 0; i
< MAXCONTROLLERS
; i
++) {
532 struct controller
*c
= controllers
[i
];
537 if (!do_enter(c
, cg
))
544 static void cgfs_escape(void)
546 if (!cgfs_enter("/")) {
547 mysyslog(LOG_WARNING
, "Failed to escape to init's cgroup\n");
551 static bool get_uid_gid(const char *user
, uid_t
*uid
, gid_t
*gid
)
553 struct passwd
*pwent
;
555 pwent
= getpwnam(user
);
558 *uid
= pwent
->pw_uid
;
559 *gid
= pwent
->pw_gid
;
565 static int handle_login(const char *user
)
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
;
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
;
587 if (!cgfs_create(cg
, uid
, gid
, &existed
)) {
588 mysyslog(LOG_ERR
, "Failed to create a cgroup for user %s\n", user
);
589 return PAM_SESSION_ERR
;
597 if (!cgfs_enter(cg
)) {
598 mysyslog(LOG_ERR
, "Failed to enter user cgroup %s for user %s\n", cg
, user
);
599 return PAM_SESSION_ERR
;
607 int pam_sm_open_session(pam_handle_t
*pamh
, int flags
, int argc
,
610 const char *PAM_user
= NULL
;
613 if (!get_active_controllers()) {
614 mysyslog(LOG_ERR
, "Failed to get list of controllers\n");
615 return PAM_SESSION_ERR
;
618 if (argc
> 1 && strcmp(argv
[0], "-c") == 0)
619 filter_controllers(argv
[1]);
621 ret
= pam_get_user(pamh
, &PAM_user
, NULL
);
622 if (ret
!= PAM_SUCCESS
) {
623 mysyslog(LOG_ERR
, "PAM-CGFS: couldn't get user\n");
624 return PAM_SESSION_ERR
;
627 ret
= handle_login(PAM_user
);
631 static void prune_empty_cgroups(struct controller
*c
, const char *user
)
634 if (!c
->mount_path
|| !c
->init_path
)
636 char *path
= must_strcat(c
->mount_path
, c
->init_path
, "user/", user
, NULL
);
638 fprintf(stderr
, "Pruning %s\n", path
);
640 recursive_rmdir(path
);
647 * Since we can't rely on kernel's autoremove, remove stale cgroups
648 * any time the user logs out.
650 static void prune_user_cgs(const char *user
)
654 for (i
= 0; i
< MAXCONTROLLERS
; i
++)
655 prune_empty_cgroups(controllers
[i
], user
);
658 int pam_sm_close_session(pam_handle_t
*pamh
, int flags
, int argc
,
661 const char *PAM_user
= NULL
;
662 int ret
= pam_get_user(pamh
, &PAM_user
, NULL
);
664 if (ret
!= PAM_SUCCESS
) {
665 mysyslog(LOG_ERR
, "PAM-CGFS: couldn't get user\n");
666 return PAM_SESSION_ERR
;
670 get_active_controllers();
671 if (argc
> 1 && strcmp(argv
[0], "-c") == 0)
672 filter_controllers(argv
[1]);
675 prune_user_cgs(PAM_user
);