COMMAND
wu-ftpd
SYSTEMS AFFECTED
Systems running wu-ftpd v.2.4
PROBLEM
There is a serious security bug in wu-ftpd v2.4 (including the
version from Academ) which may allow both regular and anonymous
users to access files as uid 0 (root). The same bug is also
responsible for an advisory lock not being unlocked - potentially
resulting in blocked access to future ftp logins and filling up
the process table and swap space until the server dies.
The ftpd server installs two signal handlers as part of its
startup procedure: one to catch SIGPIPE for control/data port
connection closes, and one to catch SIGURG for when out-of-band
signaling is used with the ABOR (abort file transfer) command.
The SIGPIPE handler is:
lostconn(int sig)
{
if (debug)
syslog(LOG_DEBUG, "lost connection to %s [%s]", remotehost,
remoteaddr)
;
dologout(-1);
}
...which causes the ftpd server to exit via dologout() whenever
the control or data connection is unexpectedly closed. The
function dologout() is:
dologout(int status)
{
if (logged_in) {
(void) seteuid((uid_t) 0);
logwtmp(ttyline, "", "");
}
syslog(LOG_INFO, "FTP session closed");
if (xferlog)
close(xferlog);
acl_remove();
/* beware of flushing buffers after a SIGPIPE */
_exit(status);
}
...which changes the effective uid to 0, adds a logout record to
wtmp, closes the xferlog log file, removes this instance of the
server from the PID file for his class, and exits. The initial
part of the SIGURG handler is:
myoob(int sig)
{
char *cp;
/* only process if transfer occurring */
if (!transflag)
return;
cp = tmpline;
if (getline(cp, 7, stdin) == NULL) {
reply(221, "You could at least say goodbye.");
dologout(0);
}
upper(cp);
if (strcmp(cp, "ABOR\r\n") == 0) {
tmpline[0] = '\0';
reply(426, "Transfer aborted. Data connection closed.");
reply(226, "Abort successful");
longjmp(urgcatch, 1);
}
(...)
...which does nothing if transflag is 0 - not currently doing a
file transfer, but if you are and an ABOR command was issued
along with the "urgent" data that caused this signal, then the
procedure does a longjmp() restoring the "urgcatch" saved state,
which ultimately returns back to the server main command loop.
Now, some FTP client programs will abort a file transfer by BOTH
closing the data connection AND issuing an ABOR with out-of-band
signaling. In many instances, the ftpd server gets the SIGPIPE
due to the closed data connection and begins the dologout()
procedure. While it is uid 0 and sometimes while it also has the
pid file advisory lock (which occurs in the acl_remove()
procedure), the ftpd server will sometimes be interrupted by the
SIGURG that is delivered as part of the ABOR command. Since
transflag is not 0 (a file transfer WAS occuring), the signal
handler does a longjmp which ultimately returns to the main
command loop...and presto, you are uid 0, and to make things even
better, the xferlog log file is closed so nothing you do is even
logged. Credit for this goes to ? (I lost header, but it was from
someone posting BugTraq).
SOLUTION
The fix that follows is by Wietse Venema. It minimizes system
calls (one syscall to delay all signals, and one to undo the
delay). Tested on: BSD/OS 2.1, SunOS 5.5, SunOS 4.1.3_U1,
FreeBSD 2.1.5, IRIX 5.3, and HP-UX 9.5.
/*
* delay_signaling(), enable_signaling - delay signal delivery for a while
*
* Author: Wietse Venema
*/
#include <sys/types.h>
#include <sys/signal.h>
#include <syslog.h>
static sigset_t saved_sigmask;
static sigset_t block_sigmask;
static int delaying;
static int init_done;
/* init_mask - compute signal mask only once */
static void init_mask()
{
int sig;
init_done = 1;
sigemptyset(&block_sigmask);
for (sig = 1; sig < NSIG; sig++)
sigaddset(&block_sigmask, sig);
}
/* enable_signaling - deliver delayed signals and disable signal delay */
int enable_signaling()
{
if (delaying != 0) {
delaying = 0;
if (sigprocmask(SIG_SETMASK, &saved_sigmask, (sigset_t *) 0)<0) {
syslog(LOG_ERR, "sigprocmask: %m");
return (-1);
}
}
return (0);
}
/* delay_signaling - save signal mask and block all signals */
int delay_signaling()
{
if (init_done == 0)
init_mask();
if (delaying == 0) {
delaying = 1;
if (sigprocmask(SIG_BLOCK, &block_sigmask, &saved_sigmask)<0) {
syslog(LOG_ERR, "sigprocmask: %m");
return (-1);
}
}
return (0);
}
#ifdef TEST
#include <stdio.h>
void gotsig(sig)
int sig;
{
printf("Got signal %d\n", sig);
}
main(argc, argv)
int argc;
char **argv;
{
signal(SIGINT, gotsig);
signal(SIGQUIT, gotsig);
delay_signaling();
sleep(5);
enable_signaling();
exit(0);
}
#endif
Yet another fix is available by Henrik P Johnson. Below comes an
hopefully improved version of the sigfix.c file to fix wu-ftp.
This will block signals while within crusial parts of the FTP
server, yet the signals will occur after the resumesigs is
called. It seems to work on HP, OSF, linux and Solaris.
/* ######################### sigfix.c ################################# */
void
#ifdef __STDC__
suspendsigs(void)
#else
suspendsigs()
#endif
{
sigset_t sset=0;
#ifdef SIGPIPE
sset=SIGPIPE;
#endif
#ifdef SIGURG
sset|=SIGURG;
#endif
sigprocmask(SIG_BLOCK,&sset,NULL);
}
void
#ifdef __STDC__
resumesigs(void)
#else
reseumesigs()
#endif
{
sigset_t sset=0;
#ifdef SIGPIPE
sset=SIGPIPE;
#endif
#ifdef SIGURG
sset|=SIGURG;
#endif
sigprocmask(SIG_UNBLOCK,&sset,NULL);
}