1
0
mirror of https://github.com/InfrastructureServices/vsftpd.git synced 2025-04-19 01:24:02 +03:00

Updated to v3.0.0

This commit is contained in:
Dag Wieers 2012-04-05 00:00:00 +02:00
parent 6ab49f7a58
commit 8ab4684483
34 changed files with 1020 additions and 154 deletions

4
BUGS
View File

@ -13,10 +13,6 @@ complies here, almost all other FTPd's don't
directory name itself
- In ASCII mode, the SIZE command needs to take into account the size of
the file _after_ ASCII linefeed mangling?
- ASCII mode uploads: we're only supposed to remove \r if they preceed \n,
but I rip them out unconditionally.
- Security model: vsf_privop_get_ftp_port_sock() should probably do the
connect() to the remote location itself.
- If someone has one of the timeouts (command, data) setup, but not the other,
then timeout will behave whackily.

View File

@ -1274,3 +1274,52 @@ on chroot_local_user but such is life.
At this point: v2.3.5 released!
===============================
- Update vsf_findlibs.sh to work on Ubuntu 11.10+
- Make listen mode the default.
- Add -Werror to build flags.
- Fix missing "const" in ssl.c
- Add seccompsandbox.c to support a seccomp filter sandbox; works against Ubuntu
12.04 ABI.
- Rearrange ftppolicy.c a bit so the syscall list is easily comparable with
seccompsandbox.c
- Rename deprecated "sandbox" to "ptrace_sandbox".
- Add a few more state checks to the privileged helper processes.
- Add tunable "seccomp_sandbox", default on.
- Use hardened build flags. Distros of course override these and provide their
own build flags but no harm in showing how it could be done.
- Retry creating a PASV socket upon port reuse race between bind() and listen(),
patch from Ralph Wuerthner <ralph.wuerthner@de.ibm.com>.
- Don't die() if recv() indicates a closed remote connection. Problem report
on a Windows client from Herbert van den Bergh,
<herbert.van.den.bergh@oracle.com>.
- Add new config setting "allow_writeable_chroot" to help people in a bit of
a spot with the v2.3.5 defensive change. Only applies to non-anonymous.
- Remove a couple of fixed things from BUGS.
- strlen() trunction fix -- no particular impact.
- Apply some tidyups from mmoufid@yorku.ca.
(vsftpd-3.0.0-pre1)
- Fix delete_failed_uploads if there is a timeout. Report from Alejandro
Hernández Hdez <aalejandrohdez@gmail.com>.
- Fix other data channel bugs such as failure to log failure upon timeout.
- Use exit codes a bit more consistently.
- Fix bad interaction between SSL and trans_chunk_size.
- Redo data timeout to fire properly for SSL sessions.
- Redo idle timeout to fire properly for SSL sessions.
- Make sure PROT_EXEC isn't allowed, thanks to Will Drewry for noticing.
- Use 10 minutes as a max linger time just in case an alarm gets lost.
(vsftpd-3.0.0-pre2)
- Change PR_SET_NO_NEW_PRIVS define, from Kees Cook.
- Add AES128-SHA to default SSL cipher suites for FileZilla compatibility.
Unfortunately the default vsftpd SSL confiuration still doesn't fully work
with FileZilla, because FileZilla has a data connection security problem:
no client certificate presentation and no session reuse. At least the error
message is now very clear.
- Add restart_syscall to seccomp policy. Triggers reliably if you strace whilst
a data transfer is in progress.
- Fix delete_failed_uploads for anonymous sessions.
- Don't listen for urgent data if the control connection is SSL, due to possible
protocol synchronization issues.
At this point: v3.0.0 released!
===============================

View File

@ -3,10 +3,14 @@ CC = gcc
INSTALL = install
IFLAGS = -idirafter dummyinc
#CFLAGS = -g
CFLAGS = -O2 -Wall -W -Wshadow #-pedantic -Werror -Wconversion
CFLAGS = -O2 -fPIE -fstack-protector --param=ssp-buffer-size=4 \
-Wall -W -Wshadow -Werror -Wformat-security \
-D_FORTIFY_SOURCE=2 \
#-pedantic -Wconversion
LIBS = `./vsf_findlibs.sh`
LINK = -Wl,-s
LDFLAGS = -fPIE -pie -Wl,-z,relro -Wl,-z,now
OBJS = main.o utility.o prelogin.o ftpcmdio.o postlogin.o privsock.o \
tunables.o ftpdataio.o secbuf.o ls.o \
@ -14,14 +18,15 @@ OBJS = main.o utility.o prelogin.o ftpcmdio.o postlogin.o privsock.o \
banner.o filestr.o parseconf.o secutil.o \
ascii.o oneprocess.o twoprocess.o privops.o standalone.o hash.o \
tcpwrap.o ipaddrparse.o access.o features.o readwrite.o opts.o \
ssl.o sslslave.o ptracesandbox.o ftppolicy.o sysutil.o sysdeputil.o
ssl.o sslslave.o ptracesandbox.o ftppolicy.o sysutil.o sysdeputil.o \
seccompsandbox.o
.c.o:
$(CC) -c $*.c $(CFLAGS) $(IFLAGS)
vsftpd: $(OBJS)
$(CC) -o vsftpd $(OBJS) $(LINK) $(LIBS) $(LDFLAGS)
$(CC) -o vsftpd $(OBJS) $(LINK) $(LDFLAGS) $(LIBS)
install:
if [ -x /usr/local/sbin ]; then \

2
README
View File

@ -1,4 +1,4 @@
This is vsftpd, version 2.3.5
This is vsftpd, version 3.0.0
Author: Chris Evans
Contact: scarybeasts@gmail.com
Website: http://vsftpd.beasts.org/

5
TODO
View File

@ -1,9 +1,12 @@
CRITICAL
========
NOT SO CRITICAL
===============
- ABOR handling broken for SSL connections (does any client actually use it?)
- Add -Wconversion
- Don't leak SSL private key to compromised process? May be impossible. We'll
see, would be an interesting security story.
- Better reporting of failed uploads due to out of device space or quota all
@ -25,8 +28,6 @@ can result in a non-zero exit code.
- add example global bandwidth limiting.
- have a chown_uploads for non-anon users too; also more control over
permissions of uploaded file
- Fix SSL session timeout more gracefully. Currently, clients report an SSL read
error rather than an FTP status message saying timeout.
ON THE BACK BURNER
==================

View File

@ -21,12 +21,11 @@
#include "readwrite.h"
/* Internal functions */
static void control_getline(struct mystr* p_str, struct vsf_session* p_sess);
static int control_getline(struct mystr* p_str, struct vsf_session* p_sess);
static void ftp_write_text_common(struct vsf_session* p_sess, int status,
const char* p_text, int noblock, char sep);
const char* p_text, char sep);
static void ftp_write_str_common(struct vsf_session* p_sess, int status,
char sep, const struct mystr* p_str,
int noblock);
char sep, const struct mystr* p_str);
static void handle_alarm_timeout(void* p_private);
void
@ -41,20 +40,22 @@ static void
handle_alarm_timeout(void* p_private)
{
struct vsf_session* p_sess = (struct vsf_session*) p_private;
vsf_cmdio_write_exit(p_sess, FTP_IDLE_TIMEOUT, "Timeout.");
p_sess->idle_timeout = 1;
vsf_sysutil_activate_noblock(VSFTP_COMMAND_FD);
vsf_sysutil_shutdown_read_failok(VSFTP_COMMAND_FD);
}
void
vsf_cmdio_write(struct vsf_session* p_sess, int status, const char* p_text)
{
ftp_write_text_common(p_sess, status, p_text, 0, ' ');
ftp_write_text_common(p_sess, status, p_text, ' ');
}
void
vsf_cmdio_write_hyphen(struct vsf_session* p_sess, int status,
const char* p_text)
{
ftp_write_text_common(p_sess, status, p_text, 0, '-');
ftp_write_text_common(p_sess, status, p_text, '-');
}
void
@ -75,45 +76,47 @@ vsf_cmdio_write_raw(struct vsf_session* p_sess, const char* p_text)
}
void
vsf_cmdio_write_exit(struct vsf_session* p_sess, int status, const char* p_text)
vsf_cmdio_write_exit(struct vsf_session* p_sess, int status, const char* p_text,
int exit_val)
{
/* Unblock any readers on the dying control channel. This is needed for SSL
* connections, where the SSL control channel slave is in a separate
* process.
*/
vsf_sysutil_activate_noblock(VSFTP_COMMAND_FD);
vsf_sysutil_shutdown_read_failok(VSFTP_COMMAND_FD);
ftp_write_text_common(p_sess, status, p_text, 1, ' ');
vsf_cmdio_write(p_sess, status, p_text);
vsf_sysutil_shutdown_failok(VSFTP_COMMAND_FD);
vsf_sysutil_exit(0);
vsf_sysutil_exit(exit_val);
}
static void
ftp_write_text_common(struct vsf_session* p_sess, int status,
const char* p_text, int noblock, char sep)
const char* p_text, char sep)
{
/* XXX - could optimize */
static struct mystr s_the_str;
str_alloc_text(&s_the_str, p_text);
ftp_write_str_common(p_sess, status, sep, &s_the_str, noblock);
ftp_write_str_common(p_sess, status, sep, &s_the_str);
}
void
vsf_cmdio_write_str_hyphen(struct vsf_session* p_sess, int status,
const struct mystr* p_str)
{
ftp_write_str_common(p_sess, status, '-', p_str, 0);
ftp_write_str_common(p_sess, status, '-', p_str);
}
void
vsf_cmdio_write_str(struct vsf_session* p_sess, int status,
const struct mystr* p_str)
{
ftp_write_str_common(p_sess, status, ' ', p_str, 0);
ftp_write_str_common(p_sess, status, ' ', p_str);
}
static void
ftp_write_str_common(struct vsf_session* p_sess, int status, char sep,
const struct mystr* p_str, int noblock)
const struct mystr* p_str)
{
static struct mystr s_write_buf_str;
static struct mystr s_text_mangle_str;
@ -136,19 +139,11 @@ ftp_write_str_common(struct vsf_session* p_sess, int status, char sep,
str_append_char(&s_write_buf_str, sep);
str_append_str(&s_write_buf_str, &s_text_mangle_str);
str_append_text(&s_write_buf_str, "\r\n");
if (noblock)
{
vsf_sysutil_activate_noblock(VSFTP_COMMAND_FD);
}
retval = ftp_write_str(p_sess, &s_write_buf_str, kVSFRWControl);
if (retval != 0 && !noblock)
if (retval != 0)
{
die("ftp_write");
}
if (noblock)
{
vsf_sysutil_deactivate_noblock(VSFTP_COMMAND_FD);
}
}
void
@ -168,13 +163,26 @@ void
vsf_cmdio_get_cmd_and_arg(struct vsf_session* p_sess, struct mystr* p_cmd_str,
struct mystr* p_arg_str, int set_alarm)
{
int ret;
/* Prepare an alarm to timeout the session.. */
if (set_alarm)
{
vsf_cmdio_set_alarm(p_sess);
}
/* Blocks */
control_getline(p_cmd_str, p_sess);
ret = control_getline(p_cmd_str, p_sess);
if (p_sess->idle_timeout)
{
vsf_cmdio_write_exit(p_sess, FTP_IDLE_TIMEOUT, "Timeout.", 1);
}
if (ret == 0)
{
/* Remote end hung up without a polite QUIT. The shutdown is to make
* sure buggy clients don't ever see an OOPS message.
*/
vsf_sysutil_shutdown_failok(VSFTP_COMMAND_FD);
vsf_sysutil_exit(1);
}
/* View a single space as a command of " ", which although a useless command,
* permits the caller to distinguish input of "" from " ".
*/
@ -207,7 +215,7 @@ vsf_cmdio_get_cmd_and_arg(struct vsf_session* p_sess, struct mystr* p_cmd_str,
}
}
static void
static int
control_getline(struct mystr* p_str, struct vsf_session* p_sess)
{
int ret;
@ -216,9 +224,13 @@ control_getline(struct mystr* p_str, struct vsf_session* p_sess)
vsf_secbuf_alloc(&p_sess->p_control_line_buf, VSFTP_MAX_COMMAND_LINE);
}
ret = ftp_getline(p_sess, p_str, p_sess->p_control_line_buf);
if (ret < 0)
if (ret == 0)
{
vsf_cmdio_write_exit(p_sess, FTP_BADCMD, "Input line too long.");
return ret;
}
else if (ret < 0)
{
vsf_cmdio_write_exit(p_sess, FTP_BADCMD, "Input line too long.", 1);
}
/* As mandated by the FTP specifications.. */
str_replace_char(p_str, '\0', '\n');
@ -231,5 +243,6 @@ control_getline(struct mystr* p_str, struct vsf_session* p_sess)
--len;
}
}
return 1;
}

View File

@ -51,7 +51,7 @@ void vsf_cmdio_write_raw(struct vsf_session* p_sess, const char* p_text);
* write is _guaranteed_ to not block (ditching output if neccessary).
*/
void vsf_cmdio_write_exit(struct vsf_session* p_sess, int status,
const char* p_text);
const char* p_text, int exit_val);
/* vsf_cmdio_write_str()
* PURPOSE

View File

@ -61,12 +61,11 @@ vsf_ftpdataio_dispose_transfer_fd(struct vsf_session* p_sess)
{
bug("no data descriptor in vsf_ftpdataio_dispose_transfer_fd");
}
/* Reset the data connection alarm so it runs anew with the blocking close */
start_data_alarm(p_sess);
vsf_sysutil_uninstall_io_handler();
if (p_sess->data_use_ssl && p_sess->ssl_slave_active)
{
char result;
start_data_alarm(p_sess);
priv_sock_send_cmd(p_sess->ssl_consumer_fd, PRIV_SOCK_DO_SSL_CLOSE);
result = priv_sock_get_result(p_sess->ssl_consumer_fd);
if (result != PRIV_SOCK_RESULT_OK)
@ -76,8 +75,17 @@ vsf_ftpdataio_dispose_transfer_fd(struct vsf_session* p_sess)
}
else if (p_sess->p_data_ssl)
{
start_data_alarm(p_sess);
dispose_ret = ssl_data_close(p_sess);
}
if (!p_sess->abor_received && !p_sess->data_timeout && dispose_ret == 1)
{
/* If we didn't get a failure, linger on the close() in order to get more
* accurate transfer times.
*/
start_data_alarm(p_sess);
vsf_sysutil_activate_linger(p_sess->data_fd);
}
/* This close() blocks because we set SO_LINGER */
retval = vsf_sysutil_close_failok(p_sess->data_fd);
if (vsf_sysutil_retval_is_error(retval))
@ -86,11 +94,15 @@ vsf_ftpdataio_dispose_transfer_fd(struct vsf_session* p_sess)
vsf_sysutil_deactivate_linger_failok(p_sess->data_fd);
(void) vsf_sysutil_close_failok(p_sess->data_fd);
}
p_sess->data_fd = -1;
if (tunable_data_connection_timeout > 0)
{
vsf_sysutil_clear_alarm();
}
p_sess->data_fd = -1;
if (p_sess->abor_received || p_sess->data_timeout)
{
dispose_ret = 0;
}
return dispose_ret;
}
@ -188,11 +200,16 @@ handle_sigalrm(void* p_private)
struct vsf_session* p_sess = (struct vsf_session*) p_private;
if (!p_sess->data_progress)
{
vsf_cmdio_write_exit(p_sess, FTP_DATA_TIMEOUT,
"Data timeout. Reconnect. Sorry.");
p_sess->data_timeout = 1;
vsf_sysutil_shutdown_failok(p_sess->data_fd);
vsf_sysutil_shutdown_read_failok(VSFTP_COMMAND_FD);
vsf_sysutil_activate_noblock(VSFTP_COMMAND_FD);
}
else
{
p_sess->data_progress = 0;
start_data_alarm(p_sess);
}
p_sess->data_progress = 0;
start_data_alarm(p_sess);
}
void
@ -224,10 +241,6 @@ init_data_sock_params(struct vsf_session* p_sess, int sock_fd)
vsf_sysutil_activate_keepalive(sock_fd);
/* And in the vague hope it might help... */
vsf_sysutil_set_iptos_throughput(sock_fd);
/* Set up lingering, so that we wait for all data to transfer, and report
* more accurate transfer rates.
*/
vsf_sysutil_activate_linger(sock_fd);
/* Start the timeout monitor */
vsf_sysutil_install_io_handler(handle_io, p_sess);
start_data_alarm(p_sess);

View File

@ -41,41 +41,52 @@ policy_setup(struct pt_sandbox* p_sandbox, const struct vsf_session* p_sess)
*/
ptrace_sandbox_permit_read(p_sandbox);
ptrace_sandbox_permit_write(p_sandbox);
/* Reading FTP commands from the network. */
ptrace_sandbox_permit_recv(p_sandbox);
/* Querying time is harmless; used for log timestamps and internally to
* OpenSSL
*/
ptrace_sandbox_permit_query_time(p_sandbox);
/* Stat'ing is needed for downloading and directory listings. Can blanket
* enable for any filename thanks to the chroot().
*/
ptrace_sandbox_permit_file_stats(p_sandbox);
ptrace_sandbox_permit_fd_stats(p_sandbox);
/* Querying and changing directory. */
ptrace_sandbox_permit_getcwd(p_sandbox);
ptrace_sandbox_permit_chdir(p_sandbox);
/* Setting umask. */
ptrace_sandbox_permit_umask(p_sandbox);
/* Typically post-login things follow. */
/* Since we're in a chroot(), we can just blanket allow filesystem readonly
* open.
*/
ptrace_sandbox_permit_open(p_sandbox, 0);
ptrace_sandbox_permit_close(p_sandbox);
/* High-speed transfers... */
ptrace_sandbox_permit_sendfile(p_sandbox);
/* Reading directories. */
ptrace_sandbox_permit_getdents(p_sandbox);
/* Reading symlink targets. */
/* Other pathname-based metadata queries. */
ptrace_sandbox_permit_file_stats(p_sandbox);
ptrace_sandbox_permit_readlink(p_sandbox);
/* File locking. */
ptrace_sandbox_permit_fcntl(p_sandbox);
/* Seeking for REST. */
/* Querying, reading and changing directory. */
ptrace_sandbox_permit_getcwd(p_sandbox);
ptrace_sandbox_permit_chdir(p_sandbox);
ptrace_sandbox_permit_getdents(p_sandbox);
/* Simple fd-based operations. */
ptrace_sandbox_permit_fd_stats(p_sandbox);
ptrace_sandbox_permit_seek(p_sandbox);
ptrace_sandbox_permit_shutdown(p_sandbox);
ptrace_sandbox_permit_fcntl(p_sandbox);
ptrace_sandbox_permit_setsockopt(p_sandbox);
ptrace_sandbox_set_setsockopt_validator(p_sandbox, setsockopt_validator, 0);
/* Misc */
/* Setting umask. */
ptrace_sandbox_permit_umask(p_sandbox);
/* Select for data connection readyness. */
ptrace_sandbox_permit_select(p_sandbox);
/* Always need ability to take signals (SIGPIPE) */
ptrace_sandbox_permit_sigreturn(p_sandbox);
/* Sleeping (bandwidth limit, connect retires, anon login fails) */
ptrace_sandbox_permit_sleep(p_sandbox);
/* High-speed transfers... */
ptrace_sandbox_permit_sendfile(p_sandbox);
/* TODO - Grrrr! nscd cache access is leaking into child. Need to find out
* out how to disable that. Also means that text_userdb_names loads values
* from the real system data.
*/
if (tunable_text_userdb_names)
{
ptrace_sandbox_permit_mremap(p_sandbox);
}
/* May need ability to install signal handlers. */
if (tunable_async_abor_enable ||
tunable_idle_session_timeout > 0 ||
@ -88,17 +99,7 @@ policy_setup(struct pt_sandbox* p_sandbox, const struct vsf_session* p_sess)
{
ptrace_sandbox_permit_alarm(p_sandbox);
}
/* TODO - Grrrr! nscd cache access is leaking into child. Need to find out
* out how to disable that. Also means that text_userdb_names loads values
* from the real system data.
*/
if (tunable_text_userdb_names)
{
ptrace_sandbox_permit_mremap(p_sandbox);
}
/* Set up network permissions according to config and session. */
ptrace_sandbox_permit_recv(p_sandbox);
ptrace_sandbox_permit_shutdown(p_sandbox);
ptrace_sandbox_permit_socket(p_sandbox);
ptrace_sandbox_set_socket_validator(p_sandbox,
socket_validator,
@ -108,9 +109,6 @@ policy_setup(struct pt_sandbox* p_sandbox, const struct vsf_session* p_sess)
ptrace_sandbox_set_bind_validator(p_sandbox,
connect_validator,
(void*) p_sess);
ptrace_sandbox_permit_setsockopt(p_sandbox);
ptrace_sandbox_set_setsockopt_validator(p_sandbox, setsockopt_validator, 0);
ptrace_sandbox_permit_shutdown(p_sandbox);
if (tunable_port_enable)
{
ptrace_sandbox_permit_connect(p_sandbox);

2
main.c
View File

@ -38,7 +38,7 @@ main(int argc, const char* argv[])
struct vsf_session the_session =
{
/* Control connection */
0, 0, 0,
0, 0, 0, 0, 0,
/* Data connection */
-1, 0, -1, 0, 0, 0, 0,
/* Login */

View File

@ -29,6 +29,7 @@ str_netfd_alloc(struct vsf_session* p_sess,
unsigned int i;
char* p_readpos = p_readbuf;
unsigned int left = maxlen;
str_empty(p_str);
while (1)
{
if (p_readpos + left != p_readbuf + maxlen)
@ -38,7 +39,6 @@ str_netfd_alloc(struct vsf_session* p_sess,
/* Did we hit the max? */
if (left == 0)
{
str_empty(p_str);
return -1;
}
retval = (*p_peekfunc)(p_sess, p_readpos, left);
@ -48,7 +48,7 @@ str_netfd_alloc(struct vsf_session* p_sess,
}
else if (retval == 0)
{
die("vsf_sysutil_recv_peek: no data");
return 0;
}
bytes_read = (unsigned int) retval;
/* Search for the terminator */
@ -57,13 +57,14 @@ str_netfd_alloc(struct vsf_session* p_sess,
if (p_readpos[i] == term)
{
/* Got it! */
retval = (*p_readfunc)(p_sess, p_readpos, i + 1);
i++;
retval = (*p_readfunc)(p_sess, p_readpos, i);
if (vsf_sysutil_retval_is_error(retval) ||
(unsigned int) retval != i + 1)
(unsigned int) retval != i)
{
die("vsf_sysutil_read_loop");
}
if (p_readpos[i] != term)
if (p_readpos[i - 1] != term)
{
die("missing terminator in str_netfd_alloc");
}

View File

@ -28,7 +28,8 @@ typedef int (*str_netfd_read_t)(struct vsf_session*
* p_readfunc - a function called to read data from the network
* RETURNS
* -1 upon reaching max buffer length without seeing terminator, or the number
* of bytes read, _excluding_ the terminator.
* of bytes read, _including_ the terminator. 0 for an EOF on the socket.
* Does not return (exits) for a serious socket error.
*/
int str_netfd_alloc(struct vsf_session* p_sess,
struct mystr* p_str,

View File

@ -25,13 +25,14 @@
#include "sysutil.h"
#include "ptracesandbox.h"
#include "ftppolicy.h"
#include "seccompsandbox.h"
static void one_process_start(void* p_arg);
void
vsf_one_process_start(struct vsf_session* p_sess)
{
if (tunable_sandbox)
if (tunable_ptrace_sandbox)
{
struct pt_sandbox* p_sandbox = ptrace_sandbox_alloc();
if (p_sandbox == 0)
@ -97,10 +98,13 @@ one_process_start(void* p_arg)
str_free(&user_name);
str_free(&chdir_str);
}
if (tunable_sandbox)
if (tunable_ptrace_sandbox)
{
ptrace_sandbox_attach_point();
}
seccomp_sandbox_init();
seccomp_sandbox_setup_postlogin(p_sess);
seccomp_sandbox_lockdown();
init_connection(p_sess);
}

View File

@ -99,12 +99,14 @@ parseconf_bool_array[] =
{ "ssl_request_cert", &tunable_ssl_request_cert },
{ "delete_failed_uploads", &tunable_delete_failed_uploads },
{ "implicit_ssl", &tunable_implicit_ssl },
{ "sandbox", &tunable_sandbox },
{ "ptrace_sandbox", &tunable_ptrace_sandbox },
{ "require_ssl_reuse", &tunable_require_ssl_reuse },
{ "isolate", &tunable_isolate },
{ "isolate_network", &tunable_isolate_network },
{ "ftp_enable", &tunable_ftp_enable },
{ "http_enable", &tunable_http_enable },
{ "seccomp_sandbox", &tunable_seccomp_sandbox },
{ "allow_writeable_chroot", &tunable_allow_writeable_chroot },
{ 0, 0 }
};

View File

@ -100,7 +100,11 @@ process_post_login(struct vsf_session* p_sess)
bug("should not be reached");
}
if (tunable_async_abor_enable)
/* Don't support async ABOR if we have an SSL channel. The spec says SHOULD
* NOT, and I think there are synchronization issues between command and
* data reads.
*/
if (tunable_async_abor_enable && !p_sess->control_use_ssl)
{
vsf_sysutil_install_sighandler(kVSFSysUtilSigURG, handle_sigurg, p_sess, 0);
vsf_sysutil_activate_sigurg(VSFTP_COMMAND_FD);
@ -180,7 +184,7 @@ process_post_login(struct vsf_session* p_sess)
}
else if (str_equal_text(&p_sess->ftp_cmd_str, "QUIT"))
{
vsf_cmdio_write_exit(p_sess, FTP_GOODBYE, "Goodbye.");
vsf_cmdio_write_exit(p_sess, FTP_GOODBYE, "Goodbye.", 0);
}
else if (str_equal_text(&p_sess->ftp_cmd_str, "PWD") ||
str_equal_text(&p_sess->ftp_cmd_str, "XPWD"))
@ -443,6 +447,11 @@ process_post_login(struct vsf_session* p_sess)
{
vsf_log_do_log(p_sess, 0);
}
if (p_sess->data_timeout)
{
vsf_cmdio_write_exit(p_sess, FTP_DATA_TIMEOUT,
"Data timeout. Reconnect. Sorry.", 1);
}
}
}
@ -755,7 +764,11 @@ handle_retr(struct vsf_session* p_sess, int is_http)
}
else if (trans_ret.retval == -2)
{
vsf_cmdio_write(p_sess, FTP_BADSENDNET, "Failure writing network stream.");
if (!p_sess->data_timeout)
{
vsf_cmdio_write(p_sess, FTP_BADSENDNET,
"Failure writing network stream.");
}
}
else
{
@ -893,18 +906,22 @@ handle_dir_common(struct vsf_session* p_sess, int full_details, int stat_cmd)
{
vsf_cmdio_write(p_sess, FTP_STATFILE_OK, "End of status");
}
else if (retval != 0)
{
if (!p_sess->data_timeout)
{
vsf_cmdio_write(p_sess, FTP_BADSENDNET,
"Failure writing network stream.");
}
}
else if (p_dir == 0 || !dir_allow_read)
{
vsf_cmdio_write(p_sess, FTP_TRANSFEROK,
"Transfer done (but failed to open directory).");
}
else if (retval == 0)
{
vsf_cmdio_write(p_sess, FTP_TRANSFEROK, "Directory send OK.");
}
else
{
vsf_cmdio_write(p_sess, FTP_BADSENDNET, "Failure writing network stream.");
vsf_cmdio_write(p_sess, FTP_TRANSFEROK, "Directory send OK.");
}
check_abor(p_sess);
dir_close_out:
@ -1124,9 +1141,13 @@ handle_upload_common(struct vsf_session* p_sess, int is_append, int is_unique)
{
vsf_cmdio_write(p_sess, FTP_BADSENDFILE, "Failure writing to local file.");
}
else if (trans_ret.retval == -2 || p_sess->abor_received)
else if (trans_ret.retval == -2)
{
vsf_cmdio_write(p_sess, FTP_BADSENDNET, "Failure reading network stream.");
if (!p_sess->data_timeout)
{
vsf_cmdio_write(p_sess, FTP_BADSENDNET,
"Failure reading network stream.");
}
}
else
{

View File

@ -21,6 +21,7 @@
#include "secutil.h"
#include "sysstr.h"
#include "sysdeputil.h"
#include "seccompsandbox.h"
static void minimize_privilege(struct vsf_session* p_sess);
static void process_post_login_req(struct vsf_session* p_sess);
@ -110,6 +111,9 @@ minimize_privilege(struct vsf_session* p_sess)
str_free(&user_str);
str_free(&dir_str);
}
seccomp_sandbox_init();
seccomp_sandbox_setup_postlogin_broker();
seccomp_sandbox_lockdown();
}
static void

View File

@ -73,7 +73,7 @@ check_limits(struct vsf_session* p_sess)
str_alloc_text(&str_log_line, "Connection refused: too many sessions.");
vsf_log_line(p_sess, kVSFLogEntryConnection, &str_log_line);
vsf_cmdio_write_exit(p_sess, FTP_TOO_MANY_USERS,
"There are too many connected users, please try later.");
"There are too many connected users, please try later.", 1);
}
if (tunable_max_per_ip > 0 &&
p_sess->num_this_ip > tunable_max_per_ip)
@ -82,14 +82,14 @@ check_limits(struct vsf_session* p_sess)
"Connection refused: too many sessions for this address.");
vsf_log_line(p_sess, kVSFLogEntryConnection, &str_log_line);
vsf_cmdio_write_exit(p_sess, FTP_IP_LIMIT,
"There are too many connections from your internet address.");
"There are too many connections from your internet address.", 1);
}
if (!p_sess->tcp_wrapper_ok)
{
str_alloc_text(&str_log_line,
"Connection refused: tcp_wrappers denial.");
vsf_log_line(p_sess, kVSFLogEntryConnection, &str_log_line);
vsf_cmdio_write_exit(p_sess, FTP_IP_DENY, "Service not available.");
vsf_cmdio_write_exit(p_sess, FTP_IP_DENY, "Service not available.", 1);
}
vsf_log_line(p_sess, kVSFLogEntryConnection, &str_log_line);
}
@ -133,7 +133,7 @@ parse_username_password(struct vsf_session* p_sess)
}
else if (str_equal_text(&p_sess->ftp_cmd_str, "QUIT"))
{
vsf_cmdio_write_exit(p_sess, FTP_GOODBYE, "Goodbye.");
vsf_cmdio_write_exit(p_sess, FTP_GOODBYE, "Goodbye.", 0);
}
else if (str_equal_text(&p_sess->ftp_cmd_str, "FEAT"))
{
@ -228,16 +228,14 @@ handle_user_command(struct vsf_session* p_sess)
if (tunable_ssl_enable && !is_anon && !p_sess->control_use_ssl &&
tunable_force_local_logins_ssl)
{
vsf_cmdio_write(
p_sess, FTP_LOGINERR, "Non-anonymous sessions must use encryption.");
vsf_sysutil_exit(0);
vsf_cmdio_write_exit(
p_sess, FTP_LOGINERR, "Non-anonymous sessions must use encryption.", 1);
}
if (tunable_ssl_enable && is_anon && !p_sess->control_use_ssl &&
tunable_force_anon_logins_ssl)
{
vsf_cmdio_write(
p_sess, FTP_LOGINERR, "Anonymous sessions must use encryption.");
vsf_sysutil_exit(0);
vsf_cmdio_write_exit(
p_sess, FTP_LOGINERR, "Anonymous sessions must use encryption.", 1);
}
if (tunable_userlist_enable)
{
@ -299,6 +297,7 @@ static void check_login_fails(struct vsf_session* p_sess)
{
if (++p_sess->login_fails >= tunable_max_login_fails)
{
vsf_sysutil_exit(0);
vsf_sysutil_shutdown_failok(VSFTP_COMMAND_FD);
vsf_sysutil_exit(1);
}
}

View File

@ -42,6 +42,10 @@ vsf_privop_get_ftp_port_sock(struct vsf_session* p_sess,
int i;
int s = vsf_sysutil_get_ipsock(p_sess->p_local_addr);
int port = 0;
if (p_sess->pasv_listen_fd != -1)
{
die("listed fd is active?");
}
if (vsf_sysutil_is_port_reserved(remote_port))
{
die("Illegal port request");
@ -117,20 +121,15 @@ vsf_privop_pasv_listen(struct vsf_session* p_sess)
{
static struct vsf_sysutil_sockaddr* s_p_sockaddr;
int bind_retries = 10;
unsigned short the_port = 0;
unsigned short the_port;
/* IPPORT_RESERVED */
unsigned short min_port = 1024;
unsigned short max_port = 65535;
int is_ipv6 = vsf_sysutil_sockaddr_is_ipv6(p_sess->p_local_addr);
if (is_ipv6)
if (p_sess->pasv_listen_fd != -1)
{
p_sess->pasv_listen_fd = vsf_sysutil_get_ipv6_sock();
die("listed fd already active");
}
else
{
p_sess->pasv_listen_fd = vsf_sysutil_get_ipv4_sock();
}
vsf_sysutil_activate_reuseaddr(p_sess->pasv_listen_fd);
if (tunable_pasv_min_port > min_port && tunable_pasv_min_port <= max_port)
{
@ -152,6 +151,15 @@ vsf_privop_pasv_listen(struct vsf_session* p_sess)
scaled_port += ((double) the_port / (double) 65536) *
((double) max_port - min_port + 1);
the_port = (unsigned short) scaled_port;
if (is_ipv6)
{
p_sess->pasv_listen_fd = vsf_sysutil_get_ipv6_sock();
}
else
{
p_sess->pasv_listen_fd = vsf_sysutil_get_ipv4_sock();
}
vsf_sysutil_activate_reuseaddr(p_sess->pasv_listen_fd);
vsf_sysutil_sockaddr_clone(&s_p_sockaddr, p_sess->p_local_addr);
vsf_sysutil_sockaddr_set_port(s_p_sockaddr, the_port);
retval = vsf_sysutil_bind(p_sess->pasv_listen_fd, s_p_sockaddr);
@ -167,6 +175,8 @@ vsf_privop_pasv_listen(struct vsf_session* p_sess)
if (vsf_sysutil_get_error() == kVSFSysUtilErrADDRINUSE ||
vsf_sysutil_get_error() == kVSFSysUtilErrACCES)
{
vsf_sysutil_close(p_sess->pasv_listen_fd);
p_sess->pasv_listen_fd = -1;
continue;
}
die("vsf_sysutil_bind / listen");
@ -183,6 +193,10 @@ vsf_privop_accept_pasv(struct vsf_session* p_sess)
{
struct vsf_sysutil_sockaddr* p_accept_addr = 0;
int remote_fd;
if (p_sess->pasv_listen_fd == -1)
{
die("listed fd not active");
}
vsf_sysutil_sockaddr_alloc(&p_accept_addr);
remote_fd = vsf_sysutil_accept_timeout(p_sess->pasv_listen_fd, p_accept_addr,
tunable_accept_timeout);

View File

@ -83,6 +83,7 @@ ftp_read_data(struct vsf_session* p_sess, char* p_buf, unsigned int len)
{
int ret;
priv_sock_send_cmd(p_sess->ssl_consumer_fd, PRIV_SOCK_DO_SSL_READ);
priv_sock_send_int(p_sess->ssl_consumer_fd, len);
ret = priv_sock_get_int(p_sess->ssl_consumer_fd);
priv_sock_recv_buf(p_sess->ssl_consumer_fd, p_buf, len);
/* Need to do this here too because it is useless in the slave process. */

654
seccompsandbox.c Normal file
View File

@ -0,0 +1,654 @@
/*
* Part of Very Secure FTPd
* Licence: GPL v2
* Author: Chris Evans
* seccompsandbox.c
*
* Code to lock down the accessible kernel API in a Linux seccomp filter
* sandbox. Works in Ubuntu 11.10 and newer.
*/
#include "seccompsandbox.h"
#if defined(__linux__) && defined(__x86_64__)
#include "session.h"
#include "sysutil.h"
#include "tunables.h"
#include "utility.h"
#include <errno.h>
#include <netinet/in.h>
#include <sys/fcntl.h>
#include <sys/mman.h>
#include <sys/prctl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <linux/filter.h>
#include <asm/unistd.h>
/* #define DEBUG_SIGSYS 1 */
#ifndef PR_SET_NO_NEW_PRIVS
#define PR_SET_NO_NEW_PRIVS 38
#endif
#ifndef __NR_openat
#define __NR_openat 257
#endif
#ifndef O_LARGEFILE
#define O_LARGEFILE 00100000
#endif
#define kMaxSyscalls 100
#ifdef DEBUG_SIGSYS
#include <signal.h>
#include <string.h>
void
handle_sigsys(int sig)
{
(void) sig;
}
#endif
static const int kOpenFlags =
O_CREAT|O_EXCL|O_APPEND|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_LARGEFILE;
static size_t s_syscall_index;
static size_t s_1_arg_validations;
static size_t s_2_arg_validations;
static size_t s_3_arg_validations;
static int s_syscalls[kMaxSyscalls];
static int s_args_1[kMaxSyscalls];
static int s_vals_1[kMaxSyscalls];
static int s_args_2[kMaxSyscalls];
static int s_vals_2[kMaxSyscalls];
static int s_args_3[kMaxSyscalls];
static int s_vals_3[kMaxSyscalls];
static void
allow_nr(int nr)
{
if (s_syscall_index >= kMaxSyscalls)
{
bug("out of syscall space");
}
if (nr < 0)
{
bug("negative syscall");
}
s_syscalls[s_syscall_index++] = nr;
}
static void
allow_nr_1_arg_match(int nr, int arg, int val)
{
if (s_syscall_index >= kMaxSyscalls)
{
bug("out of syscall space");
}
if (nr < 0)
{
bug("negative syscall");
}
if (arg < 1 || arg > 6)
{
bug("arg out of range");
}
s_args_1[s_syscall_index] = arg;
s_vals_1[s_syscall_index] = val;
s_syscalls[s_syscall_index++] = nr;
s_1_arg_validations++;
}
static void
allow_nr_1_arg_mask(int nr, int arg, int val)
{
if (s_syscall_index >= kMaxSyscalls)
{
bug("out of syscall space");
}
if (nr < 0)
{
bug("negative syscall");
}
if (arg < 1 || arg > 6)
{
bug("arg out of range");
}
s_args_1[s_syscall_index] = 100 + arg;
s_vals_1[s_syscall_index] = val;
s_syscalls[s_syscall_index++] = nr;
s_1_arg_validations++;
}
static void
allow_nr_2_arg_match(int nr, int arg1, int val1, int arg2, int val2)
{
if (s_syscall_index >= kMaxSyscalls)
{
bug("out of syscall space");
}
if (nr < 0)
{
bug("negative syscall");
}
if (arg1 < 1 || arg1 > 6)
{
bug("arg1 out of range");
}
if (arg2 < 1 || arg2 > 6)
{
bug("arg2 out of range");
}
s_args_1[s_syscall_index] = arg1;
s_vals_1[s_syscall_index] = val1;
s_args_2[s_syscall_index] = arg2;
s_vals_2[s_syscall_index] = val2;
s_syscalls[s_syscall_index++] = nr;
s_2_arg_validations++;
}
static void
allow_nr_2_arg_mask_match(int nr, int arg1, int val1, int arg2, int val2)
{
if (s_syscall_index >= kMaxSyscalls)
{
bug("out of syscall space");
}
if (nr < 0)
{
bug("negative syscall");
}
if (arg1 < 1 || arg1 > 6)
{
bug("arg1 out of range");
}
if (arg2 < 1 || arg2 > 6)
{
bug("arg2 out of range");
}
s_args_1[s_syscall_index] = 100 + arg1;
s_vals_1[s_syscall_index] = val1;
s_args_2[s_syscall_index] = arg2;
s_vals_2[s_syscall_index] = val2;
s_syscalls[s_syscall_index++] = nr;
s_2_arg_validations++;
}
static void
allow_nr_3_arg_match(int nr, int arg1, int val1, int arg2, int val2, int arg3,
int val3)
{
if (s_syscall_index >= kMaxSyscalls)
{
bug("out of syscall space");
}
if (nr < 0)
{
bug("negative syscall");
}
if (arg1 < 1 || arg1 > 6)
{
bug("arg1 out of range");
}
if (arg2 < 1 || arg2 > 6)
{
bug("arg2 out of range");
}
if (arg3 < 1 || arg3 > 6)
{
bug("arg3 out of range");
}
s_args_1[s_syscall_index] = arg1;
s_vals_1[s_syscall_index] = val1;
s_args_2[s_syscall_index] = arg2;
s_vals_2[s_syscall_index] = val2;
s_args_3[s_syscall_index] = arg3;
s_vals_3[s_syscall_index] = val3;
s_syscalls[s_syscall_index++] = nr;
s_3_arg_validations++;
}
static void
seccomp_sandbox_setup_data_connections()
{
allow_nr_3_arg_match(__NR_socket, 1, PF_INET, 2, SOCK_STREAM, 3, IPPROTO_TCP);
allow_nr_3_arg_match(__NR_socket,
1, PF_INET6,
2, SOCK_STREAM,
3, IPPROTO_TCP);
allow_nr(__NR_bind);
allow_nr(__NR_select);
if (tunable_port_enable)
{
allow_nr(__NR_connect);
allow_nr_2_arg_match(__NR_getsockopt, 2, SOL_SOCKET, 3, SO_ERROR);
allow_nr_2_arg_match(__NR_setsockopt, 2, SOL_SOCKET, 3, SO_REUSEADDR);
allow_nr_1_arg_match(__NR_fcntl, 2, F_GETFL);
allow_nr_2_arg_match(__NR_fcntl, 2, F_SETFL, 3, O_RDWR|O_NONBLOCK);
allow_nr_2_arg_match(__NR_fcntl, 2, F_SETFL, 3, O_RDWR);
}
if (tunable_pasv_enable)
{
allow_nr(__NR_listen);
allow_nr(__NR_accept);
}
}
static void
seccomp_sandbox_setup_base()
{
/* Simple reads and writes on existing descriptors. */
allow_nr(__NR_read);
allow_nr(__NR_write);
/* Needed for memory management. */
allow_nr_2_arg_match(__NR_mmap,
3, PROT_READ|PROT_WRITE,
4, MAP_PRIVATE|MAP_ANON);
allow_nr_1_arg_mask(__NR_mprotect, 3, PROT_READ);
allow_nr(__NR_brk);
/* Misc simple low-risk calls. */
allow_nr(__NR_rt_sigreturn); /* Used to handle SIGPIPE. */
allow_nr(__NR_restart_syscall);
allow_nr(__NR_close);
/* Always need to be able to exit ! */
allow_nr(__NR_exit_group);
}
void
seccomp_sandbox_init()
{
if (s_syscall_index != 0)
{
bug("bad state in seccomp_sandbox_init");
}
}
void
seccomp_sandbox_setup_prelogin(const struct vsf_session* p_sess)
{
(void) p_sess;
seccomp_sandbox_setup_base();
/* Peeking FTP commands from the network. */
allow_nr_1_arg_match(__NR_recvfrom, 4, MSG_PEEK);
/* Misc simple low-risk calls */
allow_nr(__NR_nanosleep); /* Used for bandwidth / login throttling. */
allow_nr(__NR_getpid); /* Used by logging. */
allow_nr(__NR_shutdown); /* Used for QUIT or a timeout. */
allow_nr_1_arg_match(__NR_fcntl, 2, F_GETFL);
/* It's safe to allow O_RDWR in fcntl because these flags cannot be changed.
* Also, sockets are O_RDWR.
*/
allow_nr_2_arg_mask_match(__NR_fcntl, 3, kOpenFlags|O_ACCMODE, 2, F_SETFL);
/* Config-dependent items follow. */
if (tunable_idle_session_timeout > 0)
{
allow_nr(__NR_rt_sigaction);
allow_nr(__NR_alarm);
}
if (tunable_xferlog_enable || tunable_dual_log_enable)
{
/* For file locking. */
allow_nr_1_arg_match(__NR_fcntl, 2, F_SETLKW);
allow_nr_1_arg_match(__NR_fcntl, 2, F_SETLK);
}
if (tunable_ssl_enable)
{
allow_nr_1_arg_match(__NR_recvmsg, 3, 0);
}
}
void
seccomp_sandbox_setup_postlogin(const struct vsf_session* p_sess)
{
int is_anon = p_sess->is_anonymous;
int open_flag = kOpenFlags;
if (tunable_write_enable)
{
open_flag |= O_ACCMODE;
}
/* Put lstat() first because it is a very hot syscall for large directory
* listings. And the current BPF only allows a linear scan of allowed
* syscalls.
*/
allow_nr(__NR_lstat);
/* Allow all the simple pre-login things and then expand upon them. */
seccomp_sandbox_setup_prelogin(p_sess);
/* Simple file descriptor-based operations. */
if (tunable_xferlog_enable || tunable_dual_log_enable ||
tunable_lock_upload_files)
{
allow_nr_1_arg_match(__NR_fcntl, 2, F_SETLKW);
allow_nr_1_arg_match(__NR_fcntl, 2, F_SETLK);
}
if (tunable_async_abor_enable)
{
allow_nr_2_arg_match(__NR_fcntl, 2, F_SETOWN, 3, vsf_sysutil_getpid());
}
allow_nr_2_arg_match(__NR_setsockopt, 2, SOL_SOCKET, 3, SO_KEEPALIVE);
allow_nr_2_arg_match(__NR_setsockopt, 2, SOL_SOCKET, 3, SO_LINGER);
allow_nr_2_arg_match(__NR_setsockopt, 2, IPPROTO_IP, 3, IP_TOS);
allow_nr(__NR_fstat);
allow_nr(__NR_lseek);
/* Since we use chroot() to restrict filesystem access, we can just blanket
* allow open().
*/
allow_nr_1_arg_mask(__NR_open, 2, open_flag);
allow_nr_1_arg_mask(__NR_openat, 3, open_flag);
/* Other pathname-based metadata queries. */
allow_nr(__NR_stat);
allow_nr(__NR_readlink);
/* Directory handling: query, change, read. */
allow_nr(__NR_getcwd);
allow_nr(__NR_chdir);
allow_nr(__NR_getdents);
/* Misc */
allow_nr(__NR_umask);
/* Config-dependent items follow. */
if (tunable_use_sendfile)
{
allow_nr(__NR_sendfile);
}
if (tunable_idle_session_timeout > 0 ||
tunable_data_connection_timeout > 0 ||
tunable_async_abor_enable)
{
allow_nr(__NR_rt_sigaction);
}
if (tunable_idle_session_timeout > 0 || tunable_data_connection_timeout > 0)
{
allow_nr(__NR_alarm);
}
if (tunable_one_process_model)
{
seccomp_sandbox_setup_data_connections();
if (is_anon && tunable_chown_uploads)
{
allow_nr(__NR_fchmod);
allow_nr(__NR_fchown);
}
}
else
{
/* Need to receieve file descriptors from privileged broker. */
allow_nr_1_arg_match(__NR_recvmsg, 3, 0);
if ((is_anon && tunable_chown_uploads) || tunable_ssl_enable)
{
/* Need to send file descriptors to privileged broker. */
allow_nr_1_arg_match(__NR_sendmsg, 3, 0);
}
}
if (tunable_text_userdb_names)
{
allow_nr_2_arg_match(__NR_mmap, 3, PROT_READ, 4, MAP_SHARED);
allow_nr(__NR_munmap);
}
if (tunable_write_enable)
{
if (!is_anon || tunable_anon_mkdir_write_enable)
{
allow_nr(__NR_mkdir);
}
if (!is_anon ||
tunable_anon_other_write_enable ||
tunable_delete_failed_uploads)
{
allow_nr(__NR_unlink);
}
if (!is_anon || tunable_anon_other_write_enable)
{
allow_nr(__NR_rmdir);
allow_nr(__NR_rename);
allow_nr(__NR_ftruncate);
if (tunable_mdtm_write)
{
allow_nr(__NR_utime);
allow_nr(__NR_utimes);
}
}
if (!is_anon && tunable_chmod_enable)
{
allow_nr(__NR_chmod);
}
}
}
void
seccomp_sandbox_setup_postlogin_broker()
{
seccomp_sandbox_setup_base();
seccomp_sandbox_setup_data_connections();
allow_nr_1_arg_match(__NR_sendmsg, 3, 0);
}
void
seccomp_sandbox_lockdown()
{
size_t len = (s_syscall_index * 2) +
(s_1_arg_validations * 3) +
(s_2_arg_validations * 5) +
(s_3_arg_validations * 7) +
5;
struct sock_filter filters[len];
struct sock_filter* p_filter = filters;
struct sock_fprog prog;
size_t i;
int ret;
prog.len = len;
prog.filter = filters;
/* Validate the syscall architecture. */
p_filter->code = BPF_LD+BPF_W+BPF_ABS;
p_filter->jt = 0;
p_filter->jf = 0;
/* Offset 4 for syscall architecture. */
p_filter->k = 4;
p_filter++;
p_filter->code = BPF_JMP+BPF_JEQ+BPF_K;
p_filter->jt = 1;
p_filter->jf = 0;
/* AUDIT_ARCH_X86_64 */
p_filter->k = 0xc000003e;
p_filter++;
p_filter->code = BPF_RET+BPF_K;
p_filter->jt = 0;
p_filter->jf = 0;
/* SECCOMP_RET_KILL */
p_filter->k = 0;
p_filter++;
/* Load the syscall number. */
p_filter->code = BPF_LD+BPF_W+BPF_ABS;
p_filter->jt = 0;
p_filter->jf = 0;
/* Offset 0 for syscall number. */
p_filter->k = 0;
p_filter++;
for (i = 0; i < s_syscall_index; ++i)
{
int block_size = 1;
if (s_args_3[i])
{
block_size = 8;
}
else if (s_args_2[i])
{
block_size = 6;
}
else if (s_args_1[i])
{
block_size = 4;
}
/* Check for syscall number match. */
p_filter->code = BPF_JMP+BPF_JEQ+BPF_K;
p_filter->jt = 0;
p_filter->jf = block_size;
p_filter->k = s_syscalls[i];
p_filter++;
/* Check argument matches if necessary. */
if (s_args_3[i])
{
p_filter->code = BPF_LD+BPF_W+BPF_ABS;
p_filter->jt = 0;
p_filter->jf = 0;
p_filter->k = 16 + ((s_args_3[i] - 1) * 8);
p_filter++;
p_filter->code = BPF_JMP+BPF_JEQ+BPF_K;
p_filter->jt = 0;
p_filter->jf = 5;
p_filter->k = s_vals_3[i];
p_filter++;
}
if (s_args_2[i])
{
p_filter->code = BPF_LD+BPF_W+BPF_ABS;
p_filter->jt = 0;
p_filter->jf = 0;
p_filter->k = 16 + ((s_args_2[i] - 1) * 8);
p_filter++;
p_filter->code = BPF_JMP+BPF_JEQ+BPF_K;
p_filter->jt = 0;
p_filter->jf = 3;
p_filter->k = s_vals_2[i];
p_filter++;
}
if (s_args_1[i])
{
int arg = s_args_1[i];
int code = BPF_JMP+BPF_JEQ+BPF_K;
int val = s_vals_1[i];
int jt = 0;
int jf = 1;
if (arg > 100)
{
arg -= 100;
code = BPF_JMP+BPF_JSET+BPF_K;
val = ~val;
jt = 1;
jf = 0;
}
p_filter->code = BPF_LD+BPF_W+BPF_ABS;
p_filter->jt = 0;
p_filter->jf = 0;
p_filter->k = 16 + ((arg - 1) * 8);
p_filter++;
p_filter->code = code;
p_filter->jt = jt;
p_filter->jf = jf;
p_filter->k = val;
p_filter++;
}
p_filter->code = BPF_RET+BPF_K;
p_filter->jt = 0;
p_filter->jf = 0;
/* SECCOMP_RET_ALLOW */
p_filter->k = 0x7fff0000;
p_filter++;
if (s_args_1[i])
{
/* We trashed the accumulator so put it back. */
p_filter->code = BPF_LD+BPF_W+BPF_ABS;
p_filter->jt = 0;
p_filter->jf = 0;
p_filter->k = 0;
p_filter++;
}
}
/* No "allow" matches so kill. */
p_filter->code = BPF_RET+BPF_K;
p_filter->jt = 0;
p_filter->jf = 0;
#ifdef DEBUG_SIGSYS
/* SECCOMP_RET_TRAP */
p_filter->k = 0x00030000;
#else
/* SECCOMP_RET_KILL */
p_filter->k = 0;
#endif
ret = prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
if (ret != 0)
{
if (errno == EINVAL)
{
/* Kernel isn't good enough. */
return;
}
die("prctl PR_SET_NO_NEW_PRIVS");
}
if (!tunable_seccomp_sandbox)
{
return;
}
#ifdef DEBUG_SIGSYS
{
struct sigaction sa;
memset(&sa, '\0', sizeof(sa));
sa.sa_handler = handle_sigsys;
sigaction(SIGSYS, &sa, NULL);
}
#endif
ret = prctl(PR_SET_SECCOMP, 2, &prog, 0, 0);
if (ret != 0)
{
die("prctl PR_SET_SECCOMP failed");
}
}
#else /* __linux__ && __x86_64__ */
void
seccomp_sandbox_init()
{
}
void
seccomp_sandbox_setup_prelogin(const struct vsf_session* p_sess)
{
(void) p_sess;
}
void
seccomp_sandbox_setup_postlogin(const struct vsf_session* p_sess)
{
(void) p_sess;
}
void
seccomp_sandbox_setup_postlogin_broker()
{
}
void
seccomp_sandbox_lockdown()
{
}
#endif /* __linux__ && __x86_64__ */

17
seccompsandbox.h Normal file
View File

@ -0,0 +1,17 @@
#ifndef VSF_SECCOMPSANDBOX_H
#define VSF_SECCOMPSANDBOX_H
struct vsf_session;
void seccomp_sandbox_init();
void seccomp_sandbox_setup_prelogin(const struct vsf_session* p_sess);
void seccomp_sandbox_setup_postlogin(const struct vsf_session* p_sess);
void seccomp_sandbox_setup_postlogin_broker();
void seccomp_sandbox_lockdown();
#endif /* VSF_SECCOMPSANDBOX_H */

View File

@ -132,7 +132,8 @@ vsf_secutil_change_credentials(const struct mystr* p_user_str,
/* Misconfiguration check: don't ever chroot() to a directory writable by
* the current user.
*/
if (options & VSF_SECUTIL_OPTION_CHROOT)
if ((options & VSF_SECUTIL_OPTION_CHROOT) &&
!(options & VSF_SECUTIL_OPTION_ALLOW_WRITEABLE_ROOT))
{
if (vsf_sysutil_write_access("/"))
{

View File

@ -23,15 +23,17 @@ struct mystr;
*/
/* chroot() the user into the new directory */
#define VSF_SECUTIL_OPTION_CHROOT 1
#define VSF_SECUTIL_OPTION_CHROOT 1
/* Activate any supplementary groups the user may have */
#define VSF_SECUTIL_OPTION_USE_GROUPS 2
#define VSF_SECUTIL_OPTION_USE_GROUPS 2
/* Do the chdir() as the effective userid of the target user */
#define VSF_SECUTIL_OPTION_CHANGE_EUID 4
#define VSF_SECUTIL_OPTION_CHANGE_EUID 4
/* Use RLIMIT_NOFILE to prevent the opening of new fds */
#define VSF_SECUTIL_OPTION_NO_FDS 8
#define VSF_SECUTIL_OPTION_NO_FDS 8
/* Use RLIMIT_NPROC to prevent the launching of new processes */
#define VSF_SECUTIL_OPTION_NO_PROCS 16
#define VSF_SECUTIL_OPTION_NO_PROCS 16
/* Permit a writeable chroot() root */
#define VSF_SECUTIL_OPTION_ALLOW_WRITEABLE_ROOT 32
void vsf_secutil_change_credentials(const struct mystr* p_user_str,
const struct mystr* p_dir_str,

View File

@ -21,6 +21,8 @@ struct vsf_session
struct vsf_sysutil_sockaddr* p_local_addr;
struct vsf_sysutil_sockaddr* p_remote_addr;
char* p_control_line_buf;
int idle_timeout;
int data_timeout;
/* Details of the data connection */
int pasv_listen_fd;

8
ssl.c
View File

@ -171,7 +171,7 @@ ssl_control_handshake(struct vsf_session* p_sess)
/* Technically, we shouldn't leak such detailed error messages. */
str_append_text(&err_str, get_ssl_error());
vsf_cmdio_write_str(p_sess, FTP_TLS_FAIL, &err_str);
vsf_sysutil_exit(0);
vsf_sysutil_exit(1);
}
p_sess->control_use_ssl = 1;
}
@ -476,6 +476,8 @@ ssl_accept(struct vsf_session* p_sess, int fd)
void
ssl_comm_channel_init(struct vsf_session* p_sess)
{
const struct vsf_sysutil_socketpair_retval retval =
vsf_sysutil_unix_stream_socketpair();
if (p_sess->ssl_consumer_fd != -1)
{
bug("ssl_consumer_fd active");
@ -484,8 +486,6 @@ ssl_comm_channel_init(struct vsf_session* p_sess)
{
bug("ssl_slave_fd active");
}
const struct vsf_sysutil_socketpair_retval retval =
vsf_sysutil_unix_stream_socketpair();
p_sess->ssl_consumer_fd = retval.socket_one;
p_sess->ssl_slave_fd = retval.socket_two;
}
@ -552,7 +552,7 @@ get_ssl(struct vsf_session* p_sess, int fd)
if (tunable_debug_ssl)
{
const char* p_ssl_version = SSL_get_cipher_version(p_ssl);
SSL_CIPHER* p_ssl_cipher = SSL_get_current_cipher(p_ssl);
const SSL_CIPHER* p_ssl_cipher = SSL_get_current_cipher(p_ssl);
const char* p_cipher_name = SSL_CIPHER_get_name(p_ssl_cipher);
X509* p_ssl_cert = SSL_get_peer_certificate(p_ssl);
int reused = SSL_session_reused(p_ssl);

View File

@ -71,13 +71,26 @@ ssl_slave(struct vsf_session* p_sess)
}
else if (cmd == PRIV_SOCK_DO_SSL_READ)
{
str_trunc(&data_str, VSFTP_DATA_BUFSIZE);
int size = priv_sock_get_int(p_sess->ssl_slave_fd);
if (size <= 0 || size > VSFTP_DATA_BUFSIZE)
{
bug("bad size");
}
if (p_sess->data_fd == -1 || p_sess->p_data_ssl == 0)
{
bug("invalid state");
}
str_trunc(&data_str, (unsigned int) size);
ret = ssl_read_into_str(p_sess, p_sess->p_data_ssl, &data_str);
priv_sock_send_int(p_sess->ssl_slave_fd, ret);
priv_sock_send_str(p_sess->ssl_slave_fd, &data_str);
}
else if (cmd == PRIV_SOCK_DO_SSL_WRITE)
{
if (p_sess->data_fd == -1 || p_sess->p_data_ssl == 0)
{
bug("invalid state");
}
priv_sock_get_str(p_sess->ssl_slave_fd, &data_str);
ret = ssl_write(p_sess->p_data_ssl,
str_getbuf(&data_str),
@ -93,6 +106,10 @@ ssl_slave(struct vsf_session* p_sess)
}
else
{
if (p_sess->data_fd == -1 || p_sess->p_data_ssl == 0)
{
bug("invalid state");
}
ret = ssl_data_close(p_sess);
if (ret == 1)
{

35
str.c
View File

@ -42,7 +42,12 @@ private_str_alloc_memchunk(struct mystr* p_str, const char* p_src,
unsigned int len)
{
/* Make sure this will fit in the buffer */
unsigned int buf_needed = len + 1;
unsigned int buf_needed;
if (len + 1 < len)
{
bug("integer overflow");
}
buf_needed = len + 1;
if (buf_needed > p_str->alloc_bytes)
{
str_free(p_str);
@ -58,7 +63,17 @@ void
private_str_append_memchunk(struct mystr* p_str, const char* p_src,
unsigned int len)
{
unsigned int buf_needed = p_str->len + len + 1;
unsigned int buf_needed;
if (len + p_str->len < len)
{
bug("integer overflow");
}
buf_needed = len + p_str->len;
if (buf_needed + 1 < buf_needed)
{
bug("integer overflow");
}
buf_needed++;
if (buf_needed > p_str->alloc_bytes)
{
p_str->p_buf = vsf_sysutil_realloc(p_str->p_buf, buf_needed);
@ -98,6 +113,10 @@ str_alloc_alt_term(struct mystr* p_str, const char* p_src, char term)
{
p_search++;
len++;
if (len == 0)
{
bug("integer overflow");
}
}
private_str_alloc_memchunk(p_str, p_src, len);
}
@ -150,6 +169,10 @@ str_reserve(struct mystr* p_str, unsigned int res_len)
{
/* Reserve space for the trailing zero as well. */
res_len++;
if (res_len == 0)
{
bug("integer overflow");
}
if (res_len > p_str->alloc_bytes)
{
p_str->p_buf = vsf_sysutil_realloc(p_str->p_buf, res_len);
@ -640,12 +663,20 @@ str_getline(const struct mystr* p_str, struct mystr* p_line_str,
while (curr_pos < buf_len && p_buf[curr_pos] != '\n')
{
curr_pos++;
if (curr_pos == 0)
{
bug("integer overflow");
}
}
out_len = curr_pos - start_pos;
/* If we ended on a \n - skip it */
if (curr_pos < buf_len && p_buf[curr_pos] == '\n')
{
curr_pos++;
if (curr_pos == 0)
{
bug("integer overflow");
}
}
private_str_alloc_memchunk(p_line_str, p_buf + start_pos, out_len);
*p_pos = curr_pos;

View File

@ -725,7 +725,7 @@ vsf_sysutil_activate_linger(int fd)
struct linger the_linger;
vsf_sysutil_memclr(&the_linger, sizeof(the_linger));
the_linger.l_onoff = 1;
the_linger.l_linger = 32767;
the_linger.l_linger = 60 * 10;
retval = setsockopt(fd, SOL_SOCKET, SO_LINGER, &the_linger,
sizeof(the_linger));
if (retval != 0)
@ -1020,13 +1020,13 @@ vsf_sysutil_next_dirent(struct vsf_sysutil_dir* p_dir)
unsigned int
vsf_sysutil_strlen(const char* p_text)
{
unsigned int ret = strlen(p_text);
size_t ret = strlen(p_text);
/* A defense in depth measure. */
if (ret > INT_MAX / 8)
{
die("string suspiciously long");
}
return ret;
return (unsigned int) ret;
}
char*
@ -2057,7 +2057,8 @@ vsf_sysutil_sockaddr_set_ipv6addr(struct vsf_sysutil_sockaddr* p_sockptr,
const void*
vsf_sysutil_sockaddr_ipv6_v4(const struct vsf_sysutil_sockaddr* p_addr)
{
static char pattern[12] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF };
static unsigned char pattern[12] =
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF };
const unsigned char* p_addr_start;
if (p_addr->u.u_sockaddr.sa_family != AF_INET6)
{
@ -2074,7 +2075,7 @@ vsf_sysutil_sockaddr_ipv6_v4(const struct vsf_sysutil_sockaddr* p_addr)
const void*
vsf_sysutil_sockaddr_ipv4_v6(const struct vsf_sysutil_sockaddr* p_addr)
{
static char ret[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF };
static unsigned char ret[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF };
if (p_addr->u.u_sockaddr.sa_family != AF_INET)
{
return 0;

View File

@ -80,12 +80,14 @@ int tunable_strict_ssl_write_shutdown;
int tunable_ssl_request_cert;
int tunable_delete_failed_uploads;
int tunable_implicit_ssl;
int tunable_sandbox;
int tunable_ptrace_sandbox;
int tunable_require_ssl_reuse;
int tunable_isolate;
int tunable_isolate_network;
int tunable_ftp_enable;
int tunable_http_enable;
int tunable_seccomp_sandbox;
int tunable_allow_writeable_chroot;
unsigned int tunable_accept_timeout;
unsigned int tunable_connect_timeout;
@ -178,7 +180,7 @@ tunables_load_defaults()
tunable_use_localtime = 0;
tunable_check_shell = 1;
tunable_hide_ids = 0;
tunable_listen = 0;
tunable_listen = 1;
tunable_port_promiscuous = 0;
tunable_passwd_chroot_enable = 0;
tunable_no_anon_password = 0;
@ -218,12 +220,14 @@ tunables_load_defaults()
tunable_ssl_request_cert = 1;
tunable_delete_failed_uploads = 0;
tunable_implicit_ssl = 0;
tunable_sandbox = 0;
tunable_ptrace_sandbox = 0;
tunable_require_ssl_reuse = 1;
tunable_isolate = 1;
tunable_isolate_network = 1;
tunable_ftp_enable = 1;
tunable_http_enable = 0;
tunable_seccomp_sandbox = 1;
tunable_allow_writeable_chroot = 0;
tunable_accept_timeout = 60;
tunable_connect_timeout = 60;
@ -280,7 +284,7 @@ tunables_load_defaults()
install_str_setting("/usr/share/ssl/certs/vsftpd.pem",
&tunable_rsa_cert_file);
install_str_setting(0, &tunable_dsa_cert_file);
install_str_setting("DES-CBC3-SHA", &tunable_ssl_ciphers);
install_str_setting("AES128-SHA:DES-CBC3-SHA", &tunable_ssl_ciphers);
install_str_setting(0, &tunable_rsa_private_key_file);
install_str_setting(0, &tunable_dsa_private_key_file);
install_str_setting(0, &tunable_ca_certs_file);

View File

@ -81,12 +81,14 @@ extern int tunable_strict_ssl_write_shutdown; /* Need SSL_shutdown() on write */
extern int tunable_ssl_request_cert; /* Ask client for cert */
extern int tunable_delete_failed_uploads; /* Delete an upload that failed */
extern int tunable_implicit_ssl; /* Use implicit SSL protocol */
extern int tunable_sandbox; /* Deploy ptrace sandbox */
extern int tunable_ptrace_sandbox; /* DEPRECATED ptrace sandbox */
extern int tunable_require_ssl_reuse; /* Require re-used data conn */
extern int tunable_isolate; /* Use container clone() flags */
extern int tunable_isolate_network; /* Use CLONE_NEWNET */
extern int tunable_ftp_enable; /* Allow FTP protocol */
extern int tunable_http_enable; /* Allow HTTP protocol */
extern int tunable_seccomp_sandbox; /* seccomp filter sandbox */
extern int tunable_allow_writeable_chroot; /* Allow misconfiguration */
/* Integer/numeric defines */
extern unsigned int tunable_accept_timeout;

View File

@ -27,6 +27,7 @@
#include "sysutil.h"
#include "sysdeputil.h"
#include "sslslave.h"
#include "seccompsandbox.h"
static void drop_all_privs(void);
static void handle_sigchld(void* duff);
@ -51,16 +52,15 @@ handle_sigchld(void* duff)
/* Child died, so we'll do the same! Report it as an error unless the child
* exited normally with zero exit code
*/
if (vsf_sysutil_retval_is_error(vsf_sysutil_wait_get_retval(&wait_retval)) ||
!vsf_sysutil_wait_exited_normally(&wait_retval) ||
vsf_sysutil_wait_get_exitcode(&wait_retval) != 0)
{
if (vsf_sysutil_retval_is_error(vsf_sysutil_wait_get_retval(&wait_retval)))
{
die("waiting for child");
}
else if (!vsf_sysutil_wait_exited_normally(&wait_retval))
{
die("child died");
}
else
{
vsf_sysutil_exit(0);
}
vsf_sysutil_exit(0);
}
static void
@ -132,6 +132,9 @@ vsf_two_process_start(struct vsf_session* p_sess)
}
}
drop_all_privs();
seccomp_sandbox_init();
seccomp_sandbox_setup_prelogin(p_sess);
seccomp_sandbox_lockdown();
init_connection(p_sess);
/* NOTREACHED */
}
@ -426,6 +429,10 @@ common_do_login(struct vsf_session* p_sess, const struct mystr* p_user_str,
{
secutil_option |= VSF_SECUTIL_OPTION_CHANGE_EUID;
}
if (!was_anon && tunable_allow_writeable_chroot)
{
secutil_option |= VSF_SECUTIL_OPTION_ALLOW_WRITEABLE_ROOT;
}
calculate_chdir_dir(was_anon, &userdir_str, &chroot_str, &chdir_str,
p_user_str, p_orig_user_str);
vsf_secutil_change_credentials(p_user_str, &userdir_str, &chroot_str,
@ -439,6 +446,9 @@ common_do_login(struct vsf_session* p_sess, const struct mystr* p_user_str,
str_free(&chdir_str);
str_free(&userdir_str);
p_sess->is_anonymous = anon;
seccomp_sandbox_init();
seccomp_sandbox_setup_postlogin(p_sess);
seccomp_sandbox_lockdown();
process_post_login(p_sess);
bug("should not get here: common_do_login");
}
@ -448,6 +458,7 @@ common_do_login(struct vsf_session* p_sess, const struct mystr* p_user_str,
{
ssl_comm_channel_set_producer_context(p_sess);
}
/* The seccomp sandbox lockdown for the priv parent is done inside here */
vsf_priv_parent_postlogin(p_sess);
bug("should not get here in common_do_login");
}

View File

@ -18,7 +18,7 @@ die(const char* p_text)
#ifdef DIE_DEBUG
bug(p_text);
#endif
vsf_sysutil_exit(1);
vsf_sysutil_exit(2);
}
void
@ -39,7 +39,7 @@ bug(const char* p_text)
(void) vsf_sysutil_write_loop(VSFTP_COMMAND_FD, p_text,
vsf_sysutil_strlen(p_text));
(void) vsf_sysutil_write_loop(VSFTP_COMMAND_FD, "\r\n", 2);
vsf_sysutil_exit(1);
vsf_sysutil_exit(2);
}
void

View File

@ -16,6 +16,7 @@ if find_func pam_start sysdeputil.o; then
locate_library /lib/libpam.so.0 && echo "/lib/libpam.so.0";
locate_library /usr/lib/libpam.so && echo "-lpam";
locate_library /usr/lib64/libpam.so && echo "-lpam";
locate_library /lib/x86_64-linux-gnu/libpam.so.0 && echo "-lpam";
# HP-UX ends shared libraries with .sl
locate_library /usr/lib/libpam.sl && echo "-lpam";
# AIX ends shared libraries with .a
@ -24,6 +25,7 @@ else
locate_library /lib/libcrypt.so && echo "-lcrypt";
locate_library /usr/lib/libcrypt.so && echo "-lcrypt";
locate_library /usr/lib64/libcrypt.so && echo "-lcrypt";
locate_library /lib/x86_64-linux-gnu/libcrypt.so && echo "-lcrypt";
fi
# Look for the dynamic linker library. Needed by older RedHat when

View File

@ -1,7 +1,7 @@
#ifndef VSF_VERSION_H
#define VSF_VERSION_H
#define VSF_VERSION "2.3.5"
#define VSF_VERSION "3.0.0"
#endif /* VSF_VERSION_H */