Klaus Demo nginx / bd1e719
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. Andrey Belov 10 years ago
5 changed file(s) with 294 addition(s) and 24 deletion(s). Raw diff Collapse all Expand all
9090
9191 void ngx_cpuinfo(void);
9292
93 #if (NGX_HAVE_OPENAT)
94 #define NGX_DISABLE_SYMLINKS_OFF 0
95 #define NGX_DISABLE_SYMLINKS_ON 1
96 #define NGX_DISABLE_SYMLINKS_NOTOWNER 2
97 #endif
9398
9499 #endif /* _NGX_CORE_H_INCLUDED_ */
2121
2222
2323 static void ngx_open_file_cache_cleanup(void *data);
24 #if (NGX_HAVE_OPENAT)
25 static ngx_fd_t ngx_openat_file_owner(ngx_fd_t at_fd, const u_char *name,
26 ngx_int_t mode, ngx_int_t create, ngx_int_t access);
27 #endif
28 static ngx_fd_t ngx_open_file_wrapper(ngx_str_t *name,
29 ngx_open_file_info_t *of, ngx_int_t mode, ngx_int_t create,
30 ngx_int_t access);
31 static ngx_int_t ngx_file_info_wrapper(ngx_str_t *name,
32 ngx_open_file_info_t *of, ngx_file_info_t *fi);
2433 static ngx_int_t ngx_open_and_stat_file(ngx_str_t *name,
2534 ngx_open_file_info_t *of, ngx_log_t *log);
2635 static void ngx_open_file_add_event(ngx_open_file_cache_t *cache,
146155
147156 if (of->test_only) {
148157
149 if (ngx_file_info(name->data, &fi) == NGX_FILE_ERROR) {
150 of->err = ngx_errno;
151 of->failed = ngx_file_info_n;
158 if (ngx_file_info_wrapper(name, of, &fi) == NGX_FILE_ERROR) {
152159 return NGX_ERROR;
153160 }
154161
216223 if (file->use_event
217224 || (file->event == NULL
218225 && (of->uniq == 0 || of->uniq == file->uniq)
219 && now - file->created < of->valid))
226 && now - file->created < of->valid
227 #if (NGX_HAVE_OPENAT)
228 && of->disable_symlinks == file->disable_symlinks
229 #endif
230 ))
220231 {
221232 if (file->err == 0) {
222233
238249
239250 } else {
240251 of->err = file->err;
252 #if (NGX_HAVE_OPENAT)
253 of->failed = file->disable_symlinks ? ngx_openat_file_n
254 : ngx_open_file_n;
255 #else
241256 of->failed = ngx_open_file_n;
257 #endif
242258 }
243259
244260 goto found;
374390
375391 file->fd = of->fd;
376392 file->err = of->err;
393 #if (NGX_HAVE_OPENAT)
394 file->disable_symlinks = of->disable_symlinks;
395 #endif
377396
378397 if (of->err == 0) {
379398 file->uniq = of->uniq;
458477 }
459478
460479
480 #if (NGX_HAVE_OPENAT)
481
482 static ngx_fd_t
483 ngx_openat_file_owner(ngx_fd_t at_fd, const u_char *name,
484 ngx_int_t mode, ngx_int_t create, ngx_int_t access)
485 {
486 ngx_fd_t fd;
487 ngx_file_info_t fi, atfi;
488
489 /*
490 * To allow symlinks with the same owner, use openat() (followed
491 * by fstat()) and fstatat(AT_SYMLINK_NOFOLLOW), and then compare
492 * uids between fstat() and fstatat().
493 *
494 * As there is a race between openat() and fstatat() we don't
495 * know if openat() in fact opened symlink or not. Therefore,
496 * we have to compare uids even if fstatat() reports the opened
497 * component isn't a symlink (as we don't know whether it was
498 * symlink during openat() or not).
499 */
500
501 fd = ngx_openat_file(at_fd, name, mode, create, access);
502
503 if (fd == NGX_FILE_ERROR) {
504 return NGX_FILE_ERROR;
505 }
506
507 if (ngx_file_at_info(at_fd, name, &atfi, AT_SYMLINK_NOFOLLOW)
508 == NGX_FILE_ERROR)
509 {
510 ngx_close_file(fd);
511 return NGX_FILE_ERROR;
512 }
513
514 if (ngx_fd_info(fd, &fi) == NGX_FILE_ERROR) {
515 ngx_close_file(fd);
516 return NGX_FILE_ERROR;
517 }
518
519 if (fi.st_uid != atfi.st_uid) {
520 ngx_close_file(fd);
521 ngx_set_errno(NGX_ELOOP);
522 return NGX_FILE_ERROR;
523 }
524
525 return fd;
526 }
527
528 #endif
529
530
531 static ngx_fd_t
532 ngx_open_file_wrapper(ngx_str_t *name, ngx_open_file_info_t *of,
533 ngx_int_t mode, ngx_int_t create, ngx_int_t access)
534 {
535 ngx_fd_t fd;
536
537 #if !(NGX_HAVE_OPENAT)
538
539 fd = ngx_open_file(name->data, mode, create, access);
540
541 if (fd == NGX_FILE_ERROR) {
542 of->err = ngx_errno;
543 of->failed = ngx_open_file_n;
544 return NGX_FILE_ERROR;
545 }
546
547 return fd;
548
549 #else
550
551 u_char *p, *cp, *end;
552 ngx_fd_t at_fd;
553
554 if (of->disable_symlinks == NGX_DISABLE_SYMLINKS_OFF) {
555 fd = ngx_open_file(name->data, mode, create, access);
556
557 if (fd == NGX_FILE_ERROR) {
558 of->err = ngx_errno;
559 of->failed = ngx_open_file_n;
560 return NGX_FILE_ERROR;
561 }
562
563 return fd;
564 }
565
566 at_fd = ngx_openat_file(AT_FDCWD, "/", NGX_FILE_RDONLY|NGX_FILE_NONBLOCK,
567 NGX_FILE_OPEN, 0);
568
569 if (at_fd == NGX_FILE_ERROR) {
570 of->err = ngx_errno;
571 of->failed = ngx_openat_file_n;
572 return NGX_FILE_ERROR;
573 }
574
575 end = name->data + name->len;
576 p = name->data + 1;
577
578 for ( ;; ) {
579 cp = ngx_strlchr(p, end, '/');
580 if (cp == NULL) {
581 break;
582 }
583
584 *cp = '\0';
585
586 if (of->disable_symlinks == NGX_DISABLE_SYMLINKS_NOTOWNER) {
587 fd = ngx_openat_file_owner(at_fd, p,
588 NGX_FILE_RDONLY|NGX_FILE_NONBLOCK,
589 NGX_FILE_OPEN, 0);
590
591 } else {
592 fd = ngx_openat_file(at_fd, p,
593 NGX_FILE_RDONLY|NGX_FILE_NONBLOCK|NGX_FILE_NOFOLLOW,
594 NGX_FILE_OPEN, 0);
595 }
596
597 *cp = '/';
598
599 ngx_close_file(at_fd);
600
601 if (fd == NGX_FILE_ERROR) {
602 of->err = ngx_errno;
603 of->failed = ngx_openat_file_n;
604 return NGX_FILE_ERROR;
605 }
606
607 p = cp + 1;
608 at_fd = fd;
609 }
610
611 if (of->disable_symlinks == NGX_DISABLE_SYMLINKS_NOTOWNER) {
612 fd = ngx_openat_file_owner(at_fd, p, mode, create, access);
613
614 } else {
615 fd = ngx_openat_file(at_fd, p, mode|NGX_FILE_NOFOLLOW, create, access);
616 }
617
618 if (fd == NGX_FILE_ERROR) {
619 of->err = ngx_errno;
620 of->failed = ngx_openat_file_n;
621 }
622
623 ngx_close_file(at_fd);
624
625 return fd;
626 #endif
627 }
628
629
630 static ngx_int_t
631 ngx_file_info_wrapper(ngx_str_t *name, ngx_open_file_info_t *of,
632 ngx_file_info_t *fi)
633 {
634 ngx_int_t rc;
635
636 #if !(NGX_HAVE_OPENAT)
637
638 rc = ngx_file_info(name->data, fi);
639
640 if (rc == NGX_FILE_ERROR) {
641 of->err = ngx_errno;
642 of->failed = ngx_file_info_n;
643 return NGX_FILE_ERROR;
644 }
645
646 return rc;
647
648 #else
649
650 ngx_fd_t fd;
651
652 if (of->disable_symlinks == NGX_DISABLE_SYMLINKS_OFF) {
653
654 rc = ngx_file_info(name->data, fi);
655
656 if (rc == NGX_FILE_ERROR) {
657 of->err = ngx_errno;
658 of->failed = ngx_file_info_n;
659 return NGX_FILE_ERROR;
660 }
661
662 return rc;
663 }
664
665 fd = ngx_open_file_wrapper(name, of, NGX_FILE_RDONLY|NGX_FILE_NONBLOCK,
666 NGX_FILE_OPEN, 0);
667
668 if (fd == NGX_FILE_ERROR) {
669 return NGX_FILE_ERROR;
670 }
671
672 if (ngx_fd_info(fd, fi) == NGX_FILE_ERROR) {
673 of->err = ngx_errno;
674 of->failed = ngx_fd_info_n;
675 ngx_close_file(fd);
676 return NGX_FILE_ERROR;
677 }
678
679 ngx_close_file(fd);
680
681 return NGX_OK;
682 #endif
683 }
684
685
461686 static ngx_int_t
462687 ngx_open_and_stat_file(ngx_str_t *name, ngx_open_file_info_t *of,
463688 ngx_log_t *log)
467692
468693 if (of->fd != NGX_INVALID_FILE) {
469694
470 if (ngx_file_info(name->data, &fi) == NGX_FILE_ERROR) {
471 of->failed = ngx_file_info_n;
472 goto failed;
695 if (ngx_file_info_wrapper(name, of, &fi) == NGX_FILE_ERROR) {
696 of->fd = NGX_INVALID_FILE;
697 return NGX_ERROR;
473698 }
474699
475700 if (of->uniq == ngx_file_uniq(&fi)) {
478703
479704 } else if (of->test_dir) {
480705
481 if (ngx_file_info(name->data, &fi) == NGX_FILE_ERROR) {
482 of->failed = ngx_file_info_n;
483 goto failed;
706 if (ngx_file_info_wrapper(name, of, &fi) == NGX_FILE_ERROR) {
707 of->fd = NGX_INVALID_FILE;
708 return NGX_ERROR;
484709 }
485710
486711 if (ngx_is_dir(&fi)) {
495720 * This flag has no effect on a regular files.
496721 */
497722
498 fd = ngx_open_file(name->data, NGX_FILE_RDONLY|NGX_FILE_NONBLOCK,
499 NGX_FILE_OPEN, 0);
723 fd = ngx_open_file_wrapper(name, of, NGX_FILE_RDONLY|NGX_FILE_NONBLOCK,
724 NGX_FILE_OPEN, 0);
500725
501726 } else {
502 fd = ngx_open_file(name->data, NGX_FILE_APPEND,
503 NGX_FILE_CREATE_OR_OPEN,
504 NGX_FILE_DEFAULT_ACCESS);
727 fd = ngx_open_file_wrapper(name, of, NGX_FILE_APPEND,
728 NGX_FILE_CREATE_OR_OPEN,
729 NGX_FILE_DEFAULT_ACCESS);
505730 }
506731
507732 if (fd == NGX_INVALID_FILE) {
508 of->failed = ngx_open_file_n;
509 goto failed;
733 of->fd = NGX_INVALID_FILE;
734 return NGX_ERROR;
510735 }
511736
512737 if (ngx_fd_info(fd, &fi) == NGX_FILE_ERROR) {
564789 of->is_exec = ngx_is_exec(&fi);
565790
566791 return NGX_OK;
567
568 failed:
569
570 of->fd = NGX_INVALID_FILE;
571 of->err = ngx_errno;
572
573 return NGX_ERROR;
574792 }
575793
576794
3131
3232 ngx_uint_t min_uses;
3333
34 #if (NGX_HAVE_OPENAT)
35 unsigned disable_symlinks:2;
36 #endif
37
3438 unsigned test_dir:1;
3539 unsigned test_only:1;
3640 unsigned log:1;
6266 ngx_err_t err;
6367
6468 uint32_t uses;
69
70 #if (NGX_HAVE_OPENAT)
71 unsigned disable_symlinks:2;
72 #endif
6573
6674 unsigned count:24;
6775 unsigned close:1;
186186 #endif
187187
188188
189 #if (NGX_HAVE_OPENAT)
190
191 static ngx_conf_enum_t ngx_http_core_disable_symlinks[] = {
192 { ngx_string("off"), NGX_DISABLE_SYMLINKS_OFF },
193 { ngx_string("if_not_owner"), NGX_DISABLE_SYMLINKS_NOTOWNER },
194 { ngx_string("on"), NGX_DISABLE_SYMLINKS_ON },
195 { ngx_null_string, 0 }
196 };
197
198 #endif
199
200
189201 static ngx_command_t ngx_http_core_commands[] = {
190202
191203 { ngx_string("variables_hash_max_size"),
760772 NGX_HTTP_LOC_CONF_OFFSET,
761773 0,
762774 NULL },
775
776 #endif
777
778 #if (NGX_HAVE_OPENAT)
779
780 { ngx_string("disable_symlinks"),
781 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
782 ngx_conf_set_enum_slot,
783 NGX_HTTP_LOC_CONF_OFFSET,
784 offsetof(ngx_http_core_loc_conf_t, disable_symlinks),
785 &ngx_http_core_disable_symlinks },
763786
764787 #endif
765788
12961319 of.test_only = 1;
12971320 of.errors = clcf->open_file_cache_errors;
12981321 of.events = clcf->open_file_cache_events;
1322 #if (NGX_HAVE_OPENAT)
1323 of.disable_symlinks = clcf->disable_symlinks;
1324 #endif
12991325
13001326 if (ngx_open_cached_file(clcf->open_file_cache, &path, &of, r->pool)
13011327 != NGX_OK)
33433369 #endif
33443370 #endif
33453371
3372 #if (NGX_HAVE_OPENAT)
3373 clcf->disable_symlinks = NGX_CONF_UNSET_UINT;
3374 #endif
3375
33463376 return clcf;
33473377 }
33483378
36223652 #endif
36233653 #endif
36243654
3655 #if (NGX_HAVE_OPENAT)
3656 ngx_conf_merge_uint_value(conf->disable_symlinks, prev->disable_symlinks,
3657 NGX_DISABLE_SYMLINKS_OFF);
3658 #endif
3659
36253660 return NGX_CONF_OK;
36263661 }
36273662
401401 #if (NGX_PCRE)
402402 ngx_array_t *gzip_disable; /* gzip_disable */
403403 #endif
404 #endif
405
406 #if (NGX_HAVE_OPENAT)
407 ngx_uint_t disable_symlinks; /* disable_symlinks */
404408 #endif
405409
406410 ngx_array_t *error_pages; /* error_page */