Klaus Demo nginx / b683a85
Merge of r4674, r4675, r4676: win32 fixes. *) Win32: disallowed access to various non-canonical name variants. This includes trailings dots and spaces, NTFS streams (and short names, as previously checked). The checks are now also done in ngx_file_info(), thus allowing to use the "try_files" directive to protect external scripts. *) Win32: normalization of trailing dot inside uri. Windows treats "/directory./" identical to "/directory/". Do the same when working on Windows. Note that the behaviour is different from one with last path component (where multiple spaces and dots are ignored by Windows). *) Win32: uris with ":$" are now rejected. There are too many problems with special NTFS streams, notably "::$data", "::$index_allocation" and ":$i30:$index_allocation". For now we don't reject all URIs with ":" like Apache does as there are no good reasons seen yet, and there are multiple programs using it in URLs (e.g. MediaWiki). Maxim Dounin 10 years ago
3 changed file(s) with 209 addition(s) and 42 deletion(s). Raw diff Collapse all Expand all
542542
543543 switch (ch) {
544544 case '/':
545 #if (NGX_WIN32)
546 if (r->uri_ext == p) {
547 r->complex_uri = 1;
548 state = sw_uri;
549 break;
550 }
551 #endif
545552 r->uri_ext = NULL;
546553 state = sw_after_slash_in_uri;
547554 break;
11161123 switch(ch) {
11171124 #if (NGX_WIN32)
11181125 case '\\':
1126 if (u - 2 >= r->uri.data
1127 && *(u - 1) == '.' && *(u - 2) != '.')
1128 {
1129 u--;
1130 }
1131
11191132 r->uri_ext = NULL;
11201133
11211134 if (p == r->uri_start + r->uri.len) {
11331146 break;
11341147 #endif
11351148 case '/':
1149 #if (NGX_WIN32)
1150 if (u - 2 >= r->uri.data
1151 && *(u - 1) == '.' && *(u - 2) != '.')
1152 {
1153 u--;
1154 }
1155 #endif
11361156 r->uri_ext = NULL;
11371157 state = sw_slash;
11381158 *u++ = ch;
811811
812812 #if (NGX_WIN32)
813813 {
814 u_char *p;
814 u_char *p, *last;
815
816 p = r->uri.data;
817 last = r->uri.data + r->uri.len;
818
819 while (p < last) {
820
821 if (*p++ == ':') {
822
823 /*
824 * this check covers "::$data", "::$index_allocation" and
825 * ":$i30:$index_allocation"
826 */
827
828 if (p < last && *p == '$') {
829 ngx_log_error(NGX_LOG_INFO, c->log, 0,
830 "client sent unsafe win32 URI");
831 ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
832 return;
833 }
834 }
835 }
815836
816837 p = r->uri.data + r->uri.len - 1;
817838
824845
825846 if (*p == '.') {
826847 p--;
827 continue;
828 }
829
830 if (ngx_strncasecmp(p - 6, (u_char *) "::$data", 7) == 0) {
831 p -= 7;
832848 continue;
833849 }
834850
1010
1111 #define NGX_UTF16_BUFLEN 256
1212
13 static ngx_int_t ngx_win32_check_filename(u_char *name, u_short *u,
14 size_t len);
1315 static u_short *ngx_utf8_to_utf16(u_short *utf16, u_char *utf8, size_t *len);
1416
1517
1921 ngx_open_file(u_char *name, u_long mode, u_long create, u_long access)
2022 {
2123 size_t len;
22 u_long n;
23 u_short *u, *lu;
24 u_short *u;
2425 ngx_fd_t fd;
2526 ngx_err_t err;
2627 u_short utf16[NGX_UTF16_BUFLEN];
3334 }
3435
3536 fd = INVALID_HANDLE_VALUE;
36 lu = NULL;
37
38 if (create == NGX_FILE_OPEN) {
39
40 lu = malloc(len * 2);
41 if (lu == NULL) {
42 goto failed;
43 }
44
45 n = GetLongPathNameW(u, lu, len);
46
47 if (n == 0) {
48 goto failed;
49 }
50
51 if (n != len - 1 || _wcsicmp(u, lu) != 0) {
52 ngx_set_errno(NGX_ENOENT);
53 goto failed;
54 }
37
38 if (create == NGX_FILE_OPEN
39 && ngx_win32_check_filename(name, u, len) != NGX_OK)
40 {
41 goto failed;
5542 }
5643
5744 fd = CreateFileW(u, mode,
6047
6148 failed:
6249
63 err = ngx_errno;
64
65 if (lu) {
66 ngx_free(lu);
67 }
68
6950 if (u != utf16) {
51 err = ngx_errno;
7052 ngx_free(u);
71 }
72
73 ngx_set_errno(err);
53 ngx_set_errno(err);
54 }
7455
7556 return fd;
7657 }
293274 return NGX_FILE_ERROR;
294275 }
295276
277 rc = NGX_FILE_ERROR;
278
279 if (ngx_win32_check_filename(file, u, len) != NGX_OK) {
280 goto failed;
281 }
282
296283 rc = GetFileAttributesExW(u, GetFileExInfoStandard, &fa);
297
298 if (u != utf16) {
299 err = ngx_errno;
300 ngx_free(u);
301 ngx_set_errno(err);
302 }
303284
304285 sb->dwFileAttributes = fa.dwFileAttributes;
305286 sb->ftCreationTime = fa.ftCreationTime;
307288 sb->ftLastWriteTime = fa.ftLastWriteTime;
308289 sb->nFileSizeHigh = fa.nFileSizeHigh;
309290 sb->nFileSizeLow = fa.nFileSizeLow;
291
292 failed:
293
294 if (u != utf16) {
295 err = ngx_errno;
296 ngx_free(u);
297 ngx_set_errno(err);
298 }
310299
311300 return rc;
312301 }
639628 }
640629
641630
631 static ngx_int_t
632 ngx_win32_check_filename(u_char *name, u_short *u, size_t len)
633 {
634 u_char *p, ch;
635 u_long n;
636 u_short *lu;
637 ngx_err_t err;
638 enum {
639 sw_start = 0,
640 sw_normal,
641 sw_after_slash,
642 sw_after_colon,
643 sw_after_dot
644 } state;
645
646 /* check for NTFS streams (":"), trailing dots and spaces */
647
648 lu = NULL;
649 state = sw_start;
650
651 for (p = name; *p; p++) {
652 ch = *p;
653
654 switch (state) {
655
656 case sw_start:
657
658 /*
659 * skip till first "/" to allow paths starting with drive and
660 * relative path, like "c:html/"
661 */
662
663 if (ch == '/' || ch == '\\') {
664 state = sw_after_slash;
665 }
666
667 break;
668
669 case sw_normal:
670
671 if (ch == ':') {
672 state = sw_after_colon;
673 break;
674 }
675
676 if (ch == '.' || ch == ' ') {
677 state = sw_after_dot;
678 break;
679 }
680
681 if (ch == '/' || ch == '\\') {
682 state = sw_after_slash;
683 break;
684 }
685
686 break;
687
688 case sw_after_slash:
689
690 if (ch == '/' || ch == '\\') {
691 break;
692 }
693
694 if (ch == '.') {
695 break;
696 }
697
698 if (ch == ':') {
699 state = sw_after_colon;
700 break;
701 }
702
703 state = sw_normal;
704 break;
705
706 case sw_after_colon:
707
708 if (ch == '/' || ch == '\\') {
709 state = sw_after_slash;
710 break;
711 }
712
713 goto invalid;
714
715 case sw_after_dot:
716
717 if (ch == '/' || ch == '\\') {
718 goto invalid;
719 }
720
721 if (ch == ':') {
722 goto invalid;
723 }
724
725 if (ch == '.' || ch == ' ') {
726 break;
727 }
728
729 state = sw_normal;
730 break;
731 }
732 }
733
734 if (state == sw_after_dot) {
735 goto invalid;
736 }
737
738 /* check if long name match */
739
740 lu = malloc(len * 2);
741 if (lu == NULL) {
742 return NGX_ERROR;
743 }
744
745 n = GetLongPathNameW(u, lu, len);
746
747 if (n == 0) {
748 goto failed;
749 }
750
751 if (n != len - 1 || _wcsicmp(u, lu) != 0) {
752 goto invalid;
753 }
754
755 return NGX_OK;
756
757 invalid:
758
759 ngx_set_errno(NGX_ENOENT);
760
761 failed:
762
763 if (lu) {
764 err = ngx_errno;
765 ngx_free(lu);
766 ngx_set_errno(err);
767 }
768
769 return NGX_ERROR;
770 }
771
772
642773 static u_short *
643774 ngx_utf8_to_utf16(u_short *utf16, u_char *utf8, size_t *len)
644775 {