]> git.proxmox.com Git - mirror_lxcfs.git/commitdiff
add pam module
authorSerge Hallyn <serge.hallyn@ubuntu.com>
Wed, 20 Jan 2016 06:04:54 +0000 (22:04 -0800)
committerSerge Hallyn <serge.hallyn@ubuntu.com>
Thu, 21 Jan 2016 21:22:39 +0000 (13:22 -0800)
Signed-off-by: Serge Hallyn <serge.hallyn@ubuntu.com>
.gitignore
Makefile.am
configure.ac
pam/pam_cgfs.c [new file with mode: 0644]

index e7d22ea26992dc94ab17264f12202ccaf774dd61..435dd951caad8fbf3f112040375512753e383a08 100644 (file)
@@ -28,3 +28,9 @@ tests/cpusetrange
 tests/test-read
 lxcfs_mkdir
 *.o
+pam/.dirstamp
+pam/pam_cgfs_la-pam_cgfs.lo
+pam_cgfs.la
+tags
+lxcfs-*.tar.gz
+.libs
index cc19ce7c9826f8a56c31b28b3d25ff7abecf60b7..4ceaffaebc321944f8333a5032d5ec1d2836b892 100644 (file)
@@ -34,6 +34,21 @@ TEST_CPUSET: tests/cpusetrange.c cpuset.c
 TEST_SYSCALLS: tests/test_syscalls.c
        $(CC) -o tests/test_syscalls tests/test_syscalls.c
 
+pam_LTLIBRARIES = pam_cgfs.la
+pam_cgfs_la_SOURCES = pam/pam_cgfs.c
+pam_cgfs_la_CFLAGS = $(AM_CFLAGS)
+pam_cgfs_la_LIBADD = $(AM_LIBS) $(PAM_LIBS) -L$(top_srcdir)
+pam_cgfs_la_LDFLAGS = $(AM_LDFLAGS) -module -avoid-version -shared
+# temporary
+pam_cgfs_la_CFLAGS += -DDEBUG
+
+install-data-hook:     install-pamLTLIBRARIES
+       rm -f "$(DESTDIR)$(pamdir)/pam_cgfs.la"
+       rm -f "$(DESTDIR)$(pamdir)/pam_cgfs.a"
+uninstall-local:
+       rm -f "$(DESTDIR)$(pamdir)/pam_cgfs.so"
+
+
 tests: TEST_READ TEST_CPUSET TEST_SYSCALLS
 
 distclean:
index d9df86ac098405ad67fa3d562b1c21236a63e34b..47f77a284aadbe2d74cdf946974af021520f157c 100644 (file)
@@ -5,6 +5,8 @@ AC_INIT([lxcfs], [0.16], [lxc-devel@lists.linuxcontainers.org])
 AC_SUBST(ACLOCAL_AMFLAGS, "-I m4")
 AC_CONFIG_MACRO_DIR([m4])
 
+AM_INIT_AUTOMAKE([subdir-objects])
+
 AC_GNU_SOURCE
 AC_CONFIG_HEADERS([config.h])
 AC_CONFIG_FILES([
@@ -40,4 +42,33 @@ AS_AC_EXPAND(LXCFSSHAREDIR, "$datarootdir/lxcfs")
 AS_AC_EXPAND(LXCCONFDIR, "$datarootdir/lxc/config/common.conf.d")
 AS_AC_EXPAND(LXCFSTARGETDIR, "$localstatedir/lib/lxcfs")
 
+AC_ARG_WITH(
+       [pamdir],
+       [AS_HELP_STRING([--with-pamdir=PATH],[Specify the directory where PAM modules are stored,
+                                               or "none" if PAM modules are not to be built])],
+       [pamdir="${withval}"],
+       [
+               if test "${prefix}" = "/usr"; then
+                       pamdir="/lib${libdir##*/lib}/security"
+               else
+                       pamdir="\$(libdir)/security"
+               fi
+       ]
+)
+
+AM_CONDITIONAL([HAVE_PAM], [test x"$pamdir" != "xnone"])
+if test "z$pamdir" != "znone"; then
+       AC_ARG_VAR([PAM_CFLAGS], [C compiler flags for pam])
+       AC_ARG_VAR([PAM_LIBS], [linker flags for pam])
+       AC_CHECK_LIB(
+               [pam],
+               [pam_authenticate],
+               [PAM_LIBS="-lpam"],
+               [AC_MSG_ERROR([*** libpam not found.])
+               ])
+
+       AC_SUBST(PAM_LIBS)
+       AC_SUBST([pamdir])
+fi
+
 AC_OUTPUT
diff --git a/pam/pam_cgfs.c b/pam/pam_cgfs.c
new file mode 100644 (file)
index 0000000..39efeaa
--- /dev/null
@@ -0,0 +1,681 @@
+/* pam-cgfs
+ *
+ * Copyright © 2016 Canonical, Inc
+ * Author: Serge Hallyn <serge.hallyn@ubuntu.com>
+ *
+ * When a user logs in, this pam module will create cgroups which
+ * the user may administer, for all controllers except name=systemd,
+ * or for any controllers listed on the command line (if any are
+ * listed).
+ *
+ * The cgroup created will be "user/$user/0" for the first session,
+ * "user/$user/1" for the second, etc.
+ *
+ * All requested cgroups must be mounted under /sys/fs/cgroup/$controller,
+ * no messing around with finding mountpoints.
+ *
+ * See COPYING file for details.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <syslog.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <sys/mount.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+#include <pwd.h>
+#include <stdbool.h>
+#include <dirent.h>
+
+#define PAM_SM_SESSION
+#include <security/_pam_macros.h>
+#include <security/pam_modules.h>
+
+#include <linux/unistd.h>
+
+static bool initialized;
+
+static void mysyslog(int err, const char *format, ...)
+{
+       va_list args;
+
+       va_start(args, format);
+       openlog("PAM-CGFS", LOG_CONS|LOG_PID, LOG_AUTH);
+       vsyslog(err, format, args);
+       va_end(args);
+       closelog();
+}
+
+static char *must_strcat(const char *first, ...) __attribute__((sentinel));
+
+static char *must_strcat(const char *first, ...)
+{
+       va_list args;
+       char *dest, *cur, *new;
+       size_t len;
+
+       do {
+               dest = strdup(first);
+       } while (!dest);
+       len = strlen(dest);
+
+       va_start(args, first);
+
+       while ((cur = va_arg(args, char *)) != NULL) {
+               size_t newlen = len + strlen(cur);
+               do {
+                       new = realloc(dest, newlen + 1);
+               } while (!new);
+               dest = new;
+               strcat(dest, cur);
+               len = newlen;
+       }
+       va_end(args);
+
+       return dest;
+}
+
+static bool exists(const char *path)
+{
+       struct stat sb;
+       int ret;
+
+       ret = stat(path, &sb);
+       return ret == 0;
+}
+
+static bool is_dir(const char *path)
+{
+       struct stat sb;
+
+       if (stat(path, &sb) < 0)
+               return false;
+       if (S_ISDIR(sb.st_mode))
+               return true;
+       return false;
+}
+
+static bool mkdir_p(const char *root, char *path)
+{
+       char *b, orig, *e;
+
+       if (strlen(path) < strlen(root))
+               return false;
+       if (strlen(path) == strlen(root))
+               return true;
+
+       b = path + strlen(root) + 1;
+       while (1) {
+               while (*b && *b == '/')
+                       b++;
+               if (!*b)
+                       return true;
+               e = b + 1;
+               while (*e && *e != '/')
+                       e++;
+               orig = *e;
+               if (orig)
+                       *e = '\0';
+               if (exists(path))
+                       goto next;
+               if (mkdir(path, 0755) < 0) {
+#if DEBUG
+                       fprintf(stderr, "Failed to create %s: %m\n", path);
+#endif
+                       return false;
+               }
+next:
+               if (!orig)
+                       return true;
+               *e = orig;
+               b = e + 1;
+       }
+       
+}
+
+struct controller {
+       struct controller *next;
+       int id;
+       char *name;
+       char *mount_path;
+       char *init_path;
+};
+
+#define MAXCONTROLLERS 20
+static struct controller *controllers[MAXCONTROLLERS];
+
+/* Find the path at which each controller is mounted. */
+static void get_mounted_paths(void)
+{
+       int i;
+       struct controller *c;
+       char *path;
+
+       for (i = 0; i < MAXCONTROLLERS; i++) {
+               c = controllers[i];
+               if (!c || c->mount_path)
+                       continue;
+               path = must_strcat("/sys/fs/cgroup/", c->name, NULL);
+               if (!exists(path)) {
+                       free(path);
+                       continue;
+               }
+               while (c) {
+                       c->mount_path = path;
+                       c = c->next;
+               }
+       }
+}
+
+static bool add_controller(int id, char *tok)
+{
+       struct controller *c;
+       
+       if ((c = malloc(sizeof(struct controller))) == NULL)
+               return false;
+       c->id = id;
+       if ((c->name = strdup(tok)) == NULL)
+               return false;
+       c->next = controllers[id];
+       c->mount_path = NULL;
+       c->init_path = NULL;
+       controllers[id] = c;
+       return true;
+}
+
+static void drop_controller(int which)
+{
+       struct controller *c = controllers[which];
+
+       if (c) {
+               free(c->init_path); // all comounts share this
+               free(c->mount_path);
+       }
+       while (c) {
+               struct controller *tmp = c->next;
+               free(c->name);
+               free(c);
+               c = tmp;
+       }
+       controllers[which] = NULL;
+}
+
+static bool single_in_filter(char *c, const char *filter)
+{
+       char *dup = strdupa(filter), *tok;
+       for (tok = strtok(dup, ","); tok; tok = strtok(NULL, ",")) {
+               if (strcmp(c, tok) == 0)
+                       return true;
+       }
+       return false;
+}
+
+static bool controller_in_filter(struct controller *controller, const char *filter)
+{
+       struct controller *c;
+
+       for (c = controller; c; c = c->next) {
+               if (single_in_filter(c->name, filter))
+                       return true;
+       }
+       return false;
+}
+
+/*
+ * Passed a comma-delimited list of requested controllers.
+ * Pulls any controllers not in the list out of the
+ * list of controllers
+ */
+static void filter_controllers(const char *filter)
+{
+       int i;
+       for (i = 0; i < MAXCONTROLLERS; i++) {
+               if (!controllers[i])
+                       continue;
+               if (filter && !controller_in_filter(controllers[i], filter))
+                       drop_controller(i);
+       }
+}
+
+#define INIT_SCOPE "/init.scope"
+static void prune_init_scope(char *cg)
+{
+       char *point;
+
+       if (!cg)
+               return;
+
+       point = cg + strlen(cg) - strlen(INIT_SCOPE);
+       if (point < cg)
+               return;
+       if (strcmp(point, INIT_SCOPE) == 0) {
+               if (point == cg)
+                       *(point+1) = '\0';
+               else
+                       *point = '\0';
+       }
+}
+
+static bool fill_in_init_paths(void)
+{
+       FILE *f;
+       char *line = NULL;
+       size_t len = 0;
+       struct controller *c;
+
+       f = fopen("/proc/1/cgroup", "r");
+       if (!f)
+               return false;
+       while (getline(&line, &len, f) != -1) {
+               int id;
+               char *subsystems, *ip;
+               if (sscanf(line, "%d:%m[^:]:%ms", &id, &subsystems, &ip) != 3) {
+                       mysyslog(LOG_ERR, "Corrupt /proc/1/cgroup\n");
+                       fclose(f);
+                       return false;
+               }
+               free(subsystems);
+               if (id < 0 || id > 20) {
+                       mysyslog(LOG_ERR, "Too many subsystems\n");
+                       free(ip);
+                       fclose(f);
+                       return false;
+               }
+               if (ip[0] != '/') {
+                       free(ip);
+                       mysyslog(LOG_ERR, "ERROR: init cgroup path is not absolute!\n");
+                       return false;
+               }
+               prune_init_scope(ip);
+               for (c = controllers[id]; c; c = c->next)
+                       c->init_path = ip;
+       }
+       fclose(f);
+       return true;
+}
+
+#if DEBUG
+static void print_found_controllers(void) {
+       struct controller *c;
+       int i;
+
+       for (i = 0; i < MAXCONTROLLERS; i++) {
+               c = controllers[i];
+               if (!c) {
+                       fprintf(stderr, "Nothing in controller %d\n", i);
+                       continue;
+               }
+               fprintf(stderr, "Controller %d:\n", i);
+               while (c) {
+                       fprintf(stderr, " Next mount: index %d name %s\n", c->id, c->name);
+                       fprintf(stderr, "             mount path %s\n", c->mount_path ? c->mount_path : "(none)");
+                       fprintf(stderr, "             init task path %s\n", c->init_path);
+                       c = c->next;
+               }
+       }
+}
+#else
+static inline void print_found_controllers(void) { };
+#endif
+/*
+ * Get the list of cgroup controllers currently mounted.
+ * This includes both kernel and named subsystems, so get the list from
+ * /proc/self/cgroup rather than /proc/cgroups.
+ */
+static bool get_active_controllers(void)
+{
+       FILE *f;
+       char *line = NULL, *tok;
+       size_t len = 0;
+
+       f = fopen("/proc/self/cgroup", "r");
+       if (!f)
+               return false;
+       while (getline(&line, &len, f) != -1) {
+               int id;
+               char *subsystems;
+               if (sscanf(line, "%d:%m[^:]:", &id, &subsystems) != 2) {
+                       mysyslog(LOG_ERR, "Corrupt /proc/self/cgroup\n");
+                       fclose(f);
+                       return false;
+               }
+               if (id < 0 || id > 20) {
+                       mysyslog(LOG_ERR, "Too many subsystems\n");
+                       free(subsystems);
+                       fclose(f);
+                       return false;
+               }
+               if (strcmp(subsystems, "name=systemd") == 0)
+                       goto next;
+               for (tok = strtok(subsystems, ","); tok; tok = strtok(NULL, ","))
+                       add_controller(id, tok);
+next:
+               free(subsystems);
+       }
+       fclose(f);
+
+       get_mounted_paths();
+
+       if (!fill_in_init_paths()) {
+               mysyslog(LOG_ERR, "Failed finding cgroups for init task\n");
+               return false;
+       }
+
+       print_found_controllers();
+
+       initialized = true;
+
+       return true;
+}
+
+static bool cgfs_create_forone(const struct controller *c, const char *cg, bool *existed)
+{
+       while (c) {
+               if (!c->mount_path || !c->init_path)
+                       goto next;
+               char *path = must_strcat(c->mount_path, c->init_path, cg, NULL);
+#if DEBUG
+               fprintf(stderr, "Creating %s for %s\n", path, c->name);
+#endif
+               if (exists(path)) {
+                       free(path);
+                       *existed = true;
+#if DEBUG
+               fprintf(stderr, "%s existed\n", path);
+#endif
+                       return true;
+               }
+               bool pass = mkdir_p(c->mount_path, path);
+#if DEBUG
+               fprintf(stderr, "Creating %s %s\n", path, pass ? "succeeded" : "failed");
+#endif
+               free(path);
+               if (pass)
+                       return true;
+next:
+               c = c->next;
+       }
+       return false;
+}
+
+static void recursive_rmdir(const char *path)
+{
+       struct dirent *direntp;
+       DIR *dir;
+
+       dir = opendir(path);
+       if (!dir)
+               return;
+       while ((direntp = readdir(dir))!= NULL) {
+               if (!strcmp(direntp->d_name, ".") ||
+                               !strcmp(direntp->d_name, ".."))
+                       continue;
+
+               char *dpath = must_strcat(path, "/", direntp->d_name, NULL);
+               if (is_dir(dpath)) {
+                       recursive_rmdir(dpath);
+#if DEBUG
+                       fprintf(stderr, "attempting to remove %s\n", dpath);
+#endif
+                       if (rmdir(dpath) < 0) {
+#if DEBUG
+                               fprintf(stderr, "Failed removing %s: %m\n", dpath);
+#endif
+                       }
+               }
+               free(dpath);
+       }
+
+       closedir(dir);
+}
+
+/*
+ * Try to remove a cgroup in a controller to cleanup during failure.
+ * All mounts of comounted controllers are the same, so we just look
+ * for the first mount which exists, try to remove the directory, and
+ * return.
+ */
+static void cgfs_remove_forone(int idx, const char *cg)
+{
+       struct controller *c = controllers[idx];
+       char *path;
+
+       while (c) {
+               if (c->mount_path) {
+                       path = must_strcat(c->mount_path, cg, NULL);
+                       recursive_rmdir(path);
+                       free(path);
+               }
+               c = c->next;
+       }
+}
+
+static bool cgfs_create(const char *cg, bool *existed)
+{
+       *existed = false;
+       int i, j;
+
+#if DEBUG
+       fprintf(stderr, "creating %s\n", cg);
+#endif
+       for (i = 0; i < MAXCONTROLLERS; i++) {
+               struct controller *c = controllers[i];
+
+               if (!c)
+                       continue;
+
+               if (!cgfs_create_forone(c, cg, existed)) {
+                       for (j = 0; j < i; j++)
+                               cgfs_remove_forone(j, cg);
+                       return false;
+               }
+       }
+
+       return true;
+}
+
+static bool cgfs_chown(const char *cg, uid_t uid, gid_t gid)
+{
+       /* TODO */
+       return true;
+}
+
+static bool write_int(char *path, int v)
+{
+       FILE *f = fopen(path, "w");
+       if (!f)
+               return false;
+       fprintf(f, "%d\n", v);
+       fclose(f);
+       return true;
+}
+
+static bool do_enter(struct controller *c, const char *cg)
+{
+       char *path;
+       bool pass;
+
+       while (c) {
+               if (!c->mount_path || !c->init_path)
+                       continue;
+               path = must_strcat(c->mount_path, c->init_path, cg, "/cgroup.procs", NULL);
+               if (!exists(path)) {
+                       free(path);
+                       path = must_strcat(c->mount_path, c->init_path, cg, "/tasks", NULL);
+               }
+#if DEBUG
+               fprintf(stderr, "Attempting to enter %s:%s using %s\n", c->name, cg, path);
+#endif
+               pass = write_int(path, (int)getpid());
+               free(path);
+               if (pass) /* only have to enter one of the comounts */
+                       return true;
+#if DEBUG
+               if (!pass)
+                       fprintf(stderr, "Failed to enter %s:%s\n", c->name, cg);
+#endif
+               c = c->next;
+       }
+
+       return false;
+}
+
+static bool cgfs_enter(const char *cg)
+{
+       int i;
+
+       for (i = 0; i < MAXCONTROLLERS; i++) {
+               struct controller *c = controllers[i];
+
+               if (!c)
+                       continue;
+
+               if (!do_enter(c, cg))
+                       return false;
+       }
+
+       return true;
+}
+
+static void cgfs_escape(void)
+{
+       if (!cgfs_enter("/")) {
+               mysyslog(LOG_WARNING, "Failed to escape to init's cgroup\n");
+       }
+}
+
+static bool get_uid_gid(const char *user, uid_t *uid, gid_t *gid)
+{
+       struct passwd *pwent;
+
+       pwent = getpwnam(user);
+       if (!pwent)
+               return false;
+       *uid = pwent->pw_uid;
+       *gid = pwent->pw_gid;
+
+       return true;
+}
+
+#define DIRNAMSZ 200
+static int handle_login(const char *user)
+{
+       int idx = 0, ret;
+       bool existed;
+       uid_t uid = 0;
+       gid_t gid = 0;
+       char cg[MAXPATHLEN];
+       
+       if (!get_uid_gid(user, &uid, &gid)) {
+               mysyslog(LOG_ERR, "Failed to get uid and gid for %s\n", user);
+               return PAM_SESSION_ERR;
+       }
+
+       cgfs_escape();
+
+       while (idx >= 0) {
+               ret = snprintf(cg, MAXPATHLEN, "/user/%s/%d", user, idx);
+               if (ret < 0 || ret >= MAXPATHLEN) {
+                       mysyslog(LOG_ERR, "username too long\n");
+                       return PAM_SESSION_ERR;
+               }
+
+               if (!cgfs_create(cg, &existed)) {
+                       mysyslog(LOG_ERR, "Failed to create a cgroup for user %s\n", user);
+                       return PAM_SESSION_ERR;
+               }
+
+               if (existed == 1) {
+                       idx++;
+                       continue;
+               }
+
+               if (!cgfs_chown(cg, uid, gid)) {
+                       mysyslog(LOG_ERR, "Warning: failed to chown %s for user %s\n", cg, user);
+               }
+
+               if (!cgfs_enter(cg)) {
+                       mysyslog(LOG_ERR, "Failed to enter user cgroup %s for user %s\n", cg, user);
+                       return PAM_SESSION_ERR;
+               }
+               break;
+       }
+
+       return PAM_SUCCESS;
+}
+
+int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc,
+               const char **argv)
+{
+       const char *PAM_user = NULL;
+       int ret;
+
+       if (!get_active_controllers()) {
+               mysyslog(LOG_ERR, "Failed to get list of controllers\n");
+               return PAM_SESSION_ERR;
+       }
+
+       if (argc > 1 && strcmp(argv[0], "-c") == 0)
+               filter_controllers(argv[1]);
+
+       ret = pam_get_user(pamh, &PAM_user, NULL);
+       if (ret != PAM_SUCCESS) {
+               mysyslog(LOG_ERR, "PAM-CGFS: couldn't get user\n");
+               return PAM_SESSION_ERR;
+       }
+
+       ret = handle_login(PAM_user);
+       return ret;
+}
+
+static void prune_empty_cgroups(struct controller *c, const char *user)
+{
+       while (c) {
+               if (!c->mount_path || !c->init_path)
+                       goto next;
+               char *path = must_strcat(c->mount_path, c->init_path, "user/", user, NULL);
+#if DEBUG
+       fprintf(stderr, "Pruning %s\n", path);
+#endif
+               recursive_rmdir(path);
+next:
+               c = c->next;
+       }
+}
+
+/*
+ * Since we can't rely on kernel's autoremove, remove stale cgroups
+ * any time the user logs out.
+ */
+static void prune_user_cgs(const char *user)
+{
+       int i;
+
+       for (i = 0; i < MAXCONTROLLERS; i++)
+               prune_empty_cgroups(controllers[i], user);
+}
+
+int pam_sm_close_session(pam_handle_t *pamh, int flags, int argc,
+               const char **argv)
+{
+       const char *PAM_user = NULL;
+       int ret = pam_get_user(pamh, &PAM_user, NULL);
+
+       if (ret != PAM_SUCCESS) {
+               mysyslog(LOG_ERR, "PAM-CGFS: couldn't get user\n");
+               return PAM_SESSION_ERR;
+       }
+
+       if (!initialized) {
+               get_active_controllers();
+               if (argc > 1 && strcmp(argv[0], "-c") == 0)
+                       filter_controllers(argv[1]);
+       }
+
+       prune_user_cgs(PAM_user);
+       return PAM_SUCCESS;
+}