]>
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 the user
7 * may administer, either for all controllers or for any controllers listed
8 * on the command line (if any are listed).
10 * The cgroup created will be "user/$user/0" for the first session,
11 * "user/$user/1" for the second, etc.
13 * name=systemd is handled specially. If the host is an upstart system
14 * or the login is noninteractive, then the logged in user does not get
15 * a cgroup created. On a systemd interactive login, one is created but
16 * not chowned to the user. In the former case, we create one as usual,
17 * in the latter case we simply chown whatever cgroup the user is in.
19 * All requested cgroups must be mounted under /sys/fs/cgroup/$controller,
20 * no messing around with finding mountpoints.
22 * See COPYING file for details.
31 #include <sys/mount.h>
32 #include <sys/types.h>
34 #include <sys/param.h>
39 #define PAM_SM_SESSION
40 #include <security/_pam_macros.h>
41 #include <security/pam_modules.h>
43 #include <linux/unistd.h>
45 static bool initialized
;
47 static void mysyslog(int err
, const char *format
, ...)
51 va_start(args
, format
);
52 openlog("PAM-CGFS", LOG_CONS
|LOG_PID
, LOG_AUTH
);
53 vsyslog(err
, format
, args
);
58 static char *must_strcat(const char *first
, ...) __attribute__((sentinel
));
60 static char *must_strcat(const char *first
, ...)
63 char *dest
, *cur
, *new;
71 va_start(args
, first
);
73 while ((cur
= va_arg(args
, char *)) != NULL
) {
74 size_t newlen
= len
+ strlen(cur
);
76 new = realloc(dest
, newlen
+ 1);
87 static bool exists(const char *path
)
92 ret
= stat(path
, &sb
);
96 static bool is_dir(const char *path
)
100 if (stat(path
, &sb
) < 0)
102 if (S_ISDIR(sb
.st_mode
))
107 static bool mkdir_p(const char *root
, char *path
)
111 if (strlen(path
) < strlen(root
))
113 if (strlen(path
) == strlen(root
))
116 b
= path
+ strlen(root
) + 1;
118 while (*b
&& *b
== '/')
123 while (*e
&& *e
!= '/')
130 if (mkdir(path
, 0755) < 0) {
132 fprintf(stderr
, "Failed to create %s: %m\n", path
);
146 struct controller
*next
;
148 bool systemd_created
;
155 #define MAXCONTROLLERS 20
156 static struct controller
*controllers
[MAXCONTROLLERS
];
159 * if cpu and cpuacct are comounted, it's possible a mount
160 * exists for only one. Find it.
162 static char *find_controller_path(struct controller
*c
)
165 char *path
= must_strcat("/sys/fs/cgroup/", c
->name
, NULL
);
169 if (strncmp(c
->name
, "name=", 5) == 0) {
170 path
= must_strcat("/sys/fs/cgroup/", c
->name
+ 5, NULL
);
180 /* Find the path at which each controller is mounted. */
181 static void get_mounted_paths(void)
184 struct controller
*c
;
187 for (i
= 0; i
< MAXCONTROLLERS
; i
++) {
189 if (!c
|| c
->mount_path
)
191 path
= find_controller_path(c
);
195 c
->mount_path
= path
;
201 static void add_controller(int id
, char *tok
, char *cur_path
)
203 struct controller
*c
;
206 c
= malloc(sizeof(struct controller
));
209 c
->name
= strdup(tok
);
212 c
->cur_path
= strdup(cur_path
);
213 } while (!c
->cur_path
);
215 c
->next
= controllers
[id
];
216 c
->mount_path
= NULL
;
221 static void drop_controller(int which
)
223 struct controller
*c
= controllers
[which
];
226 free(c
->init_path
); // all comounts share this
230 struct controller
*tmp
= c
->next
;
236 controllers
[which
] = NULL
;
239 static bool single_in_filter(char *c
, const char *filter
)
241 char *dup
= strdupa(filter
), *tok
;
242 for (tok
= strtok(dup
, ","); tok
; tok
= strtok(NULL
, ",")) {
243 if (strcmp(c
, tok
) == 0)
249 static bool controller_in_filter(struct controller
*controller
, const char *filter
)
251 struct controller
*c
;
253 for (c
= controller
; c
; c
= c
->next
) {
254 if (single_in_filter(c
->name
, filter
))
261 * Passed a comma-delimited list of requested controllers.
262 * Pulls any controllers not in the list out of the
263 * list of controllers
265 static void filter_controllers(const char *filter
)
268 for (i
= 0; i
< MAXCONTROLLERS
; i
++) {
271 if (filter
&& !controller_in_filter(controllers
[i
], filter
))
276 #define INIT_SCOPE "/init.scope"
277 static void prune_init_scope(char *cg
)
280 size_t cg_len
, initscope_len
;
286 initscope_len
= strlen(INIT_SCOPE
);
287 if (cg_len
< initscope_len
)
290 point
= cg
+ cg_len
- initscope_len
;
291 if (strcmp(point
, INIT_SCOPE
) == 0) {
299 static bool fill_in_init_paths(void)
304 struct controller
*c
;
307 f
= fopen("/proc/1/cgroup", "r");
310 while (getline(&line
, &len
, f
) != -1) {
312 char *subsystems
, *ip
;
313 if (sscanf(line
, "%d:%m[^:]:%ms", &id
, &subsystems
, &ip
) != 3) {
314 mysyslog(LOG_ERR
, "Corrupt /proc/1/cgroup\n");
318 if (id
< 0 || id
> 20) {
319 mysyslog(LOG_ERR
, "Too many subsystems\n");
325 mysyslog(LOG_ERR
, "ERROR: init cgroup path is not absolute!\n");
328 prune_init_scope(ip
);
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;
343 static void print_found_controllers(void) {
344 struct controller
*c
;
347 for (i
= 0; i
< MAXCONTROLLERS
; i
++) {
350 fprintf(stderr
, "Nothing in controller %d\n", i
);
353 fprintf(stderr
, "Controller %d:\n", i
);
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
);
358 fprintf(stderr
, " login task path %s\n", c
->cur_path
);
364 static inline void print_found_controllers(void) { };
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.
371 static bool get_active_controllers(void)
374 char *line
= NULL
, *tok
, *cur_path
;
377 f
= fopen("/proc/self/cgroup", "r");
380 while (getline(&line
, &len
, f
) != -1) {
383 if (sscanf(line
, "%d:%m[^:]:%ms", &id
, &subsystems
, &cur_path
) != 3) {
384 mysyslog(LOG_ERR
, "Corrupt /proc/self/cgroup\n");
389 if (id
< 0 || id
> 20) {
390 mysyslog(LOG_ERR
, "Too many subsystems\n");
397 for (tok
= strtok(subsystems
, ","); tok
; tok
= strtok(NULL
, ","))
398 add_controller(id
, tok
, cur_path
);
407 if (!fill_in_init_paths()) {
408 mysyslog(LOG_ERR
, "Failed finding cgroups for init task\n");
412 print_found_controllers();
420 * the systemd-created path is: user-$uid.slice/session-c$session.scope
421 * If that is not the end of our systemd path, then we're not part of
422 * the PAM call that created that path.
424 * The last piece is chowned to $uid, the user- part not.
425 * Note - if the user creates paths that look like what we're looking for
426 * to 'fool' us, either
427 * . they fool us, we create new cgroups, and they get auto-logged-out.
428 * . they fool a root sudo, systemd cgroup is not changed but chowned,
429 * and they lose ownership of their cgroups
431 static bool systemd_created_slice_for_us(struct controller
*c
, const char *in
, uid_t uid
)
433 char *p
, *copy
= strdupa(in
);
437 if (!copy
|| strlen(copy
) < strlen("/user-0.slice/session-0.scope"))
439 p
= copy
+ strlen(copy
) - 1;
440 /* skip any trailing '/' (shouldn't be any, but be sure) */
441 while (p
>= copy
&& *p
== '/')
446 /* Get last path element */
447 while (p
>= copy
&& *p
!= '/')
451 /* make sure it is session-something.scope */
453 if (strncmp(p
+1, "session-", strlen("session-")) != 0 ||
454 strncmp(p
+1 + len
- 6, ".scope", 6) != 0)
457 /* ok last path piece checks out, now check the second to last */
459 while (p
>= copy
&& *(--p
) != '/');
460 if (sscanf(p
+1, "user-%d.slice/", &id
) != 1)
469 * Handle systemd creation. Return true if all's done. Returns false if
470 * the caller needs to create=chown a cgroup
472 static bool handle_systemd_create(struct controller
*c
, uid_t uid
, gid_t gid
)
476 if (!c
->systemd_created
)
479 user_path
= must_strcat(c
->mount_path
, c
->cur_path
, NULL
);
481 // Is this actually our cgroup, or was it created for someone
483 if (!systemd_created_slice_for_us(c
, user_path
, uid
)) {
484 c
->systemd_created
= false;
489 // a name=systemd cgroup has already been created, just chown it
490 if (chown(user_path
, uid
, gid
) < 0)
491 mysyslog(LOG_WARNING
, "Failed to chown %s to %d:%d: %m\n",
492 user_path
, (int)uid
, (int)gid
);
497 static bool cgfs_create_forone(struct controller
*c
, uid_t uid
, gid_t gid
, const char *cg
, bool *existed
)
500 if (!c
->mount_path
|| !c
->init_path
)
503 if (strcmp(c
->name
, "name=systemd") == 0 && handle_systemd_create(c
, uid
, gid
))
506 char *path
= must_strcat(c
->mount_path
, c
->init_path
, cg
, NULL
);
508 fprintf(stderr
, "Creating %s for %s\n", path
, c
->name
);
514 fprintf(stderr
, "%s existed\n", path
);
519 bool pass
= mkdir_p(c
->mount_path
, path
);
521 fprintf(stderr
, "Creating %s %s\n", path
, pass
? "succeeded" : "failed");
524 if (chown(path
, uid
, gid
) < 0)
525 mysyslog(LOG_WARNING
, "Failed to chown %s to %d:%d: %m\n",
526 path
, (int)uid
, (int)gid
);
537 static void recursive_rmdir(const char *path
)
539 struct dirent
*direntp
;
545 while ((direntp
= readdir(dir
))!= NULL
) {
546 if (!strcmp(direntp
->d_name
, ".") ||
547 !strcmp(direntp
->d_name
, ".."))
550 char *dpath
= must_strcat(path
, "/", direntp
->d_name
, NULL
);
552 recursive_rmdir(dpath
);
554 fprintf(stderr
, "attempting to remove %s\n", dpath
);
556 if (rmdir(dpath
) < 0) {
558 fprintf(stderr
, "Failed removing %s: %m\n", dpath
);
569 * Try to remove a cgroup in a controller to cleanup during failure.
570 * All mounts of comounted controllers are the same, so we just look
571 * for the first mount which exists, try to remove the directory, and
574 static void cgfs_remove_forone(int idx
, const char *cg
)
576 struct controller
*c
= controllers
[idx
];
581 path
= must_strcat(c
->mount_path
, cg
, NULL
);
582 recursive_rmdir(path
);
589 static bool cgfs_create(const char *cg
, uid_t uid
, gid_t gid
, bool *existed
)
595 fprintf(stderr
, "creating %s\n", cg
);
597 for (i
= 0; i
< MAXCONTROLLERS
; i
++) {
598 struct controller
*c
= controllers
[i
];
603 if (!cgfs_create_forone(c
, uid
, gid
, cg
, existed
)) {
604 for (j
= 0; j
< i
; j
++)
605 cgfs_remove_forone(j
, cg
);
613 static bool write_int(char *path
, int v
)
615 FILE *f
= fopen(path
, "w");
620 if (fprintf(f
, "%d\n", v
) < 0)
627 static bool do_enter(struct controller
*c
, const char *cg
)
633 if (!c
->mount_path
|| !c
->init_path
)
635 path
= must_strcat(c
->mount_path
, c
->init_path
, cg
, "/cgroup.procs", NULL
);
638 path
= must_strcat(c
->mount_path
, c
->init_path
, cg
, "/tasks", NULL
);
641 fprintf(stderr
, "Attempting to enter %s:%s using %s\n", c
->name
, cg
, path
);
643 pass
= write_int(path
, (int)getpid());
645 if (pass
) /* only have to enter one of the comounts */
649 fprintf(stderr
, "Failed to enter %s:%s\n", c
->name
, cg
);
658 static bool cgfs_enter(const char *cg
, bool skip_systemd
)
662 for (i
= 0; i
< MAXCONTROLLERS
; i
++) {
663 struct controller
*c
= controllers
[i
];
668 if (strcmp(c
->name
, "name=systemd") == 0) {
671 if (c
->systemd_created
)
675 if (!do_enter(c
, cg
))
682 static void cgfs_escape(void)
684 if (!cgfs_enter("/", true)) {
685 mysyslog(LOG_WARNING
, "Failed to escape to init's cgroup\n");
689 static bool get_uid_gid(const char *user
, uid_t
*uid
, gid_t
*gid
)
691 struct passwd
*pwent
;
693 pwent
= getpwnam(user
);
696 *uid
= pwent
->pw_uid
;
697 *gid
= pwent
->pw_gid
;
703 static int handle_login(const char *user
)
711 if (!get_uid_gid(user
, &uid
, &gid
)) {
712 mysyslog(LOG_ERR
, "Failed to get uid and gid for %s\n", user
);
713 return PAM_SESSION_ERR
;
719 ret
= snprintf(cg
, MAXPATHLEN
, "/user/%s/%d", user
, idx
);
720 if (ret
< 0 || ret
>= MAXPATHLEN
) {
721 mysyslog(LOG_ERR
, "username too long\n");
722 return PAM_SESSION_ERR
;
725 if (!cgfs_create(cg
, uid
, gid
, &existed
)) {
726 mysyslog(LOG_ERR
, "Failed to create a cgroup for user %s\n", user
);
727 return PAM_SESSION_ERR
;
735 if (!cgfs_enter(cg
, false)) {
736 mysyslog(LOG_ERR
, "Failed to enter user cgroup %s for user %s\n", cg
, user
);
737 return PAM_SESSION_ERR
;
745 int pam_sm_open_session(pam_handle_t
*pamh
, int flags
, int argc
,
748 const char *PAM_user
= NULL
;
751 if (!get_active_controllers()) {
752 mysyslog(LOG_ERR
, "Failed to get list of controllers\n");
753 return PAM_SESSION_ERR
;
756 if (argc
> 1 && strcmp(argv
[0], "-c") == 0)
757 filter_controllers(argv
[1]);
759 ret
= pam_get_user(pamh
, &PAM_user
, NULL
);
760 if (ret
!= PAM_SUCCESS
) {
761 mysyslog(LOG_ERR
, "PAM-CGFS: couldn't get user\n");
762 return PAM_SESSION_ERR
;
765 ret
= handle_login(PAM_user
);
769 static void prune_empty_cgroups(struct controller
*c
, const char *user
)
772 if (!c
->mount_path
|| !c
->init_path
)
774 char *path
= must_strcat(c
->mount_path
, c
->init_path
, "user/", user
, NULL
);
776 fprintf(stderr
, "Pruning %s\n", path
);
778 recursive_rmdir(path
);
786 * Since we can't rely on kernel's autoremove, remove stale cgroups
787 * any time the user logs out.
789 static void prune_user_cgs(const char *user
)
793 for (i
= 0; i
< MAXCONTROLLERS
; i
++)
794 prune_empty_cgroups(controllers
[i
], user
);
797 int pam_sm_close_session(pam_handle_t
*pamh
, int flags
, int argc
,
800 const char *PAM_user
= NULL
;
801 int ret
= pam_get_user(pamh
, &PAM_user
, NULL
);
803 if (ret
!= PAM_SUCCESS
) {
804 mysyslog(LOG_ERR
, "PAM-CGFS: couldn't get user\n");
805 return PAM_SESSION_ERR
;
809 get_active_controllers();
810 if (argc
> 1 && strcmp(argv
[0], "-c") == 0)
811 filter_controllers(argv
[1]);
814 prune_user_cgs(PAM_user
);