diff options
| author | Denis Vlasenko <vda.linux@googlemail.com> | 2009-03-16 14:53:54 +0000 |
|---|---|---|
| committer | Denis Vlasenko <vda.linux@googlemail.com> | 2009-03-16 14:53:54 +0000 |
| commit | f2160b6a09f4e4879fb718526106c548d8f8ec23 (patch) | |
| tree | ff52e5b8dfb026571302023c39f925092a579f41 | |
| parent | 7a6766428e3306c3bdc98e0fff207e5015af8930 (diff) | |
| download | busybox-w32-f2160b6a09f4e4879fb718526106c548d8f8ec23.tar.gz busybox-w32-f2160b6a09f4e4879fb718526106c548d8f8ec23.tar.bz2 busybox-w32-f2160b6a09f4e4879fb718526106c548d8f8ec23.zip | |
ftpd: security tightened up:
PORT is not allowed on !IPv4
PORT must have IP == peer's IP
upload is allowed only into regular files
function old new delta
ftpd_main 1815 2019 +204
handle_upload_common 260 306 +46
------------------------------------------------------------------------------
(add/remove: 0/0 grow/shrink: 2/0 up/down: 250/0) Total: 250 bytes
| -rw-r--r-- | networking/ftpd.c | 196 |
1 files changed, 107 insertions, 89 deletions
diff --git a/networking/ftpd.c b/networking/ftpd.c index 37b340e78..22cec83a8 100644 --- a/networking/ftpd.c +++ b/networking/ftpd.c | |||
| @@ -66,9 +66,9 @@ | |||
| 66 | /* Convert a constant to 3-digit string, packed into uint32_t */ | 66 | /* Convert a constant to 3-digit string, packed into uint32_t */ |
| 67 | enum { | 67 | enum { |
| 68 | /* Shift for Nth decimal digit */ | 68 | /* Shift for Nth decimal digit */ |
| 69 | SHIFT0 = 16 * BB_LITTLE_ENDIAN + 8 * BB_BIG_ENDIAN, | ||
| 70 | SHIFT1 = 8 * BB_LITTLE_ENDIAN + 16 * BB_BIG_ENDIAN, | ||
| 71 | SHIFT2 = 0 * BB_LITTLE_ENDIAN + 24 * BB_BIG_ENDIAN, | 69 | SHIFT2 = 0 * BB_LITTLE_ENDIAN + 24 * BB_BIG_ENDIAN, |
| 70 | SHIFT1 = 8 * BB_LITTLE_ENDIAN + 16 * BB_BIG_ENDIAN, | ||
| 71 | SHIFT0 = 16 * BB_LITTLE_ENDIAN + 8 * BB_BIG_ENDIAN, | ||
| 72 | }; | 72 | }; |
| 73 | #define STRNUM32(s) (uint32_t)(0 \ | 73 | #define STRNUM32(s) (uint32_t)(0 \ |
| 74 | | (('0' + ((s) / 1 % 10)) << SHIFT0) \ | 74 | | (('0' + ((s) / 1 % 10)) << SHIFT0) \ |
| @@ -76,47 +76,6 @@ enum { | |||
| 76 | | (('0' + ((s) / 100 % 10)) << SHIFT2) \ | 76 | | (('0' + ((s) / 100 % 10)) << SHIFT2) \ |
| 77 | ) | 77 | ) |
| 78 | 78 | ||
| 79 | enum { | ||
| 80 | OPT_l = (1 << 0), | ||
| 81 | OPT_1 = (1 << 1), | ||
| 82 | OPT_v = (1 << 2), | ||
| 83 | OPT_S = (1 << 3), | ||
| 84 | OPT_w = (1 << 4), | ||
| 85 | |||
| 86 | #define mk_const4(a,b,c,d) (((a * 0x100 + b) * 0x100 + c) * 0x100 + d) | ||
| 87 | #define mk_const3(a,b,c) ((a * 0x100 + b) * 0x100 + c) | ||
| 88 | const_ALLO = mk_const4('A', 'L', 'L', 'O'), | ||
| 89 | const_APPE = mk_const4('A', 'P', 'P', 'E'), | ||
| 90 | const_CDUP = mk_const4('C', 'D', 'U', 'P'), | ||
| 91 | const_CWD = mk_const3('C', 'W', 'D'), | ||
| 92 | const_DELE = mk_const4('D', 'E', 'L', 'E'), | ||
| 93 | const_EPSV = mk_const4('E', 'P', 'S', 'V'), | ||
| 94 | const_HELP = mk_const4('H', 'E', 'L', 'P'), | ||
| 95 | const_LIST = mk_const4('L', 'I', 'S', 'T'), | ||
| 96 | const_MKD = mk_const3('M', 'K', 'D'), | ||
| 97 | const_MODE = mk_const4('M', 'O', 'D', 'E'), | ||
| 98 | const_NLST = mk_const4('N', 'L', 'S', 'T'), | ||
| 99 | const_NOOP = mk_const4('N', 'O', 'O', 'P'), | ||
| 100 | const_PASS = mk_const4('P', 'A', 'S', 'S'), | ||
| 101 | const_PASV = mk_const4('P', 'A', 'S', 'V'), | ||
| 102 | const_PORT = mk_const4('P', 'O', 'R', 'T'), | ||
| 103 | const_PWD = mk_const3('P', 'W', 'D'), | ||
| 104 | const_QUIT = mk_const4('Q', 'U', 'I', 'T'), | ||
| 105 | const_REST = mk_const4('R', 'E', 'S', 'T'), | ||
| 106 | const_RETR = mk_const4('R', 'E', 'T', 'R'), | ||
| 107 | const_RMD = mk_const3('R', 'M', 'D'), | ||
| 108 | const_RNFR = mk_const4('R', 'N', 'F', 'R'), | ||
| 109 | const_RNTO = mk_const4('R', 'N', 'T', 'O'), | ||
| 110 | const_SIZE = mk_const4('S', 'I', 'Z', 'E'), | ||
| 111 | const_STAT = mk_const4('S', 'T', 'A', 'T'), | ||
| 112 | const_STOR = mk_const4('S', 'T', 'O', 'R'), | ||
| 113 | const_STOU = mk_const4('S', 'T', 'O', 'U'), | ||
| 114 | const_STRU = mk_const4('S', 'T', 'R', 'U'), | ||
| 115 | const_SYST = mk_const4('S', 'Y', 'S', 'T'), | ||
| 116 | const_TYPE = mk_const4('T', 'Y', 'P', 'E'), | ||
| 117 | const_USER = mk_const4('U', 'S', 'E', 'R'), | ||
| 118 | }; | ||
| 119 | |||
| 120 | struct globals { | 79 | struct globals { |
| 121 | char *p_control_line_buf; | 80 | char *p_control_line_buf; |
| 122 | len_and_sockaddr *local_addr; | 81 | len_and_sockaddr *local_addr; |
| @@ -182,21 +141,16 @@ replace_char(char *str, char from, char to) | |||
| 182 | return p - str; | 141 | return p - str; |
| 183 | } | 142 | } |
| 184 | 143 | ||
| 144 | /* NB: status_str is char[4] packed into uint32_t */ | ||
| 185 | static void | 145 | static void |
| 186 | cmdio_write(uint32_t status_str, const char *str) | 146 | cmdio_write(uint32_t status_str, const char *str) |
| 187 | { | 147 | { |
| 188 | union { | ||
| 189 | char buf[4]; | ||
| 190 | uint32_t v32; | ||
| 191 | } u; | ||
| 192 | char *response; | 148 | char *response; |
| 193 | int len; | 149 | int len; |
| 194 | 150 | ||
| 195 | u.v32 = status_str; | ||
| 196 | |||
| 197 | /* FTP allegedly uses telnet protocol for command link. | 151 | /* FTP allegedly uses telnet protocol for command link. |
| 198 | * In telnet, 0xff is an escape char, and needs to be escaped: */ | 152 | * In telnet, 0xff is an escape char, and needs to be escaped: */ |
| 199 | response = escape_text(u.buf, str, (0xff << 8) + '\r'); | 153 | response = escape_text((char *) &status_str, str, (0xff << 8) + '\r'); |
| 200 | 154 | ||
| 201 | /* ?! does FTP send embedded LFs as NULs? wow */ | 155 | /* ?! does FTP send embedded LFs as NULs? wow */ |
| 202 | len = replace_char(response, '\n', '\0'); | 156 | len = replace_char(response, '\n', '\0'); |
| @@ -444,46 +398,58 @@ handle_epsv(void) | |||
| 444 | static void | 398 | static void |
| 445 | handle_port(void) | 399 | handle_port(void) |
| 446 | { | 400 | { |
| 447 | unsigned short port; | 401 | unsigned port, port_hi; |
| 448 | char *raw, *port_part; | 402 | char *raw, *comma; |
| 449 | len_and_sockaddr *lsa; | 403 | socklen_t peer_ipv4_len; |
| 404 | struct sockaddr_in peer_ipv4; | ||
| 405 | struct in_addr port_ipv4_sin_addr; | ||
| 450 | 406 | ||
| 451 | port_pasv_cleanup(); | 407 | port_pasv_cleanup(); |
| 452 | 408 | ||
| 453 | raw = G.ftp_arg; | 409 | raw = G.ftp_arg; |
| 454 | 410 | ||
| 455 | /* Buglets: | 411 | /* PORT command format makes sense only over IPv4 */ |
| 456 | * xatou16 will accept wrong input, | 412 | if (!raw || G.local_addr->u.sa.sa_family != AF_INET) { |
| 457 | * xatou16 will exit instead of generating error to peer | 413 | bail: |
| 458 | */ | 414 | cmdio_write_error(FTP_BADCMD); |
| 415 | return; | ||
| 416 | } | ||
| 459 | 417 | ||
| 460 | port_part = strrchr(raw, ','); | 418 | comma = strrchr(raw, ','); |
| 461 | if (port_part == NULL) | 419 | if (comma == NULL) |
| 420 | goto bail; | ||
| 421 | *comma = '\0'; | ||
| 422 | port = bb_strtou(&comma[1], NULL, 10); | ||
| 423 | if (errno || port > 0xff) | ||
| 462 | goto bail; | 424 | goto bail; |
| 463 | port = xatou16(&port_part[1]); | ||
| 464 | *port_part = '\0'; | ||
| 465 | 425 | ||
| 466 | port_part = strrchr(raw, ','); | 426 | comma = strrchr(raw, ','); |
| 467 | if (port_part == NULL) | 427 | if (comma == NULL) |
| 468 | goto bail; | 428 | goto bail; |
| 469 | port |= xatou16(&port_part[1]) << 8; | 429 | *comma = '\0'; |
| 470 | *port_part = '\0'; | 430 | port_hi = bb_strtou(&comma[1], NULL, 10); |
| 431 | if (errno || port_hi > 0xff) | ||
| 432 | goto bail; | ||
| 433 | port |= port_hi << 8; | ||
| 471 | 434 | ||
| 472 | replace_char(raw, ',', '.'); | 435 | replace_char(raw, ',', '.'); |
| 473 | lsa = xdotted2sockaddr(raw, port); | ||
| 474 | 436 | ||
| 475 | if (lsa == NULL) { | 437 | /* We are verifying that PORT's IP matches getpeername(). |
| 476 | bail: | 438 | * Otherwise peer can make us open data connections |
| 477 | cmdio_write_error(FTP_BADCMD); | 439 | * to other hosts (security problem!) |
| 478 | return; | 440 | * This code would be too simplistic: |
| 479 | } | 441 | * lsa = xdotted2sockaddr(raw, port); |
| 480 | 442 | * if (lsa == NULL) goto bail; | |
| 481 | /* Should we verify that lsa matches getpeername(STDIN)? | 443 | */ |
| 482 | * Otherwise peer can make us open data connections | 444 | if (!inet_aton(raw, &port_ipv4_sin_addr)) |
| 483 | * to other hosts (security problem!) | 445 | goto bail; |
| 484 | */ | 446 | peer_ipv4_len = sizeof(peer_ipv4); |
| 447 | if (getpeername(STDIN_FILENO, &peer_ipv4, &peer_ipv4_len) != 0) | ||
| 448 | goto bail; | ||
| 449 | if (memcmp(&port_ipv4_sin_addr, &peer_ipv4.sin_addr, sizeof(struct in_addr)) != 0) | ||
| 450 | goto bail; | ||
| 485 | 451 | ||
| 486 | G.port_addr = lsa; | 452 | G.port_addr = xdotted2sockaddr(raw, port); |
| 487 | cmdio_write_ok(FTP_PORTOK); | 453 | cmdio_write_ok(FTP_PORTOK); |
| 488 | } | 454 | } |
| 489 | 455 | ||
| @@ -612,6 +578,8 @@ handle_dir_common(int opts) | |||
| 612 | if (!(opts & USE_CTRL_CONN) && !port_or_pasv_was_seen()) | 578 | if (!(opts & USE_CTRL_CONN) && !port_or_pasv_was_seen()) |
| 613 | return; /* port_or_pasv_was_seen emitted error response */ | 579 | return; /* port_or_pasv_was_seen emitted error response */ |
| 614 | 580 | ||
| 581 | /* -n prevents user/groupname display, | ||
| 582 | * which can be problematic in chroot */ | ||
| 615 | ls_fd = popen_ls((opts & LONG_LISTING) ? "-l" : "-1"); | 583 | ls_fd = popen_ls((opts & LONG_LISTING) ? "-l" : "-1"); |
| 616 | ls_fp = fdopen(ls_fd, "r"); | 584 | ls_fp = fdopen(ls_fd, "r"); |
| 617 | if (!ls_fp) /* never happens. paranoia */ | 585 | if (!ls_fp) /* never happens. paranoia */ |
| @@ -749,11 +717,12 @@ handle_rnto(void) | |||
| 749 | static void | 717 | static void |
| 750 | handle_upload_common(int is_append, int is_unique) | 718 | handle_upload_common(int is_append, int is_unique) |
| 751 | { | 719 | { |
| 752 | char *tempname = NULL; | 720 | struct stat statbuf; |
| 721 | char *tempname; | ||
| 753 | off_t bytes_transferred; | 722 | off_t bytes_transferred; |
| 723 | off_t offset; | ||
| 754 | int local_file_fd; | 724 | int local_file_fd; |
| 755 | int remote_fd; | 725 | int remote_fd; |
| 756 | off_t offset; | ||
| 757 | 726 | ||
| 758 | offset = G.restart_pos; | 727 | offset = G.restart_pos; |
| 759 | G.restart_pos = 0; | 728 | G.restart_pos = 0; |
| @@ -761,6 +730,7 @@ handle_upload_common(int is_append, int is_unique) | |||
| 761 | if (!port_or_pasv_was_seen()) | 730 | if (!port_or_pasv_was_seen()) |
| 762 | return; /* port_or_pasv_was_seen emitted error response */ | 731 | return; /* port_or_pasv_was_seen emitted error response */ |
| 763 | 732 | ||
| 733 | tempname = NULL; | ||
| 764 | local_file_fd = -1; | 734 | local_file_fd = -1; |
| 765 | if (is_unique) { | 735 | if (is_unique) { |
| 766 | tempname = xstrdup(" FILE: uniq.XXXXXX"); | 736 | tempname = xstrdup(" FILE: uniq.XXXXXX"); |
| @@ -773,13 +743,17 @@ handle_upload_common(int is_append, int is_unique) | |||
| 773 | flags = O_WRONLY | O_CREAT; | 743 | flags = O_WRONLY | O_CREAT; |
| 774 | local_file_fd = open(G.ftp_arg, flags, 0666); | 744 | local_file_fd = open(G.ftp_arg, flags, 0666); |
| 775 | } | 745 | } |
| 776 | if (local_file_fd < 0) { | 746 | |
| 747 | if (local_file_fd < 0 | ||
| 748 | || fstat(local_file_fd, &statbuf) != 0 | ||
| 749 | || !S_ISREG(statbuf.st_mode) | ||
| 750 | ) { | ||
| 777 | cmdio_write_error(FTP_UPLOADFAIL); | 751 | cmdio_write_error(FTP_UPLOADFAIL); |
| 752 | if (local_file_fd >= 0) | ||
| 753 | goto close_local_and_bail; | ||
| 778 | return; | 754 | return; |
| 779 | } | 755 | } |
| 780 | 756 | ||
| 781 | /* TODO: paranoia: fstat it, refuse to do anything if it's not a regular file */ | ||
| 782 | |||
| 783 | if (offset) | 757 | if (offset) |
| 784 | xlseek(local_file_fd, offset, SEEK_SET); | 758 | xlseek(local_file_fd, offset, SEEK_SET); |
| 785 | 759 | ||
| @@ -787,7 +761,7 @@ handle_upload_common(int is_append, int is_unique) | |||
| 787 | free(tempname); | 761 | free(tempname); |
| 788 | 762 | ||
| 789 | if (remote_fd < 0) | 763 | if (remote_fd < 0) |
| 790 | goto bail; | 764 | goto close_local_and_bail; |
| 791 | 765 | ||
| 792 | /* TODO: if we'll implement timeout, this will need more clever handling. | 766 | /* TODO: if we'll implement timeout, this will need more clever handling. |
| 793 | * Perhaps alarm(N) + checking that current position on local_file_fd | 767 | * Perhaps alarm(N) + checking that current position on local_file_fd |
| @@ -800,7 +774,8 @@ handle_upload_common(int is_append, int is_unique) | |||
| 800 | cmdio_write_error(FTP_BADSENDFILE); | 774 | cmdio_write_error(FTP_BADSENDFILE); |
| 801 | else | 775 | else |
| 802 | cmdio_write_ok(FTP_TRANSFEROK); | 776 | cmdio_write_ok(FTP_TRANSFEROK); |
| 803 | bail: | 777 | |
| 778 | close_local_and_bail: | ||
| 804 | close(local_file_fd); | 779 | close(local_file_fd); |
| 805 | } | 780 | } |
| 806 | 781 | ||
| @@ -838,17 +813,18 @@ cmdio_get_cmd_and_arg(void) | |||
| 838 | if (!cmd) | 813 | if (!cmd) |
| 839 | exit(0); | 814 | exit(0); |
| 840 | 815 | ||
| 816 | /* Trailing '\n' is already stripped, strip '\r' */ | ||
| 841 | len = strlen(cmd) - 1; | 817 | len = strlen(cmd) - 1; |
| 842 | while (len >= 0 && cmd[len] == '\r') { | 818 | while ((ssize_t)len >= 0 && cmd[len] == '\r') { |
| 843 | cmd[len] = '\0'; | 819 | cmd[len] = '\0'; |
| 844 | len--; | 820 | len--; |
| 845 | } | 821 | } |
| 846 | 822 | ||
| 847 | G.ftp_arg = strchr(cmd, ' '); | 823 | G.ftp_arg = strchr(cmd, ' '); |
| 848 | if (G.ftp_arg != NULL) { | 824 | if (G.ftp_arg != NULL) |
| 849 | *G.ftp_arg = '\0'; | 825 | *G.ftp_arg++ = '\0'; |
| 850 | G.ftp_arg++; | 826 | |
| 851 | } | 827 | /* Uppercase and pack into uint32_t first word of the command */ |
| 852 | cmdval = 0; | 828 | cmdval = 0; |
| 853 | while (*cmd) | 829 | while (*cmd) |
| 854 | cmdval = (cmdval << 8) + ((unsigned char)*cmd++ & (unsigned char)~0x20); | 830 | cmdval = (cmdval << 8) + ((unsigned char)*cmd++ & (unsigned char)~0x20); |
| @@ -856,6 +832,47 @@ cmdio_get_cmd_and_arg(void) | |||
| 856 | return cmdval; | 832 | return cmdval; |
| 857 | } | 833 | } |
| 858 | 834 | ||
| 835 | #define mk_const4(a,b,c,d) (((a * 0x100 + b) * 0x100 + c) * 0x100 + d) | ||
| 836 | #define mk_const3(a,b,c) ((a * 0x100 + b) * 0x100 + c) | ||
| 837 | enum { | ||
| 838 | const_ALLO = mk_const4('A', 'L', 'L', 'O'), | ||
| 839 | const_APPE = mk_const4('A', 'P', 'P', 'E'), | ||
| 840 | const_CDUP = mk_const4('C', 'D', 'U', 'P'), | ||
| 841 | const_CWD = mk_const3('C', 'W', 'D'), | ||
| 842 | const_DELE = mk_const4('D', 'E', 'L', 'E'), | ||
| 843 | const_EPSV = mk_const4('E', 'P', 'S', 'V'), | ||
| 844 | const_HELP = mk_const4('H', 'E', 'L', 'P'), | ||
| 845 | const_LIST = mk_const4('L', 'I', 'S', 'T'), | ||
| 846 | const_MKD = mk_const3('M', 'K', 'D'), | ||
| 847 | const_MODE = mk_const4('M', 'O', 'D', 'E'), | ||
| 848 | const_NLST = mk_const4('N', 'L', 'S', 'T'), | ||
| 849 | const_NOOP = mk_const4('N', 'O', 'O', 'P'), | ||
| 850 | const_PASS = mk_const4('P', 'A', 'S', 'S'), | ||
| 851 | const_PASV = mk_const4('P', 'A', 'S', 'V'), | ||
| 852 | const_PORT = mk_const4('P', 'O', 'R', 'T'), | ||
| 853 | const_PWD = mk_const3('P', 'W', 'D'), | ||
| 854 | const_QUIT = mk_const4('Q', 'U', 'I', 'T'), | ||
| 855 | const_REST = mk_const4('R', 'E', 'S', 'T'), | ||
| 856 | const_RETR = mk_const4('R', 'E', 'T', 'R'), | ||
| 857 | const_RMD = mk_const3('R', 'M', 'D'), | ||
| 858 | const_RNFR = mk_const4('R', 'N', 'F', 'R'), | ||
| 859 | const_RNTO = mk_const4('R', 'N', 'T', 'O'), | ||
| 860 | const_SIZE = mk_const4('S', 'I', 'Z', 'E'), | ||
| 861 | const_STAT = mk_const4('S', 'T', 'A', 'T'), | ||
| 862 | const_STOR = mk_const4('S', 'T', 'O', 'R'), | ||
| 863 | const_STOU = mk_const4('S', 'T', 'O', 'U'), | ||
| 864 | const_STRU = mk_const4('S', 'T', 'R', 'U'), | ||
| 865 | const_SYST = mk_const4('S', 'Y', 'S', 'T'), | ||
| 866 | const_TYPE = mk_const4('T', 'Y', 'P', 'E'), | ||
| 867 | const_USER = mk_const4('U', 'S', 'E', 'R'), | ||
| 868 | |||
| 869 | OPT_l = (1 << 0), | ||
| 870 | OPT_1 = (1 << 1), | ||
| 871 | OPT_v = (1 << 2), | ||
| 872 | OPT_S = (1 << 3), | ||
| 873 | OPT_w = (1 << 4), | ||
| 874 | }; | ||
| 875 | |||
| 859 | int ftpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; | 876 | int ftpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; |
| 860 | int ftpd_main(int argc, char **argv) | 877 | int ftpd_main(int argc, char **argv) |
| 861 | { | 878 | { |
| @@ -865,6 +882,7 @@ int ftpd_main(int argc, char **argv) | |||
| 865 | 882 | ||
| 866 | if (opts & (OPT_l|OPT_1)) { | 883 | if (opts & (OPT_l|OPT_1)) { |
| 867 | /* Our secret backdoor to ls */ | 884 | /* Our secret backdoor to ls */ |
| 885 | /* TODO: pass -n too? */ | ||
| 868 | xchdir(argv[2]); | 886 | xchdir(argv[2]); |
| 869 | argv[2] = (char*)"--"; | 887 | argv[2] = (char*)"--"; |
| 870 | return ls_main(argc, argv); | 888 | return ls_main(argc, argv); |
