COMMAND

    edquota

SYSTEMS AFFECTED

    Most systems

PROBLEM

    Solar Designer found following, but let start with his tiny FAQ:

        Q: How do I crash a Linux-based shell provider?
        A: Register with username "67108864".

        Q: How do I just  bypass the quota? My admin  uses BSD-derived
           edquota(8).
        A: Register as "65535".

        Q: How do I consume some hours of their CPU time, as root?
        A: Register as "12345678". The next quotacheck run (usually at
           reboot) will take hours to complete.

        Q: How  do  I  reduce  someone  else's increased quota to  the
           default?
        A: Register with their UID as your username.

        Q: How do I corrupt their quota.user file?
        A: They  have to  allow 9  character long  usernames for that.
           Read below.

    Of these, only the first scenario is Linux-specific.  Others apply
    to many systems:  BSD 4.3, BSDI  2.0, FreeBSD 2.2.5,  SunOS 4.1.4,
    Solaris 2.5 seem  to be affected  - at least  their edquota(8) got
    the "feature", too.   In general, only  some setups are  affected:
    [free] shell providers  mostly.  Users  should be allowed  to pick
    all-digit usernames for these exploits to work.

    Now, to the edquota feature (yes, this was meant to be a feature):
    it  has  "special  support"  for  all-digit usernames.  Simply, it
    treats them as  UIDs.  Other  user-level quota utilities  have the
    same  feature,  but  that  doesn't  seem  to be a security problem
    there.   However,  a  typical  ISP  setup  would  use edquota in a
    script, running  as  root,  to  set  the quota for every new  user
    created.   While  this  feature  by  itself  is a security problem
    (see the 2nd and 4th   questions above), things are even worse  in
    reality.   Only  some  versions  of  edquota  check  and  disallow
    negative UIDs, and none of those tested do any check for UIDs past
    65535.

    Everything depends  on the  way quota  file is  updated.  There're
    several approaches here.  Some versions of edquota will only  work
    when the quota is on at  the moment, and use quotactl(2).   Others
    first try to use quotactl(), and, if that fails, assume the  quota
    is off (some are wise enough to check errno though), and write  to
    the file  directly. (Of  those, many  don't care  to check  return
    value from lseek(), which brings  a reliability problem.)  If  our
    version of edquota supports direct quota file access, _and_ it  is
    run while the quota is  off, then the attacker is  probably lucky,
    since it  will happily  lseek() to  whatever UID  it got  from the
    username.  Otherwise,  everything depends on  how well the  kernel
    checks if the values passed to quotactl() are valid.  Again,  many
    systems seem to  let the attacker  succeed, perhaps thinking  that
    they did the  super-user check already.   Some check for  negative
    UIDs only, which is definitely not enough.

    Let's  assume  the  attacker  succeeded  in  making the quota file
    really huge.  What's the problem with this, it's just the filesize
    and doesn't  take that  much of  physical storage  anyway?  Still,
    there're several problems.  First, some versions of quotacheck(8),
    which typically runs at reboot, got the following code:

       if (fstat(fd, &st) == 0) {
          max_id = st.st_size / sizeof(struct dqblk);
    [...]
       for (id = 1; id <= max_id; id++) {

    That is, its execution time will increase with the file size.  For
    449 Megs,  this was  over 8  hours of  CPU time.   Then, there's a
    problem  when  9  character  long  usernames  are  allowed,  _and_
    sizeof(struct dqblk) is not a power of 2.  Nine decimal digits are
    enough to cause an integer  overflow when edquota (or the  kernel)
    multiplies UID by sizeof(struct dqblk).  This can be used to write
    a block not at a block boundary, corrupting the quota file.

    Finally, there's  a Linux  kernel bug  (might be  present on  some
    other systems also; the impact will likely differ though). There's
    no check whether the UID supplied via quotactl() is valid, so that
    it is  possible to  get negative  file offsets.   Now, if  it used
    lseek() the way it is accessible via the syscall, everything would
    be fine.  However, the kernel simply does:

                    filp->f_pos = dqoff(dquot->dq_id);

    The system  stops responding,  and the  console gets  flooded with
    ext2 warning messages.   Hopefully there's someone  around to  hit
    that reset button.  The username from first FAQ question  exploits
    exactly this bug (combined  with the edquota feature,  of course).
    Here's another exploit, just to show this specific problem:

    #include <stdio.h>
    #include <unistd.h>
    #include <linux/quota.h>

    #define DEVICE                          "/dev/hda3"

    int main()
    {
            struct dqblk block;

            if (quotactl(QCMD(Q_SETQUOTA, USRQUOTA), DEVICE,
                (unsigned int)~0 / sizeof(block), (caddr_t)&block))
                    perror("quotactl");

            return 0;
    }

    It should be run as root,  and is mainly for checking whether  the
    bug got fixed -- it's not a real exploit.  Be sure to run it  with
    quota  enabled,  and  don't  forget  to set DEVICE correctly. This
    crashes Linux kernel 2.0.33 just fine.

SOLUTION

    If you don't  need the edquota  feature, you can  just disable it.
    Patch  for  Linux  quota  utils,  v1.51  follows  (below is kernel
    patch):

    --- edquota.c.orig      Fri Mar 20 18:20:54 1998
    +++ edquota.c   Fri Mar 20 18:23:30 1998
    @@ -173,8 +173,6 @@
        struct passwd  *pw;
        struct group   *gr;

    -   if (alldigits(name))
    -      return (atoi(name));
        switch (quotatype) {
           case USRQUOTA:
              if (pw = getpwnam(name))

    A real fix should probably either add an extra option (like  '-n')
    for numeric UIDs, or at least check getpwnam() before alldigits().
    Another  obvious  workaround  for  a  particular  site would be to
    disallow  all-digit  usernames.   And  finally,  here's  the Linux
    kernel patch, for 2.0.33:

    --- linux/fs/dquot.c.orig       Sat Mar 21 06:37:47 1998
    +++ linux/fs/dquot.c    Sat Mar 21 06:40:02 1998
    @@ -1075,6 +1075,9 @@
                            return(-EINVAL);
            }

    +       if (id & ~0xFFFF)
    +               return(-EINVAL);
    +
            flags |= QUOTA_SYSCALL;
            if (has_quota_enabled(dev, type))
                    return(set_dqblk(dev, id, type, flags, (struct dqblk *) addr));