xorl %eax, %eax

CVE-2009-1041: FreeBSD ktimer Local Privilege Escalation

with 4 comments

This is a really interesting vulnerability. It was disclosed by the FreeBSD Project on 23 March 2009 and affects FreeBSD 7.0 including 7.1 branch. The following code is from FreeBSD 7.0 release. The vulnerable function is under kern/kern_time.c:

1065 static struct itimer *
1066 itimer_find(struct proc *p, int timerid)
1067 {
1068         struct itimer *it;
1070         PROC_LOCK_ASSERT(p, MA_OWNED);
1071         if ((p->p_itimers == NULL) || (timerid >= TIMER_MAX) ||
1072             (it = p->p_itimers->its_timers[timerid]) == NULL) {
1073                 return (NULL);
1074         }
1075         ITIMER_LOCK(it);1076         if ((it->it_flags & ITF_DELETING) != 0) {
1077                 ITIMER_UNLOCK(it);
1078                 it = NULL;
1079         }
1080         return (it);
1081 }

As you can see, this function is used to retrieve the itimer from a process. This is used because of the FreeBSD kernel supports per-process real-timers (POSIX ktimer implementation). To be more specific, here is what this function does in some simple steps:
1) Check the mutex lock using PROC_LOCK_ASSERT() macro
2) If the process timer is NULL, or the wanted timerid is more than TIMER_MAX, or the process’ itimers is NULL return NULL.
3) Else, lock the requested timer using ITIMER_LOCK() macro
4) If the itimer has flag to be deleted, then unlock it and set it to NULL
5) Else, return the requested itimer.
It is obvious that at line 1071, the timerid (which is a signed integer) might bypass the second condition if it is set to a negative number. TIMER_MAX is defined at timers.h like this:

86 #define TIMER_MAX       32

This is what the patch was about. It just includes an additional check for negative values of timerid like this:

-    if ((p->p_itimers == NULL) || (timerid >= TIMER_MAX) ||
+    if ((p->p_itimers == NULL) ||
+        (timerid < 0) || (timerid >= TIMER_MAX) ||
         (it = p->p_itimers->its_timers[timerid]) == NULL) {
         return (NULL);

With this in mind, we can move on with the analysis. It is pretty easy to directly interact with this signed value. Here are a couple of functions you can use to do this:

1083 static int1084 kern_timer_delete(struct thread *td, int timerid)
1085 {
1086         struct proc *p = td->td_proc;
1087         struct itimer *it;
1089         PROC_LOCK(p);
1090         it = itimer_find(p, timerid); 
1113 }

This function is used to delete an itimer of the process passed as the first argument. Another way to reach this code (easier one) is this:

1123 int
1124 ktimer_settime(struct thread *td, struct ktimer_settime_args *uap)
1125 {
1126         struct proc *p = td->td_proc;
1127         struct itimer *it;
1128         struct itimerspec val, oval, *ovalp;
1131         error = copyin(uap->value, &val, sizeof(val));
1135         if (uap->ovalue != NULL)
1136                 ovalp = &oval;
1141         if (uap->timerid < 3 ||
1142             (it = itimer_find(p, uap->timerid)) == NULL) {
1156 }

This is probably, the most straightforward way, you can directly call from user-space this routine to set the time of a ktimer. As you can see at lines 1141-1142, itimer_find() is called directly with the user controlled timerid. Other functions where the vulnerable function is being called are:

1164 int
1165 ktimer_gettime(struct thread *td, struct ktimer_gettime_args *uap)1166 {
1173         if (uap->timerid < 3 ||
1174            (it = itimer_find(p, uap->timerid)) == NULL) {
1188 }

1195 int
1196 ktimer_getoverrun(struct thread *td, struct ktimer_getoverrun_args *uap)
1197 {
1203         if (uap->timerid < 3 ||
1204             (it = itimer_find(p, uap->timerid)) == NULL) {
1214 }

1312 int
1313 itimer_accept(struct proc *p, int timerid, ksiginfo_t *ksi)
1314 {
1318         it = itimer_find(p, timerid);
1327 }

It is important that in whatever routine you choose to reach the vulnerable code, you’ll set timerid to something that when is checked at line 1072 will not be NULL at offset:


Now, assuming that you found an evil integer to insert to timerid, and also found a cool way to bypass the above checks. You have to find a use of timerid in one of the above routines that could lead to privilege escalation. A fairly cool one (which I haven’t tested it yet but I will when I go back home) is this:

1148 error = CLOCK_CALL(it->it_clockid, timer_settime,
1149                   (it, uap->flags, &val, ovalp));

from ktimer_settime() and ktimer_gettimer(). This macro is defined like this:

103 #define CLOCK_CALL(clock, call, arglist)                \
104         ((*posix_clocks[clock].call) arglist)

Where this is:

60 #define MAX_CLOCKS      (CLOCK_MONOTONIC+1)
62 static struct kclock    posix_clocks[MAX_CLOCKS];

And CLOCK_MONOTONIC is 4 according to time.h. This means that you can dereference that array to point to a location out of its bounds (what about timerid = 0x80000000 ? (again, I haven’t test anything yet)) and force it to execute whatever it is contained at offset:


Once again, I have to clarify that everything you read in here is 100% result of source code studying, I haven’t test any of the above assumptions. This post was made in just 28 minutes because I found it a really cool bug and it worth a post as soon as possible. Anyway, back to work! :)

Written by xorl

March 23, 2009 at 13:44

4 Responses

Subscribe to comments with RSS.

  1. :p 28 minutes which took me more than 2 hours to figure out!


    March 23, 2009 at 18:32

  2. hehe, that is exactly how we exploited the bug, nice description, is perhaps better than the one i gave during the presentation at cansec’09 (where we disclosed this bug).


    March 24, 2009 at 19:52

  3. The fully working exploit for this bug was published hours before your post at both FD and milw0rm (http://www.milw0rm.com/exploits/8261). It is rather unfortunate that the released FreeBSD advisory does not properly credit the discovery of the vulnerability.


    March 25, 2009 at 19:41

  4. argp: I posted this exactly after your “i’m looking at it right now” on #social which was about two hours before seeing this bug on FD (by redsand on #social once again).

    mu-b: The funny part is that I did this post based 100% on the source code, I had no fbsd box to test my assumptions :P


    March 26, 2009 at 11:37

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 )

Google+ photo

You are commenting using your Google+ 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 )


Connecting to %s