CVE-2009-4131: Linux kernel EXT4 IOCTL Insufficient Checks

A couple of weeks ago, this bug was being discussed everywhere. Unfortunately, I didn’t have time to blog about it before. Anyway, here is my delayed post…
The vulnerability was discovered by Akira Fujita of NEC on 7 December 2009 and it affects Linux kernel prior to 2.6.32-git6 release. Here is the buggy code as seen in fs/ext4/ioctl.c of 2.6.31 release of the Linux kernel.

It’ll initialize ‘donor_filp’ with the file descriptor of donor from the user controlled argument using fget() and check the ‘CAP_DAC_OVERRIDE’ (override all DAC access) capability. The next code to be executed is:

If it had reached this, it will immediately invoke ext4_move_extents() and just release the donor file descriptor. At last, if there was no error code returned, the updated ‘me’ structure will be copied back to the user space one.
Now here are the three fixes that Akira Fujita made on this IOCTL command…

1. In current EXT4_IOC_MOVE_EXT, there are read access mode checks for
original and donor files, but they allow the illegal write access to
donor file, since donor file is overwritten by original file data. To
fix this problem, change access mode checks of original (r->r/w) and
donor (r->w) files.

Well, this is probably the most important one from a security point of view. To remove this functionality of arbitrary write access the following code was added for the original file:

The capability check was removed and new code was added to check for write access before attempting to write to it. Because of this missing checks, a user could write to files even if he hadn’t write access on them since there was no check for that!

2. Disallow the use of donor files that have a setuid or setgid bits.

Another important fix. You could write to any file of the system, even on SETUID/SETGID files regardless of your actual file access modes, to fix this one mext_check_arguments() (from fs/ext4/move_extent.c) was updated to include this code:

As you probably have imagined and it isn’t that important from a security perspective in comparison to the other two.
This trivial vulnerability was exploited by countless people but spender published his exploit which you can download here.
The exploit is executed using a BASH shell script (ext4_own.sh) which is this:

This script checks if ‘passwd.bak’ file is present and copies ‘/usr/bin/passwd’ to that file if it isn’t. It then compiles ext4.c and modify_shadow.c files which are part of the exploit and executes ext4 executable. Finally, it flushes the cache using sync(1), performs a grep(1) that I don’t know why he’s doing it and then executes ‘/usr/bin/passwd’ to run ‘modify_shadow’ as you’ll see below. It ends by switching to root user using su(1).
The ext4.c file is also quite simple… Apart from these:

In a few words, he sets the donor file descriptor to ‘/usr/bin/passwd’ and the original file to ‘./modify_shadow’. Because of the previously discussed bug and since the starting offsets are 0, it will copy ‘./modify_shadow’ to ‘/usr/bin/passwd’. Let’s have a look at the other source code file now…

This is also straightforward. He first backups ‘/etc/shadow’ to ‘/etc/shadow-‘ and then finds the “root” entry in ‘/etc/shadow’ and replaces it with a hardcoded one that has password of ‘password’ as the “echo” in ext4_own.sh says. That was with spender’s exploit for CVE-2009-4131.
More recently, fotisl blogged about this bug too. He also included a code which you can download here. Once again, apart from the expected…

The arguments include the offset and the length so I’m assuming that this is more of a simple ‘EXT4_IOC_MOVE_EXT’ user space application but because of the EXT4 implementation it can be easily used to perform arbitrary writes to files. After opening the donor and original files, it continues like this:

Update:
After a quick conversation with spender on twitter he explained to me the grep(1) use that I did not understand, here is his explanation in his own words (I’m just pasting it since it is more convenenient here because it wasn’t on a single tweet):
spender’s reply:
For the changes you make via the ioctl to be visible immediately, you need to flush various caches in the kernel. On normal distros (ones using ext4) /usr/share will be quite large — 2GB on my machine. The grep is effectively used to read files. This causes the file/page caches to flush, then following that /usr/bin/passwd when executed will finally reflect its new content. Otherwise, as fotisl was having the problem, you would need to remount or reboot the machine to see the reflected changes.
end of spender’s reply.
Even though I disagree with him in numerous subjects, I have to publicly thank him for this response.
Thanks spender.