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