diff options
author | Hitendra Prajapati <hprajapati@mvista.com> | 2025-07-22 17:57:29 +0530 |
---|---|---|
committer | Steve Sakoman <steve@sakoman.com> | 2025-07-29 07:59:52 -0700 |
commit | cf89d7b3bf621240876d2f0215395d8034e81fdc (patch) | |
tree | 74c20902e3007dd100ec09d3e287d81053caea84 | |
parent | 99f48be958e8b141502f8fea0ef34e1c9b85cb27 (diff) | |
download | poky-cf89d7b3bf621240876d2f0215395d8034e81fdc.tar.gz |
libpam: fix CVE-2025-6020
Upstream-Status: Backport from https://github.com/linux-pam/linux-pam/commit/475bd60c552b98c7eddb3270b0b4196847c0072e && https://github.com/linux-pam/linux-pam/commit/592d84e1265d04c3104acee815a503856db503a1 && https://github.com/linux-pam/linux-pam/commit/976c20079358d133514568fc7fd95c02df8b5773
(From OE-Core rev: dd5bbac75b1d8f7ebd83d5c9945bd860e397ba07)
Signed-off-by: Hitendra Prajapati <hprajapati@mvista.com>
Signed-off-by: Steve Sakoman <steve@sakoman.com>
6 files changed, 2206 insertions, 0 deletions
diff --git a/meta/recipes-extended/pam/libpam/0001-pam-inline-pam-asprintf.patch b/meta/recipes-extended/pam/libpam/0001-pam-inline-pam-asprintf.patch new file mode 100644 index 0000000000..9d1a0223df --- /dev/null +++ b/meta/recipes-extended/pam/libpam/0001-pam-inline-pam-asprintf.patch | |||
@@ -0,0 +1,101 @@ | |||
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 | 36 ++++++++++++++++++++++++++++++++++ | ||
26 | 2 files changed, 42 insertions(+) | ||
27 | |||
28 | diff --git a/libpam/include/pam_cc_compat.h b/libpam/include/pam_cc_compat.h | ||
29 | index 0a6e32d..af05428 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 7721c0b..ec0497c 100644 | ||
47 | --- a/libpam/include/pam_inline.h | ||
48 | +++ b/libpam/include/pam_inline.h | ||
49 | @@ -9,6 +9,8 @@ | ||
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 | @@ -126,6 +128,40 @@ pam_drop_response(struct pam_response *reply, int replies) | ||
59 | } | ||
60 | |||
61 | |||
62 | +static inline char * PAM_FORMAT((printf, 1, 2)) PAM_NONNULL((1)) PAM_ATTRIBUTE_MALLOC | ||
63 | +pam_asprintf(const char *fmt, ...) | ||
64 | +{ | ||
65 | + int rc; | ||
66 | + char *res; | ||
67 | + va_list ap; | ||
68 | + | ||
69 | + va_start(ap, fmt); | ||
70 | + rc = vasprintf(&res, fmt, ap); | ||
71 | + va_end(ap); | ||
72 | + | ||
73 | + return rc < 0 ? NULL : res; | ||
74 | +} | ||
75 | + | ||
76 | +static inline int PAM_FORMAT((printf, 3, 4)) PAM_NONNULL((3)) | ||
77 | +pam_snprintf(char *str, size_t size, const char *fmt, ...) | ||
78 | +{ | ||
79 | + int rc; | ||
80 | + va_list ap; | ||
81 | + | ||
82 | + va_start(ap, fmt); | ||
83 | + rc = vsnprintf(str, size, fmt, ap); | ||
84 | + va_end(ap); | ||
85 | + | ||
86 | + if (rc < 0 || (unsigned int) rc >= size) | ||
87 | + return -1; | ||
88 | + return rc; | ||
89 | +} | ||
90 | + | ||
91 | +#define pam_sprintf(str_, fmt_, ...) \ | ||
92 | + pam_snprintf((str_), sizeof(str_) + PAM_MUST_BE_ARRAY(str_), (fmt_), \ | ||
93 | + ##__VA_ARGS__) | ||
94 | + | ||
95 | + | ||
96 | static inline int | ||
97 | pam_read_passwords(int fd, int npass, char **passwords) | ||
98 | { | ||
99 | -- | ||
100 | 2.49.0 | ||
101 | |||
diff --git a/meta/recipes-extended/pam/libpam/0002-pam-namespace-rebase.patch b/meta/recipes-extended/pam/libpam/0002-pam-namespace-rebase.patch new file mode 100644 index 0000000000..ff5a8a4946 --- /dev/null +++ b/meta/recipes-extended/pam/libpam/0002-pam-namespace-rebase.patch | |||
@@ -0,0 +1,750 @@ | |||
1 | From df1dab1a1a7900650ad4be157fea1a002048cc49 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-rebase | ||
5 | |||
6 | Refresh the pam-namespace. | ||
7 | |||
8 | Upstream-Status: Backport [https://github.com/linux-pam/linux-pam/commit/a8b4dce7b53d73de372e150028c970ee0a2a2e97] | ||
9 | Signed-off-by: Hitendra Prajapati <hprajapati@mvista.com> | ||
10 | --- | ||
11 | modules/pam_namespace/pam_namespace.c | 444 +++++++++++++------------- | ||
12 | modules/pam_namespace/pam_namespace.h | 7 +- | ||
13 | 2 files changed, 224 insertions(+), 227 deletions(-) | ||
14 | |||
15 | diff --git a/modules/pam_namespace/pam_namespace.c b/modules/pam_namespace/pam_namespace.c | ||
16 | index b026861..166bfce 100644 | ||
17 | --- a/modules/pam_namespace/pam_namespace.c | ||
18 | +++ b/modules/pam_namespace/pam_namespace.c | ||
19 | @@ -41,7 +41,7 @@ | ||
20 | #include "pam_namespace.h" | ||
21 | #include "argv_parse.h" | ||
22 | |||
23 | -/* --- evaluting all files in VENDORDIR/security/namespace.d and /etc/security/namespace.d --- */ | ||
24 | +/* --- evaluating all files in VENDORDIR/security/namespace.d and /etc/security/namespace.d --- */ | ||
25 | static const char *base_name(const char *path) | ||
26 | { | ||
27 | const char *base = strrchr(path, '/'); | ||
28 | @@ -55,6 +55,155 @@ compare_filename(const void *a, const void *b) | ||
29 | base_name(* (char * const *) b)); | ||
30 | } | ||
31 | |||
32 | +static void close_fds_pre_exec(struct instance_data *idata) | ||
33 | +{ | ||
34 | + if (pam_modutil_sanitize_helper_fds(idata->pamh, PAM_MODUTIL_IGNORE_FD, | ||
35 | + PAM_MODUTIL_IGNORE_FD, PAM_MODUTIL_IGNORE_FD) < 0) { | ||
36 | + _exit(1); | ||
37 | + } | ||
38 | +} | ||
39 | + | ||
40 | +static void | ||
41 | +strip_trailing_slashes(char *str) | ||
42 | +{ | ||
43 | + char *p = str + strlen(str); | ||
44 | + | ||
45 | + while (--p > str && *p == '/') | ||
46 | + *p = '\0'; | ||
47 | +} | ||
48 | + | ||
49 | +static int protect_mount(int dfd, const char *path, struct instance_data *idata) | ||
50 | +{ | ||
51 | + struct protect_dir_s *dir = idata->protect_dirs; | ||
52 | + char tmpbuf[64]; | ||
53 | + | ||
54 | + while (dir != NULL) { | ||
55 | + if (strcmp(path, dir->dir) == 0) { | ||
56 | + return 0; | ||
57 | + } | ||
58 | + dir = dir->next; | ||
59 | + } | ||
60 | + | ||
61 | + if (pam_sprintf(tmpbuf, "/proc/self/fd/%d", dfd) < 0) | ||
62 | + return -1; | ||
63 | + | ||
64 | + dir = calloc(1, sizeof(*dir)); | ||
65 | + | ||
66 | + if (dir == NULL) { | ||
67 | + return -1; | ||
68 | + } | ||
69 | + | ||
70 | + dir->dir = strdup(path); | ||
71 | + | ||
72 | + if (dir->dir == NULL) { | ||
73 | + free(dir); | ||
74 | + return -1; | ||
75 | + } | ||
76 | + | ||
77 | + if (idata->flags & PAMNS_DEBUG) { | ||
78 | + pam_syslog(idata->pamh, LOG_INFO, | ||
79 | + "Protect mount of %s over itself", path); | ||
80 | + } | ||
81 | + | ||
82 | + if (mount(tmpbuf, tmpbuf, NULL, MS_BIND, NULL) != 0) { | ||
83 | + int save_errno = errno; | ||
84 | + pam_syslog(idata->pamh, LOG_ERR, | ||
85 | + "Protect mount of %s failed: %m", tmpbuf); | ||
86 | + free(dir->dir); | ||
87 | + free(dir); | ||
88 | + errno = save_errno; | ||
89 | + return -1; | ||
90 | + } | ||
91 | + | ||
92 | + dir->next = idata->protect_dirs; | ||
93 | + idata->protect_dirs = dir; | ||
94 | + | ||
95 | + return 0; | ||
96 | +} | ||
97 | + | ||
98 | +static int protect_dir(const char *path, mode_t mode, int do_mkdir, | ||
99 | + struct instance_data *idata) | ||
100 | +{ | ||
101 | + char *p = strdup(path); | ||
102 | + char *d; | ||
103 | + char *dir = p; | ||
104 | + int dfd = AT_FDCWD; | ||
105 | + int dfd_next; | ||
106 | + int save_errno; | ||
107 | + int flags = O_RDONLY | O_DIRECTORY; | ||
108 | + int rv = -1; | ||
109 | + struct stat st; | ||
110 | + | ||
111 | + if (p == NULL) { | ||
112 | + return -1; | ||
113 | + } | ||
114 | + | ||
115 | + if (*dir == '/') { | ||
116 | + dfd = open("/", flags); | ||
117 | + if (dfd == -1) { | ||
118 | + goto error; | ||
119 | + } | ||
120 | + dir++; /* assume / is safe */ | ||
121 | + } | ||
122 | + | ||
123 | + while ((d=strchr(dir, '/')) != NULL) { | ||
124 | + *d = '\0'; | ||
125 | + dfd_next = openat(dfd, dir, flags); | ||
126 | + if (dfd_next == -1) { | ||
127 | + goto error; | ||
128 | + } | ||
129 | + | ||
130 | + if (dfd != AT_FDCWD) | ||
131 | + close(dfd); | ||
132 | + dfd = dfd_next; | ||
133 | + | ||
134 | + if (fstat(dfd, &st) != 0) { | ||
135 | + goto error; | ||
136 | + } | ||
137 | + | ||
138 | + if (flags & O_NOFOLLOW) { | ||
139 | + /* we are inside user-owned dir - protect */ | ||
140 | + if (protect_mount(dfd, p, idata) == -1) | ||
141 | + goto error; | ||
142 | + } else if (st.st_uid != 0 || st.st_gid != 0 || | ||
143 | + (st.st_mode & S_IWOTH)) { | ||
144 | + /* do not follow symlinks on subdirectories */ | ||
145 | + flags |= O_NOFOLLOW; | ||
146 | + } | ||
147 | + | ||
148 | + *d = '/'; | ||
149 | + dir = d + 1; | ||
150 | + } | ||
151 | + | ||
152 | + rv = openat(dfd, dir, flags); | ||
153 | + | ||
154 | + if (rv == -1) { | ||
155 | + if (!do_mkdir || mkdirat(dfd, dir, mode) != 0) { | ||
156 | + goto error; | ||
157 | + } | ||
158 | + rv = openat(dfd, dir, flags); | ||
159 | + } | ||
160 | + | ||
161 | + if (flags & O_NOFOLLOW) { | ||
162 | + /* we are inside user-owned dir - protect */ | ||
163 | + if (protect_mount(rv, p, idata) == -1) { | ||
164 | + save_errno = errno; | ||
165 | + close(rv); | ||
166 | + rv = -1; | ||
167 | + errno = save_errno; | ||
168 | + } | ||
169 | + } | ||
170 | + | ||
171 | +error: | ||
172 | + save_errno = errno; | ||
173 | + free(p); | ||
174 | + if (dfd != AT_FDCWD && dfd >= 0) | ||
175 | + close(dfd); | ||
176 | + errno = save_errno; | ||
177 | + | ||
178 | + return rv; | ||
179 | +} | ||
180 | + | ||
181 | /* Evaluating a list of files which have to be parsed in the right order: | ||
182 | * | ||
183 | * - If etc/security/namespace.d/@filename@.conf exists, then | ||
184 | @@ -129,6 +278,7 @@ static char **read_namespace_dir(struct instance_data *idata) | ||
185 | return file_list; | ||
186 | } | ||
187 | |||
188 | + | ||
189 | /* | ||
190 | * Adds an entry for a polyinstantiated directory to the linked list of | ||
191 | * polyinstantiated directories. It is called from process_line() while | ||
192 | @@ -198,7 +348,7 @@ static void cleanup_protect_data(pam_handle_t *pamh UNUSED , void *data, int err | ||
193 | unprotect_dirs(data); | ||
194 | } | ||
195 | |||
196 | -static char *expand_variables(const char *orig, const char *var_names[], const char *var_values[]) | ||
197 | +static char *expand_variables(const char *orig, const char *const var_names[], const char *var_values[]) | ||
198 | { | ||
199 | const char *src = orig; | ||
200 | char *dst; | ||
201 | @@ -209,7 +359,7 @@ static char *expand_variables(const char *orig, const char *var_names[], const c | ||
202 | if (*src == '$') { | ||
203 | int i; | ||
204 | for (i = 0; var_names[i]; i++) { | ||
205 | - int namelen = strlen(var_names[i]); | ||
206 | + size_t namelen = strlen(var_names[i]); | ||
207 | if (strncmp(var_names[i], src+1, namelen) == 0) { | ||
208 | dstlen += strlen(var_values[i]) - 1; /* $ */ | ||
209 | src += namelen; | ||
210 | @@ -227,7 +377,7 @@ static char *expand_variables(const char *orig, const char *var_names[], const c | ||
211 | if (c == '$') { | ||
212 | int i; | ||
213 | for (i = 0; var_names[i]; i++) { | ||
214 | - int namelen = strlen(var_names[i]); | ||
215 | + size_t namelen = strlen(var_names[i]); | ||
216 | if (strncmp(var_names[i], src+1, namelen) == 0) { | ||
217 | dst = stpcpy(dst, var_values[i]); | ||
218 | --dst; | ||
219 | @@ -311,8 +461,7 @@ static int parse_iscript_params(char *params, struct polydir_s *poly) | ||
220 | |||
221 | if (*params != '\0') { | ||
222 | if (*params != '/') { /* path is relative to NAMESPACE_D_DIR */ | ||
223 | - if (asprintf(&poly->init_script, "%s%s", NAMESPACE_D_DIR, params) == -1) | ||
224 | - return -1; | ||
225 | + poly->init_script = pam_asprintf("%s%s", NAMESPACE_D_DIR, params); | ||
226 | } else { | ||
227 | poly->init_script = strdup(params); | ||
228 | } | ||
229 | @@ -394,9 +543,9 @@ static int parse_method(char *method, struct polydir_s *poly, | ||
230 | { | ||
231 | enum polymethod pm; | ||
232 | char *sptr = NULL; | ||
233 | - static const char *method_names[] = { "user", "context", "level", "tmpdir", | ||
234 | + static const char *const method_names[] = { "user", "context", "level", "tmpdir", | ||
235 | "tmpfs", NULL }; | ||
236 | - static const char *flag_names[] = { "create", "noinit", "iscript", | ||
237 | + static const char *const flag_names[] = { "create", "noinit", "iscript", | ||
238 | "shared", "mntopts", NULL }; | ||
239 | static const unsigned int flag_values[] = { POLYDIR_CREATE, POLYDIR_NOINIT, | ||
240 | POLYDIR_ISCRIPT, POLYDIR_SHARED, POLYDIR_MNTOPTS }; | ||
241 | @@ -421,7 +570,7 @@ static int parse_method(char *method, struct polydir_s *poly, | ||
242 | |||
243 | while ((flag=strtok_r(NULL, ":", &sptr)) != NULL) { | ||
244 | for (i = 0; flag_names[i]; i++) { | ||
245 | - int namelen = strlen(flag_names[i]); | ||
246 | + size_t namelen = strlen(flag_names[i]); | ||
247 | |||
248 | if (strncmp(flag, flag_names[i], namelen) == 0) { | ||
249 | poly->flags |= flag_values[i]; | ||
250 | @@ -467,27 +616,27 @@ static int parse_method(char *method, struct polydir_s *poly, | ||
251 | * of the namespace configuration file. It skips over comments and incomplete | ||
252 | * or malformed lines. It processes a valid line with information on | ||
253 | * polyinstantiating a directory by populating appropriate fields of a | ||
254 | - * polyinstatiated directory structure and then calling add_polydir_entry to | ||
255 | + * polyinstantiated directory structure and then calling add_polydir_entry to | ||
256 | * add that entry to the linked list of polyinstantiated directories. | ||
257 | */ | ||
258 | static int process_line(char *line, const char *home, const char *rhome, | ||
259 | struct instance_data *idata) | ||
260 | { | ||
261 | char *dir = NULL, *instance_prefix = NULL, *rdir = NULL; | ||
262 | + const char *config_dir, *config_instance_prefix; | ||
263 | char *method, *uids; | ||
264 | char *tptr; | ||
265 | struct polydir_s *poly; | ||
266 | int retval = 0; | ||
267 | char **config_options = NULL; | ||
268 | - static const char *var_names[] = {"HOME", "USER", NULL}; | ||
269 | + static const char *const var_names[] = {"HOME", "USER", NULL}; | ||
270 | const char *var_values[] = {home, idata->user}; | ||
271 | const char *rvar_values[] = {rhome, idata->ruser}; | ||
272 | - int len; | ||
273 | |||
274 | /* | ||
275 | * skip the leading white space | ||
276 | */ | ||
277 | - while (*line && isspace(*line)) | ||
278 | + while (*line && isspace((unsigned char)*line)) | ||
279 | line++; | ||
280 | |||
281 | /* | ||
282 | @@ -523,22 +672,19 @@ static int process_line(char *line, const char *home, const char *rhome, | ||
283 | goto erralloc; | ||
284 | } | ||
285 | |||
286 | - dir = config_options[0]; | ||
287 | - if (dir == NULL) { | ||
288 | + config_dir = config_options[0]; | ||
289 | + if (config_dir == NULL) { | ||
290 | pam_syslog(idata->pamh, LOG_NOTICE, "Invalid line missing polydir"); | ||
291 | goto skipping; | ||
292 | } | ||
293 | - instance_prefix = config_options[1]; | ||
294 | - if (instance_prefix == NULL) { | ||
295 | + config_instance_prefix = config_options[1]; | ||
296 | + if (config_instance_prefix == NULL) { | ||
297 | pam_syslog(idata->pamh, LOG_NOTICE, "Invalid line missing instance_prefix"); | ||
298 | - instance_prefix = NULL; | ||
299 | goto skipping; | ||
300 | } | ||
301 | method = config_options[2]; | ||
302 | if (method == NULL) { | ||
303 | pam_syslog(idata->pamh, LOG_NOTICE, "Invalid line missing method"); | ||
304 | - instance_prefix = NULL; | ||
305 | - dir = NULL; | ||
306 | goto skipping; | ||
307 | } | ||
308 | |||
309 | @@ -553,19 +699,16 @@ static int process_line(char *line, const char *home, const char *rhome, | ||
310 | /* | ||
311 | * Expand $HOME and $USER in poly dir and instance dir prefix | ||
312 | */ | ||
313 | - if ((rdir=expand_variables(dir, var_names, rvar_values)) == NULL) { | ||
314 | - instance_prefix = NULL; | ||
315 | - dir = NULL; | ||
316 | + if ((rdir = expand_variables(config_dir, var_names, rvar_values)) == NULL) { | ||
317 | goto erralloc; | ||
318 | } | ||
319 | |||
320 | - if ((dir=expand_variables(dir, var_names, var_values)) == NULL) { | ||
321 | - instance_prefix = NULL; | ||
322 | + if ((dir = expand_variables(config_dir, var_names, var_values)) == NULL) { | ||
323 | goto erralloc; | ||
324 | } | ||
325 | |||
326 | - if ((instance_prefix=expand_variables(instance_prefix, var_names, var_values)) | ||
327 | - == NULL) { | ||
328 | + if ((instance_prefix = expand_variables(config_instance_prefix, | ||
329 | + var_names, var_values)) == NULL) { | ||
330 | goto erralloc; | ||
331 | } | ||
332 | |||
333 | @@ -575,15 +718,8 @@ static int process_line(char *line, const char *home, const char *rhome, | ||
334 | pam_syslog(idata->pamh, LOG_DEBUG, "Expanded instance prefix: '%s'", instance_prefix); | ||
335 | } | ||
336 | |||
337 | - len = strlen(dir); | ||
338 | - if (len > 0 && dir[len-1] == '/') { | ||
339 | - dir[len-1] = '\0'; | ||
340 | - } | ||
341 | - | ||
342 | - len = strlen(rdir); | ||
343 | - if (len > 0 && rdir[len-1] == '/') { | ||
344 | - rdir[len-1] = '\0'; | ||
345 | - } | ||
346 | + strip_trailing_slashes(dir); | ||
347 | + strip_trailing_slashes(rdir); | ||
348 | |||
349 | if (dir[0] == '\0' || rdir[0] == '\0') { | ||
350 | pam_syslog(idata->pamh, LOG_NOTICE, "Invalid polydir"); | ||
351 | @@ -594,26 +730,19 @@ static int process_line(char *line, const char *home, const char *rhome, | ||
352 | * Populate polyinstantiated directory structure with appropriate | ||
353 | * pathnames and the method with which to polyinstantiate. | ||
354 | */ | ||
355 | - if (strlen(dir) >= sizeof(poly->dir) | ||
356 | - || strlen(rdir) >= sizeof(poly->rdir) | ||
357 | - || strlen(instance_prefix) >= sizeof(poly->instance_prefix)) { | ||
358 | - pam_syslog(idata->pamh, LOG_NOTICE, "Pathnames too long"); | ||
359 | - goto skipping; | ||
360 | - } | ||
361 | - strcpy(poly->dir, dir); | ||
362 | - strcpy(poly->rdir, rdir); | ||
363 | - strcpy(poly->instance_prefix, instance_prefix); | ||
364 | - | ||
365 | if (parse_method(method, poly, idata) != 0) { | ||
366 | goto skipping; | ||
367 | } | ||
368 | |||
369 | - if (poly->method == TMPDIR) { | ||
370 | - if (sizeof(poly->instance_prefix) - strlen(poly->instance_prefix) < 7) { | ||
371 | - pam_syslog(idata->pamh, LOG_NOTICE, "Pathnames too long"); | ||
372 | - goto skipping; | ||
373 | - } | ||
374 | - strcat(poly->instance_prefix, "XXXXXX"); | ||
375 | +#define COPY_STR(dst, src, apd) \ | ||
376 | + pam_sprintf((dst), "%s%s", (src), (apd)) | ||
377 | + | ||
378 | + if (COPY_STR(poly->dir, dir, "") < 0 | ||
379 | + || COPY_STR(poly->rdir, rdir, "") < 0 | ||
380 | + || COPY_STR(poly->instance_prefix, instance_prefix, | ||
381 | + poly->method == TMPDIR ? "XXXXXX" : "") < 0) { | ||
382 | + pam_syslog(idata->pamh, LOG_NOTICE, "Pathnames too long"); | ||
383 | + goto skipping; | ||
384 | } | ||
385 | |||
386 | /* | ||
387 | @@ -637,7 +766,7 @@ static int process_line(char *line, const char *home, const char *rhome, | ||
388 | if (uids) { | ||
389 | uid_t *uidptr; | ||
390 | const char *ustr, *sstr; | ||
391 | - int count, i; | ||
392 | + size_t count, i; | ||
393 | |||
394 | if (*uids == '~') { | ||
395 | poly->flags |= POLYDIR_EXCLUSIVE; | ||
396 | @@ -646,8 +775,13 @@ static int process_line(char *line, const char *home, const char *rhome, | ||
397 | for (count = 0, ustr = sstr = uids; sstr; ustr = sstr + 1, count++) | ||
398 | sstr = strchr(ustr, ','); | ||
399 | |||
400 | + if (count > UINT_MAX || count > SIZE_MAX / sizeof(uid_t)) { | ||
401 | + pam_syslog(idata->pamh, LOG_ERR, "Too many uids encountered in configuration"); | ||
402 | + goto skipping; | ||
403 | + } | ||
404 | + | ||
405 | poly->num_uids = count; | ||
406 | - poly->uid = (uid_t *) malloc(count * sizeof (uid_t)); | ||
407 | + poly->uid = malloc(count * sizeof (uid_t)); | ||
408 | uidptr = poly->uid; | ||
409 | if (uidptr == NULL) { | ||
410 | goto erralloc; | ||
411 | @@ -996,6 +1130,7 @@ static int form_context(const struct polydir_s *polyptr, | ||
412 | return rc; | ||
413 | } | ||
414 | /* Should never get here */ | ||
415 | + freecon(scon); | ||
416 | return PAM_SUCCESS; | ||
417 | } | ||
418 | #endif | ||
419 | @@ -1057,10 +1192,8 @@ static int poly_name(const struct polydir_s *polyptr, char **i_name, | ||
420 | |||
421 | switch (pm) { | ||
422 | case USER: | ||
423 | - if (asprintf(i_name, "%s", idata->user) < 0) { | ||
424 | - *i_name = NULL; | ||
425 | + if ((*i_name = strdup(idata->user)) == NULL) | ||
426 | goto fail; | ||
427 | - } | ||
428 | break; | ||
429 | |||
430 | #ifdef WITH_SELINUX | ||
431 | @@ -1070,17 +1203,12 @@ static int poly_name(const struct polydir_s *polyptr, char **i_name, | ||
432 | pam_syslog(idata->pamh, LOG_ERR, "Error translating directory context"); | ||
433 | goto fail; | ||
434 | } | ||
435 | - if (polyptr->flags & POLYDIR_SHARED) { | ||
436 | - if (asprintf(i_name, "%s", rawcon) < 0) { | ||
437 | - *i_name = NULL; | ||
438 | - goto fail; | ||
439 | - } | ||
440 | - } else { | ||
441 | - if (asprintf(i_name, "%s_%s", rawcon, idata->user) < 0) { | ||
442 | - *i_name = NULL; | ||
443 | - goto fail; | ||
444 | - } | ||
445 | - } | ||
446 | + if (polyptr->flags & POLYDIR_SHARED) | ||
447 | + *i_name = strdup(rawcon); | ||
448 | + else | ||
449 | + *i_name = pam_asprintf("%s_%s", rawcon, idata->user); | ||
450 | + if (*i_name == NULL) | ||
451 | + goto fail; | ||
452 | break; | ||
453 | |||
454 | #endif /* WITH_SELINUX */ | ||
455 | @@ -1110,11 +1238,12 @@ static int poly_name(const struct polydir_s *polyptr, char **i_name, | ||
456 | *i_name = hash; | ||
457 | hash = NULL; | ||
458 | } else { | ||
459 | - char *newname; | ||
460 | - if (asprintf(&newname, "%.*s_%s", NAMESPACE_MAX_DIR_LEN-1-(int)strlen(hash), | ||
461 | - *i_name, hash) < 0) { | ||
462 | + char *newname = | ||
463 | + pam_asprintf("%.*s_%s", | ||
464 | + NAMESPACE_MAX_DIR_LEN - 1 - (int)strlen(hash), | ||
465 | + *i_name, hash); | ||
466 | + if (newname == NULL) | ||
467 | goto fail; | ||
468 | - } | ||
469 | free(*i_name); | ||
470 | *i_name = newname; | ||
471 | } | ||
472 | @@ -1139,137 +1268,6 @@ fail: | ||
473 | return rc; | ||
474 | } | ||
475 | |||
476 | -static int protect_mount(int dfd, const char *path, struct instance_data *idata) | ||
477 | -{ | ||
478 | - struct protect_dir_s *dir = idata->protect_dirs; | ||
479 | - char tmpbuf[64]; | ||
480 | - | ||
481 | - while (dir != NULL) { | ||
482 | - if (strcmp(path, dir->dir) == 0) { | ||
483 | - return 0; | ||
484 | - } | ||
485 | - dir = dir->next; | ||
486 | - } | ||
487 | - | ||
488 | - dir = calloc(1, sizeof(*dir)); | ||
489 | - | ||
490 | - if (dir == NULL) { | ||
491 | - return -1; | ||
492 | - } | ||
493 | - | ||
494 | - dir->dir = strdup(path); | ||
495 | - | ||
496 | - if (dir->dir == NULL) { | ||
497 | - free(dir); | ||
498 | - return -1; | ||
499 | - } | ||
500 | - | ||
501 | - snprintf(tmpbuf, sizeof(tmpbuf), "/proc/self/fd/%d", dfd); | ||
502 | - | ||
503 | - if (idata->flags & PAMNS_DEBUG) { | ||
504 | - pam_syslog(idata->pamh, LOG_INFO, | ||
505 | - "Protect mount of %s over itself", path); | ||
506 | - } | ||
507 | - | ||
508 | - if (mount(tmpbuf, tmpbuf, NULL, MS_BIND, NULL) != 0) { | ||
509 | - int save_errno = errno; | ||
510 | - pam_syslog(idata->pamh, LOG_ERR, | ||
511 | - "Protect mount of %s failed: %m", tmpbuf); | ||
512 | - free(dir->dir); | ||
513 | - free(dir); | ||
514 | - errno = save_errno; | ||
515 | - return -1; | ||
516 | - } | ||
517 | - | ||
518 | - dir->next = idata->protect_dirs; | ||
519 | - idata->protect_dirs = dir; | ||
520 | - | ||
521 | - return 0; | ||
522 | -} | ||
523 | - | ||
524 | -static int protect_dir(const char *path, mode_t mode, int do_mkdir, | ||
525 | - struct instance_data *idata) | ||
526 | -{ | ||
527 | - char *p = strdup(path); | ||
528 | - char *d; | ||
529 | - char *dir = p; | ||
530 | - int dfd = AT_FDCWD; | ||
531 | - int dfd_next; | ||
532 | - int save_errno; | ||
533 | - int flags = O_RDONLY | O_DIRECTORY; | ||
534 | - int rv = -1; | ||
535 | - struct stat st; | ||
536 | - | ||
537 | - if (p == NULL) { | ||
538 | - goto error; | ||
539 | - } | ||
540 | - | ||
541 | - if (*dir == '/') { | ||
542 | - dfd = open("/", flags); | ||
543 | - if (dfd == -1) { | ||
544 | - goto error; | ||
545 | - } | ||
546 | - dir++; /* assume / is safe */ | ||
547 | - } | ||
548 | - | ||
549 | - while ((d=strchr(dir, '/')) != NULL) { | ||
550 | - *d = '\0'; | ||
551 | - dfd_next = openat(dfd, dir, flags); | ||
552 | - if (dfd_next == -1) { | ||
553 | - goto error; | ||
554 | - } | ||
555 | - | ||
556 | - if (dfd != AT_FDCWD) | ||
557 | - close(dfd); | ||
558 | - dfd = dfd_next; | ||
559 | - | ||
560 | - if (fstat(dfd, &st) != 0) { | ||
561 | - goto error; | ||
562 | - } | ||
563 | - | ||
564 | - if (flags & O_NOFOLLOW) { | ||
565 | - /* we are inside user-owned dir - protect */ | ||
566 | - if (protect_mount(dfd, p, idata) == -1) | ||
567 | - goto error; | ||
568 | - } else if (st.st_uid != 0 || st.st_gid != 0 || | ||
569 | - (st.st_mode & S_IWOTH)) { | ||
570 | - /* do not follow symlinks on subdirectories */ | ||
571 | - flags |= O_NOFOLLOW; | ||
572 | - } | ||
573 | - | ||
574 | - *d = '/'; | ||
575 | - dir = d + 1; | ||
576 | - } | ||
577 | - | ||
578 | - rv = openat(dfd, dir, flags); | ||
579 | - | ||
580 | - if (rv == -1) { | ||
581 | - if (!do_mkdir || mkdirat(dfd, dir, mode) != 0) { | ||
582 | - goto error; | ||
583 | - } | ||
584 | - rv = openat(dfd, dir, flags); | ||
585 | - } | ||
586 | - | ||
587 | - if (flags & O_NOFOLLOW) { | ||
588 | - /* we are inside user-owned dir - protect */ | ||
589 | - if (protect_mount(rv, p, idata) == -1) { | ||
590 | - save_errno = errno; | ||
591 | - close(rv); | ||
592 | - rv = -1; | ||
593 | - errno = save_errno; | ||
594 | - } | ||
595 | - } | ||
596 | - | ||
597 | -error: | ||
598 | - save_errno = errno; | ||
599 | - free(p); | ||
600 | - if (dfd != AT_FDCWD && dfd >= 0) | ||
601 | - close(dfd); | ||
602 | - errno = save_errno; | ||
603 | - | ||
604 | - return rv; | ||
605 | -} | ||
606 | - | ||
607 | static int check_inst_parent(char *ipath, struct instance_data *idata) | ||
608 | { | ||
609 | struct stat instpbuf; | ||
610 | @@ -1281,13 +1279,12 @@ static int check_inst_parent(char *ipath, struct instance_data *idata) | ||
611 | * admin explicitly instructs to ignore the instance parent | ||
612 | * mode by the "ignore_instance_parent_mode" argument). | ||
613 | */ | ||
614 | - inst_parent = (char *) malloc(strlen(ipath)+1); | ||
615 | + inst_parent = strdup(ipath); | ||
616 | if (!inst_parent) { | ||
617 | pam_syslog(idata->pamh, LOG_CRIT, "Error allocating pathname string"); | ||
618 | return PAM_SESSION_ERR; | ||
619 | } | ||
620 | |||
621 | - strcpy(inst_parent, ipath); | ||
622 | trailing_slash = strrchr(inst_parent, '/'); | ||
623 | if (trailing_slash) | ||
624 | *trailing_slash = '\0'; | ||
625 | @@ -1371,9 +1368,10 @@ static int inst_init(const struct polydir_s *polyptr, const char *ipath, | ||
626 | if (setuid(geteuid()) < 0) { | ||
627 | /* ignore failures, they don't matter */ | ||
628 | } | ||
629 | + close_fds_pre_exec(idata); | ||
630 | |||
631 | - if (execle(init_script, init_script, | ||
632 | - polyptr->dir, ipath, newdir?"1":"0", idata->user, NULL, envp) < 0) | ||
633 | + execle(init_script, init_script, | ||
634 | + polyptr->dir, ipath, newdir?"1":"0", idata->user, NULL, envp); | ||
635 | _exit(1); | ||
636 | } else if (pid > 0) { | ||
637 | while (((rc = waitpid(pid, &status, 0)) == (pid_t)-1) && | ||
638 | @@ -1424,7 +1422,9 @@ static int create_polydir(struct polydir_s *polyptr, | ||
639 | |||
640 | #ifdef WITH_SELINUX | ||
641 | if (idata->flags & PAMNS_SELINUX_ENABLED) { | ||
642 | - getfscreatecon_raw(&oldcon_raw); | ||
643 | + if (getfscreatecon_raw(&oldcon_raw) != 0) | ||
644 | + pam_syslog(idata->pamh, LOG_NOTICE, | ||
645 | + "Error retrieving fs create context: %m"); | ||
646 | |||
647 | label_handle = selabel_open(SELABEL_CTX_FILE, NULL, 0); | ||
648 | if (!label_handle) { | ||
649 | @@ -1453,6 +1453,9 @@ static int create_polydir(struct polydir_s *polyptr, | ||
650 | if (rc == -1) { | ||
651 | pam_syslog(idata->pamh, LOG_ERR, | ||
652 | "Error creating directory %s: %m", dir); | ||
653 | +#ifdef WITH_SELINUX | ||
654 | + freecon(oldcon_raw); | ||
655 | +#endif | ||
656 | return PAM_SESSION_ERR; | ||
657 | } | ||
658 | |||
659 | @@ -1640,16 +1643,14 @@ static int ns_setup(struct polydir_s *polyptr, | ||
660 | |||
661 | retval = protect_dir(polyptr->dir, 0, 0, idata); | ||
662 | |||
663 | - if (retval < 0 && errno != ENOENT) { | ||
664 | - pam_syslog(idata->pamh, LOG_ERR, "Polydir %s access error: %m", | ||
665 | - polyptr->dir); | ||
666 | - return PAM_SESSION_ERR; | ||
667 | - } | ||
668 | - | ||
669 | if (retval < 0) { | ||
670 | - if ((polyptr->flags & POLYDIR_CREATE) && | ||
671 | - create_polydir(polyptr, idata) != PAM_SUCCESS) | ||
672 | - return PAM_SESSION_ERR; | ||
673 | + if (errno != ENOENT || !(polyptr->flags & POLYDIR_CREATE)) { | ||
674 | + pam_syslog(idata->pamh, LOG_ERR, "Polydir %s access error: %m", | ||
675 | + polyptr->dir); | ||
676 | + return PAM_SESSION_ERR; | ||
677 | + } | ||
678 | + if (create_polydir(polyptr, idata) != PAM_SUCCESS) | ||
679 | + return PAM_SESSION_ERR; | ||
680 | } else { | ||
681 | close(retval); | ||
682 | } | ||
683 | @@ -1698,7 +1699,7 @@ static int ns_setup(struct polydir_s *polyptr, | ||
684 | #endif | ||
685 | } | ||
686 | |||
687 | - if (asprintf(&inst_dir, "%s%s", polyptr->instance_prefix, instname) < 0) | ||
688 | + if ((inst_dir = pam_asprintf("%s%s", polyptr->instance_prefix, instname)) == NULL) | ||
689 | goto error_out; | ||
690 | |||
691 | if (idata->flags & PAMNS_DEBUG) | ||
692 | @@ -1810,8 +1811,9 @@ static int cleanup_tmpdirs(struct instance_data *idata) | ||
693 | _exit(1); | ||
694 | } | ||
695 | #endif | ||
696 | - if (execle("/bin/rm", "/bin/rm", "-rf", pptr->instance_prefix, NULL, envp) < 0) | ||
697 | - _exit(1); | ||
698 | + close_fds_pre_exec(idata); | ||
699 | + execle("/bin/rm", "/bin/rm", "-rf", pptr->instance_prefix, NULL, envp); | ||
700 | + _exit(1); | ||
701 | } else if (pid > 0) { | ||
702 | while (((rc = waitpid(pid, &status, 0)) == (pid_t)-1) && | ||
703 | (errno == EINTR)); | ||
704 | @@ -1826,7 +1828,7 @@ static int cleanup_tmpdirs(struct instance_data *idata) | ||
705 | } | ||
706 | } else if (pid < 0) { | ||
707 | pam_syslog(idata->pamh, LOG_ERR, | ||
708 | - "Cannot fork to run namespace init script, %m"); | ||
709 | + "Cannot fork to cleanup temporary directory, %m"); | ||
710 | rc = PAM_SESSION_ERR; | ||
711 | goto out; | ||
712 | } | ||
713 | diff --git a/modules/pam_namespace/pam_namespace.h b/modules/pam_namespace/pam_namespace.h | ||
714 | index a991b4c..180e042 100644 | ||
715 | --- a/modules/pam_namespace/pam_namespace.h | ||
716 | +++ b/modules/pam_namespace/pam_namespace.h | ||
717 | @@ -44,21 +44,16 @@ | ||
718 | #include <stdlib.h> | ||
719 | #include <errno.h> | ||
720 | #include <syslog.h> | ||
721 | -#include <dlfcn.h> | ||
722 | -#include <stdarg.h> | ||
723 | #include <pwd.h> | ||
724 | #include <grp.h> | ||
725 | #include <limits.h> | ||
726 | #include <sys/types.h> | ||
727 | #include <sys/stat.h> | ||
728 | -#include <sys/resource.h> | ||
729 | #include <sys/mount.h> | ||
730 | #include <sys/wait.h> | ||
731 | -#include <libgen.h> | ||
732 | #include <fcntl.h> | ||
733 | #include <sched.h> | ||
734 | #include <glob.h> | ||
735 | -#include <locale.h> | ||
736 | #include "security/pam_modules.h" | ||
737 | #include "security/pam_modutil.h" | ||
738 | #include "security/pam_ext.h" | ||
739 | @@ -114,7 +109,7 @@ | ||
740 | #define PAMNS_MOUNT_PRIVATE 0x00080000 /* Make the polydir mounts private */ | ||
741 | |||
742 | /* polydir flags */ | ||
743 | -#define POLYDIR_EXCLUSIVE 0x00000001 /* polyinstatiate exclusively for override uids */ | ||
744 | +#define POLYDIR_EXCLUSIVE 0x00000001 /* polyinstantiate exclusively for override uids */ | ||
745 | #define POLYDIR_CREATE 0x00000002 /* create the polydir */ | ||
746 | #define POLYDIR_NOINIT 0x00000004 /* no init script */ | ||
747 | #define POLYDIR_SHARED 0x00000008 /* share context/level instances among users */ | ||
748 | -- | ||
749 | 2.49.0 | ||
750 | |||
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..ff0331aa38 --- /dev/null +++ b/meta/recipes-extended/pam/libpam/CVE-2025-6020-01.patch | |||
@@ -0,0 +1,1128 @@ | |||
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 | 637 ++++++++++++++++++-------- | ||
28 | modules/pam_namespace/pam_namespace.h | 10 + | ||
29 | 2 files changed, 457 insertions(+), 190 deletions(-) | ||
30 | |||
31 | diff --git a/modules/pam_namespace/pam_namespace.c b/modules/pam_namespace/pam_namespace.c | ||
32 | index 166bfce..9d993d4 100644 | ||
33 | --- a/modules/pam_namespace/pam_namespace.c | ||
34 | +++ b/modules/pam_namespace/pam_namespace.c | ||
35 | @@ -41,6 +41,8 @@ | ||
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 | @@ -75,7 +77,7 @@ strip_trailing_slashes(char *str) | ||
45 | static int protect_mount(int dfd, const char *path, struct instance_data *idata) | ||
46 | { | ||
47 | struct protect_dir_s *dir = idata->protect_dirs; | ||
48 | - char tmpbuf[64]; | ||
49 | + char tmpbuf[MAGIC_LNK_FD_SIZE]; | ||
50 | |||
51 | while (dir != NULL) { | ||
52 | if (strcmp(path, dir->dir) == 0) { | ||
53 | @@ -121,56 +123,107 @@ static int protect_mount(int dfd, const char *path, struct instance_data *idata) | ||
54 | return 0; | ||
55 | } | ||
56 | |||
57 | -static int protect_dir(const char *path, mode_t mode, int do_mkdir, | ||
58 | +/* | ||
59 | + * Returns a fd to the given absolute path, acquired securely. This means: | ||
60 | + * - iterating on each segment of the path, | ||
61 | + * - not following user symlinks, | ||
62 | + * - using race-free operations. | ||
63 | + * | ||
64 | + * Takes a bit mask to specify the operation mode: | ||
65 | + * - SECURE_OPENDIR_PROTECT: call protect_mount() on each unsafe segment of path | ||
66 | + * - SECURE_OPENDIR_MKDIR: create last segment of path if does not exist | ||
67 | + * - SECURE_OPENDIR_FULL_FD: open the directory with O_RDONLY instead of O_PATH, | ||
68 | + * allowing more operations to be done with the returned fd | ||
69 | + * | ||
70 | + * Be aware that using SECURE_OPENDIR_PROTECT: | ||
71 | + * - will modify some external state (global structure...) and should not be | ||
72 | + * called in cleanup code paths. See wrapper secure_opendir_stateless() | ||
73 | + * - need a non-NULL idata to call protect_mount() | ||
74 | + */ | ||
75 | +static int secure_opendir(const char *path, int opm, mode_t mode, | ||
76 | struct instance_data *idata) | ||
77 | { | ||
78 | - char *p = strdup(path); | ||
79 | + char *p; | ||
80 | char *d; | ||
81 | - char *dir = p; | ||
82 | - int dfd = AT_FDCWD; | ||
83 | + char *dir; | ||
84 | + int dfd = -1; | ||
85 | int dfd_next; | ||
86 | int save_errno; | ||
87 | - int flags = O_RDONLY | O_DIRECTORY; | ||
88 | + int flags = O_DIRECTORY | O_CLOEXEC; | ||
89 | int rv = -1; | ||
90 | struct stat st; | ||
91 | |||
92 | - if (p == NULL) { | ||
93 | + if (opm & SECURE_OPENDIR_FULL_FD) | ||
94 | + flags |= O_RDONLY; | ||
95 | + else | ||
96 | + flags |= O_PATH; | ||
97 | + | ||
98 | + /* Check for args consistency */ | ||
99 | + if ((opm & SECURE_OPENDIR_PROTECT) && idata == NULL) | ||
100 | return -1; | ||
101 | - } | ||
102 | |||
103 | - if (*dir == '/') { | ||
104 | - dfd = open("/", flags); | ||
105 | - if (dfd == -1) { | ||
106 | - goto error; | ||
107 | - } | ||
108 | - dir++; /* assume / is safe */ | ||
109 | + /* Accept only absolute paths */ | ||
110 | + if (*path != '/') | ||
111 | + return -1; | ||
112 | + | ||
113 | + dir = p = strdup(path); | ||
114 | + if (p == NULL) | ||
115 | + return -1; | ||
116 | + | ||
117 | + /* Assume '/' is safe */ | ||
118 | + dfd = open("/", flags); | ||
119 | + if (dfd == -1) | ||
120 | + goto error; | ||
121 | + | ||
122 | + /* Needed to not loop too far and call openat() on NULL */ | ||
123 | + strip_trailing_slashes(p); | ||
124 | + | ||
125 | + dir++; | ||
126 | + | ||
127 | + /* In case path is '/' */ | ||
128 | + if (*dir == '\0') { | ||
129 | + free(p); | ||
130 | + return dfd; | ||
131 | } | ||
132 | |||
133 | while ((d=strchr(dir, '/')) != NULL) { | ||
134 | *d = '\0'; | ||
135 | + | ||
136 | dfd_next = openat(dfd, dir, flags); | ||
137 | - if (dfd_next == -1) { | ||
138 | + if (dfd_next == -1) | ||
139 | goto error; | ||
140 | - } | ||
141 | - | ||
142 | - if (dfd != AT_FDCWD) | ||
143 | - close(dfd); | ||
144 | - dfd = dfd_next; | ||
145 | |||
146 | - if (fstat(dfd, &st) != 0) { | ||
147 | + if (fstat(dfd_next, &st) != 0) { | ||
148 | + close(dfd_next); | ||
149 | goto error; | ||
150 | } | ||
151 | |||
152 | - if (flags & O_NOFOLLOW) { | ||
153 | + if ((flags & O_NOFOLLOW) && (opm & SECURE_OPENDIR_PROTECT)) { | ||
154 | /* we are inside user-owned dir - protect */ | ||
155 | - if (protect_mount(dfd, p, idata) == -1) | ||
156 | + if (protect_mount(dfd_next, p, idata) == -1) { | ||
157 | + close(dfd_next); | ||
158 | + goto error; | ||
159 | + } | ||
160 | + /* | ||
161 | + * Reopen the directory to obtain a new descriptor | ||
162 | + * after protect_mount(), this is necessary in cases | ||
163 | + * when another directory is going to be mounted over | ||
164 | + * the given path. | ||
165 | + */ | ||
166 | + close(dfd_next); | ||
167 | + dfd_next = openat(dfd, dir, flags); | ||
168 | + if (dfd_next == -1) | ||
169 | goto error; | ||
170 | - } else if (st.st_uid != 0 || st.st_gid != 0 || | ||
171 | - (st.st_mode & S_IWOTH)) { | ||
172 | + } else if (st.st_uid != 0 | ||
173 | + || (st.st_gid != 0 && (st.st_mode & S_IWGRP)) | ||
174 | + || (st.st_mode & S_IWOTH)) { | ||
175 | /* do not follow symlinks on subdirectories */ | ||
176 | flags |= O_NOFOLLOW; | ||
177 | } | ||
178 | |||
179 | + close(dfd); | ||
180 | + dfd = dfd_next; | ||
181 | + | ||
182 | *d = '/'; | ||
183 | dir = d + 1; | ||
184 | } | ||
185 | @@ -178,13 +231,14 @@ static int protect_dir(const char *path, mode_t mode, int do_mkdir, | ||
186 | rv = openat(dfd, dir, flags); | ||
187 | |||
188 | if (rv == -1) { | ||
189 | - if (!do_mkdir || mkdirat(dfd, dir, mode) != 0) { | ||
190 | + if ((opm & SECURE_OPENDIR_MKDIR) && mkdirat(dfd, dir, mode) == 0) | ||
191 | + rv = openat(dfd, dir, flags); | ||
192 | + | ||
193 | + if (rv == -1) | ||
194 | goto error; | ||
195 | - } | ||
196 | - rv = openat(dfd, dir, flags); | ||
197 | } | ||
198 | |||
199 | - if (flags & O_NOFOLLOW) { | ||
200 | + if ((flags & O_NOFOLLOW) && (opm & SECURE_OPENDIR_PROTECT)) { | ||
201 | /* we are inside user-owned dir - protect */ | ||
202 | if (protect_mount(rv, p, idata) == -1) { | ||
203 | save_errno = errno; | ||
204 | @@ -192,18 +246,95 @@ static int protect_dir(const char *path, mode_t mode, int do_mkdir, | ||
205 | rv = -1; | ||
206 | errno = save_errno; | ||
207 | } | ||
208 | + /* | ||
209 | + * Reopen the directory to obtain a new descriptor after | ||
210 | + * protect_mount(), this is necessary in cases when another | ||
211 | + * directory is going to be mounted over the given path. | ||
212 | + */ | ||
213 | + close(rv); | ||
214 | + rv = openat(dfd, dir, flags); | ||
215 | } | ||
216 | |||
217 | error: | ||
218 | save_errno = errno; | ||
219 | free(p); | ||
220 | - if (dfd != AT_FDCWD && dfd >= 0) | ||
221 | + if (dfd >= 0) | ||
222 | close(dfd); | ||
223 | errno = save_errno; | ||
224 | |||
225 | return rv; | ||
226 | } | ||
227 | |||
228 | +/* | ||
229 | + * Returns a fd to the given path, acquired securely. | ||
230 | + * It can be called in all situations, including in cleanup code paths, as | ||
231 | + * it does not modify external state (no access to global structures...). | ||
232 | + */ | ||
233 | +static int secure_opendir_stateless(const char *path) | ||
234 | +{ | ||
235 | + return secure_opendir(path, 0, 0, NULL); | ||
236 | +} | ||
237 | + | ||
238 | +/* | ||
239 | + * Umount securely the given path, even if the directories along | ||
240 | + * the path are under user control. It should protect against | ||
241 | + * symlinks attacks and race conditions. | ||
242 | + */ | ||
243 | +static int secure_umount(const char *path) | ||
244 | +{ | ||
245 | + int save_errno; | ||
246 | + int rv = -1; | ||
247 | + int dfd = -1; | ||
248 | + char s_path[MAGIC_LNK_FD_SIZE]; | ||
249 | + | ||
250 | + dfd = secure_opendir_stateless(path); | ||
251 | + if (dfd == -1) | ||
252 | + return rv; | ||
253 | + | ||
254 | + if (pam_sprintf(s_path, "/proc/self/fd/%d", dfd) < 0) | ||
255 | + goto error; | ||
256 | + | ||
257 | + /* | ||
258 | + * We still have a fd open to path itself, | ||
259 | + * so we need to do a lazy umount. | ||
260 | + */ | ||
261 | + rv = umount2(s_path, MNT_DETACH); | ||
262 | + | ||
263 | +error: | ||
264 | + save_errno = errno; | ||
265 | + close(dfd); | ||
266 | + errno = save_errno; | ||
267 | + return rv; | ||
268 | +} | ||
269 | + | ||
270 | +/* | ||
271 | + * Rmdir the given path securely, protecting against symlinks attacks | ||
272 | + * and race conditions. | ||
273 | + * This function is currently called only in cleanup code paths where | ||
274 | + * any errors returned are not handled, so do not handle them either. | ||
275 | + * Basically, try to rmdir the path on a best-effort basis. | ||
276 | + */ | ||
277 | +static void secure_try_rmdir(const char *path) | ||
278 | +{ | ||
279 | + int dfd; | ||
280 | + char *buf; | ||
281 | + char *parent; | ||
282 | + | ||
283 | + buf = strdup(path); | ||
284 | + if (buf == NULL) | ||
285 | + return; | ||
286 | + | ||
287 | + parent = dirname(buf); | ||
288 | + | ||
289 | + dfd = secure_opendir_stateless(parent); | ||
290 | + if (dfd >= 0) { | ||
291 | + unlinkat(dfd, base_name(path), AT_REMOVEDIR); | ||
292 | + close(dfd); | ||
293 | + } | ||
294 | + | ||
295 | + free(buf); | ||
296 | +} | ||
297 | + | ||
298 | /* Evaluating a list of files which have to be parsed in the right order: | ||
299 | * | ||
300 | * - If etc/security/namespace.d/@filename@.conf exists, then | ||
301 | @@ -330,7 +461,7 @@ static void unprotect_dirs(struct protect_dir_s *dir) | ||
302 | struct protect_dir_s *next; | ||
303 | |||
304 | while (dir != NULL) { | ||
305 | - umount(dir->dir); | ||
306 | + secure_umount(dir->dir); | ||
307 | free(dir->dir); | ||
308 | next = dir->next; | ||
309 | free(dir); | ||
310 | @@ -734,13 +865,9 @@ static int process_line(char *line, const char *home, const char *rhome, | ||
311 | goto skipping; | ||
312 | } | ||
313 | |||
314 | -#define COPY_STR(dst, src, apd) \ | ||
315 | - pam_sprintf((dst), "%s%s", (src), (apd)) | ||
316 | - | ||
317 | - if (COPY_STR(poly->dir, dir, "") < 0 | ||
318 | - || COPY_STR(poly->rdir, rdir, "") < 0 | ||
319 | - || COPY_STR(poly->instance_prefix, instance_prefix, | ||
320 | - poly->method == TMPDIR ? "XXXXXX" : "") < 0) { | ||
321 | + if (pam_sprintf(poly->dir, "%s", dir) < 0 | ||
322 | + || pam_sprintf(poly->rdir, "%s", rdir) < 0 | ||
323 | + || pam_sprintf(poly->instance_prefix, "%s", instance_prefix) < 0) { | ||
324 | pam_syslog(idata->pamh, LOG_NOTICE, "Pathnames too long"); | ||
325 | goto skipping; | ||
326 | } | ||
327 | @@ -1023,6 +1150,23 @@ static char *md5hash(const char *instname, struct instance_data *idata) | ||
328 | } | ||
329 | |||
330 | #ifdef WITH_SELINUX | ||
331 | +static char *secure_getfilecon(pam_handle_t *pamh, const char *dir) | ||
332 | +{ | ||
333 | + char *ctx = NULL; | ||
334 | + int dfd = secure_opendir(dir, SECURE_OPENDIR_FULL_FD, 0, NULL); | ||
335 | + if (dfd < 0) { | ||
336 | + pam_syslog(pamh, LOG_ERR, "Error getting fd to %s: %m", dir); | ||
337 | + return NULL; | ||
338 | + } | ||
339 | + if (fgetfilecon(dfd, &ctx) < 0) | ||
340 | + ctx = NULL; | ||
341 | + if (ctx == NULL) | ||
342 | + pam_syslog(pamh, LOG_ERR, | ||
343 | + "Error getting poly dir context for %s: %m", dir); | ||
344 | + close(dfd); | ||
345 | + return ctx; | ||
346 | +} | ||
347 | + | ||
348 | static int form_context(const struct polydir_s *polyptr, | ||
349 | char **i_context, char **origcon, | ||
350 | struct instance_data *idata) | ||
351 | @@ -1034,12 +1178,9 @@ static int form_context(const struct polydir_s *polyptr, | ||
352 | /* | ||
353 | * Get the security context of the directory to polyinstantiate. | ||
354 | */ | ||
355 | - rc = getfilecon(polyptr->dir, origcon); | ||
356 | - if (rc < 0 || *origcon == NULL) { | ||
357 | - pam_syslog(idata->pamh, LOG_ERR, | ||
358 | - "Error getting poly dir context, %m"); | ||
359 | + *origcon = secure_getfilecon(idata->pamh, polyptr->dir); | ||
360 | + if (*origcon == NULL) | ||
361 | return PAM_SESSION_ERR; | ||
362 | - } | ||
363 | |||
364 | if (polyptr->method == USER) return PAM_SUCCESS; | ||
365 | |||
366 | @@ -1136,29 +1277,52 @@ static int form_context(const struct polydir_s *polyptr, | ||
367 | #endif | ||
368 | |||
369 | /* | ||
370 | - * poly_name returns the name of the polyinstantiated instance directory | ||
371 | + * From the instance differentiation string, set in the polyptr structure: | ||
372 | + * - the absolute path to the instance dir, | ||
373 | + * - the absolute path to the previous dir (parent), | ||
374 | + * - the instance name (may be different than the instance differentiation string) | ||
375 | + */ | ||
376 | +static int set_polydir_paths(struct polydir_s *polyptr, const char *inst_differentiation) | ||
377 | +{ | ||
378 | + char *tmp; | ||
379 | + | ||
380 | + if (pam_sprintf(polyptr->instance_absolute, "%s%s", | ||
381 | + polyptr->instance_prefix, inst_differentiation) < 0) | ||
382 | + return -1; | ||
383 | + | ||
384 | + polyptr->instname = strrchr(polyptr->instance_absolute, '/') + 1; | ||
385 | + | ||
386 | + if (pam_sprintf(polyptr->instance_parent, "%s", polyptr->instance_absolute) < 0) | ||
387 | + return -1; | ||
388 | + | ||
389 | + tmp = strrchr(polyptr->instance_parent, '/') + 1; | ||
390 | + *tmp = '\0'; | ||
391 | + | ||
392 | + return 0; | ||
393 | +} | ||
394 | + | ||
395 | +/* | ||
396 | + * Set the name of the polyinstantiated instance directory | ||
397 | * based on the method used for polyinstantiation (user, context or level) | ||
398 | * In addition, the function also returns the security contexts of the | ||
399 | * original directory to polyinstantiate and the polyinstantiated instance | ||
400 | * directory. | ||
401 | */ | ||
402 | #ifdef WITH_SELINUX | ||
403 | -static int poly_name(const struct polydir_s *polyptr, char **i_name, | ||
404 | - char **i_context, char **origcon, | ||
405 | - struct instance_data *idata) | ||
406 | +static int poly_name(struct polydir_s *polyptr, char **i_context, | ||
407 | + char **origcon, struct instance_data *idata) | ||
408 | #else | ||
409 | -static int poly_name(const struct polydir_s *polyptr, char **i_name, | ||
410 | - struct instance_data *idata) | ||
411 | +static int poly_name(struct polydir_s *polyptr, struct instance_data *idata) | ||
412 | #endif | ||
413 | { | ||
414 | int rc; | ||
415 | + char *inst_differentiation = NULL; | ||
416 | char *hash = NULL; | ||
417 | enum polymethod pm; | ||
418 | #ifdef WITH_SELINUX | ||
419 | char *rawcon = NULL; | ||
420 | #endif | ||
421 | |||
422 | - *i_name = NULL; | ||
423 | #ifdef WITH_SELINUX | ||
424 | *i_context = NULL; | ||
425 | *origcon = NULL; | ||
426 | @@ -1192,7 +1356,7 @@ static int poly_name(const struct polydir_s *polyptr, char **i_name, | ||
427 | |||
428 | switch (pm) { | ||
429 | case USER: | ||
430 | - if ((*i_name = strdup(idata->user)) == NULL) | ||
431 | + if ((inst_differentiation = strdup(idata->user)) == NULL) | ||
432 | goto fail; | ||
433 | break; | ||
434 | |||
435 | @@ -1204,20 +1368,24 @@ static int poly_name(const struct polydir_s *polyptr, char **i_name, | ||
436 | goto fail; | ||
437 | } | ||
438 | if (polyptr->flags & POLYDIR_SHARED) | ||
439 | - *i_name = strdup(rawcon); | ||
440 | + inst_differentiation = strdup(rawcon); | ||
441 | else | ||
442 | - *i_name = pam_asprintf("%s_%s", rawcon, idata->user); | ||
443 | - if (*i_name == NULL) | ||
444 | + inst_differentiation = pam_asprintf("%s_%s", rawcon, idata->user); | ||
445 | + if (inst_differentiation == NULL) | ||
446 | goto fail; | ||
447 | break; | ||
448 | |||
449 | #endif /* WITH_SELINUX */ | ||
450 | |||
451 | case TMPDIR: | ||
452 | + if ((inst_differentiation = strdup("XXXXXX")) == NULL) | ||
453 | + goto fail; | ||
454 | + goto success; | ||
455 | + | ||
456 | case TMPFS: | ||
457 | - if ((*i_name=strdup("")) == NULL) | ||
458 | + if ((inst_differentiation=strdup("")) == NULL) | ||
459 | goto fail; | ||
460 | - return PAM_SUCCESS; | ||
461 | + goto success; | ||
462 | |||
463 | default: | ||
464 | if (idata->flags & PAMNS_DEBUG) | ||
465 | @@ -1226,32 +1394,37 @@ static int poly_name(const struct polydir_s *polyptr, char **i_name, | ||
466 | } | ||
467 | |||
468 | if (idata->flags & PAMNS_DEBUG) | ||
469 | - pam_syslog(idata->pamh, LOG_DEBUG, "poly_name %s", *i_name); | ||
470 | + pam_syslog(idata->pamh, LOG_DEBUG, "poly_name %s", inst_differentiation); | ||
471 | |||
472 | - if ((idata->flags & PAMNS_GEN_HASH) || strlen(*i_name) > NAMESPACE_MAX_DIR_LEN) { | ||
473 | - hash = md5hash(*i_name, idata); | ||
474 | + if ((idata->flags & PAMNS_GEN_HASH) || strlen(inst_differentiation) > NAMESPACE_MAX_DIR_LEN) { | ||
475 | + hash = md5hash(inst_differentiation, idata); | ||
476 | if (hash == NULL) { | ||
477 | goto fail; | ||
478 | } | ||
479 | if (idata->flags & PAMNS_GEN_HASH) { | ||
480 | - free(*i_name); | ||
481 | - *i_name = hash; | ||
482 | + free(inst_differentiation); | ||
483 | + inst_differentiation = hash; | ||
484 | hash = NULL; | ||
485 | } else { | ||
486 | char *newname = | ||
487 | pam_asprintf("%.*s_%s", | ||
488 | NAMESPACE_MAX_DIR_LEN - 1 - (int)strlen(hash), | ||
489 | - *i_name, hash); | ||
490 | + inst_differentiation, hash); | ||
491 | if (newname == NULL) | ||
492 | goto fail; | ||
493 | - free(*i_name); | ||
494 | - *i_name = newname; | ||
495 | + free(inst_differentiation); | ||
496 | + inst_differentiation = newname; | ||
497 | } | ||
498 | } | ||
499 | - rc = PAM_SUCCESS; | ||
500 | |||
501 | +success: | ||
502 | + if (set_polydir_paths(polyptr, inst_differentiation) == -1) | ||
503 | + goto fail; | ||
504 | + | ||
505 | + rc = PAM_SUCCESS; | ||
506 | fail: | ||
507 | free(hash); | ||
508 | + free(inst_differentiation); | ||
509 | #ifdef WITH_SELINUX | ||
510 | freecon(rawcon); | ||
511 | #endif | ||
512 | @@ -1262,55 +1435,35 @@ fail: | ||
513 | freecon(*origcon); | ||
514 | *origcon = NULL; | ||
515 | #endif | ||
516 | - free(*i_name); | ||
517 | - *i_name = NULL; | ||
518 | } | ||
519 | return rc; | ||
520 | } | ||
521 | |||
522 | -static int check_inst_parent(char *ipath, struct instance_data *idata) | ||
523 | +static int check_inst_parent(int dfd, struct instance_data *idata) | ||
524 | { | ||
525 | struct stat instpbuf; | ||
526 | - char *inst_parent, *trailing_slash; | ||
527 | - int dfd; | ||
528 | + | ||
529 | /* | ||
530 | - * stat the instance parent path to make sure it exists | ||
531 | - * and is a directory. Check that its mode is 000 (unless the | ||
532 | - * admin explicitly instructs to ignore the instance parent | ||
533 | - * mode by the "ignore_instance_parent_mode" argument). | ||
534 | + * Stat the instance parent directory to make sure it's writable by | ||
535 | + * root only (unless the admin explicitly instructs to ignore the | ||
536 | + * instance parent mode by the "ignore_instance_parent_mode" argument). | ||
537 | */ | ||
538 | - inst_parent = strdup(ipath); | ||
539 | - if (!inst_parent) { | ||
540 | - pam_syslog(idata->pamh, LOG_CRIT, "Error allocating pathname string"); | ||
541 | - return PAM_SESSION_ERR; | ||
542 | - } | ||
543 | |||
544 | - trailing_slash = strrchr(inst_parent, '/'); | ||
545 | - if (trailing_slash) | ||
546 | - *trailing_slash = '\0'; | ||
547 | - | ||
548 | - dfd = protect_dir(inst_parent, 0, 1, idata); | ||
549 | + if (idata->flags & PAMNS_IGN_INST_PARENT_MODE) | ||
550 | + return PAM_SUCCESS; | ||
551 | |||
552 | - if (dfd == -1 || fstat(dfd, &instpbuf) < 0) { | ||
553 | + if (fstat(dfd, &instpbuf) < 0) { | ||
554 | pam_syslog(idata->pamh, LOG_ERR, | ||
555 | - "Error creating or accessing instance parent %s, %m", inst_parent); | ||
556 | - if (dfd != -1) | ||
557 | - close(dfd); | ||
558 | - free(inst_parent); | ||
559 | + "Error accessing instance parent, %m"); | ||
560 | return PAM_SESSION_ERR; | ||
561 | } | ||
562 | |||
563 | - if ((idata->flags & PAMNS_IGN_INST_PARENT_MODE) == 0) { | ||
564 | - if ((instpbuf.st_mode & (S_IRWXU|S_IRWXG|S_IRWXO)) || instpbuf.st_uid != 0) { | ||
565 | - pam_syslog(idata->pamh, LOG_ERR, "Mode of inst parent %s not 000 or owner not root", | ||
566 | - inst_parent); | ||
567 | - close(dfd); | ||
568 | - free(inst_parent); | ||
569 | - return PAM_SESSION_ERR; | ||
570 | - } | ||
571 | + if ((instpbuf.st_mode & (S_IRWXU|S_IRWXG|S_IRWXO)) || instpbuf.st_uid != 0) { | ||
572 | + pam_syslog(idata->pamh, LOG_ERR, | ||
573 | + "Mode of inst parent not 000 or owner not root"); | ||
574 | + return PAM_SESSION_ERR; | ||
575 | } | ||
576 | - close(dfd); | ||
577 | - free(inst_parent); | ||
578 | + | ||
579 | return PAM_SUCCESS; | ||
580 | } | ||
581 | |||
582 | @@ -1449,14 +1602,16 @@ static int create_polydir(struct polydir_s *polyptr, | ||
583 | } | ||
584 | #endif | ||
585 | |||
586 | - rc = protect_dir(dir, mode, 1, idata); | ||
587 | + rc = secure_opendir(dir, | ||
588 | + SECURE_OPENDIR_PROTECT | SECURE_OPENDIR_MKDIR | SECURE_OPENDIR_FULL_FD, | ||
589 | + mode, idata); | ||
590 | if (rc == -1) { | ||
591 | pam_syslog(idata->pamh, LOG_ERR, | ||
592 | "Error creating directory %s: %m", dir); | ||
593 | #ifdef WITH_SELINUX | ||
594 | freecon(oldcon_raw); | ||
595 | #endif | ||
596 | - return PAM_SESSION_ERR; | ||
597 | + return -1; | ||
598 | } | ||
599 | |||
600 | #ifdef WITH_SELINUX | ||
601 | @@ -1477,9 +1632,9 @@ static int create_polydir(struct polydir_s *polyptr, | ||
602 | pam_syslog(idata->pamh, LOG_ERR, | ||
603 | "Error changing mode of directory %s: %m", dir); | ||
604 | close(rc); | ||
605 | - umount(dir); /* undo the eventual protection bind mount */ | ||
606 | - rmdir(dir); | ||
607 | - return PAM_SESSION_ERR; | ||
608 | + secure_umount(dir); /* undo the eventual protection bind mount */ | ||
609 | + secure_try_rmdir(dir); | ||
610 | + return -1; | ||
611 | } | ||
612 | } | ||
613 | |||
614 | @@ -1497,41 +1652,37 @@ static int create_polydir(struct polydir_s *polyptr, | ||
615 | pam_syslog(idata->pamh, LOG_ERR, | ||
616 | "Unable to change owner on directory %s: %m", dir); | ||
617 | close(rc); | ||
618 | - umount(dir); /* undo the eventual protection bind mount */ | ||
619 | - rmdir(dir); | ||
620 | - return PAM_SESSION_ERR; | ||
621 | + secure_umount(dir); /* undo the eventual protection bind mount */ | ||
622 | + secure_try_rmdir(dir); | ||
623 | + return -1; | ||
624 | } | ||
625 | |||
626 | - close(rc); | ||
627 | - | ||
628 | if (idata->flags & PAMNS_DEBUG) | ||
629 | pam_syslog(idata->pamh, LOG_DEBUG, | ||
630 | "Polydir owner %u group %u", uid, gid); | ||
631 | |||
632 | - return PAM_SUCCESS; | ||
633 | + return rc; | ||
634 | } | ||
635 | |||
636 | /* | ||
637 | - * Create polyinstantiated instance directory (ipath). | ||
638 | + * Create polyinstantiated instance directory. | ||
639 | + * To protect against races, changes are done on a fd to the parent of the | ||
640 | + * instance directory (dfd_iparent) and a relative path (polyptr->instname). | ||
641 | + * The absolute path (polyptr->instance_absolute) is only updated when creating | ||
642 | + * a tmpdir and used for logging purposes. | ||
643 | */ | ||
644 | #ifdef WITH_SELINUX | ||
645 | -static int create_instance(struct polydir_s *polyptr, char *ipath, struct stat *statbuf, | ||
646 | - const char *icontext, const char *ocontext, | ||
647 | - struct instance_data *idata) | ||
648 | +static int create_instance(struct polydir_s *polyptr, int dfd_iparent, | ||
649 | + struct stat *statbuf, const char *icontext, const char *ocontext, | ||
650 | + struct instance_data *idata) | ||
651 | #else | ||
652 | -static int create_instance(struct polydir_s *polyptr, char *ipath, struct stat *statbuf, | ||
653 | - struct instance_data *idata) | ||
654 | +static int create_instance(struct polydir_s *polyptr, int dfd_iparent, | ||
655 | + struct stat *statbuf, struct instance_data *idata) | ||
656 | #endif | ||
657 | { | ||
658 | struct stat newstatbuf; | ||
659 | int fd; | ||
660 | |||
661 | - /* | ||
662 | - * Check to make sure instance parent is valid. | ||
663 | - */ | ||
664 | - if (check_inst_parent(ipath, idata)) | ||
665 | - return PAM_SESSION_ERR; | ||
666 | - | ||
667 | /* | ||
668 | * Create instance directory and set its security context to the context | ||
669 | * returned by the security policy. Set its mode and ownership | ||
670 | @@ -1540,29 +1691,39 @@ static int create_instance(struct polydir_s *polyptr, char *ipath, struct stat * | ||
671 | */ | ||
672 | |||
673 | if (polyptr->method == TMPDIR) { | ||
674 | - if (mkdtemp(polyptr->instance_prefix) == NULL) { | ||
675 | - pam_syslog(idata->pamh, LOG_ERR, "Error creating temporary instance %s, %m", | ||
676 | - polyptr->instance_prefix); | ||
677 | - polyptr->method = NONE; /* do not clean up! */ | ||
678 | - return PAM_SESSION_ERR; | ||
679 | - } | ||
680 | - /* copy the actual directory name to ipath */ | ||
681 | - strcpy(ipath, polyptr->instance_prefix); | ||
682 | - } else if (mkdir(ipath, S_IRUSR) < 0) { | ||
683 | + char s_path[PATH_MAX]; | ||
684 | + /* | ||
685 | + * Create the template for mkdtemp() as a magic link based on | ||
686 | + * our existing fd to avoid symlink attacks and races. | ||
687 | + */ | ||
688 | + if (pam_sprintf(s_path, "/proc/self/fd/%d/%s", dfd_iparent, polyptr->instname) < 0 | ||
689 | + || mkdtemp(s_path) == NULL) { | ||
690 | + pam_syslog(idata->pamh, LOG_ERR, | ||
691 | + "Error creating temporary instance dir %s, %m", | ||
692 | + polyptr->instance_absolute); | ||
693 | + polyptr->method = NONE; /* do not clean up! */ | ||
694 | + return PAM_SESSION_ERR; | ||
695 | + } | ||
696 | + | ||
697 | + /* Copy the actual directory name to polyptr->instname */ | ||
698 | + strcpy(polyptr->instname, base_name(s_path)); | ||
699 | + } else if (mkdirat(dfd_iparent, polyptr->instname, S_IRUSR) < 0) { | ||
700 | if (errno == EEXIST) | ||
701 | return PAM_IGNORE; | ||
702 | else { | ||
703 | pam_syslog(idata->pamh, LOG_ERR, "Error creating %s, %m", | ||
704 | - ipath); | ||
705 | + polyptr->instance_absolute); | ||
706 | return PAM_SESSION_ERR; | ||
707 | } | ||
708 | } | ||
709 | |||
710 | - /* Open a descriptor to it to prevent races */ | ||
711 | - fd = open(ipath, O_DIRECTORY | O_RDONLY); | ||
712 | + /* Open a descriptor to prevent races, based on our existing fd. */ | ||
713 | + fd = openat(dfd_iparent, polyptr->instname, | ||
714 | + O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC); | ||
715 | if (fd < 0) { | ||
716 | - pam_syslog(idata->pamh, LOG_ERR, "Error opening %s, %m", ipath); | ||
717 | - rmdir(ipath); | ||
718 | + pam_syslog(idata->pamh, LOG_ERR, "Error opening %s, %m", | ||
719 | + polyptr->instance_absolute); | ||
720 | + unlinkat(dfd_iparent, polyptr->instname, AT_REMOVEDIR); | ||
721 | return PAM_SESSION_ERR; | ||
722 | } | ||
723 | #ifdef WITH_SELINUX | ||
724 | @@ -1572,17 +1733,19 @@ static int create_instance(struct polydir_s *polyptr, char *ipath, struct stat * | ||
725 | if (icontext) { | ||
726 | if (fsetfilecon(fd, icontext) < 0) { | ||
727 | pam_syslog(idata->pamh, LOG_ERR, | ||
728 | - "Error setting context of %s to %s", ipath, icontext); | ||
729 | + "Error setting context of %s to %s", | ||
730 | + polyptr->instance_absolute, icontext); | ||
731 | close(fd); | ||
732 | - rmdir(ipath); | ||
733 | + unlinkat(dfd_iparent, polyptr->instname, AT_REMOVEDIR); | ||
734 | return PAM_SESSION_ERR; | ||
735 | } | ||
736 | } else { | ||
737 | if (fsetfilecon(fd, ocontext) < 0) { | ||
738 | pam_syslog(idata->pamh, LOG_ERR, | ||
739 | - "Error setting context of %s to %s", ipath, ocontext); | ||
740 | + "Error setting context of %s to %s", | ||
741 | + polyptr->instance_absolute, ocontext); | ||
742 | close(fd); | ||
743 | - rmdir(ipath); | ||
744 | + unlinkat(dfd_iparent, polyptr->instname, AT_REMOVEDIR); | ||
745 | return PAM_SESSION_ERR; | ||
746 | } | ||
747 | } | ||
748 | @@ -1590,9 +1753,9 @@ static int create_instance(struct polydir_s *polyptr, char *ipath, struct stat * | ||
749 | #endif | ||
750 | if (fstat(fd, &newstatbuf) < 0) { | ||
751 | pam_syslog(idata->pamh, LOG_ERR, "Error stating %s, %m", | ||
752 | - ipath); | ||
753 | + polyptr->instance_absolute); | ||
754 | close(fd); | ||
755 | - rmdir(ipath); | ||
756 | + unlinkat(dfd_iparent, polyptr->instname, AT_REMOVEDIR); | ||
757 | return PAM_SESSION_ERR; | ||
758 | } | ||
759 | if (newstatbuf.st_uid != statbuf->st_uid || | ||
760 | @@ -1600,17 +1763,17 @@ static int create_instance(struct polydir_s *polyptr, char *ipath, struct stat * | ||
761 | if (fchown(fd, statbuf->st_uid, statbuf->st_gid) < 0) { | ||
762 | pam_syslog(idata->pamh, LOG_ERR, | ||
763 | "Error changing owner for %s, %m", | ||
764 | - ipath); | ||
765 | + polyptr->instance_absolute); | ||
766 | close(fd); | ||
767 | - rmdir(ipath); | ||
768 | + unlinkat(dfd_iparent, polyptr->instname, AT_REMOVEDIR); | ||
769 | return PAM_SESSION_ERR; | ||
770 | } | ||
771 | } | ||
772 | if (fchmod(fd, statbuf->st_mode & 07777) < 0) { | ||
773 | pam_syslog(idata->pamh, LOG_ERR, "Error changing mode for %s, %m", | ||
774 | - ipath); | ||
775 | + polyptr->instance_absolute); | ||
776 | close(fd); | ||
777 | - rmdir(ipath); | ||
778 | + unlinkat(dfd_iparent, polyptr->instname, AT_REMOVEDIR); | ||
779 | return PAM_SESSION_ERR; | ||
780 | } | ||
781 | close(fd); | ||
782 | @@ -1629,9 +1792,12 @@ static int ns_setup(struct polydir_s *polyptr, | ||
783 | struct instance_data *idata) | ||
784 | { | ||
785 | int retval; | ||
786 | + int dfd_iparent = -1; | ||
787 | + int dfd_ipath = -1; | ||
788 | + int dfd_pptrdir = -1; | ||
789 | int newdir = 1; | ||
790 | - char *inst_dir = NULL; | ||
791 | - char *instname = NULL; | ||
792 | + char s_ipath[MAGIC_LNK_FD_SIZE]; | ||
793 | + char s_pptrdir[MAGIC_LNK_FD_SIZE]; | ||
794 | struct stat statbuf; | ||
795 | #ifdef WITH_SELINUX | ||
796 | char *instcontext = NULL, *origcontext = NULL; | ||
797 | @@ -1641,37 +1807,48 @@ static int ns_setup(struct polydir_s *polyptr, | ||
798 | pam_syslog(idata->pamh, LOG_DEBUG, | ||
799 | "Set namespace for directory %s", polyptr->dir); | ||
800 | |||
801 | - retval = protect_dir(polyptr->dir, 0, 0, idata); | ||
802 | + dfd_pptrdir = secure_opendir(polyptr->dir, SECURE_OPENDIR_PROTECT, 0, idata); | ||
803 | |||
804 | - if (retval < 0) { | ||
805 | + if (dfd_pptrdir < 0) { | ||
806 | if (errno != ENOENT || !(polyptr->flags & POLYDIR_CREATE)) { | ||
807 | pam_syslog(idata->pamh, LOG_ERR, "Polydir %s access error: %m", | ||
808 | polyptr->dir); | ||
809 | return PAM_SESSION_ERR; | ||
810 | } | ||
811 | - if (create_polydir(polyptr, idata) != PAM_SUCCESS) | ||
812 | + dfd_pptrdir = create_polydir(polyptr, idata); | ||
813 | + if (dfd_pptrdir < 0) | ||
814 | return PAM_SESSION_ERR; | ||
815 | - } else { | ||
816 | - close(retval); | ||
817 | } | ||
818 | |||
819 | if (polyptr->method == TMPFS) { | ||
820 | - if (mount("tmpfs", polyptr->dir, "tmpfs", polyptr->mount_flags, polyptr->mount_opts) < 0) { | ||
821 | - pam_syslog(idata->pamh, LOG_ERR, "Error mounting tmpfs on %s, %m", | ||
822 | - polyptr->dir); | ||
823 | - return PAM_SESSION_ERR; | ||
824 | - } | ||
825 | + /* | ||
826 | + * There is no function mount() that operate on a fd, so instead, we | ||
827 | + * get the magic link corresponding to the fd and give it to mount(). | ||
828 | + * This protects against potential races exploitable by an unpriv user. | ||
829 | + */ | ||
830 | + if (pam_sprintf(s_pptrdir, "/proc/self/fd/%d", dfd_pptrdir) < 0) { | ||
831 | + pam_syslog(idata->pamh, LOG_ERR, "Error pam_sprintf s_pptrdir"); | ||
832 | + goto error_out; | ||
833 | + } | ||
834 | |||
835 | - if (polyptr->flags & POLYDIR_NOINIT) | ||
836 | - return PAM_SUCCESS; | ||
837 | + if (mount("tmpfs", s_pptrdir, "tmpfs", polyptr->mount_flags, polyptr->mount_opts) < 0) { | ||
838 | + pam_syslog(idata->pamh, LOG_ERR, "Error mounting tmpfs on %s, %m", | ||
839 | + polyptr->dir); | ||
840 | + goto error_out; | ||
841 | + } | ||
842 | + | ||
843 | + if (polyptr->flags & POLYDIR_NOINIT) { | ||
844 | + retval = PAM_SUCCESS; | ||
845 | + goto cleanup; | ||
846 | + } | ||
847 | |||
848 | - return inst_init(polyptr, "tmpfs", idata, 1); | ||
849 | + retval = inst_init(polyptr, "tmpfs", idata, 1); | ||
850 | + goto cleanup; | ||
851 | } | ||
852 | |||
853 | - if (stat(polyptr->dir, &statbuf) < 0) { | ||
854 | - pam_syslog(idata->pamh, LOG_ERR, "Error stating %s: %m", | ||
855 | - polyptr->dir); | ||
856 | - return PAM_SESSION_ERR; | ||
857 | + if (fstat(dfd_pptrdir, &statbuf) < 0) { | ||
858 | + pam_syslog(idata->pamh, LOG_ERR, "Error stating %s: %m", polyptr->dir); | ||
859 | + goto error_out; | ||
860 | } | ||
861 | |||
862 | /* | ||
863 | @@ -1680,15 +1857,16 @@ static int ns_setup(struct polydir_s *polyptr, | ||
864 | * security policy. | ||
865 | */ | ||
866 | #ifdef WITH_SELINUX | ||
867 | - retval = poly_name(polyptr, &instname, &instcontext, | ||
868 | - &origcontext, idata); | ||
869 | + retval = poly_name(polyptr, &instcontext, &origcontext, idata); | ||
870 | #else | ||
871 | - retval = poly_name(polyptr, &instname, idata); | ||
872 | + retval = poly_name(polyptr, idata); | ||
873 | #endif | ||
874 | |||
875 | if (retval != PAM_SUCCESS) { | ||
876 | - if (retval != PAM_IGNORE) | ||
877 | + if (retval != PAM_IGNORE) { | ||
878 | pam_syslog(idata->pamh, LOG_ERR, "Error getting instance name"); | ||
879 | + goto error_out; | ||
880 | + } | ||
881 | goto cleanup; | ||
882 | } else { | ||
883 | #ifdef WITH_SELINUX | ||
884 | @@ -1699,22 +1877,33 @@ static int ns_setup(struct polydir_s *polyptr, | ||
885 | #endif | ||
886 | } | ||
887 | |||
888 | - if ((inst_dir = pam_asprintf("%s%s", polyptr->instance_prefix, instname)) == NULL) | ||
889 | - goto error_out; | ||
890 | - | ||
891 | - if (idata->flags & PAMNS_DEBUG) | ||
892 | - pam_syslog(idata->pamh, LOG_DEBUG, "instance_dir %s", | ||
893 | - inst_dir); | ||
894 | + /* | ||
895 | + * Gets a fd in a secure manner (we may be operating on a path under | ||
896 | + * user control), and check it's compliant. | ||
897 | + * Then, we should *always* operate on *this* fd and a relative path | ||
898 | + * to be protected against race conditions. | ||
899 | + */ | ||
900 | + dfd_iparent = secure_opendir(polyptr->instance_parent, | ||
901 | + SECURE_OPENDIR_PROTECT | SECURE_OPENDIR_MKDIR, 0, idata); | ||
902 | + if (dfd_iparent == -1) { | ||
903 | + pam_syslog(idata->pamh, LOG_ERR, | ||
904 | + "polyptr->instance_parent %s access error", | ||
905 | + polyptr->instance_parent); | ||
906 | + goto error_out; | ||
907 | + } | ||
908 | + if (check_inst_parent(dfd_iparent, idata)) { | ||
909 | + goto error_out; | ||
910 | + } | ||
911 | |||
912 | /* | ||
913 | * Create instance directory with appropriate security | ||
914 | * contexts, owner, group and mode bits. | ||
915 | */ | ||
916 | #ifdef WITH_SELINUX | ||
917 | - retval = create_instance(polyptr, inst_dir, &statbuf, instcontext, | ||
918 | - origcontext, idata); | ||
919 | + retval = create_instance(polyptr, dfd_iparent, &statbuf, instcontext, | ||
920 | + origcontext, idata); | ||
921 | #else | ||
922 | - retval = create_instance(polyptr, inst_dir, &statbuf, idata); | ||
923 | + retval = create_instance(polyptr, dfd_iparent, &statbuf, idata); | ||
924 | #endif | ||
925 | |||
926 | if (retval == PAM_IGNORE) { | ||
927 | @@ -1726,19 +1915,48 @@ static int ns_setup(struct polydir_s *polyptr, | ||
928 | goto error_out; | ||
929 | } | ||
930 | |||
931 | + /* | ||
932 | + * Instead of getting a new secure fd, we reuse the fd opened on directory | ||
933 | + * polyptr->instance_parent to ensure we are working on the same dir as | ||
934 | + * previously, and thus ensure that previous checks (e.g. check_inst_parent()) | ||
935 | + * are still relevant. | ||
936 | + */ | ||
937 | + dfd_ipath = openat(dfd_iparent, polyptr->instname, | ||
938 | + O_PATH | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC); | ||
939 | + if (dfd_ipath == -1) { | ||
940 | + pam_syslog(idata->pamh, LOG_ERR, "Error openat on %s, %m", | ||
941 | + polyptr->instname); | ||
942 | + goto error_out; | ||
943 | + } | ||
944 | + | ||
945 | + if (pam_sprintf(s_ipath, "/proc/self/fd/%d", dfd_ipath) < 0) { | ||
946 | + pam_syslog(idata->pamh, LOG_ERR, "Error pam_sprintf s_ipath"); | ||
947 | + goto error_out; | ||
948 | + } | ||
949 | + | ||
950 | + if (pam_sprintf(s_pptrdir, "/proc/self/fd/%d", dfd_pptrdir) < 0) { | ||
951 | + pam_syslog(idata->pamh, LOG_ERR, "Error pam_sprintf s_pptrdir"); | ||
952 | + goto error_out; | ||
953 | + } | ||
954 | + | ||
955 | /* | ||
956 | * Bind mount instance directory on top of the polyinstantiated | ||
957 | * directory to provide an instance of polyinstantiated directory | ||
958 | * based on polyinstantiated method. | ||
959 | + * | ||
960 | + * Operates on magic links created from two fd obtained securely | ||
961 | + * to protect against race conditions and symlink attacks. Indeed, | ||
962 | + * the source and destination can be in a user controled path. | ||
963 | */ | ||
964 | - if (mount(inst_dir, polyptr->dir, NULL, MS_BIND, NULL) < 0) { | ||
965 | - pam_syslog(idata->pamh, LOG_ERR, "Error mounting %s on %s, %m", | ||
966 | - inst_dir, polyptr->dir); | ||
967 | + if(mount(s_ipath, s_pptrdir, NULL, MS_BIND, NULL) < 0) { | ||
968 | + pam_syslog(idata->pamh, LOG_ERR, | ||
969 | + "Error mounting %s on %s (%s on %s), %m", | ||
970 | + s_ipath, s_pptrdir, polyptr->instance_absolute, polyptr->dir); | ||
971 | goto error_out; | ||
972 | } | ||
973 | |||
974 | if (!(polyptr->flags & POLYDIR_NOINIT)) | ||
975 | - retval = inst_init(polyptr, inst_dir, idata, newdir); | ||
976 | + retval = inst_init(polyptr, polyptr->instance_absolute, idata, newdir); | ||
977 | |||
978 | goto cleanup; | ||
979 | |||
980 | @@ -1750,8 +1968,12 @@ error_out: | ||
981 | retval = PAM_SESSION_ERR; | ||
982 | |||
983 | cleanup: | ||
984 | - free(inst_dir); | ||
985 | - free(instname); | ||
986 | + if (dfd_iparent != -1) | ||
987 | + close(dfd_iparent); | ||
988 | + if (dfd_ipath != -1) | ||
989 | + close(dfd_ipath); | ||
990 | + if (dfd_pptrdir != -1) | ||
991 | + close(dfd_pptrdir); | ||
992 | #ifdef WITH_SELINUX | ||
993 | freecon(instcontext); | ||
994 | freecon(origcontext); | ||
995 | @@ -1790,6 +2012,7 @@ static int cleanup_tmpdirs(struct instance_data *idata) | ||
996 | { | ||
997 | struct polydir_s *pptr; | ||
998 | pid_t rc, pid; | ||
999 | + int dfd = -1; | ||
1000 | struct sigaction newsa, oldsa; | ||
1001 | int status; | ||
1002 | |||
1003 | @@ -1801,7 +2024,17 @@ static int cleanup_tmpdirs(struct instance_data *idata) | ||
1004 | } | ||
1005 | |||
1006 | for (pptr = idata->polydirs_ptr; pptr; pptr = pptr->next) { | ||
1007 | - if (pptr->method == TMPDIR && access(pptr->instance_prefix, F_OK) == 0) { | ||
1008 | + if (pptr->method == TMPDIR) { | ||
1009 | + | ||
1010 | + dfd = secure_opendir_stateless(pptr->instance_parent); | ||
1011 | + if (dfd == -1) | ||
1012 | + continue; | ||
1013 | + | ||
1014 | + if (faccessat(dfd, pptr->instname, F_OK, AT_SYMLINK_NOFOLLOW) != 0) { | ||
1015 | + close(dfd); | ||
1016 | + continue; | ||
1017 | + } | ||
1018 | + | ||
1019 | pid = fork(); | ||
1020 | if (pid == 0) { | ||
1021 | static char *envp[] = { NULL }; | ||
1022 | @@ -1811,10 +2044,21 @@ static int cleanup_tmpdirs(struct instance_data *idata) | ||
1023 | _exit(1); | ||
1024 | } | ||
1025 | #endif | ||
1026 | + if (fchdir(dfd) == -1) { | ||
1027 | + pam_syslog(idata->pamh, LOG_ERR, "Failed fchdir to %s: %m", | ||
1028 | + pptr->instance_absolute); | ||
1029 | + _exit(1); | ||
1030 | + } | ||
1031 | + | ||
1032 | close_fds_pre_exec(idata); | ||
1033 | - execle("/bin/rm", "/bin/rm", "-rf", pptr->instance_prefix, NULL, envp); | ||
1034 | + | ||
1035 | + execle("/bin/rm", "/bin/rm", "-rf", pptr->instname, NULL, envp); | ||
1036 | _exit(1); | ||
1037 | } else if (pid > 0) { | ||
1038 | + | ||
1039 | + if (dfd != -1) | ||
1040 | + close(dfd); | ||
1041 | + | ||
1042 | while (((rc = waitpid(pid, &status, 0)) == (pid_t)-1) && | ||
1043 | (errno == EINTR)); | ||
1044 | if (rc == (pid_t)-1) { | ||
1045 | @@ -1827,6 +2071,10 @@ static int cleanup_tmpdirs(struct instance_data *idata) | ||
1046 | "Error removing %s", pptr->instance_prefix); | ||
1047 | } | ||
1048 | } else if (pid < 0) { | ||
1049 | + | ||
1050 | + if (dfd != -1) | ||
1051 | + close(dfd); | ||
1052 | + | ||
1053 | pam_syslog(idata->pamh, LOG_ERR, | ||
1054 | "Cannot fork to cleanup temporary directory, %m"); | ||
1055 | rc = PAM_SESSION_ERR; | ||
1056 | @@ -1850,6 +2098,7 @@ out: | ||
1057 | static int setup_namespace(struct instance_data *idata, enum unmnt_op unmnt) | ||
1058 | { | ||
1059 | int retval = 0, need_poly = 0, changing_dir = 0; | ||
1060 | + int dfd = -1; | ||
1061 | char *cptr, *fptr, poly_parent[PATH_MAX]; | ||
1062 | struct polydir_s *pptr; | ||
1063 | |||
1064 | @@ -1965,13 +2214,21 @@ static int setup_namespace(struct instance_data *idata, enum unmnt_op unmnt) | ||
1065 | strcpy(poly_parent, "/"); | ||
1066 | else if (cptr) | ||
1067 | *cptr = '\0'; | ||
1068 | - if (chdir(poly_parent) < 0) { | ||
1069 | + | ||
1070 | + dfd = secure_opendir_stateless(poly_parent); | ||
1071 | + if (dfd == -1) { | ||
1072 | + pam_syslog(idata->pamh, LOG_ERR, | ||
1073 | + "Failed opening %s to fchdir: %m", poly_parent); | ||
1074 | + } | ||
1075 | + else if (fchdir(dfd) == -1) { | ||
1076 | pam_syslog(idata->pamh, LOG_ERR, | ||
1077 | - "Can't chdir to %s, %m", poly_parent); | ||
1078 | + "Failed fchdir to %s: %m", poly_parent); | ||
1079 | } | ||
1080 | + if (dfd != -1) | ||
1081 | + close(dfd); | ||
1082 | } | ||
1083 | |||
1084 | - if (umount(pptr->rdir) < 0) { | ||
1085 | + if (secure_umount(pptr->rdir) < 0) { | ||
1086 | int saved_errno = errno; | ||
1087 | pam_syslog(idata->pamh, LOG_ERR, "Unmount of %s failed, %m", | ||
1088 | pptr->rdir); | ||
1089 | @@ -2041,7 +2298,7 @@ static int orig_namespace(struct instance_data *idata) | ||
1090 | "Unmounting instance dir for user %d & dir %s", | ||
1091 | idata->uid, pptr->dir); | ||
1092 | |||
1093 | - if (umount(pptr->dir) < 0) { | ||
1094 | + if (secure_umount(pptr->dir) < 0) { | ||
1095 | pam_syslog(idata->pamh, LOG_ERR, "Unmount of %s failed, %m", | ||
1096 | pptr->dir); | ||
1097 | return PAM_SESSION_ERR; | ||
1098 | diff --git a/modules/pam_namespace/pam_namespace.h b/modules/pam_namespace/pam_namespace.h | ||
1099 | index 180e042..721d39a 100644 | ||
1100 | --- a/modules/pam_namespace/pam_namespace.h | ||
1101 | +++ b/modules/pam_namespace/pam_namespace.h | ||
1102 | @@ -121,6 +121,13 @@ | ||
1103 | #define NAMESPACE_POLYDIR_DATA "pam_namespace:polydir_data" | ||
1104 | #define NAMESPACE_PROTECT_DATA "pam_namespace:protect_data" | ||
1105 | |||
1106 | +/* | ||
1107 | + * Operation mode for function secure_opendir() | ||
1108 | + */ | ||
1109 | +#define SECURE_OPENDIR_PROTECT 0x00000001 | ||
1110 | +#define SECURE_OPENDIR_MKDIR 0x00000002 | ||
1111 | +#define SECURE_OPENDIR_FULL_FD 0x00000004 | ||
1112 | + | ||
1113 | /* | ||
1114 | * Polyinstantiation method options, based on user, security context | ||
1115 | * or both | ||
1116 | @@ -158,6 +165,9 @@ struct polydir_s { | ||
1117 | char dir[PATH_MAX]; /* directory to polyinstantiate */ | ||
1118 | char rdir[PATH_MAX]; /* directory to unmount (based on RUSER) */ | ||
1119 | char instance_prefix[PATH_MAX]; /* prefix for instance dir path name */ | ||
1120 | + char instance_absolute[PATH_MAX]; /* absolute path to the instance dir (instance_parent + instname) */ | ||
1121 | + char instance_parent[PATH_MAX]; /* parent dir of the instance dir */ | ||
1122 | + char *instname; /* last segment of the path to the instance dir */ | ||
1123 | enum polymethod method; /* method used to polyinstantiate */ | ||
1124 | unsigned int num_uids; /* number of override uids */ | ||
1125 | uid_t *uid; /* list of override uids */ | ||
1126 | -- | ||
1127 | 2.49.0 | ||
1128 | |||
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..18c2a82fb4 --- /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 d9053a1..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 9d993d4..4c8153b 100644 | ||
90 | --- a/modules/pam_namespace/pam_namespace.c | ||
91 | +++ b/modules/pam_namespace/pam_namespace.c | ||
92 | @@ -1467,6 +1467,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 | @@ -1524,7 +1597,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.49.0 | ||
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..238bef47ec --- /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 4c8153b..791dd07 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.49.0 | ||
35 | |||
diff --git a/meta/recipes-extended/pam/libpam_1.5.3.bb b/meta/recipes-extended/pam/libpam_1.5.3.bb index 714cdb6552..815085cc82 100644 --- a/meta/recipes-extended/pam/libpam_1.5.3.bb +++ b/meta/recipes-extended/pam/libpam_1.5.3.bb | |||
@@ -29,6 +29,11 @@ SRC_URI = "${GITHUB_BASE_URI}/download/v${PV}/Linux-PAM-${PV}.tar.xz \ | |||
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-inline-pam-asprintf.patch \ | ||
33 | file://0002-pam-namespace-rebase.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] = "7ac4b50feee004a9fa88f1dfd2d2fa738a82896763050cd773b3c54b0a818283" | 39 | SRC_URI[sha256sum] = "7ac4b50feee004a9fa88f1dfd2d2fa738a82896763050cd773b3c54b0a818283" |