comparison src/core/ngx_open_file_cache.c @ 4477:7033faf6dc3c

Added disable_symlinks directive. To completely disable symlinks (disable_symlinks on) we use openat(O_NOFOLLOW) for each path component to avoid races. To allow symlinks with the same owner (disable_symlinks if_not_owner), use openat() (followed by fstat()) and fstatat(AT_SYMLINK_NOFOLLOW), and then compare uids between fstat() and fstatat(). As there is a race between openat() and fstatat() we don't know if openat() in fact opened symlink or not. Therefore, we have to compare uids even if fstatat() reports the opened component isn't a symlink (as we don't know whether it was symlink during openat() or not). Default value is off, i.e. symlinks are allowed.
author Andrey Belov <defan@nginx.com>
date Mon, 13 Feb 2012 16:29:04 +0000
parents 94ef9d25ec5b
children 5e6436812c9a
comparison
equal deleted inserted replaced
4476:94ef9d25ec5b 4477:7033faf6dc3c
20 20
21 #define NGX_MIN_READ_AHEAD (128 * 1024) 21 #define NGX_MIN_READ_AHEAD (128 * 1024)
22 22
23 23
24 static void ngx_open_file_cache_cleanup(void *data); 24 static void ngx_open_file_cache_cleanup(void *data);
25 #if (NGX_HAVE_OPENAT)
26 static ngx_fd_t ngx_openat_file_owner(ngx_fd_t at_fd, const u_char *name,
27 ngx_int_t mode, ngx_int_t create, ngx_int_t access);
28 #endif
29 static ngx_fd_t ngx_open_file_wrapper(ngx_str_t *name,
30 ngx_open_file_info_t *of, ngx_int_t mode, ngx_int_t create,
31 ngx_int_t access);
32 static ngx_int_t ngx_file_info_wrapper(ngx_str_t *name,
33 ngx_open_file_info_t *of, ngx_file_info_t *fi);
25 static ngx_int_t ngx_open_and_stat_file(ngx_str_t *name, 34 static ngx_int_t ngx_open_and_stat_file(ngx_str_t *name,
26 ngx_open_file_info_t *of, ngx_log_t *log); 35 ngx_open_file_info_t *of, ngx_log_t *log);
27 static void ngx_open_file_add_event(ngx_open_file_cache_t *cache, 36 static void ngx_open_file_add_event(ngx_open_file_cache_t *cache,
28 ngx_cached_open_file_t *file, ngx_open_file_info_t *of, ngx_log_t *log); 37 ngx_cached_open_file_t *file, ngx_open_file_info_t *of, ngx_log_t *log);
29 static void ngx_open_file_cleanup(void *data); 38 static void ngx_open_file_cleanup(void *data);
145 154
146 if (cache == NULL) { 155 if (cache == NULL) {
147 156
148 if (of->test_only) { 157 if (of->test_only) {
149 158
150 if (ngx_file_info(name->data, &fi) == NGX_FILE_ERROR) { 159 if (ngx_file_info_wrapper(name, of, &fi) == NGX_FILE_ERROR) {
151 of->err = ngx_errno;
152 of->failed = ngx_file_info_n;
153 return NGX_ERROR; 160 return NGX_ERROR;
154 } 161 }
155 162
156 of->uniq = ngx_file_uniq(&fi); 163 of->uniq = ngx_file_uniq(&fi);
157 of->mtime = ngx_file_mtime(&fi); 164 of->mtime = ngx_file_mtime(&fi);
215 } 222 }
216 223
217 if (file->use_event 224 if (file->use_event
218 || (file->event == NULL 225 || (file->event == NULL
219 && (of->uniq == 0 || of->uniq == file->uniq) 226 && (of->uniq == 0 || of->uniq == file->uniq)
220 && now - file->created < of->valid)) 227 && now - file->created < of->valid
228 #if (NGX_HAVE_OPENAT)
229 && of->disable_symlinks == file->disable_symlinks
230 #endif
231 ))
221 { 232 {
222 if (file->err == 0) { 233 if (file->err == 0) {
223 234
224 of->fd = file->fd; 235 of->fd = file->fd;
225 of->uniq = file->uniq; 236 of->uniq = file->uniq;
237 ngx_open_file_add_event(cache, file, of, pool->log); 248 ngx_open_file_add_event(cache, file, of, pool->log);
238 } 249 }
239 250
240 } else { 251 } else {
241 of->err = file->err; 252 of->err = file->err;
253 #if (NGX_HAVE_OPENAT)
254 of->failed = file->disable_symlinks ? ngx_openat_file_n
255 : ngx_open_file_n;
256 #else
242 of->failed = ngx_open_file_n; 257 of->failed = ngx_open_file_n;
258 #endif
243 } 259 }
244 260
245 goto found; 261 goto found;
246 } 262 }
247 263
373 389
374 update: 390 update:
375 391
376 file->fd = of->fd; 392 file->fd = of->fd;
377 file->err = of->err; 393 file->err = of->err;
394 #if (NGX_HAVE_OPENAT)
395 file->disable_symlinks = of->disable_symlinks;
396 #endif
378 397
379 if (of->err == 0) { 398 if (of->err == 0) {
380 file->uniq = of->uniq; 399 file->uniq = of->uniq;
381 file->mtime = of->mtime; 400 file->mtime = of->mtime;
382 file->size = of->size; 401 file->size = of->size;
457 476
458 return NGX_ERROR; 477 return NGX_ERROR;
459 } 478 }
460 479
461 480
481 #if (NGX_HAVE_OPENAT)
482
483 static ngx_fd_t
484 ngx_openat_file_owner(ngx_fd_t at_fd, const u_char *name,
485 ngx_int_t mode, ngx_int_t create, ngx_int_t access)
486 {
487 ngx_fd_t fd;
488 ngx_file_info_t fi, atfi;
489
490 /*
491 * To allow symlinks with the same owner, use openat() (followed
492 * by fstat()) and fstatat(AT_SYMLINK_NOFOLLOW), and then compare
493 * uids between fstat() and fstatat().
494 *
495 * As there is a race between openat() and fstatat() we don't
496 * know if openat() in fact opened symlink or not. Therefore,
497 * we have to compare uids even if fstatat() reports the opened
498 * component isn't a symlink (as we don't know whether it was
499 * symlink during openat() or not).
500 */
501
502 fd = ngx_openat_file(at_fd, name, mode, create, access);
503
504 if (fd == NGX_FILE_ERROR) {
505 return NGX_FILE_ERROR;
506 }
507
508 if (ngx_file_at_info(at_fd, name, &atfi, AT_SYMLINK_NOFOLLOW)
509 == NGX_FILE_ERROR)
510 {
511 ngx_close_file(fd);
512 return NGX_FILE_ERROR;
513 }
514
515 if (ngx_fd_info(fd, &fi) == NGX_FILE_ERROR) {
516 ngx_close_file(fd);
517 return NGX_FILE_ERROR;
518 }
519
520 if (fi.st_uid != atfi.st_uid) {
521 ngx_close_file(fd);
522 ngx_set_errno(NGX_ELOOP);
523 return NGX_FILE_ERROR;
524 }
525
526 return fd;
527 }
528
529 #endif
530
531
532 static ngx_fd_t
533 ngx_open_file_wrapper(ngx_str_t *name, ngx_open_file_info_t *of,
534 ngx_int_t mode, ngx_int_t create, ngx_int_t access)
535 {
536 ngx_fd_t fd;
537
538 #if !(NGX_HAVE_OPENAT)
539
540 fd = ngx_open_file(name->data, mode, create, access);
541
542 if (fd == NGX_FILE_ERROR) {
543 of->err = ngx_errno;
544 of->failed = ngx_open_file_n;
545 return NGX_FILE_ERROR;
546 }
547
548 return fd;
549
550 #else
551
552 u_char *p, *cp, *end;
553 ngx_fd_t at_fd;
554
555 if (of->disable_symlinks == NGX_DISABLE_SYMLINKS_OFF) {
556 fd = ngx_open_file(name->data, mode, create, access);
557
558 if (fd == NGX_FILE_ERROR) {
559 of->err = ngx_errno;
560 of->failed = ngx_open_file_n;
561 return NGX_FILE_ERROR;
562 }
563
564 return fd;
565 }
566
567 at_fd = ngx_openat_file(AT_FDCWD, "/", NGX_FILE_RDONLY|NGX_FILE_NONBLOCK,
568 NGX_FILE_OPEN, 0);
569
570 if (at_fd == NGX_FILE_ERROR) {
571 of->err = ngx_errno;
572 of->failed = ngx_openat_file_n;
573 return NGX_FILE_ERROR;
574 }
575
576 end = name->data + name->len;
577 p = name->data + 1;
578
579 for ( ;; ) {
580 cp = ngx_strlchr(p, end, '/');
581 if (cp == NULL) {
582 break;
583 }
584
585 *cp = '\0';
586
587 if (of->disable_symlinks == NGX_DISABLE_SYMLINKS_NOTOWNER) {
588 fd = ngx_openat_file_owner(at_fd, p,
589 NGX_FILE_RDONLY|NGX_FILE_NONBLOCK,
590 NGX_FILE_OPEN, 0);
591
592 } else {
593 fd = ngx_openat_file(at_fd, p,
594 NGX_FILE_RDONLY|NGX_FILE_NONBLOCK|NGX_FILE_NOFOLLOW,
595 NGX_FILE_OPEN, 0);
596 }
597
598 *cp = '/';
599
600 ngx_close_file(at_fd);
601
602 if (fd == NGX_FILE_ERROR) {
603 of->err = ngx_errno;
604 of->failed = ngx_openat_file_n;
605 return NGX_FILE_ERROR;
606 }
607
608 p = cp + 1;
609 at_fd = fd;
610 }
611
612 if (of->disable_symlinks == NGX_DISABLE_SYMLINKS_NOTOWNER) {
613 fd = ngx_openat_file_owner(at_fd, p, mode, create, access);
614
615 } else {
616 fd = ngx_openat_file(at_fd, p, mode|NGX_FILE_NOFOLLOW, create, access);
617 }
618
619 if (fd == NGX_FILE_ERROR) {
620 of->err = ngx_errno;
621 of->failed = ngx_openat_file_n;
622 }
623
624 ngx_close_file(at_fd);
625
626 return fd;
627 #endif
628 }
629
630
631 static ngx_int_t
632 ngx_file_info_wrapper(ngx_str_t *name, ngx_open_file_info_t *of,
633 ngx_file_info_t *fi)
634 {
635 ngx_int_t rc;
636
637 #if !(NGX_HAVE_OPENAT)
638
639 rc = ngx_file_info(name->data, fi);
640
641 if (rc == NGX_FILE_ERROR) {
642 of->err = ngx_errno;
643 of->failed = ngx_file_info_n;
644 return NGX_FILE_ERROR;
645 }
646
647 return rc;
648
649 #else
650
651 ngx_fd_t fd;
652
653 if (of->disable_symlinks == NGX_DISABLE_SYMLINKS_OFF) {
654
655 rc = ngx_file_info(name->data, fi);
656
657 if (rc == NGX_FILE_ERROR) {
658 of->err = ngx_errno;
659 of->failed = ngx_file_info_n;
660 return NGX_FILE_ERROR;
661 }
662
663 return rc;
664 }
665
666 fd = ngx_open_file_wrapper(name, of, NGX_FILE_RDONLY|NGX_FILE_NONBLOCK,
667 NGX_FILE_OPEN, 0);
668
669 if (fd == NGX_FILE_ERROR) {
670 return NGX_FILE_ERROR;
671 }
672
673 if (ngx_fd_info(fd, fi) == NGX_FILE_ERROR) {
674 of->err = ngx_errno;
675 of->failed = ngx_fd_info_n;
676 ngx_close_file(fd);
677 return NGX_FILE_ERROR;
678 }
679
680 ngx_close_file(fd);
681
682 return NGX_OK;
683 #endif
684 }
685
686
462 static ngx_int_t 687 static ngx_int_t
463 ngx_open_and_stat_file(ngx_str_t *name, ngx_open_file_info_t *of, 688 ngx_open_and_stat_file(ngx_str_t *name, ngx_open_file_info_t *of,
464 ngx_log_t *log) 689 ngx_log_t *log)
465 { 690 {
466 ngx_fd_t fd; 691 ngx_fd_t fd;
467 ngx_file_info_t fi; 692 ngx_file_info_t fi;
468 693
469 if (of->fd != NGX_INVALID_FILE) { 694 if (of->fd != NGX_INVALID_FILE) {
470 695
471 if (ngx_file_info(name->data, &fi) == NGX_FILE_ERROR) { 696 if (ngx_file_info_wrapper(name, of, &fi) == NGX_FILE_ERROR) {
472 of->failed = ngx_file_info_n; 697 of->fd = NGX_INVALID_FILE;
473 goto failed; 698 return NGX_ERROR;
474 } 699 }
475 700
476 if (of->uniq == ngx_file_uniq(&fi)) { 701 if (of->uniq == ngx_file_uniq(&fi)) {
477 goto done; 702 goto done;
478 } 703 }
479 704
480 } else if (of->test_dir) { 705 } else if (of->test_dir) {
481 706
482 if (ngx_file_info(name->data, &fi) == NGX_FILE_ERROR) { 707 if (ngx_file_info_wrapper(name, of, &fi) == NGX_FILE_ERROR) {
483 of->failed = ngx_file_info_n; 708 of->fd = NGX_INVALID_FILE;
484 goto failed; 709 return NGX_ERROR;
485 } 710 }
486 711
487 if (ngx_is_dir(&fi)) { 712 if (ngx_is_dir(&fi)) {
488 goto done; 713 goto done;
489 } 714 }
494 /* 719 /*
495 * Use non-blocking open() not to hang on FIFO files, etc. 720 * Use non-blocking open() not to hang on FIFO files, etc.
496 * This flag has no effect on a regular files. 721 * This flag has no effect on a regular files.
497 */ 722 */
498 723
499 fd = ngx_open_file(name->data, NGX_FILE_RDONLY|NGX_FILE_NONBLOCK, 724 fd = ngx_open_file_wrapper(name, of, NGX_FILE_RDONLY|NGX_FILE_NONBLOCK,
500 NGX_FILE_OPEN, 0); 725 NGX_FILE_OPEN, 0);
501 726
502 } else { 727 } else {
503 fd = ngx_open_file(name->data, NGX_FILE_APPEND, 728 fd = ngx_open_file_wrapper(name, of, NGX_FILE_APPEND,
504 NGX_FILE_CREATE_OR_OPEN, 729 NGX_FILE_CREATE_OR_OPEN,
505 NGX_FILE_DEFAULT_ACCESS); 730 NGX_FILE_DEFAULT_ACCESS);
506 } 731 }
507 732
508 if (fd == NGX_INVALID_FILE) { 733 if (fd == NGX_INVALID_FILE) {
509 of->failed = ngx_open_file_n; 734 of->fd = NGX_INVALID_FILE;
510 goto failed; 735 return NGX_ERROR;
511 } 736 }
512 737
513 if (ngx_fd_info(fd, &fi) == NGX_FILE_ERROR) { 738 if (ngx_fd_info(fd, &fi) == NGX_FILE_ERROR) {
514 ngx_log_error(NGX_LOG_CRIT, log, ngx_errno, 739 ngx_log_error(NGX_LOG_CRIT, log, ngx_errno,
515 ngx_fd_info_n " \"%V\" failed", name); 740 ngx_fd_info_n " \"%V\" failed", name);
563 of->is_file = ngx_is_file(&fi); 788 of->is_file = ngx_is_file(&fi);
564 of->is_link = ngx_is_link(&fi); 789 of->is_link = ngx_is_link(&fi);
565 of->is_exec = ngx_is_exec(&fi); 790 of->is_exec = ngx_is_exec(&fi);
566 791
567 return NGX_OK; 792 return NGX_OK;
568
569 failed:
570
571 of->fd = NGX_INVALID_FILE;
572 of->err = ngx_errno;
573
574 return NGX_ERROR;
575 } 793 }
576 794
577 795
578 /* 796 /*
579 * we ignore any possible event setting error and 797 * we ignore any possible event setting error and