diff --git a/0003-Fix-CVE-2024-10963.patch b/0003-Fix-CVE-2024-10963.patch
new file mode 100644
index 0000000000000000000000000000000000000000..3455bb28538f460ea4ef847c572457289b60c7f9
--- /dev/null
+++ b/0003-Fix-CVE-2024-10963.patch
@@ -0,0 +1,221 @@
+diff --git a/modules/pam_access/access.conf.5.xml b/modules/pam_access/access.conf.5.xml
+index ff1cb22..fc24411 100644
+--- a/modules/pam_access/access.conf.5.xml
++++ b/modules/pam_access/access.conf.5.xml
+@@ -222,6 +222,10 @@
+ item and the line will be most probably ignored. For this reason, it is not
+ recommended to put spaces around the ':' characters.
+
++
++ Hostnames should be written as Fully-Qualified Host Name (FQHN) to avoid
++ confusion with device names or PAM service names.
++
+
+
+
+@@ -246,4 +250,4 @@
+ introduced by Mike Becher <mike.becher@lrz-muenchen.de>.
+
+
+-
+\ No newline at end of file
++
+diff --git a/modules/pam_access/pam_access.8.xml b/modules/pam_access/pam_access.8.xml
+index 010e749..b3edad0 100644
+--- a/modules/pam_access/pam_access.8.xml
++++ b/modules/pam_access/pam_access.8.xml
+@@ -22,11 +22,14 @@
+
+ debug
+
++
++ noaudit
++
+
+ nodefgroup
+
+
+- noaudit
++ nodns
+
+
+ accessfile=file
+@@ -129,6 +132,33 @@
+
+
+
++
++
++ nodefgroup
++
++
++
++ User tokens which are not enclosed in parentheses will not be
++ matched against the group database. The backwards compatible default is
++ to try the group database match even for tokens not enclosed
++ in parentheses.
++
++
++
++
++
++
++ nodns
++
++
++
++ Do not try to resolve tokens as hostnames, only IPv4 and IPv6
++ addresses will be resolved. Which means to allow login from a
++ remote host, the IP addresses need to be specified in access.conf.
++
++
++
++
+
+
+ fieldsep=separators
+@@ -170,20 +200,6 @@
+
+
+
+-
+-
+- nodefgroup
+-
+-
+-
+- User tokens which are not enclosed in parentheses will not be
+- matched against the group database. The backwards compatible default is
+- to try the group database match even for tokens not enclosed
+- in parentheses.
+-
+-
+-
+-
+
+
+
+@@ -286,4 +302,4 @@
+ was developed and provided by Mike Becher <mike.becher@lrz-muenchen.de>.
+
+
+-
+\ No newline at end of file
++
+diff --git a/modules/pam_access/pam_access.c b/modules/pam_access/pam_access.c
+index f70b7e4..d06496c 100644
+--- a/modules/pam_access/pam_access.c
++++ b/modules/pam_access/pam_access.c
+@@ -99,6 +99,7 @@ struct login_info {
+ int debug; /* Print debugging messages. */
+ int only_new_group_syntax; /* Only allow group entries of the form "(xyz)" */
+ int noaudit; /* Do not audit denials */
++ int nodns; /* Do not try to resolve tokens as hostnames */
+ const char *fs; /* field separator */
+ const char *sep; /* list-element separator */
+ int from_remote_host; /* If PAM_RHOST was used for from */
+@@ -150,6 +151,8 @@ parse_args(pam_handle_t *pamh, struct login_info *loginfo,
+ loginfo->only_new_group_syntax = YES;
+ } else if (strcmp (argv[i], "noaudit") == 0) {
+ loginfo->noaudit = YES;
++ } else if (strcmp (argv[i], "nodns") == 0) {
++ loginfo->nodns = YES;
+ } else {
+ pam_syslog(pamh, LOG_ERR, "unrecognized option [%s]", argv[i]);
+ }
+@@ -732,7 +735,7 @@ remote_match (pam_handle_t *pamh, char *tok, struct login_info *item)
+ if ((str_len = strlen(string)) > tok_len
+ && strcasecmp(tok, string + str_len - tok_len) == 0)
+ return YES;
+- } else if (tok[tok_len - 1] == '.') { /* internet network numbers (end with ".") */
++ } else if (tok[tok_len - 1] == '.') { /* internet network numbers/subnet (end with ".") */
+ struct addrinfo hint;
+
+ memset (&hint, '\0', sizeof (hint));
+@@ -807,6 +810,39 @@ string_match (pam_handle_t *pamh, const char *tok, const char *string,
+ }
+
+
++static int
++is_device (pam_handle_t *pamh, const char *tok)
++{
++ struct stat st;
++ const char *dev = "/dev/";
++ char *devname;
++
++ devname = malloc (strlen(dev) + strlen (tok) + 1);
++ if (devname == NULL) {
++ pam_syslog(pamh, LOG_ERR, "Cannot allocate memory for device name: %m");
++ /*
++ * We should return an error and abort, but pam_access has no good
++ * error handling.
++ */
++ return NO;
++ }
++
++ char *cp = stpcpy (devname, dev);
++ strcpy (cp, tok);
++
++ if (lstat(devname, &st) != 0)
++ {
++ free (devname);
++ return NO;
++ }
++ free (devname);
++
++ if (S_ISCHR(st.st_mode))
++ return YES;
++
++ return NO;
++}
++
+ /* network_netmask_match - match a string against one token
+ * where string is a hostname or ip (v4,v6) address and tok
+ * represents either a hostname, a single ip (v4,v6) address
+@@ -868,10 +904,42 @@ network_netmask_match (pam_handle_t *pamh,
+ return NO;
+ }
+ }
++ else if (isipaddr(tok, NULL, NULL) == YES)
++ {
++ if (getaddrinfo (tok, NULL, NULL, &ai) != 0)
++ {
++ if (item->debug)
++ pam_syslog(pamh, LOG_DEBUG, "cannot resolve IP address \"%s\"", tok);
++
++ return NO;
++ }
++ netmask_ptr = NULL;
++ }
++ else if (item->nodns)
++ {
++ /* Only hostnames are left, which we would need to resolve via DNS */
++ return NO;
++ }
+ else
+ {
++ /* Bail out on X11 Display entries and ttys. */
++ if (tok[0] == ':')
++ {
++ if (item->debug)
++ pam_syslog (pamh, LOG_DEBUG,
++ "network_netmask_match: tok=%s is X11 display", tok);
++ return NO;
++ }
++ if (is_device (pamh, tok))
++ {
++ if (item->debug)
++ pam_syslog (pamh, LOG_DEBUG,
++ "network_netmask_match: tok=%s is a TTY", tok);
++ return NO;
++ }
++
+ /*
+- * It is either an IP address or a hostname.
++ * It is most likely a hostname.
+ * Let getaddrinfo sort everything out
+ */
+ if (getaddrinfo (tok, NULL, NULL, &ai) != 0)
+--
+2.43.5
+
diff --git a/0004-FIX-CVE-2024-22365.patch b/0004-FIX-CVE-2024-22365.patch
new file mode 100644
index 0000000000000000000000000000000000000000..dfecc927f93507a16dd4d588e741d138a42b448e
--- /dev/null
+++ b/0004-FIX-CVE-2024-22365.patch
@@ -0,0 +1,39 @@
+diff --git a/modules/pam_namespace/pam_namespace.c b/modules/pam_namespace/pam_namespace.c
+index f34ce93..ef85644 100644
+--- a/modules/pam_namespace/pam_namespace.c
++++ b/modules/pam_namespace/pam_namespace.c
+@@ -1194,7 +1194,7 @@ static int protect_dir(const char *path, mode_t mode, int do_mkdir,
+ int dfd = AT_FDCWD;
+ int dfd_next;
+ int save_errno;
+- int flags = O_RDONLY;
++ int flags = O_RDONLY | O_DIRECTORY;
+ int rv = -1;
+ struct stat st;
+
+@@ -1248,22 +1248,6 @@ static int protect_dir(const char *path, mode_t mode, int do_mkdir,
+ rv = openat(dfd, dir, flags);
+ }
+
+- if (rv != -1) {
+- if (fstat(rv, &st) != 0) {
+- save_errno = errno;
+- close(rv);
+- rv = -1;
+- errno = save_errno;
+- goto error;
+- }
+- if (!S_ISDIR(st.st_mode)) {
+- close(rv);
+- errno = ENOTDIR;
+- rv = -1;
+- goto error;
+- }
+- }
+-
+ if (flags & O_NOFOLLOW) {
+ /* we are inside user-owned dir - protect */
+ if (protect_mount(rv, p, idata) == -1) {
+--
+2.43.5
+
diff --git a/0006-FIX-CVE-2025-6020-1.patch b/0006-FIX-CVE-2025-6020-1.patch
new file mode 100644
index 0000000000000000000000000000000000000000..42d12935c99d57e368d124bfa580f81a75ae76a5
--- /dev/null
+++ b/0006-FIX-CVE-2025-6020-1.patch
@@ -0,0 +1,1128 @@
+From df1dab1a1a7900650ad4be157fea1a002048cc49 Mon Sep 17 00:00:00 2001
+From: Olivier Bal-Petre
+Date: Tue, 4 Mar 2025 14:37:02 +0100
+Subject: [PATCH 1/3] pam_namespace: fix potential privilege escalation
+
+Existing protection provided by protect_dir() and protect_mount() were
+bind mounting on themselves all directories part of the to-be-secured
+paths. However, this works *only* against attacks executed by processes
+in the same mount namespace as the one the mountpoint was created in.
+Therefore, a user with an out-of-mount-namespace access, or multiple
+users colluding, could exploit multiple race conditions, and, for
+instance, elevate their privileges to root.
+
+This commit keeps the existing protection as a defense in depth
+measure, and to keep the existing behavior of the module. However,
+it converts all the needed function calls to operate on file
+descriptors instead of absolute paths to protect against race
+conditions globally.
+
+Signed-off-by: Olivier Bal-Petre
+Signed-off-by: Dmitry V. Levin
+
+Conflict:NA
+Reference:https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/pam/1.5.3-5ubuntu5.4/pam_1.5.3-5ubuntu5.4.debian.tar.xz
+
+---
+ modules/pam_namespace/pam_namespace.c | 637 ++++++++++++++++++--------
+ modules/pam_namespace/pam_namespace.h | 11 +
+ 2 files changed, 458 insertions(+), 190 deletions(-)
+
+--- a/modules/pam_namespace/pam_namespace.c
++++ b/modules/pam_namespace/pam_namespace.c
+@@ -78,6 +78,8 @@ pam_snprintf(char *str, size_t size, con
+ ##__VA_ARGS__)
+
+
++#define MAGIC_LNK_FD_SIZE 64
++
+ /* --- evaluating all files in VENDORDIR/security/namespace.d and /etc/security/namespace.d --- */
+ static const char *base_name(const char *path)
+ {
+@@ -104,7 +106,7 @@ strip_trailing_slashes(char *str)
+ static int protect_mount(int dfd, const char *path, struct instance_data *idata)
+ {
+ struct protect_dir_s *dir = idata->protect_dirs;
+- char tmpbuf[64];
++ char tmpbuf[MAGIC_LNK_FD_SIZE];
+
+ while (dir != NULL) {
+ if (strcmp(path, dir->dir) == 0) {
+@@ -149,56 +151,107 @@ static int protect_mount(int dfd, const
+ return 0;
+ }
+
+-static int protect_dir(const char *path, mode_t mode, int do_mkdir,
++/*
++ * Returns a fd to the given absolute path, acquired securely. This means:
++ * - iterating on each segment of the path,
++ * - not following user symlinks,
++ * - using race-free operations.
++ *
++ * Takes a bit mask to specify the operation mode:
++ * - SECURE_OPENDIR_PROTECT: call protect_mount() on each unsafe segment of path
++ * - SECURE_OPENDIR_MKDIR: create last segment of path if does not exist
++ * - SECURE_OPENDIR_FULL_FD: open the directory with O_RDONLY instead of O_PATH,
++ * allowing more operations to be done with the returned fd
++ *
++ * Be aware that using SECURE_OPENDIR_PROTECT:
++ * - will modify some external state (global structure...) and should not be
++ * called in cleanup code paths. See wrapper secure_opendir_stateless()
++ * - need a non-NULL idata to call protect_mount()
++ */
++static int secure_opendir(const char *path, int opm, mode_t mode,
+ struct instance_data *idata)
+ {
+- char *p = strdup(path);
++ char *p;
+ char *d;
+- char *dir = p;
+- int dfd = AT_FDCWD;
++ char *dir;
++ int dfd = -1;
+ int dfd_next;
+ int save_errno;
+- int flags = O_RDONLY | O_DIRECTORY;
++ int flags = O_DIRECTORY | O_CLOEXEC;
+ int rv = -1;
+ struct stat st;
+
+- if (p == NULL) {
++ if (opm & SECURE_OPENDIR_FULL_FD)
++ flags |= O_RDONLY;
++ else
++ flags |= O_PATH;
++
++ /* Check for args consistency */
++ if ((opm & SECURE_OPENDIR_PROTECT) && idata == NULL)
+ return -1;
+- }
+
+- if (*dir == '/') {
+- dfd = open("/", flags);
+- if (dfd == -1) {
+- goto error;
+- }
+- dir++; /* assume / is safe */
++ /* Accept only absolute paths */
++ if (*path != '/')
++ return -1;
++
++ dir = p = strdup(path);
++ if (p == NULL)
++ return -1;
++
++ /* Assume '/' is safe */
++ dfd = open("/", flags);
++ if (dfd == -1)
++ goto error;
++
++ /* Needed to not loop too far and call openat() on NULL */
++ strip_trailing_slashes(p);
++
++ dir++;
++
++ /* In case path is '/' */
++ if (*dir == '\0') {
++ free(p);
++ return dfd;
+ }
+
+ while ((d=strchr(dir, '/')) != NULL) {
+ *d = '\0';
++
+ dfd_next = openat(dfd, dir, flags);
+- if (dfd_next == -1) {
++ if (dfd_next == -1)
+ goto error;
+- }
+-
+- if (dfd != AT_FDCWD)
+- close(dfd);
+- dfd = dfd_next;
+
+- if (fstat(dfd, &st) != 0) {
++ if (fstat(dfd_next, &st) != 0) {
++ close(dfd_next);
+ goto error;
+ }
+
+- if (flags & O_NOFOLLOW) {
++ if ((flags & O_NOFOLLOW) && (opm & SECURE_OPENDIR_PROTECT)) {
+ /* we are inside user-owned dir - protect */
+- if (protect_mount(dfd, p, idata) == -1)
++ if (protect_mount(dfd_next, p, idata) == -1) {
++ close(dfd_next);
++ goto error;
++ }
++ /*
++ * Reopen the directory to obtain a new descriptor
++ * after protect_mount(), this is necessary in cases
++ * when another directory is going to be mounted over
++ * the given path.
++ */
++ close(dfd_next);
++ dfd_next = openat(dfd, dir, flags);
++ if (dfd_next == -1)
+ goto error;
+- } else if (st.st_uid != 0 || st.st_gid != 0 ||
+- (st.st_mode & S_IWOTH)) {
++ } else if (st.st_uid != 0
++ || (st.st_gid != 0 && (st.st_mode & S_IWGRP))
++ || (st.st_mode & S_IWOTH)) {
+ /* do not follow symlinks on subdirectories */
+ flags |= O_NOFOLLOW;
+ }
+
++ close(dfd);
++ dfd = dfd_next;
++
+ *d = '/';
+ dir = d + 1;
+ }
+@@ -206,13 +259,14 @@ static int protect_dir(const char *path,
+ rv = openat(dfd, dir, flags);
+
+ if (rv == -1) {
+- if (!do_mkdir || mkdirat(dfd, dir, mode) != 0) {
++ if ((opm & SECURE_OPENDIR_MKDIR) && mkdirat(dfd, dir, mode) == 0)
++ rv = openat(dfd, dir, flags);
++
++ if (rv == -1)
+ goto error;
+- }
+- rv = openat(dfd, dir, flags);
+ }
+
+- if (flags & O_NOFOLLOW) {
++ if ((flags & O_NOFOLLOW) && (opm & SECURE_OPENDIR_PROTECT)) {
+ /* we are inside user-owned dir - protect */
+ if (protect_mount(rv, p, idata) == -1) {
+ save_errno = errno;
+@@ -220,18 +274,95 @@ static int protect_dir(const char *path,
+ rv = -1;
+ errno = save_errno;
+ }
++ /*
++ * Reopen the directory to obtain a new descriptor after
++ * protect_mount(), this is necessary in cases when another
++ * directory is going to be mounted over the given path.
++ */
++ close(rv);
++ rv = openat(dfd, dir, flags);
+ }
+
+ error:
+ save_errno = errno;
+ free(p);
+- if (dfd != AT_FDCWD && dfd >= 0)
++ if (dfd >= 0)
+ close(dfd);
+ errno = save_errno;
+
+ return rv;
+ }
+
++/*
++ * Returns a fd to the given path, acquired securely.
++ * It can be called in all situations, including in cleanup code paths, as
++ * it does not modify external state (no access to global structures...).
++ */
++static int secure_opendir_stateless(const char *path)
++{
++ return secure_opendir(path, 0, 0, NULL);
++}
++
++/*
++ * Umount securely the given path, even if the directories along
++ * the path are under user control. It should protect against
++ * symlinks attacks and race conditions.
++ */
++static int secure_umount(const char *path)
++{
++ int save_errno;
++ int rv = -1;
++ int dfd = -1;
++ char s_path[MAGIC_LNK_FD_SIZE];
++
++ dfd = secure_opendir_stateless(path);
++ if (dfd == -1)
++ return rv;
++
++ if (pam_sprintf(s_path, "/proc/self/fd/%d", dfd) < 0)
++ goto error;
++
++ /*
++ * We still have a fd open to path itself,
++ * so we need to do a lazy umount.
++ */
++ rv = umount2(s_path, MNT_DETACH);
++
++error:
++ save_errno = errno;
++ close(dfd);
++ errno = save_errno;
++ return rv;
++}
++
++/*
++ * Rmdir the given path securely, protecting against symlinks attacks
++ * and race conditions.
++ * This function is currently called only in cleanup code paths where
++ * any errors returned are not handled, so do not handle them either.
++ * Basically, try to rmdir the path on a best-effort basis.
++ */
++static void secure_try_rmdir(const char *path)
++{
++ int dfd;
++ char *buf;
++ char *parent;
++
++ buf = strdup(path);
++ if (buf == NULL)
++ return;
++
++ parent = dirname(buf);
++
++ dfd = secure_opendir_stateless(parent);
++ if (dfd >= 0) {
++ unlinkat(dfd, base_name(path), AT_REMOVEDIR);
++ close(dfd);
++ }
++
++ free(buf);
++}
++
+ /* Evaluating a list of files which have to be parsed in the right order:
+ *
+ * - If etc/security/namespace.d/@filename@.conf exists, then
+@@ -357,7 +488,7 @@ static void unprotect_dirs(struct protec
+ struct protect_dir_s *next;
+
+ while (dir != NULL) {
+- umount(dir->dir);
++ secure_umount(dir->dir);
+ free(dir->dir);
+ next = dir->next;
+ free(dir);
+@@ -761,13 +892,9 @@ static int process_line(char *line, cons
+ goto skipping;
+ }
+
+-#define COPY_STR(dst, src, apd) \
+- pam_sprintf((dst), "%s%s", (src), (apd))
+-
+- if (COPY_STR(poly->dir, dir, "") < 0
+- || COPY_STR(poly->rdir, rdir, "") < 0
+- || COPY_STR(poly->instance_prefix, instance_prefix,
+- poly->method == TMPDIR ? "XXXXXX" : "") < 0) {
++ 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;
+ }
+@@ -1050,6 +1177,23 @@ static char *md5hash(const char *instnam
+ }
+
+ #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)
+@@ -1061,12 +1205,9 @@ static int form_context(const struct pol
+ /*
+ * 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;
+
+@@ -1163,29 +1304,52 @@ static int form_context(const struct pol
+ #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;
+@@ -1219,7 +1383,7 @@ static int poly_name(const struct polydi
+
+ switch (pm) {
+ case USER:
+- if ((*i_name = strdup(idata->user)) == NULL)
++ if ((inst_differentiation = strdup(idata->user)) == NULL)
+ goto fail;
+ break;
+
+@@ -1231,20 +1395,24 @@ static int poly_name(const struct polydi
+ goto fail;
+ }
+ if (polyptr->flags & POLYDIR_SHARED)
+- *i_name = strdup(rawcon);
++ inst_differentiation = strdup(rawcon);
+ else
+- *i_name = pam_asprintf("%s_%s", rawcon, idata->user);
+- if (*i_name == NULL)
++ inst_differentiation = pam_asprintf("%s_%s", rawcon, idata->user);
++ if (inst_differentiation == NULL)
+ goto fail;
+ break;
+
+ #endif /* WITH_SELINUX */
+
+ case TMPDIR:
++ if ((inst_differentiation = strdup("XXXXXX")) == NULL)
++ goto fail;
++ goto success;
++
+ case TMPFS:
+- if ((*i_name=strdup("")) == NULL)
++ if ((inst_differentiation=strdup("")) == NULL)
+ goto fail;
+- return PAM_SUCCESS;
++ goto success;
+
+ default:
+ if (idata->flags & PAMNS_DEBUG)
+@@ -1253,32 +1421,37 @@ static int poly_name(const struct polydi
+ }
+
+ 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 =
+ pam_asprintf("%.*s_%s",
+ NAMESPACE_MAX_DIR_LEN - 1 - (int)strlen(hash),
+- *i_name, 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
+@@ -1289,55 +1462,35 @@ fail:
+ freecon(*origcon);
+ *origcon = NULL;
+ #endif
+- free(*i_name);
+- *i_name = NULL;
+ }
+ return rc;
+ }
+
+-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';
+-
+- dfd = protect_dir(inst_parent, 0, 1, idata);
++ if (idata->flags & PAMNS_IGN_INST_PARENT_MODE)
++ return PAM_SUCCESS;
+
+- 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;
+ }
+
+@@ -1475,14 +1628,16 @@ static int create_polydir(struct polydir
+ }
+ #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
+@@ -1503,9 +1658,9 @@ static int create_polydir(struct polydir
+ 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;
+ }
+ }
+
+@@ -1523,42 +1678,38 @@ static int create_polydir(struct polydir
+ 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
+ * attributes to match that of the original directory that is being
+@@ -1566,29 +1717,39 @@ static int create_instance(struct polydi
+ */
+
+ 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
+@@ -1598,17 +1759,19 @@ static int create_instance(struct polydi
+ 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;
+ }
+ }
+@@ -1616,9 +1779,9 @@ static int create_instance(struct polydi
+ #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 ||
+@@ -1626,17 +1789,17 @@ static int create_instance(struct polydi
+ 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);
+@@ -1655,9 +1818,12 @@ static int ns_setup(struct polydir_s *po
+ 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;
+@@ -1667,37 +1833,48 @@ static int ns_setup(struct polydir_s *po
+ 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) {
++ 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;
+ }
+- if (create_polydir(polyptr, idata) != PAM_SUCCESS)
++ dfd_pptrdir = create_polydir(polyptr, idata);
++ if (dfd_pptrdir < 0)
+ return PAM_SESSION_ERR;
+- } else {
+- close(retval);
+ }
+
+ 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;
++ }
++
++ retval = inst_init(polyptr, "tmpfs", idata, 1);
++ goto cleanup;
+ }
+
+- if (stat(polyptr->dir, &statbuf) < 0) {
+- pam_syslog(idata->pamh, LOG_ERR, "Error stating %s: %m",
+- polyptr->dir);
+- return PAM_SESSION_ERR;
++ if (fstat(dfd_pptrdir, &statbuf) < 0) {
++ pam_syslog(idata->pamh, LOG_ERR, "Error stating %s: %m", polyptr->dir);
++ goto error_out;
+ }
+
+ /*
+@@ -1706,15 +1883,16 @@ static int ns_setup(struct polydir_s *po
+ * security policy.
+ */
+ #ifdef WITH_SELINUX
+- retval = poly_name(polyptr, &instname, &instcontext,
+- &origcontext, idata);
++ retval = poly_name(polyptr, &instcontext, &origcontext, idata);
+ #else
+- retval = poly_name(polyptr, &instname, idata);
++ retval = poly_name(polyptr, idata);
+ #endif
+
+ if (retval != PAM_SUCCESS) {
+- if (retval != PAM_IGNORE)
++ if (retval != PAM_IGNORE) {
+ pam_syslog(idata->pamh, LOG_ERR, "Error getting instance name");
++ goto error_out;
++ }
+ goto cleanup;
+ } else {
+ #ifdef WITH_SELINUX
+@@ -1725,22 +1903,33 @@ static int ns_setup(struct polydir_s *po
+ #endif
+ }
+
+- if ((inst_dir = pam_asprintf("%s%s", polyptr->instance_prefix, instname)) == NULL)
+- goto error_out;
+-
+- if (idata->flags & PAMNS_DEBUG)
+- pam_syslog(idata->pamh, LOG_DEBUG, "instance_dir %s",
+- inst_dir);
++ /*
++ * Gets a fd in a secure manner (we may be operating on a path under
++ * user control), and check it's compliant.
++ * Then, we should *always* operate on *this* fd and a relative path
++ * to be protected against race conditions.
++ */
++ dfd_iparent = secure_opendir(polyptr->instance_parent,
++ SECURE_OPENDIR_PROTECT | SECURE_OPENDIR_MKDIR, 0, idata);
++ if (dfd_iparent == -1) {
++ pam_syslog(idata->pamh, LOG_ERR,
++ "polyptr->instance_parent %s access error",
++ polyptr->instance_parent);
++ goto error_out;
++ }
++ if (check_inst_parent(dfd_iparent, idata)) {
++ goto error_out;
++ }
+
+ /*
+ * Create instance directory with appropriate security
+ * contexts, owner, group and mode bits.
+ */
+ #ifdef WITH_SELINUX
+- retval = create_instance(polyptr, inst_dir, &statbuf, instcontext,
+- origcontext, idata);
++ retval = create_instance(polyptr, dfd_iparent, &statbuf, instcontext,
++ origcontext, idata);
+ #else
+- retval = create_instance(polyptr, inst_dir, &statbuf, idata);
++ retval = create_instance(polyptr, dfd_iparent, &statbuf, idata);
+ #endif
+
+ if (retval == PAM_IGNORE) {
+@@ -1753,18 +1942,47 @@ static int ns_setup(struct polydir_s *po
+ }
+
+ /*
++ * Instead of getting a new secure fd, we reuse the fd opened on directory
++ * polyptr->instance_parent to ensure we are working on the same dir as
++ * previously, and thus ensure that previous checks (e.g. check_inst_parent())
++ * are still relevant.
++ */
++ dfd_ipath = openat(dfd_iparent, polyptr->instname,
++ O_PATH | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
++ if (dfd_ipath == -1) {
++ pam_syslog(idata->pamh, LOG_ERR, "Error openat on %s, %m",
++ polyptr->instname);
++ goto error_out;
++ }
++
++ if (pam_sprintf(s_ipath, "/proc/self/fd/%d", dfd_ipath) < 0) {
++ pam_syslog(idata->pamh, LOG_ERR, "Error pam_sprintf s_ipath");
++ goto error_out;
++ }
++
++ 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;
++ }
++
++ /*
+ * Bind mount instance directory on top of the polyinstantiated
+ * directory to provide an instance of polyinstantiated directory
+ * based on polyinstantiated method.
++ *
++ * Operates on magic links created from two fd obtained securely
++ * to protect against race conditions and symlink attacks. Indeed,
++ * the source and destination can be in a user controled path.
+ */
+- if (mount(inst_dir, polyptr->dir, NULL, MS_BIND, NULL) < 0) {
+- pam_syslog(idata->pamh, LOG_ERR, "Error mounting %s on %s, %m",
+- inst_dir, polyptr->dir);
++ if(mount(s_ipath, s_pptrdir, NULL, MS_BIND, NULL) < 0) {
++ pam_syslog(idata->pamh, LOG_ERR,
++ "Error mounting %s on %s (%s on %s), %m",
++ s_ipath, s_pptrdir, polyptr->instance_absolute, polyptr->dir);
+ goto error_out;
+ }
+
+ if (!(polyptr->flags & POLYDIR_NOINIT))
+- retval = inst_init(polyptr, inst_dir, idata, newdir);
++ retval = inst_init(polyptr, polyptr->instance_absolute, idata, newdir);
+
+ goto cleanup;
+
+@@ -1776,8 +1994,12 @@ error_out:
+ retval = PAM_SESSION_ERR;
+
+ cleanup:
+- free(inst_dir);
+- free(instname);
++ if (dfd_iparent != -1)
++ close(dfd_iparent);
++ if (dfd_ipath != -1)
++ close(dfd_ipath);
++ if (dfd_pptrdir != -1)
++ close(dfd_pptrdir);
+ #ifdef WITH_SELINUX
+ freecon(instcontext);
+ freecon(origcontext);
+@@ -1816,6 +2038,7 @@ static int cleanup_tmpdirs(struct instan
+ {
+ struct polydir_s *pptr;
+ pid_t rc, pid;
++ int dfd = -1;
+ struct sigaction newsa, oldsa;
+ int status;
+
+@@ -1827,7 +2050,17 @@ static int cleanup_tmpdirs(struct instan
+ }
+
+ for (pptr = idata->polydirs_ptr; pptr; pptr = pptr->next) {
+- if (pptr->method == TMPDIR && access(pptr->instance_prefix, F_OK) == 0) {
++ if (pptr->method == TMPDIR) {
++
++ dfd = secure_opendir_stateless(pptr->instance_parent);
++ if (dfd == -1)
++ continue;
++
++ if (faccessat(dfd, pptr->instname, F_OK, AT_SYMLINK_NOFOLLOW) != 0) {
++ close(dfd);
++ continue;
++ }
++
+ pid = fork();
+ if (pid == 0) {
+ static char *envp[] = { NULL };
+@@ -1837,9 +2070,20 @@ static int cleanup_tmpdirs(struct instan
+ _exit(1);
+ }
+ #endif
+- execle("/bin/rm", "/bin/rm", "-rf", pptr->instance_prefix, NULL, envp);
++ if (fchdir(dfd) == -1) {
++ pam_syslog(idata->pamh, LOG_ERR, "Failed fchdir to %s: %m",
++ pptr->instance_absolute);
++ _exit(1);
++ }
++
++
++ execle("/bin/rm", "/bin/rm", "-rf", pptr->instname, NULL, envp);
+ _exit(1);
+ } else if (pid > 0) {
++
++ if (dfd != -1)
++ close(dfd);
++
+ while (((rc = waitpid(pid, &status, 0)) == (pid_t)-1) &&
+ (errno == EINTR));
+ if (rc == (pid_t)-1) {
+@@ -1852,6 +2096,10 @@ static int cleanup_tmpdirs(struct instan
+ "Error removing %s", pptr->instance_prefix);
+ }
+ } else if (pid < 0) {
++
++ if (dfd != -1)
++ close(dfd);
++
+ pam_syslog(idata->pamh, LOG_ERR,
+ "Cannot fork to cleanup temporary directory, %m");
+ rc = PAM_SESSION_ERR;
+@@ -1875,6 +2123,7 @@ out:
+ static int setup_namespace(struct instance_data *idata, enum unmnt_op unmnt)
+ {
+ int retval = 0, need_poly = 0, changing_dir = 0;
++ int dfd = -1;
+ char *cptr, *fptr, poly_parent[PATH_MAX];
+ struct polydir_s *pptr;
+
+@@ -1990,13 +2239,21 @@ static int setup_namespace(struct instan
+ strcpy(poly_parent, "/");
+ else if (cptr)
+ *cptr = '\0';
+- if (chdir(poly_parent) < 0) {
++
++ dfd = secure_opendir_stateless(poly_parent);
++ if (dfd == -1) {
++ pam_syslog(idata->pamh, LOG_ERR,
++ "Failed opening %s to fchdir: %m", poly_parent);
++ }
++ else if (fchdir(dfd) == -1) {
+ pam_syslog(idata->pamh, LOG_ERR,
+- "Can't chdir to %s, %m", poly_parent);
++ "Failed fchdir to %s: %m", poly_parent);
+ }
++ if (dfd != -1)
++ close(dfd);
+ }
+
+- if (umount(pptr->rdir) < 0) {
++ if (secure_umount(pptr->rdir) < 0) {
+ int saved_errno = errno;
+ pam_syslog(idata->pamh, LOG_ERR, "Unmount of %s failed, %m",
+ pptr->rdir);
+@@ -2066,7 +2323,7 @@ static int orig_namespace(struct instanc
+ "Unmounting instance dir for user %d & dir %s",
+ idata->uid, pptr->dir);
+
+- if (umount(pptr->dir) < 0) {
++ if (secure_umount(pptr->dir) < 0) {
+ pam_syslog(idata->pamh, LOG_ERR, "Unmount of %s failed, %m",
+ pptr->dir);
+ return PAM_SESSION_ERR;
+--- a/modules/pam_namespace/pam_namespace.h
++++ b/modules/pam_namespace/pam_namespace.h
+@@ -51,6 +51,7 @@
+ #include
+ #include
+ #include
++#include
+ #include
+ #include
+ #include
+@@ -122,6 +123,13 @@
+ #define NAMESPACE_PROTECT_DATA "pam_namespace:protect_data"
+
+ /*
++ * Operation mode for function secure_opendir()
++ */
++#define SECURE_OPENDIR_PROTECT 0x00000001
++#define SECURE_OPENDIR_MKDIR 0x00000002
++#define SECURE_OPENDIR_FULL_FD 0x00000004
++
++/*
+ * Polyinstantiation method options, based on user, security context
+ * or both
+ */
+@@ -158,6 +166,9 @@ struct polydir_s {
+ char dir[PATH_MAX]; /* directory to polyinstantiate */
+ char rdir[PATH_MAX]; /* directory to unmount (based on RUSER) */
+ char instance_prefix[PATH_MAX]; /* prefix for instance dir path name */
++ char instance_absolute[PATH_MAX]; /* absolute path to the instance dir (instance_parent + instname) */
++ char instance_parent[PATH_MAX]; /* parent dir of the instance dir */
++ char *instname; /* last segment of the path to the instance dir */
+ enum polymethod method; /* method used to polyinstantiate */
+ unsigned int num_uids; /* number of override uids */
+ uid_t *uid; /* list of override uids */
diff --git a/0006-FIX-CVE-2025-6020-2.patch b/0006-FIX-CVE-2025-6020-2.patch
new file mode 100644
index 0000000000000000000000000000000000000000..c9a6e393d77bc5f8802547f472b12742df7b6dba
--- /dev/null
+++ b/0006-FIX-CVE-2025-6020-2.patch
@@ -0,0 +1,180 @@
+From a195c0daaba325aa09180b9492bf78ba03c5af89 Mon Sep 17 00:00:00 2001
+From: Olivier Bal-Petre
+Date: Tue, 4 Mar 2025 14:37:02 +0100
+Subject: [PATCH 2/3] pam_namespace: add flags to indicate path safety
+
+Add two flags in the script to indicate if the paths to the polydir
+and the instance directories are safe (root owned and writable by
+root only).
+
+Signed-off-by: Olivier Bal-Petre
+Signed-off-by: Dmitry V. Levin
+
+Conflict:NA
+Reference:https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/pam/1.5.3-5ubuntu5.4/pam_1.5.3-5ubuntu5.4.debian.tar.xz
+
+---
+ modules/pam_namespace/namespace.init | 56 ++++++++++++-------
+ modules/pam_namespace/pam_namespace.c | 79 ++++++++++++++++++++++++++-
+ 2 files changed, 115 insertions(+), 20 deletions(-)
+
+--- a/modules/pam_namespace/namespace.init
++++ b/modules/pam_namespace/namespace.init
+@@ -1,25 +1,43 @@
+ #!/bin/sh
+-# It receives polydir path as $1, the instance path as $2,
+-# a flag whether the instance dir was newly created (0 - no, 1 - yes) in $3,
+-# and user name in $4.
++# It receives as arguments:
++# - $1 polydir path (see WARNING below)
++# - $2 instance path (see WARNING below)
++# - $3 flag whether the instance dir was newly created (0 - no, 1 - yes)
++# - $4 user name
++# - $5 flag whether the polydir path ($1) is safe (0 - unsafe, 1 -safe)
++# - $6 flag whether the instance path ($2) is safe (0 - unsafe, 1 - safe)
++#
++# WARNING: This script is invoked with full root privileges. Accessing
++# the polydir ($1) and the instance ($2) directories in this context may be
++# extremely dangerous as those can be under user control. The flags $5 and $6
++# are provided to let you know if all the segments part of the path (except the
++# last one) are owned by root and are writable by root only. If the path does
++# not meet these criteria, you expose yourself to possible symlink attacks when
++# accessing these path.
++# However, even if the path components are safe, the content of the
++# directories may still be owned/writable by a user, so care must be taken!
+ #
+ # The following section will copy the contents of /etc/skel if this is a
+ # newly created home directory.
+-if [ "$3" = 1 ]; then
+- # This line will fix the labeling on all newly created directories
+- [ -x /sbin/restorecon ] && /sbin/restorecon "$1"
+- user="$4"
+- passwd=$(getent passwd "$user")
+- homedir=$(echo "$passwd" | cut -f6 -d":")
+- if [ "$1" = "$homedir" ]; then
+- gid=$(echo "$passwd" | cut -f4 -d":")
+- cp -rT /etc/skel "$homedir"
+- chown -R "$user":"$gid" "$homedir"
+- mask=$(sed -E -n 's/^UMASK[[:space:]]+([^#[:space:]]+).*/\1/p' /etc/login.defs)
+- mode=$(printf "%o" $((0777 & ~mask)))
+- chmod ${mode:-700} "$homedir"
+- [ -x /sbin/restorecon ] && /sbin/restorecon -R "$homedir"
+- fi
+-fi
+
++# Executes only if the polydir path is safe
++if [ "$5" = 1 ]; then
++
++ if [ "$3" = 1 ]; then
++ # This line will fix the labeling on all newly created directories
++ [ -x /sbin/restorecon ] && /sbin/restorecon "$1"
++ user="$4"
++ passwd=$(getent passwd "$user")
++ homedir=$(echo "$passwd" | cut -f6 -d":")
++ if [ "$1" = "$homedir" ]; then
++ gid=$(echo "$passwd" | cut -f4 -d":")
++ cp -rT /etc/skel "$homedir"
++ chown -R "$user":"$gid" "$homedir"
++ mask=$(sed -E -n 's/^UMASK[[:space:]]+([^#[:space:]]+).*/\1/p' /etc/login.defs)
++ mode=$(printf "%o" $((0777 & ~mask)))
++ chmod ${mode:-700} "$homedir"
++ [ -x /sbin/restorecon ] && /sbin/restorecon -R "$homedir"
++ fi
++ fi
++fi
+ exit 0
+--- a/modules/pam_namespace/pam_namespace.c
++++ b/modules/pam_namespace/pam_namespace.c
+@@ -1495,6 +1495,79 @@ static int check_inst_parent(int dfd, st
+ }
+
+ /*
++ * 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
+ * execute it and pass directory to polyinstantiate and instance
+@@ -1553,7 +1626,11 @@ static int inst_init(const struct polydi
+ }
+
+ execle(init_script, init_script,
+- polyptr->dir, ipath, newdir?"1":"0", idata->user, NULL, envp);
++ 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) &&
diff --git a/0006-FIX-CVE-2025-6020-3.patch b/0006-FIX-CVE-2025-6020-3.patch
new file mode 100644
index 0000000000000000000000000000000000000000..7f43a1377dbfcd69add5817e3d858275b22ca537
--- /dev/null
+++ b/0006-FIX-CVE-2025-6020-3.patch
@@ -0,0 +1,30 @@
+From 2c978bab94a0a62e5b8bc0d52a777dca394d90cb Mon Sep 17 00:00:00 2001
+From: "Dmitry V. Levin"
+Date: Tue, 27 May 2025 08:00:00 +0000
+Subject: [PATCH 3/3] pam_namespace: secure_opendir: do not look at the group
+ ownership
+
+When the directory is not group-writable, the group ownership does
+not matter, and when it is group-writable, there should not be any
+exceptions for the root group as there is no guarantee that the root
+group does not include non-root users.
+
+Conflict:NA
+Reference:https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/pam/1.5.3-5ubuntu5.4/pam_1.5.3-5ubuntu5.4.debian.tar.xz
+
+---
+ modules/pam_namespace/pam_namespace.c | 3 +--
+ 1 file changed, 1 insertion(+), 2 deletions(-)
+
+--- a/modules/pam_namespace/pam_namespace.c
++++ b/modules/pam_namespace/pam_namespace.c
+@@ -243,8 +243,7 @@ static int secure_opendir(const char *pa
+ if (dfd_next == -1)
+ goto error;
+ } else if (st.st_uid != 0
+- || (st.st_gid != 0 && (st.st_mode & S_IWGRP))
+- || (st.st_mode & S_IWOTH)) {
++ || (st.st_mode & (S_IWGRP|S_IWOTH))) {
+ /* do not follow symlinks on subdirectories */
+ flags |= O_NOFOLLOW;
+ }
diff --git a/pam.spec b/pam.spec
index 855f735de9e1e7391f9e04b5e0aafee6e7da0132..ac5e68ad866b0015578f6779eed478e6f71cb03f 100644
--- a/pam.spec
+++ b/pam.spec
@@ -1,4 +1,4 @@
-%define anolis_release 2
+%define anolis_release 3
%global soname_version 0
Name: pam
@@ -31,6 +31,30 @@ Patch2: 0001-change-ndbm-to-gdbm.patch
Patch3: 0001-add-sm3-crypt-support.patch
# https://github.com/linux-pam/linux-pam/pull/686/commits/b3020da7da384d769f27a8713257fbe1001878be
Patch4: 0002-Fix-CVE-2024-10041.patch
+# https://github.com/linux-pam/linux-pam/commit/23393bef92c1e768eda329813d7af55481c6ca9f
+# https://github.com/linux-pam/linux-pam/commit/940747f88c16e029b69a74e80a2e94f65cb3e628
+Patch5: 0003-Fix-CVE-2024-10963.patch
+# https://github.com/linux-pam/linux-pam/commit/031bb5a5d0d950253b68138b498dc93be69a64cb
+Patch6: 0004-FIX-CVE-2024-22365.patch
+Patch7: pam_namespace_170.patch
+Patch8: pam_namespace_post170-01.patch
+Patch9: pam_namespace_post170-02.patch
+Patch10: pam_namespace_post170-03.patch
+Patch11: pam_namespace_post170-04.patch
+Patch12: pam_namespace_post170-05.patch
+Patch13: pam_namespace_post170-06.patch
+Patch14: pam_namespace_post170-07.patch
+Patch15: pam_namespace_post170-08.patch
+Patch16: pam_namespace_post170-09.patch
+Patch17: pam_namespace_post170-10.patch
+Patch18: pam_namespace_post170-11.patch
+Patch19: pam_namespace_post170-12.patch
+Patch20: pam_namespace_post170-13.patch
+Patch21: pam_namespace_post170-14.patch
+Patch22: pam_namespace_revert_abi.patch
+Patch23: 0006-FIX-CVE-2025-6020-1.patch
+Patch24: 0006-FIX-CVE-2025-6020-2.patch
+Patch25: 0006-FIX-CVE-2025-6020-3.patch
BuildRequires: audit-libs-devel
BuildRequires: autoconf
@@ -345,6 +369,9 @@ done
%{abidir}/libpam*.dump
%changelog
+* Tue Aug 19 2025 wh02252983 - 1.5.3-3
+- add patch to Fix CVE-2024-10963 CVE-2024-22365 CVE-2025-6020
+
* Tue May 20 2025 wenxin - 1.5.3-2
- Fix CVE-2024-10041
diff --git a/pam_namespace_170.patch b/pam_namespace_170.patch
new file mode 100644
index 0000000000000000000000000000000000000000..30a0a381750c9a76c189c4e290f67dd3f148712b
--- /dev/null
+++ b/pam_namespace_170.patch
@@ -0,0 +1,338 @@
+From 971a74d79b48a19ff1446642f39b3c5a8a7db247 Mon Sep 17 00:00:00 2001
+From: Marc Deslauriers
+Date: Tue, 18 Feb 2025 08:00:00 +0000
+Subject: [PATCH] backport pam_namespace module from 1.7.0
+
+Changes to 1.7.0 for backport:
+- don't rename SCONFIGDIR to SCONFIG_DIR
+- dropped documentation changes so manpages don't regenerate
+
+Conflict:Delete the modification of 031bb5a5d0d950253b68138b498dc93be69a64cbbecause it was merged early.
+Reference:https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/pam/1.5.3-5ubuntu5.4/pam_1.5.3-5ubuntu5.4.debian.tar.xz
+
+--- a/modules/pam_namespace/argv_parse.c
++++ b/modules/pam_namespace/argv_parse.c
+@@ -28,6 +28,9 @@
+ * Version 1.1, modified 2/27/1999
+ */
+
++#include "config.h"
++
++#include
+ #include
+ #include
+ #include
+@@ -56,16 +59,21 @@ int argv_parse(const char *in_buf, int *
+ outcp = buf;
+ for (cp = in_buf; (ch = *cp); cp++) {
+ if (state == STATE_WHITESPACE) {
+- if (isspace((int) ch))
++ if (isspace((unsigned char)ch))
+ continue;
+ /* Not whitespace, so start a new token */
+ state = STATE_TOKEN;
+ if (argc >= max_argc) {
++ if (max_argc >= INT_MAX - 3) {
++ free(argv);
++ free(buf);
++ return -1;
++ }
+ max_argc += 3;
+ new_argv = realloc(argv,
+ (max_argc+1)*sizeof(char *));
+ if (!new_argv) {
+- if (argv) free(argv);
++ free(argv);
+ free(buf);
+ return -1;
+ }
+@@ -81,7 +89,7 @@ int argv_parse(const char *in_buf, int *
+ continue;
+ }
+ /* Must be processing characters in a word */
+- if (isspace((int) ch)) {
++ if (isspace((unsigned char)ch)) {
+ /*
+ * Terminate the current word and start
+ * looking for the beginning of the next word.
+@@ -131,8 +139,7 @@ int argv_parse(const char *in_buf, int *
+ void argv_free(char **argv)
+ {
+ if (argv) {
+- if (*argv)
+- free(*argv);
++ free(*argv);
+ free(argv);
+ }
+ }
+--- a/modules/pam_namespace/namespace.init
++++ b/modules/pam_namespace/namespace.init
+@@ -15,7 +15,7 @@ if [ "$3" = 1 ]; then
+ gid=$(echo "$passwd" | cut -f4 -d":")
+ cp -rT /etc/skel "$homedir"
+ chown -R "$user":"$gid" "$homedir"
+- mask=$(awk '/^UMASK/{gsub("#.*$", "", $2); print $2; exit}' /etc/login.defs)
++ mask=$(sed -E -n 's/^UMASK[[:space:]]+([^#[:space:]]+).*/\1/p' /etc/login.defs)
+ mode=$(printf "%o" $((0777 & ~mask)))
+ chmod ${mode:-700} "$homedir"
+ [ -x /sbin/restorecon ] && /sbin/restorecon -R "$homedir"
+--- a/modules/pam_namespace/pam_namespace.c
++++ b/modules/pam_namespace/pam_namespace.c
+@@ -34,12 +34,14 @@
+
+ #define _ATFILE_SOURCE
+
++#include "config.h"
++#include
+ #include "pam_cc_compat.h"
+ #include "pam_inline.h"
+ #include "pam_namespace.h"
+ #include "argv_parse.h"
+
+-/* --- evaluting all files in VENDORDIR/security/namespace.d and /etc/security/namespace.d --- */
++/* --- evaluating all files in VENDORDIR/security/namespace.d and /etc/security/namespace.d --- */
+ static const char *base_name(const char *path)
+ {
+ const char *base = strrchr(path, '/');
+@@ -53,6 +55,14 @@ compare_filename(const void *a, const vo
+ base_name(* (char * const *) b));
+ }
+
++static void close_fds_pre_exec(struct instance_data *idata)
++{
++ if (pam_modutil_sanitize_helper_fds(idata->pamh, PAM_MODUTIL_IGNORE_FD,
++ PAM_MODUTIL_IGNORE_FD, PAM_MODUTIL_IGNORE_FD) < 0) {
++ _exit(1);
++ }
++}
++
+ /* Evaluating a list of files which have to be parsed in the right order:
+ *
+ * - If etc/security/namespace.d/@filename@.conf exists, then
+@@ -196,7 +206,7 @@ static void cleanup_protect_data(pam_han
+ unprotect_dirs(data);
+ }
+
+-static char *expand_variables(const char *orig, const char *var_names[], const char *var_values[])
++static char *expand_variables(const char *orig, const char *const var_names[], const char *var_values[])
+ {
+ const char *src = orig;
+ char *dst;
+@@ -207,7 +217,7 @@ static char *expand_variables(const char
+ if (*src == '$') {
+ int i;
+ for (i = 0; var_names[i]; i++) {
+- int namelen = strlen(var_names[i]);
++ size_t namelen = strlen(var_names[i]);
+ if (strncmp(var_names[i], src+1, namelen) == 0) {
+ dstlen += strlen(var_values[i]) - 1; /* $ */
+ src += namelen;
+@@ -225,7 +235,7 @@ static char *expand_variables(const char
+ if (c == '$') {
+ int i;
+ for (i = 0; var_names[i]; i++) {
+- int namelen = strlen(var_names[i]);
++ size_t namelen = strlen(var_names[i]);
+ if (strncmp(var_names[i], src+1, namelen) == 0) {
+ dst = stpcpy(dst, var_values[i]);
+ --dst;
+@@ -392,9 +402,9 @@ static int parse_method(char *method, st
+ {
+ enum polymethod pm;
+ char *sptr = NULL;
+- static const char *method_names[] = { "user", "context", "level", "tmpdir",
++ static const char *const method_names[] = { "user", "context", "level", "tmpdir",
+ "tmpfs", NULL };
+- static const char *flag_names[] = { "create", "noinit", "iscript",
++ static const char *const flag_names[] = { "create", "noinit", "iscript",
+ "shared", "mntopts", NULL };
+ static const unsigned int flag_values[] = { POLYDIR_CREATE, POLYDIR_NOINIT,
+ POLYDIR_ISCRIPT, POLYDIR_SHARED, POLYDIR_MNTOPTS };
+@@ -419,7 +429,7 @@ static int parse_method(char *method, st
+
+ while ((flag=strtok_r(NULL, ":", &sptr)) != NULL) {
+ for (i = 0; flag_names[i]; i++) {
+- int namelen = strlen(flag_names[i]);
++ size_t namelen = strlen(flag_names[i]);
+
+ if (strncmp(flag, flag_names[i], namelen) == 0) {
+ poly->flags |= flag_values[i];
+@@ -465,7 +475,7 @@ static int parse_method(char *method, st
+ * of the namespace configuration file. It skips over comments and incomplete
+ * or malformed lines. It processes a valid line with information on
+ * polyinstantiating a directory by populating appropriate fields of a
+- * polyinstatiated directory structure and then calling add_polydir_entry to
++ * polyinstantiated directory structure and then calling add_polydir_entry to
+ * add that entry to the linked list of polyinstantiated directories.
+ */
+ static int process_line(char *line, const char *home, const char *rhome,
+@@ -477,15 +487,15 @@ static int process_line(char *line, cons
+ struct polydir_s *poly;
+ int retval = 0;
+ char **config_options = NULL;
+- static const char *var_names[] = {"HOME", "USER", NULL};
++ static const char *const var_names[] = {"HOME", "USER", NULL};
+ const char *var_values[] = {home, idata->user};
+ const char *rvar_values[] = {rhome, idata->ruser};
+- int len;
++ size_t len;
+
+ /*
+ * skip the leading white space
+ */
+- while (*line && isspace(*line))
++ while (*line && isspace((unsigned char)*line))
+ line++;
+
+ /*
+@@ -529,7 +539,7 @@ static int process_line(char *line, cons
+ instance_prefix = config_options[1];
+ if (instance_prefix == NULL) {
+ pam_syslog(idata->pamh, LOG_NOTICE, "Invalid line missing instance_prefix");
+- instance_prefix = NULL;
++ dir = NULL;
+ goto skipping;
+ }
+ method = config_options[2];
+@@ -592,26 +602,20 @@ static int process_line(char *line, cons
+ * Populate polyinstantiated directory structure with appropriate
+ * pathnames and the method with which to polyinstantiate.
+ */
+- if (strlen(dir) >= sizeof(poly->dir)
+- || strlen(rdir) >= sizeof(poly->rdir)
+- || strlen(instance_prefix) >= sizeof(poly->instance_prefix)) {
+- pam_syslog(idata->pamh, LOG_NOTICE, "Pathnames too long");
+- goto skipping;
+- }
+- strcpy(poly->dir, dir);
+- strcpy(poly->rdir, rdir);
+- strcpy(poly->instance_prefix, instance_prefix);
+-
+ if (parse_method(method, poly, idata) != 0) {
+ goto skipping;
+ }
+
+- if (poly->method == TMPDIR) {
+- if (sizeof(poly->instance_prefix) - strlen(poly->instance_prefix) < 7) {
+- pam_syslog(idata->pamh, LOG_NOTICE, "Pathnames too long");
+- goto skipping;
+- }
+- strcat(poly->instance_prefix, "XXXXXX");
++#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" : "")) {
++ pam_syslog(idata->pamh, LOG_NOTICE, "Pathnames too long");
++ goto skipping;
+ }
+
+ /*
+@@ -635,7 +639,7 @@ static int process_line(char *line, cons
+ if (uids) {
+ uid_t *uidptr;
+ const char *ustr, *sstr;
+- int count, i;
++ size_t count, i;
+
+ if (*uids == '~') {
+ poly->flags |= POLYDIR_EXCLUSIVE;
+@@ -644,8 +648,13 @@ static int process_line(char *line, cons
+ for (count = 0, ustr = sstr = uids; sstr; ustr = sstr + 1, count++)
+ sstr = strchr(ustr, ',');
+
++ if (count > UINT_MAX || count > SIZE_MAX / sizeof(uid_t)) {
++ pam_syslog(idata->pamh, LOG_ERR, "Too many uids encountered in configuration");
++ goto skipping;
++ }
++
+ poly->num_uids = count;
+- poly->uid = (uid_t *) malloc(count * sizeof (uid_t));
++ poly->uid = malloc(count * sizeof (uid_t));
+ uidptr = poly->uid;
+ if (uidptr == NULL) {
+ goto erralloc;
+@@ -994,6 +1003,7 @@ static int form_context(const struct pol
+ return rc;
+ }
+ /* Should never get here */
++ freecon(scon);
+ return PAM_SUCCESS;
+ }
+ #endif
+@@ -1295,13 +1289,12 @@ static int check_inst_parent(char *ipath
+ * admin explicitly instructs to ignore the instance parent
+ * mode by the "ignore_instance_parent_mode" argument).
+ */
+- inst_parent = (char *) malloc(strlen(ipath)+1);
++ inst_parent = strdup(ipath);
+ if (!inst_parent) {
+ pam_syslog(idata->pamh, LOG_CRIT, "Error allocating pathname string");
+ return PAM_SESSION_ERR;
+ }
+
+- strcpy(inst_parent, ipath);
+ trailing_slash = strrchr(inst_parent, '/');
+ if (trailing_slash)
+ *trailing_slash = '\0';
+@@ -1386,6 +1379,8 @@ static int inst_init(const struct polydi
+ /* 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);
+@@ -1438,7 +1433,9 @@ static int create_polydir(struct polydir
+
+ #ifdef WITH_SELINUX
+ if (idata->flags & PAMNS_SELINUX_ENABLED) {
+- getfscreatecon_raw(&oldcon_raw);
++ if (getfscreatecon_raw(&oldcon_raw) != 0)
++ pam_syslog(idata->pamh, LOG_NOTICE,
++ "Error retrieving fs create context: %m");
+
+ label_handle = selabel_open(SELABEL_CTX_FILE, NULL, 0);
+ if (!label_handle) {
+@@ -1467,6 +1464,9 @@ static int create_polydir(struct polydir
+ 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;
+ }
+
+@@ -1824,6 +1824,7 @@ static int cleanup_tmpdirs(struct instan
+ _exit(1);
+ }
+ #endif
++ close_fds_pre_exec(idata);
+ if (execle("/bin/rm", "/bin/rm", "-rf", pptr->instance_prefix, NULL, envp) < 0)
+ _exit(1);
+ } else if (pid > 0) {
+@@ -1840,7 +1841,7 @@ static int cleanup_tmpdirs(struct instan
+ }
+ } else if (pid < 0) {
+ pam_syslog(idata->pamh, LOG_ERR,
+- "Cannot fork to run namespace init script, %m");
++ "Cannot fork to cleanup temporary directory, %m");
+ rc = PAM_SESSION_ERR;
+ goto out;
+ }
+--- a/modules/pam_namespace/pam_namespace.h
++++ b/modules/pam_namespace/pam_namespace.h
+@@ -114,7 +114,7 @@
+ #define PAMNS_MOUNT_PRIVATE 0x00080000 /* Make the polydir mounts private */
+
+ /* polydir flags */
+-#define POLYDIR_EXCLUSIVE 0x00000001 /* polyinstatiate exclusively for override uids */
++#define POLYDIR_EXCLUSIVE 0x00000001 /* polyinstantiate exclusively for override uids */
+ #define POLYDIR_CREATE 0x00000002 /* create the polydir */
+ #define POLYDIR_NOINIT 0x00000004 /* no init script */
+ #define POLYDIR_SHARED 0x00000008 /* share context/level instances among users */
diff --git a/pam_namespace_post170-01.patch b/pam_namespace_post170-01.patch
new file mode 100644
index 0000000000000000000000000000000000000000..e9faca2761fc089137230bbbee812e8c1691f0ba
--- /dev/null
+++ b/pam_namespace_post170-01.patch
@@ -0,0 +1,94 @@
+From 10b80543807e3fc5af5f8bcfd8bb6e219bb3cecc Mon Sep 17 00:00:00 2001
+From: "Dmitry V. Levin"
+Date: Tue, 18 Feb 2025 08:00:00 +0000
+Subject: [PATCH] pam_inline: introduce pam_asprintf(), pam_snprintf(), and
+ pam_sprintf()
+
+pam_asprintf() is essentially asprintf() with the following semantic
+difference: it returns the string itself instead of its length.
+
+pam_snprintf() is essentially snprintf() with the following semantic
+difference: it returns -1 in case of truncation.
+
+pam_sprintf() is essentially snprintf() but with a check that the buffer
+is an array, and with an automatically calculated buffer size.
+
+Use of these helpers would make error checking simpler.
+
+Backport of the following commit, but add the new functions to
+pam_namespace directly to limit the scope of changes.
+
+Conflict:NA
+Reference:https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/pam/1.5.3-5ubuntu5.4/pam_1.5.3-5ubuntu5.4.debian.tar.xz
+
+---
+ libpam/include/pam_cc_compat.h | 6 ++++++
+ libpam/include/pam_inline.h | 35 ++++++++++++++++++++++++++++++++++
+ 2 files changed, 41 insertions(+)
+
+--- a/libpam/include/pam_cc_compat.h
++++ b/libpam/include/pam_cc_compat.h
+@@ -21,6 +21,12 @@
+ # define PAM_ATTRIBUTE_ALIGNED(arg) /* empty */
+ #endif
+
++#if PAM_GNUC_PREREQ(3, 0)
++# define PAM_ATTRIBUTE_MALLOC __attribute__((__malloc__))
++#else
++# define PAM_ATTRIBUTE_MALLOC /* empty */
++#endif
++
+ #if PAM_GNUC_PREREQ(4, 6)
+ # define DIAG_PUSH_IGNORE_CAST_QUAL \
+ _Pragma("GCC diagnostic push"); \
+--- a/modules/pam_namespace/pam_namespace.c
++++ b/modules/pam_namespace/pam_namespace.c
+@@ -36,11 +36,48 @@
+
+ #include "config.h"
+ #include
++#include
++#include
+ #include "pam_cc_compat.h"
+ #include "pam_inline.h"
+ #include "pam_namespace.h"
+ #include "argv_parse.h"
+
++
++static char * PAM_FORMAT((printf, 1, 2)) PAM_NONNULL((1)) PAM_ATTRIBUTE_MALLOC
++pam_asprintf(const char *fmt, ...)
++{
++ int rc;
++ char *res;
++ va_list ap;
++
++ va_start(ap, fmt);
++ rc = vasprintf(&res, fmt, ap);
++ va_end(ap);
++
++ return rc < 0 ? NULL : res;
++}
++
++static int PAM_FORMAT((printf, 3, 4)) PAM_NONNULL((3))
++pam_snprintf(char *str, size_t size, const char *fmt, ...)
++{
++ int rc;
++ va_list ap;
++
++ va_start(ap, fmt);
++ rc = vsnprintf(str, size, fmt, ap);
++ va_end(ap);
++
++ if (rc < 0 || (unsigned int) rc >= size)
++ return -1;
++ return rc;
++}
++
++#define pam_sprintf(str_, fmt_, ...) \
++ pam_snprintf((str_), sizeof(str_) + PAM_MUST_BE_ARRAY(str_), (fmt_), \
++ ##__VA_ARGS__)
++
++
+ /* --- evaluating all files in VENDORDIR/security/namespace.d and /etc/security/namespace.d --- */
+ static const char *base_name(const char *path)
+ {
diff --git a/pam_namespace_post170-02.patch b/pam_namespace_post170-02.patch
new file mode 100644
index 0000000000000000000000000000000000000000..79cf8668012ba54ae5e1ee8a203a06588d5057a3
--- /dev/null
+++ b/pam_namespace_post170-02.patch
@@ -0,0 +1,45 @@
+From dc6242a1bf47aadd1cb3ab8572167969f48621c0 Mon Sep 17 00:00:00 2001
+From: Olivier Bal-Petre
+Date: Mon, 24 Feb 2025 10:09:21 +0100
+Subject: [PATCH] pam_namespace: fix logic in return value handling
+
+The case in which protect_dir() returns an error and the flag
+POLYDIR_CREATE (flag "create" in namespace.conf) is not set was
+not handled. Therefore, the program continued without a polydir
+and returned later on failed mount(2) or stat(2) calls.
+
+Signed-off-by: Olivier Bal-Petre
+
+Conflict:NA
+Reference:https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/pam/1.5.3-5ubuntu5.4/pam_1.5.3-5ubuntu5.4.debian.tar.xz
+
+---
+ modules/pam_namespace/pam_namespace.c | 16 +++++++---------
+ 1 file changed, 7 insertions(+), 9 deletions(-)
+
+--- a/modules/pam_namespace/pam_namespace.c
++++ b/modules/pam_namespace/pam_namespace.c
+@@ -1691,16 +1691,14 @@ static int ns_setup(struct polydir_s *po
+
+ retval = protect_dir(polyptr->dir, 0, 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;
++ if (errno != ENOENT || !(polyptr->flags & POLYDIR_CREATE)) {
++ pam_syslog(idata->pamh, LOG_ERR, "Polydir %s access error: %m",
++ polyptr->dir);
++ return PAM_SESSION_ERR;
++ }
++ if (create_polydir(polyptr, idata) != PAM_SUCCESS)
++ return PAM_SESSION_ERR;
+ } else {
+ close(retval);
+ }
diff --git a/pam_namespace_post170-03.patch b/pam_namespace_post170-03.patch
new file mode 100644
index 0000000000000000000000000000000000000000..e474847ab0a054b78123fc5ee01da3650794b74c
--- /dev/null
+++ b/pam_namespace_post170-03.patch
@@ -0,0 +1,86 @@
+From 07f1d987466b33780c7147d9d55e1a52425b5005 Mon Sep 17 00:00:00 2001
+From: "Dmitry V. Levin"
+Date: Wed, 19 Feb 2025 08:00:00 +0000
+Subject: [PATCH] treewide: cleanup: use pam_asprintf() instead of asprintf()
+
+Conflict:NA
+Reference:https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/pam/1.5.3-5ubuntu5.4/pam_1.5.3-5ubuntu5.4.debian.tar.xz
+
+---
+ modules/pam_namespace/pam_namespace.c | 35 ++++++++----------
+ 1 files changed, 14 insertions(+), 21 deletions(-)
+
+--- a/modules/pam_namespace/pam_namespace.c
++++ b/modules/pam_namespace/pam_namespace.c
+@@ -356,8 +356,7 @@ static int parse_iscript_params(char *pa
+
+ 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);
+ }
+@@ -1102,10 +1101,8 @@ static int poly_name(const struct polydi
+
+ switch (pm) {
+ case USER:
+- if (asprintf(i_name, "%s", idata->user) < 0) {
+- *i_name = NULL;
++ if ((*i_name = pam_asprintf("%s", idata->user)) == NULL)
+ goto fail;
+- }
+ break;
+
+ #ifdef WITH_SELINUX
+@@ -1115,17 +1112,12 @@ static int poly_name(const struct polydi
+ 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)
++ *i_name = pam_asprintf("%s", rawcon);
++ else
++ *i_name = pam_asprintf("%s_%s", rawcon, idata->user);
++ if (*i_name == NULL)
++ goto fail;
+ break;
+
+ #endif /* WITH_SELINUX */
+@@ -1155,11 +1147,12 @@ static int poly_name(const struct polydi
+ *i_name = 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),
++ *i_name, hash);
++ if (newname == NULL)
+ goto fail;
+- }
+ free(*i_name);
+ *i_name = newname;
+ }
+@@ -1747,7 +1740,7 @@ static int ns_setup(struct polydir_s *po
+ #endif
+ }
+
+- if (asprintf(&inst_dir, "%s%s", polyptr->instance_prefix, instname) < 0)
++ if ((inst_dir = pam_asprintf("%s%s", polyptr->instance_prefix, instname)) == NULL)
+ goto error_out;
+
+ if (idata->flags & PAMNS_DEBUG)
diff --git a/pam_namespace_post170-04.patch b/pam_namespace_post170-04.patch
new file mode 100644
index 0000000000000000000000000000000000000000..cf72fb8ca9a72f25d3c693d5d90dbbdac6233ee2
--- /dev/null
+++ b/pam_namespace_post170-04.patch
@@ -0,0 +1,41 @@
+From 1ed51a63a1d5f2d8673d31efffc5cf4b69bfbd32 Mon Sep 17 00:00:00 2001
+From: "Dmitry V. Levin"
+Date: Fri, 21 Feb 2025 08:00:00 +0000
+Subject: [PATCH] treewide: cleanup: use pam_sprintf() instead of snprintf()
+
+Conflict:NA
+Reference:https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/pam/1.5.3-5ubuntu5.4/pam_1.5.3-5ubuntu5.4.debian.tar.xz
+
+---
+ modules/pam_namespace/pam_namespace.c | 11 +++++------
+ 1 files changed, 5 insertions(+), 6 deletions(-)
+
+--- a/modules/pam_namespace/pam_namespace.c
++++ b/modules/pam_namespace/pam_namespace.c
+@@ -643,13 +643,12 @@ static int process_line(char *line, cons
+ }
+
+ #define COPY_STR(dst, src, apd) \
+- (snprintf((dst), sizeof(dst), "%s%s", (src), (apd)) != \
+- (ssize_t) (strlen(src) + strlen(apd)))
++ pam_sprintf((dst), "%s%s", (src), (apd))
+
+- if (COPY_STR(poly->dir, dir, "")
+- || COPY_STR(poly->rdir, rdir, "")
++ if (COPY_STR(poly->dir, dir, "") < 0
++ || COPY_STR(poly->rdir, rdir, "") < 0
+ || COPY_STR(poly->instance_prefix, instance_prefix,
+- poly->method == TMPDIR ? "XXXXXX" : "")) {
++ poly->method == TMPDIR ? "XXXXXX" : "") < 0) {
+ pam_syslog(idata->pamh, LOG_NOTICE, "Pathnames too long");
+ goto skipping;
+ }
+@@ -1202,7 +1201,7 @@ static int protect_mount(int dfd, const
+ return -1;
+ }
+
+- snprintf(tmpbuf, sizeof(tmpbuf), "/proc/self/fd/%d", dfd);
++ pam_sprintf(tmpbuf, "/proc/self/fd/%d", dfd);
+
+ if (idata->flags & PAMNS_DEBUG) {
+ pam_syslog(idata->pamh, LOG_INFO,
diff --git a/pam_namespace_post170-05.patch b/pam_namespace_post170-05.patch
new file mode 100644
index 0000000000000000000000000000000000000000..1968e37fe5c75f57a50e3250045632f7fc557b33
--- /dev/null
+++ b/pam_namespace_post170-05.patch
@@ -0,0 +1,33 @@
+From 8c781b46bb5c86dce1975a54d3397cd17c9ac553 Mon Sep 17 00:00:00 2001
+From: "Dmitry V. Levin"
+Date: Mon, 3 Mar 2025 08:00:00 +0000
+Subject: [PATCH] pam_namespace: cleanup: replace pam_asprintf("%s", ...) with
+ strdup(...)
+
+Conflict:NA
+Reference:https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/pam/1.5.3-5ubuntu5.4/pam_1.5.3-5ubuntu5.4.debian.tar.xz
+
+---
+ modules/pam_namespace/pam_namespace.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+--- a/modules/pam_namespace/pam_namespace.c
++++ b/modules/pam_namespace/pam_namespace.c
+@@ -1100,7 +1100,7 @@ static int poly_name(const struct polydi
+
+ switch (pm) {
+ case USER:
+- if ((*i_name = pam_asprintf("%s", idata->user)) == NULL)
++ if ((*i_name = strdup(idata->user)) == NULL)
+ goto fail;
+ break;
+
+@@ -1112,7 +1112,7 @@ static int poly_name(const struct polydi
+ goto fail;
+ }
+ if (polyptr->flags & POLYDIR_SHARED)
+- *i_name = pam_asprintf("%s", rawcon);
++ *i_name = strdup(rawcon);
+ else
+ *i_name = pam_asprintf("%s_%s", rawcon, idata->user);
+ if (*i_name == NULL)
diff --git a/pam_namespace_post170-06.patch b/pam_namespace_post170-06.patch
new file mode 100644
index 0000000000000000000000000000000000000000..779b63947c89e8d983bf51b1a54ceded34cf71fa
--- /dev/null
+++ b/pam_namespace_post170-06.patch
@@ -0,0 +1,38 @@
+From 3648239774c7276af31ae499be7679560a59ff79 Mon Sep 17 00:00:00 2001
+From: "Dmitry V. Levin"
+Date: Tue, 4 Mar 2025 08:00:00 +0000
+Subject: [PATCH] pam_namespace: cleanup: remove #include of unused headers
+
+Conflict:NA
+Reference:https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/pam/1.5.3-5ubuntu5.4/pam_1.5.3-5ubuntu5.4.debian.tar.xz
+
+---
+ modules/pam_namespace/pam_namespace.h | 5 -----
+ 1 file changed, 5 deletions(-)
+
+diff --git a/modules/pam_namespace/pam_namespace.h b/modules/pam_namespace/pam_namespace.h
+index 17999380d..46445aa62 100644
+--- a/modules/pam_namespace/pam_namespace.h
++++ b/modules/pam_namespace/pam_namespace.h
+@@ -44,21 +44,16 @@
+ #include
+ #include
+ #include
+-#include
+-#include
+ #include
+ #include
+ #include
+ #include
+ #include
+-#include
+ #include
+ #include
+-#include
+ #include
+ #include
+ #include
+-#include
+ #include "security/pam_modules.h"
+ #include "security/pam_modutil.h"
+ #include "security/pam_ext.h"
diff --git a/pam_namespace_post170-07.patch b/pam_namespace_post170-07.patch
new file mode 100644
index 0000000000000000000000000000000000000000..431567b824b5d8c55c1f2851c9ab61e2d29e3c65
--- /dev/null
+++ b/pam_namespace_post170-07.patch
@@ -0,0 +1,144 @@
+From 3db1fbfad402bedfd2177987cd260b79964ae8e4 Mon Sep 17 00:00:00 2001
+From: Olivier Bal-Petre
+Date: Tue, 4 Mar 2025 14:37:02 +0100
+Subject: [PATCH] pam_namespace: cleanup: reduce excessive nesting in
+ inst_init()
+
+Signed-off-by: Olivier Bal-Petre
+
+Conflict:NA
+Reference:https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/pam/1.5.3-5ubuntu5.4/pam_1.5.3-5ubuntu5.4.debian.tar.xz
+
+---
+ modules/pam_namespace/pam_namespace.c | 112 +++++++++++++-------------
+ 1 file changed, 56 insertions(+), 56 deletions(-)
+
+--- a/modules/pam_namespace/pam_namespace.c
++++ b/modules/pam_namespace/pam_namespace.c
+@@ -1378,68 +1378,68 @@ static int inst_init(const struct polydi
+ 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;
+-
+- 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 };
++ if (access(init_script, F_OK) != 0)
++ return PAM_SUCCESS;
++
++ 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;
++ }
++
++ 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);
+- }
++ 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;
++ /* 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;
+ }
+- return PAM_SUCCESS;
++ rc = PAM_SUCCESS;
++out:
++ (void) sigaction(SIGCHLD, &oldsa, NULL);
++ return rc;
+ }
+
+ static int create_polydir(struct polydir_s *polyptr,
diff --git a/pam_namespace_post170-08.patch b/pam_namespace_post170-08.patch
new file mode 100644
index 0000000000000000000000000000000000000000..7204b21ebd1baf25842ae0e93b0e04d698bc6307
--- /dev/null
+++ b/pam_namespace_post170-08.patch
@@ -0,0 +1,76 @@
+From ae6581bc81c12818353e98f4ad5b8f189852deb6 Mon Sep 17 00:00:00 2001
+From: "Dmitry V. Levin"
+Date: Wed, 5 Mar 2025 08:00:00 +0000
+Subject: [PATCH] pam_namespace: simplify error handling in process_line()
+
+Use different variables for string pointers with different memory
+allocation semantics.
+
+Conflict:NA
+Reference:https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/pam/1.5.3-5ubuntu5.4/pam_1.5.3-5ubuntu5.4.debian.tar.xz
+
+---
+ modules/pam_namespace/pam_namespace.c | 23 +++++++++--------------
+ 1 file changed, 9 insertions(+), 14 deletions(-)
+
+--- a/modules/pam_namespace/pam_namespace.c
++++ b/modules/pam_namespace/pam_namespace.c
+@@ -518,6 +518,7 @@ static int process_line(char *line, cons
+ 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;
+@@ -567,22 +568,19 @@ static int process_line(char *line, cons
+ 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;
+ }
+
+@@ -597,19 +595,16 @@ static int process_line(char *line, cons
+ /*
+ * 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;
+ }
+
diff --git a/pam_namespace_post170-09.patch b/pam_namespace_post170-09.patch
new file mode 100644
index 0000000000000000000000000000000000000000..25b8bff94e5bd93b5c2d2f5e469c2f3b8e1335e3
--- /dev/null
+++ b/pam_namespace_post170-09.patch
@@ -0,0 +1,60 @@
+From c27e22f33ce2e2145f40f1570aacab765d430171 Mon Sep 17 00:00:00 2001
+From: "Dmitry V. Levin"
+Date: Thu, 6 Mar 2025 08:00:00 +0000
+Subject: [PATCH] pam_namespace: introduce strip_trailing_slashes() helper
+
+Use strip_trailing_slashes() instead of implementing it in-place every
+time. Given that polydir is not permitted to be "/" anyway, strip not
+just a single trailing slash but all of them.
+
+Conflict:NA
+Reference:https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/pam/1.5.3-5ubuntu5.4/pam_1.5.3-5ubuntu5.4.debian.tar.xz
+
+---
+ modules/pam_namespace/pam_namespace.c | 21 +++++++++++----------
+ 1 file changed, 11 insertions(+), 10 deletions(-)
+
+--- a/modules/pam_namespace/pam_namespace.c
++++ b/modules/pam_namespace/pam_namespace.c
+@@ -506,6 +506,15 @@ static int parse_method(char *method, st
+ return 0;
+ }
+
++static void
++strip_trailing_slashes(char *str)
++{
++ char *p = str + strlen(str);
++
++ while (p-- > str && *p == '/')
++ *p = '\0';
++}
++
+ /*
+ * Called from parse_config_file, this function processes a single line
+ * of the namespace configuration file. It skips over comments and incomplete
+@@ -527,7 +536,6 @@ static int process_line(char *line, cons
+ 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
+@@ -614,15 +622,8 @@ static int process_line(char *line, cons
+ 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");
diff --git a/pam_namespace_post170-10.patch b/pam_namespace_post170-10.patch
new file mode 100644
index 0000000000000000000000000000000000000000..cb31b4abba7a7987fd0a87644790d9aaaea7557d
--- /dev/null
+++ b/pam_namespace_post170-10.patch
@@ -0,0 +1,23 @@
+From 40dea25961ba8d368be3d91d59ff72ecb5878de7 Mon Sep 17 00:00:00 2001
+From: "Dmitry V. Levin"
+Date: Fri, 7 Mar 2025 08:00:00 +0000
+Subject: [PATCH] pam_namespace: simplify error handling a bit in protect_dir()
+
+Conflict:NA
+Reference:https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/pam/1.5.3-5ubuntu5.4/pam_1.5.3-5ubuntu5.4.debian.tar.xz
+
+---
+ modules/pam_namespace/pam_namespace.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/modules/pam_namespace/pam_namespace.c
++++ b/modules/pam_namespace/pam_namespace.c
+@@ -1234,7 +1234,7 @@ static int protect_dir(const char *path,
+ struct stat st;
+
+ if (p == NULL) {
+- goto error;
++ return -1;
+ }
+
+ if (*dir == '/') {
diff --git a/pam_namespace_post170-11.patch b/pam_namespace_post170-11.patch
new file mode 100644
index 0000000000000000000000000000000000000000..f95760778dfd3eab3eba37069013a90bee3a8984
--- /dev/null
+++ b/pam_namespace_post170-11.patch
@@ -0,0 +1,26 @@
+From 3da5496be24031a5c806752d9f1892100346d8ff Mon Sep 17 00:00:00 2001
+From: "Dmitry V. Levin"
+Date: Mon, 24 Mar 2025 08:00:00 +0000
+Subject: [PATCH] pam_namespace: extend strip_trailing_slashes() helper
+
+Make strip_trailing_slashes() suitable for arbitrary path names
+by not stripping the first character.
+
+Conflict:NA
+Reference:https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/pam/1.5.3-5ubuntu5.4/pam_1.5.3-5ubuntu5.4.debian.tar.xz
+
+---
+ modules/pam_namespace/pam_namespace.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/modules/pam_namespace/pam_namespace.c
++++ b/modules/pam_namespace/pam_namespace.c
+@@ -511,7 +511,7 @@ strip_trailing_slashes(char *str)
+ {
+ char *p = str + strlen(str);
+
+- while (p-- > str && *p == '/')
++ while (--p > str && *p == '/')
+ *p = '\0';
+ }
+
diff --git a/pam_namespace_post170-12.patch b/pam_namespace_post170-12.patch
new file mode 100644
index 0000000000000000000000000000000000000000..dac94866e18473bc3775b97f2f6cff6d540162d1
--- /dev/null
+++ b/pam_namespace_post170-12.patch
@@ -0,0 +1,36 @@
+From 2ca27382c398da212c2909142259768cb452f4a3 Mon Sep 17 00:00:00 2001
+From: "Dmitry V. Levin"
+Date: Sun, 20 Apr 2025 08:00:00 +0000
+Subject: [PATCH] pam_namespace: check pam_sprintf return code
+
+Even though snprintf truncation doesn't seem to be possible in this case,
+check pam_sprintf return code as a matter of principle.
+
+Conflict:NA
+Reference:https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/pam/1.5.3-5ubuntu5.4/pam_1.5.3-5ubuntu5.4.debian.tar.xz
+
+---
+ modules/pam_namespace/pam_namespace.c | 5 +++--
+ 1 file changed, 3 insertions(+), 2 deletions(-)
+
+--- a/modules/pam_namespace/pam_namespace.c
++++ b/modules/pam_namespace/pam_namespace.c
+@@ -1184,6 +1184,9 @@ static int protect_mount(int dfd, const
+ dir = dir->next;
+ }
+
++ if (pam_sprintf(tmpbuf, "/proc/self/fd/%d", dfd) < 0)
++ return -1;
++
+ dir = calloc(1, sizeof(*dir));
+
+ if (dir == NULL) {
+@@ -1197,8 +1200,6 @@ static int protect_mount(int dfd, const
+ return -1;
+ }
+
+- pam_sprintf(tmpbuf, "/proc/self/fd/%d", dfd);
+-
+ if (idata->flags & PAMNS_DEBUG) {
+ pam_syslog(idata->pamh, LOG_INFO,
+ "Protect mount of %s over itself", path);
diff --git a/pam_namespace_post170-13.patch b/pam_namespace_post170-13.patch
new file mode 100644
index 0000000000000000000000000000000000000000..77d8adaa89babc5ba51f450475f0c7ff3b61ce76
--- /dev/null
+++ b/pam_namespace_post170-13.patch
@@ -0,0 +1,40 @@
+From 08ac66a6316de6231fee5d817dc26fd4077da715 Mon Sep 17 00:00:00 2001
+From: "Dmitry V. Levin"
+Date: Sun, 20 Apr 2025 08:00:00 +0000
+Subject: [PATCH] pam_namespace: do not check execle return code
+
+This function returns only if an error has occurred.
+
+Conflict:NA
+Reference:https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/pam/1.5.3-5ubuntu5.4/pam_1.5.3-5ubuntu5.4.debian.tar.xz
+
+---
+ modules/pam_namespace/pam_namespace.c | 10 +++++-----
+ 1 file changed, 5 insertions(+), 5 deletions(-)
+
+--- a/modules/pam_namespace/pam_namespace.c
++++ b/modules/pam_namespace/pam_namespace.c
+@@ -1410,9 +1410,9 @@ static int inst_init(const struct polydi
+
+ close_fds_pre_exec(idata);
+
+- if (execle(init_script, init_script,
+- polyptr->dir, ipath, newdir?"1":"0", idata->user, NULL, envp) < 0)
+- _exit(1);
++ execle(init_script, init_script,
++ polyptr->dir, ipath, newdir?"1":"0", idata->user, NULL, envp);
++ _exit(1);
+ } else if (pid > 0) {
+ while (((rc = waitpid(pid, &status, 0)) == (pid_t)-1) &&
+ (errno == EINTR));
+@@ -1849,8 +1849,8 @@ static int cleanup_tmpdirs(struct instan
+ }
+ #endif
+ close_fds_pre_exec(idata);
+- if (execle("/bin/rm", "/bin/rm", "-rf", pptr->instance_prefix, NULL, envp) < 0)
+- _exit(1);
++ execle("/bin/rm", "/bin/rm", "-rf", pptr->instance_prefix, NULL, envp);
++ _exit(1);
+ } else if (pid > 0) {
+ while (((rc = waitpid(pid, &status, 0)) == (pid_t)-1) &&
+ (errno == EINTR));
diff --git a/pam_namespace_post170-14.patch b/pam_namespace_post170-14.patch
new file mode 100644
index 0000000000000000000000000000000000000000..2d2584d0bc1aeb2431696bd11adf587c329dfa79
--- /dev/null
+++ b/pam_namespace_post170-14.patch
@@ -0,0 +1,324 @@
+From bc856cd9b9b461e8e2a537f4d9db87d315f5fe7b Mon Sep 17 00:00:00 2001
+From: "Dmitry V. Levin"
+Date: Fri, 18 Apr 2025 08:00:00 +0000
+Subject: [PATCH] pam_namespace: move functions around
+
+This is a no-op change needed to facilitate subsequent commits.
+
+Conflict:NA
+Reference:https://github.com/linux-pam/linux-pam/commit/bc856cd9b9b461e8e2a537f4d9db87d315f5fe7b
+
+---
+ modules/pam_namespace/pam_namespace.c | 282 +++++++++++++-------------
+ 1 file changed, 141 insertions(+), 141 deletions(-)
+
+diff --git a/modules/pam_namespace/pam_namespace.c b/modules/pam_namespace/pam_namespace.c
+index 3c4c9bc6..ba3fd48b 100644
+--- a/modules/pam_namespace/pam_namespace.c
++++ b/modules/pam_namespace/pam_namespace.c
+@@ -63,6 +63,147 @@ static void close_fds_pre_exec(struct instance_data *idata)
+ }
+ }
+
++static void
++strip_trailing_slashes(char *str)
++{
++ char *p = str + strlen(str);
++
++ while (--p > str && *p == '/')
++ *p = '\0';
++}
++
++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;
++ }
++
++ if (pam_sprintf(tmpbuf, "/proc/self/fd/%d", dfd) < 0)
++ return -1;
++
++ dir = calloc(1, sizeof(*dir));
++
++ if (dir == NULL) {
++ return -1;
++ }
++
++ dir->dir = strdup(path);
++
++ if (dir->dir == NULL) {
++ free(dir);
++ return -1;
++ }
++
++ 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) {
++ return -1;
++ }
++
++ 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;
++}
++
+ /* Evaluating a list of files which have to be parsed in the right order:
+ *
+ * - If etc/security/namespace.d/@filename@.conf exists, then
+@@ -469,15 +610,6 @@ static int parse_method(char *method, struct polydir_s *poly,
+ return 0;
+ }
+
+-static void
+-strip_trailing_slashes(char *str)
+-{
+- char *p = str + strlen(str);
+-
+- while (--p > str && *p == '/')
+- *p = '\0';
+-}
+-
+ /*
+ * Called from parse_config_file, this function processes a single line
+ * of the namespace configuration file. It skips over comments and incomplete
+@@ -1135,138 +1267,6 @@ fail:
+ 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;
+- }
+-
+- if (pam_sprintf(tmpbuf, "/proc/self/fd/%d", dfd) < 0)
+- return -1;
+-
+- dir = calloc(1, sizeof(*dir));
+-
+- if (dir == NULL) {
+- return -1;
+- }
+-
+- dir->dir = strdup(path);
+-
+- if (dir->dir == NULL) {
+- free(dir);
+- return -1;
+- }
+-
+- 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) {
+- return -1;
+- }
+-
+- 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)
+ {
+ struct stat instpbuf;
+--
+2.43.0
+
diff --git a/pam_namespace_revert_abi.patch b/pam_namespace_revert_abi.patch
new file mode 100644
index 0000000000000000000000000000000000000000..3878a483eb566e3c13c292748ea4446093b6d814
--- /dev/null
+++ b/pam_namespace_revert_abi.patch
@@ -0,0 +1,61 @@
+From c48622d95e3d441fcee6228be1952fe7ee299f6d Mon Sep 17 00:00:00 2001
+From: Matthias Gerstner
+Date: Tue, 2 Jan 2024 12:13:19 +0100
+Subject: [PATCH] pam_namespace: close unnecessary file descriptors before
+ exec()
+
+Currently the `rm` subprocess and the namespace init script inherit a
+random set of open file descriptors from the process running PAM.
+Depending on the actual PAM stack configuration these can even be
+security sensitive files. In any case it is unclean to inherit
+unexpected open file descriptors to child processes like this.
+
+To address this close all file descriptors except stdio before executing
+a new program.
+
+This patch reverts the following commit to prevent an ABI change in
+pam_namespace that may result in unintended consequences for running
+daemons.
+
+Conflict:NA
+Reference:https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/pam/1.5.3-5ubuntu5.4/pam_1.5.3-5ubuntu5.4.debian.tar.xz
+
+---
+ modules/pam_namespace/pam_namespace.c | 11 +++++++++++
+ 1 file changed, 11 insertions(+)
+
+
+--- a/modules/pam_namespace/pam_namespace.c
++++ b/modules/pam_namespace/pam_namespace.c
+@@ -92,14 +92,6 @@ compare_filename(const void *a, const vo
+ base_name(* (char * const *) b));
+ }
+
+-static void close_fds_pre_exec(struct instance_data *idata)
+-{
+- if (pam_modutil_sanitize_helper_fds(idata->pamh, PAM_MODUTIL_IGNORE_FD,
+- PAM_MODUTIL_IGNORE_FD, PAM_MODUTIL_IGNORE_FD) < 0) {
+- _exit(1);
+- }
+-}
+-
+ static void
+ strip_trailing_slashes(char *str)
+ {
+@@ -1407,8 +1399,6 @@ static int inst_init(const struct polydi
+ /* ignore failures, they don't matter */
+ }
+
+- close_fds_pre_exec(idata);
+-
+ execle(init_script, init_script,
+ polyptr->dir, ipath, newdir?"1":"0", idata->user, NULL, envp);
+ _exit(1);
+@@ -1847,7 +1837,6 @@ static int cleanup_tmpdirs(struct instan
+ _exit(1);
+ }
+ #endif
+- close_fds_pre_exec(idata);
+ execle("/bin/rm", "/bin/rm", "-rf", pptr->instance_prefix, NULL, envp);
+ _exit(1);
+ } else if (pid > 0) {