lkml.org 
[lkml]   [1998]   [Jul]   [6]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
Patch in this message
/
Date
From
Subjectrepost: patch for 2.1.108 LDT problems
The attached patch is an update for 2.1.108 of the previous patch to fix
various LDT-related problems.

The patch ensures that each clone task using an LDT gets its own slot in
the GDT, in case the prior task exits first. It also adds some sanity
checks when LDT entries are being changed, and improves the register
dump routine to display additional information.

The patch is well tested and is necessary to avoid easily-triggered
oopses when running apps that use LDTs.

Regards,
Bill--- linux-2.1.108/arch/i386/kernel/process.c.old Fri Jul 3 10:32:20 1998
+++ linux-2.1.108/arch/i386/kernel/process.c Fri Jul 3 12:14:52 1998
@@ -427,15 +427,20 @@

void release_segments(struct mm_struct *mm)
{
- void * ldt;
+ void * ldt = mm->segments;
+ int nr;

/* forget local segments */
__asm__ __volatile__("movl %w0,%%fs ; movl %w0,%%gs ; lldt %w0"
: /* no outputs */
: "r" (0));
current->tss.ldt = 0;
+ /*
+ * Set the GDT entry back to the default.
+ */
+ nr = current->tarray_ptr - &task[0];
+ set_ldt_desc(gdt+(nr<<1)+FIRST_LDT_ENTRY, &default_ldt, 1);

- ldt = mm->segments;
if (ldt) {
mm->segments = NULL;
vfree(ldt);
@@ -480,20 +485,31 @@
{
}

+/*
+ * If new_mm is NULL, we're being called to set up the LDT descriptor
+ * for a clone task. Each clone must have a separate entry in the GDT.
+ */
void copy_segments(int nr, struct task_struct *p, struct mm_struct *new_mm)
{
- int ldt_size = 1;
- void * ldt = &default_ldt;
struct mm_struct * old_mm = current->mm;
+ void * old_ldt = old_mm->segments, * ldt = old_ldt;
+ int ldt_size = LDT_ENTRIES;

p->tss.ldt = _LDT(nr);
- if (old_mm->segments) {
- new_mm->segments = vmalloc(LDT_ENTRIES*LDT_ENTRY_SIZE);
- if (new_mm->segments) {
- ldt = new_mm->segments;
- ldt_size = LDT_ENTRIES;
- memcpy(ldt, old_mm->segments, LDT_ENTRIES*LDT_ENTRY_SIZE);
+ if (old_ldt) {
+ if (new_mm) {
+ ldt = vmalloc(LDT_ENTRIES*LDT_ENTRY_SIZE);
+ new_mm->segments = ldt;
+ if (!ldt) {
+ printk(KERN_WARNING "ldt allocation failed\n");
+ goto no_ldt;
+ }
+ memcpy(ldt, old_ldt, LDT_ENTRIES*LDT_ENTRY_SIZE);
}
+ } else {
+ no_ldt:
+ ldt = &default_ldt;
+ ldt_size = 1;
}
set_ldt_desc(gdt+(nr<<1)+FIRST_LDT_ENTRY, ldt, ldt_size);
}
--- linux-2.1.108/arch/i386/kernel/ldt.c.old Tue May 5 11:23:28 1998
+++ linux-2.1.108/arch/i386/kernel/ldt.c Fri Jul 3 12:14:52 1998
@@ -16,6 +16,8 @@
#include <asm/system.h>
#include <asm/ldt.h>

+/* #define LDT_PARANOIA 1 */
+
static int read_ldt(void * ptr, unsigned long bytecount)
{
void * address = current->mm->segments;
@@ -33,23 +35,48 @@
return copy_to_user(ptr, address, size) ? -EFAULT : size;
}

-static int write_ldt(void * ptr, unsigned long bytecount, int oldmode)
+/*
+ * Define masks for error checking
+ */
+#define SEG_NEED_NZ 1
+#define SEG_IS_ZERO 1
+#define SEG_NEED_VALID 2
+#define SEG_IS_INVALID 2
+#define SEG_NEED_CODE 4
+#define SEG_IS_NOTCODE 4
+#define SEG_NEED_READ 8
+#define SEG_IS_NOTREAD 8
+#define SEG_NEED_WRITE 16
+#define SEG_IS_NOTWRITE 16
+
+static int write_ldt(void * ptr, unsigned long bytecount, int oldmode,
+ struct pt_regs *regs)
{
+ struct mm_struct * mm = current->mm;
+ void * ldt;
+ __u32 entry_1, entry_2, *lp;
+ __u16 selector, reg_fs, reg_gs;
+ unsigned int require = 0, attrib = 0;
+ char * which;
+ int error;
struct modify_ldt_ldt_s ldt_info;
- unsigned long *lp;
- struct mm_struct * mm;
- int error, i;

+ error = -EINVAL;
if (bytecount != sizeof(ldt_info))
- return -EINVAL;
- error = copy_from_user(&ldt_info, ptr, sizeof(ldt_info));
- if (error)
- return -EFAULT;
-
- if ((ldt_info.contents == 3 && (oldmode || ldt_info.seg_not_present == 0)) || ldt_info.entry_number >= LDT_ENTRIES)
- return -EINVAL;
-
- mm = current->mm;
+ goto out;
+ error = -EFAULT;
+ if (copy_from_user(&ldt_info, ptr, sizeof(ldt_info)))
+ goto out;
+
+ error = -EINVAL;
+ if (ldt_info.entry_number >= LDT_ENTRIES)
+ goto out;
+ if (ldt_info.contents == 3) {
+ if (oldmode)
+ goto out;
+ if (ldt_info.seg_not_present == 0)
+ goto out;
+ }

/*
* Horrible dependencies! Try to get rid of this. This is wrong,
@@ -62,60 +89,149 @@
* For no good reason except historical, the GDT index of the LDT
* is chosen to follow the index number in the task[] array.
*/
- if (!mm->segments) {
- for (i=1 ; i<NR_TASKS ; i++) {
- if (task[i] == current) {
- if (!(mm->segments = (void *) vmalloc(LDT_ENTRIES*LDT_ENTRY_SIZE)))
- return -ENOMEM;
- memset(mm->segments, 0, LDT_ENTRIES*LDT_ENTRY_SIZE);
- set_ldt_desc(gdt+(i<<1)+FIRST_LDT_ENTRY, mm->segments, LDT_ENTRIES);
- load_ldt(i);
- }
+ ldt = mm->segments;
+ if (!ldt) {
+ error = -ENOMEM;
+ ldt = vmalloc(LDT_ENTRIES*LDT_ENTRY_SIZE);
+ if (!ldt)
+ goto out;
+ memset(ldt, 0, LDT_ENTRIES*LDT_ENTRY_SIZE);
+ /*
+ * Make sure someone else hasn't allocated it for us ...
+ */
+ if (!mm->segments) {
+ int i = current->tarray_ptr - &task[0];
+ mm->segments = ldt;
+ set_ldt_desc(gdt+(i<<1)+FIRST_LDT_ENTRY, ldt, LDT_ENTRIES);
+ load_ldt(i);
+ if (mm->count > 1)
+ printk(KERN_WARNING
+ "LDT allocated for cloned task!\n");
+ } else {
+ vfree(ldt);
}
}
+
+ /*
+ * Check whether the entry to be changed is currently in use.
+ * If it is, we may need extra validation checks in case the
+ * kernel is forced to save and restore the selector.
+ *
+ * Note: we check the fs and gs values as well, as these are
+ * loaded by the signal code and during a task switch.
+ */
+ selector = (ldt_info.entry_number << 3) | 4;
+ __asm__("movw %%fs,%0" : "=r"(reg_fs));
+ __asm__("movw %%gs,%0" : "=r"(reg_gs));
+ which = NULL;
+
+ if (selector == (__u16) (regs->xcs & ~3)) {
+ which = "CS";
+ require = SEG_NEED_VALID | SEG_NEED_CODE | SEG_NEED_NZ;
+ }
+ else if (selector == (__u16) (regs->xss & ~3)) {
+ which = "SS";
+ require = SEG_NEED_VALID | SEG_NEED_WRITE | SEG_NEED_NZ;
+ }
+ else if (selector == (__u16) (regs->xds & ~3)) {
+ which = "DS";
+ require = SEG_NEED_VALID | SEG_NEED_READ | SEG_NEED_NZ;
+ }
+ else if (selector == (__u16) (regs->xes & ~3)) {
+ which = "ES";
+ require = SEG_NEED_VALID | SEG_NEED_READ | SEG_NEED_NZ;
+ }
+ else if (selector == (reg_fs & ~3)) {
+ which = "FS";
+ require = SEG_NEED_VALID | SEG_NEED_READ | SEG_NEED_NZ;
+ }
+ else if (selector == (reg_gs & ~3)) {
+ which = "GS";
+ require = SEG_NEED_VALID | SEG_NEED_READ | SEG_NEED_NZ;
+ }
+ if (which)
+ printk(KERN_WARNING "write_ldt: selector %04x in use by %s\n",
+ selector, which);

- lp = (unsigned long *) (LDT_ENTRY_SIZE * ldt_info.entry_number + (unsigned long) mm->segments);
+ lp = (__u32 *) ((selector & ~7) + (char *) ldt);
+
/* Allow LDTs to be cleared by the user. */
- if (ldt_info.base_addr == 0 && ldt_info.limit == 0
- && (oldmode ||
- ( ldt_info.contents == 0
- && ldt_info.read_exec_only == 1
- && ldt_info.seg_32bit == 0
- && ldt_info.limit_in_pages == 0
- && ldt_info.seg_not_present == 1
- && ldt_info.useable == 0 )) ) {
- *lp = 0;
- *(lp+1) = 0;
- return 0;
+ if (ldt_info.base_addr == 0 && ldt_info.limit == 0) {
+ if (oldmode ||
+ (ldt_info.contents == 0 &&
+ ldt_info.read_exec_only == 1 &&
+ ldt_info.seg_32bit == 0 &&
+ ldt_info.limit_in_pages == 0 &&
+ ldt_info.seg_not_present == 1 &&
+ ldt_info.useable == 0 )) {
+ entry_1 = 0;
+ entry_2 = 0;
+ attrib |= SEG_IS_ZERO;
+ goto out_check;
+ }
}
- *lp = ((ldt_info.base_addr & 0x0000ffff) << 16) |
+
+ entry_1 = ((ldt_info.base_addr & 0x0000ffff) << 16) |
(ldt_info.limit & 0x0ffff);
- *(lp+1) = (ldt_info.base_addr & 0xff000000) |
- ((ldt_info.base_addr & 0x00ff0000)>>16) |
+ entry_2 = (ldt_info.base_addr & 0xff000000) |
+ ((ldt_info.base_addr & 0x00ff0000) >> 16) |
(ldt_info.limit & 0xf0000) |
- (ldt_info.contents << 10) |
((ldt_info.read_exec_only ^ 1) << 9) |
+ (ldt_info.contents << 10) |
+ ((ldt_info.seg_not_present ^ 1) << 15) |
(ldt_info.seg_32bit << 22) |
(ldt_info.limit_in_pages << 23) |
- ((ldt_info.seg_not_present ^1) << 15) |
0x7000;
- if (!oldmode) *(lp+1) |= (ldt_info.useable << 20);
- return 0;
+ if (!oldmode)
+ entry_2 |= (ldt_info.useable << 20);
+
+ /* N.B. perform validation checks */
+ if (ldt_info.read_exec_only)
+ attrib |= SEG_IS_NOTWRITE;
+ if (!(ldt_info.contents & MODIFY_LDT_CONTENTS_CODE))
+ attrib |= SEG_IS_NOTCODE;
+
+ /*
+ * Check whether any attributes conflict with requirements.
+ */
+out_check:
+ if (require & attrib)
+ goto out_busy;
+
+ /* OK to change the entry ... */
+ *lp = entry_1;
+ *(lp+1) = entry_2;
+ error = 0;
+#ifdef LDT_PARANOIA
+printk("write_ldt: loaded %04x, entry=(%08x, %08x)\n",
+selector, entry_1, entry_2);
+#endif
+out:
+ return error;
+
+out_busy:
+ error = -EBUSY;
+ printk("write_ldt: can't change %04x to (%08x, %08x)\n",
+ selector, entry_1, entry_2);
+ goto out;
}

asmlinkage int sys_modify_ldt(int func, void *ptr, unsigned long bytecount)
{
- int ret;
+ int ret = -ENOSYS;

lock_kernel();
- if (func == 0)
+ switch (func) {
+ case 0:
ret = read_ldt(ptr, bytecount);
- else if (func == 1)
- ret = write_ldt(ptr, bytecount, 1);
- else if (func == 0x11)
- ret = write_ldt(ptr, bytecount, 0);
- else
- ret = -ENOSYS;
+ break;
+ case 1:
+ ret = write_ldt(ptr, bytecount, 1, (struct pt_regs *) &func);
+ break;
+ case 0x11:
+ ret = write_ldt(ptr, bytecount, 0, (struct pt_regs *) &func);
+ break;
+ }
unlock_kernel();
return ret;
}
--- linux-2.1.108/arch/i386/kernel/traps.c.old Fri Jul 3 10:32:20 1998
+++ linux-2.1.108/arch/i386/kernel/traps.c Fri Jul 3 12:14:52 1998
@@ -66,6 +66,10 @@
unlock_kernel(); \
}

+/*
+ * N.B. The use of %fs in these macros can cause problems
+ * if the selector is invalid ... use another register?
+ */
#define get_seg_byte(seg,addr) ({ \
register unsigned char __res; \
__asm__("pushl %%fs;movl %%ax,%%fs;movb %%fs:%2,%%al;popl %%fs" \
@@ -83,6 +87,21 @@
__asm__("movl %%fs,%%ax":"=a" (__res):); \
__res;})

+#define _gs() ({ \
+register unsigned short __res; \
+__asm__("movl %%gs,%%ax":"=a" (__res):); \
+__res;})
+
+#define _ldt() ({ \
+register unsigned short __res; \
+__asm__("sldt %%ax":"=a" (__res):); \
+__res;})
+
+#define _tr() ({ \
+register unsigned short __res; \
+__asm__("str %%ax":"=a" (__res):); \
+__res;})
+
void page_exception(void);

asmlinkage void divide_error(void);
@@ -129,17 +148,23 @@
esp = regs->esp;
ss = regs->xss & 0xffff;
}
+
printk("CPU: %d\nEIP: %04x:[<%08lx>]\nEFLAGS: %08lx\n",
smp_processor_id(), 0xffff & regs->xcs, regs->eip, regs->eflags);
printk("eax: %08lx ebx: %08lx ecx: %08lx edx: %08lx\n",
regs->eax, regs->ebx, regs->ecx, regs->edx);
printk("esi: %08lx edi: %08lx ebp: %08lx esp: %08lx\n",
regs->esi, regs->edi, regs->ebp, esp);
- printk("ds: %04x es: %04x ss: %04x\n",
- regs->xds & 0xffff, regs->xes & 0xffff, ss);
- store_TR(i);
- printk("Process %s (pid: %d, process nr: %d, stackpage=%08lx)\nStack: ",
- current->comm, current->pid, 0xffff & i, 4096+(unsigned long)current);
+ printk("ds: %04x es: %04x ss: %04x fs: %04x gs: %04x\n",
+ regs->xds & 0xffff, regs->xes & 0xffff, ss, _fs(), _gs());
+
+ i = (current->tarray_ptr - &task[0]);
+ printk("Process %s (pid: %d, process nr: %d, stackpage=%08lx)\n",
+ current->comm, current->pid, i, 4096+(unsigned long)current);
+ printk("ldt: %04x tss.ldt: %04x LDT: %04x tr: %04x TSS: %04x\n",
+ _ldt(), current->tss.ldt, (__u16)_LDT(i), _tr(), (__u16)_TSS(i));
+
+ printk("Stack: ");
stack = (unsigned long *) esp;
for(i=0; i < kstack_depth_to_print; i++) {
if (((long) stack & 4095) == 0)
--- linux-2.1.108/kernel/fork.c.old Fri Jul 3 10:32:34 1998
+++ linux-2.1.108/kernel/fork.c Fri Jul 3 11:49:01 1998
@@ -308,6 +308,10 @@

if (clone_flags & CLONE_VM) {
mmget(current->mm);
+ /*
+ * Set up the LDT descriptor for the clone task.
+ */
+ copy_segments(nr, tsk, NULL);
SET_PAGE_DIR(tsk, current->mm->pgd);
return 0;
}
\
 
 \ /
  Last update: 2005-03-22 13:43    [from the cache]
©2003-2011 Jasper Spaans