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);
}