]> git.hungrycats.org Git - linux/commitdiff
[PATCH] ldt-fix-2.5.32-A3
authorIngo Molnar <mingo@elte.hu>
Fri, 30 Aug 2002 08:45:53 +0000 (01:45 -0700)
committerLinus Torvalds <torvalds@home.transmeta.com>
Fri, 30 Aug 2002 08:45:53 +0000 (01:45 -0700)
this is an updated version of the LDT fixes. It fixes the following kinds
of problems:

 - fix a possible gcc optimization causing a race causing the loading of a
   corrupt LDT descriptor upon context switch. [this fix got simplified
   over previous versions.]

 - remove an unconditional OOM printk, and there's no need to set ->size
   in the OOM path.

 - fix preemption bugs, load_LDT()/clear_LDT() was not preemption-safe,
   when it was used outside of spinlocks.

the context-switch race is the following. 'LDT modification' is the
following operation: the seg->ldt pointer is modified, then seg->size is
modified. In theory gcc is free to reschedule the two modifications, and
first modify ->size, then ->ldt. Thus if this modification is not
synchronized with context-switches, another thread might see a temporary
state of the new ->size [which was increased], but still the old pointer.
Ie.:

CPU0 CPU1

pc->size = newsize;
load_LDT(); // (oldptr, newsize)
pc->ldt = newptr;

the corrupt LDT is loaded until the SMP cross-call is sent, leaving the
window open for many usecs.

the fix is to put a wmb() after ->ldt modifications. [this is also in
preparation of not-write-ordered SMP x86 designs.]

arch/i386/kernel/ldt.c
include/asm-i386/desc.h
include/asm-i386/mmu.h
include/asm-i386/mmu_context.h

index 3d13f4692619bfb61363bafb018c0d3884e7d9c4..760b4c201db6a264db2e4e4921150064c5603e27 100644 (file)
@@ -49,17 +49,20 @@ static int alloc_ldt(mm_context_t *pc, int mincount, int reload)
                memcpy(newldt, pc->ldt, oldsize*LDT_ENTRY_SIZE);
        oldldt = pc->ldt;
        memset(newldt+oldsize*LDT_ENTRY_SIZE, 0, (mincount-oldsize)*LDT_ENTRY_SIZE);
-       wmb();
        pc->ldt = newldt;
+       wmb();
        pc->size = mincount;
+       wmb();
+
        if (reload) {
                load_LDT(pc);
 #ifdef CONFIG_SMP
-               if (current->mm->cpu_vm_mask != (1<<smp_processor_id()))
+               preempt_disable();
+               if (current->mm->cpu_vm_mask != (1 << smp_processor_id()))
                        smp_call_function(flush_ldt, 0, 1, 1);
+               preempt_enable();
 #endif
        }
-       wmb();
        if (oldsize) {
                if (oldsize*LDT_ENTRY_SIZE > PAGE_SIZE)
                        vfree(oldldt);
@@ -72,11 +75,8 @@ static int alloc_ldt(mm_context_t *pc, int mincount, int reload)
 static inline int copy_ldt(mm_context_t *new, mm_context_t *old)
 {
        int err = alloc_ldt(new, old->size, 0);
-       if (err < 0) {
-               printk(KERN_WARNING "ldt allocation failed\n");
-               new->size = 0;
+       if (err < 0)
                return err;
-       }
        memcpy(new->ldt, old->ldt, old->size*LDT_ENTRY_SIZE);
        return 0;
 }
index 1fb6ee840853c01c1d89937f7c5184526272a6f0..1c2ef24540e7c593f21598302bfb2903292b97a6 100644 (file)
@@ -86,14 +86,17 @@ static inline void load_TLS(struct thread_struct *t, unsigned int cpu)
 
 static inline void clear_LDT(void)
 {
-       set_ldt_desc(smp_processor_id(), &default_ldt[0], 5);
+       int cpu = get_cpu();
+
+       set_ldt_desc(cpu, &default_ldt[0], 5);
        load_LDT_desc();
+       put_cpu();
 }
 
 /*
  * load one particular LDT into the current CPU
  */
-static inline void load_LDT (mm_context_t *pc)
+static inline void load_LDT_nolock(mm_context_t *pc, int cpu)
 {
        void *segments = pc->ldt;
        int count = pc->size;
@@ -103,10 +106,17 @@ static inline void load_LDT (mm_context_t *pc)
                count = 5;
        }
                
-       set_ldt_desc(smp_processor_id(), segments, count);
+       set_ldt_desc(cpu, segments, count);
        load_LDT_desc();
 }
 
+static inline void load_LDT(mm_context_t *pc)
+{
+       int cpu = get_cpu();
+       load_LDT_nolock(pc, cpu);
+       put_cpu();
+}
+
 #endif /* !__ASSEMBLY__ */
 
 #endif
index 8ca14eb8c5a26e8cedd59efa387b537bce28e051..86d1064f6988bb0cfcfe6d07eeee69dbfea89df0 100644 (file)
@@ -10,7 +10,7 @@
 typedef struct { 
        int size;
        struct semaphore sem;
-       void *  ldt;
+       void *ldt;
 } mm_context_t;
 
 #endif
index 0da1f3a11983ed2b0db017c7180bd7f482d6473a..2b64727e67bd0e7773c6470bada8d11f69e5ddc7 100644 (file)
@@ -44,7 +44,7 @@ static inline void switch_mm(struct mm_struct *prev, struct mm_struct *next, str
                 * load the LDT, if the LDT is different:
                 */
                if (unlikely(prev->context.ldt != next->context.ldt))
-                       load_LDT(&next->context);
+                       load_LDT_nolock(&next->context, cpu);
        }
 #ifdef CONFIG_SMP
        else {
@@ -56,7 +56,7 @@ static inline void switch_mm(struct mm_struct *prev, struct mm_struct *next, str
                         * tlb flush IPI delivery. We must reload %cr3.
                         */
                        load_cr3(next->pgd);
-                       load_LDT(&next->context);
+                       load_LDT_nolock(&next->context, cpu);
                }
        }
 #endif