-/* pam-cgfs
- *
- * Copyright © 2016 Canonical, Inc
- * Author: Serge Hallyn <serge.hallyn@ubuntu.com>
- * Author: Christian Brauner <christian.brauner@ubuntu.com>
- *
- * When a user logs in, this pam module will create cgroups which the user may
- * administer. It handles both pure cgroupfs v1 and pure cgroupfs v2, as well as
- * mixed mounts, where some controllers are mounted in a standard cgroupfs v1
- * hierarchy location (/sys/fs/cgroup/<controller>) and others are in the
- * cgroupfs v2 hierarchy.
- * Writeable cgroups are either created for all controllers or, if specified,
- * for any controllers listed on the command line.
- * The cgroup created will be "user/$user/0" for the first session,
- * "user/$user/1" for the second, etc.
- *
- * Systems with a systemd init system are treated specially, both with respect
- * to cgroupfs v1 and cgroupfs v2. For both, cgroupfs v1 and cgroupfs v2, We
- * check whether systemd already placed us in a cgroup it created:
- *
- * user.slice/user-uid.slice/session-n.scope
- *
- * by checking whether uid == our uid. If it did, we simply chown the last
- * part (session-n.scope). If it did not we create a cgroup as outlined above
- * (user/$user/n) and chown it to our uid.
- * The same holds for cgroupfs v2 where this assumptions becomes crucial:
- * We __have to__ be placed in our under the cgroup systemd created for us on
- * login, otherwise things like starting an xserver or similar will not work.
- *
- * All requested cgroups must be mounted under /sys/fs/cgroup/$controller,
- * no messing around with finding mountpoints.
- *
- * See COPYING file for details.
- */
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "config.h"
#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
+#include <linux/unistd.h>
#include <pwd.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include <syslog.h>
-#include <unistd.h>
-#include <linux/unistd.h>
#include <sys/mount.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/vfs.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include "file_utils.h"
+#include "macro.h"
+#include "memory_utils.h"
+#include "string_utils.h"
#define PAM_SM_SESSION
#include <security/_pam_macros.h>
#include <security/pam_modules.h>
-#include "utils.h"
-
-#ifndef HAVE_STRLCPY
-#include "include/strlcpy.h"
+#if !HAVE_STRLCPY
+#include "strlcpy.h"
#endif
-#ifndef HAVE_STRLCAT
-#include "include/strlcat.h"
+#if !HAVE_STRLCAT
+#include "strlcat.h"
#endif
-#define pam_cgfs_debug_stream(stream, format, ...) \
- do { \
- fprintf(stream, "%s: %d: %s: " format, __FILE__, __LINE__, \
- __func__, __VA_ARGS__); \
+#define pam_cgfs_debug_stream(stream, format, ...) \
+ do { \
+ fprintf(stream, "%s: %d: %s: " format, __FILE__, __LINE__, \
+ __func__, __VA_ARGS__); \
} while (false)
#define pam_cgfs_error(format, ...) pam_cgfs_debug_stream(stderr, format, __VA_ARGS__)
#ifdef DEBUG
#define pam_cgfs_debug(format, ...) pam_cgfs_error(format, __VA_ARGS__)
#else
-#define pam_cgfs_debug(format, ...)
+#define pam_cgfs_debug(format, ...) \
+ do { \
+ } while (false)
#endif /* DEBUG */
-/* Taken over modified from the kernel sources. */
-#define NBITS 32 /* bits in uint32_t */
-#define DIV_ROUND_UP(n, d) (((n) + (d)-1) / (d))
-#define BITS_TO_LONGS(nr) DIV_ROUND_UP(nr, NBITS)
-
static enum cg_mount_mode {
CGROUP_UNKNOWN = -1,
CGROUP_MIXED = 0,
static void append_line(char **dest, size_t oldlen, char *new, size_t newlen);
static int append_null_to_list(void ***list);
static void batch_realloc(char **mem, size_t oldlen, size_t newlen);
-static inline void clear_bit(unsigned bit, uint32_t *bitarr)
-{
- bitarr[bit / NBITS] &= ~(1 << (bit % NBITS));
-}
static char *copy_to_eol(char *s);
-static void free_string_list(char **list);
static char *get_mountpoint(char *line);
static bool get_uid_gid(const char *user, uid_t *uid, gid_t *gid);
static int handle_login(const char *user, uid_t uid, gid_t gid);
-static inline bool is_set(unsigned bit, uint32_t *bitarr)
-{
- return (bitarr[bit / NBITS] & (1 << (bit % NBITS))) != 0;
-}
static bool is_lxcfs(const char *line);
static bool is_cgv1(char *line);
static bool is_cgv2(char *line);
-static void *must_alloc(size_t sz);
static void must_add_to_list(char ***clist, char *entry);
static void must_append_controller(char **klist, char **nlist, char ***clist,
char *entry);
static void must_append_string(char ***list, char *entry);
static void mysyslog(int err, const char *format, ...) __attribute__((sentinel));
static char *read_file(char *fnam);
-static int read_from_file(const char *filename, void* buf, size_t count);
static int recursive_rmdir(char *dirname);
-static inline void set_bit(unsigned bit, uint32_t *bitarr)
-{
- bitarr[bit / NBITS] |= (1 << (bit % NBITS));
-}
static bool string_in_list(char **list, const char *entry);
static char *string_join(const char *sep, const char **parts, bool use_as_prefix);
static void trim(char *s);
static bool write_int(char *path, int v);
-static ssize_t write_nointr(int fd, const void* buf, size_t count);
-static int write_to_file(const char *filename, const void *buf, size_t count,
- bool add_newline);
/* cgroupfs prototypes. */
static bool cg_belongs_to_uid_gid(const char *path, uid_t uid, gid_t gid);
}
/* Create directory and (if necessary) its parents. */
-static bool mkdir_parent(const char *root, char *path)
+static bool lxc_mkdir_parent(const char *root, char *path)
{
char *b, orig, *e;
return true;
b = path + strlen(root) + 1;
- while (true) {
+ for (;;) {
while (*b && (*b == '/'))
b++;
if (!*b)
va_list args;
va_start(args, format);
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"
openlog("PAM-CGFS", LOG_CONS | LOG_PID, LOG_AUTH);
vsyslog(err, format, args);
+#pragma GCC diagnostic pop
va_end(args);
closelog();
}
s[--len] = '\0';
}
-/* Allocate pointer; do not fail. */
-static void *must_alloc(size_t sz)
-{
- return must_realloc(NULL, sz);
-}
-
/* Make allocated copy of string. End of string is taken to be '\n'. */
static char *copy_to_eol(char *s)
{
return NULL;
len = newline - s;
- sret = must_alloc(len + 1);
+ sret = must_realloc(NULL, len + 1);
memcpy(sret, s, len);
sret[len] = '\0';
return len;
}
-/* Free null-terminated array of strings. */
-static void free_string_list(char **list)
-{
- char **it;
-
- for (it = list; it && *it; it++)
- free(*it);
- free(list);
-}
-
/* Write single integer to file. */
static bool write_int(char *path, int v)
{
/* Recursively remove directory and its parents. */
static int recursive_rmdir(char *dirname)
{
+ __do_closedir DIR *dir = NULL;
struct dirent *direntp;
- DIR *dir;
int r = 0;
dir = opendir(dirname);
r = -1;
}
- if (closedir(dir) < 0) {
- if (!r)
- pam_cgfs_debug("Failed to delete %s: %s\n", dirname, strerror(errno));
- r = -1;
- }
-
return r;
}
*p2 = '\0';
len = strlen(p);
- sret = must_alloc(len + 1);
+ sret = must_realloc(NULL, len + 1);
memcpy(sret, p, len);
sret[len] = '\0';
size_t len;
len = strlen(entry);
- s = must_alloc(len + 6);
+ s = must_realloc(NULL, len + 6);
ret = snprintf(s, len + 6, "name=%s", entry);
if (ret < 0 || (size_t)ret >= (len + 6)) {
/* Check if a cgroupfs v2 controller is present in the string @cgline. */
static bool cgv1_controller_in_clist(char *cgline, char *c)
{
+ __do_free char *tmp = NULL;
size_t len;
- char *tok, *eol, *tmp;
+ char *tok, *eol;
char *saveptr = NULL;
eol = strchr(cgline, ':');
return false;
len = eol - cgline;
- tmp = alloca(len + 1);
+ tmp = must_realloc(NULL, len + 1);
memcpy(tmp, cgline, len);
tmp[len] = '\0';
p = basecginfo;
- while (true) {
+ for (;;) {
p = strchr(p, ':');
if (!p)
return NULL;
struct cgv1_hierarchy *new;
int newentry;
- new = must_alloc(sizeof(*new));
+ new = must_realloc(NULL, sizeof(*new));
new->controllers = clist;
new->mountpoint = mountpoint;
struct cgv2_hierarchy *new;
int newentry;
- new = must_alloc(sizeof(*new));
+ new = must_realloc(NULL, sizeof(*new));
new->controllers = clist;
new->mountpoint = mountpoint;
}
/* Detect and store information about the cgroupfs v2 hierarchy. Currently only
- * deals with the empty v2 hierachy as we do not retrieve enabled controllers.
+ * deals with the empty v2 hierarchy as we do not retrieve enabled controllers.
*/
static bool cgv2_init(uid_t uid, gid_t gid)
{
struct passwd pwent;
struct passwd *pwentp = NULL;
char *buf;
- size_t bufsize;
+ ssize_t bufsize;
int ret;
bufsize = sysconf(_SC_GETPW_R_SIZE_MAX);
return cpus;
}
-static ssize_t write_nointr(int fd, const void* buf, size_t count)
-{
- ssize_t ret;
-
-again:
- ret = write(fd, buf, count);
- if (ret < 0 && errno == EINTR)
- goto again;
-
- return ret;
-}
-
-static int write_to_file(const char *filename, const void* buf, size_t count, bool add_newline)
-{
- int fd, saved_errno;
- ssize_t ret;
-
- fd = open(filename, O_WRONLY | O_TRUNC | O_CREAT | O_CLOEXEC, 0666);
- if (fd < 0)
- return -1;
-
- ret = write_nointr(fd, buf, count);
- if (ret < 0)
- goto out_error;
- if ((size_t)ret != count)
- goto out_error;
-
- if (add_newline) {
- ret = write_nointr(fd, "\n", 1);
- if (ret != 1)
- goto out_error;
- }
-
- close(fd);
- return 0;
-
-out_error:
- saved_errno = errno;
- close(fd);
- errno = saved_errno;
- return -1;
-}
-
#define __ISOL_CPUS "/sys/devices/system/cpu/isolated"
static bool cg_filter_and_set_cpus(char *path, bool am_initialized)
{
/* Get maximum number of cpus found in possible cpuset. */
maxposs = cg_get_max_cpus(posscpus);
- if (maxposs < 0)
+ if (maxposs < 0 || maxposs >= INT_MAX - 1)
goto on_error;
if (!file_exists(__ISOL_CPUS)) {
/* Get maximum number of cpus found in isolated cpuset. */
maxisol = cg_get_max_cpus(isolcpus);
- if (maxisol < 0)
+ if (maxisol < 0 || maxisol >= INT_MAX - 1)
goto on_error;
if (maxposs < maxisol)
copy_parent:
*lastslash = oldv;
- if (fpath)
- free(fpath);
+ free(fpath);
fpath = must_make_path(path, "cpuset.cpus", NULL);
- ret = write_to_file(fpath, cpulist, strlen(cpulist), false);
+ ret = lxc_write_to_file(fpath, cpulist, strlen(cpulist), false, 0660);
if (ret < 0) {
pam_cgfs_debug("Could not write cpu list to: %s\n", fpath);
goto on_error;
bret = true;
on_error:
- free(fpath);
+ *lastslash = oldv;
+ free(fpath);
free(isolcpus);
free(isolmask);
return bret;
}
-int read_from_file(const char *filename, void* buf, size_t count)
-{
- int fd = -1, saved_errno;
- ssize_t ret;
-
- fd = open(filename, O_RDONLY | O_CLOEXEC);
- if (fd < 0)
- return -1;
-
- if (!buf || !count) {
- char buf2[100];
- size_t count2 = 0;
-
- while ((ret = read(fd, buf2, 100)) > 0)
- count2 += ret;
- if (ret >= 0)
- ret = count2;
- } else {
- memset(buf, 0, count);
- ret = read(fd, buf, count);
- }
-
- if (ret < 0)
- pam_cgfs_debug("read %s: %s", filename, strerror(errno));
-
- saved_errno = errno;
- close(fd);
- errno = saved_errno;
- return ret;
-}
-
/* Copy contents of parent(@path)/@file to @path/@file */
static bool cg_copy_parent_file(char *path, char *file)
{
*lastslash = '\0';
fpath = must_make_path(path, file, NULL);
- len = read_from_file(fpath, NULL, 0);
- if (len <= 0)
+ len = lxc_read_from_file(fpath, NULL, 0);
+ if (len <= 0) {
+ pam_cgfs_debug("Failed to read %s: %s", fpath, strerror(errno));
goto bad;
+ }
- value = must_alloc(len + 1);
- if (read_from_file(fpath, value, len) != len)
+ value = must_realloc(NULL, len + 1);
+ if (lxc_read_from_file(fpath, value, len) != len) {
+ pam_cgfs_debug("Failed to read %s: %s", fpath, strerror(errno));
goto bad;
+ }
free(fpath);
*lastslash = oldv;
fpath = must_make_path(path, file, NULL);
- ret = write_to_file(fpath, value, len, false);
+ ret = lxc_write_to_file(fpath, value, len, false, 0660);
if (ret < 0)
pam_cgfs_debug("Unable to write %s to %s", value, fpath);
clonechildrenpath = must_make_path(h->mountpoint, "cgroup.clone_children", NULL);
- if (read_from_file(clonechildrenpath, &v, 1) < 0) {
- pam_cgfs_debug("Failed to read '%s'", clonechildrenpath);
+ if (lxc_read_from_file(clonechildrenpath, &v, 1) < 0) {
+ pam_cgfs_debug("Failed to read %s: %s", clonechildrenpath, strerror(errno));
free(clonechildrenpath);
return false;
}
return true;
}
- if (write_to_file(clonechildrenpath, "1", 1, false) < 0) {
+ if (lxc_write_to_file(clonechildrenpath, "1", 1, false, 0660) < 0) {
/* Set clone_children so children inherit our settings */
pam_cgfs_debug("Failed to write 1 to %s", clonechildrenpath);
free(clonechildrenpath);
return true;
}
- if (read_from_file(clonechildrenpath, &v, 1) < 0) {
- pam_cgfs_debug("Failed to read '%s'", clonechildrenpath);
+ if (lxc_read_from_file(clonechildrenpath, &v, 1) < 0) {
+ pam_cgfs_debug("Failed to read %s: %s", clonechildrenpath, strerror(errno));
free(clonechildrenpath);
free(cgpath);
return false;
}
free(cgpath);
- if (write_to_file(clonechildrenpath, "1", 1, false) < 0) {
+ if (lxc_write_to_file(clonechildrenpath, "1", 1, false, 0660) < 0) {
/* Set clone_children so children inherit our settings */
pam_cgfs_debug("Failed to write 1 to %s", clonechildrenpath);
free(clonechildrenpath);
return our_cg;
}
- created = mkdir_parent(it->mountpoint, path);
+ created = lxc_mkdir_parent(it->mountpoint, path);
if (!created) {
free(path);
continue;
}
}
- created = mkdir_parent(v2->mountpoint, path);
+ created = lxc_mkdir_parent(v2->mountpoint, path);
if (!created) {
free(path);
return false;
{
int idx = 0, ret;
bool existed;
- char cg[MAXPATHLEN];
+ char cg[PATH_MAX];
cg_escape();
while (idx >= 0) {
- ret = snprintf(cg, MAXPATHLEN, "/user/%s/%d", user, idx);
- if (ret < 0 || ret >= MAXPATHLEN) {
+ ret = snprintf(cg, PATH_MAX, "/user/%s/%d", user, idx);
+ if (ret < 0 || ret >= PATH_MAX) {
mysyslog(LOG_ERR, "Username too long\n", NULL);
return PAM_SESSION_ERR;
}