/* * Part of Very Secure FTPd * Licence: GPL v2 * Author: Chris Evans * ftppolicy.c * * Code to build a sandbox policy based on current session options. */ #include "ftppolicy.h" #include "ptracesandbox.h" #include "tunables.h" #include "session.h" #include "sysutil.h" /* For AF_INET etc. network constants. */ #include #include #include #include #include #include static int socket_validator(struct pt_sandbox* p_sandbox, void* p_arg); static int connect_validator(struct pt_sandbox* p_sandbox, void* p_arg); static int getsockopt_validator(struct pt_sandbox* p_sandbox, void* p_arg); static int setsockopt_validator(struct pt_sandbox* p_sandbox, void* p_arg); void policy_setup(struct pt_sandbox* p_sandbox, const struct vsf_session* p_sess) { int is_anon = p_sess->is_anonymous; /* Always need to be able to exit! */ ptrace_sandbox_permit_exit(p_sandbox); /* Needed for memory management. */ ptrace_sandbox_permit_mmap(p_sandbox); ptrace_sandbox_permit_mprotect(p_sandbox); ptrace_sandbox_permit_brk(p_sandbox); /* Simple reads and writes are required. Permitting write does not imply * filesystem write access because access control is done at open time. */ ptrace_sandbox_permit_read(p_sandbox); ptrace_sandbox_permit_write(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); /* 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. */ ptrace_sandbox_permit_readlink(p_sandbox); /* File locking. */ ptrace_sandbox_permit_fcntl(p_sandbox); /* Seeking for REST. */ ptrace_sandbox_permit_seek(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); /* May need ability to install signal handlers. */ if (tunable_async_abor_enable || tunable_idle_session_timeout > 0 || tunable_data_connection_timeout > 0) { ptrace_sandbox_permit_sigaction(p_sandbox); } /* May need ability to set up timeout alarms. */ if (tunable_idle_session_timeout > 0 || tunable_data_connection_timeout > 0) { 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, (void*) p_sess); ptrace_sandbox_permit_bind(p_sandbox); /* Yes, reuse of the connect validator is intentional. */ 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); ptrace_sandbox_set_connect_validator(p_sandbox, connect_validator, (void*) p_sess); ptrace_sandbox_permit_getsockopt(p_sandbox); ptrace_sandbox_set_getsockopt_validator(p_sandbox, getsockopt_validator, 0); } if (tunable_pasv_enable) { ptrace_sandbox_permit_listen(p_sandbox); ptrace_sandbox_permit_accept(p_sandbox); } /* Set up write permissions according to config and session. */ if (tunable_write_enable) { if (!is_anon || tunable_anon_upload_enable) { ptrace_sandbox_permit_open(p_sandbox, 1); } if (!is_anon || tunable_anon_mkdir_write_enable) { ptrace_sandbox_permit_mkdir(p_sandbox); } if (!is_anon || tunable_anon_other_write_enable) { ptrace_sandbox_permit_unlink(p_sandbox); ptrace_sandbox_permit_rmdir(p_sandbox); ptrace_sandbox_permit_rename(p_sandbox); ptrace_sandbox_permit_ftruncate(p_sandbox); if (tunable_mdtm_write) { ptrace_sandbox_permit_utime(p_sandbox); } } if (!is_anon && tunable_chmod_enable) { ptrace_sandbox_permit_chmod(p_sandbox); } if (is_anon && tunable_chown_uploads) { ptrace_sandbox_permit_fchmod(p_sandbox); ptrace_sandbox_permit_fchown(p_sandbox); } } } static int socket_validator(struct pt_sandbox* p_sandbox, void* p_arg) { int ret; struct vsf_session* p_sess = (struct vsf_session*) p_arg; unsigned long arg1; unsigned long arg2; unsigned long expected_family = AF_INET; if (vsf_sysutil_sockaddr_is_ipv6(p_sess->p_local_addr)) { expected_family = AF_INET6; } ret = ptrace_sandbox_get_socketcall_arg(p_sandbox, 0, &arg1); if (ret != 0) { return ret; } ret = ptrace_sandbox_get_socketcall_arg(p_sandbox, 1, &arg2); if (ret != 0) { return ret; } if (arg1 != expected_family || arg2 != SOCK_STREAM) { return -1; } return 0; } static int connect_validator(struct pt_sandbox* p_sandbox, void* p_arg) { int ret; struct vsf_session* p_sess = (struct vsf_session*) p_arg; unsigned long arg2; unsigned long arg3; unsigned long expected_family = AF_INET; unsigned long expected_len = sizeof(struct sockaddr_in); void* p_buf = 0; struct sockaddr* p_sockaddr; static struct vsf_sysutil_sockaddr* p_sockptr; if (vsf_sysutil_sockaddr_is_ipv6(p_sess->p_local_addr)) { expected_family = AF_INET6; expected_len = sizeof(struct sockaddr_in6); } ret = ptrace_sandbox_get_socketcall_arg(p_sandbox, 1, &arg2); if (ret != 0) { return ret; } ret = ptrace_sandbox_get_socketcall_arg(p_sandbox, 2, &arg3); if (ret != 0) { return ret; } if (arg3 != expected_len) { return -1; } p_buf = vsf_sysutil_malloc((int) expected_len); ret = ptrace_sandbox_get_buf(p_sandbox, arg2, expected_len, p_buf); if (ret != 0) { vsf_sysutil_free(p_buf); return -2; } p_sockaddr = (struct sockaddr*) p_buf; if (p_sockaddr->sa_family != expected_family) { vsf_sysutil_free(p_buf); return -3; } if (expected_family == AF_INET) { struct sockaddr_in* p_sockaddr_in = (struct sockaddr_in*) p_sockaddr; vsf_sysutil_sockaddr_alloc_ipv4(&p_sockptr); vsf_sysutil_sockaddr_set_ipv4addr(p_sockptr, (const unsigned char*) &p_sockaddr_in->sin_addr); } else { struct sockaddr_in6* p_sockaddr_in6 = (struct sockaddr_in6*) p_sockaddr; vsf_sysutil_sockaddr_alloc_ipv6(&p_sockptr); vsf_sysutil_sockaddr_set_ipv6addr(p_sockptr, (const unsigned char*) &p_sockaddr_in6->sin6_addr); } if (!vsf_sysutil_sockaddr_addr_equal(p_sess->p_remote_addr, p_sockptr)) { vsf_sysutil_free(p_buf); return -4; } vsf_sysutil_free(p_buf); return 0; } static int getsockopt_validator(struct pt_sandbox* p_sandbox, void* p_arg) { int ret; unsigned long arg2; unsigned long arg3; (void) p_arg; ret = ptrace_sandbox_get_socketcall_arg(p_sandbox, 1, &arg2); if (ret != 0) { return ret; } ret = ptrace_sandbox_get_socketcall_arg(p_sandbox, 2, &arg3); if (ret != 0) { return ret; } if (arg2 != SOL_SOCKET || arg3 != SO_ERROR) { return -1; } return 0; } static int setsockopt_validator(struct pt_sandbox* p_sandbox, void* p_arg) { int ret; unsigned long arg2; unsigned long arg3; (void) p_arg; ret = ptrace_sandbox_get_socketcall_arg(p_sandbox, 1, &arg2); if (ret != 0) { return ret; } ret = ptrace_sandbox_get_socketcall_arg(p_sandbox, 2, &arg3); if (ret != 0) { return ret; } if (arg2 == SOL_SOCKET) { if (arg3 != SO_KEEPALIVE && arg3 != SO_REUSEADDR && arg3 != SO_OOBINLINE && arg3 != SO_LINGER) { return -1; } } else if (arg2 == IPPROTO_TCP) { if (arg3 != TCP_NODELAY) { return -2; } } else if (arg2 == IPPROTO_IP) { if (arg3 != IP_TOS) { return -3; } } else { return -4; } return 0; }