Line data Source code
1 : /*
2 : Unix SMB/CIFS implementation.
3 :
4 : POSIX NTVFS backend - pvfs_sys wrappers
5 :
6 : Copyright (C) Andrew Tridgell 2010
7 : Copyright (C) Andrew Bartlett 2010
8 :
9 : This program is free software; you can redistribute it and/or modify
10 : it under the terms of the GNU General Public License as published by
11 : the Free Software Foundation; either version 3 of the License, or
12 : (at your option) any later version.
13 :
14 : This program is distributed in the hope that it will be useful,
15 : but WITHOUT ANY WARRANTY; without even the implied warranty of
16 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 : GNU General Public License for more details.
18 :
19 : You should have received a copy of the GNU General Public License
20 : along with this program. If not, see <http://www.gnu.org/licenses/>.
21 : */
22 :
23 : #include "includes.h"
24 : #include "vfs_posix.h"
25 : #include "../lib/util/unix_privs.h"
26 :
27 : /*
28 : these wrapper functions must only be called when the appropriate ACL
29 : has already been checked. The wrappers will override a EACCES result
30 : by gaining root privileges if the 'pvfs:perm override' is set on the
31 : share (it is enabled by default)
32 :
33 : Careful use of O_NOFOLLOW and O_DIRECTORY is used to prevent
34 : security attacks via symlinks
35 : */
36 :
37 :
38 : struct pvfs_sys_ctx {
39 : struct pvfs_state *pvfs;
40 : void *privs;
41 : const char *old_wd;
42 : struct stat st_orig;
43 : };
44 :
45 :
46 : /*
47 : we create PVFS_NOFOLLOW and PVFS_DIRECTORY as aliases for O_NOFOLLOW
48 : and O_DIRECTORY on systems that have them. On systems that don't
49 : have O_NOFOLLOW we are less safe, but the root override code is off
50 : by default.
51 : */
52 : #ifdef O_NOFOLLOW
53 : #define PVFS_NOFOLLOW O_NOFOLLOW
54 : #else
55 : #define PVFS_NOFOLLOW 0
56 : #endif
57 : #ifdef O_DIRECTORY
58 : #define PVFS_DIRECTORY O_DIRECTORY
59 : #else
60 : #define PVFS_DIRECTORY 0
61 : #endif
62 :
63 : /*
64 : return to original directory when context is destroyed
65 : */
66 0 : static int pvfs_sys_pushdir_destructor(struct pvfs_sys_ctx *ctx)
67 : {
68 0 : struct stat st;
69 :
70 0 : if (ctx->old_wd == NULL) {
71 0 : return 0;
72 : }
73 :
74 0 : if (chdir(ctx->old_wd) != 0) {
75 0 : smb_panic("Failed to restore working directory");
76 : }
77 0 : if (stat(".", &st) != 0) {
78 0 : smb_panic("Failed to stat working directory");
79 : }
80 0 : if (st.st_ino != ctx->st_orig.st_ino ||
81 0 : st.st_dev != ctx->st_orig.st_dev) {
82 0 : smb_panic("Working directory changed during call");
83 : }
84 :
85 0 : return 0;
86 : }
87 :
88 :
89 : /*
90 : chdir() to the directory part of a pathname, but disallow any
91 : component with a symlink
92 :
93 : Note that we can't use O_NOFOLLOW on the whole path as that only
94 : prevents links in the final component of the path
95 : */
96 0 : static int pvfs_sys_chdir_nosymlink(struct pvfs_sys_ctx *ctx, const char *pathname)
97 : {
98 0 : char *p, *path;
99 0 : size_t base_len = strlen(ctx->pvfs->base_directory);
100 :
101 : /* don't check for symlinks in the base directory of the share */
102 0 : if (strncmp(ctx->pvfs->base_directory, pathname, base_len) == 0 &&
103 0 : pathname[base_len] == '/') {
104 0 : if (chdir(ctx->pvfs->base_directory) != 0) {
105 0 : return -1;
106 : }
107 0 : pathname += base_len + 1;
108 : }
109 :
110 0 : path = talloc_strdup(ctx, pathname);
111 0 : if (path == NULL) {
112 0 : return -1;
113 : }
114 0 : while ((p = strchr(path, '/'))) {
115 0 : int fd;
116 0 : struct stat st1, st2;
117 0 : *p = 0;
118 0 : fd = open(path, PVFS_NOFOLLOW | PVFS_DIRECTORY | O_RDONLY);
119 0 : if (fd == -1) {
120 0 : return -1;
121 : }
122 0 : if (chdir(path) != 0) {
123 0 : close(fd);
124 0 : return -1;
125 : }
126 0 : if (stat(".", &st1) != 0 ||
127 0 : fstat(fd, &st2) != 0) {
128 0 : close(fd);
129 0 : return -1;
130 : }
131 0 : close(fd);
132 0 : if (st1.st_ino != st2.st_ino ||
133 0 : st1.st_dev != st2.st_dev) {
134 0 : DEBUG(0,(__location__ ": Inode changed during chdir in '%s' - symlink attack?\n",
135 : pathname));
136 0 : return -1;
137 : }
138 0 : path = p + 1;
139 : }
140 :
141 0 : return 0;
142 : }
143 :
144 :
145 : /*
146 : become root, and change directory to the directory component of a
147 : path. Return a talloc context which when freed will move us back
148 : to the original directory, and return us to the original uid
149 :
150 : change the pathname argument to contain just the base component of
151 : the path
152 :
153 : return NULL on error, which could include an attempt to subvert
154 : security using symlink tricks
155 : */
156 0 : static struct pvfs_sys_ctx *pvfs_sys_pushdir(struct pvfs_state *pvfs,
157 : const char **pathname)
158 : {
159 0 : struct pvfs_sys_ctx *ctx;
160 0 : char *cwd, *p, *dirname;
161 0 : int ret;
162 :
163 0 : ctx = talloc_zero(pvfs, struct pvfs_sys_ctx);
164 0 : if (ctx == NULL) {
165 0 : return NULL;
166 : }
167 0 : ctx->pvfs = pvfs;
168 0 : ctx->privs = root_privileges();
169 0 : if (ctx->privs == NULL) {
170 0 : talloc_free(ctx);
171 0 : return NULL;
172 : }
173 :
174 0 : talloc_steal(ctx, ctx->privs);
175 :
176 0 : if (!pathname) {
177 : /* no pathname needed */
178 0 : return ctx;
179 : }
180 :
181 0 : p = strrchr(*pathname, '/');
182 0 : if (p == NULL) {
183 : /* we don't need to change directory */
184 0 : return ctx;
185 : }
186 :
187 : /* we keep the old st around, so we can tell that
188 : we have come back to the right directory */
189 0 : if (stat(".", &ctx->st_orig) != 0) {
190 0 : talloc_free(ctx);
191 0 : return NULL;
192 : }
193 :
194 0 : cwd = get_current_dir_name();
195 0 : if (cwd == NULL) {
196 0 : talloc_free(ctx);
197 0 : return NULL;
198 : }
199 :
200 0 : ctx->old_wd = talloc_strdup(ctx, cwd);
201 0 : free(cwd);
202 :
203 0 : if (ctx->old_wd == NULL) {
204 0 : talloc_free(ctx);
205 0 : return NULL;
206 : }
207 :
208 0 : dirname = talloc_strndup(ctx, *pathname, (p - *pathname));
209 0 : if (dirname == NULL) {
210 0 : talloc_free(ctx);
211 0 : return NULL;
212 : }
213 :
214 0 : ret = pvfs_sys_chdir_nosymlink(ctx, *pathname);
215 0 : if (ret == -1) {
216 0 : talloc_free(ctx);
217 0 : return NULL;
218 : }
219 :
220 0 : talloc_set_destructor(ctx, pvfs_sys_pushdir_destructor);
221 :
222 : /* return the basename as the filename that should be operated on */
223 0 : (*pathname) = talloc_strdup(ctx, p+1);
224 0 : if (! *pathname) {
225 0 : talloc_free(ctx);
226 0 : return NULL;
227 : }
228 :
229 0 : return ctx;
230 : }
231 :
232 :
233 : /*
234 : chown a file that we created with a root privileges override
235 : */
236 0 : static int pvfs_sys_fchown(struct pvfs_state *pvfs, struct pvfs_sys_ctx *ctx, int fd)
237 : {
238 0 : return fchown(fd, root_privileges_original_uid(ctx->privs), -1);
239 : }
240 :
241 : /*
242 : chown a directory that we created with a root privileges override
243 : */
244 0 : static int pvfs_sys_chown(struct pvfs_state *pvfs, struct pvfs_sys_ctx *ctx, const char *name)
245 : {
246 : /* to avoid symlink hacks, we need to use fchown() on a directory fd */
247 0 : int ret, fd;
248 0 : fd = open(name, PVFS_DIRECTORY | PVFS_NOFOLLOW | O_RDONLY);
249 0 : if (fd == -1) {
250 0 : return -1;
251 : }
252 0 : ret = pvfs_sys_fchown(pvfs, ctx, fd);
253 0 : close(fd);
254 0 : return ret;
255 : }
256 :
257 :
258 : /*
259 : wrap open for system override
260 : */
261 186192 : int pvfs_sys_open(struct pvfs_state *pvfs, const char *filename, int flags, mode_t mode, bool allow_override)
262 : {
263 0 : int fd, ret;
264 0 : struct pvfs_sys_ctx *ctx;
265 0 : int saved_errno, orig_errno;
266 186192 : int retries = 5;
267 :
268 186192 : orig_errno = errno;
269 :
270 186192 : fd = open(filename, flags, mode);
271 186192 : if (fd != -1 ||
272 0 : !allow_override ||
273 0 : errno != EACCES) {
274 186192 : return fd;
275 : }
276 :
277 0 : saved_errno = errno;
278 0 : ctx = pvfs_sys_pushdir(pvfs, &filename);
279 0 : if (ctx == NULL) {
280 0 : errno = saved_errno;
281 0 : return -1;
282 : }
283 :
284 : /* don't allow permission overrides to follow links */
285 0 : flags |= PVFS_NOFOLLOW;
286 :
287 : /*
288 : if O_CREAT was specified and O_EXCL was not specified
289 : then initially do the open without O_CREAT, as in that case
290 : we know that we did not create the file, so we don't have
291 : to fchown it
292 : */
293 0 : if ((flags & O_CREAT) && !(flags & O_EXCL)) {
294 0 : try_again:
295 0 : fd = open(filename, flags & ~O_CREAT, mode);
296 : /* if this open succeeded, or if it failed
297 : with anything other than ENOENT, then we return the
298 : open result, with the original errno */
299 0 : if (fd == -1 && errno != ENOENT) {
300 0 : talloc_free(ctx);
301 0 : errno = saved_errno;
302 0 : return -1;
303 : }
304 0 : if (fd != -1) {
305 : /* the file already existed and we opened it */
306 0 : talloc_free(ctx);
307 0 : errno = orig_errno;
308 0 : return fd;
309 : }
310 :
311 0 : fd = open(filename, flags | O_EXCL, mode);
312 0 : if (fd == -1 && errno != EEXIST) {
313 0 : talloc_free(ctx);
314 0 : errno = saved_errno;
315 0 : return -1;
316 : }
317 0 : if (fd != -1) {
318 : /* we created the file, we need to set the
319 : right ownership on it */
320 0 : ret = pvfs_sys_fchown(pvfs, ctx, fd);
321 0 : if (ret == -1) {
322 0 : close(fd);
323 0 : unlink(filename);
324 0 : talloc_free(ctx);
325 0 : errno = saved_errno;
326 0 : return -1;
327 : }
328 0 : talloc_free(ctx);
329 0 : errno = orig_errno;
330 0 : return fd;
331 : }
332 :
333 : /* the file got created between the two times
334 : we tried to open it! Try again */
335 0 : if (retries-- > 0) {
336 0 : goto try_again;
337 : }
338 :
339 0 : talloc_free(ctx);
340 0 : errno = saved_errno;
341 0 : return -1;
342 : }
343 :
344 0 : fd = open(filename, flags, mode);
345 0 : if (fd == -1) {
346 0 : talloc_free(ctx);
347 0 : errno = saved_errno;
348 0 : return -1;
349 : }
350 :
351 : /* if we have created a file then fchown it */
352 0 : if (flags & O_CREAT) {
353 0 : ret = pvfs_sys_fchown(pvfs, ctx, fd);
354 0 : if (ret == -1) {
355 0 : close(fd);
356 0 : unlink(filename);
357 0 : talloc_free(ctx);
358 0 : errno = saved_errno;
359 0 : return -1;
360 : }
361 : }
362 :
363 0 : talloc_free(ctx);
364 0 : return fd;
365 : }
366 :
367 :
368 : /*
369 : wrap unlink for system override
370 : */
371 96969 : int pvfs_sys_unlink(struct pvfs_state *pvfs, const char *filename, bool allow_override)
372 : {
373 0 : int ret;
374 0 : struct pvfs_sys_ctx *ctx;
375 0 : int saved_errno, orig_errno;
376 :
377 96969 : orig_errno = errno;
378 :
379 96969 : ret = unlink(filename);
380 96969 : if (ret != -1 ||
381 0 : !allow_override ||
382 0 : errno != EACCES) {
383 96969 : return ret;
384 : }
385 :
386 0 : saved_errno = errno;
387 :
388 0 : ctx = pvfs_sys_pushdir(pvfs, &filename);
389 0 : if (ctx == NULL) {
390 0 : errno = saved_errno;
391 0 : return -1;
392 : }
393 :
394 0 : ret = unlink(filename);
395 0 : if (ret == -1) {
396 0 : talloc_free(ctx);
397 0 : errno = saved_errno;
398 0 : return -1;
399 : }
400 :
401 0 : talloc_free(ctx);
402 0 : errno = orig_errno;
403 0 : return ret;
404 : }
405 :
406 :
407 0 : static bool contains_symlink(const char *path)
408 : {
409 0 : int fd = open(path, PVFS_NOFOLLOW | O_RDONLY);
410 0 : int posix_errno = errno;
411 0 : if (fd != -1) {
412 0 : close(fd);
413 0 : return false;
414 : }
415 :
416 : #if defined(ENOTSUP) && defined(OSF1)
417 : /* handle special Tru64 errno */
418 : if (errno == ENOTSUP) {
419 : posix_errno = ELOOP;
420 : }
421 : #endif /* ENOTSUP */
422 :
423 : #ifdef EFTYPE
424 : /* fix broken NetBSD errno */
425 : if (errno == EFTYPE) {
426 : posix_errno = ELOOP;
427 : }
428 : #endif /* EFTYPE */
429 :
430 : /* fix broken FreeBSD errno */
431 0 : if (errno == EMLINK) {
432 0 : posix_errno = ELOOP;
433 : }
434 :
435 0 : return (posix_errno == ELOOP);
436 : }
437 :
438 : /*
439 : wrap rename for system override
440 : */
441 116 : int pvfs_sys_rename(struct pvfs_state *pvfs, const char *name1, const char *name2, bool allow_override)
442 : {
443 0 : int ret;
444 0 : struct pvfs_sys_ctx *ctx;
445 0 : int saved_errno, orig_errno;
446 :
447 116 : orig_errno = errno;
448 :
449 116 : ret = rename(name1, name2);
450 116 : if (ret != -1 ||
451 4 : !allow_override ||
452 0 : errno != EACCES) {
453 116 : return ret;
454 : }
455 :
456 0 : saved_errno = errno;
457 :
458 0 : ctx = pvfs_sys_pushdir(pvfs, &name1);
459 0 : if (ctx == NULL) {
460 0 : errno = saved_errno;
461 0 : return -1;
462 : }
463 :
464 : /* we need the destination as an absolute path */
465 0 : if (name2[0] != '/') {
466 0 : name2 = talloc_asprintf(ctx, "%s/%s", ctx->old_wd, name2);
467 0 : if (name2 == NULL) {
468 0 : talloc_free(ctx);
469 0 : errno = saved_errno;
470 0 : return -1;
471 : }
472 : }
473 :
474 : /* make sure the destination isn't a symlink beforehand */
475 0 : if (contains_symlink(name2)) {
476 0 : talloc_free(ctx);
477 0 : errno = saved_errno;
478 0 : return -1;
479 : }
480 :
481 0 : ret = rename(name1, name2);
482 0 : if (ret == -1) {
483 0 : talloc_free(ctx);
484 0 : errno = saved_errno;
485 0 : return -1;
486 : }
487 :
488 : /* make sure the destination isn't a symlink afterwards */
489 0 : if (contains_symlink(name2)) {
490 0 : DEBUG(0,(__location__ ": Possible symlink attack in rename to '%s' - unlinking\n", name2));
491 0 : unlink(name2);
492 0 : talloc_free(ctx);
493 0 : errno = saved_errno;
494 0 : return -1;
495 : }
496 :
497 0 : talloc_free(ctx);
498 0 : errno = orig_errno;
499 0 : return ret;
500 : }
501 :
502 :
503 : /*
504 : wrap mkdir for system override
505 : */
506 7252 : int pvfs_sys_mkdir(struct pvfs_state *pvfs, const char *dirname, mode_t mode, bool allow_override)
507 : {
508 0 : int ret;
509 0 : struct pvfs_sys_ctx *ctx;
510 0 : int saved_errno, orig_errno;
511 :
512 7252 : orig_errno = errno;
513 :
514 7252 : ret = mkdir(dirname, mode);
515 7252 : if (ret != -1 ||
516 0 : !allow_override ||
517 0 : errno != EACCES) {
518 7252 : return ret;
519 : }
520 :
521 0 : saved_errno = errno;
522 0 : ctx = pvfs_sys_pushdir(pvfs, &dirname);
523 0 : if (ctx == NULL) {
524 0 : errno = saved_errno;
525 0 : return -1;
526 : }
527 :
528 0 : ret = mkdir(dirname, mode);
529 0 : if (ret == -1) {
530 0 : talloc_free(ctx);
531 0 : errno = saved_errno;
532 0 : return -1;
533 : }
534 :
535 0 : ret = pvfs_sys_chown(pvfs, ctx, dirname);
536 0 : if (ret == -1) {
537 0 : rmdir(dirname);
538 0 : talloc_free(ctx);
539 0 : errno = saved_errno;
540 0 : return -1;
541 : }
542 :
543 0 : talloc_free(ctx);
544 0 : errno = orig_errno;
545 0 : return ret;
546 : }
547 :
548 :
549 : /*
550 : wrap rmdir for system override
551 : */
552 7294 : int pvfs_sys_rmdir(struct pvfs_state *pvfs, const char *dirname, bool allow_override)
553 : {
554 0 : int ret;
555 0 : struct pvfs_sys_ctx *ctx;
556 0 : int saved_errno, orig_errno;
557 :
558 7294 : orig_errno = errno;
559 :
560 7294 : ret = rmdir(dirname);
561 7294 : if (ret != -1 ||
562 22 : !allow_override ||
563 0 : errno != EACCES) {
564 7294 : return ret;
565 : }
566 :
567 0 : saved_errno = errno;
568 :
569 0 : ctx = pvfs_sys_pushdir(pvfs, &dirname);
570 0 : if (ctx == NULL) {
571 0 : errno = saved_errno;
572 0 : return -1;
573 : }
574 :
575 0 : ret = rmdir(dirname);
576 0 : if (ret == -1) {
577 0 : talloc_free(ctx);
578 0 : errno = saved_errno;
579 0 : return -1;
580 : }
581 :
582 0 : talloc_free(ctx);
583 0 : errno = orig_errno;
584 0 : return ret;
585 : }
586 :
587 : /*
588 : wrap fchmod for system override
589 : */
590 804 : int pvfs_sys_fchmod(struct pvfs_state *pvfs, int fd, mode_t mode, bool allow_override)
591 : {
592 0 : int ret;
593 0 : struct pvfs_sys_ctx *ctx;
594 0 : int saved_errno, orig_errno;
595 :
596 804 : orig_errno = errno;
597 :
598 804 : ret = fchmod(fd, mode);
599 804 : if (ret != -1 ||
600 0 : !allow_override ||
601 0 : errno != EACCES) {
602 804 : return ret;
603 : }
604 :
605 0 : saved_errno = errno;
606 :
607 0 : ctx = pvfs_sys_pushdir(pvfs, NULL);
608 0 : if (ctx == NULL) {
609 0 : errno = saved_errno;
610 0 : return -1;
611 : }
612 :
613 0 : ret = fchmod(fd, mode);
614 0 : if (ret == -1) {
615 0 : talloc_free(ctx);
616 0 : errno = saved_errno;
617 0 : return -1;
618 : }
619 :
620 0 : talloc_free(ctx);
621 0 : errno = orig_errno;
622 0 : return ret;
623 : }
624 :
625 :
626 : /*
627 : wrap chmod for system override
628 : */
629 135 : int pvfs_sys_chmod(struct pvfs_state *pvfs, const char *filename, mode_t mode, bool allow_override)
630 : {
631 0 : int ret;
632 0 : struct pvfs_sys_ctx *ctx;
633 0 : int saved_errno, orig_errno;
634 :
635 135 : orig_errno = errno;
636 :
637 135 : ret = chmod(filename, mode);
638 135 : if (ret != -1 ||
639 0 : !allow_override ||
640 0 : errno != EACCES) {
641 135 : return ret;
642 : }
643 :
644 0 : saved_errno = errno;
645 :
646 0 : ctx = pvfs_sys_pushdir(pvfs, &filename);
647 0 : if (ctx == NULL) {
648 0 : errno = saved_errno;
649 0 : return -1;
650 : }
651 :
652 0 : ret = chmod(filename, mode);
653 0 : if (ret == -1) {
654 0 : talloc_free(ctx);
655 0 : errno = saved_errno;
656 0 : return -1;
657 : }
658 :
659 0 : talloc_free(ctx);
660 0 : errno = orig_errno;
661 0 : return ret;
662 : }
|