xorl %eax, %eax

Linux kernel do_nanosleep() NULL Pointer Dereference

leave a comment »

This bug was initially reported by Hiroshi Shimamoto to LKML on 3 August 2009. Today I saw an email about that by Eugene Teo on oss-security. As he states, the vulnerability must have been introduced in 2.6.28-rc1.
This code could be reached through clock_nanosleep(2) system call as H. Shimamoto demonstrated with his PoC code which is a simple:

#include <time.h>

int main(void)
{
	struct timespec ts;
	ts.tv_sec = 1;
	ts.tv_nsec = 0;

	return clock_nanosleep(4, 0, &ts, NULL);
}

The code path is this…

/*
 * nanosleep for monotonic and realtime clocks
 */
static int common_nsleep(const clockid_t which_clock, int flags,
                         struct timespec *tsave, struct timespec __user *rmtp)
{
        return hrtimer_nanosleep(tsave, rmtp, flags & TIMER_ABSTIME ?
                                 HRTIMER_MODE_ABS : HRTIMER_MODE_REL,
                                 which_clock);
}

This routine is located at kernel/posix-timers.c and now, let’s move to hrtimer_nanosleep() which can be found at kernel/hrtimer.c and includes this code:

long hrtimer_nanosleep(struct timespec *rqtp, struct timespec __user *rmtp,
                       const enum hrtimer_mode mode, const clockid_t clockid)
{
        struct restart_block *restart;
        struct hrtimer_sleeper t;
        int ret = 0;
        unsigned long slack;

        slack = current->timer_slack_ns;
        if (rt_task(current))
                slack = 0;

        hrtimer_init_on_stack(&t.timer, clockid, mode);
        hrtimer_set_expires_range_ns(&t.timer, timespec_to_ktime(*rqtp), slack);
        if (do_nanosleep(&t, mode))
                goto out;
    ...
}

And do_nanosleep() can be found at kernel/hrtimer.c and it begins with this code:

static int __sched do_nanosleep(struct hrtimer_sleeper *t, enum hrtimer_mode mode)
{
        hrtimer_init_sleeper(t, current);

        do {
                set_current_state(TASK_INTERRUPTIBLE);
                hrtimer_start_expires(&t->timer, mode);
    ...
}

hrtimer_start_expires() is a simple inline routine from include/linux/hrtimer.h that calls hrtimer_start_range_ns() from kernel/hrtimer.c which is a simple wrapper around __hrtimer_start_range_ns() which will execute this:

int __hrtimer_start_range_ns(struct hrtimer *timer, ktime_t tim,
                unsigned long delta_ns, const enum hrtimer_mode mode,
                int wakeup)
{
        struct hrtimer_clock_base *base, *new_base;
        unsigned long flags;
        int ret, leftmost;

        base = lock_hrtimer_base(timer, &flags);
    ...
}

The OOPS is triggered when lock_hrtimer_base() attempts to access the spinlock of that timer. To fix this bug, the following patch was applied:

@@ -202,6 +202,12 @@ static int no_timer_create(struct k_itimer *new_timer)
 	return -EOPNOTSUPP;
 }
 
+static int no_nsleep(const clockid_t which_clock, int flags,
+		     struct timespec *tsave, struct timespec __user *rmtp)
+{
+	return -EOPNOTSUPP;
+}
+
 /*
  * Return nonzero if we know a priori this clockid_t value is bogus.
  */
@@ -254,6 +260,7 @@ static __init int init_posix_timers(void)
 		.clock_get = posix_get_monotonic_raw,
 		.clock_set = do_posix_clock_nosettime,
 		.timer_create = no_timer_create,
+		.nsleep = no_nsleep,
 	};

Written by xorl

August 6, 2009 at 14:17

Posted in bugs, linux

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s