Everhart,Glenn From: Solar Designer [solar@FALSE.COM] Sent: Saturday, March 21, 1998 7:38 AM To: BUGTRAQ@NETSPACE.ORG Subject: edquota(8) feature 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 #include #include #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