diff options
6 files changed, 1959 insertions, 0 deletions
diff --git a/meta/recipes-extended/pam/libpam/0001-pam_inline-introduce-pam_asprint.patch b/meta/recipes-extended/pam/libpam/0001-pam_inline-introduce-pam_asprint.patch new file mode 100644 index 0000000000..48e8b255f2 --- /dev/null +++ b/meta/recipes-extended/pam/libpam/0001-pam_inline-introduce-pam_asprint.patch | |||
| @@ -0,0 +1,102 @@ | |||
| 1 | From 10b80543807e3fc5af5f8bcfd8bb6e219bb3cecc Mon Sep 17 00:00:00 2001 | ||
| 2 | From: "Dmitry V. Levin" <ldv@strace.io> | ||
| 3 | Date: Tue, 18 Feb 2025 08:00:00 +0000 | ||
| 4 | Subject: [PATCH] pam_inline: introduce pam_asprintf(), pam_snprintf(), and | ||
| 5 | pam_sprintf() | ||
| 6 | |||
| 7 | pam_asprintf() is essentially asprintf() with the following semantic | ||
| 8 | difference: it returns the string itself instead of its length. | ||
| 9 | |||
| 10 | pam_snprintf() is essentially snprintf() with the following semantic | ||
| 11 | difference: it returns -1 in case of truncation. | ||
| 12 | |||
| 13 | pam_sprintf() is essentially snprintf() but with a check that the buffer | ||
| 14 | is an array, and with an automatically calculated buffer size. | ||
| 15 | |||
| 16 | Use of these helpers would make error checking simpler. | ||
| 17 | |||
| 18 | (cherry picked from commit 10b80543807e3fc5af5f8bcfd8bb6e219bb3cecc) | ||
| 19 | Signed-off-by: Dmitry V. Levin <ldv@strace.io> | ||
| 20 | |||
| 21 | Upstream-Status: Backport [https://github.com/linux-pam/linux-pam/commit/10b80543807e3fc5af5f8bcfd8bb6e219bb3cecc] | ||
| 22 | Signed-off-by: Hitendra Prajapati <hprajapati@mvista.com> | ||
| 23 | --- | ||
| 24 | libpam/include/pam_cc_compat.h | 6 ++++++ | ||
| 25 | libpam/include/pam_inline.h | 37 ++++++++++++++++++++++++++++++++++ | ||
| 26 | 2 files changed, 43 insertions(+) | ||
| 27 | |||
| 28 | diff --git a/libpam/include/pam_cc_compat.h b/libpam/include/pam_cc_compat.h | ||
| 29 | index 6919036..45c74b5 100644 | ||
| 30 | --- a/libpam/include/pam_cc_compat.h | ||
| 31 | +++ b/libpam/include/pam_cc_compat.h | ||
| 32 | @@ -21,6 +21,12 @@ | ||
| 33 | # define PAM_ATTRIBUTE_ALIGNED(arg) /* empty */ | ||
| 34 | #endif | ||
| 35 | |||
| 36 | +#if PAM_GNUC_PREREQ(3, 0) | ||
| 37 | +# define PAM_ATTRIBUTE_MALLOC __attribute__((__malloc__)) | ||
| 38 | +#else | ||
| 39 | +# define PAM_ATTRIBUTE_MALLOC /* empty */ | ||
| 40 | +#endif | ||
| 41 | + | ||
| 42 | #if PAM_GNUC_PREREQ(4, 6) | ||
| 43 | # define DIAG_PUSH_IGNORE_CAST_QUAL \ | ||
| 44 | _Pragma("GCC diagnostic push"); \ | ||
| 45 | diff --git a/libpam/include/pam_inline.h b/libpam/include/pam_inline.h | ||
| 46 | index ec2f3bf..666a028 100644 | ||
| 47 | --- a/libpam/include/pam_inline.h | ||
| 48 | +++ b/libpam/include/pam_inline.h | ||
| 49 | @@ -9,6 +9,9 @@ | ||
| 50 | #define PAM_INLINE_H | ||
| 51 | |||
| 52 | #include "pam_cc_compat.h" | ||
| 53 | +#include <stdarg.h> | ||
| 54 | +#include <stdio.h> | ||
| 55 | +#include <stdlib.h> | ||
| 56 | #include <string.h> | ||
| 57 | #include <unistd.h> | ||
| 58 | #include <errno.h> | ||
| 59 | @@ -66,6 +69,40 @@ pam_str_skip_icase_prefix_len(const char *str, const char *prefix, size_t prefix | ||
| 60 | #define pam_str_skip_icase_prefix(str_, prefix_) \ | ||
| 61 | pam_str_skip_icase_prefix_len((str_), (prefix_), sizeof(prefix_) - 1 + PAM_MUST_BE_ARRAY(prefix_)) | ||
| 62 | |||
| 63 | +static inline char * PAM_FORMAT((printf, 1, 2)) PAM_NONNULL((1)) PAM_ATTRIBUTE_MALLOC | ||
| 64 | +pam_asprintf(const char *fmt, ...) | ||
| 65 | +{ | ||
| 66 | + int rc; | ||
| 67 | + char *res; | ||
| 68 | + va_list ap; | ||
| 69 | + | ||
| 70 | + va_start(ap, fmt); | ||
| 71 | + rc = vasprintf(&res, fmt, ap); | ||
| 72 | + va_end(ap); | ||
| 73 | + | ||
| 74 | + return rc < 0 ? NULL : res; | ||
| 75 | +} | ||
| 76 | + | ||
| 77 | +static inline int PAM_FORMAT((printf, 3, 4)) PAM_NONNULL((3)) | ||
| 78 | +pam_snprintf(char *str, size_t size, const char *fmt, ...) | ||
| 79 | +{ | ||
| 80 | + int rc; | ||
| 81 | + va_list ap; | ||
| 82 | + | ||
| 83 | + va_start(ap, fmt); | ||
| 84 | + rc = vsnprintf(str, size, fmt, ap); | ||
| 85 | + va_end(ap); | ||
| 86 | + | ||
| 87 | + if (rc < 0 || (unsigned int) rc >= size) | ||
| 88 | + return -1; | ||
| 89 | + return rc; | ||
| 90 | +} | ||
| 91 | + | ||
| 92 | +#define pam_sprintf(str_, fmt_, ...) \ | ||
| 93 | + pam_snprintf((str_), sizeof(str_) + PAM_MUST_BE_ARRAY(str_), (fmt_), \ | ||
| 94 | + ##__VA_ARGS__) | ||
| 95 | + | ||
| 96 | + | ||
| 97 | static inline int | ||
| 98 | pam_read_passwords(int fd, int npass, char **passwords) | ||
| 99 | { | ||
| 100 | -- | ||
| 101 | 2.50.1 | ||
| 102 | |||
diff --git a/meta/recipes-extended/pam/libpam/0001-pam_namespace-include-stdint-h.patch b/meta/recipes-extended/pam/libpam/0001-pam_namespace-include-stdint-h.patch new file mode 100644 index 0000000000..124e5f1c3c --- /dev/null +++ b/meta/recipes-extended/pam/libpam/0001-pam_namespace-include-stdint-h.patch | |||
| @@ -0,0 +1,42 @@ | |||
| 1 | From cc9d40b7cdbd3e15ccaa324a0dda1680ef9dea13 Mon Sep 17 00:00:00 2001 | ||
| 2 | From: Jacob Heider <jacob@pkgx.dev> | ||
| 3 | Date: Wed, 17 Jan 2024 11:49:26 -0500 | ||
| 4 | Subject: [PATCH] pam_namespace: include stdint.h | ||
| 5 | |||
| 6 | pam_namespace.c makes use of SIZE_MAX but doesn't include stdint.h, | ||
| 7 | resulting in the following build failures on 1.6.0: | ||
| 8 | |||
| 9 | pam_namespace.c: In function 'process_line': | ||
| 10 | pam_namespace.c:649:41: error: 'SIZE_MAX' undeclared (first use in this function) | ||
| 11 | 649 | if (count > UINT_MAX || count > SIZE_MAX / sizeof(uid_t)) { | ||
| 12 | | ^~~~~~~~ | ||
| 13 | pam_namespace.c:41:1: note: 'SIZE_MAX' is defined in header '<stdint.h>'; did you forget to '#include <stdint.h>'? | ||
| 14 | 40 | #include "argv_parse.h" | ||
| 15 | +++ |+#include <stdint.h> | ||
| 16 | 41 | | ||
| 17 | pam_namespace.c:649:41: note: each undeclared identifier is reported only once for each function it appears in | ||
| 18 | 649 | if (count > UINT_MAX || count > SIZE_MAX / sizeof(uid_t)) { | ||
| 19 | | ^~~~~~~~ | ||
| 20 | |||
| 21 | Fixes: v1.6.0~100 ("pam_namespace: validate amount of uids in config") | ||
| 22 | Resolves: https://github.com/linux-pam/linux-pam/issues/733 | ||
| 23 | |||
| 24 | Upstream-Status: Backport [https://github.com/linux-pam/linux-pam/commit/cc9d40b7cdbd3e15ccaa324a0dda1680ef9dea13] | ||
| 25 | Signed-off-by: Khem Raj <raj.khem@gmail.com> | ||
| 26 | --- | ||
| 27 | modules/pam_namespace/pam_namespace.c | 2 ++ | ||
| 28 | 1 file changed, 2 insertions(+) | ||
| 29 | |||
| 30 | diff --git a/modules/pam_namespace/pam_namespace.c b/modules/pam_namespace/pam_namespace.c | ||
| 31 | index f72d67189..b16731c22 100644 | ||
| 32 | --- a/modules/pam_namespace/pam_namespace.c | ||
| 33 | +++ b/modules/pam_namespace/pam_namespace.c | ||
| 34 | @@ -34,6 +34,8 @@ | ||
| 35 | |||
| 36 | #define _ATFILE_SOURCE | ||
| 37 | |||
| 38 | +#include "config.h" | ||
| 39 | +#include <stdint.h> | ||
| 40 | #include "pam_cc_compat.h" | ||
| 41 | #include "pam_inline.h" | ||
| 42 | #include "pam_namespace.h" | ||
diff --git a/meta/recipes-extended/pam/libpam/CVE-2025-6020-01.patch b/meta/recipes-extended/pam/libpam/CVE-2025-6020-01.patch new file mode 100644 index 0000000000..4f5f780f9c --- /dev/null +++ b/meta/recipes-extended/pam/libpam/CVE-2025-6020-01.patch | |||
| @@ -0,0 +1,1588 @@ | |||
| 1 | From 475bd60c552b98c7eddb3270b0b4196847c0072e Mon Sep 17 00:00:00 2001 | ||
| 2 | From: Olivier Bal-Petre <olivier.bal-petre@ssi.gouv.fr> | ||
| 3 | Date: Tue, 4 Mar 2025 14:37:02 +0100 | ||
| 4 | Subject: [PATCH] pam_namespace: fix potential privilege escalation | ||
| 5 | |||
| 6 | Existing protection provided by protect_dir() and protect_mount() were | ||
| 7 | bind mounting on themselves all directories part of the to-be-secured | ||
| 8 | paths. However, this works *only* against attacks executed by processes | ||
| 9 | in the same mount namespace as the one the mountpoint was created in. | ||
| 10 | Therefore, a user with an out-of-mount-namespace access, or multiple | ||
| 11 | users colluding, could exploit multiple race conditions, and, for | ||
| 12 | instance, elevate their privileges to root. | ||
| 13 | |||
| 14 | This commit keeps the existing protection as a defense in depth | ||
| 15 | measure, and to keep the existing behavior of the module. However, | ||
| 16 | it converts all the needed function calls to operate on file | ||
| 17 | descriptors instead of absolute paths to protect against race | ||
| 18 | conditions globally. | ||
| 19 | |||
| 20 | Signed-off-by: Olivier Bal-Petre <olivier.bal-petre@ssi.gouv.fr> | ||
| 21 | Signed-off-by: Dmitry V. Levin <ldv@strace.io> | ||
| 22 | |||
| 23 | Upstream-Status: Backport [https://github.com/linux-pam/linux-pam/commit/475bd60c552b98c7eddb3270b0b4196847c0072e] | ||
| 24 | CVE: CVE-2025-6020 | ||
| 25 | Signed-off-by: Hitendra Prajapati <hprajapati@mvista.com> | ||
| 26 | --- | ||
| 27 | modules/pam_namespace/pam_namespace.c | 999 ++++++++++++++++---------- | ||
| 28 | modules/pam_namespace/pam_namespace.h | 17 +- | ||
| 29 | 2 files changed, 647 insertions(+), 369 deletions(-) | ||
| 30 | |||
| 31 | diff --git a/modules/pam_namespace/pam_namespace.c b/modules/pam_namespace/pam_namespace.c | ||
| 32 | index 2a5082b..22d8445 100644 | ||
| 33 | --- a/modules/pam_namespace/pam_namespace.c | ||
| 34 | +++ b/modules/pam_namespace/pam_namespace.c | ||
| 35 | @@ -41,6 +41,300 @@ | ||
| 36 | #include "pam_namespace.h" | ||
| 37 | #include "argv_parse.h" | ||
| 38 | |||
| 39 | +#define MAGIC_LNK_FD_SIZE 64 | ||
| 40 | + | ||
| 41 | +/* --- evaluating all files in VENDORDIR/security/namespace.d and /etc/security/namespace.d --- */ | ||
| 42 | +static const char *base_name(const char *path) | ||
| 43 | +{ | ||
| 44 | + const char *base = strrchr(path, '/'); | ||
| 45 | + return base ? base+1 : path; | ||
| 46 | +} | ||
| 47 | + | ||
| 48 | +static int | ||
| 49 | +compare_filename(const void *a, const void *b) | ||
| 50 | +{ | ||
| 51 | + return strcmp(base_name(* (char * const *) a), | ||
| 52 | + base_name(* (char * const *) b)); | ||
| 53 | +} | ||
| 54 | + | ||
| 55 | +static void close_fds_pre_exec(struct instance_data *idata) | ||
| 56 | +{ | ||
| 57 | + if (pam_modutil_sanitize_helper_fds(idata->pamh, PAM_MODUTIL_IGNORE_FD, | ||
| 58 | + PAM_MODUTIL_IGNORE_FD, PAM_MODUTIL_IGNORE_FD) < 0) { | ||
| 59 | + _exit(1); | ||
| 60 | + } | ||
| 61 | +} | ||
| 62 | + | ||
| 63 | +static void | ||
| 64 | +strip_trailing_slashes(char *str) | ||
| 65 | +{ | ||
| 66 | + char *p = str + strlen(str); | ||
| 67 | + | ||
| 68 | + while (--p > str && *p == '/') | ||
| 69 | + *p = '\0'; | ||
| 70 | +} | ||
| 71 | + | ||
| 72 | +static int protect_mount(int dfd, const char *path, struct instance_data *idata) | ||
| 73 | +{ | ||
| 74 | + struct protect_dir_s *dir = idata->protect_dirs; | ||
| 75 | + char tmpbuf[MAGIC_LNK_FD_SIZE]; | ||
| 76 | + | ||
| 77 | + while (dir != NULL) { | ||
| 78 | + if (strcmp(path, dir->dir) == 0) { | ||
| 79 | + return 0; | ||
| 80 | + } | ||
| 81 | + dir = dir->next; | ||
| 82 | + } | ||
| 83 | + | ||
| 84 | + if (pam_sprintf(tmpbuf, "/proc/self/fd/%d", dfd) < 0) | ||
| 85 | + return -1; | ||
| 86 | + | ||
| 87 | + dir = calloc(1, sizeof(*dir)); | ||
| 88 | + | ||
| 89 | + if (dir == NULL) { | ||
| 90 | + return -1; | ||
| 91 | + } | ||
| 92 | + | ||
| 93 | + dir->dir = strdup(path); | ||
| 94 | + | ||
| 95 | + if (dir->dir == NULL) { | ||
| 96 | + free(dir); | ||
| 97 | + return -1; | ||
| 98 | + } | ||
| 99 | + | ||
| 100 | + if (idata->flags & PAMNS_DEBUG) { | ||
| 101 | + pam_syslog(idata->pamh, LOG_INFO, | ||
| 102 | + "Protect mount of %s over itself", path); | ||
| 103 | + } | ||
| 104 | + | ||
| 105 | + if (mount(tmpbuf, tmpbuf, NULL, MS_BIND, NULL) != 0) { | ||
| 106 | + int save_errno = errno; | ||
| 107 | + pam_syslog(idata->pamh, LOG_ERR, | ||
| 108 | + "Protect mount of %s failed: %m", tmpbuf); | ||
| 109 | + free(dir->dir); | ||
| 110 | + free(dir); | ||
| 111 | + errno = save_errno; | ||
| 112 | + return -1; | ||
| 113 | + } | ||
| 114 | + | ||
| 115 | + dir->next = idata->protect_dirs; | ||
| 116 | + idata->protect_dirs = dir; | ||
| 117 | + | ||
| 118 | + return 0; | ||
| 119 | +} | ||
| 120 | + | ||
| 121 | +/* | ||
| 122 | + * Returns a fd to the given absolute path, acquired securely. This means: | ||
| 123 | + * - iterating on each segment of the path, | ||
| 124 | + * - not following user symlinks, | ||
| 125 | + * - using race-free operations. | ||
| 126 | + * | ||
| 127 | + * Takes a bit mask to specify the operation mode: | ||
| 128 | + * - SECURE_OPENDIR_PROTECT: call protect_mount() on each unsafe segment of path | ||
| 129 | + * - SECURE_OPENDIR_MKDIR: create last segment of path if does not exist | ||
| 130 | + * - SECURE_OPENDIR_FULL_FD: open the directory with O_RDONLY instead of O_PATH, | ||
| 131 | + * allowing more operations to be done with the returned fd | ||
| 132 | + * | ||
| 133 | + * Be aware that using SECURE_OPENDIR_PROTECT: | ||
| 134 | + * - will modify some external state (global structure...) and should not be | ||
| 135 | + * called in cleanup code paths. See wrapper secure_opendir_stateless() | ||
| 136 | + * - need a non-NULL idata to call protect_mount() | ||
| 137 | + */ | ||
| 138 | +static int secure_opendir(const char *path, int opm, mode_t mode, | ||
| 139 | + struct instance_data *idata) | ||
| 140 | +{ | ||
| 141 | + char *p; | ||
| 142 | + char *d; | ||
| 143 | + char *dir; | ||
| 144 | + int dfd = -1; | ||
| 145 | + int dfd_next; | ||
| 146 | + int save_errno; | ||
| 147 | + int flags = O_DIRECTORY | O_CLOEXEC; | ||
| 148 | + int rv = -1; | ||
| 149 | + struct stat st; | ||
| 150 | + | ||
| 151 | + if (opm & SECURE_OPENDIR_FULL_FD) | ||
| 152 | + flags |= O_RDONLY; | ||
| 153 | + else | ||
| 154 | + flags |= O_PATH; | ||
| 155 | + | ||
| 156 | + /* Check for args consistency */ | ||
| 157 | + if ((opm & SECURE_OPENDIR_PROTECT) && idata == NULL) | ||
| 158 | + return -1; | ||
| 159 | + | ||
| 160 | + /* Accept only absolute paths */ | ||
| 161 | + if (*path != '/') | ||
| 162 | + return -1; | ||
| 163 | + | ||
| 164 | + dir = p = strdup(path); | ||
| 165 | + if (p == NULL) | ||
| 166 | + return -1; | ||
| 167 | + | ||
| 168 | + /* Assume '/' is safe */ | ||
| 169 | + dfd = open("/", flags); | ||
| 170 | + if (dfd == -1) | ||
| 171 | + goto error; | ||
| 172 | + | ||
| 173 | + /* Needed to not loop too far and call openat() on NULL */ | ||
| 174 | + strip_trailing_slashes(p); | ||
| 175 | + | ||
| 176 | + dir++; | ||
| 177 | + | ||
| 178 | + /* In case path is '/' */ | ||
| 179 | + if (*dir == '\0') { | ||
| 180 | + free(p); | ||
| 181 | + return dfd; | ||
| 182 | + } | ||
| 183 | + | ||
| 184 | + while ((d=strchr(dir, '/')) != NULL) { | ||
| 185 | + *d = '\0'; | ||
| 186 | + | ||
| 187 | + dfd_next = openat(dfd, dir, flags); | ||
| 188 | + if (dfd_next == -1) | ||
| 189 | + goto error; | ||
| 190 | + | ||
| 191 | + if (fstat(dfd_next, &st) != 0) { | ||
| 192 | + close(dfd_next); | ||
| 193 | + goto error; | ||
| 194 | + } | ||
| 195 | + | ||
| 196 | + if ((flags & O_NOFOLLOW) && (opm & SECURE_OPENDIR_PROTECT)) { | ||
| 197 | + /* we are inside user-owned dir - protect */ | ||
| 198 | + if (protect_mount(dfd_next, p, idata) == -1) { | ||
| 199 | + close(dfd_next); | ||
| 200 | + goto error; | ||
| 201 | + } | ||
| 202 | + /* | ||
| 203 | + * Reopen the directory to obtain a new descriptor | ||
| 204 | + * after protect_mount(), this is necessary in cases | ||
| 205 | + * when another directory is going to be mounted over | ||
| 206 | + * the given path. | ||
| 207 | + */ | ||
| 208 | + close(dfd_next); | ||
| 209 | + dfd_next = openat(dfd, dir, flags); | ||
| 210 | + if (dfd_next == -1) | ||
| 211 | + goto error; | ||
| 212 | + } else if (st.st_uid != 0 | ||
| 213 | + || (st.st_gid != 0 && (st.st_mode & S_IWGRP)) | ||
| 214 | + || (st.st_mode & S_IWOTH)) { | ||
| 215 | + /* do not follow symlinks on subdirectories */ | ||
| 216 | + flags |= O_NOFOLLOW; | ||
| 217 | + } | ||
| 218 | + | ||
| 219 | + close(dfd); | ||
| 220 | + dfd = dfd_next; | ||
| 221 | + | ||
| 222 | + *d = '/'; | ||
| 223 | + dir = d + 1; | ||
| 224 | + } | ||
| 225 | + | ||
| 226 | + rv = openat(dfd, dir, flags); | ||
| 227 | + | ||
| 228 | + if (rv == -1) { | ||
| 229 | + if ((opm & SECURE_OPENDIR_MKDIR) && mkdirat(dfd, dir, mode) == 0) | ||
| 230 | + rv = openat(dfd, dir, flags); | ||
| 231 | + | ||
| 232 | + if (rv == -1) | ||
| 233 | + goto error; | ||
| 234 | + } | ||
| 235 | + | ||
| 236 | + if ((flags & O_NOFOLLOW) && (opm & SECURE_OPENDIR_PROTECT)) { | ||
| 237 | + /* we are inside user-owned dir - protect */ | ||
| 238 | + if (protect_mount(rv, p, idata) == -1) { | ||
| 239 | + save_errno = errno; | ||
| 240 | + close(rv); | ||
| 241 | + rv = -1; | ||
| 242 | + errno = save_errno; | ||
| 243 | + } | ||
| 244 | + /* | ||
| 245 | + * Reopen the directory to obtain a new descriptor after | ||
| 246 | + * protect_mount(), this is necessary in cases when another | ||
| 247 | + * directory is going to be mounted over the given path. | ||
| 248 | + */ | ||
| 249 | + close(rv); | ||
| 250 | + rv = openat(dfd, dir, flags); | ||
| 251 | + } | ||
| 252 | + | ||
| 253 | +error: | ||
| 254 | + save_errno = errno; | ||
| 255 | + free(p); | ||
| 256 | + if (dfd >= 0) | ||
| 257 | + close(dfd); | ||
| 258 | + errno = save_errno; | ||
| 259 | + | ||
| 260 | + return rv; | ||
| 261 | +} | ||
| 262 | + | ||
| 263 | +/* | ||
| 264 | + * Returns a fd to the given path, acquired securely. | ||
| 265 | + * It can be called in all situations, including in cleanup code paths, as | ||
| 266 | + * it does not modify external state (no access to global structures...). | ||
| 267 | + */ | ||
| 268 | +static int secure_opendir_stateless(const char *path) | ||
| 269 | +{ | ||
| 270 | + return secure_opendir(path, 0, 0, NULL); | ||
| 271 | +} | ||
| 272 | + | ||
| 273 | +/* | ||
| 274 | + * Umount securely the given path, even if the directories along | ||
| 275 | + * the path are under user control. It should protect against | ||
| 276 | + * symlinks attacks and race conditions. | ||
| 277 | + */ | ||
| 278 | +static int secure_umount(const char *path) | ||
| 279 | +{ | ||
| 280 | + int save_errno; | ||
| 281 | + int rv = -1; | ||
| 282 | + int dfd = -1; | ||
| 283 | + char s_path[MAGIC_LNK_FD_SIZE]; | ||
| 284 | + | ||
| 285 | + dfd = secure_opendir_stateless(path); | ||
| 286 | + if (dfd == -1) | ||
| 287 | + return rv; | ||
| 288 | + | ||
| 289 | + if (pam_sprintf(s_path, "/proc/self/fd/%d", dfd) < 0) | ||
| 290 | + goto error; | ||
| 291 | + | ||
| 292 | + /* | ||
| 293 | + * We still have a fd open to path itself, | ||
| 294 | + * so we need to do a lazy umount. | ||
| 295 | + */ | ||
| 296 | + rv = umount2(s_path, MNT_DETACH); | ||
| 297 | + | ||
| 298 | +error: | ||
| 299 | + save_errno = errno; | ||
| 300 | + close(dfd); | ||
| 301 | + errno = save_errno; | ||
| 302 | + return rv; | ||
| 303 | +} | ||
| 304 | + | ||
| 305 | +/* | ||
| 306 | + * Rmdir the given path securely, protecting against symlinks attacks | ||
| 307 | + * and race conditions. | ||
| 308 | + * This function is currently called only in cleanup code paths where | ||
| 309 | + * any errors returned are not handled, so do not handle them either. | ||
| 310 | + * Basically, try to rmdir the path on a best-effort basis. | ||
| 311 | + */ | ||
| 312 | +static void secure_try_rmdir(const char *path) | ||
| 313 | +{ | ||
| 314 | + int dfd; | ||
| 315 | + char *buf; | ||
| 316 | + char *parent; | ||
| 317 | + | ||
| 318 | + buf = strdup(path); | ||
| 319 | + if (buf == NULL) | ||
| 320 | + return; | ||
| 321 | + | ||
| 322 | + parent = dirname(buf); | ||
| 323 | + | ||
| 324 | + dfd = secure_opendir_stateless(parent); | ||
| 325 | + if (dfd >= 0) { | ||
| 326 | + unlinkat(dfd, base_name(path), AT_REMOVEDIR); | ||
| 327 | + close(dfd); | ||
| 328 | + } | ||
| 329 | + | ||
| 330 | + free(buf); | ||
| 331 | +} | ||
| 332 | + | ||
| 333 | /* | ||
| 334 | * Adds an entry for a polyinstantiated directory to the linked list of | ||
| 335 | * polyinstantiated directories. It is called from process_line() while | ||
| 336 | @@ -73,6 +367,7 @@ static void del_polydir(struct polydir_s *poly) | ||
| 337 | } | ||
| 338 | } | ||
| 339 | |||
| 340 | + | ||
| 341 | /* | ||
| 342 | * Deletes all the entries in the linked list. | ||
| 343 | */ | ||
| 344 | @@ -92,7 +387,7 @@ static void unprotect_dirs(struct protect_dir_s *dir) | ||
| 345 | struct protect_dir_s *next; | ||
| 346 | |||
| 347 | while (dir != NULL) { | ||
| 348 | - umount(dir->dir); | ||
| 349 | + secure_umount(dir->dir); | ||
| 350 | free(dir->dir); | ||
| 351 | next = dir->next; | ||
| 352 | free(dir); | ||
| 353 | @@ -110,7 +405,7 @@ static void cleanup_protect_data(pam_handle_t *pamh UNUSED , void *data, int err | ||
| 354 | unprotect_dirs(data); | ||
| 355 | } | ||
| 356 | |||
| 357 | -static char *expand_variables(const char *orig, const char *var_names[], const char *var_values[]) | ||
| 358 | +static char *expand_variables(const char *orig, const char *const var_names[], const char *var_values[]) | ||
| 359 | { | ||
| 360 | const char *src = orig; | ||
| 361 | char *dst; | ||
| 362 | @@ -121,7 +416,7 @@ static char *expand_variables(const char *orig, const char *var_names[], const c | ||
| 363 | if (*src == '$') { | ||
| 364 | int i; | ||
| 365 | for (i = 0; var_names[i]; i++) { | ||
| 366 | - int namelen = strlen(var_names[i]); | ||
| 367 | + size_t namelen = strlen(var_names[i]); | ||
| 368 | if (strncmp(var_names[i], src+1, namelen) == 0) { | ||
| 369 | dstlen += strlen(var_values[i]) - 1; /* $ */ | ||
| 370 | src += namelen; | ||
| 371 | @@ -139,7 +434,7 @@ static char *expand_variables(const char *orig, const char *var_names[], const c | ||
| 372 | if (c == '$') { | ||
| 373 | int i; | ||
| 374 | for (i = 0; var_names[i]; i++) { | ||
| 375 | - int namelen = strlen(var_names[i]); | ||
| 376 | + size_t namelen = strlen(var_names[i]); | ||
| 377 | if (strncmp(var_names[i], src+1, namelen) == 0) { | ||
| 378 | dst = stpcpy(dst, var_values[i]); | ||
| 379 | --dst; | ||
| 380 | @@ -223,8 +518,7 @@ static int parse_iscript_params(char *params, struct polydir_s *poly) | ||
| 381 | |||
| 382 | if (*params != '\0') { | ||
| 383 | if (*params != '/') { /* path is relative to NAMESPACE_D_DIR */ | ||
| 384 | - if (asprintf(&poly->init_script, "%s%s", NAMESPACE_D_DIR, params) == -1) | ||
| 385 | - return -1; | ||
| 386 | + poly->init_script = pam_asprintf("%s%s", NAMESPACE_D_DIR, params); | ||
| 387 | } else { | ||
| 388 | poly->init_script = strdup(params); | ||
| 389 | } | ||
| 390 | @@ -306,9 +600,9 @@ static int parse_method(char *method, struct polydir_s *poly, | ||
| 391 | { | ||
| 392 | enum polymethod pm; | ||
| 393 | char *sptr = NULL; | ||
| 394 | - static const char *method_names[] = { "user", "context", "level", "tmpdir", | ||
| 395 | + static const char *const method_names[] = { "user", "context", "level", "tmpdir", | ||
| 396 | "tmpfs", NULL }; | ||
| 397 | - static const char *flag_names[] = { "create", "noinit", "iscript", | ||
| 398 | + static const char *const flag_names[] = { "create", "noinit", "iscript", | ||
| 399 | "shared", "mntopts", NULL }; | ||
| 400 | static const unsigned int flag_values[] = { POLYDIR_CREATE, POLYDIR_NOINIT, | ||
| 401 | POLYDIR_ISCRIPT, POLYDIR_SHARED, POLYDIR_MNTOPTS }; | ||
| 402 | @@ -333,7 +627,7 @@ static int parse_method(char *method, struct polydir_s *poly, | ||
| 403 | |||
| 404 | while ((flag=strtok_r(NULL, ":", &sptr)) != NULL) { | ||
| 405 | for (i = 0; flag_names[i]; i++) { | ||
| 406 | - int namelen = strlen(flag_names[i]); | ||
| 407 | + size_t namelen = strlen(flag_names[i]); | ||
| 408 | |||
| 409 | if (strncmp(flag, flag_names[i], namelen) == 0) { | ||
| 410 | poly->flags |= flag_values[i]; | ||
| 411 | @@ -379,27 +673,27 @@ static int parse_method(char *method, struct polydir_s *poly, | ||
| 412 | * of the namespace configuration file. It skips over comments and incomplete | ||
| 413 | * or malformed lines. It processes a valid line with information on | ||
| 414 | * polyinstantiating a directory by populating appropriate fields of a | ||
| 415 | - * polyinstatiated directory structure and then calling add_polydir_entry to | ||
| 416 | + * polyinstantiated directory structure and then calling add_polydir_entry to | ||
| 417 | * add that entry to the linked list of polyinstantiated directories. | ||
| 418 | */ | ||
| 419 | static int process_line(char *line, const char *home, const char *rhome, | ||
| 420 | struct instance_data *idata) | ||
| 421 | { | ||
| 422 | char *dir = NULL, *instance_prefix = NULL, *rdir = NULL; | ||
| 423 | + const char *config_dir, *config_instance_prefix; | ||
| 424 | char *method, *uids; | ||
| 425 | char *tptr; | ||
| 426 | struct polydir_s *poly; | ||
| 427 | int retval = 0; | ||
| 428 | char **config_options = NULL; | ||
| 429 | - static const char *var_names[] = {"HOME", "USER", NULL}; | ||
| 430 | + static const char *const var_names[] = {"HOME", "USER", NULL}; | ||
| 431 | const char *var_values[] = {home, idata->user}; | ||
| 432 | const char *rvar_values[] = {rhome, idata->ruser}; | ||
| 433 | - int len; | ||
| 434 | |||
| 435 | /* | ||
| 436 | * skip the leading white space | ||
| 437 | */ | ||
| 438 | - while (*line && isspace(*line)) | ||
| 439 | + while (*line && isspace((unsigned char)*line)) | ||
| 440 | line++; | ||
| 441 | |||
| 442 | /* | ||
| 443 | @@ -435,22 +729,19 @@ static int process_line(char *line, const char *home, const char *rhome, | ||
| 444 | goto erralloc; | ||
| 445 | } | ||
| 446 | |||
| 447 | - dir = config_options[0]; | ||
| 448 | - if (dir == NULL) { | ||
| 449 | + config_dir = config_options[0]; | ||
| 450 | + if (config_dir == NULL) { | ||
| 451 | pam_syslog(idata->pamh, LOG_NOTICE, "Invalid line missing polydir"); | ||
| 452 | goto skipping; | ||
| 453 | } | ||
| 454 | - instance_prefix = config_options[1]; | ||
| 455 | - if (instance_prefix == NULL) { | ||
| 456 | + config_instance_prefix = config_options[1]; | ||
| 457 | + if (config_instance_prefix == NULL) { | ||
| 458 | pam_syslog(idata->pamh, LOG_NOTICE, "Invalid line missing instance_prefix"); | ||
| 459 | - instance_prefix = NULL; | ||
| 460 | goto skipping; | ||
| 461 | } | ||
| 462 | method = config_options[2]; | ||
| 463 | if (method == NULL) { | ||
| 464 | pam_syslog(idata->pamh, LOG_NOTICE, "Invalid line missing method"); | ||
| 465 | - instance_prefix = NULL; | ||
| 466 | - dir = NULL; | ||
| 467 | goto skipping; | ||
| 468 | } | ||
| 469 | |||
| 470 | @@ -465,19 +756,16 @@ static int process_line(char *line, const char *home, const char *rhome, | ||
| 471 | /* | ||
| 472 | * Expand $HOME and $USER in poly dir and instance dir prefix | ||
| 473 | */ | ||
| 474 | - if ((rdir=expand_variables(dir, var_names, rvar_values)) == NULL) { | ||
| 475 | - instance_prefix = NULL; | ||
| 476 | - dir = NULL; | ||
| 477 | + if ((rdir = expand_variables(config_dir, var_names, rvar_values)) == NULL) { | ||
| 478 | goto erralloc; | ||
| 479 | } | ||
| 480 | |||
| 481 | - if ((dir=expand_variables(dir, var_names, var_values)) == NULL) { | ||
| 482 | - instance_prefix = NULL; | ||
| 483 | + if ((dir = expand_variables(config_dir, var_names, var_values)) == NULL) { | ||
| 484 | goto erralloc; | ||
| 485 | } | ||
| 486 | |||
| 487 | - if ((instance_prefix=expand_variables(instance_prefix, var_names, var_values)) | ||
| 488 | - == NULL) { | ||
| 489 | + if ((instance_prefix = expand_variables(config_instance_prefix, | ||
| 490 | + var_names, var_values)) == NULL) { | ||
| 491 | goto erralloc; | ||
| 492 | } | ||
| 493 | |||
| 494 | @@ -487,15 +775,8 @@ static int process_line(char *line, const char *home, const char *rhome, | ||
| 495 | pam_syslog(idata->pamh, LOG_DEBUG, "Expanded instance prefix: '%s'", instance_prefix); | ||
| 496 | } | ||
| 497 | |||
| 498 | - len = strlen(dir); | ||
| 499 | - if (len > 0 && dir[len-1] == '/') { | ||
| 500 | - dir[len-1] = '\0'; | ||
| 501 | - } | ||
| 502 | - | ||
| 503 | - len = strlen(rdir); | ||
| 504 | - if (len > 0 && rdir[len-1] == '/') { | ||
| 505 | - rdir[len-1] = '\0'; | ||
| 506 | - } | ||
| 507 | + strip_trailing_slashes(dir); | ||
| 508 | + strip_trailing_slashes(rdir); | ||
| 509 | |||
| 510 | if (dir[0] == '\0' || rdir[0] == '\0') { | ||
| 511 | pam_syslog(idata->pamh, LOG_NOTICE, "Invalid polydir"); | ||
| 512 | @@ -506,26 +787,15 @@ static int process_line(char *line, const char *home, const char *rhome, | ||
| 513 | * Populate polyinstantiated directory structure with appropriate | ||
| 514 | * pathnames and the method with which to polyinstantiate. | ||
| 515 | */ | ||
| 516 | - if (strlen(dir) >= sizeof(poly->dir) | ||
| 517 | - || strlen(rdir) >= sizeof(poly->rdir) | ||
| 518 | - || strlen(instance_prefix) >= sizeof(poly->instance_prefix)) { | ||
| 519 | - pam_syslog(idata->pamh, LOG_NOTICE, "Pathnames too long"); | ||
| 520 | - goto skipping; | ||
| 521 | - } | ||
| 522 | - strcpy(poly->dir, dir); | ||
| 523 | - strcpy(poly->rdir, rdir); | ||
| 524 | - strcpy(poly->instance_prefix, instance_prefix); | ||
| 525 | - | ||
| 526 | if (parse_method(method, poly, idata) != 0) { | ||
| 527 | goto skipping; | ||
| 528 | } | ||
| 529 | |||
| 530 | - if (poly->method == TMPDIR) { | ||
| 531 | - if (sizeof(poly->instance_prefix) - strlen(poly->instance_prefix) < 7) { | ||
| 532 | - pam_syslog(idata->pamh, LOG_NOTICE, "Pathnames too long"); | ||
| 533 | - goto skipping; | ||
| 534 | - } | ||
| 535 | - strcat(poly->instance_prefix, "XXXXXX"); | ||
| 536 | + if (pam_sprintf(poly->dir, "%s", dir) < 0 | ||
| 537 | + || pam_sprintf(poly->rdir, "%s", rdir) < 0 | ||
| 538 | + || pam_sprintf(poly->instance_prefix, "%s", instance_prefix) < 0) { | ||
| 539 | + pam_syslog(idata->pamh, LOG_NOTICE, "Pathnames too long"); | ||
| 540 | + goto skipping; | ||
| 541 | } | ||
| 542 | |||
| 543 | /* | ||
| 544 | @@ -549,7 +819,7 @@ static int process_line(char *line, const char *home, const char *rhome, | ||
| 545 | if (uids) { | ||
| 546 | uid_t *uidptr; | ||
| 547 | const char *ustr, *sstr; | ||
| 548 | - int count, i; | ||
| 549 | + size_t count, i; | ||
| 550 | |||
| 551 | if (*uids == '~') { | ||
| 552 | poly->flags |= POLYDIR_EXCLUSIVE; | ||
| 553 | @@ -558,8 +828,13 @@ static int process_line(char *line, const char *home, const char *rhome, | ||
| 554 | for (count = 0, ustr = sstr = uids; sstr; ustr = sstr + 1, count++) | ||
| 555 | sstr = strchr(ustr, ','); | ||
| 556 | |||
| 557 | + if (count > UINT_MAX || count > SIZE_MAX / sizeof(uid_t)) { | ||
| 558 | + pam_syslog(idata->pamh, LOG_ERR, "Too many uids encountered in configuration"); | ||
| 559 | + goto skipping; | ||
| 560 | + } | ||
| 561 | + | ||
| 562 | poly->num_uids = count; | ||
| 563 | - poly->uid = (uid_t *) malloc(count * sizeof (uid_t)); | ||
| 564 | + poly->uid = malloc(count * sizeof (uid_t)); | ||
| 565 | uidptr = poly->uid; | ||
| 566 | if (uidptr == NULL) { | ||
| 567 | goto erralloc; | ||
| 568 | @@ -798,6 +1073,23 @@ static char *md5hash(const char *instname, struct instance_data *idata) | ||
| 569 | } | ||
| 570 | |||
| 571 | #ifdef WITH_SELINUX | ||
| 572 | +static char *secure_getfilecon(pam_handle_t *pamh, const char *dir) | ||
| 573 | +{ | ||
| 574 | + char *ctx = NULL; | ||
| 575 | + int dfd = secure_opendir(dir, SECURE_OPENDIR_FULL_FD, 0, NULL); | ||
| 576 | + if (dfd < 0) { | ||
| 577 | + pam_syslog(pamh, LOG_ERR, "Error getting fd to %s: %m", dir); | ||
| 578 | + return NULL; | ||
| 579 | + } | ||
| 580 | + if (fgetfilecon(dfd, &ctx) < 0) | ||
| 581 | + ctx = NULL; | ||
| 582 | + if (ctx == NULL) | ||
| 583 | + pam_syslog(pamh, LOG_ERR, | ||
| 584 | + "Error getting poly dir context for %s: %m", dir); | ||
| 585 | + close(dfd); | ||
| 586 | + return ctx; | ||
| 587 | +} | ||
| 588 | + | ||
| 589 | static int form_context(const struct polydir_s *polyptr, | ||
| 590 | char **i_context, char **origcon, | ||
| 591 | struct instance_data *idata) | ||
| 592 | @@ -809,12 +1101,9 @@ static int form_context(const struct polydir_s *polyptr, | ||
| 593 | /* | ||
| 594 | * Get the security context of the directory to polyinstantiate. | ||
| 595 | */ | ||
| 596 | - rc = getfilecon(polyptr->dir, origcon); | ||
| 597 | - if (rc < 0 || *origcon == NULL) { | ||
| 598 | - pam_syslog(idata->pamh, LOG_ERR, | ||
| 599 | - "Error getting poly dir context, %m"); | ||
| 600 | + *origcon = secure_getfilecon(idata->pamh, polyptr->dir); | ||
| 601 | + if (*origcon == NULL) | ||
| 602 | return PAM_SESSION_ERR; | ||
| 603 | - } | ||
| 604 | |||
| 605 | if (polyptr->method == USER) return PAM_SUCCESS; | ||
| 606 | |||
| 607 | @@ -905,34 +1194,58 @@ static int form_context(const struct polydir_s *polyptr, | ||
| 608 | return rc; | ||
| 609 | } | ||
| 610 | /* Should never get here */ | ||
| 611 | + freecon(scon); | ||
| 612 | return PAM_SUCCESS; | ||
| 613 | } | ||
| 614 | #endif | ||
| 615 | |||
| 616 | /* | ||
| 617 | - * poly_name returns the name of the polyinstantiated instance directory | ||
| 618 | + * From the instance differentiation string, set in the polyptr structure: | ||
| 619 | + * - the absolute path to the instance dir, | ||
| 620 | + * - the absolute path to the previous dir (parent), | ||
| 621 | + * - the instance name (may be different than the instance differentiation string) | ||
| 622 | + */ | ||
| 623 | +static int set_polydir_paths(struct polydir_s *polyptr, const char *inst_differentiation) | ||
| 624 | +{ | ||
| 625 | + char *tmp; | ||
| 626 | + | ||
| 627 | + if (pam_sprintf(polyptr->instance_absolute, "%s%s", | ||
| 628 | + polyptr->instance_prefix, inst_differentiation) < 0) | ||
| 629 | + return -1; | ||
| 630 | + | ||
| 631 | + polyptr->instname = strrchr(polyptr->instance_absolute, '/') + 1; | ||
| 632 | + | ||
| 633 | + if (pam_sprintf(polyptr->instance_parent, "%s", polyptr->instance_absolute) < 0) | ||
| 634 | + return -1; | ||
| 635 | + | ||
| 636 | + tmp = strrchr(polyptr->instance_parent, '/') + 1; | ||
| 637 | + *tmp = '\0'; | ||
| 638 | + | ||
| 639 | + return 0; | ||
| 640 | +} | ||
| 641 | + | ||
| 642 | +/* | ||
| 643 | + * Set the name of the polyinstantiated instance directory | ||
| 644 | * based on the method used for polyinstantiation (user, context or level) | ||
| 645 | * In addition, the function also returns the security contexts of the | ||
| 646 | * original directory to polyinstantiate and the polyinstantiated instance | ||
| 647 | * directory. | ||
| 648 | */ | ||
| 649 | #ifdef WITH_SELINUX | ||
| 650 | -static int poly_name(const struct polydir_s *polyptr, char **i_name, | ||
| 651 | - char **i_context, char **origcon, | ||
| 652 | - struct instance_data *idata) | ||
| 653 | +static int poly_name(struct polydir_s *polyptr, char **i_context, | ||
| 654 | + char **origcon, struct instance_data *idata) | ||
| 655 | #else | ||
| 656 | -static int poly_name(const struct polydir_s *polyptr, char **i_name, | ||
| 657 | - struct instance_data *idata) | ||
| 658 | +static int poly_name(struct polydir_s *polyptr, struct instance_data *idata) | ||
| 659 | #endif | ||
| 660 | { | ||
| 661 | int rc; | ||
| 662 | + char *inst_differentiation = NULL; | ||
| 663 | char *hash = NULL; | ||
| 664 | enum polymethod pm; | ||
| 665 | #ifdef WITH_SELINUX | ||
| 666 | char *rawcon = NULL; | ||
| 667 | #endif | ||
| 668 | |||
| 669 | - *i_name = NULL; | ||
| 670 | #ifdef WITH_SELINUX | ||
| 671 | *i_context = NULL; | ||
| 672 | *origcon = NULL; | ||
| 673 | @@ -966,10 +1279,8 @@ static int poly_name(const struct polydir_s *polyptr, char **i_name, | ||
| 674 | |||
| 675 | switch (pm) { | ||
| 676 | case USER: | ||
| 677 | - if (asprintf(i_name, "%s", idata->user) < 0) { | ||
| 678 | - *i_name = NULL; | ||
| 679 | + if ((inst_differentiation = strdup(idata->user)) == NULL) | ||
| 680 | goto fail; | ||
| 681 | - } | ||
| 682 | break; | ||
| 683 | |||
| 684 | #ifdef WITH_SELINUX | ||
| 685 | @@ -979,26 +1290,25 @@ static int poly_name(const struct polydir_s *polyptr, char **i_name, | ||
| 686 | pam_syslog(idata->pamh, LOG_ERR, "Error translating directory context"); | ||
| 687 | goto fail; | ||
| 688 | } | ||
| 689 | - if (polyptr->flags & POLYDIR_SHARED) { | ||
| 690 | - if (asprintf(i_name, "%s", rawcon) < 0) { | ||
| 691 | - *i_name = NULL; | ||
| 692 | - goto fail; | ||
| 693 | - } | ||
| 694 | - } else { | ||
| 695 | - if (asprintf(i_name, "%s_%s", rawcon, idata->user) < 0) { | ||
| 696 | - *i_name = NULL; | ||
| 697 | - goto fail; | ||
| 698 | - } | ||
| 699 | - } | ||
| 700 | + if (polyptr->flags & POLYDIR_SHARED) | ||
| 701 | + inst_differentiation = strdup(rawcon); | ||
| 702 | + else | ||
| 703 | + inst_differentiation = pam_asprintf("%s_%s", rawcon, idata->user); | ||
| 704 | + if (inst_differentiation == NULL) | ||
| 705 | + goto fail; | ||
| 706 | break; | ||
| 707 | |||
| 708 | #endif /* WITH_SELINUX */ | ||
| 709 | |||
| 710 | case TMPDIR: | ||
| 711 | + if ((inst_differentiation = strdup("XXXXXX")) == NULL) | ||
| 712 | + goto fail; | ||
| 713 | + goto success; | ||
| 714 | + | ||
| 715 | case TMPFS: | ||
| 716 | - if ((*i_name=strdup("")) == NULL) | ||
| 717 | + if ((inst_differentiation=strdup("")) == NULL) | ||
| 718 | goto fail; | ||
| 719 | - return PAM_SUCCESS; | ||
| 720 | + goto success; | ||
| 721 | |||
| 722 | default: | ||
| 723 | if (idata->flags & PAMNS_DEBUG) | ||
| 724 | @@ -1007,31 +1317,37 @@ static int poly_name(const struct polydir_s *polyptr, char **i_name, | ||
| 725 | } | ||
| 726 | |||
| 727 | if (idata->flags & PAMNS_DEBUG) | ||
| 728 | - pam_syslog(idata->pamh, LOG_DEBUG, "poly_name %s", *i_name); | ||
| 729 | + pam_syslog(idata->pamh, LOG_DEBUG, "poly_name %s", inst_differentiation); | ||
| 730 | |||
| 731 | - if ((idata->flags & PAMNS_GEN_HASH) || strlen(*i_name) > NAMESPACE_MAX_DIR_LEN) { | ||
| 732 | - hash = md5hash(*i_name, idata); | ||
| 733 | + if ((idata->flags & PAMNS_GEN_HASH) || strlen(inst_differentiation) > NAMESPACE_MAX_DIR_LEN) { | ||
| 734 | + hash = md5hash(inst_differentiation, idata); | ||
| 735 | if (hash == NULL) { | ||
| 736 | goto fail; | ||
| 737 | } | ||
| 738 | if (idata->flags & PAMNS_GEN_HASH) { | ||
| 739 | - free(*i_name); | ||
| 740 | - *i_name = hash; | ||
| 741 | + free(inst_differentiation); | ||
| 742 | + inst_differentiation = hash; | ||
| 743 | hash = NULL; | ||
| 744 | } else { | ||
| 745 | - char *newname; | ||
| 746 | - if (asprintf(&newname, "%.*s_%s", NAMESPACE_MAX_DIR_LEN-1-(int)strlen(hash), | ||
| 747 | - *i_name, hash) < 0) { | ||
| 748 | + char *newname = | ||
| 749 | + pam_asprintf("%.*s_%s", | ||
| 750 | + NAMESPACE_MAX_DIR_LEN - 1 - (int)strlen(hash), | ||
| 751 | + inst_differentiation, hash); | ||
| 752 | + if (newname == NULL) | ||
| 753 | goto fail; | ||
| 754 | - } | ||
| 755 | - free(*i_name); | ||
| 756 | - *i_name = newname; | ||
| 757 | + free(inst_differentiation); | ||
| 758 | + inst_differentiation = newname; | ||
| 759 | } | ||
| 760 | } | ||
| 761 | - rc = PAM_SUCCESS; | ||
| 762 | |||
| 763 | +success: | ||
| 764 | + if (set_polydir_paths(polyptr, inst_differentiation) == -1) | ||
| 765 | + goto fail; | ||
| 766 | + | ||
| 767 | + rc = PAM_SUCCESS; | ||
| 768 | fail: | ||
| 769 | free(hash); | ||
| 770 | + free(inst_differentiation); | ||
| 771 | #ifdef WITH_SELINUX | ||
| 772 | freecon(rawcon); | ||
| 773 | #endif | ||
| 774 | @@ -1042,187 +1358,35 @@ fail: | ||
| 775 | freecon(*origcon); | ||
| 776 | *origcon = NULL; | ||
| 777 | #endif | ||
| 778 | - free(*i_name); | ||
| 779 | - *i_name = NULL; | ||
| 780 | } | ||
| 781 | return rc; | ||
| 782 | } | ||
| 783 | |||
| 784 | -static int protect_mount(int dfd, const char *path, struct instance_data *idata) | ||
| 785 | +static int check_inst_parent(int dfd, struct instance_data *idata) | ||
| 786 | { | ||
| 787 | - struct protect_dir_s *dir = idata->protect_dirs; | ||
| 788 | - char tmpbuf[64]; | ||
| 789 | - | ||
| 790 | - while (dir != NULL) { | ||
| 791 | - if (strcmp(path, dir->dir) == 0) { | ||
| 792 | - return 0; | ||
| 793 | - } | ||
| 794 | - dir = dir->next; | ||
| 795 | - } | ||
| 796 | - | ||
| 797 | - dir = calloc(1, sizeof(*dir)); | ||
| 798 | - | ||
| 799 | - if (dir == NULL) { | ||
| 800 | - return -1; | ||
| 801 | - } | ||
| 802 | - | ||
| 803 | - dir->dir = strdup(path); | ||
| 804 | - | ||
| 805 | - if (dir->dir == NULL) { | ||
| 806 | - free(dir); | ||
| 807 | - return -1; | ||
| 808 | - } | ||
| 809 | + struct stat instpbuf; | ||
| 810 | |||
| 811 | - snprintf(tmpbuf, sizeof(tmpbuf), "/proc/self/fd/%d", dfd); | ||
| 812 | + /* | ||
| 813 | + * Stat the instance parent directory to make sure it's writable by | ||
| 814 | + * root only (unless the admin explicitly instructs to ignore the | ||
| 815 | + * instance parent mode by the "ignore_instance_parent_mode" argument). | ||
| 816 | + */ | ||
| 817 | |||
| 818 | - if (idata->flags & PAMNS_DEBUG) { | ||
| 819 | - pam_syslog(idata->pamh, LOG_INFO, | ||
| 820 | - "Protect mount of %s over itself", path); | ||
| 821 | - } | ||
| 822 | + if (idata->flags & PAMNS_IGN_INST_PARENT_MODE) | ||
| 823 | + return PAM_SUCCESS; | ||
| 824 | |||
| 825 | - if (mount(tmpbuf, tmpbuf, NULL, MS_BIND, NULL) != 0) { | ||
| 826 | - int save_errno = errno; | ||
| 827 | + if (fstat(dfd, &instpbuf) < 0) { | ||
| 828 | pam_syslog(idata->pamh, LOG_ERR, | ||
| 829 | - "Protect mount of %s failed: %m", tmpbuf); | ||
| 830 | - free(dir->dir); | ||
| 831 | - free(dir); | ||
| 832 | - errno = save_errno; | ||
| 833 | - return -1; | ||
| 834 | - } | ||
| 835 | - | ||
| 836 | - dir->next = idata->protect_dirs; | ||
| 837 | - idata->protect_dirs = dir; | ||
| 838 | - | ||
| 839 | - return 0; | ||
| 840 | -} | ||
| 841 | - | ||
| 842 | -static int protect_dir(const char *path, mode_t mode, int do_mkdir, | ||
| 843 | - struct instance_data *idata) | ||
| 844 | -{ | ||
| 845 | - char *p = strdup(path); | ||
| 846 | - char *d; | ||
| 847 | - char *dir = p; | ||
| 848 | - int dfd = AT_FDCWD; | ||
| 849 | - int dfd_next; | ||
| 850 | - int save_errno; | ||
| 851 | - int flags = O_RDONLY | O_DIRECTORY; | ||
| 852 | - int rv = -1; | ||
| 853 | - struct stat st; | ||
| 854 | - | ||
| 855 | - if (p == NULL) { | ||
| 856 | - goto error; | ||
| 857 | - } | ||
| 858 | - | ||
| 859 | - if (*dir == '/') { | ||
| 860 | - dfd = open("/", flags); | ||
| 861 | - if (dfd == -1) { | ||
| 862 | - goto error; | ||
| 863 | - } | ||
| 864 | - dir++; /* assume / is safe */ | ||
| 865 | - } | ||
| 866 | - | ||
| 867 | - while ((d=strchr(dir, '/')) != NULL) { | ||
| 868 | - *d = '\0'; | ||
| 869 | - dfd_next = openat(dfd, dir, flags); | ||
| 870 | - if (dfd_next == -1) { | ||
| 871 | - goto error; | ||
| 872 | - } | ||
| 873 | - | ||
| 874 | - if (dfd != AT_FDCWD) | ||
| 875 | - close(dfd); | ||
| 876 | - dfd = dfd_next; | ||
| 877 | - | ||
| 878 | - if (fstat(dfd, &st) != 0) { | ||
| 879 | - goto error; | ||
| 880 | - } | ||
| 881 | - | ||
| 882 | - if (flags & O_NOFOLLOW) { | ||
| 883 | - /* we are inside user-owned dir - protect */ | ||
| 884 | - if (protect_mount(dfd, p, idata) == -1) | ||
| 885 | - goto error; | ||
| 886 | - } else if (st.st_uid != 0 || st.st_gid != 0 || | ||
| 887 | - (st.st_mode & S_IWOTH)) { | ||
| 888 | - /* do not follow symlinks on subdirectories */ | ||
| 889 | - flags |= O_NOFOLLOW; | ||
| 890 | - } | ||
| 891 | - | ||
| 892 | - *d = '/'; | ||
| 893 | - dir = d + 1; | ||
| 894 | - } | ||
| 895 | - | ||
| 896 | - rv = openat(dfd, dir, flags); | ||
| 897 | - | ||
| 898 | - if (rv == -1) { | ||
| 899 | - if (!do_mkdir || mkdirat(dfd, dir, mode) != 0) { | ||
| 900 | - goto error; | ||
| 901 | - } | ||
| 902 | - rv = openat(dfd, dir, flags); | ||
| 903 | - } | ||
| 904 | - | ||
| 905 | - if (flags & O_NOFOLLOW) { | ||
| 906 | - /* we are inside user-owned dir - protect */ | ||
| 907 | - if (protect_mount(rv, p, idata) == -1) { | ||
| 908 | - save_errno = errno; | ||
| 909 | - close(rv); | ||
| 910 | - rv = -1; | ||
| 911 | - errno = save_errno; | ||
| 912 | - } | ||
| 913 | - } | ||
| 914 | - | ||
| 915 | -error: | ||
| 916 | - save_errno = errno; | ||
| 917 | - free(p); | ||
| 918 | - if (dfd != AT_FDCWD && dfd >= 0) | ||
| 919 | - close(dfd); | ||
| 920 | - errno = save_errno; | ||
| 921 | - | ||
| 922 | - return rv; | ||
| 923 | -} | ||
| 924 | - | ||
| 925 | -static int check_inst_parent(char *ipath, struct instance_data *idata) | ||
| 926 | -{ | ||
| 927 | - struct stat instpbuf; | ||
| 928 | - char *inst_parent, *trailing_slash; | ||
| 929 | - int dfd; | ||
| 930 | - /* | ||
| 931 | - * stat the instance parent path to make sure it exists | ||
| 932 | - * and is a directory. Check that its mode is 000 (unless the | ||
| 933 | - * admin explicitly instructs to ignore the instance parent | ||
| 934 | - * mode by the "ignore_instance_parent_mode" argument). | ||
| 935 | - */ | ||
| 936 | - inst_parent = (char *) malloc(strlen(ipath)+1); | ||
| 937 | - if (!inst_parent) { | ||
| 938 | - pam_syslog(idata->pamh, LOG_CRIT, "Error allocating pathname string"); | ||
| 939 | + "Error accessing instance parent, %m"); | ||
| 940 | return PAM_SESSION_ERR; | ||
| 941 | } | ||
| 942 | |||
| 943 | - strcpy(inst_parent, ipath); | ||
| 944 | - trailing_slash = strrchr(inst_parent, '/'); | ||
| 945 | - if (trailing_slash) | ||
| 946 | - *trailing_slash = '\0'; | ||
| 947 | - | ||
| 948 | - dfd = protect_dir(inst_parent, 0, 1, idata); | ||
| 949 | - | ||
| 950 | - if (dfd == -1 || fstat(dfd, &instpbuf) < 0) { | ||
| 951 | + if ((instpbuf.st_mode & (S_IRWXU|S_IRWXG|S_IRWXO)) || instpbuf.st_uid != 0) { | ||
| 952 | pam_syslog(idata->pamh, LOG_ERR, | ||
| 953 | - "Error creating or accessing instance parent %s, %m", inst_parent); | ||
| 954 | - if (dfd != -1) | ||
| 955 | - close(dfd); | ||
| 956 | - free(inst_parent); | ||
| 957 | + "Mode of inst parent not 000 or owner not root"); | ||
| 958 | return PAM_SESSION_ERR; | ||
| 959 | } | ||
| 960 | |||
| 961 | - if ((idata->flags & PAMNS_IGN_INST_PARENT_MODE) == 0) { | ||
| 962 | - if ((instpbuf.st_mode & (S_IRWXU|S_IRWXG|S_IRWXO)) || instpbuf.st_uid != 0) { | ||
| 963 | - pam_syslog(idata->pamh, LOG_ERR, "Mode of inst parent %s not 000 or owner not root", | ||
| 964 | - inst_parent); | ||
| 965 | - close(dfd); | ||
| 966 | - free(inst_parent); | ||
| 967 | - return PAM_SESSION_ERR; | ||
| 968 | - } | ||
| 969 | - } | ||
| 970 | - close(dfd); | ||
| 971 | - free(inst_parent); | ||
| 972 | return PAM_SUCCESS; | ||
| 973 | } | ||
| 974 | |||
| 975 | @@ -1271,9 +1435,10 @@ static int inst_init(const struct polydir_s *polyptr, const char *ipath, | ||
| 976 | if (setuid(geteuid()) < 0) { | ||
| 977 | /* ignore failures, they don't matter */ | ||
| 978 | } | ||
| 979 | + close_fds_pre_exec(idata); | ||
| 980 | |||
| 981 | - if (execle(init_script, init_script, | ||
| 982 | - polyptr->dir, ipath, newdir?"1":"0", idata->user, NULL, envp) < 0) | ||
| 983 | + execle(init_script, init_script, | ||
| 984 | + polyptr->dir, ipath, newdir?"1":"0", idata->user, NULL, envp); | ||
| 985 | _exit(1); | ||
| 986 | } else if (pid > 0) { | ||
| 987 | while (((rc = waitpid(pid, &status, 0)) == (pid_t)-1) && | ||
| 988 | @@ -1324,7 +1489,9 @@ static int create_polydir(struct polydir_s *polyptr, | ||
| 989 | |||
| 990 | #ifdef WITH_SELINUX | ||
| 991 | if (idata->flags & PAMNS_SELINUX_ENABLED) { | ||
| 992 | - getfscreatecon_raw(&oldcon_raw); | ||
| 993 | + if (getfscreatecon_raw(&oldcon_raw) != 0) | ||
| 994 | + pam_syslog(idata->pamh, LOG_NOTICE, | ||
| 995 | + "Error retrieving fs create context: %m"); | ||
| 996 | |||
| 997 | label_handle = selabel_open(SELABEL_CTX_FILE, NULL, 0); | ||
| 998 | if (!label_handle) { | ||
| 999 | @@ -1349,11 +1516,16 @@ static int create_polydir(struct polydir_s *polyptr, | ||
| 1000 | } | ||
| 1001 | #endif | ||
| 1002 | |||
| 1003 | - rc = protect_dir(dir, mode, 1, idata); | ||
| 1004 | + rc = secure_opendir(dir, | ||
| 1005 | + SECURE_OPENDIR_PROTECT | SECURE_OPENDIR_MKDIR | SECURE_OPENDIR_FULL_FD, | ||
| 1006 | + mode, idata); | ||
| 1007 | if (rc == -1) { | ||
| 1008 | pam_syslog(idata->pamh, LOG_ERR, | ||
| 1009 | "Error creating directory %s: %m", dir); | ||
| 1010 | - return PAM_SESSION_ERR; | ||
| 1011 | +#ifdef WITH_SELINUX | ||
| 1012 | + freecon(oldcon_raw); | ||
| 1013 | +#endif | ||
| 1014 | + return -1; | ||
| 1015 | } | ||
| 1016 | |||
| 1017 | #ifdef WITH_SELINUX | ||
| 1018 | @@ -1374,9 +1546,9 @@ static int create_polydir(struct polydir_s *polyptr, | ||
| 1019 | pam_syslog(idata->pamh, LOG_ERR, | ||
| 1020 | "Error changing mode of directory %s: %m", dir); | ||
| 1021 | close(rc); | ||
| 1022 | - umount(dir); /* undo the eventual protection bind mount */ | ||
| 1023 | - rmdir(dir); | ||
| 1024 | - return PAM_SESSION_ERR; | ||
| 1025 | + secure_umount(dir); /* undo the eventual protection bind mount */ | ||
| 1026 | + secure_try_rmdir(dir); | ||
| 1027 | + return -1; | ||
| 1028 | } | ||
| 1029 | } | ||
| 1030 | |||
| 1031 | @@ -1394,41 +1566,37 @@ static int create_polydir(struct polydir_s *polyptr, | ||
| 1032 | pam_syslog(idata->pamh, LOG_ERR, | ||
| 1033 | "Unable to change owner on directory %s: %m", dir); | ||
| 1034 | close(rc); | ||
| 1035 | - umount(dir); /* undo the eventual protection bind mount */ | ||
| 1036 | - rmdir(dir); | ||
| 1037 | - return PAM_SESSION_ERR; | ||
| 1038 | + secure_umount(dir); /* undo the eventual protection bind mount */ | ||
| 1039 | + secure_try_rmdir(dir); | ||
| 1040 | + return -1; | ||
| 1041 | } | ||
| 1042 | |||
| 1043 | - close(rc); | ||
| 1044 | - | ||
| 1045 | if (idata->flags & PAMNS_DEBUG) | ||
| 1046 | pam_syslog(idata->pamh, LOG_DEBUG, | ||
| 1047 | "Polydir owner %u group %u", uid, gid); | ||
| 1048 | |||
| 1049 | - return PAM_SUCCESS; | ||
| 1050 | + return rc; | ||
| 1051 | } | ||
| 1052 | |||
| 1053 | /* | ||
| 1054 | - * Create polyinstantiated instance directory (ipath). | ||
| 1055 | + * Create polyinstantiated instance directory. | ||
| 1056 | + * To protect against races, changes are done on a fd to the parent of the | ||
| 1057 | + * instance directory (dfd_iparent) and a relative path (polyptr->instname). | ||
| 1058 | + * The absolute path (polyptr->instance_absolute) is only updated when creating | ||
| 1059 | + * a tmpdir and used for logging purposes. | ||
| 1060 | */ | ||
| 1061 | #ifdef WITH_SELINUX | ||
| 1062 | -static int create_instance(struct polydir_s *polyptr, char *ipath, struct stat *statbuf, | ||
| 1063 | - const char *icontext, const char *ocontext, | ||
| 1064 | - struct instance_data *idata) | ||
| 1065 | +static int create_instance(struct polydir_s *polyptr, int dfd_iparent, | ||
| 1066 | + struct stat *statbuf, const char *icontext, const char *ocontext, | ||
| 1067 | + struct instance_data *idata) | ||
| 1068 | #else | ||
| 1069 | -static int create_instance(struct polydir_s *polyptr, char *ipath, struct stat *statbuf, | ||
| 1070 | - struct instance_data *idata) | ||
| 1071 | +static int create_instance(struct polydir_s *polyptr, int dfd_iparent, | ||
| 1072 | + struct stat *statbuf, struct instance_data *idata) | ||
| 1073 | #endif | ||
| 1074 | { | ||
| 1075 | struct stat newstatbuf; | ||
| 1076 | int fd; | ||
| 1077 | |||
| 1078 | - /* | ||
| 1079 | - * Check to make sure instance parent is valid. | ||
| 1080 | - */ | ||
| 1081 | - if (check_inst_parent(ipath, idata)) | ||
| 1082 | - return PAM_SESSION_ERR; | ||
| 1083 | - | ||
| 1084 | /* | ||
| 1085 | * Create instance directory and set its security context to the context | ||
| 1086 | * returned by the security policy. Set its mode and ownership | ||
| 1087 | @@ -1437,29 +1605,39 @@ static int create_instance(struct polydir_s *polyptr, char *ipath, struct stat * | ||
| 1088 | */ | ||
| 1089 | |||
| 1090 | if (polyptr->method == TMPDIR) { | ||
| 1091 | - if (mkdtemp(polyptr->instance_prefix) == NULL) { | ||
| 1092 | - pam_syslog(idata->pamh, LOG_ERR, "Error creating temporary instance %s, %m", | ||
| 1093 | - polyptr->instance_prefix); | ||
| 1094 | - polyptr->method = NONE; /* do not clean up! */ | ||
| 1095 | - return PAM_SESSION_ERR; | ||
| 1096 | - } | ||
| 1097 | - /* copy the actual directory name to ipath */ | ||
| 1098 | - strcpy(ipath, polyptr->instance_prefix); | ||
| 1099 | - } else if (mkdir(ipath, S_IRUSR) < 0) { | ||
| 1100 | + char s_path[PATH_MAX]; | ||
| 1101 | + /* | ||
| 1102 | + * Create the template for mkdtemp() as a magic link based on | ||
| 1103 | + * our existing fd to avoid symlink attacks and races. | ||
| 1104 | + */ | ||
| 1105 | + if (pam_sprintf(s_path, "/proc/self/fd/%d/%s", dfd_iparent, polyptr->instname) < 0 | ||
| 1106 | + || mkdtemp(s_path) == NULL) { | ||
| 1107 | + pam_syslog(idata->pamh, LOG_ERR, | ||
| 1108 | + "Error creating temporary instance dir %s, %m", | ||
| 1109 | + polyptr->instance_absolute); | ||
| 1110 | + polyptr->method = NONE; /* do not clean up! */ | ||
| 1111 | + return PAM_SESSION_ERR; | ||
| 1112 | + } | ||
| 1113 | + | ||
| 1114 | + /* Copy the actual directory name to polyptr->instname */ | ||
| 1115 | + strcpy(polyptr->instname, base_name(s_path)); | ||
| 1116 | + } else if (mkdirat(dfd_iparent, polyptr->instname, S_IRUSR) < 0) { | ||
| 1117 | if (errno == EEXIST) | ||
| 1118 | return PAM_IGNORE; | ||
| 1119 | else { | ||
| 1120 | pam_syslog(idata->pamh, LOG_ERR, "Error creating %s, %m", | ||
| 1121 | - ipath); | ||
| 1122 | + polyptr->instance_absolute); | ||
| 1123 | return PAM_SESSION_ERR; | ||
| 1124 | } | ||
| 1125 | } | ||
| 1126 | |||
| 1127 | - /* Open a descriptor to it to prevent races */ | ||
| 1128 | - fd = open(ipath, O_DIRECTORY | O_RDONLY); | ||
| 1129 | + /* Open a descriptor to prevent races, based on our existing fd. */ | ||
| 1130 | + fd = openat(dfd_iparent, polyptr->instname, | ||
| 1131 | + O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC); | ||
| 1132 | if (fd < 0) { | ||
| 1133 | - pam_syslog(idata->pamh, LOG_ERR, "Error opening %s, %m", ipath); | ||
| 1134 | - rmdir(ipath); | ||
| 1135 | + pam_syslog(idata->pamh, LOG_ERR, "Error opening %s, %m", | ||
| 1136 | + polyptr->instance_absolute); | ||
| 1137 | + unlinkat(dfd_iparent, polyptr->instname, AT_REMOVEDIR); | ||
| 1138 | return PAM_SESSION_ERR; | ||
| 1139 | } | ||
| 1140 | #ifdef WITH_SELINUX | ||
| 1141 | @@ -1469,17 +1647,19 @@ static int create_instance(struct polydir_s *polyptr, char *ipath, struct stat * | ||
| 1142 | if (icontext) { | ||
| 1143 | if (fsetfilecon(fd, icontext) < 0) { | ||
| 1144 | pam_syslog(idata->pamh, LOG_ERR, | ||
| 1145 | - "Error setting context of %s to %s", ipath, icontext); | ||
| 1146 | + "Error setting context of %s to %s", | ||
| 1147 | + polyptr->instance_absolute, icontext); | ||
| 1148 | close(fd); | ||
| 1149 | - rmdir(ipath); | ||
| 1150 | + unlinkat(dfd_iparent, polyptr->instname, AT_REMOVEDIR); | ||
| 1151 | return PAM_SESSION_ERR; | ||
| 1152 | } | ||
| 1153 | } else { | ||
| 1154 | if (fsetfilecon(fd, ocontext) < 0) { | ||
| 1155 | pam_syslog(idata->pamh, LOG_ERR, | ||
| 1156 | - "Error setting context of %s to %s", ipath, ocontext); | ||
| 1157 | + "Error setting context of %s to %s", | ||
| 1158 | + polyptr->instance_absolute, ocontext); | ||
| 1159 | close(fd); | ||
| 1160 | - rmdir(ipath); | ||
| 1161 | + unlinkat(dfd_iparent, polyptr->instname, AT_REMOVEDIR); | ||
| 1162 | return PAM_SESSION_ERR; | ||
| 1163 | } | ||
| 1164 | } | ||
| 1165 | @@ -1487,9 +1667,9 @@ static int create_instance(struct polydir_s *polyptr, char *ipath, struct stat * | ||
| 1166 | #endif | ||
| 1167 | if (fstat(fd, &newstatbuf) < 0) { | ||
| 1168 | pam_syslog(idata->pamh, LOG_ERR, "Error stating %s, %m", | ||
| 1169 | - ipath); | ||
| 1170 | + polyptr->instance_absolute); | ||
| 1171 | close(fd); | ||
| 1172 | - rmdir(ipath); | ||
| 1173 | + unlinkat(dfd_iparent, polyptr->instname, AT_REMOVEDIR); | ||
| 1174 | return PAM_SESSION_ERR; | ||
| 1175 | } | ||
| 1176 | if (newstatbuf.st_uid != statbuf->st_uid || | ||
| 1177 | @@ -1497,17 +1677,17 @@ static int create_instance(struct polydir_s *polyptr, char *ipath, struct stat * | ||
| 1178 | if (fchown(fd, statbuf->st_uid, statbuf->st_gid) < 0) { | ||
| 1179 | pam_syslog(idata->pamh, LOG_ERR, | ||
| 1180 | "Error changing owner for %s, %m", | ||
| 1181 | - ipath); | ||
| 1182 | + polyptr->instance_absolute); | ||
| 1183 | close(fd); | ||
| 1184 | - rmdir(ipath); | ||
| 1185 | + unlinkat(dfd_iparent, polyptr->instname, AT_REMOVEDIR); | ||
| 1186 | return PAM_SESSION_ERR; | ||
| 1187 | } | ||
| 1188 | } | ||
| 1189 | if (fchmod(fd, statbuf->st_mode & 07777) < 0) { | ||
| 1190 | pam_syslog(idata->pamh, LOG_ERR, "Error changing mode for %s, %m", | ||
| 1191 | - ipath); | ||
| 1192 | + polyptr->instance_absolute); | ||
| 1193 | close(fd); | ||
| 1194 | - rmdir(ipath); | ||
| 1195 | + unlinkat(dfd_iparent, polyptr->instname, AT_REMOVEDIR); | ||
| 1196 | return PAM_SESSION_ERR; | ||
| 1197 | } | ||
| 1198 | close(fd); | ||
| 1199 | @@ -1526,9 +1706,12 @@ static int ns_setup(struct polydir_s *polyptr, | ||
| 1200 | struct instance_data *idata) | ||
| 1201 | { | ||
| 1202 | int retval; | ||
| 1203 | + int dfd_iparent = -1; | ||
| 1204 | + int dfd_ipath = -1; | ||
| 1205 | + int dfd_pptrdir = -1; | ||
| 1206 | int newdir = 1; | ||
| 1207 | - char *inst_dir = NULL; | ||
| 1208 | - char *instname = NULL; | ||
| 1209 | + char s_ipath[MAGIC_LNK_FD_SIZE]; | ||
| 1210 | + char s_pptrdir[MAGIC_LNK_FD_SIZE]; | ||
| 1211 | struct stat statbuf; | ||
| 1212 | #ifdef WITH_SELINUX | ||
| 1213 | char *instcontext = NULL, *origcontext = NULL; | ||
| 1214 | @@ -1538,39 +1721,48 @@ static int ns_setup(struct polydir_s *polyptr, | ||
| 1215 | pam_syslog(idata->pamh, LOG_DEBUG, | ||
| 1216 | "Set namespace for directory %s", polyptr->dir); | ||
| 1217 | |||
| 1218 | - retval = protect_dir(polyptr->dir, 0, 0, idata); | ||
| 1219 | - | ||
| 1220 | - if (retval < 0 && errno != ENOENT) { | ||
| 1221 | - pam_syslog(idata->pamh, LOG_ERR, "Polydir %s access error: %m", | ||
| 1222 | - polyptr->dir); | ||
| 1223 | - return PAM_SESSION_ERR; | ||
| 1224 | - } | ||
| 1225 | + dfd_pptrdir = secure_opendir(polyptr->dir, SECURE_OPENDIR_PROTECT, 0, idata); | ||
| 1226 | |||
| 1227 | - if (retval < 0) { | ||
| 1228 | - if ((polyptr->flags & POLYDIR_CREATE) && | ||
| 1229 | - create_polydir(polyptr, idata) != PAM_SUCCESS) | ||
| 1230 | - return PAM_SESSION_ERR; | ||
| 1231 | - } else { | ||
| 1232 | - close(retval); | ||
| 1233 | + if (dfd_pptrdir < 0) { | ||
| 1234 | + if (errno != ENOENT || !(polyptr->flags & POLYDIR_CREATE)) { | ||
| 1235 | + pam_syslog(idata->pamh, LOG_ERR, "Polydir %s access error: %m", | ||
| 1236 | + polyptr->dir); | ||
| 1237 | + return PAM_SESSION_ERR; | ||
| 1238 | + } | ||
| 1239 | + dfd_pptrdir = create_polydir(polyptr, idata); | ||
| 1240 | + if (dfd_pptrdir < 0) | ||
| 1241 | + return PAM_SESSION_ERR; | ||
| 1242 | } | ||
| 1243 | |||
| 1244 | if (polyptr->method == TMPFS) { | ||
| 1245 | - if (mount("tmpfs", polyptr->dir, "tmpfs", polyptr->mount_flags, polyptr->mount_opts) < 0) { | ||
| 1246 | - pam_syslog(idata->pamh, LOG_ERR, "Error mounting tmpfs on %s, %m", | ||
| 1247 | - polyptr->dir); | ||
| 1248 | - return PAM_SESSION_ERR; | ||
| 1249 | - } | ||
| 1250 | + /* | ||
| 1251 | + * There is no function mount() that operate on a fd, so instead, we | ||
| 1252 | + * get the magic link corresponding to the fd and give it to mount(). | ||
| 1253 | + * This protects against potential races exploitable by an unpriv user. | ||
| 1254 | + */ | ||
| 1255 | + if (pam_sprintf(s_pptrdir, "/proc/self/fd/%d", dfd_pptrdir) < 0) { | ||
| 1256 | + pam_syslog(idata->pamh, LOG_ERR, "Error pam_sprintf s_pptrdir"); | ||
| 1257 | + goto error_out; | ||
| 1258 | + } | ||
| 1259 | + | ||
| 1260 | + if (mount("tmpfs", s_pptrdir, "tmpfs", polyptr->mount_flags, polyptr->mount_opts) < 0) { | ||
| 1261 | + pam_syslog(idata->pamh, LOG_ERR, "Error mounting tmpfs on %s, %m", | ||
| 1262 | + polyptr->dir); | ||
| 1263 | + goto error_out; | ||
| 1264 | + } | ||
| 1265 | |||
| 1266 | - if (polyptr->flags & POLYDIR_NOINIT) | ||
| 1267 | - return PAM_SUCCESS; | ||
| 1268 | + if (polyptr->flags & POLYDIR_NOINIT) { | ||
| 1269 | + retval = PAM_SUCCESS; | ||
| 1270 | + goto cleanup; | ||
| 1271 | + } | ||
| 1272 | |||
| 1273 | - return inst_init(polyptr, "tmpfs", idata, 1); | ||
| 1274 | + retval = inst_init(polyptr, "tmpfs", idata, 1); | ||
| 1275 | + goto cleanup; | ||
| 1276 | } | ||
| 1277 | |||
| 1278 | - if (stat(polyptr->dir, &statbuf) < 0) { | ||
| 1279 | - pam_syslog(idata->pamh, LOG_ERR, "Error stating %s: %m", | ||
| 1280 | - polyptr->dir); | ||
| 1281 | - return PAM_SESSION_ERR; | ||
| 1282 | + if (fstat(dfd_pptrdir, &statbuf) < 0) { | ||
| 1283 | + pam_syslog(idata->pamh, LOG_ERR, "Error stating %s: %m", polyptr->dir); | ||
| 1284 | + goto error_out; | ||
| 1285 | } | ||
| 1286 | |||
| 1287 | /* | ||
| 1288 | @@ -1579,15 +1771,16 @@ static int ns_setup(struct polydir_s *polyptr, | ||
| 1289 | * security policy. | ||
| 1290 | */ | ||
| 1291 | #ifdef WITH_SELINUX | ||
| 1292 | - retval = poly_name(polyptr, &instname, &instcontext, | ||
| 1293 | - &origcontext, idata); | ||
| 1294 | + retval = poly_name(polyptr, &instcontext, &origcontext, idata); | ||
| 1295 | #else | ||
| 1296 | - retval = poly_name(polyptr, &instname, idata); | ||
| 1297 | + retval = poly_name(polyptr, idata); | ||
| 1298 | #endif | ||
| 1299 | |||
| 1300 | if (retval != PAM_SUCCESS) { | ||
| 1301 | - if (retval != PAM_IGNORE) | ||
| 1302 | + if (retval != PAM_IGNORE) { | ||
| 1303 | pam_syslog(idata->pamh, LOG_ERR, "Error getting instance name"); | ||
| 1304 | + goto error_out; | ||
| 1305 | + } | ||
| 1306 | goto cleanup; | ||
| 1307 | } else { | ||
| 1308 | #ifdef WITH_SELINUX | ||
| 1309 | @@ -1598,22 +1791,33 @@ static int ns_setup(struct polydir_s *polyptr, | ||
| 1310 | #endif | ||
| 1311 | } | ||
| 1312 | |||
| 1313 | - if (asprintf(&inst_dir, "%s%s", polyptr->instance_prefix, instname) < 0) | ||
| 1314 | - goto error_out; | ||
| 1315 | - | ||
| 1316 | - if (idata->flags & PAMNS_DEBUG) | ||
| 1317 | - pam_syslog(idata->pamh, LOG_DEBUG, "instance_dir %s", | ||
| 1318 | - inst_dir); | ||
| 1319 | + /* | ||
| 1320 | + * Gets a fd in a secure manner (we may be operating on a path under | ||
| 1321 | + * user control), and check it's compliant. | ||
| 1322 | + * Then, we should *always* operate on *this* fd and a relative path | ||
| 1323 | + * to be protected against race conditions. | ||
| 1324 | + */ | ||
| 1325 | + dfd_iparent = secure_opendir(polyptr->instance_parent, | ||
| 1326 | + SECURE_OPENDIR_PROTECT | SECURE_OPENDIR_MKDIR, 0, idata); | ||
| 1327 | + if (dfd_iparent == -1) { | ||
| 1328 | + pam_syslog(idata->pamh, LOG_ERR, | ||
| 1329 | + "polyptr->instance_parent %s access error", | ||
| 1330 | + polyptr->instance_parent); | ||
| 1331 | + goto error_out; | ||
| 1332 | + } | ||
| 1333 | + if (check_inst_parent(dfd_iparent, idata)) { | ||
| 1334 | + goto error_out; | ||
| 1335 | + } | ||
| 1336 | |||
| 1337 | /* | ||
| 1338 | * Create instance directory with appropriate security | ||
| 1339 | * contexts, owner, group and mode bits. | ||
| 1340 | */ | ||
| 1341 | #ifdef WITH_SELINUX | ||
| 1342 | - retval = create_instance(polyptr, inst_dir, &statbuf, instcontext, | ||
| 1343 | - origcontext, idata); | ||
| 1344 | + retval = create_instance(polyptr, dfd_iparent, &statbuf, instcontext, | ||
| 1345 | + origcontext, idata); | ||
| 1346 | #else | ||
| 1347 | - retval = create_instance(polyptr, inst_dir, &statbuf, idata); | ||
| 1348 | + retval = create_instance(polyptr, dfd_iparent, &statbuf, idata); | ||
| 1349 | #endif | ||
| 1350 | |||
| 1351 | if (retval == PAM_IGNORE) { | ||
| 1352 | @@ -1625,19 +1829,48 @@ static int ns_setup(struct polydir_s *polyptr, | ||
| 1353 | goto error_out; | ||
| 1354 | } | ||
| 1355 | |||
| 1356 | + /* | ||
| 1357 | + * Instead of getting a new secure fd, we reuse the fd opened on directory | ||
| 1358 | + * polyptr->instance_parent to ensure we are working on the same dir as | ||
| 1359 | + * previously, and thus ensure that previous checks (e.g. check_inst_parent()) | ||
| 1360 | + * are still relevant. | ||
| 1361 | + */ | ||
| 1362 | + dfd_ipath = openat(dfd_iparent, polyptr->instname, | ||
| 1363 | + O_PATH | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC); | ||
| 1364 | + if (dfd_ipath == -1) { | ||
| 1365 | + pam_syslog(idata->pamh, LOG_ERR, "Error openat on %s, %m", | ||
| 1366 | + polyptr->instname); | ||
| 1367 | + goto error_out; | ||
| 1368 | + } | ||
| 1369 | + | ||
| 1370 | + if (pam_sprintf(s_ipath, "/proc/self/fd/%d", dfd_ipath) < 0) { | ||
| 1371 | + pam_syslog(idata->pamh, LOG_ERR, "Error pam_sprintf s_ipath"); | ||
| 1372 | + goto error_out; | ||
| 1373 | + } | ||
| 1374 | + | ||
| 1375 | + if (pam_sprintf(s_pptrdir, "/proc/self/fd/%d", dfd_pptrdir) < 0) { | ||
| 1376 | + pam_syslog(idata->pamh, LOG_ERR, "Error pam_sprintf s_pptrdir"); | ||
| 1377 | + goto error_out; | ||
| 1378 | + } | ||
| 1379 | + | ||
| 1380 | /* | ||
| 1381 | * Bind mount instance directory on top of the polyinstantiated | ||
| 1382 | * directory to provide an instance of polyinstantiated directory | ||
| 1383 | * based on polyinstantiated method. | ||
| 1384 | + * | ||
| 1385 | + * Operates on magic links created from two fd obtained securely | ||
| 1386 | + * to protect against race conditions and symlink attacks. Indeed, | ||
| 1387 | + * the source and destination can be in a user controled path. | ||
| 1388 | */ | ||
| 1389 | - if (mount(inst_dir, polyptr->dir, NULL, MS_BIND, NULL) < 0) { | ||
| 1390 | - pam_syslog(idata->pamh, LOG_ERR, "Error mounting %s on %s, %m", | ||
| 1391 | - inst_dir, polyptr->dir); | ||
| 1392 | + if(mount(s_ipath, s_pptrdir, NULL, MS_BIND, NULL) < 0) { | ||
| 1393 | + pam_syslog(idata->pamh, LOG_ERR, | ||
| 1394 | + "Error mounting %s on %s (%s on %s), %m", | ||
| 1395 | + s_ipath, s_pptrdir, polyptr->instance_absolute, polyptr->dir); | ||
| 1396 | goto error_out; | ||
| 1397 | } | ||
| 1398 | |||
| 1399 | if (!(polyptr->flags & POLYDIR_NOINIT)) | ||
| 1400 | - retval = inst_init(polyptr, inst_dir, idata, newdir); | ||
| 1401 | + retval = inst_init(polyptr, polyptr->instance_absolute, idata, newdir); | ||
| 1402 | |||
| 1403 | goto cleanup; | ||
| 1404 | |||
| 1405 | @@ -1649,8 +1882,12 @@ error_out: | ||
| 1406 | retval = PAM_SESSION_ERR; | ||
| 1407 | |||
| 1408 | cleanup: | ||
| 1409 | - free(inst_dir); | ||
| 1410 | - free(instname); | ||
| 1411 | + if (dfd_iparent != -1) | ||
| 1412 | + close(dfd_iparent); | ||
| 1413 | + if (dfd_ipath != -1) | ||
| 1414 | + close(dfd_ipath); | ||
| 1415 | + if (dfd_pptrdir != -1) | ||
| 1416 | + close(dfd_pptrdir); | ||
| 1417 | #ifdef WITH_SELINUX | ||
| 1418 | freecon(instcontext); | ||
| 1419 | freecon(origcontext); | ||
| 1420 | @@ -1689,6 +1926,7 @@ static int cleanup_tmpdirs(struct instance_data *idata) | ||
| 1421 | { | ||
| 1422 | struct polydir_s *pptr; | ||
| 1423 | pid_t rc, pid; | ||
| 1424 | + int dfd = -1; | ||
| 1425 | struct sigaction newsa, oldsa; | ||
| 1426 | int status; | ||
| 1427 | |||
| 1428 | @@ -1700,7 +1938,17 @@ static int cleanup_tmpdirs(struct instance_data *idata) | ||
| 1429 | } | ||
| 1430 | |||
| 1431 | for (pptr = idata->polydirs_ptr; pptr; pptr = pptr->next) { | ||
| 1432 | - if (pptr->method == TMPDIR && access(pptr->instance_prefix, F_OK) == 0) { | ||
| 1433 | + if (pptr->method == TMPDIR) { | ||
| 1434 | + | ||
| 1435 | + dfd = secure_opendir_stateless(pptr->instance_parent); | ||
| 1436 | + if (dfd == -1) | ||
| 1437 | + continue; | ||
| 1438 | + | ||
| 1439 | + if (faccessat(dfd, pptr->instname, F_OK, AT_SYMLINK_NOFOLLOW) != 0) { | ||
| 1440 | + close(dfd); | ||
| 1441 | + continue; | ||
| 1442 | + } | ||
| 1443 | + | ||
| 1444 | pid = fork(); | ||
| 1445 | if (pid == 0) { | ||
| 1446 | static char *envp[] = { NULL }; | ||
| 1447 | @@ -1710,9 +1958,21 @@ static int cleanup_tmpdirs(struct instance_data *idata) | ||
| 1448 | _exit(1); | ||
| 1449 | } | ||
| 1450 | #endif | ||
| 1451 | - if (execle("/bin/rm", "/bin/rm", "-rf", pptr->instance_prefix, NULL, envp) < 0) | ||
| 1452 | - _exit(1); | ||
| 1453 | + if (fchdir(dfd) == -1) { | ||
| 1454 | + pam_syslog(idata->pamh, LOG_ERR, "Failed fchdir to %s: %m", | ||
| 1455 | + pptr->instance_absolute); | ||
| 1456 | + _exit(1); | ||
| 1457 | + } | ||
| 1458 | + | ||
| 1459 | + close_fds_pre_exec(idata); | ||
| 1460 | + | ||
| 1461 | + execle("/bin/rm", "/bin/rm", "-rf", pptr->instname, NULL, envp); | ||
| 1462 | + _exit(1); | ||
| 1463 | } else if (pid > 0) { | ||
| 1464 | + | ||
| 1465 | + if (dfd != -1) | ||
| 1466 | + close(dfd); | ||
| 1467 | + | ||
| 1468 | while (((rc = waitpid(pid, &status, 0)) == (pid_t)-1) && | ||
| 1469 | (errno == EINTR)); | ||
| 1470 | if (rc == (pid_t)-1) { | ||
| 1471 | @@ -1725,8 +1985,12 @@ static int cleanup_tmpdirs(struct instance_data *idata) | ||
| 1472 | "Error removing %s", pptr->instance_prefix); | ||
| 1473 | } | ||
| 1474 | } else if (pid < 0) { | ||
| 1475 | + | ||
| 1476 | + if (dfd != -1) | ||
| 1477 | + close(dfd); | ||
| 1478 | + | ||
| 1479 | pam_syslog(idata->pamh, LOG_ERR, | ||
| 1480 | - "Cannot fork to run namespace init script, %m"); | ||
| 1481 | + "Cannot fork to cleanup temporary directory, %m"); | ||
| 1482 | rc = PAM_SESSION_ERR; | ||
| 1483 | goto out; | ||
| 1484 | } | ||
| 1485 | @@ -1748,6 +2012,7 @@ out: | ||
| 1486 | static int setup_namespace(struct instance_data *idata, enum unmnt_op unmnt) | ||
| 1487 | { | ||
| 1488 | int retval = 0, need_poly = 0, changing_dir = 0; | ||
| 1489 | + int dfd = -1; | ||
| 1490 | char *cptr, *fptr, poly_parent[PATH_MAX]; | ||
| 1491 | struct polydir_s *pptr; | ||
| 1492 | |||
| 1493 | @@ -1863,13 +2128,21 @@ static int setup_namespace(struct instance_data *idata, enum unmnt_op unmnt) | ||
| 1494 | strcpy(poly_parent, "/"); | ||
| 1495 | else if (cptr) | ||
| 1496 | *cptr = '\0'; | ||
| 1497 | - if (chdir(poly_parent) < 0) { | ||
| 1498 | + | ||
| 1499 | + dfd = secure_opendir_stateless(poly_parent); | ||
| 1500 | + if (dfd == -1) { | ||
| 1501 | + pam_syslog(idata->pamh, LOG_ERR, | ||
| 1502 | + "Failed opening %s to fchdir: %m", poly_parent); | ||
| 1503 | + } | ||
| 1504 | + else if (fchdir(dfd) == -1) { | ||
| 1505 | pam_syslog(idata->pamh, LOG_ERR, | ||
| 1506 | - "Can't chdir to %s, %m", poly_parent); | ||
| 1507 | + "Failed fchdir to %s: %m", poly_parent); | ||
| 1508 | } | ||
| 1509 | + if (dfd != -1) | ||
| 1510 | + close(dfd); | ||
| 1511 | } | ||
| 1512 | |||
| 1513 | - if (umount(pptr->rdir) < 0) { | ||
| 1514 | + if (secure_umount(pptr->rdir) < 0) { | ||
| 1515 | int saved_errno = errno; | ||
| 1516 | pam_syslog(idata->pamh, LOG_ERR, "Unmount of %s failed, %m", | ||
| 1517 | pptr->rdir); | ||
| 1518 | @@ -1939,7 +2212,7 @@ static int orig_namespace(struct instance_data *idata) | ||
| 1519 | "Unmounting instance dir for user %d & dir %s", | ||
| 1520 | idata->uid, pptr->dir); | ||
| 1521 | |||
| 1522 | - if (umount(pptr->dir) < 0) { | ||
| 1523 | + if (secure_umount(pptr->dir) < 0) { | ||
| 1524 | pam_syslog(idata->pamh, LOG_ERR, "Unmount of %s failed, %m", | ||
| 1525 | pptr->dir); | ||
| 1526 | return PAM_SESSION_ERR; | ||
| 1527 | diff --git a/modules/pam_namespace/pam_namespace.h b/modules/pam_namespace/pam_namespace.h | ||
| 1528 | index b51f284..abd570d 100644 | ||
| 1529 | --- a/modules/pam_namespace/pam_namespace.h | ||
| 1530 | +++ b/modules/pam_namespace/pam_namespace.h | ||
| 1531 | @@ -44,21 +44,16 @@ | ||
| 1532 | #include <stdlib.h> | ||
| 1533 | #include <errno.h> | ||
| 1534 | #include <syslog.h> | ||
| 1535 | -#include <dlfcn.h> | ||
| 1536 | -#include <stdarg.h> | ||
| 1537 | #include <pwd.h> | ||
| 1538 | #include <grp.h> | ||
| 1539 | #include <limits.h> | ||
| 1540 | #include <sys/types.h> | ||
| 1541 | #include <sys/stat.h> | ||
| 1542 | -#include <sys/resource.h> | ||
| 1543 | #include <sys/mount.h> | ||
| 1544 | #include <sys/wait.h> | ||
| 1545 | -#include <libgen.h> | ||
| 1546 | #include <fcntl.h> | ||
| 1547 | #include <sched.h> | ||
| 1548 | #include <glob.h> | ||
| 1549 | -#include <locale.h> | ||
| 1550 | #include "security/pam_modules.h" | ||
| 1551 | #include "security/pam_modutil.h" | ||
| 1552 | #include "security/pam_ext.h" | ||
| 1553 | @@ -112,7 +107,7 @@ | ||
| 1554 | #define PAMNS_MOUNT_PRIVATE 0x00080000 /* Make the polydir mounts private */ | ||
| 1555 | |||
| 1556 | /* polydir flags */ | ||
| 1557 | -#define POLYDIR_EXCLUSIVE 0x00000001 /* polyinstatiate exclusively for override uids */ | ||
| 1558 | +#define POLYDIR_EXCLUSIVE 0x00000001 /* polyinstantiate exclusively for override uids */ | ||
| 1559 | #define POLYDIR_CREATE 0x00000002 /* create the polydir */ | ||
| 1560 | #define POLYDIR_NOINIT 0x00000004 /* no init script */ | ||
| 1561 | #define POLYDIR_SHARED 0x00000008 /* share context/level instances among users */ | ||
| 1562 | @@ -124,6 +119,13 @@ | ||
| 1563 | #define NAMESPACE_POLYDIR_DATA "pam_namespace:polydir_data" | ||
| 1564 | #define NAMESPACE_PROTECT_DATA "pam_namespace:protect_data" | ||
| 1565 | |||
| 1566 | +/* | ||
| 1567 | + * Operation mode for function secure_opendir() | ||
| 1568 | + */ | ||
| 1569 | +#define SECURE_OPENDIR_PROTECT 0x00000001 | ||
| 1570 | +#define SECURE_OPENDIR_MKDIR 0x00000002 | ||
| 1571 | +#define SECURE_OPENDIR_FULL_FD 0x00000004 | ||
| 1572 | + | ||
| 1573 | /* | ||
| 1574 | * Polyinstantiation method options, based on user, security context | ||
| 1575 | * or both | ||
| 1576 | @@ -161,6 +163,9 @@ struct polydir_s { | ||
| 1577 | char dir[PATH_MAX]; /* directory to polyinstantiate */ | ||
| 1578 | char rdir[PATH_MAX]; /* directory to unmount (based on RUSER) */ | ||
| 1579 | char instance_prefix[PATH_MAX]; /* prefix for instance dir path name */ | ||
| 1580 | + char instance_absolute[PATH_MAX]; /* absolute path to the instance dir (instance_parent + instname) */ | ||
| 1581 | + char instance_parent[PATH_MAX]; /* parent dir of the instance dir */ | ||
| 1582 | + char *instname; /* last segment of the path to the instance dir */ | ||
| 1583 | enum polymethod method; /* method used to polyinstantiate */ | ||
| 1584 | unsigned int num_uids; /* number of override uids */ | ||
| 1585 | uid_t *uid; /* list of override uids */ | ||
| 1586 | -- | ||
| 1587 | 2.50.1 | ||
| 1588 | |||
diff --git a/meta/recipes-extended/pam/libpam/CVE-2025-6020-02.patch b/meta/recipes-extended/pam/libpam/CVE-2025-6020-02.patch new file mode 100644 index 0000000000..712d60581c --- /dev/null +++ b/meta/recipes-extended/pam/libpam/CVE-2025-6020-02.patch | |||
| @@ -0,0 +1,187 @@ | |||
| 1 | From 592d84e1265d04c3104acee815a503856db503a1 Mon Sep 17 00:00:00 2001 | ||
| 2 | From: Olivier Bal-Petre <olivier.bal-petre@ssi.gouv.fr> | ||
| 3 | Date: Tue, 4 Mar 2025 14:37:02 +0100 | ||
| 4 | Subject: [PATCH] pam_namespace: add flags to indicate path safety | ||
| 5 | |||
| 6 | Add two flags in the script to indicate if the paths to the polydir | ||
| 7 | and the instance directories are safe (root owned and writable by | ||
| 8 | root only). | ||
| 9 | |||
| 10 | Signed-off-by: Olivier Bal-Petre <olivier.bal-petre@ssi.gouv.fr> | ||
| 11 | Signed-off-by: Dmitry V. Levin <ldv@strace.io> | ||
| 12 | |||
| 13 | Upstream-Status: Backport [https://github.com/linux-pam/linux-pam/commit/592d84e1265d04c3104acee815a503856db503a1] | ||
| 14 | CVE: CVE-2025-6020 | ||
| 15 | Signed-off-by: Hitendra Prajapati <hprajapati@mvista.com> | ||
| 16 | --- | ||
| 17 | modules/pam_namespace/namespace.init | 56 ++++++++++++------- | ||
| 18 | modules/pam_namespace/pam_namespace.c | 79 ++++++++++++++++++++++++++- | ||
| 19 | 2 files changed, 115 insertions(+), 20 deletions(-) | ||
| 20 | |||
| 21 | diff --git a/modules/pam_namespace/namespace.init b/modules/pam_namespace/namespace.init | ||
| 22 | index 67d4aa2..8782178 100755 | ||
| 23 | --- a/modules/pam_namespace/namespace.init | ||
| 24 | +++ b/modules/pam_namespace/namespace.init | ||
| 25 | @@ -1,25 +1,43 @@ | ||
| 26 | #!/bin/sh | ||
| 27 | -# It receives polydir path as $1, the instance path as $2, | ||
| 28 | -# a flag whether the instance dir was newly created (0 - no, 1 - yes) in $3, | ||
| 29 | -# and user name in $4. | ||
| 30 | +# It receives as arguments: | ||
| 31 | +# - $1 polydir path (see WARNING below) | ||
| 32 | +# - $2 instance path (see WARNING below) | ||
| 33 | +# - $3 flag whether the instance dir was newly created (0 - no, 1 - yes) | ||
| 34 | +# - $4 user name | ||
| 35 | +# - $5 flag whether the polydir path ($1) is safe (0 - unsafe, 1 -safe) | ||
| 36 | +# - $6 flag whether the instance path ($2) is safe (0 - unsafe, 1 - safe) | ||
| 37 | +# | ||
| 38 | +# WARNING: This script is invoked with full root privileges. Accessing | ||
| 39 | +# the polydir ($1) and the instance ($2) directories in this context may be | ||
| 40 | +# extremely dangerous as those can be under user control. The flags $5 and $6 | ||
| 41 | +# are provided to let you know if all the segments part of the path (except the | ||
| 42 | +# last one) are owned by root and are writable by root only. If the path does | ||
| 43 | +# not meet these criteria, you expose yourself to possible symlink attacks when | ||
| 44 | +# accessing these path. | ||
| 45 | +# However, even if the path components are safe, the content of the | ||
| 46 | +# directories may still be owned/writable by a user, so care must be taken! | ||
| 47 | # | ||
| 48 | # The following section will copy the contents of /etc/skel if this is a | ||
| 49 | # newly created home directory. | ||
| 50 | -if [ "$3" = 1 ]; then | ||
| 51 | - # This line will fix the labeling on all newly created directories | ||
| 52 | - [ -x /sbin/restorecon ] && /sbin/restorecon "$1" | ||
| 53 | - user="$4" | ||
| 54 | - passwd=$(getent passwd "$user") | ||
| 55 | - homedir=$(echo "$passwd" | cut -f6 -d":") | ||
| 56 | - if [ "$1" = "$homedir" ]; then | ||
| 57 | - gid=$(echo "$passwd" | cut -f4 -d":") | ||
| 58 | - cp -rT /etc/skel "$homedir" | ||
| 59 | - chown -R "$user":"$gid" "$homedir" | ||
| 60 | - mask=$(awk '/^UMASK/{gsub("#.*$", "", $2); print $2; exit}' /etc/login.defs) | ||
| 61 | - mode=$(printf "%o" $((0777 & ~$mask))) | ||
| 62 | - chmod ${mode:-700} "$homedir" | ||
| 63 | - [ -x /sbin/restorecon ] && /sbin/restorecon -R "$homedir" | ||
| 64 | - fi | ||
| 65 | -fi | ||
| 66 | |||
| 67 | +# Executes only if the polydir path is safe | ||
| 68 | +if [ "$5" = 1 ]; then | ||
| 69 | + | ||
| 70 | + if [ "$3" = 1 ]; then | ||
| 71 | + # This line will fix the labeling on all newly created directories | ||
| 72 | + [ -x /sbin/restorecon ] && /sbin/restorecon "$1" | ||
| 73 | + user="$4" | ||
| 74 | + passwd=$(getent passwd "$user") | ||
| 75 | + homedir=$(echo "$passwd" | cut -f6 -d":") | ||
| 76 | + if [ "$1" = "$homedir" ]; then | ||
| 77 | + gid=$(echo "$passwd" | cut -f4 -d":") | ||
| 78 | + cp -rT /etc/skel "$homedir" | ||
| 79 | + chown -R "$user":"$gid" "$homedir" | ||
| 80 | + mask=$(sed -E -n 's/^UMASK[[:space:]]+([^#[:space:]]+).*/\1/p' /etc/login.defs) | ||
| 81 | + mode=$(printf "%o" $((0777 & ~mask))) | ||
| 82 | + chmod ${mode:-700} "$homedir" | ||
| 83 | + [ -x /sbin/restorecon ] && /sbin/restorecon -R "$homedir" | ||
| 84 | + fi | ||
| 85 | + fi | ||
| 86 | +fi | ||
| 87 | exit 0 | ||
| 88 | diff --git a/modules/pam_namespace/pam_namespace.c b/modules/pam_namespace/pam_namespace.c | ||
| 89 | index 22d8445..8cba036 100644 | ||
| 90 | --- a/modules/pam_namespace/pam_namespace.c | ||
| 91 | +++ b/modules/pam_namespace/pam_namespace.c | ||
| 92 | @@ -1390,6 +1390,79 @@ static int check_inst_parent(int dfd, struct instance_data *idata) | ||
| 93 | return PAM_SUCCESS; | ||
| 94 | } | ||
| 95 | |||
| 96 | +/* | ||
| 97 | + * Check for a given absolute path that all segments except the last one are: | ||
| 98 | + * 1. a directory owned by root and not writable by group or others | ||
| 99 | + * 2. a symlink owned by root and referencing a directory respecting 1. | ||
| 100 | + * Returns 0 if safe, -1 is unsafe. | ||
| 101 | + * If the path is not accessible (does not exist, hidden under a mount...), | ||
| 102 | + * returns -1 (unsafe). | ||
| 103 | + */ | ||
| 104 | +static int check_safe_path(const char *path, struct instance_data *idata) | ||
| 105 | +{ | ||
| 106 | + char *p = strdup(path); | ||
| 107 | + char *d; | ||
| 108 | + char *dir = p; | ||
| 109 | + struct stat st; | ||
| 110 | + | ||
| 111 | + if (p == NULL) | ||
| 112 | + return -1; | ||
| 113 | + | ||
| 114 | + /* Check path is absolute */ | ||
| 115 | + if (p[0] != '/') | ||
| 116 | + goto error; | ||
| 117 | + | ||
| 118 | + strip_trailing_slashes(p); | ||
| 119 | + | ||
| 120 | + /* Last segment of the path may be owned by the user */ | ||
| 121 | + if ((d = strrchr(dir, '/')) != NULL) | ||
| 122 | + *d = '\0'; | ||
| 123 | + | ||
| 124 | + while ((d=strrchr(dir, '/')) != NULL) { | ||
| 125 | + | ||
| 126 | + /* Do not follow symlinks */ | ||
| 127 | + if (lstat(dir, &st) != 0) | ||
| 128 | + goto error; | ||
| 129 | + | ||
| 130 | + if (S_ISLNK(st.st_mode)) { | ||
| 131 | + if (st.st_uid != 0) { | ||
| 132 | + if (idata->flags & PAMNS_DEBUG) | ||
| 133 | + pam_syslog(idata->pamh, LOG_DEBUG, | ||
| 134 | + "Path deemed unsafe: Symlink %s should be owned by root", dir); | ||
| 135 | + goto error; | ||
| 136 | + } | ||
| 137 | + | ||
| 138 | + /* Follow symlinks */ | ||
| 139 | + if (stat(dir, &st) != 0) | ||
| 140 | + goto error; | ||
| 141 | + } | ||
| 142 | + | ||
| 143 | + if (!S_ISDIR(st.st_mode)) { | ||
| 144 | + if (idata->flags & PAMNS_DEBUG) | ||
| 145 | + pam_syslog(idata->pamh, LOG_DEBUG, | ||
| 146 | + "Path deemed unsafe: %s is expected to be a directory", dir); | ||
| 147 | + goto error; | ||
| 148 | + } | ||
| 149 | + | ||
| 150 | + if (st.st_uid != 0 || | ||
| 151 | + ((st.st_mode & (S_IWGRP|S_IWOTH)) && !(st.st_mode & S_ISVTX))) { | ||
| 152 | + if (idata->flags & PAMNS_DEBUG) | ||
| 153 | + pam_syslog(idata->pamh, LOG_DEBUG, | ||
| 154 | + "Path deemed unsafe: %s should be owned by root, and not be writable by group or others", dir); | ||
| 155 | + goto error; | ||
| 156 | + } | ||
| 157 | + | ||
| 158 | + *d = '\0'; | ||
| 159 | + } | ||
| 160 | + | ||
| 161 | + free(p); | ||
| 162 | + return 0; | ||
| 163 | + | ||
| 164 | +error: | ||
| 165 | + free(p); | ||
| 166 | + return -1; | ||
| 167 | +} | ||
| 168 | + | ||
| 169 | /* | ||
| 170 | * Check to see if there is a namespace initialization script in | ||
| 171 | * the /etc/security directory. If such a script exists | ||
| 172 | @@ -1438,7 +1511,11 @@ static int inst_init(const struct polydir_s *polyptr, const char *ipath, | ||
| 173 | close_fds_pre_exec(idata); | ||
| 174 | |||
| 175 | execle(init_script, init_script, | ||
| 176 | - polyptr->dir, ipath, newdir?"1":"0", idata->user, NULL, envp); | ||
| 177 | + polyptr->dir, ipath, | ||
| 178 | + newdir ? "1":"0", idata->user, | ||
| 179 | + (check_safe_path(polyptr->dir, idata) == -1) ? "0":"1", | ||
| 180 | + (check_safe_path(ipath, idata) == -1) ? "0":"1", | ||
| 181 | + NULL, envp); | ||
| 182 | _exit(1); | ||
| 183 | } else if (pid > 0) { | ||
| 184 | while (((rc = waitpid(pid, &status, 0)) == (pid_t)-1) && | ||
| 185 | -- | ||
| 186 | 2.50.1 | ||
| 187 | |||
diff --git a/meta/recipes-extended/pam/libpam/CVE-2025-6020-03.patch b/meta/recipes-extended/pam/libpam/CVE-2025-6020-03.patch new file mode 100644 index 0000000000..2a0450092e --- /dev/null +++ b/meta/recipes-extended/pam/libpam/CVE-2025-6020-03.patch | |||
| @@ -0,0 +1,35 @@ | |||
| 1 | From 976c20079358d133514568fc7fd95c02df8b5773 Mon Sep 17 00:00:00 2001 | ||
| 2 | From: "Dmitry V. Levin" <ldv@strace.io> | ||
| 3 | Date: Tue, 27 May 2025 08:00:00 +0000 | ||
| 4 | Subject: [PATCH] pam_namespace: secure_opendir: do not look at the group | ||
| 5 | ownership | ||
| 6 | |||
| 7 | When the directory is not group-writable, the group ownership does | ||
| 8 | not matter, and when it is group-writable, there should not be any | ||
| 9 | exceptions for the root group as there is no guarantee that the root | ||
| 10 | group does not include non-root users. | ||
| 11 | |||
| 12 | Upstream-Status: Backport [https://github.com/linux-pam/linux-pam/commit/976c20079358d133514568fc7fd95c02df8b5773] | ||
| 13 | CVE: CVE-2025-6020 | ||
| 14 | Signed-off-by: Hitendra Prajapati <hprajapati@mvista.com> | ||
| 15 | --- | ||
| 16 | modules/pam_namespace/pam_namespace.c | 3 +-- | ||
| 17 | 1 file changed, 1 insertion(+), 2 deletions(-) | ||
| 18 | |||
| 19 | diff --git a/modules/pam_namespace/pam_namespace.c b/modules/pam_namespace/pam_namespace.c | ||
| 20 | index 8cba036..630cf0a 100644 | ||
| 21 | --- a/modules/pam_namespace/pam_namespace.c | ||
| 22 | +++ b/modules/pam_namespace/pam_namespace.c | ||
| 23 | @@ -215,8 +215,7 @@ static int secure_opendir(const char *path, int opm, mode_t mode, | ||
| 24 | if (dfd_next == -1) | ||
| 25 | goto error; | ||
| 26 | } else if (st.st_uid != 0 | ||
| 27 | - || (st.st_gid != 0 && (st.st_mode & S_IWGRP)) | ||
| 28 | - || (st.st_mode & S_IWOTH)) { | ||
| 29 | + || (st.st_mode & (S_IWGRP|S_IWOTH))) { | ||
| 30 | /* do not follow symlinks on subdirectories */ | ||
| 31 | flags |= O_NOFOLLOW; | ||
| 32 | } | ||
| 33 | -- | ||
| 34 | 2.50.1 | ||
| 35 | |||
diff --git a/meta/recipes-extended/pam/libpam_1.5.2.bb b/meta/recipes-extended/pam/libpam_1.5.2.bb index 567f9741cb..658212dd82 100644 --- a/meta/recipes-extended/pam/libpam_1.5.2.bb +++ b/meta/recipes-extended/pam/libpam_1.5.2.bb | |||
| @@ -29,6 +29,11 @@ SRC_URI = "https://github.com/linux-pam/linux-pam/releases/download/v${PV}/Linux | |||
| 29 | file://CVE-2024-22365.patch \ | 29 | file://CVE-2024-22365.patch \ |
| 30 | file://CVE-2024-10041-1.patch \ | 30 | file://CVE-2024-10041-1.patch \ |
| 31 | file://CVE-2024-10041-2.patch \ | 31 | file://CVE-2024-10041-2.patch \ |
| 32 | file://0001-pam_namespace-include-stdint-h.patch \ | ||
| 33 | file://0001-pam_inline-introduce-pam_asprint.patch \ | ||
| 34 | file://CVE-2025-6020-01.patch \ | ||
| 35 | file://CVE-2025-6020-02.patch \ | ||
| 36 | file://CVE-2025-6020-03.patch \ | ||
| 32 | " | 37 | " |
| 33 | 38 | ||
| 34 | SRC_URI[sha256sum] = "e4ec7131a91da44512574268f493c6d8ca105c87091691b8e9b56ca685d4f94d" | 39 | SRC_URI[sha256sum] = "e4ec7131a91da44512574268f493c6d8ca105c87091691b8e9b56ca685d4f94d" |
