edquota(8) feature

From: Solar Designer (solarat_private)
Date: Sat Mar 21 1998 - 04:37:47 PST

  • Next message: Catalin Mitrofan: "An exploit for linux mh ver 6.8.4-5 ( update ) ..."

    Hello,
    
    Okay, at least two different bugs today, but let me start with a 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. I didn't
    actually find that many victims yet, so feedback is welcome. ;-)
    
    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. However, the reason I was investigating this is that our quota.user
    file grew 449 Megs large one day, so this _can_ happen.
    
    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,
    and I was unable to find a mention of this in the manpages I have. Other
    user-level quota utilities have the same feature, but that doesn't seem to
    be a security problem there. However, a typical (I think) 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 I've seen do
    any check for UIDs past 65535.
    
    Now, 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, but
    I won't go into that now.)
    
    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, I just didn't have a chance to check; 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 my 2.0.33 just fine.
    
    Well, probably it's the time for fixes. If you don't need the edquota
    feature, you can just disable it (patch for Linux quota utils, v1.51):
    
    --- 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(). (The
    latter is still a bit dangerous though.)
    
    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));
    
    Signed,
    Solar Designer
    



    This archive was generated by hypermail 2b30 : Fri Apr 13 2001 - 13:46:36 PDT