Bug#1108497: marked as done (unblock: pam/1.7.0-5) (2/3)
From
Debian Bug Tracking System@21:1/5 to
All on Sun Jul 6 19:40:01 2025
[continued from previous message]
+@@ -319,8 +590,7 @@ static int parse_iscript_params(char *params, struct polydir_s *poly)
+
+ if (*params != '\0') {
+ if (*params != '/') { /* path is relative to NAMESPACE_D_DIR */
+- if (asprintf(&poly->init_script, "%s%s", NAMESPACE_D_DIR, params) == -1)
+- return -1;
++ poly->init_script = pam_asprintf("%s%s", NAMESPACE_D_DIR, params);
+ } else {
+ poly->init_script = strdup(params);
+ }
+@@ -482,6 +752,7 @@ static int process_line(char *line, const char *home, const char *rhome,
+ struct instance_data *idata)
+ {
+ char *dir = NULL, *instance_prefix = NULL, *rdir = NULL;
++ const char *config_dir, *config_instance_prefix;
+ char *method, *uids;
+ char *tptr;
+ struct polydir_s *poly;
+@@ -490,7 +761,6 @@ static int process_line(char *line, const char *home, const char *rhome,
+ static const char *const var_names[] = {"HOME", "USER", NULL};
+ const char *var_values[] = {home, idata->user};
+ const char *rvar_values[] = {rhome, idata->ruser};
+- size_t len;
+
+ /*
+ * skip the leading white space
+@@ -531,22 +801,19 @@ static int process_line(char *line, const char *home, const char *rhome,
+ goto erralloc;
+ }
+
+- dir = config_options[0];
+- if (dir == NULL) {
++ config_dir = config_options[0];
++ if (config_dir == NULL) {
+ pam_syslog(idata->pamh, LOG_NOTICE, "Invalid line missing polydir"); + goto skipping;
+ }
+- instance_prefix = config_options[1];
+- if (instance_prefix == NULL) {
++ config_instance_prefix = config_options[1];
++ if (config_instance_prefix == NULL) {
+ pam_syslog(idata->pamh, LOG_NOTICE, "Invalid line missing instance_prefix");
+- dir = NULL;
+ goto skipping;
+ }
+ method = config_options[2];
+ if (method == NULL) {
+ pam_syslog(idata->pamh, LOG_NOTICE, "Invalid line missing method"); +- instance_prefix = NULL;
+- dir = NULL;
+ goto skipping;
+ }
+
+@@ -561,19 +828,16 @@ static int process_line(char *line, const char *home, const char *rhome,
+ /*
+ * Expand $HOME and $USER in poly dir and instance dir prefix
+ */
+- if ((rdir=expand_variables(dir, var_names, rvar_values)) == NULL) {
+- instance_prefix = NULL;
+- dir = NULL;
++ if ((rdir = expand_variables(config_dir, var_names, rvar_values)) == NULL) {
+ goto erralloc;
+ }
+
+- if ((dir=expand_variables(dir, var_names, var_values)) == NULL) {
+- instance_prefix = NULL;
++ if ((dir = expand_variables(config_dir, var_names, var_values)) == NULL) {
+ goto erralloc;
+ }
+
+- if ((instance_prefix=expand_variables(instance_prefix, var_names, var_values))
+- == NULL) {
++ if ((instance_prefix = expand_variables(config_instance_prefix,
++ var_names, var_values)) == NULL) { + goto erralloc;
+ }
+
+@@ -583,15 +847,8 @@ static int process_line(char *line, const char *home, const char *rhome,
+ pam_syslog(idata->pamh, LOG_DEBUG, "Expanded instance prefix: '%s'", instance_prefix);
+ }
+
+- len = strlen(dir);
+- if (len > 0 && dir[len-1] == '/') {
+- dir[len-1] = '\0';
+- }
+-
+- len = strlen(rdir);
+- if (len > 0 && rdir[len-1] == '/') {
+- rdir[len-1] = '\0';
+- }
++ strip_trailing_slashes(dir);
++ strip_trailing_slashes(rdir);
+
+ if (dir[0] == '\0' || rdir[0] == '\0') {
+ pam_syslog(idata->pamh, LOG_NOTICE, "Invalid polydir");
+@@ -606,14 +863,9 @@ static int process_line(char *line, const char *home, const char *rhome,
+ goto skipping;
+ }
+
+-#define COPY_STR(dst, src, apd) \
+- (snprintf((dst), sizeof(dst), "%s%s", (src), (apd)) != \
+- (ssize_t) (strlen(src) + strlen(apd)))
+-
+- if (COPY_STR(poly->dir, dir, "")
+- || COPY_STR(poly->rdir, rdir, "")
+- || COPY_STR(poly->instance_prefix, instance_prefix,
+- poly->method == TMPDIR ? "XXXXXX" : "")) {
++ if (pam_sprintf(poly->dir, "%s", dir) < 0
++ || pam_sprintf(poly->rdir, "%s", rdir) < 0
++ || pam_sprintf(poly->instance_prefix, "%s", instance_prefix) < 0) {
+ pam_syslog(idata->pamh, LOG_NOTICE, "Pathnames too long");
+ goto skipping;
+ }
+@@ -896,6 +1148,23 @@ static char *md5hash(const char *instname, struct instance_data *idata)
+ }
+
+ #ifdef WITH_SELINUX
++static char *secure_getfilecon(pam_handle_t *pamh, const char *dir)
++{
++ char *ctx = NULL;
++ int dfd = secure_opendir(dir, SECURE_OPENDIR_FULL_FD, 0, NULL);
++ if (dfd < 0) {
++ pam_syslog(pamh, LOG_ERR, "Error getting fd to %s: %m", dir); ++ return NULL;
++ }
++ if (fgetfilecon(dfd, &ctx) < 0)
++ ctx = NULL;
++ if (ctx == NULL)
++ pam_syslog(pamh, LOG_ERR,
++ "Error getting poly dir context for %s: %m", dir); ++ close(dfd);
++ return ctx;
++}
++
+ static int form_context(const struct polydir_s *polyptr,
+ char **i_context, char **origcon,
+ struct instance_data *idata)
+@@ -907,12 +1176,9 @@ static int form_context(const struct polydir_s *polyptr, + /*
+ * Get the security context of the directory to polyinstantiate.
+ */
+- rc = getfilecon(polyptr->dir, origcon);
+- if (rc < 0 || *origcon == NULL) {
+- pam_syslog(idata->pamh, LOG_ERR,
+- "Error getting poly dir context, %m");
++ *origcon = secure_getfilecon(idata->pamh, polyptr->dir);
++ if (*origcon == NULL)
+ return PAM_SESSION_ERR;
+- }
+
+ if (polyptr->method == USER) return PAM_SUCCESS;
+
+@@ -1009,29 +1275,52 @@ static int form_context(const struct polydir_s *polyptr,
+ #endif
+
+ /*
+- * poly_name returns the name of the polyinstantiated instance directory
++ * From the instance differentiation string, set in the polyptr structure:
++ * - the absolute path to the instance dir,
++ * - the absolute path to the previous dir (parent),
++ * - the instance name (may be different than the instance differentiation string)
++ */
++static int set_polydir_paths(struct polydir_s *polyptr, const char *inst_differentiation)
++{
++ char *tmp;
++
++ if (pam_sprintf(polyptr->instance_absolute, "%s%s",
++ polyptr->instance_prefix, inst_differentiation) < 0)
++ return -1;
++
++ polyptr->instname = strrchr(polyptr->instance_absolute, '/') + 1;
++
++ if (pam_sprintf(polyptr->instance_parent, "%s", polyptr->instance_absolute) < 0)
++ return -1;
++
++ tmp = strrchr(polyptr->instance_parent, '/') + 1;
++ *tmp = '\0';
++
++ return 0;
++}
++
++/*
++ * Set the name of the polyinstantiated instance directory
+ * based on the method used for polyinstantiation (user, context or level)
+ * In addition, the function also returns the security contexts of the
+ * original directory to polyinstantiate and the polyinstantiated instance
+ * directory.
+ */
+ #ifdef WITH_SELINUX
+-static int poly_name(const struct polydir_s *polyptr, char **i_name,
+- char **i_context, char **origcon,
+- struct instance_data *idata)
++static int poly_name(struct polydir_s *polyptr, char **i_context,
++ char **origcon, struct instance_data *idata)
+ #else
+-static int poly_name(const struct polydir_s *polyptr, char **i_name,
+- struct instance_data *idata)
++static int poly_name(struct polydir_s *polyptr, struct instance_data *idata) + #endif
+ {
+ int rc;
++ char *inst_differentiation = NULL;
+ char *hash = NULL;
+ enum polymethod pm;
+ #ifdef WITH_SELINUX
+ char *rawcon = NULL;
+ #endif
+
+- *i_name = NULL;
+ #ifdef WITH_SELINUX
+ *i_context = NULL;
+ *origcon = NULL;
+@@ -1065,10 +1354,8 @@ static int poly_name(const struct polydir_s *polyptr, char **i_name,
+
+ switch (pm) {
+ case USER:
+- if (asprintf(i_name, "%s", idata->user) < 0) {
+- *i_name = NULL;
++ if ((inst_differentiation = strdup(idata->user)) == NULL)
+ goto fail;
+- }
+ break;
+
+ #ifdef WITH_SELINUX
+@@ -1078,26 +1365,25 @@ static int poly_name(const struct polydir_s *polyptr, char **i_name,
+ pam_syslog(idata->pamh, LOG_ERR, "Error translating directory context");
+ goto fail;
+ }
+- if (polyptr->flags & POLYDIR_SHARED) {
+- if (asprintf(i_name, "%s", rawcon) < 0) {
+- *i_name = NULL;
+- goto fail;
+- }
+- } else {
+- if (asprintf(i_name, "%s_%s", rawcon, idata->user) < 0) {
+- *i_name = NULL;
+- goto fail;
+- }
+- }
++ if (polyptr->flags & POLYDIR_SHARED)
++ inst_differentiation = strdup(rawcon);
++ else
++ inst_differentiation = pam_asprintf("%s_%s", rawcon, idata->user);
++ if (inst_differentiation == NULL)
++ goto fail;
+ break;
+
+ #endif /* WITH_SELINUX */
+
+ case TMPDIR:
+- case TMPFS:
+- if ((*i_name=strdup("")) == NULL)
++ if ((inst_differentiation = strdup("XXXXXX")) == NULL)
+ goto fail;
+- return PAM_SUCCESS;
++ goto success;
++
++ case TMPFS:
++ if ((inst_differentiation=strdup("")) == NULL)
++ goto fail;
++ goto success;
+
+ default:
+ if (idata->flags & PAMNS_DEBUG)
+@@ -1106,31 +1392,37 @@ static int poly_name(const struct polydir_s *polyptr, char **i_name,
+ }
+
+ if (idata->flags & PAMNS_DEBUG)
+- pam_syslog(idata->pamh, LOG_DEBUG, "poly_name %s", *i_name);
++ pam_syslog(idata->pamh, LOG_DEBUG, "poly_name %s", inst_differentiation);
+
+- if ((idata->flags & PAMNS_GEN_HASH) || strlen(*i_name) > NAMESPACE_MAX_DIR_LEN) {
+- hash = md5hash(*i_name, idata);
++ if ((idata->flags & PAMNS_GEN_HASH) || strlen(inst_differentiation) > NAMESPACE_MAX_DIR_LEN) {
++ hash = md5hash(inst_differentiation, idata);
+ if (hash == NULL) {
+ goto fail;
+ }
+ if (idata->flags & PAMNS_GEN_HASH) {
+- free(*i_name);
+- *i_name = hash;
++ free(inst_differentiation);
++ inst_differentiation = hash;
+ hash = NULL;
+ } else {
+- char *newname;
+- if (asprintf(&newname, "%.*s_%s", NAMESPACE_MAX_DIR_LEN-1-(int)strlen(hash),
+- *i_name, hash) < 0) {
++ char *newname =
++ pam_asprintf("%.*s_%s",
++ NAMESPACE_MAX_DIR_LEN - 1 - (int)strlen(hash),
++ inst_differentiation, hash);
++ if (newname == NULL)
+ goto fail;
+- }
+- free(*i_name);
+- *i_name = newname;
++ free(inst_differentiation);
++ inst_differentiation = newname;
+ }
+ }
+- rc = PAM_SUCCESS;
+
++success:
++ if (set_polydir_paths(polyptr, inst_differentiation) == -1)
++ goto fail;
++
++ rc = PAM_SUCCESS;
+ fail:
+ free(hash);
++ free(inst_differentiation);
+ #ifdef WITH_SELINUX
+ freecon(rawcon);
+ #endif
+@@ -1141,189 +1433,111 @@ fail:
+ freecon(*origcon);
+ *origcon = NULL;
+ #endif
+- free(*i_name);
+- *i_name = NULL;
+ }
+ return rc;
+ }
+
+-static int protect_mount(int dfd, const char *path, struct instance_data *idata)
+-{
+- struct protect_dir_s *dir = idata->protect_dirs;
+- char tmpbuf[64];
+-
+- while (dir != NULL) {
+- if (strcmp(path, dir->dir) == 0) {
+- return 0;
+- }
+- dir = dir->next;
+- }
+-
+- dir = calloc(1, sizeof(*dir));
+-
+- if (dir == NULL) {
+- return -1;
+- }
+-
+- dir->dir = strdup(path);
+-
+- if (dir->dir == NULL) {
+- free(dir);
+- return -1;
+- }
+-
+- snprintf(tmpbuf, sizeof(tmpbuf), "/proc/self/fd/%d", dfd);
+-
+- if (idata->flags & PAMNS_DEBUG) {
+- pam_syslog(idata->pamh, LOG_INFO,
+- "Protect mount of %s over itself", path);
+- }
+-
+- if (mount(tmpbuf, tmpbuf, NULL, MS_BIND, NULL) != 0) {
+- int save_errno = errno;
+- pam_syslog(idata->pamh, LOG_ERR,
+- "Protect mount of %s failed: %m", tmpbuf);
+- free(dir->dir);
+- free(dir);
+- errno = save_errno;
+- return -1;
+- }
+-
+- dir->next = idata->protect_dirs;
+- idata->protect_dirs = dir;
+-
+- return 0;
+-}
+-
+-static int protect_dir(const char *path, mode_t mode, int do_mkdir,
+- struct instance_data *idata)
+-{
+- char *p = strdup(path);
+- char *d;
+- char *dir = p;
+- int dfd = AT_FDCWD;
+- int dfd_next;
+- int save_errno;
+- int flags = O_RDONLY | O_DIRECTORY;
+- int rv = -1;
+- struct stat st;
+-
+- if (p == NULL) {
+- goto error;
+- }
+-
+- if (*dir == '/') {
+- dfd = open("/", flags);
+- if (dfd == -1) {
+- goto error;
+- }
+- dir++; /* assume / is safe */
+- }
+-
+- while ((d=strchr(dir, '/')) != NULL) {
+- *d = '\0';
+- dfd_next = openat(dfd, dir, flags);
+- if (dfd_next == -1) {
+- goto error;
+- }
+-
+- if (dfd != AT_FDCWD)
+- close(dfd);
+- dfd = dfd_next;
+-
+- if (fstat(dfd, &st) != 0) {
+- goto error;
+- }
+-
+- if (flags & O_NOFOLLOW) {
+- /* we are inside user-owned dir - protect */
+- if (protect_mount(dfd, p, idata) == -1)
+- goto error;
+- } else if (st.st_uid != 0 || st.st_gid != 0 ||
+- (st.st_mode & S_IWOTH)) {
+- /* do not follow symlinks on subdirectories */
+- flags |= O_NOFOLLOW;
+- }
+-
+- *d = '/';
+- dir = d + 1;
+- }
+-
+- rv = openat(dfd, dir, flags);
+-
+- if (rv == -1) {
+- if (!do_mkdir || mkdirat(dfd, dir, mode) != 0) {
+- goto error;
+- }
+- rv = openat(dfd, dir, flags);
+- }
+-
+- if (flags & O_NOFOLLOW) {
+- /* we are inside user-owned dir - protect */
+- if (protect_mount(rv, p, idata) == -1) {
+- save_errno = errno;
+- close(rv);
+- rv = -1;
+- errno = save_errno;
+- }
+- }
+-
+-error:
+- save_errno = errno;
+- free(p);
+- if (dfd != AT_FDCWD && dfd >= 0)
+- close(dfd);
+- errno = save_errno;
+-
+- return rv;
+-}
+-
+-static int check_inst_parent(char *ipath, struct instance_data *idata) ++static int check_inst_parent(int dfd, struct instance_data *idata)
+ {
+ struct stat instpbuf;
+- char *inst_parent, *trailing_slash;
+- int dfd;
++
+ /*
+- * stat the instance parent path to make sure it exists
+- * and is a directory. Check that its mode is 000 (unless the
+- * admin explicitly instructs to ignore the instance parent
+- * mode by the "ignore_instance_parent_mode" argument).
++ * Stat the instance parent directory to make sure it's writable by
++ * root only (unless the admin explicitly instructs to ignore the
++ * instance parent mode by the "ignore_instance_parent_mode" argument). + */
+- inst_parent = strdup(ipath);
+- if (!inst_parent) {
+- pam_syslog(idata->pamh, LOG_CRIT, "Error allocating pathname string");
+- return PAM_SESSION_ERR;
+- }
+
+- trailing_slash = strrchr(inst_parent, '/');
+- if (trailing_slash)
+- *trailing_slash = '\0';
++ if (idata->flags & PAMNS_IGN_INST_PARENT_MODE)
++ return PAM_SUCCESS;
+
+- dfd = protect_dir(inst_parent, 0, 1, idata);
+-
+- if (dfd == -1 || fstat(dfd, &instpbuf) < 0) {
++ if (fstat(dfd, &instpbuf) < 0) {
+ pam_syslog(idata->pamh, LOG_ERR,
+- "Error creating or accessing instance parent %s, %m", inst_parent);
+- if (dfd != -1)
+- close(dfd);
+- free(inst_parent);
++ "Error accessing instance parent, %m");
+ return PAM_SESSION_ERR;
+ }
+
+- if ((idata->flags & PAMNS_IGN_INST_PARENT_MODE) == 0) {
+- if ((instpbuf.st_mode & (S_IRWXU|S_IRWXG|S_IRWXO)) || instpbuf.st_uid != 0) {
+- pam_syslog(idata->pamh, LOG_ERR, "Mode of inst parent %s not 000 or owner not root",
+- inst_parent);
+- close(dfd);
+- free(inst_parent);
+- return PAM_SESSION_ERR;
+- }
++ if ((instpbuf.st_mode & (S_IRWXU|S_IRWXG|S_IRWXO)) || instpbuf.st_uid != 0) {
++ pam_syslog(idata->pamh, LOG_ERR,
++ "Mode of inst parent not 000 or owner not root");
++ return PAM_SESSION_ERR;
+ }
+- close(dfd);
+- free(inst_parent);
++
+ return PAM_SUCCESS;
+ }
+
++/*
++ * Check for a given absolute path that all segments except the last one are: ++ * 1. a directory owned by root and not writable by group or others
++ * 2. a symlink owned by root and referencing a directory respecting 1.
++ * Returns 0 if safe, -1 is unsafe.
++ * If the path is not accessible (does not exist, hidden under a mount...), ++ * returns -1 (unsafe).
++ */
++static int check_safe_path(const char *path, struct instance_data *idata)
++{
++ char *p = strdup(path);
++ char *d;
++ char *dir = p;
++ struct stat st;
++
++ if (p == NULL)
++ return -1;
++
++ /* Check path is absolute */
++ if (p[0] != '/')
++ goto error;
++
++ strip_trailing_slashes(p);
++
++ /* Last segment of the path may be owned by the user */
++ if ((d = strrchr(dir, '/')) != NULL)
++ *d = '\0';
++
++ while ((d=strrchr(dir, '/')) != NULL) {
++
++ /* Do not follow symlinks */
++ if (lstat(dir, &st) != 0)
++ goto error;
++
++ if (S_ISLNK(st.st_mode)) {
++ if (st.st_uid != 0) {
++ if (idata->flags & PAMNS_DEBUG)
++ pam_syslog(idata->pamh, LOG_DEBUG,
++ "Path deemed unsafe: Symlink %s should be owned by root", dir);
++ goto error;
++ }
++
++ /* Follow symlinks */
++ if (stat(dir, &st) != 0)
++ goto error;
++ }
++
++ if (!S_ISDIR(st.st_mode)) {
++ if (idata->flags & PAMNS_DEBUG)
++ pam_syslog(idata->pamh, LOG_DEBUG,
++ "Path deemed unsafe: %s is expected to be a directory", dir);
++ goto error;
++ }
++
++ if (st.st_uid != 0 ||
++ ((st.st_mode & (S_IWGRP|S_IWOTH)) && !(st.st_mode & S_ISVTX))) { ++ if (idata->flags & PAMNS_DEBUG)
++ pam_syslog(idata->pamh, LOG_DEBUG,
++ "Path deemed unsafe: %s should be owned by root, and not be writable by group or others", dir);
++ goto error;
++ }
++
++ *d = '\0';
++ }
++
++ free(p);
++ return 0;
++
++error:
++ free(p);
++ return -1;
++}
++
+ /*
+ * Check to see if there is a namespace initialization script in
+ * the /etc/security directory. If such a script exists
+@@ -1349,68 +1563,72 @@ static int inst_init(const struct polydir_s *polyptr, const char *ipath,
+ if ((polyptr->flags & POLYDIR_ISCRIPT) && polyptr->init_script)
+ init_script = polyptr->init_script;
+
+- if (access(init_script, F_OK) == 0) {
+- if (access(init_script, X_OK) < 0) {
+- if (idata->flags & PAMNS_DEBUG)
+- pam_syslog(idata->pamh, LOG_ERR,
+- "Namespace init script not executable");
+- return PAM_SESSION_ERR;
+- } else {
+- struct sigaction newsa, oldsa;
++ if (access(init_script, F_OK) != 0)
++ return PAM_SUCCESS;
+
+- memset(&newsa, '\0', sizeof(newsa));
+- newsa.sa_handler = SIG_DFL;
+- if (sigaction(SIGCHLD, &newsa, &oldsa) == -1) {
+- pam_syslog(idata->pamh, LOG_ERR, "failed to reset SIGCHLD handler");
+- return PAM_SESSION_ERR;
+- }
+-
+- pid = fork();
+- if (pid == 0) {
+- static char *envp[] = { NULL };
+-#ifdef WITH_SELINUX
+- if (idata->flags & PAMNS_SELINUX_ENABLED) {
+- if (setexeccon(NULL) < 0)
+- _exit(1);
+- }
+-#endif
+- /* Pass maximum privs when we exec() */
+- if (setuid(geteuid()) < 0) {
+- /* ignore failures, they don't matter */
+- }
+-
+- close_fds_pre_exec(idata);
+-
+- if (execle(init_script, init_script,
+- polyptr->dir, ipath, newdir?"1":"0", idata->user, NULL, envp) < 0)
+- _exit(1);
+- } else if (pid > 0) {
+- while (((rc = waitpid(pid, &status, 0)) == (pid_t)-1) &&
+- (errno == EINTR));
+- if (rc == (pid_t)-1) {
+- pam_syslog(idata->pamh, LOG_ERR, "waitpid failed- %m");
+- rc = PAM_SESSION_ERR;
+- goto out;
+- }
+- if (!WIFEXITED(status) || WIFSIGNALED(status) > 0) {
+- pam_syslog(idata->pamh, LOG_ERR,
+- "Error initializing instance");
+- rc = PAM_SESSION_ERR;
+- goto out;
+- }
+- } else if (pid < 0) {
+- pam_syslog(idata->pamh, LOG_ERR,
+- "Cannot fork to run namespace init script, %m");
+- rc = PAM_SESSION_ERR;
+- goto out;
+- }
+- rc = PAM_SUCCESS;
+-out:
+- (void) sigaction(SIGCHLD, &oldsa, NULL);
+- return rc;
+- }
++ if (access(init_script, X_OK) < 0) {
++ if (idata->flags & PAMNS_DEBUG)
++ pam_syslog(idata->pamh, LOG_ERR,
++ "Namespace init script not executable");
++ return PAM_SESSION_ERR;
+ }
+- return PAM_SUCCESS;
++
++ struct sigaction newsa, oldsa;
++
++ memset(&newsa, '\0', sizeof(newsa));
++ newsa.sa_handler = SIG_DFL;
++ if (sigaction(SIGCHLD, &newsa, &oldsa) == -1) {
++ pam_syslog(idata->pamh, LOG_ERR, "failed to reset SIGCHLD handler");
++ return PAM_SESSION_ERR;
++ }
++
++ pid = fork();
++ if (pid == 0) {
++ static char *envp[] = { NULL };
++#ifdef WITH_SELINUX
++ if (idata->flags & PAMNS_SELINUX_ENABLED) {
++ if (setexeccon(NULL) < 0)
++ _exit(1);
++ }
++#endif
++ /* Pass maximum privs when we exec() */
++ if (setuid(geteuid()) < 0) {
++ /* ignore failures, they don't matter */
++ }
++
++ close_fds_pre_exec(idata);
++
++ execle(init_script, init_script,
++ polyptr->dir, ipath,
++ newdir ? "1":"0", idata->user,
++ (check_safe_path(polyptr->dir, idata) == -1) ? "0":"1", ++ (check_safe_path(ipath, idata) == -1) ? "0":"1",
++ NULL, envp);
++ _exit(1);
++ } else if (pid > 0) {
++ while (((rc = waitpid(pid, &status, 0)) == (pid_t)-1) &&
++ (errno == EINTR));
++ if (rc == (pid_t)-1) {
++ pam_syslog(idata->pamh, LOG_ERR, "waitpid failed- %m"); ++ rc = PAM_SESSION_ERR;
++ goto out;
++ }
++ if (!WIFEXITED(status) || WIFSIGNALED(status) > 0) {
++ pam_syslog(idata->pamh, LOG_ERR,
++ "Error initializing instance");
++ rc = PAM_SESSION_ERR;
++ goto out;
++ }
++ } else if (pid < 0) {
++ pam_syslog(idata->pamh, LOG_ERR,
++ "Cannot fork to run namespace init script, %m");
++ rc = PAM_SESSION_ERR;
++ goto out;
++ }
++ rc = PAM_SUCCESS;
++out:
++ (void) sigaction(SIGCHLD, &oldsa, NULL);
++ return rc;
+ }
+
+ static int create_polydir(struct polydir_s *polyptr,
+@@ -1460,14 +1678,16 @@ static int create_polydir(struct polydir_s *polyptr,
+ }
+ #endif
+
+- rc = protect_dir(dir, mode, 1, idata);
++ rc = secure_opendir(dir,
++ SECURE_OPENDIR_PROTECT | SECURE_OPENDIR_MKDIR | SECURE_OPENDIR_FULL_FD,
++ mode, idata);
+ if (rc == -1) {
+ pam_syslog(idata->pamh, LOG_ERR,
+ "Error creating directory %s: %m", dir);
+ #ifdef WITH_SELINUX
+ freecon(oldcon_raw);
+ #endif
+- return PAM_SESSION_ERR;
++ return -1;
+ }
+
+ #ifdef WITH_SELINUX
+@@ -1488,9 +1708,9 @@ static int create_polydir(struct polydir_s *polyptr,
+ pam_syslog(idata->pamh, LOG_ERR,
+ "Error changing mode of directory %s: %m", dir);
+ close(rc);
+- umount(dir); /* undo the eventual protection bind mount */
+- rmdir(dir);
+- return PAM_SESSION_ERR;
++ secure_umount(dir); /* undo the eventual protection bind mount */
++ secure_try_rmdir(dir);
++ return -1;
+ }
+ }
+
+@@ -1508,41 +1728,37 @@ static int create_polydir(struct polydir_s *polyptr,
+ pam_syslog(idata->pamh, LOG_ERR,
+ "Unable to change owner on directory %s: %m", dir);
+ close(rc);
+- umount(dir); /* undo the eventual protection bind mount */
+- rmdir(dir);
+- return PAM_SESSION_ERR;
++ secure_umount(dir); /* undo the eventual protection bind mount */
++ secure_try_rmdir(dir);
++ return -1;
+ }
+
+- close(rc);
+-
+ if (idata->flags & PAMNS_DEBUG)
+ pam_syslog(idata->pamh, LOG_DEBUG,
+ "Polydir owner %u group %u", uid, gid);
+
+- return PAM_SUCCESS;
++ return rc;
+ }
+
+ /*
+- * Create polyinstantiated instance directory (ipath).
++ * Create polyinstantiated instance directory.
++ * To protect against races, changes are done on a fd to the parent of the
++ * instance directory (dfd_iparent) and a relative path (polyptr->instname). ++ * The absolute path (polyptr->instance_absolute) is only updated when creating
++ * a tmpdir and used for logging purposes.
+ */
+ #ifdef WITH_SELINUX
+-static int create_instance(struct polydir_s *polyptr, char *ipath, struct stat *statbuf,
+- const char *icontext, const char *ocontext,
+- struct instance_data *idata)
++static int create_instance(struct polydir_s *polyptr, int dfd_iparent,
++ struct stat *statbuf, const char *icontext, const char *ocontext,
++ struct instance_data *idata)
+ #else
+-static int create_instance(struct polydir_s *polyptr, char *ipath, struct stat *statbuf,
+- struct instance_data *idata)
++static int create_instance(struct polydir_s *polyptr, int dfd_iparent,
++ struct stat *statbuf, struct instance_data *idata)
+ #endif
+ {
+ struct stat newstatbuf;
+ int fd;
+
+- /*
+- * Check to make sure instance parent is valid.
+- */
+- if (check_inst_parent(ipath, idata))
+- return PAM_SESSION_ERR;
+-
+ /*
+ * Create instance directory and set its security context to the context + * returned by the security policy. Set its mode and ownership
+@@ -1551,29 +1767,39 @@ static int create_instance(struct polydir_s *polyptr, char *ipath, struct stat *
+ */
+
+ if (polyptr->method == TMPDIR) {
+- if (mkdtemp(polyptr->instance_prefix) == NULL) {
+- pam_syslog(idata->pamh, LOG_ERR, "Error creating temporary instance %s, %m",
+- polyptr->instance_prefix);
+- polyptr->method = NONE; /* do not clean up! */
+- return PAM_SESSION_ERR;
+- }
+- /* copy the actual directory name to ipath */
+- strcpy(ipath, polyptr->instance_prefix);
+- } else if (mkdir(ipath, S_IRUSR) < 0) {
++ char s_path[PATH_MAX];
++ /*
++ * Create the template for mkdtemp() as a magic link based on
++ * our existing fd to avoid symlink attacks and races.
++ */
++ if (pam_sprintf(s_path, "/proc/self/fd/%d/%s", dfd_iparent, polyptr->instname) < 0
++ || mkdtemp(s_path) == NULL) {
++ pam_syslog(idata->pamh, LOG_ERR,
++ "Error creating temporary instance dir %s, %m",
++ polyptr->instance_absolute);
++ polyptr->method = NONE; /* do not clean up! */
++ return PAM_SESSION_ERR;
++ }
++
++ /* Copy the actual directory name to polyptr->instname */
++ strcpy(polyptr->instname, base_name(s_path));
++ } else if (mkdirat(dfd_iparent, polyptr->instname, S_IRUSR) < 0) {
+ if (errno == EEXIST)
+ return PAM_IGNORE;
+ else {
+ pam_syslog(idata->pamh, LOG_ERR, "Error creating %s, %m",
+- ipath);
++ polyptr->instance_absolute);
+ return PAM_SESSION_ERR;
+ }
+ }
+
+- /* Open a descriptor to it to prevent races */
+- fd = open(ipath, O_DIRECTORY | O_RDONLY);
++ /* Open a descriptor to prevent races, based on our existing fd. */
++ fd = openat(dfd_iparent, polyptr->instname,
++ O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
+ if (fd < 0) {
+- pam_syslog(idata->pamh, LOG_ERR, "Error opening %s, %m", ipath);
+- rmdir(ipath);
++ pam_syslog(idata->pamh, LOG_ERR, "Error opening %s, %m",
++ polyptr->instance_absolute);
++ unlinkat(dfd_iparent, polyptr->instname, AT_REMOVEDIR);
+ return PAM_SESSION_ERR;
+ }
+ #ifdef WITH_SELINUX
+@@ -1583,17 +1809,19 @@ static int create_instance(struct polydir_s *polyptr, char *ipath, struct stat *
+ if (icontext) {
+ if (fsetfilecon(fd, icontext) < 0) {
+ pam_syslog(idata->pamh, LOG_ERR,
+- "Error setting context of %s to %s", ipath, icontext); ++ "Error setting context of %s to %s",
++ polyptr->instance_absolute, icontext);
+ close(fd);
+- rmdir(ipath);
++ unlinkat(dfd_iparent, polyptr->instname, AT_REMOVEDIR);
+ return PAM_SESSION_ERR;
+ }
+ } else {
+ if (fsetfilecon(fd, ocontext) < 0) {
+ pam_syslog(idata->pamh, LOG_ERR,
+- "Error setting context of %s to %s", ipath, ocontext); ++ "Error setting context of %s to %s",
++ polyptr->instance_absolute, ocontext);
+ close(fd);
+- rmdir(ipath);
++ unlinkat(dfd_iparent, polyptr->instname, AT_REMOVEDIR);
+ return PAM_SESSION_ERR;
+ }
+ }
+@@ -1601,9 +1829,9 @@ static int create_instance(struct polydir_s *polyptr, char *ipath, struct stat *
+ #endif
+ if (fstat(fd, &newstatbuf) < 0) {
+ pam_syslog(idata->pamh, LOG_ERR, "Error stating %s, %m",
+- ipath);
++ polyptr->instance_absolute);
+ close(fd);
+- rmdir(ipath);
++ unlinkat(dfd_iparent, polyptr->instname, AT_REMOVEDIR);
+ return PAM_SESSION_ERR;
+ }
+ if (newstatbuf.st_uid != statbuf->st_uid ||
+@@ -1611,17 +1839,17 @@ static int create_instance(struct polydir_s *polyptr, char *ipath, struct stat *
+ if (fchown(fd, statbuf->st_uid, statbuf->st_gid) < 0) {
+ pam_syslog(idata->pamh, LOG_ERR,
+ "Error changing owner for %s, %m",
+- ipath);
++ polyptr->instance_absolute);
+ close(fd);
+- rmdir(ipath);
++ unlinkat(dfd_iparent, polyptr->instname, AT_REMOVEDIR);
+ return PAM_SESSION_ERR;
+ }
+ }
+ if (fchmod(fd, statbuf->st_mode & 07777) < 0) {
+ pam_syslog(idata->pamh, LOG_ERR, "Error changing mode for %s, %m",
+- ipath);
++ polyptr->instance_absolute);
+ close(fd);
+- rmdir(ipath);
++ unlinkat(dfd_iparent, polyptr->instname, AT_REMOVEDIR);
+ return PAM_SESSION_ERR;
+ }
+ close(fd);
+@@ -1640,9 +1868,12 @@ static int ns_setup(struct polydir_s *polyptr,
+ struct instance_data *idata)
+ {
+ int retval;
++ int dfd_iparent = -1;
++ int dfd_ipath = -1;
++ int dfd_pptrdir = -1;
+ int newdir = 1;
+- char *inst_dir = NULL;
+- char *instname = NULL;
++ char s_ipath[MAGIC_LNK_FD_SIZE];
++ char s_pptrdir[MAGIC_LNK_FD_SIZE];
+ struct stat statbuf;
+ #ifdef WITH_SELINUX
+ char *instcontext = NULL, *origcontext = NULL;
+@@ -1652,39 +1883,48 @@ static int ns_setup(struct polydir_s *polyptr,
+ pam_syslog(idata->pamh, LOG_DEBUG,
+ "Set namespace for directory %s", polyptr->dir);
+
+- retval = protect_dir(polyptr->dir, 0, 0, idata);
++ dfd_pptrdir = secure_opendir(polyptr->dir, SECURE_OPENDIR_PROTECT, 0, idata);
+
+- if (retval < 0 && errno != ENOENT) {
+- pam_syslog(idata->pamh, LOG_ERR, "Polydir %s access error: %m",
+- polyptr->dir);
+- return PAM_SESSION_ERR;
+- }
+-
+- if (retval < 0) {
+- if ((polyptr->flags & POLYDIR_CREATE) &&
+- create_polydir(polyptr, idata) != PAM_SUCCESS)
+- return PAM_SESSION_ERR;
+- } else {
+- close(retval);
++ if (dfd_pptrdir < 0) {
++ if (errno != ENOENT || !(polyptr->flags & POLYDIR_CREATE)) {
++ pam_syslog(idata->pamh, LOG_ERR, "Polydir %s access error: %m", ++ polyptr->dir);
++ return PAM_SESSION_ERR;
++ }
++ dfd_pptrdir = create_polydir(polyptr, idata);
++ if (dfd_pptrdir < 0)
++ return PAM_SESSION_ERR;
+ }
+
+ if (polyptr->method == TMPFS) {
+- if (mount("tmpfs", polyptr->dir, "tmpfs", polyptr->mount_flags, polyptr->mount_opts) < 0) {
+- pam_syslog(idata->pamh, LOG_ERR, "Error mounting tmpfs on %s, %m", +- polyptr->dir);
+- return PAM_SESSION_ERR;
+- }
++ /*
++ * There is no function mount() that operate on a fd, so instead, we ++ * get the magic link corresponding to the fd and give it to mount(). ++ * This protects against potential races exploitable by an unpriv user.
++ */
++ if (pam_sprintf(s_pptrdir, "/proc/self/fd/%d", dfd_pptrdir) < 0) {
++ pam_syslog(idata->pamh, LOG_ERR, "Error pam_sprintf s_pptrdir"); ++ goto error_out;
++ }
+
+- if (polyptr->flags & POLYDIR_NOINIT)
+- return PAM_SUCCESS;
++ if (mount("tmpfs", s_pptrdir, "tmpfs", polyptr->mount_flags, polyptr->mount_opts) < 0) {
++ pam_syslog(idata->pamh, LOG_ERR, "Error mounting tmpfs on %s, %m",
++ polyptr->dir);
++ goto error_out;
++ }
+
+- return inst_init(polyptr, "tmpfs", idata, 1);
++ if (polyptr->flags & POLYDIR_NOINIT) {
++ retval = PAM_SUCCESS;
++ goto cleanup;
++ }
++
[continued in next message]
--- SoupGate-Win32 v1.05
* Origin: fsxNet Usenet Gateway (21:1/5)