]>
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
];
152 * if cpu and cpuacct are comounted, it's possible a mount
153 * exists for only one. Find it.
155 static char *find_controller_path(struct controller
*c
)
158 char *path
= must_strcat("/sys/fs/cgroup/", c
->name
, NULL
);
167 /* Find the path at which each controller is mounted. */
168 static void get_mounted_paths(void)
171 struct controller
*c
;
174 for (i
= 0; i
< MAXCONTROLLERS
; i
++) {
176 if (!c
|| c
->mount_path
)
178 path
= find_controller_path(c
);
182 c
->mount_path
= path
;
188 static void add_controller(int id
, char *tok
)
190 struct controller
*c
;
193 c
= malloc(sizeof(struct controller
));
196 c
->name
= strdup(tok
);
199 c
->next
= controllers
[id
];
200 c
->mount_path
= NULL
;
205 static void drop_controller(int which
)
207 struct controller
*c
= controllers
[which
];
210 free(c
->init_path
); // all comounts share this
214 struct controller
*tmp
= c
->next
;
219 controllers
[which
] = NULL
;
222 static bool single_in_filter(char *c
, const char *filter
)
224 char *dup
= strdupa(filter
), *tok
;
225 for (tok
= strtok(dup
, ","); tok
; tok
= strtok(NULL
, ",")) {
226 if (strcmp(c
, tok
) == 0)
232 static bool controller_in_filter(struct controller
*controller
, const char *filter
)
234 struct controller
*c
;
236 for (c
= controller
; c
; c
= c
->next
) {
237 if (single_in_filter(c
->name
, filter
))
244 * Passed a comma-delimited list of requested controllers.
245 * Pulls any controllers not in the list out of the
246 * list of controllers
248 static void filter_controllers(const char *filter
)
251 for (i
= 0; i
< MAXCONTROLLERS
; i
++) {
254 if (filter
&& !controller_in_filter(controllers
[i
], filter
))
259 #define INIT_SCOPE "/init.scope"
260 static void prune_init_scope(char *cg
)
267 point
= cg
+ strlen(cg
) - strlen(INIT_SCOPE
);
270 if (strcmp(point
, INIT_SCOPE
) == 0) {
278 static bool fill_in_init_paths(void)
283 struct controller
*c
;
286 f
= fopen("/proc/1/cgroup", "r");
289 while (getline(&line
, &len
, f
) != -1) {
291 char *subsystems
, *ip
;
292 if (sscanf(line
, "%d:%m[^:]:%ms", &id
, &subsystems
, &ip
) != 3) {
293 mysyslog(LOG_ERR
, "Corrupt /proc/1/cgroup\n");
297 if (id
< 0 || id
> 20) {
298 mysyslog(LOG_ERR
, "Too many subsystems\n");
304 mysyslog(LOG_ERR
, "ERROR: init cgroup path is not absolute!\n");
307 prune_init_scope(ip
);
308 for (c
= controllers
[id
]; c
; c
= c
->next
)
319 static void print_found_controllers(void) {
320 struct controller
*c
;
323 for (i
= 0; i
< MAXCONTROLLERS
; i
++) {
326 fprintf(stderr
, "Nothing in controller %d\n", i
);
329 fprintf(stderr
, "Controller %d:\n", i
);
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
);
339 static inline void print_found_controllers(void) { };
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.
346 static bool get_active_controllers(void)
349 char *line
= NULL
, *tok
;
352 f
= fopen("/proc/self/cgroup", "r");
355 while (getline(&line
, &len
, f
) != -1) {
358 if (sscanf(line
, "%d:%m[^:]:", &id
, &subsystems
) != 2) {
359 mysyslog(LOG_ERR
, "Corrupt /proc/self/cgroup\n");
364 if (id
< 0 || id
> 20) {
365 mysyslog(LOG_ERR
, "Too many subsystems\n");
371 if (strcmp(subsystems
, "name=systemd") == 0)
373 for (tok
= strtok(subsystems
, ","); tok
; tok
= strtok(NULL
, ","))
374 add_controller(id
, tok
);
383 if (!fill_in_init_paths()) {
384 mysyslog(LOG_ERR
, "Failed finding cgroups for init task\n");
388 print_found_controllers();
395 static bool cgfs_create_forone(const struct controller
*c
, uid_t uid
, gid_t gid
, const char *cg
, bool *existed
)
398 if (!c
->mount_path
|| !c
->init_path
)
400 char *path
= must_strcat(c
->mount_path
, c
->init_path
, cg
, NULL
);
402 fprintf(stderr
, "Creating %s for %s\n", path
, c
->name
);
408 fprintf(stderr
, "%s existed\n", path
);
412 bool pass
= mkdir_p(c
->mount_path
, path
);
414 fprintf(stderr
, "Creating %s %s\n", path
, pass
? "succeeded" : "failed");
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
);
430 static void recursive_rmdir(const char *path
)
432 struct dirent
*direntp
;
438 while ((direntp
= readdir(dir
))!= NULL
) {
439 if (!strcmp(direntp
->d_name
, ".") ||
440 !strcmp(direntp
->d_name
, ".."))
443 char *dpath
= must_strcat(path
, "/", direntp
->d_name
, NULL
);
445 recursive_rmdir(dpath
);
447 fprintf(stderr
, "attempting to remove %s\n", dpath
);
449 if (rmdir(dpath
) < 0) {
451 fprintf(stderr
, "Failed removing %s: %m\n", dpath
);
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
467 static void cgfs_remove_forone(int idx
, const char *cg
)
469 struct controller
*c
= controllers
[idx
];
474 path
= must_strcat(c
->mount_path
, cg
, NULL
);
475 recursive_rmdir(path
);
482 static bool cgfs_create(const char *cg
, uid_t uid
, gid_t gid
, bool *existed
)
488 fprintf(stderr
, "creating %s\n", cg
);
490 for (i
= 0; i
< MAXCONTROLLERS
; i
++) {
491 struct controller
*c
= controllers
[i
];
496 if (!cgfs_create_forone(c
, uid
, gid
, cg
, existed
)) {
497 for (j
= 0; j
< i
; j
++)
498 cgfs_remove_forone(j
, cg
);
506 static bool write_int(char *path
, int v
)
508 FILE *f
= fopen(path
, "w");
513 if (fprintf(f
, "%d\n", v
) < 0)
520 static bool do_enter(struct controller
*c
, const char *cg
)
526 if (!c
->mount_path
|| !c
->init_path
)
528 path
= must_strcat(c
->mount_path
, c
->init_path
, cg
, "/cgroup.procs", NULL
);
531 path
= must_strcat(c
->mount_path
, c
->init_path
, cg
, "/tasks", NULL
);
534 fprintf(stderr
, "Attempting to enter %s:%s using %s\n", c
->name
, cg
, path
);
536 pass
= write_int(path
, (int)getpid());
538 if (pass
) /* only have to enter one of the comounts */
542 fprintf(stderr
, "Failed to enter %s:%s\n", c
->name
, cg
);
550 static bool cgfs_enter(const char *cg
)
554 for (i
= 0; i
< MAXCONTROLLERS
; i
++) {
555 struct controller
*c
= controllers
[i
];
560 if (!do_enter(c
, cg
))
567 static void cgfs_escape(void)
569 if (!cgfs_enter("/")) {
570 mysyslog(LOG_WARNING
, "Failed to escape to init's cgroup\n");
574 static bool get_uid_gid(const char *user
, uid_t
*uid
, gid_t
*gid
)
576 struct passwd
*pwent
;
578 pwent
= getpwnam(user
);
581 *uid
= pwent
->pw_uid
;
582 *gid
= pwent
->pw_gid
;
588 static int handle_login(const char *user
)
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
;
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
;
610 if (!cgfs_create(cg
, uid
, gid
, &existed
)) {
611 mysyslog(LOG_ERR
, "Failed to create a cgroup for user %s\n", user
);
612 return PAM_SESSION_ERR
;
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
;
630 int pam_sm_open_session(pam_handle_t
*pamh
, int flags
, int argc
,
633 const char *PAM_user
= NULL
;
636 if (!get_active_controllers()) {
637 mysyslog(LOG_ERR
, "Failed to get list of controllers\n");
638 return PAM_SESSION_ERR
;
641 if (argc
> 1 && strcmp(argv
[0], "-c") == 0)
642 filter_controllers(argv
[1]);
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
;
650 ret
= handle_login(PAM_user
);
654 static void prune_empty_cgroups(struct controller
*c
, const char *user
)
657 if (!c
->mount_path
|| !c
->init_path
)
659 char *path
= must_strcat(c
->mount_path
, c
->init_path
, "user/", user
, NULL
);
661 fprintf(stderr
, "Pruning %s\n", path
);
663 recursive_rmdir(path
);
670 * Since we can't rely on kernel's autoremove, remove stale cgroups
671 * any time the user logs out.
673 static void prune_user_cgs(const char *user
)
677 for (i
= 0; i
< MAXCONTROLLERS
; i
++)
678 prune_empty_cgroups(controllers
[i
], user
);
681 int pam_sm_close_session(pam_handle_t
*pamh
, int flags
, int argc
,
684 const char *PAM_user
= NULL
;
685 int ret
= pam_get_user(pamh
, &PAM_user
, NULL
);
687 if (ret
!= PAM_SUCCESS
) {
688 mysyslog(LOG_ERR
, "PAM-CGFS: couldn't get user\n");
689 return PAM_SESSION_ERR
;
693 get_active_controllers();
694 if (argc
> 1 && strcmp(argv
[0], "-c") == 0)
695 filter_controllers(argv
[1]);
698 prune_user_cgs(PAM_user
);