udev Local Privilege Escalation

Monday, April 20, 2009

A recent bug found by Sebastian Krahmer in udev has considerable security impact across a wide range of Linux distributions.

At the core of the vulnerability is the udevd daemon, responsible for receiving and handling various device events from the kernel. These events are delivered to udevd via netlink, a socket family (AF_NETLINK) commonly used for IPC between userspace applications and the kernel. However, netlink is not exclusively for kernel/userspace communication and can also be used to send messages between userspace applications.

The udev vulnerability resulted from a lack of verification of the netlink message source in udevd. Since udev is a privileged process and may perform operations as direct result form the netlink messages, it must verify that these messages have come from a trusted source (eg. the kernel). Unfortunately, as it does not perform such a check, an unprivileged userspace application can send a netlink message to the udev daemon and trick it into performing an operation that results in privilege escalation.

This vulnerability was fixed by adding the following checks in this patch:

if(udev_monitor->snl.nl_family!=0){
    if(snl.nl_groups==0){
        info(udev_monitor->udev,"unicastnetlinkmessageignored\n");
        returnNULL;
    }
    if((snl.nl_groups==UDEV_MONITOR_KERNEL)&&(snl.nl_pid>0)){
        info(udev_monitor->udev,"multicastkernelnetlinkmessagefrompid%dignored\n",snl.nl_pid);
        returnNULL;
    }
}

Exploiting this vulnerability is interesting because there's a number of creative ways one could go about tricking udev into elevating privileges. In kcope's exploit, he sets a malicious LD_PRELOAD so that when udev fires off its event handling process, his /tmp/suid shell will be granted the setuid bit. In my exploit, I chose to use some of the existing rule-based functionality of udev to elevate privileges.

In particular, udev has a rule file called "95-udev-late.rules" (likely in your /lib/udev/rules.d/ directory) which contains the following rule:

ACTION=="remove", ENV{REMOVE_CMD}!="", RUN+="$env{REMOVE_CMD}"

This action is useful as it allows execution of arbitrary commands when a particular device is removed. It's also very useful to us for the same reason as we can simply fake the removal of a device by sending a netlink message to udevd. The following snippet from my exploit does exactly that by specifying a malicious REMOVE_CMD and causes the privileged execution of attacker-controlled /tmp/run file:

mp = message;
mp += sprintf(mp, "remove@/d") + 1;
mp += sprintf(mp, "SUBSYSTEM=block") + 1;
mp += sprintf(mp, "DEVPATH=/dev/foo") + 1;
mp += sprintf(mp, "TIMEOUT=10") + 1;
mp += sprintf(mp, "ACTION=remove") +1;
mp += sprintf(mp, "REMOVE_CMD=/tmp/run") +1;

Overall, a great find by Sebastian and near-universal local privilege escalation on Linux platforms.

Copyright © 2021 - Jon Oberheide