xorl %eax, %eax

FreeBSD linuxulator Invalid Pointer Access

with one comment

This bug was reported by Gleb Kurtsou of FreeBSD Project on 27 December 2009 and the buggy code resides at compat/linux/linux_misc.c as you can read below…

int
linux_getppid(struct thread *td, struct linux_getppid_args *args)
{
         struct linux_emuldata *em;
         struct proc *p, *pp;

This routine is used by the Linux emulator to provide an implementation of getppid(2) function. Some information that will help us understand the bug now, the ‘proc’ structure includes the following members (as seen in sys/proc.h):

/* 
 * Process structure.
 */
struct proc {
   ...
        struct proc     *p_pptr;        /* (c + e) Pointer to parent process. */
   ...
        struct sysentvec *p_sysent;     /* (b) Syscall dispatch info. */
   ...
        void            *p_emuldata;    /* (c) Emulator state data. */
   ...
};

If we move back to linux_getppid() we’ll see that ‘p’ pointer is initialized in the following way…

         /* find the group leader */
         p = pfind(em->shared->group_pid);
 
         if (p == NULL) {
    ...   
         pp = p->p_pptr;         /* switch to parent */
    ...
         /* if its also linux process */
         if (pp->p_sysent == &elf_linux_sysvec) {
                 em = em_find(pp, EMUL_DONTLOCK);
                 KASSERT(em != NULL, ("getppid: parent emuldata not found.\n"));
 
                 td->td_retval[0] = em->shared->group_pid;
         } else
                 td->td_retval[0] = pp->p_pid;
    ...   
         return (0);
}

So, it uses pfind() to retrieve the group leader process and store it inside ‘p’ pointer. The ‘pp’ pointer contains the pointer of the parent process as you can read from the above code snippet and it is used to check if its system call dispatch info pointer points to ‘elf_linux_sysvec’ which means that this is an ELF Linux binary file. If this is the case, it will invoke em_find() which is a really simple routine located at compat/linux/linux_emul.c that is used to return the emulator state data pointer as you can see in the code below.

/* this returns locked reference to the emuldata entry (if found) */
struct linux_emuldata *
em_find(struct proc *p, int locked)
{
        struct linux_emuldata *em;
 
        if (locked == EMUL_DOLOCK)
                EMUL_LOCK(&emul_lock);

        em = p->p_emuldata;

        if (em == NULL && locked == EMUL_DOLOCK)
                EMUL_UNLOCK(&emul_lock);
 
        return (em);
}

Back to linux_getppid(), if the returned pointer is NULL, it will issue a KASSERT() that eventually leads to a kernel panic. Otherwise, it will update the thread’s return value to either the group PID or its parent PID which is the purpose of getppid(2) system call.
However, if we have a look at the emulated exiting system calls we’ll see that they do not update the ’em’ pointer after freeing its space, for example here is a code snippet of linux_proc_exit() as seen in compat/linux/linux_emul.c.

void
linux_proc_exit(void *arg __unused, struct proc *p)
{
        struct linux_emuldata *em;
        int error;
        struct thread *td = FIRST_THREAD_IN_PROC(p);
        int *child_clear_tid;
        struct proc *q, *nq;
 
        if (__predict_true(p->p_sysent != &elf_linux_sysvec))
                 return;
 
        release_futexes(p);
 
        /* find the emuldata */
        em = em_find(p, EMUL_DOLOCK);
   ...
        /* clean the stuff up */
        free(em, M_LINUX);
   ...
}

So, it basically retrieves the emulator data state pointer and frees it. Since its value was not updated to NULL, a call to linux_getppid() attempting to access this emulator data state pointer will result in an invalid pointer access inside em_find() since this memory area has already been freed by linux_proc_exit(). To fix this inconsistency between the exit() and the getppid() emulated system calls, the following patch was applied.
First of all, linux_getppid() was changed like this:

         /* if its also linux process */
-        if (pp->p_sysent == &elf_linux_sysvec) {
-                em = em_find(pp, EMUL_DONTLOCK);
-                KASSERT(em != NULL, ("getppid: parent emuldata not found.\n"));
-
+        if (pp->p_sysent == &elf_linux_sysvec &&
+            (em = em_find(pp, EMUL_DONTLOCK)) != NULL) {
                 td->td_retval[0] = em->shared->group_pid;

This is more or less the same code apart that they removed the KASSERT() call. Now, linux_proc_exit() and linux_proc_exec() which are able to free the ’em’ pointer’s space were updated in manner similar to this one:

                         free(em, M_LINUX);
+                        p->p_emuldata = NULL;
                         return;

Using this, em_find() will not result in an invalid pointer access of the previously freed pointer.

Written by xorl

December 31, 2009 at 19:59

Posted in bugs, freebsd

One Response

Subscribe to comments with RSS.

  1. you kool!

    Anh K. Huynh

    January 1, 2010 at 03:07


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