xorl %eax, %eax

CVE-2008-5395: Linux kernel PA-RISC do_show_stack() Crash

leave a comment »

This was a really interesting bug. It was fixed on 2.6.28-rc7 release of the Linux kernel and reported by Helge Deller on 20 July 2008, who also provided the code which triggered the bug at his report. It affects Linux kernel prior to 2.6.28-rc7 on 32-bit and 64-bit PA-RISC systems. Here I’ll be using 2.6.27 to demonstrate this…

498 void handle_interruption(int code, struct pt_regs *regs)
499 {
500        unsigned long fault_address = 0;
501        unsigned long fault_space = 0;
502        struct siginfo si;
     ...
543        switch(code) {
544
545        case  1:
546                /* High-priority machine check (HPMC) */
     ...
765        default:
766                if (user_mode(regs)) {
767 #ifdef PRINT_USER_FAULTS
768                        printk(KERN_DEBUG "\nhandle_interruption() pid=%d command='%s'\n",
769                            task_pid_nr(current), current->comm);
770                        show_regs(regs);
771 #endif
772                        /* SIGBUS, for lack of a better one. */
      ...
819        do_page_fault(regs, code, fault_address);
820 }


This nice little function can be found at arch/parisc/kernel/traps.c and this void routine is quiet simple. It handles the interruption based on the code passed to it. The default case (line 765) if compiled with PRINT_USER_FAULTS which is simply:

47 #define PRINT_USER_FAULTS /* (turn this on if you want user faults to be */
48                           /*  dumped to the console via printk)          */


it will print out the pid and command at lines 768-769 and then call show_regs(). This function is also part of the same source code file and specifically, it’s this:

122 void show_regs(struct pt_regs *regs)
123 {
124        int i;
125        char *level;
126        unsigned long cr30, cr31;
127
128        level = user_mode(regs) ? KERN_DEBUG : KERN_CRIT;
129
130        print_gr(level, regs);
131
132        for (i = 0; i < 8; i += 4)
133                PRINTREGS(level, regs->sr, "sr", RFMT, i);
134
135        if (user_mode(regs))
136                print_fr(level, regs);
137
138        cr30 = mfctl(30);
139        cr31 = mfctl(31);
140        printk("%s\n", level);
141        printk("%sIASQ: " RFMT " " RFMT " IAOQ: " RFMT " " RFMT "\n",
142               level, regs->iasq[0], regs->iasq[1], regs->iaoq[0], regs->iaoq[1]);
143        printk("%s IIR: %08lx    ISR: " RFMT "  IOR: " RFMT "\n",
144               level, regs->iir, regs->isr, regs->ior);
145        printk("%s CPU: %8d   CR30: " RFMT " CR31: " RFMT "\n",
146               level, current_thread_info()->cpu, cr30, cr31);
147        printk("%s ORIG_R28: " RFMT "\n", level, regs->orig_r28);
148        printk(level);
149        print_symbol(" IAOQ[0]: %s\n", regs->iaoq[0]);
150        printk(level);
151        print_symbol(" IAOQ[1]: %s\n", regs->iaoq[1]);
152        printk(level);
153        print_symbol(" RP(r2): %s\n", regs->gr[2]);
154
155        parisc_show_stack(current, NULL, regs);
156 }


What this does is more or less print registers’ values using printk() API and at last (line 155) invoking parisc_show_stack() passing to it the task_struct for the current process as well as its registers’ pt_regs structure. Now, if we move into this function we’re gonna see this:

189 void parisc_show_stack(struct task_struct *task, unsigned long *sp,
190         struct pt_regs *regs)
191 {
192        struct unwind_frame_info info;
193        struct task_struct *t;
194
195        t = task ? task : current;
196        if (regs) {
197                unwind_frame_init(&info, t, regs);
198                goto show_stack;
199        }
200
201        if (t == current) {
202                unsigned long sp;
203
204 HERE:
205                asm volatile ("copy %%r30, %0" : "=r"(sp));
206                {
207                        struct pt_regs r;
208
209                        memset(&r, 0, sizeof(struct pt_regs));
210                        r.iaoq[0] = (unsigned long)&&HERE;
211                        r.gr[2] = (unsigned long)__builtin_return_address(0);
212                        r.gr[30] = sp;
213
214                        unwind_frame_init(&info, current, &r);
215                }
216        } else {
217                unwind_frame_init_from_blocked_task(&info, t);
218        }
219
220 show_stack:
221        do_show_stack(&info);
222 }


It simply attempts to unwind the stack since regs is not NULL and at line 196 it will call unwind_frame_init() and immediately jump to show_stack label which invokes do_show_stack(). This last function, also part of traps.c is used to print out a nice stack backtrace. Here it is:

166 static void do_show_stack(struct unwind_frame_info *info)
167 {
168        int i = 1;
169
170        printk(KERN_CRIT "Backtrace:\n");
171        while (i <= 16) {
172                if (unwind_once(info) < 0 || info->ip == 0)
173                        break;
174
175                if (__kernel_text_address(info->ip)) {
176                        printk("%s [<" RFMT ">] ", (i&0x3)==1 ? KERN_CRIT : "", info->ip);
177 #ifdef CONFIG_KALLSYMS
178                        print_symbol("%s\n", info->ip);
179 #else
180                        if ((i & 0x03) == 0)
181                                printk("\n");
182 #endif
183                        i++;
184                }
185        }
186        printk("\n");
187 }


But since the stack has user space addresses, the call to unwind_frame_init() and consequently to do_show_stack() might lead to a crash. In addition, as Helge Deller pointed out that it’s useless to attempt to unwind a user space process stack. To patch this, they updated show_regs() to check whether it’s dealing with a user space process or not like this:

 void show_regs(struct pt_regs *regs)
 {
-       int i;
+       int i, user;
        char *level;
        unsigned long cr30, cr31;

-       level = user_mode(regs) ? KERN_DEBUG : KERN_CRIT;
+       user = user_mode(regs);
+       level = user ? KERN_DEBUG : KERN_CRIT;
         print_gr(level, regs);

         for (i = 0; i < 8; i += 4)
                PRINTREGS(level, regs->sr, "sr", RFMT, i);

-       if (user_mode(regs))
+       if (user)
                print_fr(level, regs);


They also updated the printk() calls to print the appropriate registers and call parisc_show_stack() only if the current process is not from user space like this:

        printk("%s ORIG_R28: " RFMT "\n", level, regs->orig_r28);
-       printk(level);
-       print_symbol(" IAOQ[0]: %s\n", regs->iaoq[0]);
-       printk(level);
-       print_symbol(" IAOQ[1]: %s\n", regs->iaoq[1]);
-       printk(level);
-       print_symbol(" RP(r2): %s\n", regs->gr[2]);
-
-       parisc_show_stack(current, NULL, regs);
+
+       if (user) {
+               printk("%s IAOQ[0]: " RFMT "\n", level, regs->iaoq[0]);
+               printk("%s IAOQ[1]: " RFMT "\n", level, regs->iaoq[1]);
+               printk("%s RP(r2): " RFMT "\n", level, regs->gr[2]);
+       } else {
+               printk("%s IAOQ[0]: %pS\n", level, (void *) regs->iaoq[0]);
+               printk("%s IAOQ[1]: %pS\n", level, (void *) regs->iaoq[1]);
+               printk("%s RP(r2): %pS\n", level, (void *) regs->gr[2]);
+
+               parisc_show_stack(current, NULL, regs);
+       }
 }


The prototype of parisc_show_stack() was also changed to be qualified as static:

-void parisc_show_stack(struct task_struct *task, unsigned long *sp,
+static void parisc_show_stack(struct task_struct *task, unsigned long *sp,
        struct pt_regs *regs)


Finally, do_show_stack() was patched to remove the CONFIG_KALLSYMS clause and also change the instruction pointer printk() like this:

                if (__kernel_text_address(info->ip)) {
-                       printk("%s [<" RFMT ">] ", (i&0x3)==1 ? KERN_CRIT : "", info->ip);
-#ifdef CONFIG_KALLSYMS
-                       print_symbol("%s\n", info->ip);
-#else
-                       if ((i & 0x03) == 0)
-                               printk("\n");
-#endif
+                       printk(KERN_CRIT " [<" RFMT ">] %pS\n",
+                               info->ip, (void *) info->ip);
                        i++;
                }
        }
-       printk("\n");
+       printk(KERN_CRIT "\n");
 }


That’s all with this nice PA-RISC bug. :-)

Written by xorl

April 12, 2009 at 14:20

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